优化: miniapp渲染(图片/checkbox/代码高亮) + MCP规则共享下沉 + 文档重写 + 节点风格对齐
This commit is contained in:
Generated
+10
-25
@@ -12,6 +12,7 @@
|
||||
"@dcloudio/uni-components": "3.0.0-alpha-4080720251125001",
|
||||
"@dcloudio/uni-h5": "3.0.0-alpha-4080720251125001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4080720251125001",
|
||||
"highlight.js": "^11.11.1",
|
||||
"marked": "^18.0.5",
|
||||
"vue": "^3.4.0",
|
||||
"vue-i18n": "^9.0.0"
|
||||
@@ -4715,7 +4716,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4729,7 +4729,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4743,7 +4742,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4757,7 +4755,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4771,7 +4768,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4785,7 +4781,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4799,7 +4794,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4813,7 +4807,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4827,7 +4820,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4841,7 +4833,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4855,7 +4846,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4869,7 +4859,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4883,7 +4872,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4897,7 +4885,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4911,7 +4898,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4925,7 +4911,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4939,7 +4924,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4953,7 +4937,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4967,7 +4950,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4981,7 +4963,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4995,7 +4976,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5009,7 +4989,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5023,7 +5002,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5037,7 +5015,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5051,7 +5028,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8052,6 +8028,15 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.11.1",
|
||||
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@dcloudio/uni-components": "3.0.0-alpha-4080720251125001",
|
||||
"@dcloudio/uni-h5": "3.0.0-alpha-4080720251125001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4080720251125001",
|
||||
"highlight.js": "^11.11.1",
|
||||
"marked": "^18.0.5",
|
||||
"vue": "^3.4.0",
|
||||
"vue-i18n": "^9.0.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* marked HTML 后处理:为代码块/内联 code/链接/表格注入 inline style(P1-G)。
|
||||
* marked HTML 后处理:为代码块/内联 code/链接/表格/图片/任务列表注入 inline style。
|
||||
*
|
||||
* 为什么后处理而非 marked 自定义 renderer:
|
||||
* - mp-weixin rich-text 不链接页面 CSS class,仅认节点 inline style → 必须把样式写进 HTML 属性。
|
||||
@@ -11,8 +11,88 @@
|
||||
* 跳转需第三方 mp-html(本次不做,记后续)。
|
||||
* - rich-text 非滚动容器,overflow-y:auto/max-height 不创建滚动区 → 长代码靠
|
||||
* white-space:pre-wrap + word-break:break-all 换行防撑爆,不限高滚动。
|
||||
* - <img> 网络图须域名进微信「downloadFile 合法域名」白名单,未配置时真机不显示
|
||||
* (基础库限制,非渲染代码问题);开发者后台配白名单后即正常。
|
||||
* - <input> 不在 rich-text 节点白名单 → GFM 任务列表勾选框被剥离成纯文本,见下替换。
|
||||
*/
|
||||
|
||||
// 语法高亮:highlight.js core + 按需注册常用语言(包体积敏感,不引全量)。
|
||||
// 无 DOM 不可用 highlightElement,hljs 产出的 token class 由 inlineHljs 转 inline style。
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
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 xml from 'highlight.js/lib/languages/xml'
|
||||
import css from 'highlight.js/lib/languages/css'
|
||||
import markdown from 'highlight.js/lib/languages/markdown'
|
||||
import rust from 'highlight.js/lib/languages/rust'
|
||||
import go from 'highlight.js/lib/languages/go'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import bash from 'highlight.js/lib/languages/bash'
|
||||
import sql from 'highlight.js/lib/languages/sql'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import diff from 'highlight.js/lib/languages/diff'
|
||||
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('typescript', typescript)
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('css', css)
|
||||
hljs.registerLanguage('markdown', markdown)
|
||||
hljs.registerLanguage('rust', rust)
|
||||
hljs.registerLanguage('go', go)
|
||||
hljs.registerLanguage('python', python)
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('bash', bash)
|
||||
hljs.registerLanguage('sql', sql)
|
||||
hljs.registerLanguage('yaml', yaml)
|
||||
hljs.registerLanguage('diff', diff)
|
||||
|
||||
// 围栏 info 常见别名 → 已注册语言(hljs.getLanguage 自带部分别名,这里补齐业务常用)
|
||||
const LANG_ALIASES: Record<string, string> = {
|
||||
js: 'javascript', ts: 'typescript', py: 'python',
|
||||
sh: 'bash', shell: 'bash', yml: 'yaml', md: 'markdown',
|
||||
html: 'xml', vue: 'xml', svg: 'xml',
|
||||
}
|
||||
|
||||
/** hljs token class → inline style(rich-text 认 style 不认 class;色板对齐桌面 github-dark) */
|
||||
const TOKEN_STYLE: Record<string, string> = {
|
||||
'hljs-keyword': 'color:#ff7b72',
|
||||
'hljs-doctag': 'color:#ff7b72',
|
||||
'hljs-meta': 'color:#ff7b72',
|
||||
'hljs-type': 'color:#ff7b72',
|
||||
'hljs-template-tag': 'color:#ff7b72',
|
||||
'hljs-template-variable': 'color:#ff7b72',
|
||||
'hljs-title': 'color:#d2a8ff',
|
||||
'hljs-attr': 'color:#79c0ff',
|
||||
'hljs-attribute': 'color:#79c0ff',
|
||||
'hljs-literal': 'color:#79c0ff',
|
||||
'hljs-number': 'color:#79c0ff',
|
||||
'hljs-operator': 'color:#79c0ff',
|
||||
'hljs-variable': 'color:#79c0ff',
|
||||
'hljs-selector-attr': 'color:#79c0ff',
|
||||
'hljs-selector-class': 'color:#79c0ff',
|
||||
'hljs-selector-id': 'color:#79c0ff',
|
||||
'hljs-regexp': 'color:#a5d6ff',
|
||||
'hljs-string': 'color:#a5d6ff',
|
||||
'hljs-built_in': 'color:#ffa657',
|
||||
'hljs-symbol': 'color:#ffa657',
|
||||
'hljs-comment': 'color:#8b949e',
|
||||
'hljs-code': 'color:#8b949e',
|
||||
'hljs-formula': 'color:#8b949e',
|
||||
'hljs-name': 'color:#7ee787',
|
||||
'hljs-quote': 'color:#7ee787',
|
||||
'hljs-selector-tag': 'color:#7ee787',
|
||||
'hljs-selector-pseudo': 'color:#7ee787',
|
||||
'hljs-section': 'color:#1f6feb;font-weight:bold',
|
||||
'hljs-bullet': 'color:#f2cc60',
|
||||
'hljs-addition': 'color:#aff5b4;background-color:#033a16',
|
||||
'hljs-deletion': 'color:#ffdcd7;background-color:#67060c',
|
||||
'hljs-emphasis': 'font-style:italic',
|
||||
'hljs-strong': 'font-weight:bold',
|
||||
}
|
||||
|
||||
/** 给指定标签注入 inline style(已有 style 则合并,无则新增)。 */
|
||||
function styleTag(html: string, tag: string, style: string): string {
|
||||
const re = new RegExp(`<${tag}(\\s[^>]*)?>`, 'g')
|
||||
@@ -28,22 +108,73 @@ function styleTag(html: string, tag: string, style: string): string {
|
||||
})
|
||||
}
|
||||
|
||||
/** 反转 marked 对代码块内容的 HTML 转义,还原原始代码文本供 hljs 高亮 */
|
||||
function unescapeCode(s: string): string {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
/** hljs token class → inline style(无 DOM 不能 highlightElement,逐 span 转内联) */
|
||||
function inlineHljs(html: string): string {
|
||||
return html.replace(/<span class="([^"]+)">/g, (_m, cls: string) => {
|
||||
const style = cls
|
||||
.split(/\s+/)
|
||||
.map((c) => TOKEN_STYLE[c])
|
||||
.filter(Boolean)
|
||||
.join(';')
|
||||
return style ? `<span style="${style}">` : '<span>'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 块代码语法高亮:仅处理已知语言,未知/无语言兜底原样(确定性强,不做自动猜测)。
|
||||
* marked 已对代码内容做 HTML 转义,先反转还原再交给 hljs(hljs 输出自带转义)。
|
||||
*/
|
||||
function highlightCodeBlocks(html: string): string {
|
||||
return html.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/gi, (m, attrs: string, inner: string) => {
|
||||
const langMatch = /class="language-([^"]+)"/i.exec(attrs)
|
||||
const langRaw = langMatch ? langMatch[1] : ''
|
||||
const lang = LANG_ALIASES[langRaw] ?? langRaw
|
||||
if (!lang || !hljs.getLanguage(lang)) return m
|
||||
let value: string
|
||||
try {
|
||||
value = hljs.highlight(unescapeCode(inner), { language: lang, ignoreIllegals: true }).value
|
||||
} catch {
|
||||
return m
|
||||
}
|
||||
return `<pre><code${attrs}>${inlineHljs(value)}</code></pre>`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 marked.parse 产出的 HTML 注入 inline style。
|
||||
*
|
||||
* 设计取舍:pre(块代码)给深色背景;<code> 不给背景(透明),仅等宽+橙色 —— 避免块代码内层 code
|
||||
* 与 pre 背景冲突的双层色块(块代码内 code 透明继承 pre 深底,内联 code 橙色等宽在消息底上也可读)。
|
||||
* 设计取舍:pre(块代码)给深色背景;<code> 不给背景(透明),仅等宽 + 橙色 —— 避免块代码内层 code
|
||||
* 与 pre 背景冲突的双层色块(块代码内 code 透明继承 pre 深底,内联 code 橙色等宽在消息底上也可读);
|
||||
* 块代码经 hljs 高亮后,关键字/字符串等 token 由内联色覆盖,未命中 token 的文本保留 code 基色。
|
||||
*
|
||||
* 正文颜色(P1-1 真机确认):rich-text 只认节点 inline style,不继承页面 CSS `.msg text`
|
||||
* 正文颜色(真机确认):rich-text 只认节点 inline style,不继承页面 CSS `.msg text`
|
||||
* (模板的 `.msg text{color:#e0e0e0}` 仅命中 `<text>` 子元素)。marked 产出的 p/h1-6/li/td/th
|
||||
* 等不带 style 时,文本在暗色气泡上呈近黑 → 暗底黑字。故给承载正文的块级元素统一注入颜色。
|
||||
* strong/em/b 等行内元素继承父级(p/li)颜色,无需逐一注入。
|
||||
*/
|
||||
export function styleMarkdown(html: string): string {
|
||||
let out = html
|
||||
// 任务列表勾选框:rich-text 节点白名单不含 input,替换为字符符号(勾选/未勾选)
|
||||
out = out.replace(/<input[^>]*type="checkbox"[^>]*>/gi, (m) => {
|
||||
const checked = /checked/i.test(m)
|
||||
const color = checked ? '#6bd99a' : '#666666'
|
||||
return `<span style="color:${color};margin-right:4px">${checked ? '☑' : '☐'}</span>`
|
||||
})
|
||||
// 块代码语法高亮(须在下方 pre/code styleTag 前执行,hljs 输出 token span 后再合并基样式)
|
||||
out = highlightCodeBlocks(out)
|
||||
// 正文着色:块级文本容器统一浅色(rich-text 样式继承链从容器传子文本)
|
||||
// word-break:break-all 防长 URL/长单词撑开气泡(rich-text 内文本不继承外层 .msg 的
|
||||
// word-break,须 inline 注入;对齐 mdRenderer 文件头"rich-text 只认 inline style")。
|
||||
// word-break,须 inline 注入;对齐文件头"rich-text 只认 inline style")。
|
||||
const BODY_STYLE = 'color:#e0e0e0;word-break:break-all'
|
||||
for (const tag of ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'blockquote', 'td']) {
|
||||
out = styleTag(out, tag, BODY_STYLE)
|
||||
@@ -54,7 +185,7 @@ export function styleMarkdown(html: string): string {
|
||||
'pre',
|
||||
'display:block;background-color:#1a1a1a;color:#d4d4d4;padding:10px;border-radius:6px;white-space:pre-wrap;word-break:break-all;font-family:monospace;font-size:12px;margin:6px 0',
|
||||
)
|
||||
// code(块内 + 内联):等宽橙色,透明背景(避与 pre 双层色块)
|
||||
// code(块内 + 内联):等宽橙色,透明背景(避与 pre 双层色块;块代码 token 由高亮色覆盖)
|
||||
out = styleTag(
|
||||
out,
|
||||
'code',
|
||||
@@ -62,6 +193,9 @@ export function styleMarkdown(html: string): string {
|
||||
)
|
||||
// 链接:蓝下划线(rich-text 不可点击,仅展示 + selectable 长按复制);word-break 防长 URL 撑开
|
||||
out = styleTag(out, 'a', 'color:#4a9eff;text-decoration:underline;word-break:break-all')
|
||||
// 图片:max-width 防超宽撑爆气泡;block 去基线间隙;圆角与气泡一致。
|
||||
// 网络图显示依赖微信「downloadFile 合法域名」白名单(见文件头),非渲染问题。
|
||||
out = styleTag(out, 'img', 'max-width:100%;display:block;border-radius:6px')
|
||||
// 表格:显式 table 布局 + 等宽字列。注意不能用 display:block(会失去表格布局语义、
|
||||
// 列宽错乱),故显式声明 display:table;rich-text 非滚动容器(见文件头注释),无法建横向
|
||||
// 滚动区 → 不做滚动容器,靠 max-width:100% + word-break:break-all 防超宽撑爆气泡;
|
||||
|
||||
Reference in New Issue
Block a user