149 lines
4.1 KiB
JavaScript
Raw Normal View History

2025-12-04 20:29:35 +01:00
/**
* @fileoverview Markdown Rendering Wrapper for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Markdown parsing and rendering with syntax highlighting
* @keywords markdown, parsing, syntax, highlight, render
*/
class MarkdownRenderer {
constructor() {
this.marked = null;
this.hljs = null;
this.initialized = false;
}
async init() {
if (this.initialized) {
return true;
}
try {
if (window.marked) {
this.marked = window.marked;
}
if (window.hljs) {
this.hljs = window.hljs;
}
if (this.marked && this.hljs) {
this.marked.setOptions({
highlight: (code, lang) => {
if (lang && this.hljs.getLanguage(lang)) {
try {
return this.hljs.highlight(code, { language: lang }).value;
} catch (e) {
return code;
}
}
return this.hljs.highlightAuto(code).value;
},
breaks: true,
gfm: true
});
} else if (this.marked) {
this.marked.setOptions({
breaks: true,
gfm: true
});
}
this.initialized = true;
return true;
} catch (e) {
return false;
}
}
render(text) {
if (!text) {
return '';
}
if (this.marked) {
try {
return this.marked.parse(text);
} catch (e) {
return this.escapeHtml(text);
}
}
return this.simpleRender(text);
}
simpleRender(text) {
let result = this.escapeHtml(text);
result = result.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
const highlighted = this.highlightCode(code.trim(), lang);
return `<pre><code class="language-${lang || 'plaintext'}">${highlighted}</code></pre>`;
});
result = result.replace(/`([^`]+)`/g, '<code>$1</code>');
result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>');
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
result = result.replace(/\n/g, '<br>');
return result;
}
highlightCode(code, lang) {
if (this.hljs && lang && this.hljs.getLanguage(lang)) {
try {
return this.hljs.highlight(code, { language: lang }).value;
} catch (e) {
return this.escapeHtml(code);
}
}
if (this.hljs) {
try {
return this.hljs.highlightAuto(code).value;
} catch (e) {
return this.escapeHtml(code);
}
}
return this.escapeHtml(code);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
renderInline(text) {
if (!text) {
return '';
}
let result = this.escapeHtml(text);
result = result.replace(/`([^`]+)`/g, '<code>$1</code>');
result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>');
return result;
}
stripMarkdown(text) {
if (!text) {
return '';
}
let result = text;
result = result.replace(/```[\s\S]*?```/g, '');
result = result.replace(/`([^`]+)`/g, '$1');
result = result.replace(/\*\*([^*]+)\*\*/g, '$1');
result = result.replace(/\*([^*]+)\*/g, '$1');
result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
return result;
}
}
const markdownRenderer = new MarkdownRenderer();
export { MarkdownRenderer, markdownRenderer };