Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
76 lines
2.2 KiB
JavaScript
76 lines
2.2 KiB
JavaScript
// ==========================================
|
|
// ChatPane Kit — Markdown Renderer
|
|
// ==========================================
|
|
// Wraps window.marked + window.DOMPurify with fallback.
|
|
// Independently importable utility.
|
|
//
|
|
// v0.37.10: Task list rendering, code block language extraction.
|
|
//
|
|
// Usage:
|
|
// import { renderMarkdown } from './markdown.js';
|
|
// const html = renderMarkdown('**bold** text');
|
|
|
|
let _lastInput = '';
|
|
let _lastOutput = '';
|
|
|
|
const _escMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
|
|
function _escapeHtml(text) {
|
|
return text.replace(/[&<>"']/g, c => _escMap[c]);
|
|
}
|
|
|
|
/**
|
|
* Configure marked with task list support if available.
|
|
*/
|
|
let _configured = false;
|
|
function _ensureConfigured() {
|
|
if (_configured) return;
|
|
_configured = true;
|
|
if (typeof marked === 'undefined') return;
|
|
|
|
marked.setOptions({
|
|
breaks: true,
|
|
gfm: true,
|
|
});
|
|
|
|
// Custom renderer for task list items
|
|
const renderer = new marked.Renderer();
|
|
const origListItem = renderer.listitem?.bind(renderer);
|
|
renderer.listitem = function(text, task, checked) {
|
|
if (task) {
|
|
const checkbox = checked
|
|
? '<input type="checkbox" checked disabled class="sw-task-checkbox" />'
|
|
: '<input type="checkbox" disabled class="sw-task-checkbox" />';
|
|
return '<li class="sw-task-item">' + checkbox + ' ' + text + '</li>\n';
|
|
}
|
|
if (origListItem) return origListItem(text, task, checked);
|
|
return '<li>' + text + '</li>\n';
|
|
};
|
|
|
|
marked.use({ renderer });
|
|
}
|
|
|
|
/**
|
|
* Render markdown text to sanitized HTML.
|
|
* Falls back to escaped plaintext when marked/DOMPurify unavailable.
|
|
*
|
|
* @param {string} text — raw markdown
|
|
* @returns {string} sanitized HTML
|
|
*/
|
|
export function renderMarkdown(text) {
|
|
if (!text) return '';
|
|
if (text === _lastInput) return _lastOutput;
|
|
|
|
let result;
|
|
if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
|
|
_ensureConfigured();
|
|
result = DOMPurify.sanitize(marked.parse(text));
|
|
} else {
|
|
result = _escapeHtml(text);
|
|
}
|
|
|
|
_lastInput = text;
|
|
_lastOutput = result;
|
|
return result;
|
|
}
|