mirror of
https://github.com/yacy/yacy_search_server.git
synced 2026-08-02 12:44:36 -04:00
86 lines
3.3 KiB
JavaScript
86 lines
3.3 KiB
JavaScript
/*
|
|
* yacy-markdown.js
|
|
* Shared markdown rendering helpers, used by yacychat.html and LogReports_p.html.
|
|
* Renders LLM-generated markdown to sanitized HTML: marked for parsing, a
|
|
* sanitizer that strips active content (the source is model output, not trusted
|
|
* input), and highlight.js for fenced code blocks. Style rules for the rendered
|
|
* output live in env/markdown.css, scoped to the .markdown-body class.
|
|
* Requires js/marked.umd.js; js/highlight.min.js is optional.
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
const markedOptions = {
|
|
gfm: true,
|
|
breaks: true,
|
|
smartLists: true,
|
|
mangle: false,
|
|
headerIds: false
|
|
};
|
|
|
|
function escapeHTML(value) {
|
|
const div = document.createElement('div');
|
|
div.textContent = value || '';
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function sanitizeHTML(html) {
|
|
if (!html) return '';
|
|
const template = document.createElement('template');
|
|
template.innerHTML = html;
|
|
const blockedTags = new Set(['script', 'style', 'iframe', 'object', 'embed', 'link', 'meta']);
|
|
const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_ELEMENT, null);
|
|
const toRemove = [];
|
|
while (walker.nextNode()) {
|
|
const el = walker.currentNode;
|
|
if (!el || !el.tagName) continue;
|
|
const tag = el.tagName.toLowerCase();
|
|
if (blockedTags.has(tag)) {
|
|
toRemove.push(el);
|
|
continue;
|
|
}
|
|
for (const attr of Array.from(el.attributes)) {
|
|
const name = attr.name.toLowerCase();
|
|
const value = attr.value || '';
|
|
if (name.startsWith('on')) {
|
|
el.removeAttribute(attr.name);
|
|
continue;
|
|
}
|
|
if ((name === 'href' || name === 'src') && /^\s*javascript:/i.test(value)) {
|
|
el.removeAttribute(attr.name);
|
|
}
|
|
}
|
|
}
|
|
toRemove.forEach(node => node.remove());
|
|
return template.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Render markdown source to sanitized HTML; falls back to escaped plain text
|
|
* when marked is not available or parsing fails.
|
|
* optionOverrides can adjust the shared marked options per call, e.g.
|
|
* { breaks: false } for documents with hard-wrapped source lines where a
|
|
* single newline must not become a visible line break.
|
|
*/
|
|
function render(source, optionOverrides) {
|
|
const text = typeof source === 'string' ? source : '';
|
|
if (typeof marked === 'undefined') return escapeHTML(text);
|
|
try {
|
|
marked.setOptions(Object.assign({}, markedOptions, optionOverrides || {}));
|
|
return sanitizeHTML(marked.parse(text));
|
|
} catch (err) {
|
|
console.warn('Markdown rendering failed', err);
|
|
return escapeHTML(text);
|
|
}
|
|
}
|
|
|
|
/** Apply highlight.js to all fenced code blocks below the given container. */
|
|
function highlightAll(container) {
|
|
if (!container || typeof hljs === 'undefined') return;
|
|
hljs.configure({ ignoreUnescapedHTML: true });
|
|
container.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
|
|
}
|
|
|
|
global.YaCyMarkdown = { markedOptions, escapeHTML, sanitizeHTML, render, highlightAll };
|
|
})(window);
|