feat(channelbridge): u 加回 IM 出站的行内白名单(改为六项)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

主人 2026-10-07 裁定「下划线确实可以保留」。

- 行内白名单:b / strong / i / em / code / u(六项)
- s 仍在外(逐字原样保留,含大写 <S>)—— 有一个守门变异专门锁这条
- 行为改动只有一行(case 列表加 "u"),其余是注释与测试同步
This commit is contained in:
AskaEth committed 2026-10-07 18:37:35 +08:00
1 parent a19d12b228
commit 74c9f2d8df
2 files changed
+16 -9

No files matched your search

@@ -15,9 +15,10 @@ import (
// 与星号块 ⇒ `<b>18:35</b>` 连标签一起字面发给微信/QQ,用户看到裸标签。
//
// 🔴 处置口径(必须与契约 v0.89 及 Web 端渲染器逐条一致,否则同一段文本两端显示不同):
// 1. 行内白名单 = **五项** `b` / `strong` / `i` / `em` / `code`;**成对且平衡**的
// 1. 行内白名单 = **六项** `b` / `strong` / `i` / `em` / `code` / `u`;**成对且平衡**的
// 白名单标签 ⇒ **剥标签、留内部文本**(微信/QQ 不支持 HTML,没有别的选择);
// 2. **白名单外**的标签(`<script>` / `<div>` / `<u>` / `<s>` …)⇒ **逐字原样保留**
// (`u` 由主人 2026-10-07 裁定加回 —— 下划线要保留渲染;`s` 仍不在白名单内。)
// 2. **白名单外**的标签(`<script>` / `<div>` / `<s>` …)⇒ **逐字原样保留**
// (它们是文本,一个字符不丢);
// 3. **结构不平衡**(未闭合 / 落单闭标签 / 交错)⇒ **整段保持原样**(契约把这一条的
// 粒度列为实现细节,本端与 Web 端取同一粒度,保证两端对同一段文本给出同一结果);
@@ -45,10 +46,10 @@ func isBreakTagName(name string) bool {
return strings.EqualFold(name, inlineBreakTag)
}
// isPairedInlineTag 报告标签名是否属于需配对处理的行内白名单(契约 v0.89:五项)。
// isPairedInlineTag 报告标签名是否属于需配对处理的行内白名单(契约 v0.89:六项)。
func isPairedInlineTag(name string) bool {
switch name {
case "b", "strong", "i", "em", "code":
case "b", "strong", "i", "em", "code", "u":
return true
default:
return false
@@ -12,7 +12,7 @@ import (
)
// 本文件覆盖 IM 出站的行内标签白名单(契约 §6 v0.89:`b` / `strong` / `i` / `em` /
// `code` **五项**)与 `<br>` 的**气泡/消息分隔符**语义(与 `<p>` 同级)。
// `code` / `u` **六项**)与 `<br>` 的**气泡/消息分隔符**语义(与 `<p>` 同级)。
// 口径与 Web 端渲染器(frontend/web/src/lib/inlineMarkup.ts)逐条对齐。
// TestInlineTags_RealSampleParagraph 复现主人 2026-10-07 报障的真样例:
@@ -35,7 +35,7 @@ func TestInlineTags_RealSampleParagraph(t *testing.T) {
}
}
// TestInlineTags_PairedWhitelistTagsStripped 报告五项白名单的成对标签只去标签、
// TestInlineTags_PairedWhitelistTagsStripped 报告六项白名单的成对标签只去标签、
// 留内部文本,且大小写不敏感。
func TestInlineTags_PairedWhitelistTagsStripped(t *testing.T) {
t.Parallel()
@@ -45,6 +45,9 @@ func TestInlineTags_PairedWhitelistTagsStripped(t *testing.T) {
{"<i>斜</i>", "斜"},
{"<em>强调</em>", "强调"},
{"<code>go test</code>", "go test"},
{"<u>下划线</u>", "下划线"},
{"<U>大写下划线</U>", "大写下划线"},
{"<u class=x>属性也丢</u>", "属性也丢"},
{"<B>大写</B>", "大写"},
{"<STRONG>大写</STRONG>", "大写"},
{"<b><i>嵌套</i></b>", "嵌套"},
@@ -138,7 +141,7 @@ func TestBreakTags_NoLiteralLeak(t *testing.T) {
// TestInlineTags_NonWhitelistKeptVerbatim 白名单外标签是文本,逐字不丢 ——
// 这与 `<p>` / `<action>`(角色扮演/段落标记,要剥)刻意不同。
// `u` / `s` 自 v0.89 修订起**不在**白名单内,因此也按文本逐字保留。
// `s` **不在**白名单内(主人只批了下划线 `u`),因此也按文本逐字保留。
func TestInlineTags_NonWhitelistKeptVerbatim(t *testing.T) {
t.Parallel()
cases := []string{
@@ -147,15 +150,18 @@ func TestInlineTags_NonWhitelistKeptVerbatim(t *testing.T) {
"<a href=\"http://x\">链接</a>",
"<img src=x onerror=y>",
"<iframe src=x></iframe>",
"<u>下划线</u>",
"<s>删除线</s>",
"<U>大写也不认</U>",
"<S>大写删除线</S>",
}
for _, in := range cases {
if got := stripNarration(in); got != in {
t.Errorf("stripNarration(%q) = %q, want verbatim", in, got)
}
}
// `u` 已由主人 2026-10-07 裁定加回白名单 ⇒ 正向断言:剥标签、留文本。
if got := stripNarration("<u>下划线</u>"); got != "下划线" {
t.Errorf(`stripNarration("<u>下划线</u>") = %q, want %q`, got, "下划线")
}
// 白名单与外标签混排:只动白名单那对,其余逐字保留。
in := "她说了 <b>这样</b> 和 <script>alert(1)</script> 两回事"
want := "她说了 这样 和 <script>alert(1)</script> 两回事"