- improvement of marked editor in eveai_chat_client by modernising options approach
- removal of old and obsolete HTML files - change of package.json to point to a specific version of marked
This commit is contained in:
@@ -18,9 +18,9 @@ export function renderMarkdown(text, options = {}) {
|
||||
const input = typeof text === 'string' ? text : String(text ?? '');
|
||||
if (!input) return '';
|
||||
|
||||
// Optional sidebar accent [[...]] -> <strong>...</strong> BEFORE parsing
|
||||
// Optional sidebar accent [[...]] -> keep content but mark with markdown strong (no raw HTML)
|
||||
const preprocessed = opts.sidebarAccent
|
||||
? input.replace(/\[\[(.*?)\]\]/g, '<strong>$1</strong>')
|
||||
? input.replace(/\[\[(.*?)\]\]/g, '**$1**')
|
||||
: input;
|
||||
|
||||
try {
|
||||
@@ -29,72 +29,82 @@ export function renderMarkdown(text, options = {}) {
|
||||
if (!hasMarked) {
|
||||
if (opts.consoleWarnOnFallback) {
|
||||
console.warn('renderMarkdown: Marked not available; falling back to plain text');
|
||||
console.log('renderMarkdown: input:', input);
|
||||
}
|
||||
return escapeToPlainHtml(preprocessed);
|
||||
}
|
||||
|
||||
// Configure marked options defensively
|
||||
if (typeof window.marked?.use === 'function') {
|
||||
// Create a lightweight renderer with restricted features
|
||||
const renderer = new window.marked.Renderer();
|
||||
// Use the global singleton API exposed by Marked in browser builds
|
||||
const md = window.marked;
|
||||
|
||||
// Links: target _blank + rel attributes; block unsafe protocols
|
||||
renderer.link = (href, title, text) => {
|
||||
const safeHref = sanitizeUrl(href);
|
||||
if (!safeHref) {
|
||||
return text; // drop link, keep text
|
||||
}
|
||||
const t = opts.linkTargetBlank ? ' target="_blank"' : '';
|
||||
const rel = ' rel="noopener noreferrer nofollow"';
|
||||
const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '';
|
||||
return `<a href="${escapeHtmlAttr(safeHref)}"${t}${rel}${titleAttr}>${text}</a>`;
|
||||
// One-time registration of our token-based renderer; renderer reads dynamic opts from md.__evieOptions
|
||||
if (!md.__evieRendererApplied) {
|
||||
md.use({
|
||||
renderer: {
|
||||
link(token) {
|
||||
const { href, title, text } = token;
|
||||
const cur = md.__evieOptions || {};
|
||||
const safeHref = sanitizeUrl(href);
|
||||
if (!safeHref) return text || '';
|
||||
const t = cur.linkTargetBlank ? ' target="_blank"' : '';
|
||||
const rel = ' rel="noopener noreferrer nofollow"';
|
||||
const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '';
|
||||
return `<a href="${escapeHtmlAttr(safeHref)}"${t}${rel}${titleAttr}>${text}</a>`;
|
||||
},
|
||||
image(token) {
|
||||
const { href, title, text } = token;
|
||||
const cur = md.__evieOptions || {};
|
||||
if (!cur.enableImages) return text ? escapeToPlainHtml(text) : '';
|
||||
const safeHref = sanitizeUrl(href);
|
||||
if (!safeHref) return text ? escapeToPlainHtml(text) : '';
|
||||
const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '';
|
||||
return `<img src="${escapeHtmlAttr(safeHref)}" alt="${escapeHtmlAttr(text || '')}"${titleAttr} />`;
|
||||
},
|
||||
code(token) {
|
||||
const { text } = token;
|
||||
// Render as escaped pre/code regardless of highlighting
|
||||
return `<pre><code>${escapeToPlainHtml(text)}</code></pre>`;
|
||||
},
|
||||
codespan(token) {
|
||||
const { text } = token;
|
||||
const cur = md.__evieOptions || {};
|
||||
if (!cur.allowInlineCode) return escapeToPlainHtml(text);
|
||||
return `<code>${escapeToPlainHtml(text)}</code>`;
|
||||
},
|
||||
table(token) {
|
||||
const cur = md.__evieOptions || {};
|
||||
if (cur.enableTables) return undefined; // default rendering
|
||||
const headerCells = token.header?.map(h => h.text).join(' | ') || '';
|
||||
const rowTexts = (token.rows || []).map(row => row.map(c => c.text).join(' | '));
|
||||
const all = [headerCells, ...rowTexts].filter(Boolean).join('\n');
|
||||
return `<p>${escapeToPlainHtml(all)}</p>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
md.__evieRendererApplied = true;
|
||||
}
|
||||
|
||||
// Set dynamic options per render
|
||||
md.__evieOptions = {
|
||||
allowInlineHTML: !!opts.allowInlineHTML,
|
||||
enableTables: !!opts.enableTables,
|
||||
enableBreaks: !!opts.enableBreaks,
|
||||
enableImages: !!opts.enableImages,
|
||||
enableCodeBlocks: !!opts.enableCodeBlocks,
|
||||
allowInlineCode: !!opts.allowInlineCode,
|
||||
linkTargetBlank: !!opts.linkTargetBlank
|
||||
};
|
||||
|
||||
// Images disabled: either drop or render alt text
|
||||
renderer.image = (href, title, text) => {
|
||||
if (!opts.enableImages) {
|
||||
return text ? escapeToPlainHtml(text) : '';
|
||||
}
|
||||
// If enabled in future, still sanitize
|
||||
const safeHref = sanitizeUrl(href);
|
||||
if (!safeHref) return text ? escapeToPlainHtml(text) : '';
|
||||
const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '';
|
||||
return `<img src="${escapeHtmlAttr(safeHref)}" alt="${escapeHtmlAttr(text || '')}"${titleAttr} />`;
|
||||
};
|
||||
|
||||
// Code blocks disabled
|
||||
renderer.code = (code, infostring) => {
|
||||
if (!opts.enableCodeBlocks) {
|
||||
return `<pre><code>${escapeToPlainHtml(code)}</code></pre>`; // still show as plain
|
||||
}
|
||||
return `<pre><code>${escapeToPlainHtml(code)}</code></pre>`;
|
||||
};
|
||||
|
||||
// Inline code disabled
|
||||
renderer.codespan = (code) => {
|
||||
if (!opts.allowInlineCode) {
|
||||
return escapeToPlainHtml(code);
|
||||
}
|
||||
return `<code>${escapeToPlainHtml(code)}</code>`;
|
||||
};
|
||||
|
||||
// Disallow raw HTML if not allowed
|
||||
const mOptions = {
|
||||
md.setOptions({
|
||||
gfm: true,
|
||||
breaks: !!opts.enableBreaks,
|
||||
headerIds: false,
|
||||
mangle: false,
|
||||
renderer
|
||||
};
|
||||
mangle: false
|
||||
});
|
||||
|
||||
// Table support via GFM is on by default; if disabled, override table renderers to simple paragraphs
|
||||
if (!opts.enableTables) {
|
||||
renderer.table = (header, body) => `${header}${body}`;
|
||||
}
|
||||
|
||||
const html = window.marked.parse(preprocessed, mOptions);
|
||||
|
||||
// Sanitize output
|
||||
const html = md.parse(preprocessed);
|
||||
const sanitized = sanitizeHtml(html);
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user