fix: 行內元素三分類(透明/隔離/整體)——jinze 下潛肢解 KaTeX 修復(下游問題六);v1 相容層移除
下游問題六:行內公式緊鄰禁則標點時,jinze 為綁鄰字透明下潛 .katex(全樹皆 SPAN,
僅憑標籤名判透明),把公式內側末字連同完整殼層逐層克隆切出(splitoff),上標漂移
錯位;pillSelector/skipAttribute/scope.avoid 三層防護全被刺穿。復現另揭兩向量:
相鄰 pill 時 descend 刺穿致 jz-hws 插入公式內部;pill 內部標點被 jiya charify。
確立三分類(§4.1,所有遍歷原語共用同一判定):
透明(styleInlines+jz-*)— 穿越/run 延續/可克隆切分;
隔離(isolateSelector,預設 "mark",新增選項)— 邊界斷 run/斷相鄰/不可切分、
兩端補隙(同 pill),內部照常處理(自成脈絡);
整體(pillSelector)— 內部對一切 pass 不可見(insidePill 過濾),禁則時作為
原子鄰字單位整體綁入 jz-jinze。
機制修復:
finder — transparentInline 改以元素判定(先排除 pill/isolate/avoid 再查標籤集);
avoidsSelf 自身判定(isAvoided 僅查祖先,自身命中 avoid 曾被穿越);descend 三態
(邊界字/無內容續找兄弟/blocked 終止——不可越界跳找更遠之字);eachTextNode
過濾 pill 內部;collectRuns 隔離元素內部自成 run。
core/split — canSplit 諮詢 finder(pill/isolate/avoid 不可切 → 退化整綁);
MARK 移出 SPLITTABLE(背景盒切分有可見接縫)。
jiyaAdjacency — pill/avoid(自身判定)斷相鄰不下探;isolate 斷而內部自成脈絡。
spacing Pass B — 邊界選擇器擴為 pill ∪ isolate;巢於 pill 內者跳過。
orphan — avoidsSelf 邊界;avoid 塊不綁(舊漏判:avoid 子樹內裸文本曾被計數包綁)。
v1 相容層移除(消費端已全數遷移 v2 API):src/compat/v1.ts、IIFE 全域別名
createCjkAutospace、死選項 autospaceClass(經查從未被任何 pass 讀取)一併刪;
I8 退役。dist/compat 殘留聲明清除、dist/core/split.d.ts 補入庫(前次遺漏)。
測試 112 全綠(問題六 ×7、isolate ×6、compat 移除 ×1);headless Chrome 實證
公式完整(splitoff=0、內部無 jz-hws/jz-char)、mark 與公式整體入 jz-jinze。
文檔:README 三分類節+選項示例+功能表(hanging 行補「已移除」);ARCHITECTURE
§4.1 三分類/§3.5 相容層移除/§6.1/§6.3/§6.4/I8 退役。
This commit is contained in:
+43
-28
@@ -50,9 +50,9 @@
|
||||
移除,DOM 回到 pass 前狀態(至少邏輯等價)。
|
||||
- **I7 邊界感知**:跨樣式行內元素的鄰接(如 `<strong>是</strong>A`)須能識別並
|
||||
正確處理——由核心 finder 統一提供,各 pass 不自行重複造輪。
|
||||
- **I8 向下相容 v1**:既有 `createCjkAutospace(options).apply(root)` 消費端
|
||||
(build.sh 單檔串接、Nvim peek、Claude 模板)**不得 break、無功能倒退**。行為
|
||||
**不要求與 v1 完全一致**,但須等價可用;以相容層達成(見 §3.5)。
|
||||
- **I8 向下相容 v1(已退役)**:相容層曾保障 v1 消費端(build.sh 單檔串接、Nvim
|
||||
peek、Claude 模板)過渡期不 break;消費端遷移 v2 API 完成後相容層已移除(見
|
||||
§3.5),此不變量隨之退役、編號保留不復用。
|
||||
- **I9 遍歷聚合**:邏輯上「一功能一 pass」,但執行上**相容之 pass 共享單次遍歷**
|
||||
(見 §3.4),以減少全樹走訪次數;聚合不得犧牲 I6(冪等可還原)與逐功能開關。
|
||||
- **I10 作用域與分塊**:作用範圍可由 `scope.include` 選擇器限定(opt-in);功能集
|
||||
@@ -175,27 +175,13 @@ for phase in phases:
|
||||
> 只是 P1 諸 pass 的 `collect` 在同一走訪內完成。若某 pass 不相容(需獨立走訪),
|
||||
> 退化為自跑一次,不影響正確性。
|
||||
|
||||
### 3.5 v1 向下相容層(I8,`src/compat/v1.ts`)
|
||||
### 3.5 v1 向下相容層 — 已移除(I8 退役)
|
||||
|
||||
導出 `createCjkAutospace(options)`,把 v1 API 映射到 `createJuzhen`,**保證不
|
||||
break、無功能倒退**,行為等價但不要求完全一致:
|
||||
|
||||
| v1 選項/行為 | 映射 |
|
||||
|---|---|
|
||||
| `.apply(root)` | → `.render(root)` |
|
||||
| `pillSelector` | → pill 鄰接處理 + avoid |
|
||||
| `styleInlines`/`blockTags`/`skipTags` | → finder 設定 |
|
||||
| `autospaceClass`(預設 `cjk-autospace`) | → 套用於 `jz-hws` 之 class,使既有
|
||||
`.cjk-autospace{…}` CSS 仍命中間隙元素 |
|
||||
| `skipAttribute`/`isSkipped` | → avoid 判定 |
|
||||
| `longWordWrap` | → `longWord` |
|
||||
| `punctuationSqueeze`(v1 halt 句內半角) | → `jiya`(margin 開明/全角;行為近似,
|
||||
非逐位等同——符合「不要求完全一致」) |
|
||||
|
||||
IIFE 構建額外掛 `globalThis.createCjkAutospace` 別名,使
|
||||
`globalThis.createCjkAutospace` 之消費端(Nvim peek/模板)零改動可跑。相容層之
|
||||
差異(間隙元素由 `span.cjk-autospace` 變為 `jz-hws[class]`、擠壓由 halt 變 margin)
|
||||
於 README 明列。
|
||||
相容層(`src/compat/v1.ts`:`createCjkAutospace` 導出、IIFE 之全域別名、
|
||||
`autospaceClass` 選項)曾把 v1 API 映射到 `createJuzhen`,服務 build.sh 單檔串接/
|
||||
Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費端遷移 v2 API 完成後
|
||||
已整體移除**(`autospaceClass` 經查從未被任何 pass 讀取,屬死選項,一併刪)。v1
|
||||
原始碼保存於 `v1-autospace` 分支。
|
||||
|
||||
### 3.6 作用域與分塊功能選擇(I10)
|
||||
|
||||
@@ -248,9 +234,31 @@ IIFE 構建額外掛 `globalThis.createCjkAutospace` 別名,使
|
||||
設計要點:finder 不知道任何具體排版規則;它只提供「在何處、跨何種邊界、對哪些
|
||||
字元」的遍歷與安全變更原語。規則由各 pass 以 unicode 表 + 回呼注入。
|
||||
|
||||
**行內元素三分類(下游問題六後確立)**:行內元素對排版有且僅有三種口徑,所有遍歷
|
||||
原語(`collectRuns`/`adjacentLogicalChar`/`eachTextNode`、`jiyaAdjacency` 之相鄰
|
||||
掃描、`core/split` 之可切判定、orphan 之實義字收集)共用同一判定:
|
||||
|
||||
- **透明**(`styleInlines` + wrap 類 `jz-*`)——穿越、run 延續、禁則可克隆切分。
|
||||
- **隔離**(`isolateSelector`,預設 `mark`)——邊界斷 run/斷相鄰/不可切分、兩端
|
||||
補隙(同 pill),**內部照常處理**(自成脈絡)。典型:帶背景色之 `<mark>`(切分
|
||||
背景盒有可見接縫,故亦自 `SPLITTABLE` 白名單移除)。
|
||||
- **整體**(`pillSelector`)——原子單位:內部對一切 pass **不可見**(`eachTextNode`
|
||||
過濾 `insidePill`,不 charify/不插間隙/不切分/不下潛),禁則時作為原子鄰字
|
||||
單位**整體綁入** `jz-jinze`。典型:`code`/`kbd`、行內公式 `.katex`。
|
||||
|
||||
判定**以元素為準、非標籤名**(`transparentInline(el)`,先排除 pill/isolate/
|
||||
avoid 再查標籤集):KaTeX 全樹皆 `<span>`,舊版按標籤名判透明曾令 `canSplit`/
|
||||
`descend` 整棵下潛——禁則把公式內側末字連同完整殼層逐層克隆切出(`splitoff`),
|
||||
上標漂移錯位(下游問題六);`pillSelector`/`skipAttribute`/`scope.avoid` 三層
|
||||
防護全被刺穿。同根修復:**avoid 以自身判定**(`avoidsSelf(el)`——`isAvoided` 僅查
|
||||
祖先鏈,元素自身命中 avoid 時曾被穿越);`adjacentLogicalChar` 之 `descend` 改三態
|
||||
(邊界字/無內容續找兄弟/**blocked** 終止——遇 pill/isolate/avoid 不可跳過續找
|
||||
更遠之字,否則重蹈「越過 `jz-char` 取更前字 → 誤隙」之同型錯誤);`core/split` 之
|
||||
`canSplit` 諮詢 finder(pill/isolate/avoid 一律不可切、退化整綁)。
|
||||
|
||||
**「行內透明」原則之一致性(下游 Bug 修復)**:「邏輯文本應透明穿越之行內元素」一語
|
||||
須在所有遍歷原語中**口徑一致**,否則同一段文本在不同原語下被切成不同邏輯串、生成相
|
||||
互矛盾之結果。`isTransparentInline` 為單一真相源,列入:樣式行內標籤、`jz-hws`/
|
||||
互矛盾之結果。`transparentInline` 為單一真相源,列入:樣式行內標籤、`jz-hws`/
|
||||
`jz-jinze`(wrap 群組)、**`jz-char`/`jz-inner`(標點原子)**。後者必須透明,否則
|
||||
`adjacentLogicalChar` 之 `descend` 遇標點原子既不進入亦不終止、而是**跳過**它續找更前
|
||||
之兄弟——`漢字。<code>` 經禁則綁定後,pill 之左鄰字應為「。」(標點,非 pill 鄰字、不
|
||||
@@ -360,11 +368,13 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
- **copy-clean(I1,較 Han 更純)**:間隙為純視覺 margin,`textContent` 不增不減
|
||||
語義字元,剪貼板得緊湊原文。
|
||||
- **切片**:涵蓋 v1 Pass A、Pass B(pill `code/kbd` 邊界亦走同一機制——左字或
|
||||
pill 元素包入 `jz-hws`;pill 由 avoid 與鄰接判定處理)。
|
||||
pill 元素包入 `jz-hws`;pill 由 avoid 與鄰接判定處理)。Pass B 之邊界選擇器為
|
||||
**pill ∪ isolate**(§4.1 三分類:隔離元素兩端補隙口徑與 pill 一致,差異僅在
|
||||
內部處理;巢於 pill 內者跳過)。
|
||||
- **邊界正規化一致性(下游回饋 1a/1b 修)**:
|
||||
- **1a pill 穿越 jz-jinze**:jinze(order 40)先於 spacing(order 50),會把尾隨
|
||||
標點之 pill 納入 `jz-jinze`。`adjacentLogicalChar` 須**透明穿越 `jz-jinze`**
|
||||
(與 `jz-hws`、樣式行內同列,見 `finder.isTransparentInline`),否則 pill 左鄰字
|
||||
(與 `jz-hws`、樣式行內同列,見 `finder.transparentInline`),否則 pill 左鄰字
|
||||
不可達、左緣間隙與作者空格剝除雙失。
|
||||
- **1b CJK↔CJK 贅餘空格**:中文不用詞間空格,故 CJK 表意字之間的單一 ASCII 作者
|
||||
空格(含跨樣式行內標籤,`collectRuns` 已透明合併)一律**剝除、不留隙**(以
|
||||
@@ -458,7 +468,8 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
(pause 右空白 + open 左空白)兩側皆收 → 全角式與開明式皆合占 1em;`。」`(stop 右空白;
|
||||
」左為墨)只收 `。`→ 全角式 1.5em/開明式 1em(」baseline 已半);`·—`(皆無指向對方之
|
||||
空白)皆不收 → 兩式皆 2em(不宜挤之例外)。多標點鏈逐對處理。`jiyaAdjacencyPass` 以**從 root 之透明遍歷**(穿越 strong/
|
||||
em… 等樣式行內;block 邊界與 avoid 子樹斷相鄰;jz-char 為原子不下探)取代原「同 parent
|
||||
em… 等樣式行內;block/pill/avoid(自身判定)斷相鄰且不下探,isolate 斷相鄰但內部
|
||||
自成脈絡照常配對——§4.1 三分類;jz-char 為原子不下探)取代原「同 parent
|
||||
直接兄弟」掃描,使**跨 inline 邊界**之連續標點亦正確配對(原漏致跨 strong/em 之標點對未挤、
|
||||
達 2em)。
|
||||
- **`margin` 後備模式(.feedback 增補)**:消費端字型未必含 `halt`(多數系統字型、
|
||||
@@ -497,7 +508,11 @@ class 約定:`bd`(標點)、`bd-open/close/cop/stop/sep/liga`、`cjk`、`j
|
||||
把邊界詞元剝入一個淺克隆(保留標籤/class/style/lang,去 id),只綁該克隆(最小綁定),
|
||||
原元素其餘部分留原位、渲染視覺無縫。克隆標 `data-jz-kind="splitoff"`,`revertPass` 之**延後
|
||||
階段**(先解包 wrap、後併 splitoff,確保前兄弟為原元素而非 wrap 殼)併回原元素、還原一致
|
||||
(I6)。僅純樣式標籤白名單可切;`a`/`abbr`/`ruby`/帶 id 者退化為整元素綁定(安全)。
|
||||
(I6)。僅純樣式標籤白名單可切,且 `canSplit` **諮詢 finder 三分類**(§4.1):`a`/`abbr`/
|
||||
`ruby`/帶 id 者,以及 **pill(整體)/isolate(隔離)/avoid 自身命中者**,一律退化為
|
||||
整元素綁定(安全)——pill 即「作為原子鄰字單位整體綁入 `jz-jinze`」之期望行為(下游問題六:
|
||||
KaTeX 全樹皆 SPAN,舊版僅憑標籤白名單曾整棵下潛肢解公式);isolate(如背景色 `mark`)切分
|
||||
有可見接縫故整綁;`MARK` 並自白名單移除。
|
||||
- **切片**:切片 1 完成行首行末避頭尾(閉類/點號避頭、開類避尾);行首縮排聯動、
|
||||
更細之禁則對象列後續,沿用同一 `jz-jinze` 結構。
|
||||
- **已知交互(待瀏覽器精修)**:`lineedge` 之行端偵測需跨 `jz-jinze` 群組邊界向上
|
||||
|
||||
Reference in New Issue
Block a user