docs: 釐清消費端元素分類與功能分級標記
This commit is contained in:
@@ -59,6 +59,14 @@ import vectorsSchema from "cjk-autospace/rules/vectors-schema" with { type: "jso
|
||||
|
||||
## 用法
|
||||
|
||||
呼叫端接入時須做兩個獨立選擇:
|
||||
|
||||
1. **元件如何參與排版**:依「行內元素三分類」選 pill(保護內部、處理交界)、
|
||||
isolate(內部也排版、保持盒子完整)或 avoid(完全排除)。
|
||||
2. **文字需要哪些功能**:依「功能分級」選 text(標題/UI 文字)或 paragraph(正文)。
|
||||
|
||||
完整操作範例與衝突規則見下方兩節;不要用其中一種分類代替另一種。
|
||||
|
||||
### 原生 `<script>`
|
||||
|
||||
```html
|
||||
@@ -228,12 +236,12 @@ createJuzhen({
|
||||
| `skipAttribute` | string | `null` | 自訂「跳過」屬性名(命中即 avoid) |
|
||||
| `isSkipped` | `(node)=>bool` | `null` | 自訂跳過謂詞(函數級排除鉤子) |
|
||||
|
||||
**HTML 屬性**(就近繼承,最近祖先勝)
|
||||
**HTML 屬性**(各屬性之繼承/衝突規則見對應小節)
|
||||
|
||||
| 屬性 | 值 | 作用 |
|
||||
|---|---|---|
|
||||
| `data-jz-level` | `text|paragraph` | 逐子樹功能分級 |
|
||||
| `data-jz-style` | `quanjiao|kaiming|banjiao` | 逐子樹標點樣式覆寫(近祖先勝、優先於選擇器與 lang) |
|
||||
| `data-jz-style` | `quanjiao|kaiming|banjiao` | 逐子樹標點樣式覆寫(近祖先勝、同層優先於選擇器,整體優先於 lang) |
|
||||
| `data-juzhen` | 功能名(空格分隔) | 白名單:僅列出之功能生效 |
|
||||
| `data-juzhen-off` | 功能名 | 黑名單:停用列出之功能 |
|
||||
| `data-jz-skip` | (存在即可) | 標記元素為 avoid(內外皆不處理) |
|
||||
@@ -271,38 +279,63 @@ lang。半角式渲染沿用既有 `halt`/`margin` 機制(見下節),本
|
||||
|
||||
### 行內元素三分類:透明/隔離/整體
|
||||
|
||||
行內元素對排版有且僅有三種口徑。**邏輯文本流之遍歷原語**(中西間隙、擠壓相鄰、禁則綁定、
|
||||
垂懸字)**共用同一判定** `isTransparent`;未在 `styleInlines` 之**未知行內標籤**(自訂元素/
|
||||
web component 等)採**嚴格白名單**、一律當**邊界**(語義同隔離、內部自成脈絡),透明須顯式
|
||||
登記 `styleInlines`。(另兩層各有其正當之邊界、刻意不併入:結構克隆切分之安全白名單
|
||||
`canSplit`、行端物理版面測量 `lineedge`,詳見 `ARCHITECTURE.md`。)
|
||||
**先決定哪些內容交給聚珍處理,再決定它與前後文字如何銜接。** 呼叫端須主動選擇
|
||||
pill/isolate/avoid;不要把「保護內部內容」一律寫成跳過,也不要只依元素外觀猜分類。
|
||||
普通 `strong`/`em`/`span` 等只改文字樣式的元素通常保持透明,無須額外標記。
|
||||
|
||||
| 分類 | 選擇器 | 邊界 | 內部 | 典型 |
|
||||
| 你的需求 | 選擇 | 元素內部 | 與外部文字的交界 | 如何啟用 |
|
||||
|---|---|---|---|---|
|
||||
| **透明** | `styleInlines`(預設 27 個常見樣式標籤;**未列之行內標籤=邊界**) | 穿越——間隙/相鄰/run 皆延續 | 照常處理;禁則可**克隆切分**(最小綁定) | `strong`/`em`/`b`/`i`/`a`/`span`(自訂元素須自行登記) |
|
||||
| **隔離** | `isolateSelector`(預設 `"mark"`) | 斷 run/斷相鄰/不可切分;**兩端**與 CJK/西文相鄰時補 `jz-hws` 間隙 | **照常處理**(自成脈絡:內部標點照擠、內部中西照補隙) | 帶背景色之 `<mark>`、badge 類盒樣式行內 |
|
||||
| **整體** | `pillSelector`(預設 `"code, kbd"`) | 同隔離(邊界斷、兩端補隙) | **完全不可見**——不 charify、不插間隙、不切分、不下潛 | `code`/`kbd`、行內公式 `.katex`、引用標 `.cite-ref` |
|
||||
| 內部由程式碼/公式等元件自己管理,但仍要接入正文排版 | **pill(整體)** | 不進入、不改寫內部文字、不拆分元素 | 視為整體;依相鄰字符規則補間隙,禁則需要時連同整個元素綁定 | `pillSelector` |
|
||||
| 內部是普通文字,仍要補間隙、處理標點,但盒子不能被拆開 | **isolate(隔離)** | 照常排版,內部自成文字脈絡 | 與外部隔開;依相鄰字符規則補間隙,禁則需要時整體綁定 | `isolateSelector` |
|
||||
| 這一區完全不交給聚珍,連交界間隙也自行管理 | **avoid(排除)** | 不處理整個子樹 | 停止跨界排版,不替它建立 pill/isolate 式交界間隙 | `data-jz-skip` 或 `scope.avoid` |
|
||||
| 只是粗體、斜體等樣式,文字仍屬同一句 | **透明(一般文字)** | 照常排版 | 前後文字連續判斷;安全的樣式元素可能被拆分後於回退時合併 | 預設 `styleInlines` 中的標籤 |
|
||||
|
||||
要點:
|
||||
**pill 與 avoid 都保護內部,差別在外部:pill 仍參與正文交界排版;avoid 完全退出。**
|
||||
**pill 與 isolate 都保持元素完整,差別在內部:pill 不處理;isolate 照常處理。**
|
||||
這裡的「完整」指聚珍不拆分元素,不代表替任意元件提供 CSS `white-space:nowrap`。
|
||||
交界也不是無條件各加一格:須符合相鄰字符規則,例如句號緊接 pill 不會因此多補間隙。
|
||||
|
||||
- **分類優先序(單一判定 `finder.category`)**:一元素恰屬其一,多選擇器同命中時依序短路定案——
|
||||
`avoid > pill > isolate > block > transparent > opaque`(`opaque`=未登記之未知行內標籤,
|
||||
當隔離式邊界)。故帶 `data-jz-skip` 之 pill/isolate 判為 avoid;同命中 pill 與 isolate 判為 pill。
|
||||
邏輯文本流各原語(`collectRuns`/`jiyaAdjacency`/`adjacentLogicalChar`)皆據此**同一分類**分派。
|
||||
- **判定以元素為準、非標籤名**:KaTeX 全樹皆 `<span>`,但 `.katex` 命中 `pillSelector`
|
||||
即為整體邊界——舊版按標籤名判透明曾整棵下潛、把公式內側末字連同殼層克隆切出(肢解)。
|
||||
- **pill 行為覆蓋 isolate**:兩者邊界口徑完全相同(兩端補隙、斷 run/斷相鄰、不可切分,
|
||||
同一段邊界處理代碼),差異僅在內部;同一元素同時命中兩選擇器時 **pill 勝出**(內部不可見)。
|
||||
- **禁則之整體綁定**:pill 緊鄰禁則標點時(`…$公式$,`),整個 pill 作為原子鄰字單位
|
||||
綁入 `jz-jinze`(公式本不可斷,無額外代價);隔離元素同樣整綁(切分背景盒有可見接縫)。
|
||||
- **avoid(排除)之七種來源**,任一命中即邊界、內外皆不處理:`isSkipped` 謂詞/`skipTags`
|
||||
標籤集/SVG/`contentEditable`/`data-jz-skip` 屬性/`skipAttribute`(自訂屬性名)/
|
||||
`scope.avoid` 選擇器。**以自身判定**(元素自身命中即是,非僅查祖先);一次性檢查用
|
||||
`isAvoided`(自身或祖先之組合判定)。**`skipTags` 預設不含 `CODE`/`KBD`**——二者已由
|
||||
`pillSelector` 以 pill 涵蓋(三分類互斥;若把 `code` 移出 `pillSelector`,須自行加入
|
||||
`skipTags`/`scope.avoid`,否則其內部將被當普通文本處理)。
|
||||
- **行內公式/夾注引用宜列為 pill**:`pillSelector: "code, kbd, .katex, .cite-ref"`——
|
||||
獨立行內單位以 pill 邊界邏輯統一處理(兩側補隙、吸收作者空格、整體綁定)。
|
||||
#### 主動指定你的元件
|
||||
|
||||
以下 class 名稱由呼叫端自行命名,透過選擇器接入;它們不是聚珍內建標記。
|
||||
`data-jz-skip` 則是內建屬性,存在即排除,寫成 `data-jz-skip="false"` 仍會排除。
|
||||
|
||||
```html
|
||||
<article class="juzhen" lang="zh-Hant">
|
||||
<p>執行<code>npm test</code>後查看結果。</p>
|
||||
<p>公式<span class="formula">x+y</span>由公式元件管理。</p>
|
||||
<p>狀態<mark class="badge">版本API,已更新</mark>仍要處理內部中西間隙與標點。</p>
|
||||
<p>外部元件<span data-jz-skip>原樣保留 API ,</span>的交界由呼叫端自行處理。</p>
|
||||
<div class="raw-widget">這個子樹也完全排除。</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
```js
|
||||
const jz = Juzhen.createJuzhen({
|
||||
scope: { include: ".juzhen", avoid: ".raw-widget" },
|
||||
pillSelector: "code, kbd, .formula, .cite-ref",
|
||||
isolateSelector: "mark, .badge",
|
||||
});
|
||||
jz.render();
|
||||
```
|
||||
|
||||
- **pill 預設只有 `code, kbd`**。公式 `.katex`、引用標 `.cite-ref` 等須自行加入。
|
||||
自訂 `pillSelector` 是取代預設選擇器;要保留 code/kbd,須像上例一併列出。
|
||||
- **isolate 預設為 `mark`**。自訂 `isolateSelector` 同樣取代預設;傳 `""` 停用此選擇器。
|
||||
只有背景色的 `span` 不會因外觀自動變成 isolate,應加 class 並列入選擇器。
|
||||
- **avoid** 可用 `data-jz-skip` 或 `scope.avoid` 主動指定;另外 `skipTags`、SVG、可編輯內容、
|
||||
`skipAttribute` 和 `isSkipped` 也可能使元素被排除。子元素不能以 pill/isolate 或
|
||||
`data-jz-level="paragraph"` 重新打開已被排除的祖先子樹。
|
||||
- **同一元素多重命中時:avoid > pill > isolate**。例如同時命中 `.formula` 與 `.badge`
|
||||
的元素採 pill;再加 `data-jz-skip` 則採 avoid。請避免互相矛盾的選擇器。
|
||||
- 從 `pillSelector` 移除 `code`/`kbd` 不等於排除它們:預設 `skipTags` 不含二者。
|
||||
若要完全跳過,須另列入 `scope.avoid`/`skipTags`。
|
||||
- 未列在 `styleInlines` 的未知行內標籤會形成隔離式邊界,不會自動當透明樣式。
|
||||
`styleInlines` 接受**大寫標籤名陣列**,與 pill/isolate 的 CSS 選擇器不同。
|
||||
內部結構由第三方管理的元件宜明確指定 pill 或 avoid,不要為了穿越它而加入透明名單。
|
||||
|
||||
分類不等於功能級別。isolate 內部是否執行禁則、避孤字等,仍由下節的 text/paragraph
|
||||
及功能開關決定;pill/avoid 內部則根本不進入文字處理。
|
||||
|
||||
### 分頁器(Paged.js 等)相容:`justifyAtoms`
|
||||
|
||||
@@ -317,63 +350,99 @@ web component 等)採**嚴格白名單**、一律當**邊界**(語義同隔
|
||||
|
||||
### 分塊功能選擇(HTML 屬性,就近繼承)
|
||||
|
||||
先按下一節選擇 text/paragraph,再用功能白名單或黑名單微調。
|
||||
它們不改變 pill/isolate/avoid 分類,也不會繞過級別限制。
|
||||
|
||||
```html
|
||||
<p data-juzhen="spacing jiya">…</p> <!-- 白名單:僅此二功能 -->
|
||||
<p data-juzhen-off="jiya">…</p> <!-- 黑名單:停用 jiya,其餘繼承 -->
|
||||
<div lang="zh-Hans">…</div> <!-- lang 驅動全角/開明 -->
|
||||
<p data-juzhen="spacing jiya">只選中西間隙與標點擠壓。</p>
|
||||
<p data-juzhen-off="orphan longWord">保留其他功能,停用避孤字與長詞斷字。</p>
|
||||
```
|
||||
|
||||
功能名區分大小寫,以空白分隔。一般呼叫端使用 `spacing`、`jiya`、`jinze`、`orphan`、
|
||||
`longWord`;前述相容 no-op 名稱不會產生效果。
|
||||
|
||||
對每個功能,從元素自身向上找:同一元素先取 `data-juzhen` 白名單,再檢查
|
||||
`data-juzhen-off` 黑名單,接著取第一條命中的 `blocks` 規則。白名單未列入的功能停用;
|
||||
黑名單未列入的功能繼續查找;皆無決定時沿祖先向上,最後回退全域開關。
|
||||
因此同一元素同時寫白名單與黑名單時,**白名單優先**;`data-juzhen=""` 是空白名單,
|
||||
不是恢復預設。全域停用的功能不會被局部白名單重新啟用。
|
||||
|
||||
### 功能分級:文本級 vs 段落級(`level`/`data-jz-level`)
|
||||
|
||||
功能分兩級:
|
||||
**text/paragraph 決定允許哪些排版功能,不是 HTML 標籤分類,也不設定 CSS display。**
|
||||
呼叫端應按內容用途選擇,不能把「現在只有一行」當作 text 的唯一判準。
|
||||
|
||||
- **文本級**(中西間隙 `spacing`、標點擠壓 `jiya`):僅依字符鄰接,**任意文本片段**(單行
|
||||
標題、按鈕、單一行內元素)皆適用且想要。
|
||||
- **段落級**(禁則 `jinze`、垂懸字 `orphan`、長詞 `longWord`):依賴
|
||||
行/段布局(換行、行端、段末),對單行/非段落文本無意義甚至有害。
|
||||
| 內容用途 | 選擇 | 允許的功能(仍受功能開關控制) | 不會替你做什麼 |
|
||||
|---|---|---|---|
|
||||
| 標題、按鈕、選單、短標籤等 UI 文字 | **text(文本級)** | `spacing` 中西間隙、`jiya` 標點擠壓 | 不做 `jinze` 禁則、`orphan` 避孤字、`longWord` 長詞斷字;不保證單行 |
|
||||
| 正文、說明段落、完整句子的列表項等連續閱讀內容 | **paragraph(段落級)** | 上述文本級功能,加上禁則、避孤字、長詞斷字 | 不自動兩端對齊,不設定字型或容器寬度 |
|
||||
|
||||
塊級元素(`p`/`div`/`li`/`h1`…)**預設段落級(全功能)**。把標題/按鈕/UI 文字標為
|
||||
**文本級**(要間隙與標點半形、不要避孤字/避頭尾):
|
||||
**沒有任何級別訊號時預設 paragraph。** `h1`/`button` 不會因名稱自動變成 text;
|
||||
`p` 也可以明確標成 text。即使一段正文在寬螢幕恰好只有一行,仍可選 paragraph;
|
||||
即使標題在窄螢幕換行,也可保留 text。需不需段落約束由呼叫端決定。
|
||||
|
||||
#### HTML 標記與選擇器
|
||||
|
||||
```html
|
||||
<h1 data-jz-level="text">標題:只跑中西間隙與標點擠壓</h1>
|
||||
<p data-jz-level="paragraph">…</p> <!-- 顯式全功能(巢狀於 text 子樹內可覆寫,最近祖先勝) -->
|
||||
<article class="juzhen" lang="zh-Hant">
|
||||
<h1 data-jz-level="text">聚珍API:使用說明</h1>
|
||||
<p data-jz-level="paragraph">正文採段落級,允許禁則與段末避孤字。</p>
|
||||
<div class="ui" data-jz-level="text">
|
||||
<button>開啟API</button>
|
||||
<p data-jz-level="paragraph">這段較長的說明重新採用段落級。</p>
|
||||
</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
```ts
|
||||
createJuzhen({ level: { text: "h1,h2,h3,button,.ui" } }); // 選擇器一鍵標記
|
||||
```
|
||||
也可在建立實例時用 `level: { text: "h1,h2,h3,button,.ui" }` 批量選定文本級子樹,
|
||||
不必逐個寫屬性;已有 pill/isolate/avoid 設定時,將 `level` 加入**同一個**選項物件。
|
||||
|
||||
解析順序如下:
|
||||
|
||||
1. 從當前元素(文字則從父元素)開始往上找,包含自身。
|
||||
2. 每一層先讀合法的 `data-jz-level="text|paragraph"`,再看是否命中 `level.text`。
|
||||
3. 找到第一個訊號即停止;都沒有才用 paragraph。非法屬性值不構成訊號,繼續查找。
|
||||
|
||||
所以**距離優先、同層屬性優先**:近處選擇器可以勝過遠處祖先屬性;同一元素標
|
||||
`paragraph` 則可以蓋過該元素命中的 `level.text`。在 text 子樹中寫
|
||||
`data-juzhen="orphan"` 不能開啟避孤字,須先明確切回 paragraph,且該功能全域已啟用。
|
||||
|
||||
#### 與 pill/isolate/avoid 搭配
|
||||
|
||||
| 組合 | 實際意思 |
|
||||
|---|---|
|
||||
| paragraph 正文中的 pill 公式 | 正文有段落約束;公式內部由元件管理,公式整體仍參與正文交界排版 |
|
||||
| text 標題中的 isolate 標籤 | 標籤內部仍補間隙、處理標點;不因 isolate 就開啟段落功能 |
|
||||
| paragraph 容器中的 avoid 元件 | 元件子樹仍完全排除;paragraph 不會取消 avoid |
|
||||
|
||||
不要用 avoid 代替 text:若只是不想讓按鈕跑避孤字,應標 text,這樣仍保留間隙與標點處理。
|
||||
也不要用 text 代替 pill:text 仍會改寫內部文字排版,不能用來保護公式或程式碼元件。
|
||||
|
||||
#### CSS 可查之 resolved 分類:`data-jz-block-level`(輸出屬性)
|
||||
|
||||
`render` 時,聚珍把 **resolved** 級別寫回每個「直接承載可處理文本」之塊級元素:
|
||||
|
||||
```html
|
||||
<div class="callout" data-jz-block-level="paragraph">…</div> <!-- 聚珍判為段落級 -->
|
||||
<h2 data-jz-block-level="text">…</h2> <!-- 聚珍判為文本級 -->
|
||||
<ul> <!-- 純容器:不標 -->
|
||||
<li data-jz-block-level="paragraph">…</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
消費端之**段落級 CSS**(首要:CJK 兩端對齊)從此與聚珍的段落級 pass(禁則/垂懸)共用
|
||||
**單一真相源**,免另行維護一份必然漂移之選擇器白名單:
|
||||
作者只寫 **`data-jz-level`**。`render` 後聚珍會對承載可處理非空白文字的塊級元素產生
|
||||
**`data-jz-block-level`**,供呼叫端 CSS 使用;不要把輸出屬性當作設定值手寫。
|
||||
|
||||
```css
|
||||
[data-jz-block-level="paragraph"] { text-align: justify; }
|
||||
.marginalia[data-jz-block-level="paragraph"] { text-align: start; } /* 窄欄 opt-out */
|
||||
/* 依聚珍輸出分類選擇對齊;這是呼叫端的樣式決定。 */
|
||||
.juzhen [data-jz-block-level="paragraph"] { text-align: justify; }
|
||||
.juzhen .marginalia[data-jz-block-level="paragraph"] { text-align: start; }
|
||||
```
|
||||
|
||||
規則:
|
||||
純容器(例如只含子段落的 `div`/`ul`)不因包住正文就取得此輸出;域外文字、pill 內部與
|
||||
avoid 子樹不計入。屬性描述**所屬塊的級別**,不是每個行內子元素的級別;同一段落內個別
|
||||
`span` 標 text,不會把整個段落輸出改成 text。若需要段落 CSS 跟著切換,應標在該塊本身。
|
||||
|
||||
- **僅標直接承載文本之塊**——純容器(`body`/`ul`/`table`…,直接文本僅空白)不標,
|
||||
否則 `body` 恆被判段落級,justify 鉤子經繼承污染全頁。此集合恰為段落級 pass 實際
|
||||
作用之塊集。avoid 子樹/pill 內部之文本不計,故未被處理之塊一致不標。
|
||||
- **輸出專用、輸入輸出分離**:作者輸入屬性 `data-jz-level` 只讀不寫;本屬性由聚珍寫入、
|
||||
聚珍**絕不讀取**(消費端手寫無效),`revert` 時全數移除。增量模式(`observe`)下分類
|
||||
屬性變化自動刷新。
|
||||
- 是否對齊仍由消費端 CSS 決定(justify 非段落之純函數——窄欄邊注可 opt-out);聚珍
|
||||
只供分類、不介入樣式。
|
||||
`revert` 會撤回本實例寫入的輸出,依所有權規則保留/恢復作者或其他實例的屬性。
|
||||
輸入 `data-jz-level` 不由聚珍改寫;`data-jz-block-level` 不參與級別判定,也不觸發分類重算。
|
||||
|
||||
#### 標記改動後如何更新
|
||||
|
||||
在第一次 `render` **之前**準備好 class、排除與級別標記。已渲染後改動時,手動呼叫
|
||||
`jz.rerender(changedBlock)`;若開啟 `jz.observe(root)`,受觀察範圍內的 `class`、
|
||||
`data-jz-skip`、`data-jz-level`、`data-juzhen`/`data-juzhen-off` 變化會觸發增量更新。
|
||||
自訂選擇器若依賴其他屬性(例如 `data-widget`),不要假設 observer 會追蹤該屬性,請手動
|
||||
重排受影響區塊。更新時以塊為單位,詳見前面的「動態頁面」。
|
||||
|
||||
### 標點懸掛(`hanging`)— 已移除
|
||||
|
||||
|
||||
Reference in New Issue
Block a user