refactor: 提取 Markdown 渲染逻辑到独立模块并优化组件解耦
This commit is contained in:
1 parent
373b219503
commit
198d4f5075
3 files changed
+352
-359
No files matched your search
@@ -1,365 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
let { htmlContent = '' } = $props<{ htmlContent: string }>();
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { Marked } from 'marked';
|
|
||||||
import hljs from 'highlight.js/lib/core';
|
|
||||||
import 'highlight.js/styles/github.css';
|
|
||||||
import {
|
|
||||||
createUniqueHeadingId,
|
|
||||||
replaceGithubAdmonitions,
|
|
||||||
replaceHugoAdmonitions,
|
|
||||||
resolveAdmonitionTitle,
|
|
||||||
sanitizeMarkdownHtml
|
|
||||||
} from '$lib/markdown/rendering';
|
|
||||||
|
|
||||||
// 按需引入及注册主流核心前端、后端、运维语法包,替代沉重的 190+ 语言全集捆绑
|
|
||||||
import bash from 'highlight.js/lib/languages/bash';
|
|
||||||
import javascript from 'highlight.js/lib/languages/javascript';
|
|
||||||
import typescript from 'highlight.js/lib/languages/typescript';
|
|
||||||
import json from 'highlight.js/lib/languages/json';
|
|
||||||
import css from 'highlight.js/lib/languages/css';
|
|
||||||
import xml from 'highlight.js/lib/languages/xml';
|
|
||||||
import python from 'highlight.js/lib/languages/python';
|
|
||||||
import sql from 'highlight.js/lib/languages/sql';
|
|
||||||
import yaml from 'highlight.js/lib/languages/yaml';
|
|
||||||
import markdown from 'highlight.js/lib/languages/markdown';
|
|
||||||
import go from 'highlight.js/lib/languages/go';
|
|
||||||
import rust from 'highlight.js/lib/languages/rust';
|
|
||||||
import cpp from 'highlight.js/lib/languages/cpp';
|
|
||||||
import c from 'highlight.js/lib/languages/c';
|
|
||||||
import java from 'highlight.js/lib/languages/java';
|
|
||||||
import php from 'highlight.js/lib/languages/php';
|
|
||||||
import dockerfile from 'highlight.js/lib/languages/dockerfile';
|
|
||||||
import nginx from 'highlight.js/lib/languages/nginx';
|
|
||||||
import ini from 'highlight.js/lib/languages/ini';
|
|
||||||
import http from 'highlight.js/lib/languages/http';
|
|
||||||
import diff from 'highlight.js/lib/languages/diff';
|
|
||||||
|
|
||||||
hljs.registerLanguage('bash', bash);
|
|
||||||
hljs.registerLanguage('sh', bash);
|
|
||||||
hljs.registerLanguage('shell', bash);
|
|
||||||
hljs.registerLanguage('javascript', javascript);
|
|
||||||
hljs.registerLanguage('js', javascript);
|
|
||||||
hljs.registerLanguage('typescript', typescript);
|
|
||||||
hljs.registerLanguage('ts', typescript);
|
|
||||||
hljs.registerLanguage('json', json);
|
|
||||||
hljs.registerLanguage('css', css);
|
|
||||||
hljs.registerLanguage('xml', xml);
|
|
||||||
hljs.registerLanguage('html', xml);
|
|
||||||
hljs.registerLanguage('python', python);
|
|
||||||
hljs.registerLanguage('py', python);
|
|
||||||
hljs.registerLanguage('sql', sql);
|
|
||||||
hljs.registerLanguage('yaml', yaml);
|
|
||||||
hljs.registerLanguage('yml', yaml);
|
|
||||||
hljs.registerLanguage('markdown', markdown);
|
|
||||||
hljs.registerLanguage('md', markdown);
|
|
||||||
hljs.registerLanguage('go', go);
|
|
||||||
hljs.registerLanguage('rust', rust);
|
|
||||||
hljs.registerLanguage('cpp', cpp);
|
|
||||||
hljs.registerLanguage('c', c);
|
|
||||||
hljs.registerLanguage('java', java);
|
|
||||||
hljs.registerLanguage('php', php);
|
|
||||||
hljs.registerLanguage('dockerfile', dockerfile);
|
|
||||||
hljs.registerLanguage('nginx', nginx);
|
|
||||||
hljs.registerLanguage('ini', ini);
|
|
||||||
hljs.registerLanguage('toml', ini);
|
|
||||||
hljs.registerLanguage('http', http);
|
|
||||||
hljs.registerLanguage('diff', diff);
|
|
||||||
|
|
||||||
// HTML 特殊字符过滤,防止自定义渲染器中的属性注入 XSS
|
|
||||||
function escapeHtml(str: string): string {
|
|
||||||
return str
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 链接安全性检查,拦截 javascript: 等危险伪协议
|
|
||||||
function sanitizeUrl(url: string): string {
|
|
||||||
const clean = url.trim().toLowerCase();
|
|
||||||
if (
|
|
||||||
clean.startsWith('javascript:') ||
|
|
||||||
clean.startsWith('vbscript:') ||
|
|
||||||
clean.startsWith('data:text/html')
|
|
||||||
) {
|
|
||||||
return '#';
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增强 highlight.js 的 bash 语法定义:补足常用运维及工程工具的主命令、子命令以及参数开关
|
|
||||||
const bashLang = hljs.getLanguage('bash');
|
|
||||||
if (bashLang && bashLang.keywords && typeof bashLang.keywords === 'object') {
|
|
||||||
const kw = bashLang.keywords as Record<string, string[] | undefined>;
|
|
||||||
// 1. 添加主命令(显示为内建或核心命令样式)
|
|
||||||
if (Array.isArray(kw.built_in)) {
|
|
||||||
kw.built_in.push(
|
|
||||||
'docker',
|
|
||||||
'podman',
|
|
||||||
'openstack',
|
|
||||||
'kubectl',
|
|
||||||
'kubeadm',
|
|
||||||
'helm',
|
|
||||||
'systemctl',
|
|
||||||
'journalctl',
|
|
||||||
'firewalld',
|
|
||||||
'iptables',
|
|
||||||
'crictl',
|
|
||||||
'nerdctl',
|
|
||||||
'ansible',
|
|
||||||
'terraform',
|
|
||||||
'vagrant',
|
|
||||||
'wrangler',
|
|
||||||
'pnpm',
|
|
||||||
'npm',
|
|
||||||
'gitea',
|
|
||||||
'curl',
|
|
||||||
'wget',
|
|
||||||
'git',
|
|
||||||
'cargo',
|
|
||||||
'bun'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// 2. 添加操作动作与资源类子命令(显示为字面或特色参数样式)
|
|
||||||
const customSubcommands = [
|
|
||||||
'run',
|
|
||||||
'exec',
|
|
||||||
'ps',
|
|
||||||
'build',
|
|
||||||
'image',
|
|
||||||
'images',
|
|
||||||
'container',
|
|
||||||
'pull',
|
|
||||||
'push',
|
|
||||||
'apply',
|
|
||||||
'get',
|
|
||||||
'describe',
|
|
||||||
'delete',
|
|
||||||
'logs',
|
|
||||||
'create',
|
|
||||||
'edit',
|
|
||||||
'rollout',
|
|
||||||
'scale',
|
|
||||||
'port-forward',
|
|
||||||
'cluster-info',
|
|
||||||
'server',
|
|
||||||
'flavor',
|
|
||||||
'volume',
|
|
||||||
'network',
|
|
||||||
'subnet',
|
|
||||||
'router',
|
|
||||||
'keypair',
|
|
||||||
'floating',
|
|
||||||
'list',
|
|
||||||
'show',
|
|
||||||
'start',
|
|
||||||
'stop',
|
|
||||||
'restart',
|
|
||||||
'status',
|
|
||||||
'enable',
|
|
||||||
'disable',
|
|
||||||
'daemon-reload',
|
|
||||||
'clone',
|
|
||||||
'commit',
|
|
||||||
'checkout',
|
|
||||||
'branch',
|
|
||||||
'merge',
|
|
||||||
'rebase',
|
|
||||||
'fetch',
|
|
||||||
'install',
|
|
||||||
'add',
|
|
||||||
'remove',
|
|
||||||
'dev',
|
|
||||||
'deploy',
|
|
||||||
'attach',
|
|
||||||
'rmi',
|
|
||||||
'rm'
|
|
||||||
];
|
|
||||||
if (Array.isArray(kw.literal)) {
|
|
||||||
kw.literal.push(...customSubcommands);
|
|
||||||
} else {
|
|
||||||
kw.literal = customSubcommands;
|
|
||||||
}
|
|
||||||
// 3. 匹配 -d, --name, -p 等选项开关(限定必须位于空格或行首后面,避免误伤中间带破折号的普通名称)
|
|
||||||
if (Array.isArray(bashLang.contains)) {
|
|
||||||
bashLang.contains.unshift({
|
|
||||||
className: 'attr',
|
|
||||||
begin: /(?<=\s|^)-+[a-zA-Z0-9_-]+/
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let { content = '' } = $props<{ content: string }>();
|
|
||||||
|
|
||||||
let container: HTMLElement;
|
let container: HTMLElement;
|
||||||
|
|
||||||
// 创建独立 Marked 实例
|
|
||||||
const markedInstance = new Marked();
|
|
||||||
// This is renderer-local bookkeeping, not UI state.
|
|
||||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
||||||
const headingIds = new Map<string, number>();
|
|
||||||
markedInstance.use({
|
|
||||||
renderer: {
|
|
||||||
code(token) {
|
|
||||||
const code = token.text;
|
|
||||||
let rawLang = (token.lang || '').trim().toLowerCase().split(' ')[0];
|
|
||||||
// 对常见的习惯表达法强制收向 bash 处理
|
|
||||||
const bashAliases: Record<string, string> = {
|
|
||||||
k8s: 'bash',
|
|
||||||
kubectl: 'bash',
|
|
||||||
openstack: 'bash',
|
|
||||||
podman: 'bash',
|
|
||||||
systemd: 'bash',
|
|
||||||
systemctl: 'bash',
|
|
||||||
console: 'bash',
|
|
||||||
terminal: 'bash',
|
|
||||||
zsh: 'bash',
|
|
||||||
sh: 'bash'
|
|
||||||
};
|
|
||||||
if (bashAliases[rawLang]) {
|
|
||||||
rawLang = bashAliases[rawLang];
|
|
||||||
}
|
|
||||||
|
|
||||||
let highlighted: string;
|
|
||||||
let language = 'plaintext';
|
|
||||||
|
|
||||||
if (rawLang && hljs.getLanguage(rawLang)) {
|
|
||||||
language = rawLang;
|
|
||||||
highlighted = hljs.highlight(code, { language }).value;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const auto = hljs.highlightAuto(code);
|
|
||||||
highlighted = auto.value || escapeHtml(code);
|
|
||||||
if (auto.language) language = auto.language;
|
|
||||||
} catch (err: any) {
|
|
||||||
highlighted = escapeHtml(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const title = escapeHtml(rawLang || language || 'text');
|
|
||||||
const encodedCode = encodeURIComponent(code);
|
|
||||||
|
|
||||||
const lines = highlighted.split('\n');
|
|
||||||
const openSpans: string[] = [];
|
|
||||||
const wrappedLines = lines
|
|
||||||
.map((line) => {
|
|
||||||
const prefix = openSpans.join('');
|
|
||||||
const matches = line.matchAll(/<span [^>]+>|<\/span>/g);
|
|
||||||
for (const m of matches) {
|
|
||||||
if (m[0] === '</span>') {
|
|
||||||
openSpans.pop();
|
|
||||||
} else {
|
|
||||||
openSpans.push(m[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const suffix = '</span>'.repeat(openSpans.length);
|
|
||||||
return `<span class="code-line">${prefix}${line}${suffix}</span>`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const chevronRight =
|
|
||||||
'<svg class="chevron-icon" viewBox="0 0 320 512" fill="currentColor" width="0.625em" height="1em"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>';
|
|
||||||
const listOlIcon =
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"></path></svg>';
|
|
||||||
const barsIcon =
|
|
||||||
'<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></svg>';
|
|
||||||
const copyIcon =
|
|
||||||
'<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M433.941 65.941l-51.882-51.882A48 48 0 00348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 00-14.059-33.941zM266 464H54a6 6 0 01-6-6V150a6 6 0 016-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 01-6 6zm128-96H182a6 6 0 01-6-6V54a6 6 0 016-6h106v88c0 13.255 10.745 24 24 24h88v200a6 6 0 01-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 011.757 4.243V112z"></path></svg>';
|
|
||||||
const checkIcon =
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>';
|
|
||||||
const ellipsisIcon =
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"></path></svg>';
|
|
||||||
|
|
||||||
return `<div class="code-block highlight is-open show-line-numbers">
|
|
||||||
<div class="code-block-header">
|
|
||||||
<button class="code-block-title-btn" aria-hidden="true">
|
|
||||||
${chevronRight}
|
|
||||||
<span class="code-title">${title}</span>
|
|
||||||
</button>
|
|
||||||
<div class="code-header-actions">
|
|
||||||
<button class="action-icon line-number-button is-active" title="Toggle line numbers">${listOlIcon}</button>
|
|
||||||
<button class="action-icon wrap-code-button" title="Toggle code wrap">${barsIcon}</button>
|
|
||||||
<button class="action-icon copy-code-button" title="Copy code" data-code="${encodedCode}">
|
|
||||||
<span class="copy-icon">${copyIcon}</span>
|
|
||||||
<span class="check-icon">${checkIcon}</span>
|
|
||||||
</button>
|
|
||||||
<span class="ellipsis-icon" aria-hidden="true">${ellipsisIcon}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<pre class="code-pre"><code class="hljs language-${language}">${wrappedLines}</code></pre>
|
|
||||||
</div>`;
|
|
||||||
},
|
|
||||||
image(token) {
|
|
||||||
const src = sanitizeUrl(token.href || '');
|
|
||||||
const alt = escapeHtml(token.text || '');
|
|
||||||
const title = escapeHtml(token.title || alt);
|
|
||||||
|
|
||||||
let figcaption = '';
|
|
||||||
if (title) {
|
|
||||||
figcaption = `<figcaption>${title}</figcaption>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<figure><a data-fancybox="gallery" data-caption="${title}" href="${escapeHtml(src)}"><img src="${escapeHtml(src)}" alt="${alt}" title="${title}"></a>${figcaption}</figure>`;
|
|
||||||
},
|
|
||||||
heading(token) {
|
|
||||||
const text = token.text;
|
|
||||||
const level = token.depth;
|
|
||||||
const id = createUniqueHeadingId(text, headingIds);
|
|
||||||
return `<h${level} id="${id}">${text}</h${level}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseAdmonitions(markdown: string) {
|
|
||||||
const icons = {
|
|
||||||
note: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"></path></svg>',
|
|
||||||
abstract:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M48 48a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm448 16H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"></path></svg>',
|
|
||||||
info: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>',
|
|
||||||
tip: '<svg viewBox="0 0 352 512" fill="currentColor" height="1em" width="1em"><path d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16h160.03c9.68-33.35 35.78-73.16 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0S0 78.8 0 176z"></path></svg>',
|
|
||||||
success:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>',
|
|
||||||
question:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z"></path></svg>',
|
|
||||||
warning:
|
|
||||||
'<svg viewBox="0 0 576 512" fill="currentColor" height="1em" width="1em"><path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.466-32.004 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>',
|
|
||||||
failure:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>',
|
|
||||||
danger:
|
|
||||||
'<svg viewBox="0 0 448 512" fill="currentColor" height="1em" width="1em"><path d="M439.15 453.06L297.17 384l141.99-69.06c7.9-3.95 11.11-13.56 7.15-21.46L432 264.85c-3.95-7.9-13.56-11.11-21.47-7.16L224 348.41 37.47 257.69c-7.9-3.95-17.51-.75-21.47 7.16L1.69 293.48c-3.96 7.9-.76 17.51 7.15 21.46L150.83 384 8.85 453.06c-7.9 3.95-11.11 13.56-7.15 21.47l14.31 28.63c3.95 7.9 13.56 11.11 21.47 7.15L224 419.59l186.53 90.72c7.9 3.95 17.51.75 21.47-7.15l14.31-28.63c3.95-7.91.75-17.52-7.16-21.47zM150 237.28l-5.48 25.87c-2.67 12.62 5.42 24.85 16.45 24.85h126.08c11.03 0 19.12-12.23 16.45-24.85l-5.5-25.87c41.78-22.41 70-62.8 70-109.28C368 61.85 303.73 0 224 0S80 61.85 80 128c0 46.48 28.22 86.87 70 109.28zM280 112c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32zm-112 0c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32z"></path></svg>',
|
|
||||||
bug: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M511.98 256.03c0-34.92-27.46-64-62.24-64h-38.38c-8.91-44.57-30.82-84.34-60.77-116.14l27.13-27.13c12.5-12.5 12.5-32.76 0-45.25l-22.63-22.63c-12.5-12.5-32.76-12.5-45.25 0l-43.14 43.14A220.617 220.617 0 0 0 256 16.03c-3.69 0-7.38.15-11.05.35l-43.15-43.15c-12.5-12.5-32.76-12.5-45.25 0l-22.63 22.63c-12.5 12.5-12.5 32.76 0 45.25l27.13 27.13c-29.95 31.8-51.86 71.57-60.77 116.14H62.26c-34.78 0-62.24 29.08-62.24 64 0 34.39 26.89 62.48 61 63.92V336.1c0 51.52 19.34 98.4 51.05 133.72l-25.56 25.56c-12.5 12.5-12.5 32.76 0 45.25l22.63 22.63c12.5 12.5 32.76 12.5 45.25 0l42.66-42.66c30.2 16.92 64.71 27.42 101 27.42s70.8-10.5 101-27.42l42.66 42.66c12.5 12.5 32.76 12.5 45.25 0l22.63-22.63c12.5-12.5 12.5-32.76 0-45.25l-25.56-25.56C430.68 434.5 450 387.62 450 336.1v-23.79c34.11-1.44 61.98-29.53 61.98-63.92v-8.36zM152 280.03c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56zm160 0c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56z"></path></svg>',
|
|
||||||
example:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h34.71L15.32 424.64A29.85 29.85 0 0 0 8 444v16c0 26.51 21.49 48 48 48h24a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H56c-8.84 0-16-7.16-16-16v-4.52a6.01 6.01 0 0 1 1.76-4.24l19.24-19.24a48.28 48.28 0 0 0 7.58 3.06L64 448c0 35.35 28.65 64 64 64s64-28.65 64-64-28.65-64-64-64c-9.67 0-18.77 2.21-27.05 6.04L75.64 361.7A29.8 29.8 0 0 0 56 352h-7.66c10.37-4.14 17.66-14.24 17.66-26.34v-3.31c0-15.46-12.54-28-28-28H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22c2.21 0 4 1.79 4 4v3.31c0 2.21-1.79 4-4 4H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h15.4c17.21 0 33.15 6.94 44.97 19.46l5.4-6.23a43.83 43.83 0 0 1 7.23-7.19h-15zM480 352H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM208 112h272a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zM480 208H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM88 32H16C7.16 32 0 39.16 0 48v20c0 8.84 7.16 16 16 16h16v64c0 8.84 7.16 16 16 16h16c8.84 0 16-7.16 16-16V48c0-8.84-7.16-16-16-16h-48c0-8.84 7.16-16 16-16zm64 160c0-35.35-28.65-64-64-64s-64 28.65-64 64 28.65 64 64 64 64-28.65 64-64zm-32 0c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32z"></path></svg>',
|
|
||||||
quote:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"></path></svg>',
|
|
||||||
important:
|
|
||||||
'<svg viewBox="0 0 192 512" fill="currentColor" height="1em" width="1em"><path d="M176 432c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zM25.26 25.33l7.12 256.33c.35 12.63 10.71 22.34 23.36 22.34h80.51c12.65 0 23.01-9.71 23.36-22.34l7.12-256.33c.37-13.37-10.36-24.33-23.75-24.33H49.02c-13.4 0-24.12 10.96-23.76 24.33z"></path></svg>',
|
|
||||||
caution:
|
|
||||||
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>'
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderAdmonition(type: string, rawTitle: string, content: string) {
|
|
||||||
const typeLower = type.toLowerCase();
|
|
||||||
const displayTitle = escapeHtml(resolveAdmonitionTitle(type, rawTitle));
|
|
||||||
const icon = icons[typeLower as keyof typeof icons] || icons.note;
|
|
||||||
return `\n<div class="details admonition ${typeLower} open">\n<div class="details-summary admonition-title"><span class="icon">${icon}</span><span>${displayTitle}</span></div>\n<div class="details-content">\n<div class="admonition-content">\n\n${content}\n\n</div>\n</div>\n</div>\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = replaceGithubAdmonitions(markdown, renderAdmonition);
|
|
||||||
html = replaceHugoAdmonitions(html, renderAdmonition);
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMarkdown(raw: string) {
|
|
||||||
headingIds.clear();
|
|
||||||
const preprocessedContent = parseAdmonitions(raw);
|
|
||||||
return sanitizeMarkdownHtml(markedInstance.parse(preprocessedContent) as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
let htmlContent = $derived(renderMarkdown(content));
|
|
||||||
|
|
||||||
async function bindEvents() {
|
async function bindEvents() {
|
||||||
if (typeof window !== 'undefined' && document.querySelector('[data-fancybox]')) {
|
if (typeof window !== 'undefined' && document.querySelector('[data-fancybox]')) {
|
||||||
const { Fancybox } = await import('@fancyapps/ui/dist/index.js');
|
const { Fancybox } = await import('@fancyapps/ui/dist/index.js');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { tick, untrack } from 'svelte';
|
import { tick, untrack } from 'svelte';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
|
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
|
||||||
|
import { renderMarkdown } from '$lib/markdown/renderer';
|
||||||
import OutlinePopover from './OutlinePopover.svelte';
|
import OutlinePopover from './OutlinePopover.svelte';
|
||||||
import { activeHeadingAt, extractOutline, type OutlineHeading } from '$lib/editor/outline';
|
import { activeHeadingAt, extractOutline, type OutlineHeading } from '$lib/editor/outline';
|
||||||
import type { EditorOutline } from '$lib/editor/outline.svelte';
|
import type { EditorOutline } from '$lib/editor/outline.svelte';
|
||||||
@@ -21,6 +22,9 @@
|
|||||||
let preview: HTMLDivElement | undefined = $state();
|
let preview: HTMLDivElement | undefined = $state();
|
||||||
let previewHeadings: HTMLElement[] = [];
|
let previewHeadings: HTMLElement[] = [];
|
||||||
let navigatingPreview = false;
|
let navigatingPreview = false;
|
||||||
|
|
||||||
|
let previewHtml = $derived(mode === 'preview' ? renderMarkdown(value || '') : '');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const resume = () => {
|
const resume = () => {
|
||||||
navigatingPreview = false;
|
navigatingPreview = false;
|
||||||
@@ -126,7 +130,7 @@
|
|||||||
navigatingPreview = false;
|
navigatingPreview = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MarkdownRenderer content={value} />
|
<MarkdownRenderer htmlContent={previewHtml} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import { Marked } from 'marked';
|
||||||
|
import hljs from 'highlight.js/lib/core';
|
||||||
|
import {
|
||||||
|
createUniqueHeadingId,
|
||||||
|
replaceGithubAdmonitions,
|
||||||
|
replaceHugoAdmonitions,
|
||||||
|
resolveAdmonitionTitle,
|
||||||
|
sanitizeMarkdownHtml
|
||||||
|
} from '$lib/markdown/rendering';
|
||||||
|
|
||||||
|
// 按需引入及注册主流核心前端、后端、运维语法包
|
||||||
|
import bash from 'highlight.js/lib/languages/bash';
|
||||||
|
import javascript from 'highlight.js/lib/languages/javascript';
|
||||||
|
import typescript from 'highlight.js/lib/languages/typescript';
|
||||||
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
import css from 'highlight.js/lib/languages/css';
|
||||||
|
import xml from 'highlight.js/lib/languages/xml';
|
||||||
|
import python from 'highlight.js/lib/languages/python';
|
||||||
|
import sql from 'highlight.js/lib/languages/sql';
|
||||||
|
import yaml from 'highlight.js/lib/languages/yaml';
|
||||||
|
import markdown from 'highlight.js/lib/languages/markdown';
|
||||||
|
import go from 'highlight.js/lib/languages/go';
|
||||||
|
import rust from 'highlight.js/lib/languages/rust';
|
||||||
|
import cpp from 'highlight.js/lib/languages/cpp';
|
||||||
|
import c from 'highlight.js/lib/languages/c';
|
||||||
|
import java from 'highlight.js/lib/languages/java';
|
||||||
|
import php from 'highlight.js/lib/languages/php';
|
||||||
|
import dockerfile from 'highlight.js/lib/languages/dockerfile';
|
||||||
|
import nginx from 'highlight.js/lib/languages/nginx';
|
||||||
|
import ini from 'highlight.js/lib/languages/ini';
|
||||||
|
import http from 'highlight.js/lib/languages/http';
|
||||||
|
import diff from 'highlight.js/lib/languages/diff';
|
||||||
|
|
||||||
|
hljs.registerLanguage('bash', bash);
|
||||||
|
hljs.registerLanguage('sh', bash);
|
||||||
|
hljs.registerLanguage('shell', bash);
|
||||||
|
hljs.registerLanguage('javascript', javascript);
|
||||||
|
hljs.registerLanguage('js', javascript);
|
||||||
|
hljs.registerLanguage('typescript', typescript);
|
||||||
|
hljs.registerLanguage('ts', typescript);
|
||||||
|
hljs.registerLanguage('json', json);
|
||||||
|
hljs.registerLanguage('css', css);
|
||||||
|
hljs.registerLanguage('xml', xml);
|
||||||
|
hljs.registerLanguage('html', xml);
|
||||||
|
hljs.registerLanguage('python', python);
|
||||||
|
hljs.registerLanguage('py', python);
|
||||||
|
hljs.registerLanguage('sql', sql);
|
||||||
|
hljs.registerLanguage('yaml', yaml);
|
||||||
|
hljs.registerLanguage('yml', yaml);
|
||||||
|
hljs.registerLanguage('markdown', markdown);
|
||||||
|
hljs.registerLanguage('md', markdown);
|
||||||
|
hljs.registerLanguage('go', go);
|
||||||
|
hljs.registerLanguage('rust', rust);
|
||||||
|
hljs.registerLanguage('cpp', cpp);
|
||||||
|
hljs.registerLanguage('c', c);
|
||||||
|
hljs.registerLanguage('java', java);
|
||||||
|
hljs.registerLanguage('php', php);
|
||||||
|
hljs.registerLanguage('dockerfile', dockerfile);
|
||||||
|
hljs.registerLanguage('nginx', nginx);
|
||||||
|
hljs.registerLanguage('ini', ini);
|
||||||
|
hljs.registerLanguage('toml', ini);
|
||||||
|
hljs.registerLanguage('http', http);
|
||||||
|
hljs.registerLanguage('diff', diff);
|
||||||
|
|
||||||
|
// HTML 特殊字符过滤,防止自定义渲染器中的属性注入 XSS
|
||||||
|
function escapeHtml(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链接安全性检查,拦截 javascript: 等危险伪协议
|
||||||
|
function sanitizeUrl(url: string): string {
|
||||||
|
const clean = url.trim().toLowerCase();
|
||||||
|
if (
|
||||||
|
clean.startsWith('javascript:') ||
|
||||||
|
clean.startsWith('vbscript:') ||
|
||||||
|
clean.startsWith('data:text/html')
|
||||||
|
) {
|
||||||
|
return '#';
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增强 highlight.js 的 bash 语法定义:补足常用运维及工程工具的主命令、子命令以及参数开关
|
||||||
|
const bashLang = hljs.getLanguage('bash');
|
||||||
|
if (bashLang && bashLang.keywords && typeof bashLang.keywords === 'object') {
|
||||||
|
const kw = bashLang.keywords as Record<string, string[] | undefined>;
|
||||||
|
// 1. 添加主命令
|
||||||
|
if (Array.isArray(kw.built_in)) {
|
||||||
|
kw.built_in.push(
|
||||||
|
'docker',
|
||||||
|
'podman',
|
||||||
|
'openstack',
|
||||||
|
'kubectl',
|
||||||
|
'kubeadm',
|
||||||
|
'helm',
|
||||||
|
'systemctl',
|
||||||
|
'journalctl',
|
||||||
|
'firewalld',
|
||||||
|
'iptables',
|
||||||
|
'crictl',
|
||||||
|
'nerdctl',
|
||||||
|
'ansible',
|
||||||
|
'terraform',
|
||||||
|
'vagrant',
|
||||||
|
'wrangler',
|
||||||
|
'pnpm',
|
||||||
|
'npm',
|
||||||
|
'gitea',
|
||||||
|
'curl',
|
||||||
|
'wget',
|
||||||
|
'git',
|
||||||
|
'cargo',
|
||||||
|
'bun'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 2. 添加操作动作与资源类子命令
|
||||||
|
const customSubcommands = [
|
||||||
|
'run',
|
||||||
|
'exec',
|
||||||
|
'ps',
|
||||||
|
'build',
|
||||||
|
'image',
|
||||||
|
'images',
|
||||||
|
'container',
|
||||||
|
'pull',
|
||||||
|
'push',
|
||||||
|
'apply',
|
||||||
|
'get',
|
||||||
|
'describe',
|
||||||
|
'delete',
|
||||||
|
'logs',
|
||||||
|
'create',
|
||||||
|
'edit',
|
||||||
|
'rollout',
|
||||||
|
'scale',
|
||||||
|
'port-forward',
|
||||||
|
'cluster-info',
|
||||||
|
'server',
|
||||||
|
'flavor',
|
||||||
|
'volume',
|
||||||
|
'network',
|
||||||
|
'subnet',
|
||||||
|
'router',
|
||||||
|
'keypair',
|
||||||
|
'floating',
|
||||||
|
'list',
|
||||||
|
'show',
|
||||||
|
'start',
|
||||||
|
'stop',
|
||||||
|
'restart',
|
||||||
|
'status',
|
||||||
|
'enable',
|
||||||
|
'disable',
|
||||||
|
'daemon-reload',
|
||||||
|
'clone',
|
||||||
|
'commit',
|
||||||
|
'checkout',
|
||||||
|
'branch',
|
||||||
|
'merge',
|
||||||
|
'rebase',
|
||||||
|
'fetch',
|
||||||
|
'install',
|
||||||
|
'add',
|
||||||
|
'remove',
|
||||||
|
'dev',
|
||||||
|
'deploy',
|
||||||
|
'attach',
|
||||||
|
'rmi',
|
||||||
|
'rm'
|
||||||
|
];
|
||||||
|
if (Array.isArray(kw.literal)) {
|
||||||
|
kw.literal.push(...customSubcommands);
|
||||||
|
} else {
|
||||||
|
kw.literal = customSubcommands;
|
||||||
|
}
|
||||||
|
// 3. 匹配选项开关
|
||||||
|
if (Array.isArray(bashLang.contains)) {
|
||||||
|
bashLang.contains.unshift({
|
||||||
|
className: 'attr',
|
||||||
|
begin: /(?<=\s|^)-+[a-zA-Z0-9_-]+/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建独立 Marked 实例
|
||||||
|
const markedInstance = new Marked();
|
||||||
|
const headingIds = new Map<string, number>();
|
||||||
|
|
||||||
|
markedInstance.use({
|
||||||
|
renderer: {
|
||||||
|
code(token) {
|
||||||
|
const code = token.text;
|
||||||
|
let rawLang = (token.lang || '').trim().toLowerCase().split(' ')[0];
|
||||||
|
const bashAliases: Record<string, string> = {
|
||||||
|
k8s: 'bash',
|
||||||
|
kubectl: 'bash',
|
||||||
|
openstack: 'bash',
|
||||||
|
podman: 'bash',
|
||||||
|
systemd: 'bash',
|
||||||
|
systemctl: 'bash',
|
||||||
|
console: 'bash',
|
||||||
|
terminal: 'bash',
|
||||||
|
zsh: 'bash',
|
||||||
|
sh: 'bash'
|
||||||
|
};
|
||||||
|
if (bashAliases[rawLang]) {
|
||||||
|
rawLang = bashAliases[rawLang];
|
||||||
|
}
|
||||||
|
|
||||||
|
let highlighted: string;
|
||||||
|
let language = 'plaintext';
|
||||||
|
|
||||||
|
if (rawLang && hljs.getLanguage(rawLang)) {
|
||||||
|
language = rawLang;
|
||||||
|
highlighted = hljs.highlight(code, { language }).value;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const auto = hljs.highlightAuto(code);
|
||||||
|
highlighted = auto.value || escapeHtml(code);
|
||||||
|
if (auto.language) language = auto.language;
|
||||||
|
} catch {
|
||||||
|
highlighted = escapeHtml(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const title = escapeHtml(rawLang || language || 'text');
|
||||||
|
const encodedCode = encodeURIComponent(code);
|
||||||
|
|
||||||
|
const lines = highlighted.split('\n');
|
||||||
|
const openSpans: string[] = [];
|
||||||
|
const wrappedLines = lines
|
||||||
|
.map((line) => {
|
||||||
|
const prefix = openSpans.join('');
|
||||||
|
const matches = line.matchAll(/<span [^>]+>|<\/span>/g);
|
||||||
|
for (const m of matches) {
|
||||||
|
if (m[0] === '</span>') {
|
||||||
|
openSpans.pop();
|
||||||
|
} else {
|
||||||
|
openSpans.push(m[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const suffix = '</span>'.repeat(openSpans.length);
|
||||||
|
return `<span class="code-line">${prefix}${line}${suffix}</span>`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const chevronRight =
|
||||||
|
'<svg class="chevron-icon" viewBox="0 0 320 512" fill="currentColor" width="0.625em" height="1em"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>';
|
||||||
|
const listOlIcon =
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"></path></svg>';
|
||||||
|
const barsIcon =
|
||||||
|
'<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></svg>';
|
||||||
|
const copyIcon =
|
||||||
|
'<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M433.941 65.941l-51.882-51.882A48 48 0 00348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 00-14.059-33.941zM266 464H54a6 6 0 01-6-6V150a6 6 0 016-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 01-6 6zm128-96H182a6 6 0 01-6-6V54a6 6 0 016-6h106v88c0 13.255 10.745 24 24 24h88v200a6 6 0 01-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 011.757 4.243V112z"></path></svg>';
|
||||||
|
const checkIcon =
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>';
|
||||||
|
const ellipsisIcon =
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"></path></svg>';
|
||||||
|
return `<div class="code-block highlight is-open show-line-numbers">
|
||||||
|
<div class="code-block-header">
|
||||||
|
<button class="code-block-title-btn" aria-hidden="true">
|
||||||
|
${chevronRight}
|
||||||
|
<span class="code-title">${title}</span>
|
||||||
|
</button>
|
||||||
|
<div class="code-header-actions">
|
||||||
|
<button class="action-icon line-number-button is-active" title="Toggle line numbers">${listOlIcon}</button>
|
||||||
|
<button class="action-icon wrap-code-button" title="Toggle code wrap">${barsIcon}</button>
|
||||||
|
<button class="action-icon copy-code-button" title="Copy code" data-code="${encodedCode}">
|
||||||
|
<span class="copy-icon">${copyIcon}</span>
|
||||||
|
<span class="check-icon">${checkIcon}</span>
|
||||||
|
</button>
|
||||||
|
<span class="ellipsis-icon" aria-hidden="true">${ellipsisIcon}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre class="code-pre"><code class="hljs language-${language}">${wrappedLines}</code></pre>
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
image(token) {
|
||||||
|
const src = sanitizeUrl(token.href || '');
|
||||||
|
const alt = escapeHtml(token.text || '');
|
||||||
|
const title = escapeHtml(token.title || alt);
|
||||||
|
let figcaption = '';
|
||||||
|
if (title) {
|
||||||
|
figcaption = `<figcaption>${title}</figcaption>`;
|
||||||
|
}
|
||||||
|
return `<figure><a data-fancybox="gallery" data-caption="${title}" href="${escapeHtml(src)}"><img src="${escapeHtml(src)}" alt="${alt}" title="${title}"></a>${figcaption}</figure>`;
|
||||||
|
},
|
||||||
|
heading(token) {
|
||||||
|
const text = token.text;
|
||||||
|
const level = token.depth;
|
||||||
|
const id = createUniqueHeadingId(text, headingIds);
|
||||||
|
return `<h${level} id="${id}">${text}</h${level}>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseAdmonitions(markdown: string) {
|
||||||
|
const icons = {
|
||||||
|
note: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"></path></svg>',
|
||||||
|
abstract:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M48 48a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm448 16H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"></path></svg>',
|
||||||
|
info: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>',
|
||||||
|
tip: '<svg viewBox="0 0 352 512" fill="currentColor" height="1em" width="1em"><path d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16h160.03c9.68-33.35 35.78-73.16 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0S0 78.8 0 176z"></path></svg>',
|
||||||
|
success:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>',
|
||||||
|
question:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z"></path></svg>',
|
||||||
|
warning:
|
||||||
|
'<svg viewBox="0 0 576 512" fill="currentColor" height="1em" width="1em"><path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.466-32.004 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>',
|
||||||
|
failure:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>',
|
||||||
|
danger:
|
||||||
|
'<svg viewBox="0 0 448 512" fill="currentColor" height="1em" width="1em"><path d="M439.15 453.06L297.17 384l141.99-69.06c7.9-3.95 11.11-13.56 7.15-21.46L432 264.85c-3.95-7.9-13.56-11.11-21.47-7.16L224 348.41 37.47 257.69c-7.9-3.95-17.51-.75-21.47 7.16L1.69 293.48c-3.96 7.9-.76 17.51 7.15 21.46L150.83 384 8.85 453.06c-7.9 3.95-11.11 13.56-7.15 21.47l14.31 28.63c3.95 7.9 13.56 11.11 21.47 7.15L224 419.59l186.53 90.72c7.9 3.95 17.51.75 21.47-7.15l14.31-28.63c3.95-7.91.75-17.52-7.16-21.47zM150 237.28l-5.48 25.87c-2.67 12.62 5.42 24.85 16.45 24.85h126.08c11.03 0 19.12-12.23 16.45-24.85l-5.5-25.87c41.78-22.41 70-62.8 70-109.28C368 61.85 303.73 0 224 0S80 61.85 80 128c0 46.48 28.22 86.87 70 109.28zM280 112c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32zm-112 0c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32z"></path></svg>',
|
||||||
|
bug: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M511.98 256.03c0-34.92-27.46-64-62.24-64h-38.38c-8.91-44.57-30.82-84.34-60.77-116.14l27.13-27.13c12.5-12.5 12.5-32.76 0-45.25l-22.63-22.63c-12.5-12.5-32.76-12.5-45.25 0l-43.14 43.14A220.617 220.617 0 0 0 256 16.03c-3.69 0-7.38.15-11.05.35l-43.15-43.15c-12.5-12.5-32.76-12.5-45.25 0l-22.63 22.63c-12.5 12.5-12.5 32.76 0 45.25l27.13 27.13c-29.95 31.8-51.86 71.57-60.77 116.14H62.26c-34.78 0-62.24 29.08-62.24 64 0 34.39 26.89 62.48 61 63.92V336.1c0 51.52 19.34 98.4 51.05 133.72l-25.56 25.56c-12.5 12.5-12.5 32.76 0 45.25l22.63 22.63c12.5 12.5 32.76 12.5 45.25 0l42.66-42.66c30.2 16.92 64.71 27.42 101 27.42s70.8-10.5 101-27.42l42.66 42.66c12.5 12.5 32.76 12.5 45.25 0l22.63-22.63c12.5-12.5 12.5-32.76 0-45.25l-25.56-25.56C430.68 434.5 450 387.62 450 336.1v-23.79c34.11-1.44 61.98-29.53 61.98-63.92v-8.36zM152 280.03c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56zm160 0c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56z"></path></svg>',
|
||||||
|
example:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h34.71L15.32 424.64A29.85 29.85 0 0 0 8 444v16c0 26.51 21.49 48 48 48h24a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H56c-8.84 0-16-7.16-16-16v-4.52a6.01 6.01 0 0 1 1.76-4.24l19.24-19.24a48.28 48.28 0 0 0 7.58 3.06L64 448c0 35.35 28.65 64 64 64s64-28.65 64-64-28.65-64-64-64c-9.67 0-18.77 2.21-27.05 6.04L75.64 361.7A29.8 29.8 0 0 0 56 352h-7.66c10.37-4.14 17.66-14.24 17.66-26.34v-3.31c0-15.46-12.54-28-28-28H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22c2.21 0 4 1.79 4 4v3.31c0 2.21-1.79 4-4 4H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h15.4c17.21 0 33.15 6.94 44.97 19.46l5.4-6.23a43.83 43.83 0 0 1 7.23-7.19h-15zM480 352H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM208 112h272a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zM480 208H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM88 32H16C7.16 32 0 39.16 0 48v20c0 8.84 7.16 16 16 16h16v64c0 8.84 7.16 16 16 16h16c8.84 0 16-7.16 16-16V48c0-8.84-7.16-16-16-16h-48c0-8.84 7.16-16 16-16zm64 160c0-35.35-28.65-64-64-64s-64 28.65-64 64 28.65 64 64 64 64-28.65 64-64zm-32 0c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32z"></path></svg>',
|
||||||
|
quote:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"></path></svg>',
|
||||||
|
important:
|
||||||
|
'<svg viewBox="0 0 192 512" fill="currentColor" height="1em" width="1em"><path d="M176 432c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zM25.26 25.33l7.12 256.33c.35 12.63 10.71 22.34 23.36 22.34h80.51c12.65 0 23.01-9.71 23.36-22.34l7.12-256.33c.37-13.37-10.36-24.33-23.75-24.33H49.02c-13.4 0-24.12 10.96-23.76 24.33z"></path></svg>',
|
||||||
|
caution:
|
||||||
|
'<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>'
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderAdmonition(type: string, rawTitle: string, content: string) {
|
||||||
|
const typeLower = type.toLowerCase();
|
||||||
|
const displayTitle = escapeHtml(resolveAdmonitionTitle(type, rawTitle));
|
||||||
|
const icon = icons[typeLower as keyof typeof icons] || icons.note;
|
||||||
|
return `\n<div class="details admonition ${typeLower} open">\n<div class="details-summary admonition-title"><span class="icon">${icon}</span><span>${displayTitle}</span></div>\n<div class="details-content">\n<div class="admonition-content">\n\n${content}\n\n</div>\n</div>\n</div>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = replaceGithubAdmonitions(markdown, renderAdmonition);
|
||||||
|
html = replaceHugoAdmonitions(html, renderAdmonition);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderMarkdown(raw: string) {
|
||||||
|
headingIds.clear();
|
||||||
|
const preprocessedContent = parseAdmonitions(raw);
|
||||||
|
return sanitizeMarkdownHtml(markedInstance.parse(preprocessedContent) as string);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user