Files
wp-agentic-writer/assets/js/sidebar-utils.js

216 lines
4.9 KiB
JavaScript

/**
* 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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
};
// 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(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/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,
};
}