zcode-pro 1.3.1 → 1.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.en.md +57 -30
- package/README.md +57 -30
- package/dist/inject.js +1195 -66
- package/dist/zcode_pro-1.4.0-py3-none-any.whl +0 -0
- package/dist/zcode_pro-1.4.0.tar.gz +0 -0
- package/package.json +1 -1
- package/src/host/helper.mjs +492 -57
- package/src/host/webdav.mjs +19 -11
- package/src/inject/core.js +132 -20
- package/src/inject/features/settings-dialog.js +400 -44
- package/src/inject/features/status-bar.js +17 -4
- package/src/inject/features/theme.js +500 -0
- package/src/inject/index.js +2 -0
- package/src/inject/ui.js +33 -2
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
// 主题(设置弹窗 外观 → 主题):皮肤 + 背景图。
|
|
2
|
+
// 皮肤:覆盖 ZCode 明/暗两套 CSS 变量(theme-zai-light / theme-zai-dark 根 class),
|
|
3
|
+
// 每套皮肤含明、暗两份色值,跟随 ZCode 当前明暗自动选用,不改用户的明暗设置。
|
|
4
|
+
// 背景图:图库存 helper 数据目录(CSS url() 引用 /bg/file/<name>,免 token),
|
|
5
|
+
// 或内置纯 CSS 渐变(grad-* 前缀 id,无图片文件)。整窗铺图:最底层是 html
|
|
6
|
+
// 背景(图 + 同色遮罩,遮罩浓度 = 1 - 可见度),侧栏/会话流/输入框等表面
|
|
7
|
+
// token 改写为 color-mix 半透明让图透出;弹窗/菜单等浮层 token 不动,保可读。
|
|
8
|
+
// 表面半透明在 JS 里做而不是纯 CSS:需要先读皮肤应用后的计算值再混透明,
|
|
9
|
+
// 明暗切换/换肤/调可见度都要重算(重算前先撤掉上一轮注入,避免 color-mix
|
|
10
|
+
// 叠 color-mix 越混越透——PiDeck 踩过同一坑)。
|
|
11
|
+
import { getConfig, rpc, liveBoot, HELPER_URL } from '../core.js';
|
|
12
|
+
|
|
13
|
+
// —— 皮肤定义:色阶制(取自 Radix Colors,MIT)——
|
|
14
|
+
// 每套皮肤 = 一个色相的 12 级色阶 × 明/暗两套 + 透明度叠色。级数语义按 Radix
|
|
15
|
+
// 约定固定:1-2 界面底色、3-4 元素底与悬停、5-6 边框与低强调底、7-8 中强调底、
|
|
16
|
+
// 9 品牌色、10 焦点描边、11-12 可读文字。ZCode 的表面层级(背景 < 侧栏 < 卡片/
|
|
17
|
+
// 输入,悬停加深)与 gray 色阶逐级对得上(gray2≈#f8f8f8 背景、gray3≈#f0f0f0
|
|
18
|
+
// 侧栏、gray4≈#ececee winAlt、gray1≈#fff 卡片;暗侧同理),按级数映射即可,
|
|
19
|
+
// 不再逐 token 手写色值——手写版文字/图标/边框的色调各自为政,观感发灰。
|
|
20
|
+
// —— 皮肤定义:知名编辑器主题(VS Code / IntelliJ 装机前列)× 12 级色阶 ——
|
|
21
|
+
// 每套 = 官方调色板锚点(背景/卡片/文字/注释色/强调色)经 OKLab 插值生成的
|
|
22
|
+
// 12 级明/暗色阶 + 透明度叠色;级数语义沿用 Radix 约定(1-2 界面底、3-4 元素
|
|
23
|
+
// 底与悬停、5-6 边框、8-10 强调、11-12 可读文字),ZCode 表面层级按级映射。
|
|
24
|
+
// 锚点取自各主题公开配色:Dracula #282a36/#bd93f9、Nord #2e3440/#88c0d0、
|
|
25
|
+
// Tokyo Night #1a1b26/#7aa2f7、Catppuccin Mocha/Latte、Gruvbox #282828/#d79921、
|
|
26
|
+
// One Dark #282c34/#61afef、Solarized #002b36/#2aa198、Rosé Pine #191724/#c4a7e7、
|
|
27
|
+
// Everforest #2d353b/#a7c080、Night Owl #011627/#82aaff、Kanagawa #1f1f28/#7e9cd8。
|
|
28
|
+
// 暗色为主设计的主题(如 Dracula)按同色相合成亮色变体。
|
|
29
|
+
const SC = (light, dark, lightA, darkA) => ({ light, dark, lightA, darkA });
|
|
30
|
+
const SCALES = {
|
|
31
|
+
dracula: SC(['#fefeff', '#f5f4f9', '#f1f0f6', '#edebf4', '#dedee4', '#c4c3cb', '#aaa9b3', '#885cdb', '#9a6ff0', '#af85ff', '#5f5c77', '#2b2a3a'],
|
|
32
|
+
['#21222c', '#282a36', '#2d303d', '#343746', '#484b59', '#656873', '#80828a', '#aa80e5', '#bd93f9', '#d3a9ff', '#7b89b3', '#f8f8f2'],
|
|
33
|
+
['#2b2a3a03', '#2b2a3a06', '#2b2a3a0f', '#2b2a3a17', '#2b2a3a1f', '#2b2a3a26', '#2b2a3a31', '#2b2a3a44', '#2b2a3a72', '#2b2a3a7c', '#2b2a3a9b', '#2b2a3adf'],
|
|
34
|
+
['#f8f8f203', '#f8f8f206', '#f8f8f20f', '#f8f8f217', '#f8f8f21f', '#f8f8f226', '#f8f8f231', '#f8f8f244', '#f8f8f272', '#f8f8f27c', '#f8f8f29b', '#f8f8f2df']),
|
|
35
|
+
nord: SC(['#fbfcfe', '#f2f5f9', '#eef2f7', '#eaeff5', '#dce0e5', '#c3c7cd', '#aaaeb6', '#4d6f99', '#5e81ac', '#7296c2', '#576377', '#2e3440'],
|
|
36
|
+
['#272c36', '#2e3440', '#343a48', '#3b4252', '#4e5564', '#686e7c', '#808692', '#75adbd', '#88c0d0', '#9ed7e7', '#8e9ab0', '#eceff4'],
|
|
37
|
+
['#2e344003', '#2e344006', '#2e34400f', '#2e344017', '#2e34401f', '#2e344026', '#2e344031', '#2e344044', '#2e344072', '#2e34407c', '#2e34409b', '#2e3440df'],
|
|
38
|
+
['#eceff403', '#eceff406', '#eceff40f', '#eceff417', '#eceff41f', '#eceff426', '#eceff431', '#eceff444', '#eceff472', '#eceff47c', '#eceff49b', '#eceff4df']),
|
|
39
|
+
tokyonight: SC(['#fafbfe', '#eef0f6', '#e9ebf2', '#e3e6ef', '#d9dce5', '#c0c5d1', '#a8aebd', '#2d488e', '#3d59a1', '#506eb8', '#5a6684', '#2f3a55'],
|
|
40
|
+
['#13141d', '#1a1b26', '#1e212f', '#24283b', '#34394f', '#4b516a', '#606784', '#688fe3', '#7aa2f7', '#8fb8ff', '#8893c3', '#c0caf5'],
|
|
41
|
+
['#2f3a5503', '#2f3a5506', '#2f3a550f', '#2f3a5517', '#2f3a551f', '#2f3a5526', '#2f3a5531', '#2f3a5544', '#2f3a5572', '#2f3a557c', '#2f3a559b', '#2f3a55df'],
|
|
42
|
+
['#c0caf503', '#c0caf506', '#c0caf50f', '#c0caf517', '#c0caf51f', '#c0caf526', '#c0caf531', '#c0caf544', '#c0caf572', '#c0caf57c', '#c0caf59b', '#c0caf5df']),
|
|
43
|
+
catppuccin: SC(['#fbfcfe', '#eff1f5', '#eaecf1', '#e4e7ed', '#dde0e6', '#c8cbd5', '#b4b7c3', '#6174e8', '#7287fd', '#869dff', '#73768c', '#4c4f69'],
|
|
44
|
+
['#181825', '#1e1e2e', '#262738', '#313244', '#424357', '#595c71', '#6e7289', '#a1abea', '#b4befe', '#cad5ff', '#979cb5', '#cdd6f4'],
|
|
45
|
+
['#4c4f6903', '#4c4f6906', '#4c4f690f', '#4c4f6917', '#4c4f691f', '#4c4f6926', '#4c4f6931', '#4c4f6944', '#4c4f6972', '#4c4f697c', '#4c4f699b', '#4c4f69df'],
|
|
46
|
+
['#cdd6f403', '#cdd6f406', '#cdd6f40f', '#cdd6f417', '#cdd6f41f', '#cdd6f426', '#cdd6f431', '#cdd6f444', '#cdd6f472', '#cdd6f47c', '#cdd6f49b', '#cdd6f4df']),
|
|
47
|
+
gruvbox: SC(['#fefbf0', '#f8f2dd', '#f5eed4', '#f3eacc', '#e3decb', '#cbc5b5', '#b3aea0', '#8e6b00', '#a07d0a', '#b6922e', '#706558', '#3c3836'],
|
|
48
|
+
['#1d2021', '#282828', '#312f2e', '#3c3836', '#4f4944', '#696256', '#807867', '#c38600', '#d79921', '#eeaf40', '#a1927f', '#ebdbb2'],
|
|
49
|
+
['#3c383603', '#3c383606', '#3c38360f', '#3c383617', '#3c38361f', '#3c383626', '#3c383631', '#3c383644', '#3c383672', '#3c38367c', '#3c38369b', '#3c3836df'],
|
|
50
|
+
['#ebdbb203', '#ebdbb206', '#ebdbb20f', '#ebdbb217', '#ebdbb21f', '#ebdbb226', '#ebdbb231', '#ebdbb244', '#ebdbb272', '#ebdbb27c', '#ebdbb29b', '#ebdbb2df']),
|
|
51
|
+
onedark: SC(['#fcfdff', '#f4f6fa', '#f0f3f8', '#edf0f6', '#dfe1e6', '#c6c8ce', '#adb0b7', '#3870c4', '#4a82d8', '#5f98f0', '#606774', '#333842'],
|
|
52
|
+
['#21252b', '#282c34', '#2d313a', '#333842', '#404550', '#525863', '#626874', '#4e9cdb', '#61afef', '#77c6ff', '#6a717e', '#abb2bf'],
|
|
53
|
+
['#33384203', '#33384206', '#3338420f', '#33384217', '#3338421f', '#33384226', '#33384231', '#33384244', '#33384272', '#3338427c', '#3338429b', '#333842df'],
|
|
54
|
+
['#abb2bf03', '#abb2bf06', '#abb2bf0f', '#abb2bf17', '#abb2bf1f', '#abb2bf26', '#abb2bf31', '#abb2bf44', '#abb2bf72', '#abb2bf7c', '#abb2bf9b', '#abb2bfdf']),
|
|
55
|
+
solarized: SC(['#fffbf0', '#fdf6e3', '#fcf4dd', '#fbf1d7', '#ebe8d8', '#d7d7ca', '#c2c6bc', '#0078be', '#268bd2', '#41a1ea', '#889899', '#586e75'],
|
|
56
|
+
['#00212b', '#002b36', '#03303b', '#073642', '#1b424d', '#31525b', '#446168', '#008e86', '#2aa198', '#47b7ae', '#62777d', '#93a1a1'],
|
|
57
|
+
['#586e7503', '#586e7506', '#586e750f', '#586e7517', '#586e751f', '#586e7526', '#586e7531', '#586e7544', '#586e7572', '#586e757c', '#586e759b', '#586e75df'],
|
|
58
|
+
['#93a1a103', '#93a1a106', '#93a1a10f', '#93a1a117', '#93a1a11f', '#93a1a126', '#93a1a131', '#93a1a144', '#93a1a172', '#93a1a17c', '#93a1a19b', '#93a1a1df']),
|
|
59
|
+
rosepine: SC(['#fffaf3', '#faf4ed', '#f8f1ea', '#f6efe8', '#e8e3e1', '#d3ced3', '#bfbac5', '#7e6896', '#907aa9', '#a58fbf', '#837d93', '#575279'],
|
|
60
|
+
['#141220', '#191724', '#1c1a28', '#1f1d2e', '#323143', '#4e4d60', '#69677b', '#b194d3', '#c4a7e7', '#dbbdff', '#817e99', '#e0def4'],
|
|
61
|
+
['#57527903', '#57527906', '#5752790f', '#57527917', '#5752791f', '#57527926', '#57527931', '#57527944', '#57527972', '#5752797c', '#5752799b', '#575279df'],
|
|
62
|
+
['#e0def403', '#e0def406', '#e0def40f', '#e0def417', '#e0def41f', '#e0def426', '#e0def431', '#e0def444', '#e0def472', '#e0def47c', '#e0def49b', '#e0def4df']),
|
|
63
|
+
everforest: SC(['#fefdf5', '#faf7ec', '#f8f4e8', '#f6f2e4', '#e7e6dd', '#d2d2cb', '#bcbeb9', '#588c47', '#6a9e59', '#7fb46e', '#76827b', '#4f585e'],
|
|
64
|
+
['#272e33', '#2d353b', '#30393f', '#343f44', '#454e50', '#5d625f', '#73756d', '#95ad6e', '#a7c080', '#bdd796', '#a7aea2', '#d3c6aa'],
|
|
65
|
+
['#4f585e03', '#4f585e06', '#4f585e0f', '#4f585e17', '#4f585e1f', '#4f585e26', '#4f585e31', '#4f585e44', '#4f585e72', '#4f585e7c', '#4f585e9b', '#4f585edf'],
|
|
66
|
+
['#d3c6aa03', '#d3c6aa06', '#d3c6aa0f', '#d3c6aa17', '#d3c6aa1f', '#d3c6aa26', '#d3c6aa31', '#d3c6aa44', '#d3c6aa72', '#d3c6aa7c', '#d3c6aa9b', '#d3c6aadf']),
|
|
67
|
+
nightowl: SC(['#fdfdfe', '#f6f7f9', '#f3f4f7', '#f0f2f5', '#e2e3e7', '#cbcbd2', '#b3b4bd', '#2253c2', '#3366d6', '#477cee', '#64667c', '#403f53'],
|
|
68
|
+
['#010e1c', '#011627', '#072033', '#122d42', '#284054', '#445a6e', '#5f7285', '#7097ea', '#82aaff', '#97c1ff', '#778ea7', '#d6deeb'],
|
|
69
|
+
['#403f5303', '#403f5306', '#403f530f', '#403f5317', '#403f531f', '#403f5326', '#403f5331', '#403f5344', '#403f5372', '#403f537c', '#403f539b', '#403f53df'],
|
|
70
|
+
['#d6deeb03', '#d6deeb06', '#d6deeb0f', '#d6deeb17', '#d6deeb1f', '#d6deeb26', '#d6deeb31', '#d6deeb44', '#d6deeb72', '#d6deeb7c', '#d6deeb9b', '#d6deebdf']),
|
|
71
|
+
kanagawa: SC(['#fcf9f0', '#f4f1e8', '#f0ede4', '#edeae1', '#e1ded5', '#cac8c0', '#b4b2aa', '#2d4d95', '#3d5fa8', '#5074bf', '#72726a', '#43443e'],
|
|
72
|
+
['#16161d', '#1f1f28', '#24242f', '#2a2a37', '#3c3c46', '#57565a', '#6f6d6c', '#6c8ac4', '#7e9cd8', '#93b2ef', '#848277', '#dcd7ba'],
|
|
73
|
+
['#43443e03', '#43443e06', '#43443e0f', '#43443e17', '#43443e1f', '#43443e26', '#43443e31', '#43443e44', '#43443e72', '#43443e7c', '#43443e9b', '#43443edf'],
|
|
74
|
+
['#dcd7ba03', '#dcd7ba06', '#dcd7ba0f', '#dcd7ba17', '#dcd7ba1f', '#dcd7ba26', '#dcd7ba31', '#dcd7ba44', '#dcd7ba72', '#dcd7ba7c', '#dcd7ba9b', '#dcd7badf']),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const THEME_SKINS = [
|
|
78
|
+
{ id: 'default', zh: '默认', en: 'Default',
|
|
79
|
+
preview: ['#f8f8f8', '#ffffff', '#ececee', '#dfdfdf', '#8b8b8b', '#18181b', '#9a9a9a', '#18181b'] },
|
|
80
|
+
{ id: 'dracula', hue: 'dracula', zh: '德古拉', en: 'Dracula' },
|
|
81
|
+
{ id: 'nord', hue: 'nord', zh: '极夜', en: 'Nord' },
|
|
82
|
+
{ id: 'tokyonight', hue: 'tokyonight', zh: '东京夜', en: 'Tokyo Night' },
|
|
83
|
+
{ id: 'catppuccin', hue: 'catppuccin', zh: '奶咖', en: 'Catppuccin' },
|
|
84
|
+
{ id: 'gruvbox', hue: 'gruvbox', zh: '暖岩', en: 'Gruvbox' },
|
|
85
|
+
{ id: 'onedark', hue: 'onedark', zh: 'One Dark', en: 'One Dark' },
|
|
86
|
+
{ id: 'solarized', hue: 'solarized', zh: '日晒', en: 'Solarized' },
|
|
87
|
+
{ id: 'rosepine', hue: 'rosepine', zh: '玫瑰松', en: 'Rosé Pine' },
|
|
88
|
+
{ id: 'everforest', hue: 'everforest', zh: '常绿', en: 'Everforest' },
|
|
89
|
+
{ id: 'nightowl', hue: 'nightowl', zh: '夜枭', en: 'Night Owl' },
|
|
90
|
+
{ id: 'kanagawa', hue: 'kanagawa', zh: '神奈川', en: 'Kanagawa' },
|
|
91
|
+
];
|
|
92
|
+
// 预览色条(8 块:底色/卡片/加深面/边框级/强调/强调亮/次级文字/正文),从明色色阶取
|
|
93
|
+
for (const s of THEME_SKINS) {
|
|
94
|
+
if (!s.hue) continue;
|
|
95
|
+
const L = SCALES[s.hue].light;
|
|
96
|
+
s.preview = [L[1], L[0], L[3], L[5], L[8], L[9], L[10], L[11]];
|
|
97
|
+
}
|
|
98
|
+
export const THEME_SKIN_IDS = THEME_SKINS.map((s) => s.id);
|
|
99
|
+
|
|
100
|
+
// '#rrggbb' + 两位十六进制 α(与 ZCode 原版边框/叠色写法一致)
|
|
101
|
+
const hexA = (hex, alphaHex) => `#${hex.slice(1)}${alphaHex}`;
|
|
102
|
+
|
|
103
|
+
// 由色阶生成整套 token 覆盖:表面按级映射,文字族取级 11/12,边框与悬停叠色
|
|
104
|
+
// 由文字色加 α 派生,品牌族取级 9。isDark 只影响取哪套色阶与个别层级偏移
|
|
105
|
+
function skinVars(s, isDark) {
|
|
106
|
+
if (!s) return null;
|
|
107
|
+
const fg = s[11];
|
|
108
|
+
return {
|
|
109
|
+
'--color-background': s[1],
|
|
110
|
+
'--color-background-win-alt': s[3],
|
|
111
|
+
'--color-background-alt': `color-mix(in oklab, ${s[1]} ${isDark ? 60 : 70}%, transparent)`,
|
|
112
|
+
'--color-sidebar': s[isDark ? 1 : 2],
|
|
113
|
+
'--color-header': s[isDark ? 2 : 0],
|
|
114
|
+
'--color-panel': s[isDark ? 2 : 0],
|
|
115
|
+
'--color-card': s[isDark ? 3 : 0],
|
|
116
|
+
'--color-card-selected': s[isDark ? 4 : 1],
|
|
117
|
+
'--color-input': s[isDark ? 3 : 0],
|
|
118
|
+
'--color-input-focused': s[isDark ? 3 : 0],
|
|
119
|
+
'--color-menu': s[isDark ? 3 : 0],
|
|
120
|
+
'--color-menu-hover': s[isDark ? 4 : 2],
|
|
121
|
+
'--color-popover': s[isDark ? 3 : 0],
|
|
122
|
+
'--color-popover-header': s[isDark ? 2 : 1],
|
|
123
|
+
'--color-highspeed-composer-surface': s[isDark ? 0 : 1],
|
|
124
|
+
'--color-tab-active': s[isDark ? 1 : 0],
|
|
125
|
+
'--color-foreground': fg,
|
|
126
|
+
'--color-foreground-subtle': s[10],
|
|
127
|
+
'--color-foreground-subtlest': `color-mix(in oklab, ${s[10]} 60%, transparent)`,
|
|
128
|
+
'--color-foreground-inverse': isDark ? '#101010' : '#ffffff',
|
|
129
|
+
'--color-popover-foreground': fg,
|
|
130
|
+
'--color-tooltip-foreground': fg,
|
|
131
|
+
'--color-brand': fg,
|
|
132
|
+
'--color-primary': fg,
|
|
133
|
+
'--color-primary-foreground': isDark ? '#101010' : '#ffffff',
|
|
134
|
+
'--color-accent': s[2],
|
|
135
|
+
'--color-highspeed-accent': s[8],
|
|
136
|
+
'--highspeed-sidebar-model-icon': s[8],
|
|
137
|
+
'--highspeed-sidebar-model-gradient': `linear-gradient(90deg,${s[7]} 0%,${s[8]} 50%,${s[9]} 100%)`,
|
|
138
|
+
'--color-hover': hexA(fg, isDark ? '12' : '0f'),
|
|
139
|
+
'--color-surface-hover': hexA(fg, isDark ? '12' : '0f'),
|
|
140
|
+
'--color-border': hexA(fg, '1a'),
|
|
141
|
+
'--color-border-hover': hexA(fg, '26'),
|
|
142
|
+
'--color-card-border': hexA(fg, '1a'),
|
|
143
|
+
'--color-input-border': hexA(fg, '1a'),
|
|
144
|
+
'--color-input-border-hover': hexA(fg, '26'),
|
|
145
|
+
'--color-input-border-focused': hexA(fg, '26'),
|
|
146
|
+
'--color-popover-border': hexA(fg, '1a'),
|
|
147
|
+
'--color-tab-border': hexA(fg, '1a'),
|
|
148
|
+
'--color-highspeed-composer-border-pre-animation': hexA(fg, '1a'),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// —— 自定义主题(skin === 'custom'):五个锚点色 × 明暗(背景/卡片/正文/次级
|
|
153
|
+
// 文字/强调),未设置的字段回退默认。色阶在页内现算,派生全部用
|
|
154
|
+
// color-mix(in oklab) 表达式(插值交给 CSS 引擎),级数语义与预设完全一致
|
|
155
|
+
export const CUSTOM_DEFAULTS = {
|
|
156
|
+
light: { bg: '#f7f8fa', card: '#ffffff', fg: '#24262b', muted: '#6b7280', accent: '#6366f1' },
|
|
157
|
+
dark: { bg: '#18181d', card: '#232329', fg: '#e8e8ec', muted: '#9494a0', accent: '#8b8ff5' },
|
|
158
|
+
};
|
|
159
|
+
const mixOf = (x, t, y) => `color-mix(in oklab, ${x} ${Math.round((1 - t) * 100)}%, ${y} ${Math.round(t * 100)}%)`;
|
|
160
|
+
function anchorsScale(a, isDark) {
|
|
161
|
+
const { bg, card, fg, muted, accent } = a;
|
|
162
|
+
return isDark ? [
|
|
163
|
+
mixOf(bg, 0.12, '#000'), // 0 最深(输入区)
|
|
164
|
+
bg, // 1 背景
|
|
165
|
+
mixOf(bg, 0.45, card), // 2 顶栏/面板
|
|
166
|
+
card, // 3 卡片/输入
|
|
167
|
+
mixOf(card, 0.12, fg), // 4 选中/悬停
|
|
168
|
+
mixOf(card, 0.28, fg), // 5
|
|
169
|
+
mixOf(card, 0.42, fg), // 6
|
|
170
|
+
mixOf(accent, 0.14, '#000'), // 7
|
|
171
|
+
accent, // 8 品牌级
|
|
172
|
+
mixOf(accent, 0.18, '#fff'), // 9
|
|
173
|
+
mixOf(muted, 0.18, fg), // 10 次级文字
|
|
174
|
+
fg, // 11 主文字
|
|
175
|
+
] : [
|
|
176
|
+
card, // 0 最浅(顶栏/卡片)
|
|
177
|
+
bg, // 1 背景
|
|
178
|
+
mixOf(bg, 0.05, fg), // 2 侧栏
|
|
179
|
+
mixOf(bg, 0.11, fg), // 3 winAlt
|
|
180
|
+
mixOf(bg, 0.17, fg), // 4 悬停
|
|
181
|
+
mixOf(bg, 0.26, fg), // 5
|
|
182
|
+
mixOf(bg, 0.36, fg), // 6
|
|
183
|
+
mixOf(accent, 0.14, '#000'),
|
|
184
|
+
accent,
|
|
185
|
+
mixOf(accent, 0.18, '#fff'),
|
|
186
|
+
mixOf(muted, 0.18, fg),
|
|
187
|
+
fg,
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// —— 应用逻辑 ——
|
|
192
|
+
|
|
193
|
+
let skinStyleEl = null; // 皮肤 token 覆盖
|
|
194
|
+
let bgStyleEl = null; // html 背景层(图 + 遮罩)
|
|
195
|
+
let injectedKeys = new Set(); // 半透明化写进 root.style 的变量(重算前先撤)
|
|
196
|
+
let classObserver = null; // 明暗切换(root class 变化)→ 重算遮罩色与透明度
|
|
197
|
+
let currentTheme = { skin: 'default', custom: null, bg: { mode: 'none', maskOpacity: 0.82, blurPx: 0, panelOpacity: 0.3 } };
|
|
198
|
+
let lastModeSig = ''; // 观察器只对明暗模式变化重算:applySkin/applyBackground
|
|
199
|
+
// 自己也会改 root class(zcodepro-skin-*/zcodepro-bg-on),不带这条过滤会自触发循环
|
|
200
|
+
let bgTimer = null; // 文件夹轮换 / 网址刷新定时器(重应用前先清)
|
|
201
|
+
let regionStyleEl = null; // 分区背景(左栏/中栏/右栏)规则
|
|
202
|
+
let regionTimers = []; // 各分区自己的轮换/刷新定时器
|
|
203
|
+
let regionFolderIdx = { left: -1, center: -1, right: -1 };
|
|
204
|
+
// 三大区域容器:左栏(侧栏宽度变量容器)、中栏(会话列)、右栏(停靠面板,
|
|
205
|
+
// conversation-column 的 browser 兄弟,面板关闭时零宽不误伤;元素重挂载后
|
|
206
|
+
// 规则自动重新命中,CSS 方案比持有元素引用稳)
|
|
207
|
+
const REGION_SELECTORS = {
|
|
208
|
+
left: 'div[class*="workspace-sidebar-panel-width"]',
|
|
209
|
+
center: '[data-testid="conversation-column"]',
|
|
210
|
+
right: '[data-testid="conversation-column"] ~ [data-testid="browser"]',
|
|
211
|
+
};
|
|
212
|
+
let folderIdx = -1; // 文件夹顺序模式的当前索引(-1 → 首次取第 0 张)
|
|
213
|
+
let urlTick = 0; // 网址刷新的缓存戳
|
|
214
|
+
let applySeq = 0; // 异步重入令牌:配置快速变化时只让最后一次生效
|
|
215
|
+
|
|
216
|
+
// 铺图时半透明化的表面 token(浮层 menu/popover 不在其中,保不透明可读)
|
|
217
|
+
const SURFACE_TOKENS = [
|
|
218
|
+
'--color-background', '--color-background-win-alt', '--color-sidebar', '--color-header',
|
|
219
|
+
'--color-panel', '--color-card', '--color-card-selected', '--color-input',
|
|
220
|
+
'--color-input-focused', '--color-highspeed-composer-surface',
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
const isDarkMode = () => document.documentElement.classList.contains('theme-zai-dark')
|
|
224
|
+
|| document.documentElement.classList.contains('dark');
|
|
225
|
+
|
|
226
|
+
// 读表面 token 的解析色(rgb()/rgba()):自定义皮肤的多数表面 token 是
|
|
227
|
+
// color-mix() 字符串,直接读自定义属性拿到的是未解析的 token 流(半透明化与
|
|
228
|
+
// 遮罩取色都会整批跳过)——挂到隐藏元素的 color 上让引擎解析成具体颜色
|
|
229
|
+
let probeEl = null;
|
|
230
|
+
function resolvedColor(token) {
|
|
231
|
+
try {
|
|
232
|
+
if (!probeEl || !probeEl.isConnected) {
|
|
233
|
+
probeEl = document.createElement('div');
|
|
234
|
+
probeEl.style.display = 'none';
|
|
235
|
+
(document.body || document.documentElement).append(probeEl);
|
|
236
|
+
}
|
|
237
|
+
probeEl.style.color = '';
|
|
238
|
+
probeEl.style.color = `var(${token})`;
|
|
239
|
+
const v = getComputedStyle(probeEl).color;
|
|
240
|
+
return /^rgba?\(/.test(v) ? v : '';
|
|
241
|
+
} catch {
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function ensureEl(id) {
|
|
247
|
+
let el = document.getElementById(id);
|
|
248
|
+
if (!el || !el.isConnected) {
|
|
249
|
+
el = document.createElement('style');
|
|
250
|
+
el.id = id;
|
|
251
|
+
(document.head || document.documentElement).append(el);
|
|
252
|
+
}
|
|
253
|
+
return el;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function applySkin(skinId, custom) {
|
|
257
|
+
skinStyleEl = ensureEl('__zcodepro_skin__');
|
|
258
|
+
const root = document.documentElement;
|
|
259
|
+
// custom 不在 THEME_SKIN_IDS(预设清单)里,清理时要一并带上
|
|
260
|
+
for (const id of [...THEME_SKIN_IDS, 'custom']) root.classList.remove('zcodepro-skin-' + id);
|
|
261
|
+
const preset = THEME_SKINS.find((s) => s.id === skinId && s.hue);
|
|
262
|
+
const isCustom = skinId === 'custom';
|
|
263
|
+
if (!preset && !isCustom) { skinStyleEl.textContent = ''; return; }
|
|
264
|
+
const cls = preset ? preset.id : 'custom';
|
|
265
|
+
root.classList.add('zcodepro-skin-' + cls);
|
|
266
|
+
const scaleOf = (isDark) => {
|
|
267
|
+
if (isCustom) {
|
|
268
|
+
const dflt = CUSTOM_DEFAULTS[isDark ? 'dark' : 'light'];
|
|
269
|
+
const src = (custom && custom[isDark ? 'dark' : 'light']) || {};
|
|
270
|
+
const a = {};
|
|
271
|
+
for (const k of ['bg', 'card', 'fg', 'muted', 'accent']) {
|
|
272
|
+
a[k] = /^#[0-9a-f]{6}$/i.test(src[k]) ? src[k] : dflt[k];
|
|
273
|
+
}
|
|
274
|
+
return anchorsScale(a, isDark);
|
|
275
|
+
}
|
|
276
|
+
const sc = SCALES[preset.hue];
|
|
277
|
+
return isDark ? sc.dark : sc.light;
|
|
278
|
+
};
|
|
279
|
+
// 选择器比应用原规则(.theme-zai-light = 0,1,0)多一个 class 与元素名,级联必胜;
|
|
280
|
+
// 暗色同时覆盖 .dark 与 .theme-zai-dark 两种根形态(与应用样式表同款写法)
|
|
281
|
+
const rules = (isDark) => {
|
|
282
|
+
const vars = skinVars(scaleOf(isDark), isDark);
|
|
283
|
+
return vars ? Object.entries(vars).map(([k, v]) => `${k}:${v};`).join('') : '';
|
|
284
|
+
};
|
|
285
|
+
// 滚动条 thumb 由文字级色派生(应用原规则只定几何不给色):颜色随皮肤换,几何归应用
|
|
286
|
+
const sb = (isDark) => {
|
|
287
|
+
const fg = scaleOf(isDark)[11];
|
|
288
|
+
return `::-webkit-scrollbar-thumb{background-color:${hexA(fg, '59')} !important;}::-webkit-scrollbar-thumb:hover{background-color:${hexA(fg, '8c')} !important;}`;
|
|
289
|
+
};
|
|
290
|
+
skinStyleEl.textContent =
|
|
291
|
+
`html.theme-zai-light.zcodepro-skin-${cls}{${rules(false)}}` +
|
|
292
|
+
`html.theme-zai-dark.zcodepro-skin-${cls},html.dark.zcodepro-skin-${cls}{${rules(true)}}` +
|
|
293
|
+
`html.theme-zai-light.zcodepro-skin-${cls}{${sb(false)}}` +
|
|
294
|
+
`html.theme-zai-dark.zcodepro-skin-${cls},html.dark.zcodepro-skin-${cls}{${sb(true)}}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function clearBgOverrides() {
|
|
298
|
+
const root = document.documentElement;
|
|
299
|
+
for (const k of injectedKeys) root.style.removeProperty(k);
|
|
300
|
+
injectedKeys = new Set();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 背景四模式:none 无 / library 本地图库单张 / folder 文件夹轮换(随机或顺序,
|
|
304
|
+
// intervalMin 分钟,0 = 不轮换)/ url 网址(intervalMin 分钟刷新,配随机图 API 用,
|
|
305
|
+
// 刷新带缓存戳)。文件夹列表经 helper 取(页面读不了磁盘),取不到一律降级为无背景
|
|
306
|
+
async function applyBackground(bg) {
|
|
307
|
+
const seq = ++applySeq;
|
|
308
|
+
bgStyleEl = ensureEl('__zcodepro_bg__');
|
|
309
|
+
const root = document.documentElement;
|
|
310
|
+
if (bgTimer) { clearInterval(bgTimer); bgTimer = null; }
|
|
311
|
+
root.classList.remove('zcodepro-bg-on');
|
|
312
|
+
clearBgOverrides();
|
|
313
|
+
|
|
314
|
+
// 全局背景与分区背景任一在用,都需要后面的遮罩/半透明化;
|
|
315
|
+
// 全局关闭时只清 html 层,分区规则交给 applyRegionBackgrounds 自己清
|
|
316
|
+
const bgActive = !!(bg && bg.mode && bg.mode !== 'none');
|
|
317
|
+
const anyRegion = !!(bg && bg.regions && (bg.regions.left || bg.regions.center || bg.regions.right));
|
|
318
|
+
if (!bgActive) bgStyleEl.textContent = '';
|
|
319
|
+
if (!bgActive && !anyRegion) {
|
|
320
|
+
await applyRegionBackgrounds(bg); // 无分区时也会清空分区规则与定时器
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 三参数(对齐 codeg 语义):maskOpacity 遮罩不透明度(直接作为遮罩 α);
|
|
325
|
+
// blurPx 图片模糊(CSS filter,只糊图片层不糊遮罩);panelOpacity 面板
|
|
326
|
+
// 不透明度(侧栏/面板/标签栏表面混入底色的比例,独立于遮罩)
|
|
327
|
+
const num01 = (v, dflt) => (typeof v === 'number' && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : dflt);
|
|
328
|
+
const maskOpacity = num01(bg.maskOpacity, 0.82);
|
|
329
|
+
const alpha = Math.min(0.99, maskOpacity);
|
|
330
|
+
const blurPx = typeof bg.blurPx === 'number' && Number.isFinite(bg.blurPx)
|
|
331
|
+
? Math.min(24, Math.max(0, Math.round(bg.blurPx))) : 0;
|
|
332
|
+
const panelOpacity = num01(bg.panelOpacity, 0.3);
|
|
333
|
+
// 遮罩色跟随当前明暗的皮肤底色:在皮肤已应用、半透明化未写入时读解析色
|
|
334
|
+
const resolvedBg = resolvedColor('--color-background');
|
|
335
|
+
const mHex = resolvedBg.match(/^rgba?\(([\d.]+),\s*([\d.]+),\s*([\d.]+)/);
|
|
336
|
+
const rgb = mHex
|
|
337
|
+
? [Math.round(+mHex[1]), Math.round(+mHex[2]), Math.round(+mHex[3])].join(',')
|
|
338
|
+
: (isDarkMode() ? '13,13,13' : '248,248,248');
|
|
339
|
+
// 表面半透明化:面板不透明度直接用 panelOpacity(0=全透图,1=纯色)
|
|
340
|
+
const panelMix = Math.round(panelOpacity * 100);
|
|
341
|
+
if (panelMix < 100) {
|
|
342
|
+
for (const t of SURFACE_TOKENS) {
|
|
343
|
+
const v = resolvedColor(t);
|
|
344
|
+
if (v) {
|
|
345
|
+
root.style.setProperty(t, `color-mix(in srgb, ${v} ${panelMix}%, transparent)`);
|
|
346
|
+
injectedKeys.add(t);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
await applyRegionBackgrounds(bg, { rgb, alpha, fit: bg.fit, seq });
|
|
351
|
+
if (seq !== applySeq) return; // 分区文件夹列表等待期间配置又变了
|
|
352
|
+
if (!bgActive) return; // 只有分区背景:到此为止,html 层不设图
|
|
353
|
+
|
|
354
|
+
// html 的背景自动成为画布背景铺满视口。应用样式表把 html/body/#root 的
|
|
355
|
+
// background-* 全钉成 initial !important,这里每条都带 !important 才能赢回来。
|
|
356
|
+
// 缩放方式:cover 填满裁剪 / contain 完整显示 / fill 拉伸 / tile 原尺寸平铺
|
|
357
|
+
//(平铺时遮罩层仍整幅不重复,只让图片层重复)
|
|
358
|
+
const FIT_CSS = {
|
|
359
|
+
cover: 'background-size:100% 100%,cover !important;background-repeat:no-repeat !important;',
|
|
360
|
+
contain: 'background-size:100% 100%,contain !important;background-repeat:no-repeat !important;',
|
|
361
|
+
fill: 'background-size:100% 100%,100% 100% !important;background-repeat:no-repeat !important;',
|
|
362
|
+
tile: 'background-size:100% 100%,auto !important;background-repeat:no-repeat,repeat !important;',
|
|
363
|
+
};
|
|
364
|
+
const fitCss = FIT_CSS[bg.fit] || FIT_CSS.cover;
|
|
365
|
+
// 模糊只作用于背景层:图与遮罩画在 html::before(fixed 铺满视口、z 轴最底),
|
|
366
|
+
// filter 写在伪元素上——写在 html 自身会把整个界面子树一起糊掉。
|
|
367
|
+
// 模糊会把伪元素边缘拉开出现透明缝,按模糊半径外扩 inset 收边
|
|
368
|
+
const blurCss = blurPx > 0 ? `filter:blur(${blurPx}px);` : '';
|
|
369
|
+
const spread = blurPx > 0 ? Math.max(20, blurPx * 2) : 0;
|
|
370
|
+
const setImage = (image) => {
|
|
371
|
+
bgStyleEl.textContent =
|
|
372
|
+
`html.zcodepro-bg-on{background-color:rgb(${rgb}) !important;}` +
|
|
373
|
+
`html.zcodepro-bg-on::before{content:'' !important;position:fixed;inset:-${spread}px;z-index:-1;pointer-events:none;` +
|
|
374
|
+
`background-image:linear-gradient(rgba(${rgb},${alpha}),rgba(${rgb},${alpha})),${image} !important;` +
|
|
375
|
+
`${fitCss}background-position:center !important;${blurCss}}`;
|
|
376
|
+
root.classList.add('zcodepro-bg-on');
|
|
377
|
+
};
|
|
378
|
+
const helperUrl = liveBoot().helperUrl || HELPER_URL;
|
|
379
|
+
|
|
380
|
+
if (bg.mode === 'library') {
|
|
381
|
+
if (!bg.file) { bgStyleEl.textContent = ''; return; }
|
|
382
|
+
setImage(`url("${helperUrl}/bg/file/${encodeURIComponent(bg.file)}")`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (bg.mode === 'folder') {
|
|
386
|
+
const res = await rpc('/bg/folder/list');
|
|
387
|
+
if (seq !== applySeq) return; // 等待期间配置又变了,本次作废
|
|
388
|
+
const files = res.ok && Array.isArray(res.files) ? res.files.filter((f) => f && f.name) : [];
|
|
389
|
+
if (!files.length) {
|
|
390
|
+
bgStyleEl.textContent = '';
|
|
391
|
+
root.classList.remove('zcodepro-bg-on');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const pick = () => {
|
|
395
|
+
let idx = 0;
|
|
396
|
+
if (bg.folderRandom) idx = files.length > 1 ? Math.floor(Math.random() * files.length) : 0;
|
|
397
|
+
else { folderIdx = (folderIdx + 1) % files.length; idx = folderIdx; }
|
|
398
|
+
setImage(`url("${helperUrl}/bg/folder/file/${encodeURIComponent(files[idx].name)}")`);
|
|
399
|
+
};
|
|
400
|
+
pick();
|
|
401
|
+
const ms = (Math.max(0, Number(bg.intervalMin) || 0)) * 60000;
|
|
402
|
+
if (ms >= 30000) bgTimer = setInterval(pick, ms);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (bg.mode === 'url') {
|
|
406
|
+
const u0 = (/^https?:\/\/\S+$/i.test(bg.url || '') && !/[<>"'\\]/.test(bg.url)) ? bg.url : '';
|
|
407
|
+
if (!u0) { bgStyleEl.textContent = ''; return; }
|
|
408
|
+
const bust = () => {
|
|
409
|
+
urlTick += 1;
|
|
410
|
+
const sep = u0.includes('?') ? '&' : '?';
|
|
411
|
+
setImage(`url("${u0}${sep}_zcp=${urlTick}")`);
|
|
412
|
+
};
|
|
413
|
+
bust();
|
|
414
|
+
const ms = (Math.max(0, Number(bg.intervalMin) || 0)) * 60000;
|
|
415
|
+
if (ms >= 30000) bgTimer = setInterval(bust, ms);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// —— 分区背景:左栏 / 中栏 / 右栏各自选来源(null = 跟随全局)——
|
|
420
|
+
// 图片解析与全局同构;规则写成 CSS(元素重挂载自动重新命中),元素自身的
|
|
421
|
+
// background-color(半透明表面色)按 CSS 规则画在自身背景图之上,层级正好
|
|
422
|
+
async function applyRegionBackgrounds(bg, pre) {
|
|
423
|
+
regionStyleEl = ensureEl('__zcodepro_bg_regions__');
|
|
424
|
+
for (const t of regionTimers) clearInterval(t);
|
|
425
|
+
regionTimers = [];
|
|
426
|
+
const rules = new Map(); // region -> 规则文本(轮换时覆盖同分区旧规则,不无限追加)
|
|
427
|
+
const flush = () => { regionStyleEl.textContent = [...rules.values()].join(''); };
|
|
428
|
+
// pre.alpha = 遮罩不透明度(直接配置值),与全局同值
|
|
429
|
+
const alpha = pre ? pre.alpha : 0.82;
|
|
430
|
+
const rgb = pre ? pre.rgb : '13,13,13';
|
|
431
|
+
const FIT_CSS = {
|
|
432
|
+
cover: '100% 100%,cover', contain: '100% 100%,contain', fill: '100% 100%,100% 100%', tile: '100% 100%,auto',
|
|
433
|
+
};
|
|
434
|
+
const sizeVal = FIT_CSS[bg && bg.fit] || FIT_CSS.cover;
|
|
435
|
+
const repeatVal = bg && bg.fit === 'tile' ? 'no-repeat,repeat' : 'no-repeat';
|
|
436
|
+
const helperUrl = liveBoot().helperUrl || HELPER_URL;
|
|
437
|
+
// 网址进 CSS 前过滤引号与尖括号:url("...") 的字符串不能被截断(CSS 注入面)
|
|
438
|
+
const safeUrl = (u) => (/^https?:\/\/\S+$/i.test(u || '') && !/[<>"'\\]/.test(u) ? u : '');
|
|
439
|
+
const addRule = (region, image) => {
|
|
440
|
+
if (!image) return;
|
|
441
|
+
rules.set(region, `${REGION_SELECTORS[region]}{background-image:linear-gradient(rgba(${rgb},${alpha}),rgba(${rgb},${alpha})),${image} !important;` +
|
|
442
|
+
`background-size:${sizeVal} !important;background-position:center !important;background-repeat:${repeatVal} !important;}`);
|
|
443
|
+
};
|
|
444
|
+
const seq = applySeq;
|
|
445
|
+
for (const region of ['left', 'center', 'right']) {
|
|
446
|
+
const r = bg && bg.regions && bg.regions[region];
|
|
447
|
+
if (!r) continue;
|
|
448
|
+
if (r.mode === 'library') {
|
|
449
|
+
if (r.file) addRule(region, `url("${helperUrl}/bg/file/${encodeURIComponent(r.file)}")`);
|
|
450
|
+
} else if (r.mode === 'url') {
|
|
451
|
+
const u = safeUrl(r.url);
|
|
452
|
+
if (u) addRule(region, `url("${u}")`);
|
|
453
|
+
} else if (r.mode === 'folder' && r.folder) {
|
|
454
|
+
// 分区列自己的目录(?dir=),不用全局的
|
|
455
|
+
const res = await rpc('/bg/folder/list?dir=' + encodeURIComponent(r.folder));
|
|
456
|
+
if (seq !== applySeq) return;
|
|
457
|
+
const files = res.ok && !res.missing && Array.isArray(res.files) ? res.files.filter((f) => f && f.name) : [];
|
|
458
|
+
if (!files.length) continue;
|
|
459
|
+
const pick = () => {
|
|
460
|
+
let idx = 0;
|
|
461
|
+
if (bg.folderRandom !== false) idx = files.length > 1 ? Math.floor(Math.random() * files.length) : 0;
|
|
462
|
+
else { regionFolderIdx[region] = (regionFolderIdx[region] + 1) % files.length; idx = regionFolderIdx[region]; }
|
|
463
|
+
addRule(region, `url("${helperUrl}/bg/folder/file/${encodeURIComponent(files[idx].name)}")`);
|
|
464
|
+
flush();
|
|
465
|
+
};
|
|
466
|
+
pick();
|
|
467
|
+
const ms = (Math.max(0, Number(bg.intervalMin) || 0)) * 60000;
|
|
468
|
+
if (ms >= 30000) regionTimers.push(setInterval(pick, ms));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
flush();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function applyAll(theme) {
|
|
475
|
+
if (theme && typeof theme === 'object') currentTheme = theme;
|
|
476
|
+
// 先皮肤后背景:半透明化读的是皮肤应用后的计算值
|
|
477
|
+
applySkin(currentTheme.skin || 'default', currentTheme.custom);
|
|
478
|
+
void applyBackground(currentTheme.bg);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function startTheme() {
|
|
482
|
+
if (!document.head && !document.documentElement) return;
|
|
483
|
+
applyAll({ skin: 'default', custom: null, bg: { mode: 'none', maskOpacity: 0.82, blurPx: 0, panelOpacity: 0.3 } });
|
|
484
|
+
void getConfig().then((cfg) => applyAll(cfg.theme)).catch(() => { /* 读不到配置保持应用默认 */ });
|
|
485
|
+
window.addEventListener('zcodepro:config-changed', () => {
|
|
486
|
+
void getConfig(true).then((cfg) => applyAll(cfg.theme)).catch(() => { /* ignore */ });
|
|
487
|
+
});
|
|
488
|
+
// 跟随系统的明暗切换只改 root class(theme-zai-light ↔ theme-zai-dark):
|
|
489
|
+
// 皮肤变量由 CSS 选择器自动换挡,背景遮罩色与半透明混色是 JS 算的,需重算
|
|
490
|
+
if (!classObserver) {
|
|
491
|
+
lastModeSig = isDarkMode() ? 'd' : 'l';
|
|
492
|
+
classObserver = new MutationObserver(() => {
|
|
493
|
+
const sig = isDarkMode() ? 'd' : 'l';
|
|
494
|
+
if (sig === lastModeSig) return; // 自身写入的 zcodepro-* class,不是明暗切换
|
|
495
|
+
lastModeSig = sig;
|
|
496
|
+
applyAll(currentTheme);
|
|
497
|
+
});
|
|
498
|
+
classObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
499
|
+
}
|
|
500
|
+
}
|
package/src/inject/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { startPluginCardRetry } from './features/plugin-card-retry.js';
|
|
|
22
22
|
import { startToolbarIcons } from './features/toolbar-icons.js';
|
|
23
23
|
import { startPinnedCollapse } from './features/pinned-collapse.js';
|
|
24
24
|
import { startStyleAdjustments } from './features/styles.js';
|
|
25
|
+
import { startTheme } from './features/theme.js';
|
|
25
26
|
import { startPluginUpdateCheck } from './features/plugin-updates.js';
|
|
26
27
|
import { ensureStyle } from './ui.js';
|
|
27
28
|
|
|
@@ -70,6 +71,7 @@ import { ensureStyle } from './ui.js';
|
|
|
70
71
|
try { startTurnUsage(); } catch { /* ignore */ }
|
|
71
72
|
try { startPinnedCollapse(); } catch { /* ignore */ }
|
|
72
73
|
try { startStyleAdjustments(); } catch { /* ignore */ }
|
|
74
|
+
try { startTheme(); } catch { /* ignore */ }
|
|
73
75
|
try { startSettingsEntry(); } catch { /* ignore */ }
|
|
74
76
|
// 插件更新检查慢(可能 git pull),异步进行,不阻塞其他功能
|
|
75
77
|
try { void startPluginUpdateCheck(); } catch { /* ignore */ }
|
package/src/inject/ui.js
CHANGED
|
@@ -26,8 +26,10 @@ export function showToast(text, kind = 'info') {
|
|
|
26
26
|
// 注意:绝不能用 bg-surface 做面板底色——该 token 在应用里是 ~3% 不透明度的着色层(#0d0d0d08),
|
|
27
27
|
// 会导致整卡透明。关键视觉另在 ensureStyle 里以自有样式表兜底(ID 选择器 + 主题变量 + 字面量回退),
|
|
28
28
|
// 应用 Tailwind 类随版本更新变动时遮罩/面板仍能正确绘制。
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// 垂直方向 items-start + 卡片 margin-top:切 tab 内容高度变化时卡片顶边不动
|
|
30
|
+
// (此前 items-center 上下双向伸缩,顶部随内容高度跳动,顶部 tab 没法稳着点)
|
|
31
|
+
const overlayClass = 'fixed inset-0 isolate z-50 flex items-start justify-center bg-black/60 supports-backdrop-filter:backdrop-blur-xs duration-100 p-4 platform-linux-desktop:top-12';
|
|
32
|
+
const overlayClassBare = 'fixed inset-0 isolate z-50 flex items-start justify-center duration-100 p-4 platform-linux-desktop:top-12';
|
|
31
33
|
const contentClass = 'w-full sm:max-w-md rounded-2xl border-none bg-popover/98 p-5 text-ui-base/relaxed text-foreground ring-border shadow-2xl';
|
|
32
34
|
|
|
33
35
|
// overlay: 'dim'(默认,半透明遮罩)| 'none'(透明遮罩)——设置弹窗用 'none',方便边调样式边看会话。
|
|
@@ -59,6 +61,7 @@ export function openDialog({ title, description, onMount, onClose, width = 'sm:m
|
|
|
59
61
|
role: 'dialog',
|
|
60
62
|
'aria-modal': 'true',
|
|
61
63
|
class: contentClass.replace('sm:max-w-md', width) + ' zcodepro-card',
|
|
64
|
+
style: 'margin-top:12vh;max-height:calc(88vh - 3rem);overflow-y:auto;',
|
|
62
65
|
});
|
|
63
66
|
const overlayEl = h('div', {
|
|
64
67
|
'data-overlay': overlay,
|
|
@@ -453,6 +456,32 @@ export function ensureStyle() {
|
|
|
453
456
|
color: var(--color-foreground, #111);
|
|
454
457
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
|
|
455
458
|
}
|
|
459
|
+
/* 主题(样式 → 主题):皮肤卡片(色块预览 + 名称)与背景缩略图 */
|
|
460
|
+
.zcodepro-skin-card {
|
|
461
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
462
|
+
padding: 8px; border: 1px solid var(--color-card-border, #8883);
|
|
463
|
+
border-radius: 10px; background: transparent; cursor: pointer; text-align: left;
|
|
464
|
+
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
465
|
+
}
|
|
466
|
+
.zcodepro-skin-card:hover { background-color: color-mix(in oklab, var(--color-foreground, #888) 6%, transparent); }
|
|
467
|
+
.zcodepro-skin-card[data-state="active"] { border-color: var(--color-primary, #333); }
|
|
468
|
+
.zcodepro-skin-swatches { display: flex; height: 18px; border-radius: 5px; overflow: hidden; }
|
|
469
|
+
.zcodepro-skin-swatches > span { flex: 1; }
|
|
470
|
+
.zcodepro-bg-card {
|
|
471
|
+
position: relative; width: 100%; aspect-ratio: 16 / 9;
|
|
472
|
+
border-radius: 8px; border: 2px solid transparent; padding: 0;
|
|
473
|
+
background-size: cover; background-position: center; cursor: pointer; overflow: hidden;
|
|
474
|
+
}
|
|
475
|
+
.zcodepro-bg-card:hover { filter: brightness(1.15); }
|
|
476
|
+
.zcodepro-bg-card[data-state="active"] { border-color: var(--color-primary, #333); }
|
|
477
|
+
.zcodepro-bg-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
478
|
+
.zcodepro-bg-del {
|
|
479
|
+
position: absolute; top: 4px; right: 4px; width: 20px; height: 20px;
|
|
480
|
+
border-radius: 6px; border: none; cursor: pointer; font-size: 11px; line-height: 1;
|
|
481
|
+
color: #fff; background: rgba(0, 0, 0, 0.55);
|
|
482
|
+
display: flex; align-items: center; justify-content: center;
|
|
483
|
+
}
|
|
484
|
+
.zcodepro-bg-del:hover { background: rgba(220, 38, 38, 0.85); }
|
|
456
485
|
/* 紧凑小按钮:几何/配色写入自有规则并取主题变量(高度=24px,与标签页一致) */
|
|
457
486
|
.zcodepro-btn-sm {
|
|
458
487
|
display: inline-flex;
|
|
@@ -540,6 +569,8 @@ export function ensureStyle() {
|
|
|
540
569
|
-webkit-appearance: none;
|
|
541
570
|
appearance: none;
|
|
542
571
|
width: 100%;
|
|
572
|
+
/* 不给 padding:padding 会缩小 thumb 的可滑区间(拖不到最左/最右)。
|
|
573
|
+
轨道起点对齐由弹窗里的固定标签列宽负责 */
|
|
543
574
|
height: 4px;
|
|
544
575
|
border-radius: 9999px;
|
|
545
576
|
background: color-mix(in oklab, var(--color-foreground, #888) 18%, transparent);
|