64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
import { marked, Renderer } from 'marked';
|
|
|
|
const WIKI_LINK = /\[\[([^\]|\n]+?)(?:\|([^\]\n]+?))?\]\]/g;
|
|
const UNSAFE_SCHEME = /^(?:javascript|data|vbscript):/i;
|
|
|
|
export function normalizeImportedMarkup(source: string): string {
|
|
return source.replace(/<aside>\s*([\s\S]*?)\s*<\/aside>/giu, (_match, rawBody: string) => {
|
|
const body = rawBody.trim().split('\n').map(line => `> ${line}`).join('\n');
|
|
return `\n\n${body}\n\n`;
|
|
});
|
|
}
|
|
|
|
export function expandWikiLinks(source: string): string {
|
|
return source.replace(WIKI_LINK, (_match, rawTarget: string, rawLabel?: string) => {
|
|
const target = rawTarget.trim();
|
|
const label = (rawLabel || rawTarget).trim();
|
|
const encoded = encodeURIComponent(target).replace(/%2F/gi, '/');
|
|
return `[${label}](hololake://knowledge/${encoded} "${target.replace(/"/g, '"')}")`;
|
|
});
|
|
}
|
|
|
|
function escapeHtml(value: string): string {
|
|
return value
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function safeHref(href: string | null): string {
|
|
const value = (href || '').trim();
|
|
return UNSAFE_SCHEME.test(value) ? '#' : value;
|
|
}
|
|
|
|
const renderer = new Renderer();
|
|
const renderBaseTable = renderer.table.bind(renderer);
|
|
renderer.html = ({ text }) => /^<br\s*\/?\s*>$/iu.test(text.trim())
|
|
? '<br>'
|
|
: `<pre class="kb-raw-html"><code>${escapeHtml(text)}</code></pre>`;
|
|
renderer.link = ({ href, title, tokens }) => {
|
|
const label = renderer.parser.parseInline(tokens);
|
|
const safe = escapeHtml(safeHref(href));
|
|
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
|
|
const wiki = safe.startsWith('hololake://knowledge/');
|
|
return `<a href="${safe}"${titleAttr}${wiki ? ' data-hololake-wiki="true"' : ''}>${label}</a>`;
|
|
};
|
|
renderer.image = ({ href, title, text }) => {
|
|
const safe = safeHref(href);
|
|
if (!safe || safe === '#') return `<span class="kb-image-blocked">图片地址已拦截</span>`;
|
|
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
|
|
return `<img src="${escapeHtml(safe)}" alt="${escapeHtml(text)}"${titleAttr} loading="lazy">`;
|
|
};
|
|
renderer.table = token => `<div class="kb-table-scroll">${renderBaseTable(token)}</div>`;
|
|
|
|
marked.setOptions({ gfm: true, breaks: false });
|
|
|
|
export function renderKnowledgeMarkdown(source: string): string {
|
|
try {
|
|
return marked.parse(expandWikiLinks(normalizeImportedMarkup(source)), { async: false, renderer }) as string;
|
|
} catch {
|
|
return '<p class="kb-render-error">此页面暂时无法渲染,请切换到编辑模式检查原文。</p>';
|
|
}
|
|
}
|