dsh-theme-gallery 0.3.0 → 0.4.1
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 +116 -6
- package/lib/client.js +1229 -28
- package/lib/palette-schemes.json +20 -0
- package/lib/themes/meng-hai-you-yu.json +1 -1
- package/lib/themes/pei-an-jie-xin.json +1 -1
- package/lib/themes/shan-qing-ting-cai.json +1 -1
- package/lib/themes/shi-liu-jin.json +293 -0
- package/lib/themes/ying-mu-cai-yun.json +1 -1
- package/package.json +1 -1
- package/schema/theme.schema.json +36 -0
package/lib/client.js
CHANGED
|
@@ -33,6 +33,21 @@
|
|
|
33
33
|
* one plugin is one sidebar entrance plus one main-column page — so it has room
|
|
34
34
|
* for previews, descriptions and whatever this gallery grows into.
|
|
35
35
|
*
|
|
36
|
+
* ## 出处与致谢(这是别人的劳动成果,必须写清楚)
|
|
37
|
+
*
|
|
38
|
+
* - **纯色/拼色卡片上的 15 个色值,全部取自「中国传统色库」项目**
|
|
39
|
+
* (chinese-colors):<https://github.com/zerosoul/chinese-colors>
|
|
40
|
+
* 作者 **tristan**(GitHub [@zerosoul](https://github.com/zerosoul)),
|
|
41
|
+
* 在线手册 <https://colors.ichuantong.cn>,许可 ISC。
|
|
42
|
+
* 该项目的 `src/assets/colors.json` 整理了 170 个中国传统色的名字与色值,
|
|
43
|
+
* 本插件只做**取色与派生**(每套方案只挑三四个名字色,其余 token 由
|
|
44
|
+
* `schemeTokens()` 按同一条造色规则算出来)。色值与命名是作者整理的成果,
|
|
45
|
+
* **感谢作者 tristan 的整理与开源**。取色数据见 `lib/palette-schemes.json`,
|
|
46
|
+
* 每套方案都带 `source` 字段记着它在色库里的「组 + id」。
|
|
47
|
+
* - 七套**复刻**皮肤的场景与配色复刻自**蜂链商城**电商新零售系统管理后台的主题,
|
|
48
|
+
* 项目地址:<https://github.com/renjie2026/fenglianshop-open>(该项目的开发者即本插件作者)。
|
|
49
|
+
* - 两套宠物主题(琥珀猫咪、虎子阿黄)为本插件原创。
|
|
50
|
+
*
|
|
36
51
|
* ## Two conversational states
|
|
37
52
|
*
|
|
38
53
|
* A skin covers the screen; a screen covered by a gradient is what makes a long
|
|
@@ -124,6 +139,7 @@ window.__ModuleLoader__.load({
|
|
|
124
139
|
const CARD_ORDER = {
|
|
125
140
|
light: 99,
|
|
126
141
|
dark: 98,
|
|
142
|
+
'shi-liu-jin': 97,
|
|
127
143
|
'ying-mu-cai-yun': 96,
|
|
128
144
|
'shan-qing-ting-cai': 95,
|
|
129
145
|
'pei-an-jie-xin': 91,
|
|
@@ -180,6 +196,284 @@ window.__ModuleLoader__.load({
|
|
|
180
196
|
return [...themes].sort((a, b) => cardRank(b.id) - cardRank(a.id))
|
|
181
197
|
}
|
|
182
198
|
|
|
199
|
+
/** 配色方案 id 的形态:`p-` 前缀,避免与主题 id 混淆(锚主题就叫 `shi-liu-jin`)。 */
|
|
200
|
+
const SCHEME_ID = /^p-[a-z0-9-]+$/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 15 套「纯色/拼色」配色方案的落点。
|
|
204
|
+
*
|
|
205
|
+
* 声明在这里、赋值在文件下方(紧接内联的 `BUNDLED_PALETTES`)—— 工厂体自上而下执行,
|
|
206
|
+
* 而 `const BUNDLED_PALETTES` 内联在主题数组之后,所以这里只能先给一个空表。
|
|
207
|
+
* 表为空时**不会**静默画错:`cardRowShape` 找不到方案就退回默认色带,
|
|
208
|
+
* 而建期(`embed-themes.mjs`)与测试都要求每个方案 id 真实存在。
|
|
209
|
+
*/
|
|
210
|
+
let PALETTE_SCHEMES = []
|
|
211
|
+
|
|
212
|
+
/** 配色卡自己的主题 id("锚"):只有它是活动主题时,所选方案才有效。 */
|
|
213
|
+
const PALETTE_ANCHOR = 'shi-liu-jin'
|
|
214
|
+
|
|
215
|
+
/** 所选配色方案记在这里。与皮肤 id 一样,**绝不**写进 `ui-theme.preference`。 */
|
|
216
|
+
const SCHEME_KEY = 'theme-gallery:palette'
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 按 id 取一套配色方案。
|
|
220
|
+
* @param id - 方案 id。
|
|
221
|
+
* @param schemes - 方案表(显式传入,便于测试与预览各自喂数据)。
|
|
222
|
+
* @returns 方案,或 undefined。
|
|
223
|
+
*/
|
|
224
|
+
function schemeById(id, schemes) {
|
|
225
|
+
const list = Array.isArray(schemes) ? schemes : []
|
|
226
|
+
return list.find((scheme) => scheme !== null && typeof scheme === 'object' && scheme.id === id)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 两个 6 位 hex 之间线性插值。
|
|
231
|
+
* @param a - 起点 `#rrggbb`。
|
|
232
|
+
* @param b - 终点 `#rrggbb`。
|
|
233
|
+
* @param t - 0 = a,1 = b。
|
|
234
|
+
* @returns `#rrggbb`。
|
|
235
|
+
*/
|
|
236
|
+
function blendHex(a, b, t) {
|
|
237
|
+
const parts = (hex) => [1, 3, 5].map((at) => parseInt(hex.slice(at, at + 2), 16))
|
|
238
|
+
const [r1, g1, b1] = parts(a)
|
|
239
|
+
const [r2, g2, b2] = parts(b)
|
|
240
|
+
const mix = (x, y) => Math.round(x + (y - x) * t).toString(16).padStart(2, '0')
|
|
241
|
+
return `#${mix(r1, r2)}${mix(g1, g2)}${mix(b1, b2)}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 把颜色往白(t > 0)或往黑(t < 0)推。
|
|
246
|
+
*
|
|
247
|
+
* 15 套配色里除了主色与底色,其余 60 多个 token 全靠它派生 —— 这样 15 套不会各写各的,
|
|
248
|
+
* 也就不会彼此漂移。压深出来的色值不是色库里的名字色,这是**派生的深浅阶**,
|
|
249
|
+
* 与既有皮肤的做法一致(例如琥珀猫咪用「琥珀深焙」做按钮族、`琥珀` 做强调色)。
|
|
250
|
+
* @param hex - `#rrggbb`。
|
|
251
|
+
* @param t - 正数提亮、负数压深,绝对值是幅度。
|
|
252
|
+
* @returns `#rrggbb`。
|
|
253
|
+
*/
|
|
254
|
+
function shade(hex, t) {
|
|
255
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(String(hex))) return hex
|
|
256
|
+
return t >= 0 ? blendHex(hex, '#FFFFFF', t) : blendHex(hex, '#000000', -t)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 卡片上那 15 个色值按钮的数据。
|
|
261
|
+
*
|
|
262
|
+
* ── 这一版换了语义 ─────────────────────────────────────────────────────────
|
|
263
|
+
*
|
|
264
|
+
* 早先 `card.rows` 直接写十六进制色块(纯展示)。现在每一格是**一个可点的配色方案 id**:
|
|
265
|
+
* 颜色从方案表来,所以卡片显示什么、点下去换什么,是同一条数据,不可能对不上。
|
|
266
|
+
*
|
|
267
|
+
* 仍然按老规矩消毒:`embed-themes.mjs` 校验的是本包出货的皮肤,而注册表里还有别的插件
|
|
268
|
+
* 贡献的主题;坏数据在这里退回默认色带,而不是把 `undefined` 画进背景。
|
|
269
|
+
* @param theme - 注册表里的主题定义。
|
|
270
|
+
* @param schemes - 配色方案表。
|
|
271
|
+
* @returns 两到三排 `{ kind, schemes }`,或 undefined(回落默认色带)。
|
|
272
|
+
*/
|
|
273
|
+
function cardRowShape(theme, schemes) {
|
|
274
|
+
const card = theme.card
|
|
275
|
+
const rows = card === undefined || card === null ? undefined : card.rows
|
|
276
|
+
if (!Array.isArray(rows) || rows.length < 2 || rows.length > 3) return undefined
|
|
277
|
+
const clean = []
|
|
278
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
279
|
+
const row = rows[index]
|
|
280
|
+
if (row === null || typeof row !== 'object') return undefined
|
|
281
|
+
// 最后一排是「拼色」,前面都是「纯色」:这是用户定的版式,也让"哪一排是拼色"
|
|
282
|
+
// 不再靠眼睛判断。写歪就整张卡退回默认色带(可见地与预期不同,而不是微妙地错)。
|
|
283
|
+
const isLast = index === rows.length - 1
|
|
284
|
+
const wanted = isLast ? 'clash' : 'solid'
|
|
285
|
+
if (row.kind !== wanted) return undefined
|
|
286
|
+
if (!Array.isArray(row.schemes) || row.schemes.length < 1 || row.schemes.length > 5) return undefined
|
|
287
|
+
for (const id of row.schemes) {
|
|
288
|
+
if (typeof id !== 'string' || !SCHEME_ID.test(id)) return undefined
|
|
289
|
+
const scheme = schemeById(id, schemes)
|
|
290
|
+
// 方案存在、且它自己的 kind 与本排一致 —— 否则会把拼色画成一块纯色。
|
|
291
|
+
if (scheme === undefined || scheme.kind !== wanted) return undefined
|
|
292
|
+
}
|
|
293
|
+
clean.push({ kind: wanted, schemes: [...row.schemes] })
|
|
294
|
+
}
|
|
295
|
+
return clean
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* WCAG 对比度(1..21)。与 `scripts/lib/card-rows.mjs` 里那份是同一条公式的两个落点:
|
|
300
|
+
* 那份在**建期**拦配色表,这份在**运行期**决定按钮文字取白还是取深。
|
|
301
|
+
* @param a - `#rrggbb`。
|
|
302
|
+
* @param b - `#rrggbb`。
|
|
303
|
+
* @returns 对比度;任一输入非法时返回 0。
|
|
304
|
+
*/
|
|
305
|
+
function wcagContrast(a, b) {
|
|
306
|
+
const lum = (hex) => {
|
|
307
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(String(hex))) return null
|
|
308
|
+
const linear = [1, 3, 5].map((at) => parseInt(hex.slice(at, at + 2), 16) / 255)
|
|
309
|
+
.map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4))
|
|
310
|
+
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
|
|
311
|
+
}
|
|
312
|
+
const la = lum(a)
|
|
313
|
+
const lb = lum(b)
|
|
314
|
+
if (la === null || lb === null) return 0
|
|
315
|
+
// 先算再返回,**不要**写成 `return (…)`:`check-scope-reach.mjs` 的调用识别会把
|
|
316
|
+
// `return (` 读成"调用了名为 return 的函数",于是报一条假违规(规则 6 的另一面:
|
|
317
|
+
// 审计误报同样要消除,否则它就不再可信)。
|
|
318
|
+
const ratio = (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05)
|
|
319
|
+
return ratio
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 这个方案的按钮/链接填充色。
|
|
324
|
+
*
|
|
325
|
+
* 缃色、橘黄、桃红这类**亮色**做填充时,白字压上去只有 1.7–3.7:1(读不清)。
|
|
326
|
+
* 所以填充色按对比度**逐个候选**试出来,而不是写死一个压深量:
|
|
327
|
+
*
|
|
328
|
+
* 1. 本色 + 本方案的深色文字(最好:按钮留在色库那个名字色上)—— 缃色/橘黄/桃红都是这一条;
|
|
329
|
+
* 2. 本色 + 白字;
|
|
330
|
+
* 3. 依次压深 0.35 / 0.5 / 0.65 再用白字(海棠红、松柏绿、竹青、苍青走这条)。
|
|
331
|
+
*
|
|
332
|
+
* 方案可以用 `fill` 显式指定(石榴金就是:用户定的按钮色是色库里的「胭脂」`#9D2933`,
|
|
333
|
+
* 而不是把石榴红压深出来的合成色)。
|
|
334
|
+
* @param scheme - 一套配色方案。
|
|
335
|
+
* @returns `#rrggbb`。
|
|
336
|
+
*/
|
|
337
|
+
function schemeButtonFill(scheme) {
|
|
338
|
+
const choice = schemeButtonChoice(scheme)
|
|
339
|
+
return choice[0]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 按钮填充色上的文字色(白或方案自己的深色),取对比度更高的那个。
|
|
344
|
+
* @param scheme - 一套配色方案。
|
|
345
|
+
* @returns `#rrggbb`。
|
|
346
|
+
*/
|
|
347
|
+
function schemeOnMain(scheme) {
|
|
348
|
+
return schemeButtonChoice(scheme)[1]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* 选出「填充色 + 文字色」这一对(规则见 {@link schemeButtonFill})。
|
|
353
|
+
* @param scheme - 一套配色方案。
|
|
354
|
+
* @returns `[fill, onFill]`。
|
|
355
|
+
*/
|
|
356
|
+
function schemeButtonChoice(scheme) {
|
|
357
|
+
const main = String(scheme.main)
|
|
358
|
+
const ink = String(scheme.ink)
|
|
359
|
+
if (typeof scheme.fill === 'string' && /^#[0-9a-fA-F]{6}$/.test(scheme.fill)) {
|
|
360
|
+
const onFill = wcagContrast(scheme.fill, '#FFFFFF') >= wcagContrast(scheme.fill, ink)
|
|
361
|
+
? '#FFFFFF' : ink
|
|
362
|
+
return [scheme.fill, onFill]
|
|
363
|
+
}
|
|
364
|
+
const candidates = [
|
|
365
|
+
[main, ink],
|
|
366
|
+
[main, '#FFFFFF'],
|
|
367
|
+
[shade(main, -0.35), '#FFFFFF'],
|
|
368
|
+
[shade(main, -0.5), '#FFFFFF'],
|
|
369
|
+
[shade(main, -0.65), '#FFFFFF'],
|
|
370
|
+
]
|
|
371
|
+
for (const candidate of candidates) {
|
|
372
|
+
if (wcagContrast(candidate[0], candidate[1]) >= 4.5) return candidate
|
|
373
|
+
}
|
|
374
|
+
return candidates.reduce((best, candidate) => (
|
|
375
|
+
wcagContrast(candidate[0], candidate[1]) > wcagContrast(best[0], best[1]) ? candidate : best
|
|
376
|
+
))
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 把一套配色方案展开成完整的 67 个 token(`overrideTokens` 层的成对格式)。
|
|
381
|
+
*
|
|
382
|
+
* 只有 `main` / `ground` / `ink` / `dots` 是色库里的名字色,其余全部由
|
|
383
|
+
* {@link shade} 派生:底色深浅阶、主色透明度边线、侧栏渐变。侧栏**刻意停在浅阶**
|
|
384
|
+
* (最深处只到主色的 0.42 白化),因为 `label-*` 是全界面共享的 —— 深底会让导航文字读不清,
|
|
385
|
+
* 这条教训写在 SKILL §14(深色系源侧栏一律"保色相提明度")。
|
|
386
|
+
*
|
|
387
|
+
* `state-{error,success,warn}` 三族**不随配色变**:它们是语义色,跟着调色板走的后果是
|
|
388
|
+
* 报错不再像报错。`state-business-primary` 除外 —— 它画"当前文件夹"的图标,属于品牌语汇:
|
|
389
|
+
* 纯色方案用主色,拼色方案用第三个点缀色(那正是"撞"出来的那个)。
|
|
390
|
+
* @param scheme - 一套配色方案。
|
|
391
|
+
* @returns `{ token: { light, dark } }`。
|
|
392
|
+
*/
|
|
393
|
+
function schemeTokens(scheme) {
|
|
394
|
+
const main = String(scheme.main)
|
|
395
|
+
const ground = String(scheme.ground)
|
|
396
|
+
const ink = String(scheme.ink)
|
|
397
|
+
const fill = schemeButtonFill(scheme)
|
|
398
|
+
const onFill = schemeOnMain(scheme)
|
|
399
|
+
const business = scheme.kind === 'clash' && Array.isArray(scheme.dots) && scheme.dots.length > 2
|
|
400
|
+
? String(scheme.dots[2])
|
|
401
|
+
: main
|
|
402
|
+
const pair = (value) => ({ light: value, dark: value })
|
|
403
|
+
const stops = [0, 20, 36, 50, 62, 74, 86, 100]
|
|
404
|
+
const sidebar = [0.94, 0.86, 0.78, 0.70, 0.62, 0.54, 0.48, 0.42]
|
|
405
|
+
.map((t, index) => `${shade(main, t)} ${stops[index]}%`).join(',')
|
|
406
|
+
return {
|
|
407
|
+
'--dsw-alias-bg-base': pair(`linear-gradient(to bottom,${shade(ground, 0.5)} 0%,${ground} 55%,${shade(ground, -0.06)} 100%)`),
|
|
408
|
+
'--dsw-alias-bg-layer-1': pair(shade(ground, 0.72)),
|
|
409
|
+
'--dsw-alias-bg-layer-2': pair('#FFFFFF'),
|
|
410
|
+
'--dsw-alias-bg-layer-3': pair('#FFFFFF'),
|
|
411
|
+
'--dsw-alias-bg-overlay': pair(shade(ground, -0.1)),
|
|
412
|
+
'--dsw-alias-bg-skeleton': pair(`${main}14`),
|
|
413
|
+
'--dsw-alias-bg-module-platform': pair(shade(ground, -0.1)),
|
|
414
|
+
'--dsw-alias-bg-multi-select': pair(shade(ground, -0.1)),
|
|
415
|
+
'--dsw-alias-border-l1': pair(`${main}0F`),
|
|
416
|
+
'--dsw-alias-border-l2': pair(`${main}21`),
|
|
417
|
+
'--dsw-alias-border-l3': pair(`${main}2E`),
|
|
418
|
+
'--dsw-alias-border-l4': pair(`${main}3D`),
|
|
419
|
+
'--dsw-alias-brand-primary': pair(fill),
|
|
420
|
+
'--dsw-alias-brand-primary-invert': pair('#FFFFFF'),
|
|
421
|
+
'--dsw-alias-brand-text': pair(fill),
|
|
422
|
+
'--dsw-alias-label-primary': pair(ink),
|
|
423
|
+
'--dsw-alias-label-primary-bluish': pair(ink),
|
|
424
|
+
'--dsw-alias-label-secondary': pair(shade(ink, 0.22)),
|
|
425
|
+
'--dsw-alias-label-tertiary': pair(shade(ink, 0.42)),
|
|
426
|
+
'--dsw-alias-label-caption': pair(shade(ink, 0.42)),
|
|
427
|
+
'--dsw-alias-label-dimmed': pair(shade(ink, 0.62)),
|
|
428
|
+
'--dsw-alias-label-primary-dimmed': pair(shade(ink, 0.22)),
|
|
429
|
+
'--dsw-alias-label-primary-foreground': pair(onFill),
|
|
430
|
+
'--dsw-alias-label-primary-inverted': pair('#FFFFFF'),
|
|
431
|
+
'--dsw-alias-link': pair(fill),
|
|
432
|
+
'--dsw-alias-interactive-bg-hover': pair(`${main}14`),
|
|
433
|
+
'--dsw-alias-interactive-bg-active': pair(`${main}2E`),
|
|
434
|
+
'--dsw-alias-interactive-bg-hover-solid': pair(shade(ground, -0.08)),
|
|
435
|
+
'--dsw-alias-interactive-bg-hover-accent': pair(`${main}3D`),
|
|
436
|
+
'--dsw-alias-button-primary-fill': pair(fill),
|
|
437
|
+
'--dsw-alias-button-primary-hover': pair(shade(fill, 0.12)),
|
|
438
|
+
'--dsw-alias-button-primary-dimmed': pair(`${fill}47`),
|
|
439
|
+
'--dsw-alias-button-ghost-active-fill': pair(`${main}29`),
|
|
440
|
+
'--dsw-alias-button-ghost-active-border': pair(main),
|
|
441
|
+
'--dsw-alias-button-ghost-active-hover': pair(`${main}47`),
|
|
442
|
+
'--dsw-alias-button-info-fill': pair(shade(ink, 0.22)),
|
|
443
|
+
'--dsw-alias-button-info-hover': pair(shade(ink, 0.1)),
|
|
444
|
+
'--dsw-alias-button-elevated-fill': pair('#FFFFFF'),
|
|
445
|
+
'--dsw-alias-button-floating-fill': pair('#FFFFFF'),
|
|
446
|
+
'--dsw-alias-button-floating-hover': pair(shade(ground, 0.8)),
|
|
447
|
+
'--dsw-alias-button-contrast-fill': pair(ink),
|
|
448
|
+
'--dsw-alias-markdown-code-block': pair(shade(ground, 0.6)),
|
|
449
|
+
'--dsw-alias-markdown-code-block-banner': pair(shade(ground, 0.45)),
|
|
450
|
+
'--dsw-alias-markdown-inline-code': pair(shade(ground, -0.08)),
|
|
451
|
+
'--dsw-alias-markdown-citation': pair(shade(ground, -0.08)),
|
|
452
|
+
'--dsw-alias-markdown-tag': pair(shade(ground, -0.08)),
|
|
453
|
+
'--dsw-alias-markdown-placeholder': pair(shade(ground, 0.4)),
|
|
454
|
+
'--dsw-alias-markdown-code-segment-selected': pair(`${main}29`),
|
|
455
|
+
'--dsw-alias-markdown-code-segment-unselected': pair(shade(ground, 0.6)),
|
|
456
|
+
'--dsw-alias-scrollbar-bg-l1': pair(`${main}29`),
|
|
457
|
+
'--dsw-alias-scrollbar-bg-l2': pair(`${main}38`),
|
|
458
|
+
'--dsw-alias-scrollbar-hover-l1': pair(`${main}52`),
|
|
459
|
+
'--dsw-alias-scrollbar-hover-l2': pair(`${main}66`),
|
|
460
|
+
'--dsw-alias-tooltip-bg': pair(ink),
|
|
461
|
+
'--dsw-alias-toast-bg': pair('#FFFFFF'),
|
|
462
|
+
'--dsw-alias-state-error-primary': pair('#C2352B'),
|
|
463
|
+
'--dsw-alias-state-error-secondary': pair('#DB5A6B'),
|
|
464
|
+
'--dsw-alias-state-success-primary': pair('#1DA981'),
|
|
465
|
+
'--dsw-alias-state-success-secondary': pair('#7FE3C0'),
|
|
466
|
+
'--dsw-alias-state-success-tertiary': pair('#D8F5E8'),
|
|
467
|
+
'--dsw-alias-state-warn-primary': pair('#C89B40'),
|
|
468
|
+
'--dsw-alias-state-warn-secondary': pair('#EACD76'),
|
|
469
|
+
'--dsw-alias-state-warn-tertiary': pair('#F7EBC8'),
|
|
470
|
+
'--dsw-alias-state-warn-label': pair('#7A5A0E'),
|
|
471
|
+
'--dsw-alias-state-business-primary': pair(business),
|
|
472
|
+
'--dsw-alias-state-business-tertiary': pair(shade(business, 0.55)),
|
|
473
|
+
'--dsw-specific-sidebar-fill': pair(`linear-gradient(to bottom,${sidebar})`),
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
183
477
|
/**
|
|
184
478
|
* Record the user's card choice BEFORE it reaches the theme service.
|
|
185
479
|
*
|
|
@@ -231,6 +525,54 @@ window.__ModuleLoader__.load({
|
|
|
231
525
|
}
|
|
232
526
|
}
|
|
233
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Record the palette scheme the user clicked on the picker card.
|
|
530
|
+
*
|
|
531
|
+
* Same storage rule as the skin id: **`localStorage` only**, never
|
|
532
|
+
* `ui-theme.preference` (a value the boot graph reads would have to be a built-in
|
|
533
|
+
* id, and an unknown one makes `buildSnapshot()` throw and refuse to start).
|
|
534
|
+
* @param id - the scheme id (`p-…`).
|
|
535
|
+
*/
|
|
536
|
+
function rememberScheme(id) {
|
|
537
|
+
try {
|
|
538
|
+
if (typeof window === 'undefined') return
|
|
539
|
+
window.localStorage?.setItem(SCHEME_KEY, String(id))
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error('[theme-gallery] could not record the palette scheme:', error)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* The palette scheme the user picked, if any.
|
|
547
|
+
* @returns the id, or null when the user never picked one.
|
|
548
|
+
*/
|
|
549
|
+
function rememberedScheme() {
|
|
550
|
+
try {
|
|
551
|
+
if (typeof window === 'undefined') return null
|
|
552
|
+
const value = window.localStorage?.getItem(SCHEME_KEY)
|
|
553
|
+
if (typeof value !== 'string' || value === '') return null
|
|
554
|
+
// 只认表里真实存在的 id:换过色表、手改过 localStorage 时,宁可当作没选,
|
|
555
|
+
// 也不要叠一层空方案(那会把整屏的颜色悄悄换掉)。
|
|
556
|
+
return schemeById(value, PALETTE_SCHEMES) === undefined ? null : value
|
|
557
|
+
} catch {
|
|
558
|
+
return null
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** 卡片上默认点亮的那一格:锚主题自己的那套配色(没选过任何方案时)。 */
|
|
563
|
+
const DEFAULT_SCHEME = 'p-shi-liu-jin'
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* 卡片上应当点亮哪一格。
|
|
567
|
+
*
|
|
568
|
+
* 没选过时给默认值**只是为了显示**:那时并不叠配色层,屏幕上是锚主题自己
|
|
569
|
+
* 手工写的那套 token(与这一格同一组关键色,观感几乎一致)。
|
|
570
|
+
* @returns 方案 id。
|
|
571
|
+
*/
|
|
572
|
+
function wantedScheme() {
|
|
573
|
+
return rememberedScheme() ?? DEFAULT_SCHEME
|
|
574
|
+
}
|
|
575
|
+
|
|
234
576
|
/** Body attribute publishing the reading state; the injected rule reads it. */
|
|
235
577
|
const READING_ATTRIBUTE = 'data-dsh-theme-reading'
|
|
236
578
|
|
|
@@ -257,7 +599,7 @@ window.__ModuleLoader__.load({
|
|
|
257
599
|
* package.json, and the release workflow fails when re-running the embed step
|
|
258
600
|
* changes a tracked file.
|
|
259
601
|
*/
|
|
260
|
-
const BUNDLED_VERSION = '0.
|
|
602
|
+
const BUNDLED_VERSION = '0.4.1'
|
|
261
603
|
|
|
262
604
|
/**
|
|
263
605
|
* Themes this package contributes, inlined from lib/themes/*.json.
|
|
@@ -849,7 +1191,7 @@ window.__ModuleLoader__.load({
|
|
|
849
1191
|
{
|
|
850
1192
|
"id": "meng-hai-you-yu",
|
|
851
1193
|
"label": "梦海游鱼",
|
|
852
|
-
"description": "梦幻海洋,游鱼作伴 ——
|
|
1194
|
+
"description": "梦幻海洋,游鱼作伴 —— 复刻自蜂链商城电商新零售系统管理后台的默认主题",
|
|
853
1195
|
"colorScheme": "light",
|
|
854
1196
|
"tokens": {
|
|
855
1197
|
"--dsw-alias-bg-base": {
|
|
@@ -1139,7 +1481,7 @@ window.__ModuleLoader__.load({
|
|
|
1139
1481
|
{
|
|
1140
1482
|
"id": "pei-an-jie-xin",
|
|
1141
1483
|
"label": "佩安杰心",
|
|
1142
|
-
"description": "檀香禅影,自在安顿 ——
|
|
1484
|
+
"description": "檀香禅影,自在安顿 —— 复刻自蜂链商城电商新零售系统管理后台同名主题",
|
|
1143
1485
|
"colorScheme": "light",
|
|
1144
1486
|
"tokens": {
|
|
1145
1487
|
"--dsw-alias-bg-base": {
|
|
@@ -1427,7 +1769,7 @@ window.__ModuleLoader__.load({
|
|
|
1427
1769
|
{
|
|
1428
1770
|
"id": "shan-qing-ting-cai",
|
|
1429
1771
|
"label": "山青婷彩",
|
|
1430
|
-
"description": "青山叠翠,蜓舞生姿 ——
|
|
1772
|
+
"description": "青山叠翠,蜓舞生姿 —— 复刻自蜂链商城电商新零售系统管理后台同名主题",
|
|
1431
1773
|
"colorScheme": "light",
|
|
1432
1774
|
"tokens": {
|
|
1433
1775
|
"--dsw-alias-bg-base": {
|
|
@@ -1712,10 +2054,328 @@ window.__ModuleLoader__.load({
|
|
|
1712
2054
|
"petals": 7
|
|
1713
2055
|
}
|
|
1714
2056
|
},
|
|
2057
|
+
{
|
|
2058
|
+
"id": "shi-liu-jin",
|
|
2059
|
+
"label": "纯色/拼色",
|
|
2060
|
+
"description": "石榴红撞赤金与翡翠 —— 纯色排 × 拼色排的配色卡:15 个色块铺满卡面,不含侧栏素材",
|
|
2061
|
+
"colorScheme": "light",
|
|
2062
|
+
"tokens": {
|
|
2063
|
+
"--dsw-alias-bg-base": {
|
|
2064
|
+
"light": "linear-gradient(to bottom,#FBF7EC 0%,#F2ECDE 55%,#EADFC4 100%)",
|
|
2065
|
+
"dark": "linear-gradient(to bottom,#FBF7EC 0%,#F2ECDE 55%,#EADFC4 100%)"
|
|
2066
|
+
},
|
|
2067
|
+
"--dsw-alias-bg-layer-1": {
|
|
2068
|
+
"light": "#F0F0F4",
|
|
2069
|
+
"dark": "#F0F0F4"
|
|
2070
|
+
},
|
|
2071
|
+
"--dsw-alias-bg-layer-2": {
|
|
2072
|
+
"light": "#FFFBF0",
|
|
2073
|
+
"dark": "#FFFBF0"
|
|
2074
|
+
},
|
|
2075
|
+
"--dsw-alias-bg-layer-3": {
|
|
2076
|
+
"light": "#FFFBF0",
|
|
2077
|
+
"dark": "#FFFBF0"
|
|
2078
|
+
},
|
|
2079
|
+
"--dsw-alias-bg-overlay": {
|
|
2080
|
+
"light": "#EEDEB0",
|
|
2081
|
+
"dark": "#EEDEB0"
|
|
2082
|
+
},
|
|
2083
|
+
"--dsw-alias-bg-skeleton": {
|
|
2084
|
+
"light": "#D1D9E0",
|
|
2085
|
+
"dark": "#D1D9E0"
|
|
2086
|
+
},
|
|
2087
|
+
"--dsw-alias-bg-module-platform": {
|
|
2088
|
+
"light": "#EEDEB0",
|
|
2089
|
+
"dark": "#EEDEB0"
|
|
2090
|
+
},
|
|
2091
|
+
"--dsw-alias-bg-multi-select": {
|
|
2092
|
+
"light": "#EEDEB0",
|
|
2093
|
+
"dark": "#EEDEB0"
|
|
2094
|
+
},
|
|
2095
|
+
"--dsw-alias-border-l1": {
|
|
2096
|
+
"light": "#9D29330F",
|
|
2097
|
+
"dark": "#9D29330F"
|
|
2098
|
+
},
|
|
2099
|
+
"--dsw-alias-border-l2": {
|
|
2100
|
+
"light": "#9D293321",
|
|
2101
|
+
"dark": "#9D293321"
|
|
2102
|
+
},
|
|
2103
|
+
"--dsw-alias-border-l3": {
|
|
2104
|
+
"light": "#9D29332E",
|
|
2105
|
+
"dark": "#9D29332E"
|
|
2106
|
+
},
|
|
2107
|
+
"--dsw-alias-border-l4": {
|
|
2108
|
+
"light": "#D1D9E080",
|
|
2109
|
+
"dark": "#D1D9E080"
|
|
2110
|
+
},
|
|
2111
|
+
"--dsw-alias-brand-primary": {
|
|
2112
|
+
"light": "#9D2933",
|
|
2113
|
+
"dark": "#9D2933"
|
|
2114
|
+
},
|
|
2115
|
+
"--dsw-alias-brand-primary-invert": {
|
|
2116
|
+
"light": "#FFFFFF",
|
|
2117
|
+
"dark": "#FFFFFF"
|
|
2118
|
+
},
|
|
2119
|
+
"--dsw-alias-brand-text": {
|
|
2120
|
+
"light": "#9D2933",
|
|
2121
|
+
"dark": "#9D2933"
|
|
2122
|
+
},
|
|
2123
|
+
"--dsw-alias-label-primary": {
|
|
2124
|
+
"light": "#312520",
|
|
2125
|
+
"dark": "#312520"
|
|
2126
|
+
},
|
|
2127
|
+
"--dsw-alias-label-primary-bluish": {
|
|
2128
|
+
"light": "#312520",
|
|
2129
|
+
"dark": "#312520"
|
|
2130
|
+
},
|
|
2131
|
+
"--dsw-alias-label-secondary": {
|
|
2132
|
+
"light": "#574266",
|
|
2133
|
+
"dark": "#574266"
|
|
2134
|
+
},
|
|
2135
|
+
"--dsw-alias-label-tertiary": {
|
|
2136
|
+
"light": "#75664D",
|
|
2137
|
+
"dark": "#75664D"
|
|
2138
|
+
},
|
|
2139
|
+
"--dsw-alias-label-caption": {
|
|
2140
|
+
"light": "#75664D",
|
|
2141
|
+
"dark": "#75664D"
|
|
2142
|
+
},
|
|
2143
|
+
"--dsw-alias-label-dimmed": {
|
|
2144
|
+
"light": "#9A8C76",
|
|
2145
|
+
"dark": "#9A8C76"
|
|
2146
|
+
},
|
|
2147
|
+
"--dsw-alias-label-primary-dimmed": {
|
|
2148
|
+
"light": "#574266",
|
|
2149
|
+
"dark": "#574266"
|
|
2150
|
+
},
|
|
2151
|
+
"--dsw-alias-label-primary-foreground": {
|
|
2152
|
+
"light": "#FFFFFF",
|
|
2153
|
+
"dark": "#FFFFFF"
|
|
2154
|
+
},
|
|
2155
|
+
"--dsw-alias-label-primary-inverted": {
|
|
2156
|
+
"light": "#FFFFFF",
|
|
2157
|
+
"dark": "#FFFFFF"
|
|
2158
|
+
},
|
|
2159
|
+
"--dsw-alias-link": {
|
|
2160
|
+
"light": "#9D2933",
|
|
2161
|
+
"dark": "#9D2933"
|
|
2162
|
+
},
|
|
2163
|
+
"--dsw-alias-interactive-bg-hover": {
|
|
2164
|
+
"light": "#9D293314",
|
|
2165
|
+
"dark": "#9D293314"
|
|
2166
|
+
},
|
|
2167
|
+
"--dsw-alias-interactive-bg-active": {
|
|
2168
|
+
"light": "#F20C002E",
|
|
2169
|
+
"dark": "#F20C002E"
|
|
2170
|
+
},
|
|
2171
|
+
"--dsw-alias-interactive-bg-hover-solid": {
|
|
2172
|
+
"light": "#EEDEB0",
|
|
2173
|
+
"dark": "#EEDEB0"
|
|
2174
|
+
},
|
|
2175
|
+
"--dsw-alias-interactive-bg-hover-accent": {
|
|
2176
|
+
"light": "#F20C003D",
|
|
2177
|
+
"dark": "#F20C003D"
|
|
2178
|
+
},
|
|
2179
|
+
"--dsw-alias-button-primary-fill": {
|
|
2180
|
+
"light": "#9D2933",
|
|
2181
|
+
"dark": "#9D2933"
|
|
2182
|
+
},
|
|
2183
|
+
"--dsw-alias-button-primary-hover": {
|
|
2184
|
+
"light": "#B33A45",
|
|
2185
|
+
"dark": "#B33A45"
|
|
2186
|
+
},
|
|
2187
|
+
"--dsw-alias-button-primary-dimmed": {
|
|
2188
|
+
"light": "#9D293347",
|
|
2189
|
+
"dark": "#9D293347"
|
|
2190
|
+
},
|
|
2191
|
+
"--dsw-alias-button-ghost-active-fill": {
|
|
2192
|
+
"light": "#F20C0029",
|
|
2193
|
+
"dark": "#F20C0029"
|
|
2194
|
+
},
|
|
2195
|
+
"--dsw-alias-button-ghost-active-border": {
|
|
2196
|
+
"light": "#F20C00",
|
|
2197
|
+
"dark": "#F20C00"
|
|
2198
|
+
},
|
|
2199
|
+
"--dsw-alias-button-ghost-active-hover": {
|
|
2200
|
+
"light": "#F20C0047",
|
|
2201
|
+
"dark": "#F20C0047"
|
|
2202
|
+
},
|
|
2203
|
+
"--dsw-alias-button-info-fill": {
|
|
2204
|
+
"light": "#574266",
|
|
2205
|
+
"dark": "#574266"
|
|
2206
|
+
},
|
|
2207
|
+
"--dsw-alias-button-info-hover": {
|
|
2208
|
+
"light": "#75664D",
|
|
2209
|
+
"dark": "#75664D"
|
|
2210
|
+
},
|
|
2211
|
+
"--dsw-alias-button-elevated-fill": {
|
|
2212
|
+
"light": "#FFFBF0",
|
|
2213
|
+
"dark": "#FFFBF0"
|
|
2214
|
+
},
|
|
2215
|
+
"--dsw-alias-button-floating-fill": {
|
|
2216
|
+
"light": "#FFFBF0",
|
|
2217
|
+
"dark": "#FFFBF0"
|
|
2218
|
+
},
|
|
2219
|
+
"--dsw-alias-button-floating-hover": {
|
|
2220
|
+
"light": "#F7F0E0",
|
|
2221
|
+
"dark": "#F7F0E0"
|
|
2222
|
+
},
|
|
2223
|
+
"--dsw-alias-button-contrast-fill": {
|
|
2224
|
+
"light": "#312520",
|
|
2225
|
+
"dark": "#312520"
|
|
2226
|
+
},
|
|
2227
|
+
"--dsw-alias-markdown-code-block": {
|
|
2228
|
+
"light": "#F0F0F4",
|
|
2229
|
+
"dark": "#F0F0F4"
|
|
2230
|
+
},
|
|
2231
|
+
"--dsw-alias-markdown-code-block-banner": {
|
|
2232
|
+
"light": "#E4E6EC",
|
|
2233
|
+
"dark": "#E4E6EC"
|
|
2234
|
+
},
|
|
2235
|
+
"--dsw-alias-markdown-inline-code": {
|
|
2236
|
+
"light": "#EEDEB0",
|
|
2237
|
+
"dark": "#EEDEB0"
|
|
2238
|
+
},
|
|
2239
|
+
"--dsw-alias-markdown-citation": {
|
|
2240
|
+
"light": "#EEDEB0",
|
|
2241
|
+
"dark": "#EEDEB0"
|
|
2242
|
+
},
|
|
2243
|
+
"--dsw-alias-markdown-tag": {
|
|
2244
|
+
"light": "#EEDEB0",
|
|
2245
|
+
"dark": "#EEDEB0"
|
|
2246
|
+
},
|
|
2247
|
+
"--dsw-alias-markdown-placeholder": {
|
|
2248
|
+
"light": "#D1D9E0",
|
|
2249
|
+
"dark": "#D1D9E0"
|
|
2250
|
+
},
|
|
2251
|
+
"--dsw-alias-markdown-code-segment-selected": {
|
|
2252
|
+
"light": "#F20C0029",
|
|
2253
|
+
"dark": "#F20C0029"
|
|
2254
|
+
},
|
|
2255
|
+
"--dsw-alias-markdown-code-segment-unselected": {
|
|
2256
|
+
"light": "#F0F0F4",
|
|
2257
|
+
"dark": "#F0F0F4"
|
|
2258
|
+
},
|
|
2259
|
+
"--dsw-alias-scrollbar-bg-l1": {
|
|
2260
|
+
"light": "#9D293329",
|
|
2261
|
+
"dark": "#9D293329"
|
|
2262
|
+
},
|
|
2263
|
+
"--dsw-alias-scrollbar-bg-l2": {
|
|
2264
|
+
"light": "#9D293338",
|
|
2265
|
+
"dark": "#9D293338"
|
|
2266
|
+
},
|
|
2267
|
+
"--dsw-alias-scrollbar-hover-l1": {
|
|
2268
|
+
"light": "#9D293352",
|
|
2269
|
+
"dark": "#9D293352"
|
|
2270
|
+
},
|
|
2271
|
+
"--dsw-alias-scrollbar-hover-l2": {
|
|
2272
|
+
"light": "#9D293366",
|
|
2273
|
+
"dark": "#9D293366"
|
|
2274
|
+
},
|
|
2275
|
+
"--dsw-alias-tooltip-bg": {
|
|
2276
|
+
"light": "#312520",
|
|
2277
|
+
"dark": "#312520"
|
|
2278
|
+
},
|
|
2279
|
+
"--dsw-alias-toast-bg": {
|
|
2280
|
+
"light": "#FFFBF0",
|
|
2281
|
+
"dark": "#FFFBF0"
|
|
2282
|
+
},
|
|
2283
|
+
"--dsw-alias-state-error-primary": {
|
|
2284
|
+
"light": "#C2352B",
|
|
2285
|
+
"dark": "#C2352B"
|
|
2286
|
+
},
|
|
2287
|
+
"--dsw-alias-state-error-secondary": {
|
|
2288
|
+
"light": "#DB5A6B",
|
|
2289
|
+
"dark": "#DB5A6B"
|
|
2290
|
+
},
|
|
2291
|
+
"--dsw-alias-state-success-primary": {
|
|
2292
|
+
"light": "#1DA981",
|
|
2293
|
+
"dark": "#1DA981"
|
|
2294
|
+
},
|
|
2295
|
+
"--dsw-alias-state-success-secondary": {
|
|
2296
|
+
"light": "#7FE3C0",
|
|
2297
|
+
"dark": "#7FE3C0"
|
|
2298
|
+
},
|
|
2299
|
+
"--dsw-alias-state-success-tertiary": {
|
|
2300
|
+
"light": "#D8F5E8",
|
|
2301
|
+
"dark": "#D8F5E8"
|
|
2302
|
+
},
|
|
2303
|
+
"--dsw-alias-state-warn-primary": {
|
|
2304
|
+
"light": "#C89B40",
|
|
2305
|
+
"dark": "#C89B40"
|
|
2306
|
+
},
|
|
2307
|
+
"--dsw-alias-state-warn-secondary": {
|
|
2308
|
+
"light": "#EACD76",
|
|
2309
|
+
"dark": "#EACD76"
|
|
2310
|
+
},
|
|
2311
|
+
"--dsw-alias-state-warn-tertiary": {
|
|
2312
|
+
"light": "#F7EBC8",
|
|
2313
|
+
"dark": "#F7EBC8"
|
|
2314
|
+
},
|
|
2315
|
+
"--dsw-alias-state-warn-label": {
|
|
2316
|
+
"light": "#7A5A0E",
|
|
2317
|
+
"dark": "#7A5A0E"
|
|
2318
|
+
},
|
|
2319
|
+
"--dsw-alias-state-business-primary": {
|
|
2320
|
+
"light": "#3DE1AD",
|
|
2321
|
+
"dark": "#3DE1AD"
|
|
2322
|
+
},
|
|
2323
|
+
"--dsw-alias-state-business-tertiary": {
|
|
2324
|
+
"light": "#A8F0D8",
|
|
2325
|
+
"dark": "#A8F0D8"
|
|
2326
|
+
},
|
|
2327
|
+
"--dsw-specific-sidebar-fill": {
|
|
2328
|
+
"light": "linear-gradient(to bottom,#D6ECF0 0%,#E4E6E4 8%,#F2ECDE 18%,#F5E7CE 30%,#EFD6AE 44%,#E5BE86 58%,#D6A45C 72%,#C48F3C 86%,#BA8330 100%)",
|
|
2329
|
+
"dark": "linear-gradient(to bottom,#D6ECF0 0%,#E4E6E4 8%,#F2ECDE 18%,#F5E7CE 30%,#EFD6AE 44%,#E5BE86 58%,#D6A45C 72%,#C48F3C 86%,#BA8330 100%)"
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2332
|
+
"reading": {
|
|
2333
|
+
"colorScheme": "light",
|
|
2334
|
+
"bg": "#FFFBF0",
|
|
2335
|
+
"alpha": 0.62,
|
|
2336
|
+
"blur": 3,
|
|
2337
|
+
"maxWidth": 640
|
|
2338
|
+
},
|
|
2339
|
+
"card": {
|
|
2340
|
+
"rows": [
|
|
2341
|
+
{
|
|
2342
|
+
"kind": "solid",
|
|
2343
|
+
"schemes": [
|
|
2344
|
+
"p-xiang-se",
|
|
2345
|
+
"p-ju-huang",
|
|
2346
|
+
"p-tao-hong",
|
|
2347
|
+
"p-hai-tang-hong",
|
|
2348
|
+
"p-jiang-zi"
|
|
2349
|
+
]
|
|
2350
|
+
},
|
|
2351
|
+
{
|
|
2352
|
+
"kind": "solid",
|
|
2353
|
+
"schemes": [
|
|
2354
|
+
"p-song-bai-lu",
|
|
2355
|
+
"p-zhu-qing",
|
|
2356
|
+
"p-cang-qing",
|
|
2357
|
+
"p-dai-zi",
|
|
2358
|
+
"p-xuan-qing"
|
|
2359
|
+
]
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
"kind": "clash",
|
|
2363
|
+
"schemes": [
|
|
2364
|
+
"p-shi-liu-jin",
|
|
2365
|
+
"p-bao-lan-jin",
|
|
2366
|
+
"p-qing-lian-jin",
|
|
2367
|
+
"p-song-hua-tao",
|
|
2368
|
+
"p-wu-jin"
|
|
2369
|
+
]
|
|
2370
|
+
}
|
|
2371
|
+
]
|
|
2372
|
+
},
|
|
2373
|
+
"accent": "#F20C00"
|
|
2374
|
+
},
|
|
1715
2375
|
{
|
|
1716
2376
|
"id": "ying-mu-cai-yun",
|
|
1717
2377
|
"label": "营慕彩云",
|
|
1718
|
-
"description": "营幕之下,彩云为伴 ——
|
|
2378
|
+
"description": "营幕之下,彩云为伴 —— 复刻自蜂链商城电商新零售系统管理后台同名主题",
|
|
1719
2379
|
"colorScheme": "light",
|
|
1720
2380
|
"tokens": {
|
|
1721
2381
|
"--dsw-alias-bg-base": {
|
|
@@ -2002,6 +2662,181 @@ window.__ModuleLoader__.load({
|
|
|
2002
2662
|
}
|
|
2003
2663
|
]
|
|
2004
2664
|
|
|
2665
|
+
/**
|
|
2666
|
+
* 15 套「纯色/拼色」配色(`lib/palette-schemes.json`,由 embed-themes 内联)。
|
|
2667
|
+
*
|
|
2668
|
+
* 它们**不是主题**:不注册进主题服务、不占面板卡片、不进官方「外观」下拉。
|
|
2669
|
+
* 点卡片上的色值按钮时,插件把 {@link schemeTokens} 展出来的这一层叠上去,
|
|
2670
|
+
* 整屏就换成那套配色;切到别的皮肤时这一层必须撤除(见 `syncScheme`)。
|
|
2671
|
+
*/
|
|
2672
|
+
const BUNDLED_PALETTES = [
|
|
2673
|
+
{
|
|
2674
|
+
"id": "p-xiang-se",
|
|
2675
|
+
"kind": "solid",
|
|
2676
|
+
"label": "缃色",
|
|
2677
|
+
"main": "#F0C239",
|
|
2678
|
+
"ground": "#F2ECDE",
|
|
2679
|
+
"ink": "#312520",
|
|
2680
|
+
"source": "黄 1-9 缃色(浅黄色)"
|
|
2681
|
+
},
|
|
2682
|
+
{
|
|
2683
|
+
"id": "p-ju-huang",
|
|
2684
|
+
"kind": "solid",
|
|
2685
|
+
"label": "橘黄",
|
|
2686
|
+
"main": "#FF8936",
|
|
2687
|
+
"ground": "#F2ECDE",
|
|
2688
|
+
"ink": "#312520",
|
|
2689
|
+
"source": "黄 1-5 橘黄(柑橘的黄色)"
|
|
2690
|
+
},
|
|
2691
|
+
{
|
|
2692
|
+
"id": "p-tao-hong",
|
|
2693
|
+
"kind": "solid",
|
|
2694
|
+
"label": "桃红",
|
|
2695
|
+
"main": "#F47983",
|
|
2696
|
+
"ground": "#EDD1D8",
|
|
2697
|
+
"ink": "#56004F",
|
|
2698
|
+
"source": "红 0-3 桃红(桃花的颜色)"
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
"id": "p-hai-tang-hong",
|
|
2702
|
+
"kind": "solid",
|
|
2703
|
+
"label": "海棠红",
|
|
2704
|
+
"main": "#DB5A6B",
|
|
2705
|
+
"ground": "#EDD1D8",
|
|
2706
|
+
"ink": "#56004F",
|
|
2707
|
+
"source": "红 0-4 海棠红(淡紫红色、较桃红色深一些)"
|
|
2708
|
+
},
|
|
2709
|
+
{
|
|
2710
|
+
"id": "p-jiang-zi",
|
|
2711
|
+
"kind": "solid",
|
|
2712
|
+
"label": "绛紫",
|
|
2713
|
+
"main": "#8C4356",
|
|
2714
|
+
"ground": "#EDD1D8",
|
|
2715
|
+
"ink": "#56004F",
|
|
2716
|
+
"source": "红 0-9 绛紫(紫中略带红的颜色)"
|
|
2717
|
+
},
|
|
2718
|
+
{
|
|
2719
|
+
"id": "p-song-bai-lu",
|
|
2720
|
+
"kind": "solid",
|
|
2721
|
+
"label": "松柏绿",
|
|
2722
|
+
"main": "#057748",
|
|
2723
|
+
"ground": "#E0F0E9",
|
|
2724
|
+
"ink": "#424C50",
|
|
2725
|
+
"source": "绿 2-30 松花绿(松柏叶的墨绿)"
|
|
2726
|
+
},
|
|
2727
|
+
{
|
|
2728
|
+
"id": "p-zhu-qing",
|
|
2729
|
+
"kind": "solid",
|
|
2730
|
+
"label": "竹青",
|
|
2731
|
+
"main": "#789262",
|
|
2732
|
+
"ground": "#E0EEE8",
|
|
2733
|
+
"ink": "#424C50",
|
|
2734
|
+
"source": "绿 2-3 竹青(竹子的绿色)"
|
|
2735
|
+
},
|
|
2736
|
+
{
|
|
2737
|
+
"id": "p-cang-qing",
|
|
2738
|
+
"kind": "solid",
|
|
2739
|
+
"label": "苍青",
|
|
2740
|
+
"main": "#7397AB",
|
|
2741
|
+
"ground": "#D6ECF0",
|
|
2742
|
+
"ink": "#4A4266",
|
|
2743
|
+
"source": "苍 4-3 苍青"
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
"id": "p-dai-zi",
|
|
2747
|
+
"kind": "solid",
|
|
2748
|
+
"label": "黛紫",
|
|
2749
|
+
"main": "#574266",
|
|
2750
|
+
"ground": "#E4C6D0",
|
|
2751
|
+
"ink": "#56004F",
|
|
2752
|
+
"source": "蓝 3-12 黛紫(深紫色)"
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
"id": "p-xuan-qing",
|
|
2756
|
+
"kind": "solid",
|
|
2757
|
+
"label": "玄青",
|
|
2758
|
+
"main": "#3D3B4F",
|
|
2759
|
+
"ground": "#F0F0F4",
|
|
2760
|
+
"ink": "#312520",
|
|
2761
|
+
"source": "黑 7-1 玄青(深黑色)"
|
|
2762
|
+
},
|
|
2763
|
+
{
|
|
2764
|
+
"id": "p-shi-liu-jin",
|
|
2765
|
+
"kind": "clash",
|
|
2766
|
+
"label": "石榴金",
|
|
2767
|
+
"main": "#F20C00",
|
|
2768
|
+
"ground": "#F2ECDE",
|
|
2769
|
+
"ink": "#312520",
|
|
2770
|
+
"fill": "#9D2933",
|
|
2771
|
+
"dots": [
|
|
2772
|
+
"#FFFFFF",
|
|
2773
|
+
"#D6ECF0",
|
|
2774
|
+
"#EACD76",
|
|
2775
|
+
"#3DE1AD"
|
|
2776
|
+
],
|
|
2777
|
+
"source": "红 0-5 石榴红 + 金银 8-0 赤金 + 绿 2-14 翡翠色;按钮族用红 0-11 胭脂(白字 7.50:1,而压在石榴红上只有 4.35:1)"
|
|
2778
|
+
},
|
|
2779
|
+
{
|
|
2780
|
+
"id": "p-bao-lan-jin",
|
|
2781
|
+
"kind": "clash",
|
|
2782
|
+
"label": "宝蓝·赤金",
|
|
2783
|
+
"main": "#4B5CC4",
|
|
2784
|
+
"ground": "#D6ECF0",
|
|
2785
|
+
"ink": "#4A4266",
|
|
2786
|
+
"dots": [
|
|
2787
|
+
"#F2BE45",
|
|
2788
|
+
"#EACD76",
|
|
2789
|
+
"#D9B611",
|
|
2790
|
+
"#FFFFFF"
|
|
2791
|
+
],
|
|
2792
|
+
"source": "蓝 3-5 宝蓝(多和小面积纯黄色(金色)配合使用)+ 金银 8-0 赤金"
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
"id": "p-qing-lian-jin",
|
|
2796
|
+
"kind": "clash",
|
|
2797
|
+
"label": "青莲·金玉",
|
|
2798
|
+
"main": "#801DAE",
|
|
2799
|
+
"ground": "#E4C6D0",
|
|
2800
|
+
"ink": "#56004F",
|
|
2801
|
+
"dots": [
|
|
2802
|
+
"#F2BE45",
|
|
2803
|
+
"#FFF143",
|
|
2804
|
+
"#3DE1AD",
|
|
2805
|
+
"#FFFFFF"
|
|
2806
|
+
],
|
|
2807
|
+
"source": "蓝 3-19 青莲(偏蓝的紫色)+ 黄 1-0 鹅黄 + 绿 2-14 翡翠色"
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
"id": "p-song-hua-tao",
|
|
2811
|
+
"kind": "clash",
|
|
2812
|
+
"label": "松花·桃粉",
|
|
2813
|
+
"main": "#BCE672",
|
|
2814
|
+
"ground": "#F7FBEC",
|
|
2815
|
+
"ink": "#424C50",
|
|
2816
|
+
"dots": [
|
|
2817
|
+
"#C93756",
|
|
2818
|
+
"#057748"
|
|
2819
|
+
],
|
|
2820
|
+
"source": "绿 2-31 松花色(嫩黄绿)+ 红 0-1 樱桃色 + 绿 2-30 松花绿"
|
|
2821
|
+
},
|
|
2822
|
+
{
|
|
2823
|
+
"id": "p-wu-jin",
|
|
2824
|
+
"kind": "clash",
|
|
2825
|
+
"label": "藏青·鹅黄",
|
|
2826
|
+
"main": "#2E4E7E",
|
|
2827
|
+
"ground": "#E2E9F2",
|
|
2828
|
+
"ink": "#312520",
|
|
2829
|
+
"dots": [
|
|
2830
|
+
"#F2BE45",
|
|
2831
|
+
"#FFFFFF",
|
|
2832
|
+
"#FFB3A7",
|
|
2833
|
+
"#3DE1AD"
|
|
2834
|
+
],
|
|
2835
|
+
"source": "蓝 8-2 藏青(深蓝)+ 金银 8-0 赤金 + 精白 + 红 0-0 粉红 + 绿 2-14 翡翠色"
|
|
2836
|
+
}
|
|
2837
|
+
]
|
|
2838
|
+
PALETTE_SCHEMES = Array.isArray(BUNDLED_PALETTES) ? BUNDLED_PALETTES : []
|
|
2839
|
+
|
|
2005
2840
|
/**
|
|
2006
2841
|
* Marks the ambient nodes this run created.
|
|
2007
2842
|
*
|
|
@@ -2428,6 +3263,51 @@ window.__ModuleLoader__.load({
|
|
|
2428
3263
|
'.tg-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.5}',
|
|
2429
3264
|
'.tg-strip{display:flex;height:6px;border-radius:999px;overflow:hidden;border:.5px solid var(--dsw-alias-border-l3)}',
|
|
2430
3265
|
'.tg-strip span{flex:1}',
|
|
3266
|
+
/* ── the PALETTE PICKER card (15 clickable colour buttons) ────────────────
|
|
3267
|
+
** 这些色格里的颜色全部来自「中国传统色库」项目(chinese-colors)**
|
|
3268
|
+
https://github.com/zerosoul/chinese-colors
|
|
3269
|
+
作者 tristan(GitHub @zerosoul)|在线手册 https://colors.ichuantong.cn |许可 ISC
|
|
3270
|
+
取色表见 lib/palette-schemes.json(每套都带 source = 该库的「组 + id」)。
|
|
3271
|
+
色名与色值是作者整理的开源成果,本插件只做取色与派生 —— **感谢作者 tristan**。
|
|
3272
|
+
|
|
3273
|
+
Three rows of 16px keep the picker within a few px of a strip card that
|
|
3274
|
+
carries a two-line description, so it does not sit visibly short in the
|
|
3275
|
+
grid. Each slot carries its own hairline: the pale schemes are near-white,
|
|
3276
|
+
and against a white card in the official LIGHT appearance a borderless
|
|
3277
|
+
swatch would simply vanish. `--dsw-alias-border-l3` adapts to both. */
|
|
3278
|
+
'.tg-picker{display:flex;flex-direction:column;gap:3px}',
|
|
3279
|
+
'.tg-pickrow{display:flex;gap:3px;height:16px}',
|
|
3280
|
+
/* 卡片本体也可点(= 用亮着的那一格),所以要给出"可点"的手势提示 —— 它是一个 div,
|
|
3281
|
+
浏览器不会自带 cursor:pointer。 */
|
|
3282
|
+
'.tg-picker-card{cursor:pointer}',
|
|
3283
|
+
/* 色格本体做成 flex 行:里面的色带按 `flex-grow` 分宽度(主色 2、次色各 1)。 */
|
|
3284
|
+
'.tg-swatch{flex:1;min-width:0;padding:0;background:none;cursor:pointer;position:relative;',
|
|
3285
|
+
'display:flex;border:.5px solid var(--dsw-alias-border-l3);border-radius:3px;font:inherit}',
|
|
3286
|
+
'.tg-swatch[aria-pressed="true"]{border-color:var(--dsw-alias-brand-primary);',
|
|
3287
|
+
'box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary)}',
|
|
3288
|
+
'.tg-swatch:focus-visible{outline:1.5px solid var(--dsw-alias-brand-primary);outline-offset:1px}',
|
|
3289
|
+
/* 一条色带。宽度由内联的 `flex-grow` 决定(纯色格是"一条带占满"的特例),
|
|
3290
|
+
左右两端的圆角靠 `:first-child` / `:last-child` 补 —— 色格自己没有 overflow:hidden
|
|
3291
|
+
(那会把悬停标签裁掉)。 */
|
|
3292
|
+
'.tg-band{display:block;height:100%;min-width:0}',
|
|
3293
|
+
'.tg-band:first-child{border-radius:2px 0 0 2px}',
|
|
3294
|
+
'.tg-band:last-child{border-radius:0 2px 2px 0}',
|
|
3295
|
+
'.tg-band:only-child{border-radius:2px}',
|
|
3296
|
+
/* ── THE HOVER NAME (user's request: no layout change, name on hover) ──────
|
|
3297
|
+
15 slots at ~34px wide cannot carry a legible name, and adding a name line
|
|
3298
|
+
would change the card's height — so the name appears ONLY while the pointer
|
|
3299
|
+
is on that slot, as an absolutely positioned chip (out of flow: nothing in
|
|
3300
|
+
the card moves by a single pixel).
|
|
3301
|
+
|
|
3302
|
+
Note `.tg-swatch` deliberately has NO `overflow:hidden`: it would clip this
|
|
3303
|
+
chip. The rounded corners are kept by rounding the CHILDREN (`.tg-band`)
|
|
3304
|
+
instead, which is what the clipping was for. */
|
|
3305
|
+
'.tg-swatch:hover::after,.tg-swatch:focus-visible::after{content:attr(data-name);',
|
|
3306
|
+
'position:absolute;left:50%;bottom:calc(100% + 5px);transform:translateX(-50%);',
|
|
3307
|
+
'white-space:nowrap;z-index:5;pointer-events:none;padding:2px 6px;border-radius:4px;',
|
|
3308
|
+
'font-size:11px;line-height:1.3;background:var(--dsw-alias-bg-base);',
|
|
3309
|
+
'color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l3);',
|
|
3310
|
+
'box-shadow:0 2px 8px rgba(0,0,0,.18)}',
|
|
2431
3311
|
].join('')
|
|
2432
3312
|
|
|
2433
3313
|
/**
|
|
@@ -2440,13 +3320,17 @@ window.__ModuleLoader__.load({
|
|
|
2440
3320
|
*/
|
|
2441
3321
|
function createGalleryStore() {
|
|
2442
3322
|
return defineStore({
|
|
2443
|
-
init: () => ({
|
|
3323
|
+
init: () => ({
|
|
3324
|
+
ids: [], labels: {}, descriptions: {}, swatches: {}, cardRows: {},
|
|
3325
|
+
scheme: '', selected: 'system', status: '', revision: -1,
|
|
3326
|
+
}),
|
|
2444
3327
|
actions: {
|
|
2445
3328
|
sync: (draft, themes, selected, revision) => {
|
|
2446
3329
|
if (revision <= draft.revision) return
|
|
2447
3330
|
const labels = {}
|
|
2448
3331
|
const descriptions = {}
|
|
2449
3332
|
const swatches = {}
|
|
3333
|
+
const cardRows = {}
|
|
2450
3334
|
for (const theme of themes) {
|
|
2451
3335
|
// Built-in themes carry no label — the official Appearance row
|
|
2452
3336
|
// localizes them from its own dictionaries — so name them here
|
|
@@ -2470,11 +3354,17 @@ window.__ModuleLoader__.load({
|
|
|
2470
3354
|
pick('--dsw-alias-label-secondary'),
|
|
2471
3355
|
pick('--dsw-alias-state-business-primary'),
|
|
2472
3356
|
].filter((value) => typeof value === 'string' && value !== '')
|
|
3357
|
+
// A palette-shaped skin can replace the strip with the palette picker
|
|
3358
|
+
// (`card.rows`). Absent — which is every scene-carrying skin and both
|
|
3359
|
+
// built-in cards — the strip above stays exactly as it was.
|
|
3360
|
+
const rows = cardRowShape(theme, PALETTE_SCHEMES)
|
|
3361
|
+
if (rows !== undefined) cardRows[theme.id] = rows
|
|
2473
3362
|
}
|
|
2474
3363
|
draft.ids = themes.map((theme) => theme.id)
|
|
2475
3364
|
draft.labels = labels
|
|
2476
3365
|
draft.descriptions = descriptions
|
|
2477
3366
|
draft.swatches = swatches
|
|
3367
|
+
draft.cardRows = cardRows
|
|
2478
3368
|
draft.selected = selected
|
|
2479
3369
|
draft.revision = revision
|
|
2480
3370
|
},
|
|
@@ -2487,39 +3377,170 @@ window.__ModuleLoader__.load({
|
|
|
2487
3377
|
* @param status - one line describing the state.
|
|
2488
3378
|
*/
|
|
2489
3379
|
note: (draft, status) => { draft.status = status },
|
|
3380
|
+
/**
|
|
3381
|
+
* 记住配色卡上点亮的是哪一格。
|
|
3382
|
+
*
|
|
3383
|
+
* 与主题列表分开放:方案 id 不是主题,混进 `ids` 会让"皮肤数"的计数口径
|
|
3384
|
+
* 与 README、与 npm 描述一起失真。
|
|
3385
|
+
* @param draft - store draft.
|
|
3386
|
+
* @param id - the scheme id.
|
|
3387
|
+
*/
|
|
3388
|
+
markScheme: (draft, id) => { draft.scheme = String(id) },
|
|
2490
3389
|
},
|
|
2491
3390
|
})
|
|
2492
3391
|
}
|
|
2493
3392
|
|
|
2494
3393
|
/**
|
|
2495
|
-
* Render one
|
|
2496
|
-
*
|
|
3394
|
+
* Render one palette slot: a button that applies a colour scheme.
|
|
3395
|
+
*
|
|
3396
|
+
* ── 拼色格的画法:按**宽度比例**分带(不再用"底 + 圆点")──────────────────────
|
|
3397
|
+
*
|
|
3398
|
+
* 用户要的:拼色不要点子,改成把色块**按宽度分份** —— 主色占 2 份、每个次色各占 1 份。
|
|
3399
|
+
* 于是 2/1 就是"一主一次"、2/1/1 就是"一主两次";而当前 5 套拼色都是"一主四次"
|
|
3400
|
+
* (`dots` 各有 4 个),画出来自然是 2/1/1/1/1 —— **比值由数据算出来,不是写死的**。
|
|
3401
|
+
* 纯色格是同一机制的退化情况(一条带占满整格),所以两类格子共用一套 DOM 与样式,
|
|
3402
|
+
* 不会出现"两种格子两套画法"的漂移。
|
|
3403
|
+
*
|
|
3404
|
+
* 画出来的颜色与实际点击后生效的颜色来自**同一张表**,所以按钮不可能宣传一个
|
|
3405
|
+
* 方案根本没画的颜色。
|
|
3406
|
+
* @param scheme - one entry from `lib/palette-schemes.json`.
|
|
3407
|
+
* @param selected - whether this scheme is the one in effect.
|
|
3408
|
+
* @param onPick - called with the scheme id.
|
|
3409
|
+
* @param index - React key.
|
|
3410
|
+
* @returns the swatch button.
|
|
3411
|
+
*/
|
|
3412
|
+
function schemeSwatch(scheme, selected, onPick, index) {
|
|
3413
|
+
const accents = Array.isArray(scheme.dots) ? scheme.dots : []
|
|
3414
|
+
const bands = scheme.kind === 'clash'
|
|
3415
|
+
? [[scheme.main, 2]].concat(accents.map((colour) => [colour, 1]))
|
|
3416
|
+
: [[scheme.main, 1]]
|
|
3417
|
+
const inner = bands.map(([colour, weight], band) => jsx('span', {
|
|
3418
|
+
className: 'tg-band',
|
|
3419
|
+
style: { background: colour, flexGrow: weight },
|
|
3420
|
+
}, band))
|
|
3421
|
+
return jsx('button', {
|
|
3422
|
+
type: 'button',
|
|
3423
|
+
className: 'tg-swatch',
|
|
3424
|
+
'aria-pressed': selected,
|
|
3425
|
+
// 悬停/聚焦时显示的色名(CSS `content:attr(data-name)`,不改布局)。
|
|
3426
|
+
'data-name': scheme.label,
|
|
3427
|
+
// The label is the only text a picker slot carries; the full provenance
|
|
3428
|
+
// (which colour-library entry it came from) lives in the tooltip.
|
|
3429
|
+
title: scheme.source === undefined ? scheme.label : `${scheme.label} · ${scheme.source}`,
|
|
3430
|
+
onClick: (event) => {
|
|
3431
|
+
// ── 必须拦住冒泡 ──────────────────────────────────────────────────────
|
|
3432
|
+
//
|
|
3433
|
+
// 卡片本体也处理点击(用它记住的那一格)。不拦的话,点子格会先切到那一格,
|
|
3434
|
+
// 紧接着冒泡到卡片的处理器、被覆盖回原来那格 —— 症状就是"点色块没反应"。
|
|
3435
|
+
// 事件参数在测试里可能缺省,所以要容错(jsx 桩调用处理器时不给事件)。
|
|
3436
|
+
if (event !== undefined && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
3437
|
+
onPick(scheme.id)
|
|
3438
|
+
},
|
|
3439
|
+
children: inner,
|
|
3440
|
+
}, index)
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
/**
|
|
3444
|
+
* Render the palette-picker card's three rows of slots.
|
|
3445
|
+
* @param rows - rows sanitised by {@link cardRowShape}.
|
|
3446
|
+
* @param selectedScheme - the scheme id currently in effect, if any.
|
|
3447
|
+
* @param onPick - called with the scheme id.
|
|
3448
|
+
* @param schemes - the palette table.
|
|
3449
|
+
* @returns the picker element.
|
|
3450
|
+
*/
|
|
3451
|
+
function cardPickerElement(rows, selectedScheme, onPick, schemes) {
|
|
3452
|
+
return jsx('span', {
|
|
3453
|
+
className: 'tg-picker',
|
|
3454
|
+
children: rows.map((row, index) => jsx('span', {
|
|
3455
|
+
className: 'tg-pickrow',
|
|
3456
|
+
children: row.schemes.map((id, slot) => {
|
|
3457
|
+
const scheme = schemeById(id, schemes)
|
|
3458
|
+
// `cardRowShape` already proved every id resolves; the guard is here so a
|
|
3459
|
+
// table that changed between sanitising and rendering can only drop a
|
|
3460
|
+
// slot, never throw inside the render and blank the panel.
|
|
3461
|
+
if (scheme === undefined) return null
|
|
3462
|
+
return schemeSwatch(scheme, id === selectedScheme, onPick, slot)
|
|
3463
|
+
}),
|
|
3464
|
+
}, index)),
|
|
3465
|
+
})
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
/**
|
|
3469
|
+
* Render one card.
|
|
3470
|
+
*
|
|
3471
|
+
* Three layouts share the card: the default token strip, the palette picker (a
|
|
3472
|
+
* theme that declares `card.rows`), and — for the picker — no description body,
|
|
3473
|
+
* because the swatches already fill it. The description still becomes the tooltip
|
|
3474
|
+
* (`title`), which is what the schema says the field is for.
|
|
3475
|
+
*
|
|
3476
|
+
* A picker card is a `div`, not a `button`: it CONTAINS 15 buttons, and nesting
|
|
3477
|
+
* buttons inside a button is invalid markup that browsers silently re-parent.
|
|
3478
|
+
* @param props - id, label, description, swatches, rows, selected, applied,
|
|
3479
|
+
* selectedScheme, onSelect, onPickScheme.
|
|
2497
3480
|
* @returns the card element.
|
|
2498
3481
|
*/
|
|
2499
3482
|
function ThemeCard(props) {
|
|
2500
|
-
const {
|
|
3483
|
+
const {
|
|
3484
|
+
id, label, description, swatches, rows, selected, applied,
|
|
3485
|
+
selectedScheme, onSelect, onPickScheme, t,
|
|
3486
|
+
} = props
|
|
3487
|
+
// ── WHY THE FLAG, AND NOT `blocks !== null` ──────────────────────────────
|
|
3488
|
+
//
|
|
3489
|
+
// Whether this card is a picker has to be read off the DATA, not off the rendered
|
|
3490
|
+
// element. `jsx()` returns a real element in React but `null` under this
|
|
3491
|
+
// repository's jsx stub, so an element-identity test silently takes the STRIP
|
|
3492
|
+
// branch in every test while being correct in the app — a stub that does not
|
|
3493
|
+
// reproduce the runtime's return value is the same trap rule 8 records for
|
|
3494
|
+
// missing side effects.
|
|
3495
|
+
//
|
|
3496
|
+
// The flag comes from re-running the sanitiser rather than from a bare
|
|
3497
|
+
// `rows !== undefined`, so a component never trusts its inputs: whatever ends up
|
|
3498
|
+
// in the store, the card either draws a valid picker or falls back to the strip.
|
|
3499
|
+
// Handing malformed rows to the picker would throw inside the render and blank
|
|
3500
|
+
// the whole panel — the silent-failure shape this project keeps paying for.
|
|
3501
|
+
const shape = cardRowShape({ card: { rows } }, PALETTE_SCHEMES)
|
|
3502
|
+
const hasPicker = shape !== undefined
|
|
3503
|
+
const head = jsxs('div', {
|
|
3504
|
+
className: 'tg-card-top',
|
|
3505
|
+
children: [
|
|
3506
|
+
jsx('span', { className: 'tg-name', children: label || id }),
|
|
3507
|
+
selected ? jsx('span', { className: 'tg-badge', children: applied }) : null,
|
|
3508
|
+
],
|
|
3509
|
+
})
|
|
3510
|
+
const body = hasPicker
|
|
3511
|
+
? cardPickerElement(shape, selectedScheme, onPickScheme, PALETTE_SCHEMES)
|
|
3512
|
+
: swatches.length > 0
|
|
3513
|
+
? jsx('span', {
|
|
3514
|
+
className: 'tg-strip',
|
|
3515
|
+
children: swatches.map((colour, index) => jsx('span', { style: { background: colour } }, index)),
|
|
3516
|
+
})
|
|
3517
|
+
: null
|
|
3518
|
+
// The picker fills the card, so it prints no body copy; `description` stays the
|
|
3519
|
+
// tooltip, which is what the schema says the field is for.
|
|
3520
|
+
const desc = !hasPicker && description
|
|
3521
|
+
? jsx('span', { className: 'tg-desc', children: description })
|
|
3522
|
+
: null
|
|
3523
|
+
if (hasPicker) {
|
|
3524
|
+
return jsxs('div', {
|
|
3525
|
+
className: 'tg-card tg-picker-card',
|
|
3526
|
+
title: description || label,
|
|
3527
|
+
// ── 点卡片本体 = 用"亮着的那一格"──────────────────────────────────────
|
|
3528
|
+
//
|
|
3529
|
+
// 用户要的:点这张卡要**生效**(换成它的配色),点具体某一格才换成那一格。
|
|
3530
|
+
// 亮着的那一格就是记住的那套;从没选过时是 `DEFAULT_SCHEME`(石榴金,
|
|
3531
|
+
// 也就是锚主题自己那套)。所以"卡片本体"与"最左那格"最终落到同一件事上,
|
|
3532
|
+
// 只是入口不同 —— 用户不必知道这个区别。
|
|
3533
|
+
onClick: () => { onPickScheme(selectedScheme || DEFAULT_SCHEME) },
|
|
3534
|
+
children: [head, body, desc],
|
|
3535
|
+
})
|
|
3536
|
+
}
|
|
2501
3537
|
return jsxs('button', {
|
|
2502
3538
|
type: 'button',
|
|
2503
3539
|
className: 'tg-card',
|
|
2504
3540
|
'aria-pressed': selected,
|
|
2505
3541
|
onClick: () => { onSelect(id) },
|
|
2506
3542
|
title: description || label,
|
|
2507
|
-
children: [
|
|
2508
|
-
jsxs('div', {
|
|
2509
|
-
className: 'tg-card-top',
|
|
2510
|
-
children: [
|
|
2511
|
-
jsx('span', { className: 'tg-name', children: label || id }),
|
|
2512
|
-
selected ? jsx('span', { className: 'tg-badge', children: applied }) : null,
|
|
2513
|
-
],
|
|
2514
|
-
}),
|
|
2515
|
-
swatches.length > 0
|
|
2516
|
-
? jsx('span', {
|
|
2517
|
-
className: 'tg-strip',
|
|
2518
|
-
children: swatches.map((colour, index) => jsx('span', { style: { background: colour } }, index)),
|
|
2519
|
-
})
|
|
2520
|
-
: null,
|
|
2521
|
-
description ? jsx('span', { className: 'tg-desc', children: description }) : null,
|
|
2522
|
-
],
|
|
3543
|
+
children: [head, body, desc],
|
|
2523
3544
|
})
|
|
2524
3545
|
}
|
|
2525
3546
|
|
|
@@ -5495,6 +6516,59 @@ window.__ModuleLoader__.load({
|
|
|
5495
6516
|
})
|
|
5496
6517
|
}
|
|
5497
6518
|
|
|
6519
|
+
/** 当前叠着的配色方案 id,没有就是 undefined。 */
|
|
6520
|
+
let stackedScheme
|
|
6521
|
+
|
|
6522
|
+
/** 配色层的 disposer(见 `syncScheme`)。 */
|
|
6523
|
+
let schemeLayerDispose
|
|
6524
|
+
|
|
6525
|
+
/**
|
|
6526
|
+
* 把所选配色方案叠到整屏上。
|
|
6527
|
+
*
|
|
6528
|
+
* ── 两条与 `syncAccent` 同源的约束 ────────────────────────────────────────
|
|
6529
|
+
*
|
|
6530
|
+
* 1. **只有锚主题是活动主题时才叠。** `overrideTokens` 的层是压在"当前活动主题"
|
|
6531
|
+
* 之上的,所以用户切到山青婷彩之后这一层必须撤除,否则会把配色糊到别人身上。
|
|
6532
|
+
* 判据取 `snapshot.active.id`,与 `syncAccent` 完全一致(**不能**引用
|
|
6533
|
+
* `wantedSkin` —— 那个函数在 `mountGallery` 内部,工厂级函数够不到它,
|
|
6534
|
+
* 正是硬性规则 4 记着的那个作用域事故)。
|
|
6535
|
+
* 2. **既有跳过、又有自发光守卫。** `overrideTokens` 与它返回的 disposer **都会**
|
|
6536
|
+
* emit `theme/change`,而本函数是被 `publish()` 调的,`publish` 又由那个事件驱动 ——
|
|
6537
|
+
* 少了守卫就是 0.1.4 那次 `RangeError` 刷屏的同一形状(规则 1)。
|
|
6538
|
+
* @param snapshot - 官方主题快照。
|
|
6539
|
+
*/
|
|
6540
|
+
function syncScheme(snapshot) {
|
|
6541
|
+
if (ctx === undefined) return
|
|
6542
|
+
const active = snapshot === undefined || snapshot === null ? {} : (snapshot.active ?? {})
|
|
6543
|
+
const remembered = rememberedScheme()
|
|
6544
|
+
// ── 石榴金那一格**不叠派生层**(用户实机反馈:"不是你第一遍做的颜色")──────────
|
|
6545
|
+
//
|
|
6546
|
+
// 第一版那套配色(两排 10 个纯色 + 拼色那一排,合起来这一整套)**不是三个颜色**,
|
|
6547
|
+
// 而是锚主题皮肤 `lib/themes/shi-liu-jin.json` 里手工写好的 67 个 token —— 侧栏渐变、
|
|
6548
|
+
// 两层底、正文、按钮族都在里面。用户说"这个配色保留,很漂亮",留的就是这一整套。
|
|
6549
|
+
//
|
|
6550
|
+
// 所以这一格必须**等于皮肤自己那套**:只要锚主题是活动主题,`syncSkin` 就已经把
|
|
6551
|
+
// 皮肤自己的调色叠上了,这里撤掉配色层即可精确复原第一版。
|
|
6552
|
+
// 反过来说:若照旧拿 `main/ground/ink` 去派生 67 个 token,得到的是一套"相似但不等"
|
|
6553
|
+
// 的颜色(`state-business-primary` 会变成金色圆点色、侧栏渐变会按主色重算),
|
|
6554
|
+
// 用户一眼就看出来了 —— 这正是本次被指出来的问题。
|
|
6555
|
+
const wanted = remembered !== null && remembered !== DEFAULT_SCHEME && active.id === PALETTE_ANCHOR
|
|
6556
|
+
? remembered
|
|
6557
|
+
: null
|
|
6558
|
+
if (wanted === stackedScheme) return
|
|
6559
|
+
const scheme = wanted === null ? undefined : schemeById(wanted, PALETTE_SCHEMES)
|
|
6560
|
+
emitting(() => {
|
|
6561
|
+
if (schemeLayerDispose !== undefined) {
|
|
6562
|
+
schemeLayerDispose()
|
|
6563
|
+
schemeLayerDispose = undefined
|
|
6564
|
+
}
|
|
6565
|
+
stackedScheme = undefined
|
|
6566
|
+
if (scheme === undefined) return
|
|
6567
|
+
schemeLayerDispose = ctx.theme.overrideTokens('theme-gallery: 配色', schemeTokens(scheme))
|
|
6568
|
+
stackedScheme = wanted
|
|
6569
|
+
})
|
|
6570
|
+
}
|
|
6571
|
+
|
|
5498
6572
|
/**
|
|
5499
6573
|
* Report whether the accent token layer actually reached the theme snapshot.
|
|
5500
6574
|
*
|
|
@@ -5578,12 +6652,14 @@ window.__ModuleLoader__.load({
|
|
|
5578
6652
|
* @param props - composed slot props.
|
|
5579
6653
|
* @returns the page element tree.
|
|
5580
6654
|
*/
|
|
5581
|
-
function ThemeGalleryPage({ t, setTheme, useStore, usePanelInfo }) {
|
|
6655
|
+
function ThemeGalleryPage({ t, setTheme, pickScheme, useStore, usePanelInfo }) {
|
|
5582
6656
|
const info = usePanelInfo((s) => s.activePanelId)
|
|
5583
6657
|
const ids = useStore((s) => s.ids)
|
|
5584
6658
|
const labels = useStore((s) => s.labels)
|
|
5585
6659
|
const descriptions = useStore((s) => s.descriptions)
|
|
5586
6660
|
const swatches = useStore((s) => s.swatches)
|
|
6661
|
+
const cardRows = useStore((s) => s.cardRows)
|
|
6662
|
+
const scheme = useStore((s) => s.scheme)
|
|
5587
6663
|
const selected = useStore((s) => s.selected)
|
|
5588
6664
|
const status = useStore((s) => s.status)
|
|
5589
6665
|
// The layout keeps this page registered whatever the selection is, so it
|
|
@@ -5632,6 +6708,11 @@ window.__ModuleLoader__.load({
|
|
|
5632
6708
|
label: labels[id],
|
|
5633
6709
|
description: descriptions[id],
|
|
5634
6710
|
swatches: swatches[id] || [],
|
|
6711
|
+
// Absent for every strip card; the palette-picker card is the only one
|
|
6712
|
+
// that has it.
|
|
6713
|
+
rows: (cardRows || {})[id],
|
|
6714
|
+
// Which of the picker's 15 slots is lit. Ignored by strip cards.
|
|
6715
|
+
selectedScheme: scheme,
|
|
5635
6716
|
selected: id === selected,
|
|
5636
6717
|
applied: t('applied'),
|
|
5637
6718
|
// The click is recorded BEFORE it is handed to the shell, because the theme
|
|
@@ -5639,6 +6720,10 @@ window.__ModuleLoader__.load({
|
|
|
5639
6720
|
// skin: a built-in card clicked after a skin used to be undone immediately.
|
|
5640
6721
|
// `rememberCardChoice` is what lets 浅色 / 深色 win — see its documentation.
|
|
5641
6722
|
onSelect: (id) => { rememberCardChoice(id); setTheme(id) },
|
|
6723
|
+
// A picker slot: record the scheme first, then hand the anchor theme to the
|
|
6724
|
+
// shell exactly like clicking the card would — so the anchor becomes active
|
|
6725
|
+
// and the scheme layer has something to sit on.
|
|
6726
|
+
onPickScheme: (schemeId) => { pickScheme(schemeId) },
|
|
5642
6727
|
t,
|
|
5643
6728
|
}, id)),
|
|
5644
6729
|
}),
|
|
@@ -6265,6 +7350,26 @@ window.__ModuleLoader__.load({
|
|
|
6265
7350
|
* only used once the document has demonstrably failed to follow the service.
|
|
6266
7351
|
*/
|
|
6267
7352
|
const paintAttempts = new Map()
|
|
7353
|
+
|
|
7354
|
+
/**
|
|
7355
|
+
* 观察到"服务已报告、文档未落色"的**时刻**(配合 {@link PAINT_GRACE_MS} 使用)。
|
|
7356
|
+
* @type {{id: string, at: number}|undefined}
|
|
7357
|
+
*/
|
|
7358
|
+
let paintSeen
|
|
7359
|
+
|
|
7360
|
+
/**
|
|
7361
|
+
* 判"这次切换丢了"之前要等多久(毫秒)。
|
|
7362
|
+
*
|
|
7363
|
+
* 表现层是异步写 token 的,所以 `setTheme` 之后**一段时间内**文档一定还是旧色 ——
|
|
7364
|
+
* 那不是"错过",只是"还没轮到表现层"。原先没有这个等待,于是**每点一次配色卡都先跳转
|
|
7365
|
+
* 一次浅色**(用户报的"第一次点击不生效、反而变成浅色主题,再点一次才行")。
|
|
7366
|
+
*
|
|
7367
|
+
* 为什么不能只按"经过了一次 publish"来判:外壳的 `theme/change` 常常是**延迟送达**的,
|
|
7368
|
+
* 同一次点击里会再来一次 publish(实测读数:一次点击的 `setTheme` 序列是
|
|
7369
|
+
* `…, 'shi-liu-jin', 'light', 'shi-liu-jin'`,中间那对就是跳转)。所以要**按时间**等。
|
|
7370
|
+
* 400ms 足以覆盖表现层的一到两帧,又远小于人再点一次的反应时间。
|
|
7371
|
+
*/
|
|
7372
|
+
const PAINT_GRACE_MS = 400
|
|
6268
7373
|
/**
|
|
6269
7374
|
* Apply the remembered skin, and confirm it landed.
|
|
6270
7375
|
*
|
|
@@ -6291,6 +7396,7 @@ window.__ModuleLoader__.load({
|
|
|
6291
7396
|
}
|
|
6292
7397
|
if (skinIsPainted(wanted)) {
|
|
6293
7398
|
paintAttempts.clear()
|
|
7399
|
+
paintSeen = undefined
|
|
6294
7400
|
noteAmbientEvent('已上色', wanted)
|
|
6295
7401
|
return
|
|
6296
7402
|
}
|
|
@@ -6309,11 +7415,29 @@ window.__ModuleLoader__.load({
|
|
|
6309
7415
|
// Budgeted and cooled down. This branch used to call `setTheme` unconditionally on every
|
|
6310
7416
|
// publish, with no throttle at all, so the shell putting the active id back to a
|
|
6311
7417
|
// built-in value made it a perpetual request ↔ publish ping-pong.
|
|
7418
|
+
paintSeen = undefined
|
|
6312
7419
|
if (requestTheme(wanted)) noteAmbientEvent('置为皮肤', wanted)
|
|
6313
7420
|
// Give the presenter a chance to land before judging it.
|
|
6314
7421
|
return
|
|
6315
7422
|
}
|
|
6316
7423
|
|
|
7424
|
+
// ── 先给它一段**时间**的耐心(代码上方那句"要跨过一次 PAUSE 才跳转"原本并没实现)──
|
|
7425
|
+
//
|
|
7426
|
+
// 直接 `setTheme` 之后的那几帧里,文档一定还没落色 —— 那不是"错过",只是"还没轮到
|
|
7427
|
+
// 表现层"。在这里就跳转的代价是:**每点一次配色卡都先闪一次浅色**,而且跳转里那两次
|
|
7428
|
+
// 写入会和外壳的偏好处理打架(用户报的"第一次点击不生效、反而变成浅色")。
|
|
7429
|
+
// 第一次只记时刻,过了 {@link PAINT_GRACE_MS} 仍未落色,才说明真的错过了。
|
|
7430
|
+
const now = Date.now()
|
|
7431
|
+
if (paintSeen === undefined || paintSeen.id !== wanted) {
|
|
7432
|
+
paintSeen = { id: wanted, at: now }
|
|
7433
|
+
noteAmbientEvent('等待上色', wanted)
|
|
7434
|
+
return
|
|
7435
|
+
}
|
|
7436
|
+
if (now - paintSeen.at < PAINT_GRACE_MS) {
|
|
7437
|
+
noteAmbientEvent('等待上色', wanted)
|
|
7438
|
+
return
|
|
7439
|
+
}
|
|
7440
|
+
|
|
6317
7441
|
// The service ALREADY reports this skin, yet the document does not show it: the change
|
|
6318
7442
|
// was missed by a presenter that had not subscribed yet. Only now is a bounce justified.
|
|
6319
7443
|
const bounced = paintAttempts.get(wanted) ?? 0
|
|
@@ -6455,9 +7579,13 @@ window.__ModuleLoader__.load({
|
|
|
6455
7579
|
// never change which themes exist, what they contain, or the order they were
|
|
6456
7580
|
// contributed — `cardsInDisplayOrder` is a view over the filtered list.
|
|
6457
7581
|
const themes = cardsInDisplayOrder([...snapshot.themes].filter((theme) => !OMITTED_IDS.has(theme.id)))
|
|
6458
|
-
storeActions.sync(themes, snapshot
|
|
7582
|
+
storeActions.sync(themes, appliedCardId(snapshot), revision)
|
|
7583
|
+
// 卡片上点亮哪一格,以及要不要把那一层配色叠上去 —— 两件事都读同一份
|
|
7584
|
+
// localStorage,所以"看到的选中态"与"屏幕上的颜色"不可能对不上。
|
|
7585
|
+
storeActions.markScheme(wantedScheme())
|
|
6459
7586
|
syncSkin(snapshot)
|
|
6460
7587
|
syncRememberedSkin(snapshot)
|
|
7588
|
+
syncScheme(snapshot)
|
|
6461
7589
|
// Expose what the theme SERVICE believes, so the page can compare it with
|
|
6462
7590
|
// what is actually in effect on the document. The presenter consumes this
|
|
6463
7591
|
// same snapshot (`snapshot.active.tokens`) and writes it to `body`, so a
|
|
@@ -6480,6 +7608,74 @@ window.__ModuleLoader__.load({
|
|
|
6480
7608
|
}
|
|
6481
7609
|
}
|
|
6482
7610
|
|
|
7611
|
+
/**
|
|
7612
|
+
* 用户点了配色卡上的一个色值按钮(或卡片本体 = 用它记住的那一套,没有就是默认那套)。
|
|
7613
|
+
*
|
|
7614
|
+
* ── 为什么必须显式切主题(第一版漏了这一步,用户实机报"点了没反应")──────────
|
|
7615
|
+
*
|
|
7616
|
+
* 第一版只做了 `rememberScheme` + `rememberCardChoice(锚主题)` + `publish()`,以为
|
|
7617
|
+
* `syncRememberedSkin` 会把锚主题切过去。**它不会**:
|
|
7618
|
+
*
|
|
7619
|
+
* • `rememberCardChoice` 只**清掉**"内置外观"标记,它从不写 `last-skin`;
|
|
7620
|
+
* • 于是 `wantedSkin()` 仍指向原来那套皮肤,而 `syncRememberedSkin` 见到活动主题
|
|
7621
|
+
* 是本包皮肤就走"记住它"那条分支 —— 活动主题被原样记回去并继续维持;
|
|
7622
|
+
* • `syncScheme` 的判据是 `active.id === PALETTE_ANCHOR`,不成立 → 配色层永不叠加。
|
|
7623
|
+
*
|
|
7624
|
+
* 面板上的普通皮肤卡之所以能切,是因为它把 `setTheme` 交给了外壳(槽位动作那条例外)。
|
|
7625
|
+
*
|
|
7626
|
+
* ── 为什么这里用 `emitting(...)` 而不是再加一条审计例外 ─────────────────────
|
|
7627
|
+
*
|
|
7628
|
+
* 这次 `theme/change` 确实是"用户换了主题"的信号,但它**不需要**靠事件传递:本函数
|
|
7629
|
+
* 紧接着自己调一次 `publish()`,那一次的快照里活动主题已经是锚主题。把它标成"我造成的",
|
|
7630
|
+
* 订阅者就跳过这次回声,正好避免一次点击触发两轮同步(规则 1)。
|
|
7631
|
+
* @param schemeId - 配色方案 id(`p-…`)。
|
|
7632
|
+
*/
|
|
7633
|
+
function chooseScheme(schemeId) {
|
|
7634
|
+
// 不认识的 id 直接丢:写进 localStorage 会让下次启动带着一个永远点不亮的选中态。
|
|
7635
|
+
if (schemeById(schemeId, PALETTE_SCHEMES) === undefined) return
|
|
7636
|
+
try {
|
|
7637
|
+
rememberScheme(schemeId)
|
|
7638
|
+
rememberCardChoice(PALETTE_ANCHOR)
|
|
7639
|
+
// 锚主题不是活动主题时必须真的切过去,否则上面那条链一步都不会发生。判据取活动主题,
|
|
7640
|
+
// 所以"已经在锚主题上再点另一格"不会白写一次 setTheme(同一个 id 的 setTheme
|
|
7641
|
+
// 是**不会 emit 的**,见 SKILL §12)。
|
|
7642
|
+
const activeId = ctx.theme.getTheme()?.active?.id
|
|
7643
|
+
if (activeId !== PALETTE_ANCHOR) {
|
|
7644
|
+
emitting(() => ctx.theme.setTheme(PALETTE_ANCHOR))
|
|
7645
|
+
}
|
|
7646
|
+
publish(ctx.theme.getTheme())
|
|
7647
|
+
} catch (error) {
|
|
7648
|
+
console.error('[theme-gallery] could not switch the palette scheme:', error)
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
|
|
7652
|
+
/**
|
|
7653
|
+
* 卡片上「已应用」徽标应该落在哪一张卡上。
|
|
7654
|
+
*
|
|
7655
|
+
* ── 为什么不直接用 `snapshot.preference`(用户实机报的 bug)────────────────
|
|
7656
|
+
*
|
|
7657
|
+
* 点一个色值按钮时,徽标在「浅色」与「纯色/拼色」之间来回跳。根因是
|
|
7658
|
+
* {@link ensureSkinPainted} 的**跳转重试**:当服务已经报告皮肤、而文档还没落色时,
|
|
7659
|
+
* 它会先切到内置主题再切回皮肤(`emitting(() => ctx.theme.setTheme(BUILT_IN_PROBE_THEME))`),
|
|
7660
|
+
* 每次都是一次真实的 preference 变化 → 一次 publish → 徽标跟着那个**中间态**跳一下。
|
|
7661
|
+
*
|
|
7662
|
+
* 徽标要回答的是"用户选的是哪一张卡",不是"服务此刻停在哪一帧"。中间态有两个特征可以
|
|
7663
|
+
* 一票排除:它等于 {@link BUILT_IN_PROBE_THEME},而且用户**没有**在面板里选过任何内置外观
|
|
7664
|
+
* (`builtInChoice()` 为空)。两条同时成立时,按"想要的那套皮肤"显示 —— 跳转只是为了
|
|
7665
|
+
* 让表现层重新落色,不是一次选择。
|
|
7666
|
+
*
|
|
7667
|
+
* 其余情况一律照服务当下的活动主题显示(用户点深色就走深色、外部改外观也跟着走),
|
|
7668
|
+
* 所以这不是"徽标只认 localStorage",而是"只把中间态排除掉"。
|
|
7669
|
+
* @param snapshot - 官方主题快照。
|
|
7670
|
+
* @returns 应该带徽标的卡片 id。
|
|
7671
|
+
*/
|
|
7672
|
+
function appliedCardId(snapshot) {
|
|
7673
|
+
const active = snapshot === undefined || snapshot === null ? {} : (snapshot.active ?? {})
|
|
7674
|
+
const wanted = wantedSkin()
|
|
7675
|
+
if (wanted !== null && active.id === BUILT_IN_PROBE_THEME && builtInChoice() === null) return wanted
|
|
7676
|
+
return active.id ?? snapshot?.preference
|
|
7677
|
+
}
|
|
7678
|
+
|
|
6483
7679
|
/**
|
|
6484
7680
|
* Resolve a bundled theme into the shape `register` actually consumes.
|
|
6485
7681
|
*
|
|
@@ -6818,7 +8014,12 @@ window.__ModuleLoader__.load({
|
|
|
6818
8014
|
// — so it is set BEFORE the publish that reads it.
|
|
6819
8015
|
injectRan = true
|
|
6820
8016
|
publish(ctx.theme.getTheme())
|
|
6821
|
-
return {
|
|
8017
|
+
return {
|
|
8018
|
+
setTheme: (id) => { ctx.theme.setTheme(id) },
|
|
8019
|
+
// 配色卡上的 15 个色值按钮走这条。它**不直接**切主题(见 `chooseScheme`),
|
|
8020
|
+
// 所以这里不新增主题写入点。
|
|
8021
|
+
pickScheme: (schemeId) => { chooseScheme(schemeId) },
|
|
8022
|
+
}
|
|
6822
8023
|
},
|
|
6823
8024
|
}, ThemeGalleryPage)
|
|
6824
8025
|
|