// 轻量 Markdown 渲染(AI 控制台回答用)——不引外部库/CDN(生产 220 在内网)。
// 安全:**先 esc() 转义,再套标记**——模型输出里的原始 HTML 只会以文本显示,
// 不会变成可执行标签。
// 支持:标题 / 段落 / 换行 / 粗体 / 斜体 / 删除线 / 行内代码 / 围栏代码块 /
// 有序与无序列表(含一层以上嵌套)/ 引用 / 表格 / 分隔线 / 链接。
// 依赖:esc()(base.js,本文件须在其后加载)
// 行内代码占位符(控制字符,正文几乎不可能出现)
const _MD_PH = '\u0001';
function _mdInline(s){
const codes = [];
// 行内代码先摘出,避免其中的 ** * ~~ 被继续当标记解析
s = s.replace(/`([^`]+)`/g, (m, c)=>{
codes.push(c);
return _MD_PH + (codes.length - 1) + _MD_PH;
});
s = s.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/~~([^~]+)~~/g, '$1')
.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1$2')
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
'$1');
return s.replace(new RegExp(_MD_PH + '(\\d+)' + _MD_PH, 'g'),
(m, i)=>'' + codes[+i] + '');
}
// 表格行 → 单元格数组
function _mdCells(line){
return line.trim().replace(/^\|/, '').replace(/\|$/, '')
.split('|').map(c=>c.trim());
}
// 列表项递归渲染(缩进 = 层级;同类标记合并,类型变了外层另起一个列表)
function _mdList(items, out, idx, indent){
const ordered = items[idx].ordered;
const tag = ordered ? 'ol' : 'ul';
out.push('<' + tag + ' class="md-list">');
while(idx < items.length && items[idx].indent >= indent){
const it = items[idx];
if(it.indent > indent) break; // 更深缩进块由外层另起列表(避免非法嵌套)
if(it.ordered !== ordered) break; // 同级但标记类型变了 → 交回外层
out.push('
' + para.map(l=>_mdInline(esc(l))).join('
') + '
' + esc(body.join('\n')) + '');
continue;
}
// 表格:表头行 + 分隔行(|---|---|)
if(/^\s*\|.*\|\s*$/.test(ln) && i + 1 < lines.length
&& /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])){
flushPara();
const head = _mdCells(ln);
i += 2;
const rows = [];
while(i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])){
rows.push(_mdCells(lines[i])); i++;
}
out.push('| ' + _mdInline(esc(c)) + ' | ').join('') + '
|---|
| ' + _mdInline(esc(c)) + ' | ').join('') + '
' + body.map(l=>_mdInline(esc(l))).join(''); continue; } // 列表(- * + / 1. 1))——含缩进续行 m = ln.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/); if(m){ flushPara(); const items = []; while(i < lines.length){ const mm = lines[i].match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/); if(!mm) break; items.push({indent: mm[1].replace(/\t/g, ' ').length, ordered: /\d/.test(mm[2]), text: mm[3]}); i++; // 紧跟的纯缩进行算上一条的续行 while(i < lines.length && /^\s+\S/.test(lines[i]) && !/^\s*([-*+]|\d+[.)])\s+/.test(lines[i])){ items[items.length - 1].text += ' ' + lines[i].trim(); i++; } } let k = 0; while(k < items.length){ const n = _mdList(items, out, k, items[k].indent); if(n <= k) break; // 防死循环 k = n; } continue; } // 空行 = 段落分隔 if(!ln.trim()){ flushPara(); i++; continue; } para.push(ln); i++; } flushPara(); return out.join(''); }
') + '