Skip to content

Commit 0886206

Browse files
committed
refactor(utils): 两支手写链归一为行级渲染单实现
1 parent 04fce5f commit 0886206

2 files changed

Lines changed: 113 additions & 9 deletions

File tree

‎app/src/utils/markdownLine.ts‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* markdownLine — 行级 markdown → HTML 串渲染的**唯一实现**(批 7 T15 / §C10.1 的归一落点)。
3+
*
4+
* @ai-context: 迁移前有**两条各自独立**的行级 HTML 串生成器——① `components/NotePreviewView.tsx`
5+
* 的 `renderMarkdown`(会话笔记预览;346 行文件、**无同名测试**、无回归网)② `utils/refineDiff.ts`
6+
* 的 `mdLineHtml`(精修工作台行级 diff)。两支链**逻辑同源、样式不同**(`### 表 6` X6 实测:
7+
* 预览侧 `font-size:15px/13px/12px` + `margin:10px 0 4px`,工作台侧 `14px/13px/12px` +
8+
* `margin:8px 0 3px` + diff 染色 `extra` 参数)⇒ 本模块导出**一支实现** + **两个具名模式常量**,
9+
* 两个模式的值**逐字取自原实现** ⇒ 两条链的可见输出**零变化**(§C11.1 的三条硬条件之一)。
10+
* 🔴 **归一 = 逻辑归一;观感统一未做,归批 8**(观感/像素属不可测面,§C6.4)。
11+
* @ai-context: 为什么**不**换成 `react-markdown`:本模块的产物是交给 `dangerouslySetInnerHTML`
12+
* 的**字符串**,换渲染器 = 改 DOM 形态 / 样式 / 安全面,而迁移前的 #4 **无回归网**(直接改 = 裸奔)
13+
* ⇒ §C10.1 逐字禁止。
14+
* @ai-context: 安全面**与迁移前逐字同序**:每行文本先 `escapeHtml` 再 `renderTimestampAnchors`
15+
* (`utils/html.ts` 是两者的唯一定义源);本模块不新增任何拼接出口,也不引入 React 上下文。
16+
* 边界:① **不覆盖** `renderMarkdown` 的屏配图分支(`- ![alt](src)`)——它需要 `convertFileSrc` +
17+
* `imageBaseUrl` / `dataDir` 两个**运行时**值,塞不进模式常量;该分支的迁移由 T17 在容器侧处置
18+
* (本模块对它按段落档处理 ⇒ 迁移前**不得**用本模块渲染配图行)。② 芯片点击不在本模块(串渲染没有
19+
* React 上下文)⇒ 由容器侧事件委托 `closest("[data-ts-ms]")` 处理(§C10.2)。
20+
*/
21+
import { escapeHtml, renderTimestampAnchors } from "./html";
22+
23+
/**
24+
* 行级渲染的**模式**(两个具名常量见下)。
25+
*
26+
* @ai-context: 模式只描述**样式族**与**染色后缀**,不描述逻辑 ⇒ 逻辑永远只有一支实现。
27+
*/
28+
export interface LineRenderMode {
29+
/** 各分支的内联样式串(每个值**逐字**取自迁移前的原实现) */
30+
readonly scale: {
31+
readonly h2: string;
32+
readonly h3: string;
33+
/** `### ` 档——🔴 **可选**:`PREVIEW` 的原实现没有这一档(`### x` 落到段落档) */
34+
readonly h4?: string;
35+
/** `> ` 档——🔴 **可选**:`COMPACT` 的原实现没有这一档(`> x` 落到段落档) */
36+
readonly quote?: string;
37+
readonly li: string;
38+
/** 段落档(**未染色**时的基底) */
39+
readonly p: string;
40+
/** 段落档在**染色**(`extra` 非空)时的基底——🔴 `COMPACT` 原实现的染色分支**不给 `color`**
41+
* (`font-size:12px;margin:2px 0`),与未染色分支的 `p`(含 `color:#374151`)**不是同一个串**;
42+
* 单列才能保证逐字节对拍不红。缺省 ⇒ 复用 `p`。 */
43+
readonly pStained?: string;
44+
};
45+
/** 追加在同一元素 `style` 末尾的额外样式(`refineDiff` 的 diff 三态染色;空串等价于无) */
46+
readonly extra?: string;
47+
}
48+
49+
/** **会话笔记预览模式**——值逐字取自 `NotePreviewView.renderMarkdown` 的 h2/h3/quote/li/p 五档。 */
50+
export const PREVIEW_MODE: LineRenderMode = {
51+
scale: {
52+
h2: "font-size:15px;margin:10px 0 4px",
53+
h3: "font-size:13px;margin:8px 0 4px;color:#0f766e",
54+
quote: "font-size:12px;color:#b45309;background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:6px 10px;margin:6px 0",
55+
li: "font-size:12px;color:#4b5563",
56+
p: "font-size:13px;color:#374151;margin:4px 0",
57+
},
58+
};
59+
60+
/** **精修工作台模式**——值逐字取自 `refineDiff.mdLineHtml` 的 h2/h3/h4/li/p 五档(染色由 `extra` 追加)。 */
61+
export const COMPACT_MODE: LineRenderMode = {
62+
scale: {
63+
h2: "font-size:14px;margin:8px 0 3px",
64+
h3: "font-size:13px;margin:6px 0 2px;color:#0f766e",
65+
h4: "font-size:12px;margin:4px 0 2px;color:#374151",
66+
li: "font-size:12px;color:#4b5563",
67+
p: "font-size:12px;color:#374151;margin:2px 0",
68+
pStained: "font-size:12px;margin:2px 0",
69+
},
70+
};
71+
72+
/** 染色后缀(原实现逐字形态 `extra ? ";" + extra : ""`——空串等价于无染色) */
73+
function tinted(base: string, extra: string | undefined): string {
74+
return extra ? `${base};${extra}` : base;
75+
}
76+
77+
/** 行内文本 → 芯片 HTML(**唯一出口**:`escapeHtml` → `renderTimestampAnchors`,与迁移前同序) */
78+
function inline(text: string): string {
79+
return renderTimestampAnchors(escapeHtml(text));
80+
}
81+
82+
/**
83+
* 单行 markdown → HTML 串(两个模式共用**这一支**实现)。
84+
*
85+
* @ai-context 分支顺序与迁移前两支链**逐字同序**(标题→引用→列表→空行→段落);`h4` / `quote`
86+
* 两个档位缺失时**整条分支跳过** ⇒ 缺失档的输入落回后续分支(`PREVIEW` 的 `### x` 落段落档、
87+
* `COMPACT` 的 `> x` 落段落档),与原实现一致。
88+
* 边界:输入是**单行**(不含 `\n`);空行与纯空白行返回空串(不产生可见 HTML)。
89+
*/
90+
export function mdLineHtml(line: string, mode: LineRenderMode): string {
91+
const { extra } = mode;
92+
const s = mode.scale;
93+
if (line.startsWith("# ")) return `<h2 style="${tinted(s.h2, extra)}">${inline(line.slice(2))}</h2>`;
94+
if (line.startsWith("## ")) return `<h3 style="${tinted(s.h3, extra)}">${inline(line.slice(3))}</h3>`;
95+
if (s.h4 && line.startsWith("### ")) return `<h4 style="${tinted(s.h4, extra)}">${inline(line.slice(4))}</h4>`;
96+
if (s.quote && line.startsWith("> ")) return `<div style="${tinted(s.quote, extra)}">${inline(line.slice(2))}</div>`;
97+
if (line.startsWith("- ")) return `<div style="${tinted(s.li, extra)}">• ${inline(line.slice(2))}</div>`;
98+
if (line.trim() === "") return "";
99+
return `<p style="${extra ? `${s.pStained ?? s.p};${extra}` : s.p}">${inline(line)}</p>`;
100+
}

