@mzzsfy/dsh-settings-nav-icons 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/client.js +5 -3
- package/src/logic.mjs +5 -3
- package/test/orchestration.test.mjs +7 -4
- package/test/parity.test.mjs +18 -6
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ window.__navicIcons.register({ '消息通知': 'bell' })
|
|
|
81
81
|
- 导航滚动补偿:官方 navList 高度随内容撑开,弹窗(overflow hidden)裁掉溢出且无滚动途径,分区一多底部不可达。插件启动时向 head 注入一条样式规则(navList `flex:1 1 0 + min-height:0 + overflow-y:auto + scrollbar-width:thin`),样式表形态对弹窗关闭重开等任意 React 重渲染持续生效;卸载即移除,零残留。类名锚定与图标改写同策略(`VOzbGW_` 哈希前缀字面量)。
|
|
82
82
|
- 实现边界:`settings.*` 子槽位的声明权与渲染权均被原版 `dsh-client-ui-settings-general` 条目占用——`renderSlot` 只授予声明了 `children` 的条目(dsh-client-ui-renderer),而 Slot 声明全局唯一、重复声明即 `already declared`,因此任何插件都无法在保留设置页内容的前提下接管 Shell 重绘导航。DOM 观察是唯一不依赖官方契约变更的路径(dream-skin 与 dsh-better-sidebar 各自内置了同原理的一次性 hack)。
|
|
83
83
|
- 匹配锚点:分区显示文本而非分区 id——id 不进 DOM,文本是唯一稳定可见锚点;CSS Modules 哈希类名(`VOzbGW_*`)取字面量。label 一律 trim 后匹配,空白 label 直接跳过。
|
|
84
|
-
- 内容改写制:决策只对官方齿轮(
|
|
84
|
+
- 内容改写制:决策只对官方齿轮(两代齿轮首段路径前缀识别:0.1.x 内联 `M14.0861` / 0.1.7 起 primitives IconSettingsOutlineMedium 齿体 `M13.0107 7.79377`;官方分区语义图标非齿轮一律不动,插件注册分区走齿轮 fallback)生效,`applyDecision` 写官方 svg 的 `innerHTML` 并把记账(`data-navic` = label)落在官方 svg 自身;不新建节点、不改属性、不驱动 style。React 安全性依据:官方 nav 图标是静态子树,重渲染前后 element 引用相等即 bailout,改写内容稳定存活(0.1.x 的 NotFoundError 仅源于 remove 节点本身);升级残留的旧注入节点(`data-navic="1"`)在改写前清除。改写后的 svg 非齿轮且带记账,与"未处理的官方原生非齿轮 svg"靠记账区分——因此声明变更不删记账,由 `registerIcons` 对命中键的 nav svg 以 `resolveForLabel` 全链(声明 → 内置映射 → 关键词 → 哈希)就地重写内容,头像槽(非 svg 节点)仍走清账重贴。
|
|
85
85
|
- 市场卡片头像槽记账为插件名,IMG/DIV 同构处理:原节点隐藏、注入图标跟随(不触碰 React 受管子树),换名重贴按父容器范围清理旧注入、不误删外来兄弟。
|
|
86
86
|
- 生命周期:单个 `MutationObserver` 常驻 `document.body`(`childList` + `subtree` + `characterData`;childList 通道仅元素级变更放行,characterData 通道按目标域精确放行——label 与市场卡片名所在行内的文本改写才唤醒,流式正文等域外文本变更不唤醒),变更去抖到 `requestAnimationFrame` 扫描,单元格与头像槽合并为单次全文档遍历,扫描结束 `takeRecords` 消除自产写入回波;`ctx.effect` 持有,卸载即断开并取消已排定帧,已停止实例的注册入口短路、不驱动 DOM。观察器句柄、在途帧与滚动样式元素挂 window 代际槽(HMR 重评估先拆上一代三者,滚动样式对旧版槽缺字段宽松判空),帧句柄与槽同步(跨代取消真实可达)。常驻全文档观察是已知性能取舍:装饰性插件的目标域(设置弹窗/市场卡片)无稳定根锚点,两级观察的回归风险大于收益。
|
|
87
87
|
- 纯逻辑层(映射表 + 改写决策)在 `src/logic.mjs`,`src/client.js` 内嵌同源实现,`node --test` 以同一套场景对两份实现做 parity 验证(全表 deepEqual + 决策函数逐函数源码对比),另有编排层契约测试(注册校验/队列三态与时序/生命周期/头像槽编排/异常隔离)与四生产者样板契约测试。
|
|
@@ -127,4 +127,4 @@ MIT
|
|
|
127
127
|
|
|
128
128
|
## dsh 版本兼容
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
三版本(0.1.2-rc.1 / 0.1.5-rc.3 / 0.1.7-rc.1)全部通过:导航图标接管。0.1.6 设置导航 5→6 标签、0.1.7-rc.1 至 12 分区(新增账号余额/Shell 管理等)均属宿主演进,不构成不兼容;守卫对无齿轮分区形态正确降级。
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -578,11 +578,13 @@ window.__ModuleLoader__.load({
|
|
|
578
578
|
av.insertAdjacentHTML('afterend', decision.html)
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
//
|
|
582
|
-
|
|
581
|
+
// 官方默认齿轮识别前缀(两代):0.1.x 内联齿轮 / 0.1.7 起 primitives
|
|
582
|
+
// IconSettingsOutlineMedium 齿体路径。官方分区语义图标非齿轮一律不动;
|
|
583
|
+
// 插件注册分区走齿轮 fallback,是本插件强补对象。
|
|
584
|
+
const GEAR_PATHS = ['M14.0861', 'M13.0107 7.79377']
|
|
583
585
|
|
|
584
586
|
function isGear(svg) {
|
|
585
|
-
return svg.querySelector('path[d^="' +
|
|
587
|
+
return GEAR_PATHS.some((prefix) => svg.querySelector('path[d^="' + prefix + '"]') !== null)
|
|
586
588
|
}
|
|
587
589
|
|
|
588
590
|
// 完整 svg 字符串剥壳取内部内容:resolveIcon 输出与 GLYPHS/声明值均为完整 svg,
|
package/src/logic.mjs
CHANGED
|
@@ -158,11 +158,13 @@ export function resolveIcon(value) {
|
|
|
158
158
|
// 官方 DOM 锚点:与 client.js 控制器字面量同源锁定(parity 断言对表)。
|
|
159
159
|
export const SELECTOR_LABEL = '.VOzbGW_navLabel'
|
|
160
160
|
|
|
161
|
-
//
|
|
162
|
-
|
|
161
|
+
// 官方默认齿轮识别前缀(两代):0.1.x 内联齿轮 / 0.1.7 起 primitives
|
|
162
|
+
// IconSettingsOutlineMedium 齿体路径。官方分区语义图标非齿轮一律不动;
|
|
163
|
+
// 插件注册分区走齿轮 fallback,是本插件强补对象。
|
|
164
|
+
export const GEAR_PATHS = ['M14.0861', 'M13.0107 7.79377']
|
|
163
165
|
|
|
164
166
|
export function isGear(svg) {
|
|
165
|
-
return svg.querySelector('path[d^="' +
|
|
167
|
+
return GEAR_PATHS.some((prefix) => svg.querySelector('path[d^="' + prefix + '"]') !== null)
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
// 完整 svg 字符串剥壳取内部内容:resolveIcon 输出与 GLYPHS/声明值均为完整 svg,
|
|
@@ -39,23 +39,26 @@ function overlayFind(root, pred) {
|
|
|
39
39
|
return null
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// 官方 svg 桩:gear=
|
|
42
|
+
// 官方 svg 桩:gear 模拟官方齿轮路径('legacy'=0.1.x 内联 / 'rc1'=0.1.7
|
|
43
|
+
// primitives 齿体 / true 同 legacy),否则为无路径原生图形;内容改写制下
|
|
43
44
|
// applyDecision 直接写 innerHTML(桩以 innerHTML 属性承接)与 dataset.navic
|
|
44
|
-
function makeOfficialSvg({ gear =
|
|
45
|
+
function makeOfficialSvg({ gear = 'legacy' } = {}) {
|
|
46
|
+
const GEAR_PREFIXES = { legacy: 'M14.0861', rc1: 'M13.0107 7.79377' }
|
|
47
|
+
const prefix = gear === true ? GEAR_PREFIXES.legacy : GEAR_PREFIXES[gear]
|
|
45
48
|
const svg = {
|
|
46
49
|
tagName: 'svg',
|
|
47
50
|
dataset: { navic: '' },
|
|
48
51
|
style: { display: '' },
|
|
49
52
|
innerHTML: '',
|
|
50
53
|
remove() {},
|
|
51
|
-
querySelector(sel) { return
|
|
54
|
+
querySelector(sel) { return prefix !== undefined && sel.includes(prefix) ? {} : null },
|
|
52
55
|
}
|
|
53
56
|
return svg
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// 导航单元格桩:官方 svg 恒为首个;内容改写制下不再产生注入节点,
|
|
57
60
|
// remove 语义仅服务于 0.1.x 升级残留清理路径
|
|
58
|
-
function makeCell(label, { gear =
|
|
61
|
+
function makeCell(label, { gear = 'legacy', navic = '' } = {}) {
|
|
59
62
|
const cell = { dataset: {}, injectedCount: 0, svgs: [] }
|
|
60
63
|
const official = makeOfficialSvg({ gear })
|
|
61
64
|
if (navic) official.dataset.navic = navic
|
package/test/parity.test.mjs
CHANGED
|
@@ -26,7 +26,7 @@ function clientLogic() {
|
|
|
26
26
|
}
|
|
27
27
|
const factory = new Function(
|
|
28
28
|
'ATTR_MARK', 'SELECTOR_LABEL',
|
|
29
|
-
section + '; return { ICONS, DECLARED_ICONS, USER_OVERRIDES, ALIAS_GROUPS, FALLBACK, GLYPHS,
|
|
29
|
+
section + '; return { ICONS, DECLARED_ICONS, USER_OVERRIDES, ALIAS_GROUPS, FALLBACK, GLYPHS, GEAR_PATHS, SVG_MAX_CHARS, STROKE_ATTRS, NAME_RULES, ATTR_MARK, SELECTOR_LABEL, poolIndexOf, resolveIcon, svgInner, resolveForLabel, themedIcon, iconSourceOf, lookupWithAlias, aliasKeysOf, decide, applyDecision, decideAvatar, applyAvatar };',
|
|
30
30
|
)
|
|
31
31
|
return factory(pick('ATTR_MARK'), pick('SELECTOR_LABEL'))
|
|
32
32
|
}
|
|
@@ -36,11 +36,16 @@ function clientLogic() {
|
|
|
36
36
|
// 内容改写制:决策直接写 official.innerHTML 与 official.dataset.navic,
|
|
37
37
|
// 不新建节点、不动其余属性;桩以普通属性承接 innerHTML 写入。
|
|
38
38
|
function makeSvg({ gear = false } = {}) {
|
|
39
|
+
// 两代官方齿轮桩路径:0.1.x 内联齿轮 / 0.1.7 primitives 齿体(实文前缀)
|
|
40
|
+
const GEAR_STUB_PATHS = {
|
|
41
|
+
legacy: 'M14.0861 3.2c-.9.4-1.4 1-1.6 1.9',
|
|
42
|
+
rc1: 'M13.0107 7.79377C12.9505 7.89401 12.9205 7.94413 12.9205 7.99951',
|
|
43
|
+
}
|
|
39
44
|
const node = {
|
|
40
45
|
dataset: {},
|
|
41
46
|
style: { display: '' },
|
|
42
47
|
inner: '',
|
|
43
|
-
paths: gear ? ['
|
|
48
|
+
paths: gear ? [GEAR_STUB_PATHS[gear === 'rc1' ? 'rc1' : 'legacy']] : ['M8 2.2a4 4 0 0 1 4 4'],
|
|
44
49
|
insertAdjacentHTML() {},
|
|
45
50
|
remove() {},
|
|
46
51
|
querySelector(sel) {
|
|
@@ -73,7 +78,7 @@ function makeCell(labelText, { marked = false, gear, noSvg = false } = {}) {
|
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
function defineScenarios(prefix, L) {
|
|
76
|
-
const { ICONS, FALLBACK,
|
|
81
|
+
const { ICONS, FALLBACK, GEAR_PATHS, poolIndexOf, themedIcon, decide, applyDecision, svgInner } = L
|
|
77
82
|
|
|
78
83
|
test(prefix + '收录分区返回改写决策:剥壳 inner,不建节点', () => {
|
|
79
84
|
const cell = makeCell('插件市场')
|
|
@@ -105,8 +110,15 @@ function defineScenarios(prefix, L) {
|
|
|
105
110
|
assert.ok(reached.size >= 2, '哈希应在池内产生区分,reached=' + reached.size)
|
|
106
111
|
})
|
|
107
112
|
|
|
108
|
-
test(prefix + '
|
|
109
|
-
assert.
|
|
113
|
+
test(prefix + 'GEAR_PATHS 收两代官方齿轮前缀(0.1.x 内联 / rc.1 primitives 齿体)', () => {
|
|
114
|
+
assert.deepEqual(GEAR_PATHS, ['M14.0861', 'M13.0107 7.79377'])
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test(prefix + 'rc.1 primitives 齿轮 fallback 同样强补(新代齿轮识别)', () => {
|
|
118
|
+
const cell = makeCell('某新装插件分区', { gear: 'rc1' })
|
|
119
|
+
const d = decide(cell)
|
|
120
|
+
assert.ok(d, '新代齿轮兜底应有决策')
|
|
121
|
+
assert.equal(d.inner, svgInner(FALLBACK[poolIndexOf('某新装插件分区')]))
|
|
110
122
|
})
|
|
111
123
|
|
|
112
124
|
test(prefix + '已改写形态(svg 记账等于 label)返回 null', () => {
|
|
@@ -401,7 +413,7 @@ test('两份实现常量同源', () => {
|
|
|
401
413
|
assert.equal(logic.STROKE_ATTRS, client.STROKE_ATTRS)
|
|
402
414
|
assert.equal(logic.ATTR_MARK, client.ATTR_MARK)
|
|
403
415
|
assert.equal(logic.SELECTOR_LABEL, client.SELECTOR_LABEL, 'label 选择器同源(精确匹配桩防子串容忍)')
|
|
404
|
-
assert.
|
|
416
|
+
assert.deepEqual(logic.GEAR_PATHS, client.GEAR_PATHS, '两代齿轮前缀同源')
|
|
405
417
|
assert.equal(logic.SVG_MAX_CHARS, client.SVG_MAX_CHARS)
|
|
406
418
|
assert.deepEqual(logic.ALIAS_GROUPS, client.ALIAS_GROUPS, '别名组同源')
|
|
407
419
|
assert.equal(Object.keys(logic.USER_OVERRIDES).length, 0, '用户覆盖表初始为空(logic 侧)')
|