feat: consolidate docs, backend/session infra, and settings updates
This commit is contained in:
215
assets/js/sidebar-utils.js
Normal file
215
assets/js/sidebar-utils.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* WP Agentic Writer - Utility Functions
|
||||
*
|
||||
* Pure utility functions with no React dependencies
|
||||
* These are shared utilities that can be used by any script
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
*/
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
const escapeHtml = (value) => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
// Normalize message content (convert objects/arrays to string)
|
||||
const normalizeMessageContent = (content) => {
|
||||
if (typeof content === 'string' || typeof content === 'number') {
|
||||
return String(content);
|
||||
}
|
||||
return JSON.stringify(content);
|
||||
};
|
||||
|
||||
// Truncate text with ellipsis
|
||||
const truncateText = (text, maxLength = 40) => {
|
||||
if (!text || text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
||||
// Convert markdown to HTML (full renderer)
|
||||
const markdownToHtml = (markdown, markdownit, DOMPurify) => {
|
||||
const raw = normalizeMessageContent(markdown);
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let rendered = '';
|
||||
if (markdownit && DOMPurify) {
|
||||
const renderer = markdownit({
|
||||
html: false,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
});
|
||||
|
||||
if (window.markdownitTaskLists) {
|
||||
renderer.use(window.markdownitTaskLists, { enabled: true, label: true, labelAfter: true });
|
||||
}
|
||||
|
||||
rendered = renderer.render(raw);
|
||||
|
||||
if (DOMPurify.sanitize) {
|
||||
rendered = DOMPurify.sanitize(rendered, {
|
||||
ADD_TAGS: ['input'],
|
||||
ADD_ATTR: ['type', 'checked', 'disabled'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
// Extract code blocks from HTML
|
||||
const extractCodeBlocks = (html) => {
|
||||
const codeBlocks = [];
|
||||
const preRegex = /<pre[^>]*><code(?:\s+class="language-([^"]*)")?>([\s\S]*?)<\/code><\/pre>/g;
|
||||
let match;
|
||||
|
||||
while ((match = preRegex.exec(html)) !== null) {
|
||||
const lang = match[1] || '';
|
||||
const code = match[2]
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'");
|
||||
codeBlocks.push({ lang, code });
|
||||
}
|
||||
|
||||
return codeBlocks;
|
||||
};
|
||||
|
||||
// Debounce function
|
||||
const debounce = (func, wait) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// Parse outline plan from AI response
|
||||
const parseOutlinePlan = (content) => {
|
||||
const sections = [];
|
||||
const lines = content.split('\n');
|
||||
|
||||
let currentSection = null;
|
||||
let currentSubsection = null;
|
||||
|
||||
lines.forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
// H2 section (## Title)
|
||||
const h2Match = trimmed.match(/^##\s+(.+)$/);
|
||||
if (h2Match) {
|
||||
if (currentSection) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
currentSection = {
|
||||
id: 'section-' + (sections.length + 1),
|
||||
title: h2Match[1].trim(),
|
||||
subsections: [],
|
||||
};
|
||||
currentSubsection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// H3 subsection (### Title)
|
||||
const h3Match = trimmed.match(/^###\s+(.+)$/);
|
||||
if (h3Match && currentSection) {
|
||||
currentSubsection = {
|
||||
id: 'subsection-' + (currentSection.subsections.length + 1),
|
||||
title: h3Match[1].trim(),
|
||||
content: '',
|
||||
};
|
||||
currentSection.subsections.push(currentSubsection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Content line
|
||||
if (currentSection) {
|
||||
if (currentSubsection) {
|
||||
currentSubsection.content += (currentSubsection.content ? '\n' : '') + trimmed;
|
||||
} else {
|
||||
if (!currentSection.content) {
|
||||
currentSection.content = trimmed;
|
||||
} else {
|
||||
currentSection.content += '\n' + trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSection) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
// Parse FAQ schema from AI response
|
||||
const parseFaqSchema = (content) => {
|
||||
const faqs = [];
|
||||
const faqBlocks = content.split(/\n\s*#{1,2}\s*Q[^\n]*\n/);
|
||||
|
||||
faqBlocks.slice(1).forEach((block) => {
|
||||
const lines = block.trim().split('\n');
|
||||
if (lines.length >= 2) {
|
||||
const question = lines[0].replace(/^[#*]+\s*/, '').trim();
|
||||
const answer = lines.slice(1).join('\n').trim();
|
||||
if (question && answer) {
|
||||
faqs.push({ question, answer });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return faqs;
|
||||
};
|
||||
|
||||
// Extract block preview from content
|
||||
const extractBlockPreview = (block) => {
|
||||
if (!block) return '';
|
||||
|
||||
const content = block.innerHTML || block.content || '';
|
||||
const text = content.replace(/<[^>]+>/g, '').trim();
|
||||
return truncateText(text, 100);
|
||||
};
|
||||
|
||||
// To text value helper
|
||||
const toTextValue = (value) => {
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
if (Array.isArray(value)) return value.map(toTextValue).join(', ');
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Export for use in other modules
|
||||
if (typeof window !== 'undefined') {
|
||||
window.WPAWUtils = {
|
||||
escapeHtml,
|
||||
normalizeMessageContent,
|
||||
truncateText,
|
||||
markdownToHtml,
|
||||
extractCodeBlocks,
|
||||
debounce,
|
||||
parseOutlinePlan,
|
||||
parseFaqSchema,
|
||||
extractBlockPreview,
|
||||
toTextValue,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user