chore:AI智能题目可以自定义调节颜色大小,字号

This commit is contained in:
2026-10-09 18:29:02 +08:00
parent e223e58425
commit 3ab78f5369

View File

@@ -2972,6 +2972,65 @@ _handleStreamError(msg) {
const BQ_MARGIN = COMPACT ? '6rpx 0' : '8rpx 0'
const BQ_LH = COMPACT ? '1.6' : '1.7'
// ===== HTML 白名单还原 (对齐后台预览 markedParse 的 WC_ALLOWED_TAGS / WC_ALLOWED_STYLE) =====
// 后台预览支持受限 HTML(染色/排版), 终端此前无条件转义所有 < >, 商家配置的
// <span style="..."> 在终端显示成字面文本。策略: 转义 → 识别白名单 → 只还原白名单。
// - 标签/属性/CSS 属性三张白名单, 其余(含 on* 事件、position、url())一律丢弃
// - 非白名单标签保持转义态, 照常显示为字面文本
const WC_TAGS = ['span', 'div', 'b', 'i', 'u', 's', 'em', 'strong', 'mark', 'small', 'sub', 'sup', 'br', 'hr', 'p', 'blockquote', 'ul', 'ol', 'li', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'code']
const WC_VOID = { br: true, hr: true } // 自闭合标签, 还原时不带结束标签
const WC_STYLES = {
color: 1, 'background-color': 1, background: 1,
'font-size': 1, 'font-weight': 1, 'font-style': 1, 'font-family': 1,
'text-align': 1, 'text-decoration': 1, 'text-indent': 1, 'text-transform': 1,
'line-height': 1, 'letter-spacing': 1, 'vertical-align': 1, 'white-space': 1,
'list-style-type': 1, 'word-break': 1, 'word-wrap': 1,
margin: 1, 'margin-top': 1, 'margin-bottom': 1, 'margin-left': 1, 'margin-right': 1,
padding: 1, 'padding-top': 1, 'padding-bottom': 1, 'padding-left': 1, 'padding-right': 1,
'border-color': 1, 'border-style': 1, 'border-width': 1, 'border-radius': 1,
width: 1, height: 1, 'min-width': 1, 'max-width': 1,
display: 1, opacity: 1, 'text-shadow': 1
}
const WC_STYLE_BAD = /expression\s*\(|javascript:|vbscript:|behavior\s*:|-moz-binding|@import|url\s*\(/i
const wcFilterStyle = (s) => {
return s.split(';').map((p) => {
const i = p.indexOf(':')
if (i === -1) return ''
const prop = p.slice(0, i).trim().toLowerCase()
const val = p.slice(i + 1).trim()
// 值里不允许引号; 命中黑名单(expression/javascript/url( 等)整条丢弃
if (!WC_STYLES[prop] || WC_STYLE_BAD.test(val) || val.indexOf('"') !== -1) return ''
return prop + ': ' + val
}).filter(Boolean).join('; ')
}
const wcRestoreHtml = (escaped) => {
// 转义后标签形如 &lt;span style="..."&gt;, 引号未被转义仍是字面 "
return escaped.replace(/&lt;(\/?)([a-zA-Z0-9]+)([\s\S]*?)&gt;/g, (m, slash, rawTag, attrs) => {
const tag = rawTag.toLowerCase()
if (WC_TAGS.indexOf(tag) === -1) return m // 非白名单: 保持转义, 显示为字面文本
if (slash) return WC_VOID[tag] ? '' : '</' + tag + '>'
if (WC_VOID[tag]) return '<' + tag + '>'
const kept = []
const attrRe = /([a-zA-Z-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g
let am
while ((am = attrRe.exec(attrs)) !== null) {
const name = am[1].toLowerCase()
const val = am[2] !== undefined ? am[2] : am[3]
if (name === 'style') {
const fs = wcFilterStyle(val)
if (fs) kept.push('style="' + fs + '"')
} else if (name === 'href' || name === 'src') {
// 协议白名单: 挡 javascript:/vbscript:/data: 等
if (/^(https?:|mailto:|tel:|\/|#)/i.test(val)) kept.push(name + '="' + val + '"')
} else if (name === 'alt' || name === 'title' || name === 'colspan' || name === 'rowspan') {
kept.push(name + '="' + val + '"')
}
// 其余属性(含 on* 事件)一律丢弃
}
return '<' + tag + (kept.length ? ' ' + kept.join(' ') : '') + '>'
})
}
// 辅助:内联格式处理
// 思路: 先把"链接/图片/裸URL/电话"整体抽成占位符保护起来, 再做加粗/斜体等,
// 最后还原, 避免各规则互相串改对方的 href / 数字。
@@ -3036,6 +3095,9 @@ _handleStreamError(msg) {
return `%%CODEBLOCK_${idx}%%`
})
// 2.5 还原白名单内 HTML (放在代码块抽离之后: 代码块里的示例 HTML 保持字面文本)
html = wcRestoreHtml(html)
// 3. 按行处理,用 <br> 连接单换行,双换行分割段落块
const lines = html.split('\n')
const out = []