| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- /**
- * scripts/v11b-patch.mjs —— v11b 验收用渲染路径补丁(见 docs/v11b-report.md 的「未达成 / 取舍」一节)。
- *
- * 上一轮的写法是「每个视图 updated() 里调 cjk(this.renderRoot)」,两个问题:
- * 1. Lit 每次渲染都会把模板里的字符串按原样重写进文本节点 —— 渲染后改的 U+2009 活不过下一次渲染;
- * 2. 33 个自定义元素里只有 7 个调了 —— 卡片 / 徽标 / 顶栏 / 五阶段页里的数字全漏掉。
- * 这个文件把同一套规则挂到 **MutationObserver** 上:文本一变就当场过一遍 mix()(幂等)。
- * 规则与 src/utils/type.js 的 spaceCJK 一致:
- * 汉字 ↔ 拉丁/数字交界两侧 U+2009;交界处的普通空格 / 全角空格换成 U+2009;连续数字整体算一个单位;
- * <code>/<pre>/<kbd>/<samp>/.kf-mono、URL、文件名跳过;textarea / input 值、aria-label 等属性不动。
- */
- (function installKfCjk() {
- const THIN = ' ';
- const EXEMPT = new Set(['CODE', 'PRE', 'KBD', 'SAMP', 'SCRIPT', 'STYLE', 'TEXTAREA', 'NOSCRIPT', 'TEMPLATE']);
- const URLISH = /https?:\/\/|www\.|[\w-]+\.(?:js|mjs|json|md|css|png|jpg|svg|html|pdf)\b/;
- const MIX = new RegExp(
- '([\\u4e00-\\u9fa5,\\u3001\\u3002])[ \\t\\u00a0\\u3000]*(?=[A-Za-z0-9])' + // 汉字(含 ,、。)+ 空白 + 拉丁
- '|(?<=[A-Za-z0-9,.%])[ \\t\\u00a0\\u3000]*([\\u4e00-\\u9fa5])', // 拉丁 + 空白 + 汉字
- 'g',
- );
- const mix = (s) => String(s).replace(MIX, (m, a, b) => (b !== undefined ? THIN + b : a + THIN));
- const hasThin = (s) => s.split('').some((c) => c.charCodeAt(0) === 0x2009);
- const NEAR = /[\u4e00-\u9fa5][ \t\u00a0\u3000]*[A-Za-z0-9]|[A-Za-z0-9][ \t\u00a0\u3000]*[\u4e00-\u9fa5]/;
- const near = (s) => NEAR.test(s);
- /* 需要处理:汉字与拉丁之间是普通空格(NEAR),或者已经夹着一个 U+2009(幂等归一) */
- const hasNear = (s) => NEAR.test(s) || /[\u4e00-\u9fa5][ \t\u00a0\u3000]*\u2009|\u2009[ \t\u00a0\u3000]*[\u4e00-\u9fa5]/.test(s);
- function exempt(t) {
- for (let n = t.parentNode; n; n = n.parentNode || n.host) {
- if (n.nodeType !== 1) continue;
- if (EXEMPT.has(n.localName.toUpperCase()) || n.classList?.contains('kf-mono')) return true;
- }
- return false;
- }
- let fixing = false;
- function fixText(t) {
- if (!t || t.nodeType !== 3 || !hasNear(t.data) || exempt(t) || URLISH.test(t.data)) return;
- const raw = hasThin(t.data) ? t.data.split(String.fromCharCode(0x2009)).join(' ') : t.data;
- const next = mix(raw);
- if (next !== t.data) t.data = next;
- }
- function fixTree(n) {
- if (n.nodeType === 3) return fixText(n);
- if (n.nodeType !== 1 && n.nodeType !== 11) return;
- const w = document.createTreeWalker(n, NodeFilter.SHOW_TEXT);
- let t;
- while ((t = w.nextNode())) fixText(t);
- }
- /* 拖拽排序插件(lit 的 live())会插入「活值重写」注释:把注释包着的文本再过一遍 */
- const mo = new MutationObserver((list) => {
- if (fixing) return;
- fixing = true;
- try {
- for (const m of list) {
- if (m.type === 'characterData') fixText(m.target);
- else {
- for (const n of m.addedNodes) { fixTree(n); if (n.nodeType === 1) { if (n.shadowRoot) observe(n.shadowRoot); for (const x of n.querySelectorAll('*')) if (x.shadowRoot) observe(x.shadowRoot); } }
- }
- }
- } finally {
- fixing = false;
- }
- });
- const seen = new WeakSet();
- function observe(root) {
- if (!root || seen.has(root)) return;
- seen.add(root);
- mo.observe(root, { childList: true, subtree: true, characterData: true });
- for (const el of root.querySelectorAll('*')) if (el.shadowRoot) observe(el.shadowRoot);
- }
- window.__kfCjk = {
- mix,
- hasThin,
- install() {
- observe(document);
- // 自定义元素后挂上来的 shadow root 也要进观察范围
- /* document.querySelectorAll 跨不过 shadow 边界:要递归进每一层 shadow root 找宿主 */
- const tick = () => {
- const seen = new Set();
- const scan = (root) => { for (const el of root.querySelectorAll('*')) { if (!el.shadowRoot || seen.has(el.shadowRoot)) continue; seen.add(el.shadowRoot); observe(el.shadowRoot); scan(el.shadowRoot); } };
- scan(document);
- };
- tick();
- setInterval(tick, 500);
- customElements.whenDefined('kf-app').then(tick);
- },
- };
- window.__kfCjk.install();
- })();
|