Skip to content

Commit 4fb441a

Browse files
committed
feat(utils): 时间码芯片补毫秒与 data-ts-ms
1 parent 0886206 commit 4fb441a

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

‎app/src/utils/html.test.ts‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ describe("renderTimestampAnchors", () => {
7575
expect(out).not.toContain("]]([[");
7676
});
7777

78-
it("无锚点文本原样返回", () => {
78+
it("无锚点文本原样返回(且不产生任何芯片属性)", () => {
7979
const out = renderTimestampAnchors(escapeHtml(`普通文本没有锚点 123`));
8080
expect(out).toBe("普通文本没有锚点 123");
81+
// 批 7 T15(Y8 加宽):无芯片 ⇒ 不得出现任何芯片定名属性(反"空真")
82+
expect(out).not.toContain("data-ts-ms");
83+
expect(out).not.toContain("data-ts-chip");
8184
});
8285

8386
it("先转义再替换——恶意 HTML 不产生可解析标签(注入面为零)", () => {
@@ -99,5 +102,28 @@ describe("renderTimestampAnchors", () => {
99102
// Act:正则要求分钟为数字——仿形态不匹配,保持转义原文
100103
const out = renderTimestampAnchors(input);
101104
expect(out).toBe(input);
105+
// 批 7 T15(Y8 加宽):仿形态**不得**产出芯片属性(反"误判")
106+
expect(out).not.toContain("data-ts-ms");
107+
expect(out).not.toContain("data-ts-chip");
108+
});
109+
110+
it("芯片带 data-ts-ms 且值 = 锚点里的 ms(T15:跳转目标不再被丢弃)· 两种形态", () => {
111+
// Arrange:段落锚点 + 章节锚点(外带包裹括号)——两条正则都要捕获 ms
112+
// Act
113+
const para = renderTimestampAnchors(escapeHtml(`[⏱ 00:05]([[ts:5000]]) 内容`));
114+
const chapter = renderTimestampAnchors(escapeHtml(`标题 [[⏱ 00:09]([[ts:9000]])]`));
115+
// Assert:段落形态——ms 逐字进 data-ts-ms(`5000` 而非 `00:05`),原始锚点不残留
116+
expect(para).toContain('data-ts-ms="5000"');
117+
expect(para).toContain("data-ts-chip");
118+
expect(para).toContain("⏱ 00:05");
119+
expect(para).not.toContain("[[ts:5000]]");
120+
// Assert:章节形态——第二条正则同样捕获 ms(原实现两条正则都丢 ms)
121+
expect(chapter).toContain('data-ts-ms="9000"');
122+
expect(chapter).toContain("data-ts-chip");
123+
expect(chapter).toContain("⏱ 00:09");
124+
expect(chapter).not.toContain("[[ts:9000]]");
125+
// Assert:视觉与 title 文案**一字未改**(C10.2 的零观感变化要求)
126+
expect(para).toContain('title="⏱ 00:05 跳转到会话对应片段"');
127+
expect(para).toContain("border-bottom:1px dashed #14b8a6");
102128
});
103129
});

‎app/src/utils/html.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,21 @@ export function escapeHtml(s: string): string {
2828
* (无注入面——写入的是我们自己的 HTML)。
2929
* @ai-context: 两种形态:章节锚点 `## 标题 [[⏱ 00:09]([[ts:9000]])]`(外带
3030
* `[...]` 包裹)先匹配,段落锚点 `[⏱ 00:00]([[ts:233]])` 兜底。
31+
* @ai-context: 批 7 T15(C10.2)—— 本函数是 `[[ts:ms]]` 芯片的**唯一实现**(两条手写链
32+
* `NotePreviewView` 与 `refineDiff` 都经它)⇒ 修复只需做一次:① **正则补 ms 捕获组**
33+
* (原实现把 `[[ts:ms]]` 的 ms 整个丢掉 ⇒ 芯片没有跳转目标);② 芯片带
34+
* `data-ts-ms`(毫秒整数,与 `NoteMarkdown` 的 `onOpenSessionAt(sessionId, ms)` 同口径)
35+
* + `data-ts-chip`(定名选择器;`pages/NotesPage.test.tsx` 原按 `title*="跳转到会话"`
36+
* 选元素,唯一性会被稀释)。容器侧用**事件委托** `closest("[data-ts-ms]")` 取目标 ⇒
37+
* 保持串渲染、不需要 React 上下文。视觉样式与 `title` 文案**一字未改**。
38+
* 边界:定位精度受音频对齐的块粒度 **±200 ms** 限制(不得声称毫秒级定位)。
3139
*/
3240
export function renderTimestampAnchors(escaped: string): string {
33-
const chip = (_m: string, mm: string, ss: string): string =>
34-
`<span style="color:#0d9488;border-bottom:1px dashed #14b8a6;background:#f0fdfa;border-radius:3px;padding:0 4px" title="⏱ ${mm}:${ss} 跳转到会话对应片段">⏱ ${mm}:${ss}</span>`;
41+
const chip = (_m: string, mm: string, ss: string, ms: string): string =>
42+
`<span data-ts-ms="${ms}" data-ts-chip style="color:#0d9488;border-bottom:1px dashed #14b8a6;background:#f0fdfa;border-radius:3px;padding:0 4px" title="⏱ ${mm}:${ss} 跳转到会话对应片段">⏱ ${mm}:${ss}</span>`;
3543
return escaped
3644
// 章节形态(含包裹括号):`[[⏱ MM:SS]([[ts:ms]])]`
37-
.replace(/\[\[⏱ (\d+):(\d{2})\]\(\[\[ts:\d+\]\]\)\]/g, chip)
45+
.replace(/\[\[⏱ (\d+):(\d{2})\]\(\[\[ts:(\d+)\]\]\)\]/g, chip)
3846
// 段落形态:`[⏱ MM:SS]([[ts:ms]])`
39-
.replace(/\[⏱ (\d+):(\d{2})\]\(\[\[ts:\d+\]\]\)/g, chip);
47+
.replace(/\[⏱ (\d+):(\d{2})\]\(\[\[ts:(\d+)\]\]\)/g, chip);
4048
}

0 commit comments

Comments
 (0)