feat(levelmark): 塊級分類 CSS 鉤子 data-jz-block-level+增量塊粒度契約(下游建議七/說明八)
建議七:新增 levelMark pass(standalone,order 10,恆開)——render 時把 resolved 功能級別寫回「直接承載可處理文本」之塊級元素:data-jz-block-level="paragraph|text", revert 全數移除(I6 之 data-jz-* 清單涵蓋)。消費端段落級 CSS(首要 text-align: justify)自此與 jinze/orphan 共用單一真相源,免另立必然漂移之選擇器白名單。 - 僅標直接承載文本之塊:純容器(body/ul/table…,直接文本僅空白)不標——否則 body 恆判 paragraph、justify 鉤子經繼承污染全頁;此集合恰為段落級 pass 實際 作用之塊集。avoid 子樹/pill 內部文本不計,未被處理之塊一致不標。 - 輸入/輸出分離:作者輸入 data-jz-level 只讀不寫;輸出屬性本庫絕不讀取、亦不在 C2 OBSERVE_ATTRS 白名單(外部改動不觸發);增量路徑 revert 即清、render 即重標。 - 實現:一次 eachTextNode 走訪+短程上攀定塊(per-run 快取),O(文本);levelFor 已記憶化。上攀止於 ctx.root,不越界外標。 說明八:README/ARCHITECTURE §3.1 明載增量粒度契約——增量須以塊為粒度(換塊 內容、增刪塊、改分類屬性皆與全量重繪一致);不支持對聚珍已切分文本節點之外科式 characterData 直改(charify 以 fragment 替換原節點,舊引用已脫離文檔)——後處理 排版器之固有限制、非缺陷,以塊為單位替換內容即可。 測試 140→145(標記/純容器不標、text 解析三路(選擇器/屬性/祖先)、avoid/ pill 不標、revert 全清含 root、C2 class 翻轉自動刷新);tsc 聲明構建通過; headless Chrome 整管線煙測零報錯、段落屬性實際落 DOM。
This commit is contained in:
Vendored
+59
-1
@@ -1386,6 +1386,63 @@ var jinzePass = {
|
||||
}
|
||||
};
|
||||
|
||||
// src/typeset/levelmark.ts
|
||||
var ATTR = "data-jz-block-level";
|
||||
var levelMarkPass = {
|
||||
name: "levelMark",
|
||||
kind: "standalone",
|
||||
order: 10,
|
||||
level: "text",
|
||||
// 不受段落級閘限制:text 級之塊亦須標記(標記本身即分級輸出)
|
||||
enabled: () => true,
|
||||
// 分類鉤子非排版功能,恆開(純屬性輸出、零版面影響)
|
||||
render(ctx) {
|
||||
const { finder, options } = ctx;
|
||||
const blockSet = options.finder.blockTags;
|
||||
const nearest = /* @__PURE__ */ new Map();
|
||||
const nearestBlock = (p) => {
|
||||
const cached = nearest.get(p);
|
||||
if (cached !== void 0) {
|
||||
return cached;
|
||||
}
|
||||
let e = p;
|
||||
let block = null;
|
||||
while (e) {
|
||||
if (blockSet.has(e.nodeName)) {
|
||||
block = e;
|
||||
break;
|
||||
}
|
||||
if (e === ctx.root) {
|
||||
break;
|
||||
}
|
||||
e = e.parentElement;
|
||||
}
|
||||
nearest.set(p, block);
|
||||
return block;
|
||||
};
|
||||
finder.eachTextNode(ctx.root, (t) => {
|
||||
if (!/\S/.test(t.data)) {
|
||||
return;
|
||||
}
|
||||
const p = t.parentElement;
|
||||
const block = p ? nearestBlock(p) : null;
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
const level = finder.levelFor(block);
|
||||
if (block.getAttribute(ATTR) !== level) {
|
||||
block.setAttribute(ATTR, level);
|
||||
}
|
||||
});
|
||||
},
|
||||
revert(ctx) {
|
||||
if (ctx.root.hasAttribute(ATTR)) {
|
||||
ctx.root.removeAttribute(ATTR);
|
||||
}
|
||||
ctx.root.querySelectorAll("[" + ATTR + "]").forEach((el) => el.removeAttribute(ATTR));
|
||||
}
|
||||
};
|
||||
|
||||
// src/typeset/lineedge.ts
|
||||
var LE = "jz-half-le";
|
||||
var ALL_EDGE = [LE];
|
||||
@@ -2129,7 +2186,8 @@ var PASSES = [
|
||||
jinzePass,
|
||||
orphanPass,
|
||||
lineEdgePass,
|
||||
gapTrimPass
|
||||
gapTrimPass,
|
||||
levelMarkPass
|
||||
];
|
||||
function feature(v, dflt) {
|
||||
return v === void 0 ? dflt : !!v;
|
||||
|
||||
Reference in New Issue
Block a user