, run: () => prefixLines("> ") }, - { label: "代码", icon:
, run: () => surround("`", "`", "code") },
- { label: "链接", icon: 还没有内容。
} -${codes[Number(idx)]}`);
-}
-
-/** 渲染成可直接塞进 dangerouslySetInnerHTML 的字符串。 */
-export function renderMarkdown(source: string): string {
- const lines = source.replace(/\r\n?/g, "\n").split("\n");
- const out: string[] = [];
- let i = 0;
-
- while (i < lines.length) {
- const line = lines[i];
-
- // 代码块:内容原样保留,只做转义,不做任何行内解析
- const fence = line.match(/^\s*```(\w*)\s*$/);
- if (fence) {
- const lang = fence[1];
- const body: string[] = [];
- i += 1;
- while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) {
- body.push(lines[i]);
- i += 1;
- }
- i += 1; // 跳过结束围栏(文件末尾没闭合也能渲染)
- const cls = lang ? ` class="language-${escapeHtml(lang)}"` : "";
- out.push(`${escapeHtml(body.join("\n"))}`);
- continue;
- }
-
- if (/^\s*$/.test(line)) {
- i += 1;
- continue;
- }
-
- const heading = line.match(/^(#{1,4})\s+(.*)$/);
- if (heading) {
- const level = heading[1].length;
- out.push(`${inline(body.join(" "))}`); - continue; - } - - // 列表:同类型的连续行归一组,嵌套只支持一层(够用,且不会渲染错) - const bullet = line.match(/^(\s*)[-*+]\s+(.*)$/); - const ordered = line.match(/^(\s*)\d+[.)]\s+(.*)$/); - if (bullet || ordered) { - const isOrdered = Boolean(ordered); - const items: string[] = []; - const pattern = isOrdered ? /^(\s*)\d+[.)]\s+(.*)$/ : /^(\s*)[-*+]\s+(.*)$/; - while (i < lines.length) { - const m = lines[i].match(pattern); - if (!m) break; - const [, padding, content] = m; - // 缩进两格以上视为子项,用一段加粗缩进表示(不生成嵌套 ol/ul,避免打错标签) - items.push( - padding.length >= 2 - ? `
${inline(paragraph.join("\n")).replace(/\n/g, "
")}