dsh-theme-gallery 0.1.3 → 0.1.5
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 +26 -0
- package/lib/client.js +241 -57
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -114,6 +114,32 @@ dsh plugin --profile web add dsh-theme-gallery
|
|
|
114
114
|
只在你想跟源码时用它。本包**没有构建步骤**,所以不会出现"缺 `lib/` 目录"那类失败;
|
|
115
115
|
它的代价是 pnpm 可能要求你为构建脚本授权(`allowBuilds`)。
|
|
116
116
|
|
|
117
|
+
### 更新到新版本
|
|
118
|
+
|
|
119
|
+
**先说清楚:DSH 桌面版本身不会提示第三方插件有更新**,所以「发现新版」和「执行更新」这两件事得靠下面的渠道。
|
|
120
|
+
|
|
121
|
+
**怎么知道有新版本?**
|
|
122
|
+
|
|
123
|
+
| 渠道 | 说明 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| **插件市场** | 装 [DSH-Plugins-Marketplace](https://github.com/bradeGithub/DSH-Plugins-Marketplace) 后,卡片会显示 **「已装 v0.1.4 → v0.1.6」** 并给**更新**按钮 —— 目前最省事的路径 |
|
|
126
|
+
| **GitHub Releases** | 在本仓库点 **Watch → Custom → Releases only**,有新版本会收到邮件 |
|
|
127
|
+
| **对照版本号** | 插件面板右上角显示**你装的版本**(如 `v0.1.4`),与 [npm 页面](https://www.npmjs.com/package/dsh-theme-gallery) 上的版本号直接对照 |
|
|
128
|
+
|
|
129
|
+
**怎么更新?**
|
|
130
|
+
|
|
131
|
+
1. **设置 → 插件 → 添加插件** → 再填一次 `dsh-theme-gallery` → **重启**(装上 `^x.y.z` 范围内的最新版)
|
|
132
|
+
2. 想要**强制拿最新**(含 0.1.x → 0.2.0 这类次版本号变更):**先移除、再添加**
|
|
133
|
+
3. 装了市场的:点卡片上的**更新**按钮
|
|
134
|
+
4. Web / CLI profile:`dsh plugin --profile web add dsh-theme-gallery`
|
|
135
|
+
|
|
136
|
+
**版本范围语义**:安装时会记录成 **`^0.1.4`**,即 `>=0.1.4 <0.2.0` ——
|
|
137
|
+
`0.1.5` / `0.1.9` 这类补丁会在重装时自动带上;**`0.2.0` 不会**(npm 对 `0.x` 的惯例是
|
|
138
|
+
把次版本号变更视为可能破坏性),那种情况按第 2 条显式重装即可。
|
|
139
|
+
|
|
140
|
+
> ⚠️ 从 **Release 的 `.tgz`** 安装的用户**没有更新机制**(tgz 是一次性快照,只能重新下载)——
|
|
141
|
+
> 想长期跟更新,建议用**包名安装**。
|
|
142
|
+
|
|
117
143
|
### 兼容性与权限
|
|
118
144
|
|
|
119
145
|
| 项 | 说明 |
|
package/lib/client.js
CHANGED
|
@@ -105,6 +105,21 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
*/
|
|
106
106
|
const READING = { bg: '#FFFFFF', alpha: 0.62, blur: 3, maxWidth: 640 }
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The package version this bundle was built from.
|
|
110
|
+
*
|
|
111
|
+
* Written by `scripts/embed-themes.mjs` from package.json, because the browser
|
|
112
|
+
* half cannot read its own manifest: the client module system resolves `require`
|
|
113
|
+
* against the platform seed table and other plugins' boot-graph rows, not
|
|
114
|
+
* against this package's files. The panel shows it so a reader can compare what
|
|
115
|
+
* they have with what npm publishes.
|
|
116
|
+
*
|
|
117
|
+
* Kept honest by two checks: `scripts/publish-check.mjs` compares it with
|
|
118
|
+
* package.json, and the release workflow fails when re-running the embed step
|
|
119
|
+
* changes a tracked file.
|
|
120
|
+
*/
|
|
121
|
+
const BUNDLED_VERSION = '0.1.5'
|
|
122
|
+
|
|
108
123
|
/**
|
|
109
124
|
* Themes this package contributes, inlined from lib/themes/*.json.
|
|
110
125
|
*
|
|
@@ -1118,22 +1133,36 @@ window.__ModuleLoader__.load({
|
|
|
1118
1133
|
}
|
|
1119
1134
|
|
|
1120
1135
|
/**
|
|
1121
|
-
*
|
|
1136
|
+
* Where the debug switch is remembered.
|
|
1137
|
+
*
|
|
1138
|
+
* A localStorage flag in addition to the URL fragment, because this plugin
|
|
1139
|
+
* ships as a package inside the desktop app: there is no address bar to add
|
|
1140
|
+
* `#theme-gallery-debug` to, so DevTools is the way in.
|
|
1141
|
+
*/
|
|
1142
|
+
const DEBUG_KEY = 'theme-gallery:debug'
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Whether the gallery shows its diagnostic detail lines.
|
|
1146
|
+
*
|
|
1147
|
+
* The raw readings — token counts, geometry, hit tests, the attempt log — are
|
|
1148
|
+
* for whoever is debugging, not for whoever is picking a skin, and this is a
|
|
1149
|
+
* shipped package. So they are opt-in, through either switch:
|
|
1150
|
+
*
|
|
1151
|
+
* DevTools console: localStorage.setItem('theme-gallery:debug', '1')
|
|
1152
|
+
* …or a URL with #theme-gallery-debug
|
|
1153
|
+
* turn it off: localStorage.removeItem('theme-gallery:debug')
|
|
1122
1154
|
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
* "registered with broken tokens", or "painted over by our own CSS"; this line
|
|
1128
|
-
* splits it by showing what the theme SERVICE believes next to what the
|
|
1129
|
-
* document actually resolves.
|
|
1130
|
-
* @returns true when the debug line should render.
|
|
1155
|
+
* The switch gates DETAIL, never bad news: a line that reports a problem prints
|
|
1156
|
+
* whether or not it is on (see `sceneryLineIsWarning`), because "没有报错" and
|
|
1157
|
+
* "没有观测到报错" have been confused in this project before.
|
|
1158
|
+
* @returns true when the detail lines should render.
|
|
1131
1159
|
*/
|
|
1132
1160
|
function debugEnabled() {
|
|
1133
1161
|
try {
|
|
1134
|
-
|
|
1162
|
+
if (typeof window !== 'undefined'
|
|
1135
1163
|
&& typeof window.location?.hash === 'string'
|
|
1136
|
-
&& window.location.hash.includes('theme-gallery-debug')
|
|
1164
|
+
&& window.location.hash.includes('theme-gallery-debug')) return true
|
|
1165
|
+
return typeof window !== 'undefined' && window.localStorage?.getItem(DEBUG_KEY) === '1'
|
|
1137
1166
|
} catch {
|
|
1138
1167
|
return false
|
|
1139
1168
|
}
|
|
@@ -1211,12 +1240,15 @@ window.__ModuleLoader__.load({
|
|
|
1211
1240
|
/**
|
|
1212
1241
|
* The one-line scenery status shown on the panel.
|
|
1213
1242
|
*
|
|
1214
|
-
* Deliberately
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1243
|
+
* Deliberately dumb: it prints the report whatever it says. An earlier
|
|
1244
|
+
* revision only spoke when a self-check considered something wrong, so a check
|
|
1245
|
+
* that PASSED while the scenery was still invisible produced **silence** — the
|
|
1246
|
+
* worst possible output for a diagnostic. Keeping the numbers means the reader
|
|
1247
|
+
* sees the geometry even when the code's opinion of it is wrong.
|
|
1248
|
+
*
|
|
1249
|
+
* It was unconditional while the scenery was being brought up; now that the
|
|
1250
|
+
* package is public the RENDER SITE decides: routine reports wait for
|
|
1251
|
+
* `debugEnabled()`, while warnings and errors always print.
|
|
1220
1252
|
* @param selected - the preference the page is showing as selected.
|
|
1221
1253
|
* @returns the line, or null when the active theme asks for no scenery.
|
|
1222
1254
|
*/
|
|
@@ -1231,6 +1263,20 @@ window.__ModuleLoader__.load({
|
|
|
1231
1263
|
}
|
|
1232
1264
|
}
|
|
1233
1265
|
|
|
1266
|
+
/**
|
|
1267
|
+
* Whether the scenery line reports a problem rather than a routine pass.
|
|
1268
|
+
*
|
|
1269
|
+
* The line has three shapes: `装饰自检通过 · …` (routine), `⚠ <warning> · …`,
|
|
1270
|
+
* and `装饰自检失败: …`. Only the first may hide behind the debug switch — a
|
|
1271
|
+
* failure has to reach the person looking at the panel without them knowing
|
|
1272
|
+
* that a switch exists.
|
|
1273
|
+
* @param line - the scenery line.
|
|
1274
|
+
* @returns true when the line carries a warning or an error.
|
|
1275
|
+
*/
|
|
1276
|
+
function sceneryLineIsWarning(line) {
|
|
1277
|
+
return typeof line === 'string' && (line.startsWith('⚠') || line.includes('失败'))
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1234
1280
|
/**
|
|
1235
1281
|
* Render the scenery report as one line.
|
|
1236
1282
|
* @returns the report text.
|
|
@@ -2736,6 +2782,28 @@ window.__ModuleLoader__.load({
|
|
|
2736
2782
|
return BUNDLED_THEMES.find((theme) => theme.id === id)
|
|
2737
2783
|
}
|
|
2738
2784
|
|
|
2785
|
+
/**
|
|
2786
|
+
* Read one token out of a bundled skin, resolving the pair form to the skin's own scheme.
|
|
2787
|
+
*
|
|
2788
|
+
* The bundled skins store most tokens as `{ light, dark }` pairs even though a skin itself is
|
|
2789
|
+
* single-scheme. `flatten` collapses them for `register`; this is the same read, exposed
|
|
2790
|
+
* separately because the accent diagnostic needs ONE token's declared value without building
|
|
2791
|
+
* the whole registrable definition — and because that diagnostic lives at factory scope, where
|
|
2792
|
+
* `flatten` (declared inside `mountGallery`) is not reachable. Reaching for it from here is the
|
|
2793
|
+
* exact scope error `tests/check-scope-reach.mjs` exists to catch.
|
|
2794
|
+
* @param definition - a bundled skin.
|
|
2795
|
+
* @param name - the token name.
|
|
2796
|
+
* @returns the declared value, or undefined when the skin has no such token.
|
|
2797
|
+
*/
|
|
2798
|
+
function declaredToken(definition, name) {
|
|
2799
|
+
const raw = definition?.tokens?.[name]
|
|
2800
|
+
if (raw === undefined) return undefined
|
|
2801
|
+
// Deliberately not `raw?.[…]`: a skin whose token is neither a string nor a pair is a data
|
|
2802
|
+
// error, and `flatten` (the other caller) must keep failing loudly on it rather than
|
|
2803
|
+
// registering an unset variable that paints nothing.
|
|
2804
|
+
return typeof raw === 'string' ? raw : raw[definition.colorScheme]
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2739
2807
|
/**
|
|
2740
2808
|
* Disposer of the active accent layer, when one is stacked.
|
|
2741
2809
|
*
|
|
@@ -2748,6 +2816,20 @@ window.__ModuleLoader__.load({
|
|
|
2748
2816
|
*/
|
|
2749
2817
|
let accentLayerDispose
|
|
2750
2818
|
|
|
2819
|
+
/**
|
|
2820
|
+
* The accent colour {@link accentLayerDispose} was built from.
|
|
2821
|
+
*
|
|
2822
|
+
* Declared here for the same reason as the disposer above: `syncAccent` both reads and writes
|
|
2823
|
+
* it, and it is called during mount, so a `let` initialised later in the body would be in its
|
|
2824
|
+
* temporal dead zone at the first call.
|
|
2825
|
+
*
|
|
2826
|
+
* It exists so that re-stacking can be skipped when the colour has not moved. Without it every
|
|
2827
|
+
* `theme/change` — including ones this plugin caused and ones caused by something else —
|
|
2828
|
+
* disposed and re-created the layer, which emits again.
|
|
2829
|
+
* @type {string|undefined}
|
|
2830
|
+
*/
|
|
2831
|
+
let stackedAccent
|
|
2832
|
+
|
|
2751
2833
|
/**
|
|
2752
2834
|
* The plugin context, published for the factory-level helpers.
|
|
2753
2835
|
*
|
|
@@ -2796,15 +2878,46 @@ window.__ModuleLoader__.load({
|
|
|
2796
2878
|
// `try` swallowed it, and the accent marker silently never appeared. The module-level `ctx`
|
|
2797
2879
|
// below is assigned at mount and read here.
|
|
2798
2880
|
if (ctx === undefined) return
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2881
|
+
const wanted = typeof accent === 'string' && accent !== '' ? accent : undefined
|
|
2882
|
+
// ── WHY BOTH A SKIP AND THE SELF-EMIT GUARD ARE NEEDED ───────────────────────────────────
|
|
2883
|
+
//
|
|
2884
|
+
// `overrideTokens` and the disposer it returns BOTH emit `theme/change`, and this function
|
|
2885
|
+
// is reached from `publish()`, which is itself driven by that event:
|
|
2886
|
+
//
|
|
2887
|
+
// publish → syncSkin → syncAccent → overrideTokens → theme/change → publish → …
|
|
2888
|
+
//
|
|
2889
|
+
// Every level nests inside the previous CALL, so nothing ever yields to the event loop. The
|
|
2890
|
+
// stack therefore grows until the engine refuses to grow it further and throws
|
|
2891
|
+
// `RangeError: Maximum call stack size exceeded` — and because every level has its own
|
|
2892
|
+
// `try/catch` (see `syncSkin`), each one logs the SAME message on the way out. That is why
|
|
2893
|
+
// the console showed a flood of "could not stack the accent layer" lines rather than one
|
|
2894
|
+
// error: one flood, hundreds of identical lines, each from a different depth.
|
|
2895
|
+
//
|
|
2896
|
+
// Fixing it takes both halves, and they are not interchangeable:
|
|
2897
|
+
//
|
|
2898
|
+
// • the SKIP stops the churn — an accent that has not moved does not need a new layer, so
|
|
2899
|
+
// an unrelated `theme/change` (the shell's `adopt()`, a streamed render, another plugin)
|
|
2900
|
+
// no longer replaces the layer and causes a repaint;
|
|
2901
|
+
// • the GUARD stops the RE-ENTRY — it is what makes the `theme/change` emitted by our own
|
|
2902
|
+
// write invisible to the subscription, so the cycle cannot close even when the colour
|
|
2903
|
+
// genuinely did change.
|
|
2904
|
+
//
|
|
2905
|
+
// Only the guard is a correctness requirement; the skip is what keeps the layer stable. The
|
|
2906
|
+
// same shape is used by `stackSkinTokens` for the palette and by `syncReadingLayer`.
|
|
2907
|
+
if (wanted === stackedAccent) return
|
|
2908
|
+
emitting(() => {
|
|
2909
|
+
if (accentLayerDispose !== undefined) {
|
|
2910
|
+
accentLayerDispose()
|
|
2911
|
+
accentLayerDispose = undefined
|
|
2912
|
+
}
|
|
2913
|
+
stackedAccent = undefined
|
|
2914
|
+
if (wanted === undefined) return
|
|
2915
|
+
accentLayerDispose = ctx.theme.overrideTokens('theme-gallery: accent', {
|
|
2916
|
+
'--dsw-alias-button-ghost-active-fill': { light: `${wanted}29`, dark: `${wanted}29` },
|
|
2917
|
+
'--dsw-alias-button-ghost-active-border': { light: wanted, dark: wanted },
|
|
2918
|
+
'--dsw-alias-button-ghost-active-hover': { light: `${wanted}47`, dark: `${wanted}47` },
|
|
2919
|
+
})
|
|
2920
|
+
stackedAccent = wanted
|
|
2808
2921
|
})
|
|
2809
2922
|
}
|
|
2810
2923
|
|
|
@@ -2821,8 +2934,27 @@ window.__ModuleLoader__.load({
|
|
|
2821
2934
|
*
|
|
2822
2935
|
* The service makes this checkable without any guesswork: `composeActive` folds every
|
|
2823
2936
|
* override layer into `snapshot.active.tokens` before publishing, so the composed value is
|
|
2824
|
-
* readable straight off `getTheme()`.
|
|
2825
|
-
*
|
|
2937
|
+
* readable straight off `getTheme()`.
|
|
2938
|
+
*
|
|
2939
|
+
* ── WHAT CAN AND CANNOT PROVE IT ─────────────────────────────────────────
|
|
2940
|
+
*
|
|
2941
|
+
* The composed VALUE is the only channel that carries information, and only when it differs
|
|
2942
|
+
* from what the skin declares for that same token:
|
|
2943
|
+
*
|
|
2944
|
+
* • both bundled skins already ship `button-ghost-active-*` in their own palettes, AND the
|
|
2945
|
+
* palette layer supplies them too — so the token COUNT is 67 either way. An earlier
|
|
2946
|
+
* comment here claimed a count that "does not move" disproves the layer; for these skins
|
|
2947
|
+
* it cannot move, so the claim was wrong and the count is reported for context only;
|
|
2948
|
+
* • 梦海游鱼 declares `#2B74B5` for that token but its accent is `#FFD166`, so reading back
|
|
2949
|
+
* `#FFD166` proves the accent layer composed OVER the palette — the palette alone cannot
|
|
2950
|
+
* produce it;
|
|
2951
|
+
* • 山青婷彩's accent is `#E88BB0` and it declares `#E88BB0` for the same token, so its
|
|
2952
|
+
* reading is identical whether or not the layer is there. That is a property of the SKIN,
|
|
2953
|
+
* not of the layer, and the verdict below says so instead of implying the reading proved
|
|
2954
|
+
* something.
|
|
2955
|
+
*
|
|
2956
|
+
* The verdict is therefore computed, never asserted: it compares the composed value with the
|
|
2957
|
+
* skin's declared value and with the accent the layer was built from.
|
|
2826
2958
|
* @returns the reading, as one segment of the panel line.
|
|
2827
2959
|
*/
|
|
2828
2960
|
function describeAccentLayer() {
|
|
@@ -2833,16 +2965,31 @@ window.__ModuleLoader__.load({
|
|
|
2833
2965
|
const active = snapshot?.active
|
|
2834
2966
|
const tokens = active?.tokens ?? {}
|
|
2835
2967
|
const TOKEN = '--dsw-alias-button-ghost-active-border'
|
|
2836
|
-
const
|
|
2968
|
+
const composed = tokens[TOKEN]
|
|
2969
|
+
const definition = typeof active?.id === 'string' ? bundledTheme(active.id) : undefined
|
|
2970
|
+
const declared = declaredToken(definition, TOKEN)
|
|
2971
|
+
const accent = definition?.accent
|
|
2837
2972
|
const layers = snapshot?.overrides === undefined
|
|
2838
2973
|
? '(服务未暴露)'
|
|
2839
2974
|
: String(snapshot.overrides.size ?? snapshot.overrides.length ?? '?')
|
|
2840
|
-
//
|
|
2841
|
-
//
|
|
2975
|
+
// Computed, not assumed — see the note above. `不可分辨` is a real answer: it says this
|
|
2976
|
+
// skin's own value coincides with its accent, so this reading proves nothing either way.
|
|
2977
|
+
const verdict = accent === undefined
|
|
2978
|
+
? '非本包皮肤(无法判断)'
|
|
2979
|
+
: accent === declared
|
|
2980
|
+
? '不可分辨(强调色=皮肤自备值)'
|
|
2981
|
+
: composed === accent
|
|
2982
|
+
? '生效(强调色压过自备值)'
|
|
2983
|
+
: composed === declared
|
|
2984
|
+
? '未生效(仍是自备值)'
|
|
2985
|
+
: `未知(既非强调色也非自备值)`
|
|
2842
2986
|
return `激活层[id=${active?.id ?? '?'}`
|
|
2843
2987
|
+ ` 层数=${layers}`
|
|
2844
|
-
+ ` token数=${Object.keys(tokens).length}`
|
|
2845
|
-
+ ` ${TOKEN.replace('--dsw-alias-', '')}=${
|
|
2988
|
+
+ ` token数=${Object.keys(tokens).length}(自备同名令牌,不构成判据)`
|
|
2989
|
+
+ ` ${TOKEN.replace('--dsw-alias-', '')}=${composed === undefined ? '(缺失)' : String(composed)}`
|
|
2990
|
+
+ ` 自备=${declared === undefined ? '(无)' : String(declared)}`
|
|
2991
|
+
+ ` 强调色=${accent ?? '(无)'}`
|
|
2992
|
+
+ ` 判据=${verdict}]`
|
|
2846
2993
|
} catch (error) {
|
|
2847
2994
|
return `激活层[读取失败: ${String(error && error.message ? error.message : error)}]`
|
|
2848
2995
|
}
|
|
@@ -2879,6 +3026,9 @@ window.__ModuleLoader__.load({
|
|
|
2879
3026
|
],
|
|
2880
3027
|
})
|
|
2881
3028
|
}
|
|
3029
|
+
// Detail lines are opt-in; a line that reports a problem is not detail.
|
|
3030
|
+
const scenery = sceneryLine(selected)
|
|
3031
|
+
const showScenery = scenery !== null && (debugEnabled() || sceneryLineIsWarning(scenery))
|
|
2882
3032
|
return jsxs('div', {
|
|
2883
3033
|
className: 'tg-page',
|
|
2884
3034
|
children: [
|
|
@@ -2888,19 +3038,21 @@ window.__ModuleLoader__.load({
|
|
|
2888
3038
|
jsx('span', { className: 'tg-title', children: t('title') }),
|
|
2889
3039
|
jsx('span', { className: 'tg-hint', children: t('hint') }),
|
|
2890
3040
|
jsx('span', { className: 'tg-hint', children: t('count', { count: ids.length }) }),
|
|
3041
|
+
// What this copy is, so a reader can compare it with the version npm
|
|
3042
|
+
// publishes without digging through the profile.
|
|
3043
|
+
jsx('span', { className: 'tg-hint', children: `v${BUNDLED_VERSION}` }),
|
|
2891
3044
|
],
|
|
2892
3045
|
}),
|
|
2893
3046
|
debugEnabled() ? jsx('div', { className: 'tg-debug', children: themeDiagnostics(selected) }) : null,
|
|
2894
|
-
//
|
|
2895
|
-
// skin's decorations are
|
|
2896
|
-
//
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
? null
|
|
2900
|
-
: jsx('div', {
|
|
3047
|
+
// Routine reports wait for the debug switch; warnings and errors print
|
|
3048
|
+
// regardless — when the active skin's decorations are missing, the reason
|
|
3049
|
+
// has to reach the person looking at the panel.
|
|
3050
|
+
showScenery
|
|
3051
|
+
? jsx('div', {
|
|
2901
3052
|
className: `tg-debug${ambientWarning(selected) === null ? '' : ' tg-warn'}`,
|
|
2902
|
-
children:
|
|
2903
|
-
})
|
|
3053
|
+
children: scenery,
|
|
3054
|
+
})
|
|
3055
|
+
: null,
|
|
2904
3056
|
jsx('div', {
|
|
2905
3057
|
className: 'tg-grid',
|
|
2906
3058
|
children: ids.map((id) => jsx(ThemeCard, {
|
|
@@ -3692,8 +3844,10 @@ window.__ModuleLoader__.load({
|
|
|
3692
3844
|
*/
|
|
3693
3845
|
function flatten(definition) {
|
|
3694
3846
|
const tokens = {}
|
|
3695
|
-
for (const
|
|
3696
|
-
|
|
3847
|
+
for (const name of Object.keys(definition.tokens ?? {})) {
|
|
3848
|
+
// Delegated so the pair rule has exactly ONE implementation — the accent diagnostic reads
|
|
3849
|
+
// a single token through the same helper, and two copies of this rule would drift.
|
|
3850
|
+
tokens[name] = declaredToken(definition, name)
|
|
3697
3851
|
}
|
|
3698
3852
|
return { ...definition, tokens }
|
|
3699
3853
|
}
|
|
@@ -4068,6 +4222,17 @@ window.__ModuleLoader__.load({
|
|
|
4068
4222
|
let readingTag
|
|
4069
4223
|
/** Disposer of the active reading token layer, when one is stacked. */
|
|
4070
4224
|
let readingLayerDispose
|
|
4225
|
+
/**
|
|
4226
|
+
* The lightened ground {@link readingLayerDispose} was built from.
|
|
4227
|
+
*
|
|
4228
|
+
* Same role as `stackedAccent`: `syncReadingLayer` writes to the theme service, and it is
|
|
4229
|
+
* scheduled from a `MutationObserver` on `document.body` — so without this, every batch of
|
|
4230
|
+
* DOM mutations would replace the layer, which emits `theme/change`, which repaints the
|
|
4231
|
+
* shell, which mutates the DOM again. That is a loop through the microtask queue rather than
|
|
4232
|
+
* through the stack, so it would burn CPU steadily instead of throwing.
|
|
4233
|
+
* @type {string|undefined}
|
|
4234
|
+
*/
|
|
4235
|
+
let stackedReading
|
|
4071
4236
|
|
|
4072
4237
|
/**
|
|
4073
4238
|
* Install the reading rule, once per plugin lifetime.
|
|
@@ -4172,19 +4337,31 @@ window.__ModuleLoader__.load({
|
|
|
4172
4337
|
function syncReadingLayer() {
|
|
4173
4338
|
if (typeof document === 'undefined') return
|
|
4174
4339
|
const reading = document.body !== null && document.body.hasAttribute(READING_ATTRIBUTE)
|
|
4175
|
-
|
|
4340
|
+
// Computed before the skip test, because the skip compares VALUES. `lighten` is pure, so
|
|
4341
|
+
// the same document state always yields the same pair and the comparison is exact.
|
|
4342
|
+
const wanted = reading ? lighten(READING.bg, READING.alpha) : undefined
|
|
4343
|
+
const wantedDark = reading
|
|
4344
|
+
// Lightened ground for both palettes: a light theme wants a softer wash, a dark one a
|
|
4345
|
+
// slightly raised surface. Both keep the theme's hue.
|
|
4346
|
+
? lighten(READING.bg, Math.max(0, READING.alpha - 0.2))
|
|
4347
|
+
: undefined
|
|
4348
|
+
|
|
4349
|
+
// Both writes below emit `theme/change`, and this function is reached from a
|
|
4350
|
+
// `MutationObserver` on `document.body` — the same tree the shell repaints when the theme
|
|
4351
|
+
// changes. See the comment in `syncAccent` for the full shape; the short version is that
|
|
4352
|
+
// an unguarded write here lets the layer drive its own trigger.
|
|
4353
|
+
if (wanted === stackedReading) return
|
|
4354
|
+
emitting(() => {
|
|
4176
4355
|
if (readingLayerDispose !== undefined) {
|
|
4177
4356
|
readingLayerDispose()
|
|
4178
4357
|
readingLayerDispose = undefined
|
|
4179
4358
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
readingLayerDispose = ctx.theme.overrideTokens('theme-gallery: reading', {
|
|
4187
|
-
'--dsw-alias-bg-base': { light: lightened, dark: darkLightened },
|
|
4359
|
+
stackedReading = undefined
|
|
4360
|
+
if (wanted === undefined) return
|
|
4361
|
+
readingLayerDispose = ctx.theme.overrideTokens('theme-gallery: reading', {
|
|
4362
|
+
'--dsw-alias-bg-base': { light: wanted, dark: wantedDark },
|
|
4363
|
+
})
|
|
4364
|
+
stackedReading = wanted
|
|
4188
4365
|
})
|
|
4189
4366
|
}
|
|
4190
4367
|
|
|
@@ -4213,10 +4390,17 @@ window.__ModuleLoader__.load({
|
|
|
4213
4390
|
observer.disconnect()
|
|
4214
4391
|
if (readingTag !== undefined) readingTag.remove()
|
|
4215
4392
|
readingTag = undefined
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4393
|
+
// Guarded for the same reason as the writes in `syncReadingLayer`, and the remembered
|
|
4394
|
+
// value is cleared with the layer: leaving it set would make that function's skip test
|
|
4395
|
+
// believe a layer is still stacked after this one removed it, and it would then refuse
|
|
4396
|
+
// to re-stack.
|
|
4397
|
+
emitting(() => {
|
|
4398
|
+
if (readingLayerDispose !== undefined) {
|
|
4399
|
+
readingLayerDispose()
|
|
4400
|
+
readingLayerDispose = undefined
|
|
4401
|
+
}
|
|
4402
|
+
stackedReading = undefined
|
|
4403
|
+
})
|
|
4220
4404
|
document.body.removeAttribute(READING_ATTRIBUTE)
|
|
4221
4405
|
const centre = centreColumn()
|
|
4222
4406
|
if (centre !== null) centre.style.removeProperty('--dsh-reading-width')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-theme-gallery",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A community theme gallery for DeepSeek Harness (DSH): JSON-defined skins with full-screen coverage and ported sidebar scenery, switched from the plugin's own sidebar panel — adding skins needs no code change",
|
|
5
5
|
"author": "renjie2026 (https://github.com/renjie2026)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"test:scopelogic": "node tests/check-scope-reach-logic.mjs",
|
|
62
62
|
"test:preview": "node scripts/build-ambient-preview.mjs",
|
|
63
63
|
"test:calls": "node tests/check-undefined-calls.mjs",
|
|
64
|
-
"test": "
|
|
64
|
+
"test:emitguard": "node tests/check-self-emit-guard.mjs && node tests/check-self-emit-guard-logic.mjs",
|
|
65
|
+
"test": "npm run test:schema && npm run test:host && npm run test:store && npm run test:order && npm run test:tdz && npm run test:theme && npm run test:ambient && npm run test:boot && npm run test:safety && npm run test:module && npm run test:bounded && npm run test:bootpath && npm run test:scope && npm run test:scopelogic && npm run test:calls && npm run test:emitguard",
|
|
65
66
|
"prepack": "node scripts/embed-themes.mjs",
|
|
66
67
|
"publish:check": "node scripts/publish-check.mjs",
|
|
67
68
|
"embed-themes": "node scripts/embed-themes.mjs",
|