‎app/src/utils/refineDiff.ts‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* #6b7280),不新造配色。
1515
*/
1616
import type { DiffOp } from "../types";
17-
import { escapeHtml, renderTimestampAnchors } from "./html";
17+
import { escapeHtml } from "./html";
18+
import { COMPACT_MODE, mdLineHtml as renderMdLine } from "./markdownLine";
1819

1920
/** 行级三态(与后端 DiffOp 标签同名——serde 小写 tag) */
2021
export type DiffRowKind = "unchanged" | "added" | "removed";
@@ -74,16 +75,19 @@ export function mdFallbackRows(md: string): DiffRow[] {
7475
return md.split("\n").map((text) => ({ kind: "unchanged" as const, text }));
7576
}
7677

77-
/** 单行 markdown → 轻量 HTML(kind 染色在行内联样式末尾追加——后者覆盖前色) */
78+
/**
79+
* 单行 markdown → 轻量 HTML(kind 染色在行内联样式末尾追加——后者覆盖前色)。
80+
*
81+
* @ai-context 批 7 T15(§C10.1 归一):行级渲染的**实现已迁入 `utils/markdownLine.ts`**(两支
82+
* 手写链合成一支),本函数保留为**薄转调**以保住既有 import 面(`utils/refineDiff.test.ts` 与
83+
* `components/RefineWorkbench.tsx` 的 `renderSideHtml` / `renderDiffColumnHtml` 一个字不改)。
84+
* 传入 `COMPACT_MODE` + 本栏三态染色 ⇒ 输出与迁移**逐字节相同**(对拍证据见 `tmp/t15/`)。
85+
* 边界:染色档的段落基底与未染色档不同(前者不含 `color`)—— 该差异由 `COMPACT_MODE.scale.pStained`
86+
* 承载,不在本文件里再写一份样式字面量(避免双真源)。
87+
*/
7888
export function mdLineHtml(line: string, kind: DiffRowKind): string {
7989
const extra = sideStyleFor(kind);
80-
const style = extra ? `font-size:12px;margin:2px 0;${extra}` : "";
81-
if (line.startsWith("# ")) return `<h2 style="font-size:14px;margin:8px 0 3px${extra ? ";" + extra : ""}">${renderTimestampAnchors(escapeHtml(line.slice(2)))}</h2>`;
82-
if (line.startsWith("## ")) return `<h3 style="font-size:13px;margin:6px 0 2px;color:#0f766e${extra ? ";" + extra : ""}">${renderTimestampAnchors(escapeHtml(line.slice(3)))}</h3>`;
83-
if (line.startsWith("### ")) return `<h4 style="font-size:12px;margin:4px 0 2px;color:#374151${extra ? ";" + extra : ""}">${renderTimestampAnchors(escapeHtml(line.slice(4)))}</h4>`;
84-
if (line.startsWith("- ")) return `<div style="font-size:12px;color:#4b5563${extra ? ";" + extra : ""}">• ${renderTimestampAnchors(escapeHtml(line.slice(2)))}</div>`;
85-
if (line.trim() === "") return "";
86-
return `<p style="${style || "font-size:12px;color:#374151;margin:2px 0"}">${renderTimestampAnchors(escapeHtml(line))}</p>`;
90+
return renderMdLine(line, extra ? { ...COMPACT_MODE, extra } : COMPACT_MODE);
8791
}
8892

8993
/** 并排双栏渲染(unchanged 行输出与旧 renderMd 字节一致——渲染回归零漂移) */

0 commit comments

Comments
 (0)