dsh-theme-palette 1.3.2 → 1.4.0
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/CHANGELOG.md +341 -0
- package/README.md +51 -597
- package/client.js +246 -35
- package/docs/DESIGN.md +228 -0
- package/package.json +4 -2
- package/tools/preview-settings.mjs +7 -2
package/client.js
CHANGED
|
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const React = require('react')
|
|
28
28
|
const h = React.createElement
|
|
29
29
|
|
|
30
|
-
const PLUGIN_VERSION = '1.
|
|
30
|
+
const PLUGIN_VERSION = '1.4.0'
|
|
31
31
|
const STORE_KEY = 'dsh-theme-palette.v1'
|
|
32
32
|
const OVERRIDE_SOURCE = 'dsh-theme-palette'
|
|
33
33
|
|
|
@@ -977,26 +977,63 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
/* ================= 文案 ================= */
|
|
980
|
-
|
|
980
|
+
/* ================= 多语言 ================= */
|
|
981
|
+
/**
|
|
982
|
+
* 语言来源优先级:用户手选 > 宿主 documentElement.lang(locale 服务写的)> navigator.language。
|
|
983
|
+
* 宿主本身只有 zh/en 两套,但插件设置页可以多给几种;「自动」永远跟随宿主。
|
|
984
|
+
*/
|
|
985
|
+
const LOCALE_KEY = 'dsh-theme-palette.lang.v1'
|
|
986
|
+
const LOCALES = ['zh-CN', 'zh-TW', 'en', 'ja']
|
|
987
|
+
// 语言名用它自己的文字写(中/繁/EN/JA),只有「自动」需要按界面语言翻译。
|
|
988
|
+
const LANG_OPTIONS = [
|
|
989
|
+
{ id: 'auto' },
|
|
990
|
+
{ id: 'zh-CN', label: '中' },
|
|
991
|
+
{ id: 'zh-TW', label: '繁' },
|
|
992
|
+
{ id: 'en', label: 'EN' },
|
|
993
|
+
{ id: 'ja', label: 'JA' },
|
|
994
|
+
]
|
|
995
|
+
|
|
996
|
+
function readLangPref() {
|
|
997
|
+
try {
|
|
998
|
+
const raw = localStorage.getItem(LOCALE_KEY)
|
|
999
|
+
if (raw === 'auto' || LOCALES.indexOf(raw) !== -1) return raw
|
|
1000
|
+
} catch (err) { /* 退回自动 */ }
|
|
1001
|
+
return 'auto'
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function writeLangPref(id) {
|
|
1005
|
+
try { localStorage.setItem(LOCALE_KEY, id) } catch (err) { /* 忽略 */ }
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function hostLocale() {
|
|
981
1009
|
try {
|
|
982
|
-
const lang = document.documentElement.lang || navigator.language || ''
|
|
983
|
-
|
|
984
|
-
|
|
1010
|
+
const lang = (document.documentElement.lang || '').trim() || navigator.language || ''
|
|
1011
|
+
if (/^zh-(hant|tw|hk|mo)/i.test(lang)) return 'zh-TW'
|
|
1012
|
+
if (/^zh/i.test(lang)) return 'zh-CN'
|
|
1013
|
+
if (/^ja/i.test(lang)) return 'ja'
|
|
1014
|
+
if (/^en/i.test(lang)) return 'en'
|
|
1015
|
+
return null
|
|
1016
|
+
} catch (err) { return null }
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function pickLocale(pref) {
|
|
1020
|
+
if (pref !== 'auto') return pref
|
|
1021
|
+
return hostLocale() || 'zh-CN'
|
|
985
1022
|
}
|
|
986
1023
|
|
|
987
1024
|
const TEXT = {
|
|
988
|
-
zh: {
|
|
1025
|
+
'zh-CN': {
|
|
989
1026
|
nav: '主题色',
|
|
990
1027
|
hint: '配色与明暗模式独立;切换后立即生效并保存在 localStorage。',
|
|
991
1028
|
md: 'Markdown 强调',
|
|
992
|
-
mdHint: '
|
|
1029
|
+
mdHint: '标题与加粗用「正文色混强调色」,比正文更有色;关掉则与正文同色。',
|
|
993
1030
|
control: '开关 / 勾选',
|
|
994
|
-
controlHint: '
|
|
1031
|
+
controlHint: '宿主的开关、复选框、单选都压在近黑/近白上;开启后改用强调色。',
|
|
995
1032
|
scroll: '滚动条',
|
|
996
|
-
scrollHint: '
|
|
1033
|
+
scrollHint: '默认中性灰;「跟随」上配色色相,「强调」用强调色。明暗关系不变。',
|
|
997
1034
|
scrollHintMono: '当前是黑白配色,没有色相可跟随,滚动条保持中性灰。',
|
|
998
1035
|
rightbar: '右侧栏底色',
|
|
999
|
-
rightbarHint: '
|
|
1036
|
+
rightbarHint: '右栏本来没有自己的背景,露的是会话区画布色。',
|
|
1000
1037
|
advanced: '更多选项',
|
|
1001
1038
|
textSub: '正文与强调的着色程度',
|
|
1002
1039
|
sidebarSub: '相对会话区的层次',
|
|
@@ -1004,14 +1041,50 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1004
1041
|
mdSub: '标题与加粗是否着色',
|
|
1005
1042
|
scrollSub: '滚动条与滑轨的色相',
|
|
1006
1043
|
controlSub: '开关与复选框是否跟强调色',
|
|
1044
|
+
langSub: '自动跟随宿主界面语言',
|
|
1007
1045
|
surface: '面板表面',
|
|
1008
|
-
surfaceHint: '
|
|
1046
|
+
surfaceHint: '决定侧栏与右栏相对会话区是「浮起」还是「凹陷」。',
|
|
1009
1047
|
ok: '✓ 配色已生效',
|
|
1010
1048
|
bad: '⚠ 部分 token 未生效(宿主可能改名),请查看控制台',
|
|
1049
|
+
healed: '配色层被宿主丢过一次,已自动恢复 ',
|
|
1050
|
+
healedSuffix: ' 次',
|
|
1011
1051
|
text: '强度',
|
|
1012
|
-
textHint: '
|
|
1052
|
+
textHint: '让正文/次级文字带配色色味;三档都满足 AA。',
|
|
1013
1053
|
sidebar: '左侧栏底色',
|
|
1014
|
-
sidebarHint: '
|
|
1054
|
+
sidebarHint: '让侧栏比会话区更浅(或更深),做出区块层次。',
|
|
1055
|
+
lang: '语言',
|
|
1056
|
+
},
|
|
1057
|
+
'zh-TW': {
|
|
1058
|
+
nav: '主題色',
|
|
1059
|
+
hint: '配色與明暗模式獨立;切換後立即生效並存在 localStorage。',
|
|
1060
|
+
md: 'Markdown 強調',
|
|
1061
|
+
mdHint: '標題與粗體用「本文色混強調色」,比本文更有色;關掉則與本文同色。',
|
|
1062
|
+
control: '開關 / 勾選',
|
|
1063
|
+
controlHint: '宿主開關、核取方塊、單選都用近黑/近白;開啟後改用強調色。',
|
|
1064
|
+
scroll: '捲軸',
|
|
1065
|
+
scrollHint: '預設中性灰;「跟隨」上配色色相,「強調」用強調色。明暗關係不變。',
|
|
1066
|
+
scrollHintMono: '目前是黑白配色,沒有色相可跟隨,捲軸保持中性灰。',
|
|
1067
|
+
rightbar: '右側欄底色',
|
|
1068
|
+
rightbarHint: '右欄本來沒有自己的背景,露的是會話區畫布色。',
|
|
1069
|
+
advanced: '更多選項',
|
|
1070
|
+
textSub: '本文與強調的著色程度',
|
|
1071
|
+
sidebarSub: '相對會話區的層次',
|
|
1072
|
+
rightbarSub: '面板底色',
|
|
1073
|
+
mdSub: '標題與粗體是否著色',
|
|
1074
|
+
scrollSub: '捲軸與軌道的色相',
|
|
1075
|
+
controlSub: '開關與核取方塊是否跟強調色',
|
|
1076
|
+
langSub: '自動跟隨宿主介面語言',
|
|
1077
|
+
surface: '面板表面',
|
|
1078
|
+
surfaceHint: '決定側欄與右欄相對會話區是「浮起」還是「凹陷」。',
|
|
1079
|
+
ok: '✓ 配色已生效',
|
|
1080
|
+
bad: '⚠ 部分 token 未生效(宿主可能改名),請看主控台',
|
|
1081
|
+
healed: '配色層被宿主丟棄過一次,已自動恢復 ',
|
|
1082
|
+
healedSuffix: ' 次',
|
|
1083
|
+
text: '強度',
|
|
1084
|
+
textHint: '讓本文/次級文字帶配色色味;三檔都符合 AA。',
|
|
1085
|
+
sidebar: '左側欄底色',
|
|
1086
|
+
sidebarHint: '讓側欄比會話區更淺(或更深),做出區塊層次。',
|
|
1087
|
+
lang: '語言',
|
|
1015
1088
|
},
|
|
1016
1089
|
en: {
|
|
1017
1090
|
nav: 'Color theme',
|
|
@@ -1019,12 +1092,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1019
1092
|
md: 'Markdown emphasis',
|
|
1020
1093
|
mdHint: 'Headings and bold mix the accent into body colour; off keeps them identical to body text.',
|
|
1021
1094
|
control: 'Switches / checks',
|
|
1022
|
-
controlHint: 'Switches, checkboxes and radios
|
|
1095
|
+
controlHint: 'Switches, checkboxes and radios use near-black/white in the host. Turn this on to use the accent colour.',
|
|
1023
1096
|
scroll: 'Scrollbars',
|
|
1024
|
-
scrollHint: '
|
|
1097
|
+
scrollHint: 'Neutral grey by default. Tint follows the palette hue; Accent uses the accent colour. Lightness is unchanged.',
|
|
1025
1098
|
scrollHintMono: 'Monochrome has no hue to follow; scrollbars stay neutral.',
|
|
1026
1099
|
rightbar: 'Right panel surface',
|
|
1027
|
-
rightbarHint: 'The right panel has no fill of its own; it shows the canvas.
|
|
1100
|
+
rightbarHint: 'The right panel has no fill of its own; it shows the canvas.',
|
|
1028
1101
|
advanced: 'More options',
|
|
1029
1102
|
textSub: 'How strongly text follows the palette',
|
|
1030
1103
|
sidebarSub: 'Layer relative to the conversation',
|
|
@@ -1032,14 +1105,98 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1032
1105
|
mdSub: 'Tint headings and bold',
|
|
1033
1106
|
scrollSub: 'Hue of the scrollbar',
|
|
1034
1107
|
controlSub: 'Switches and checkboxes',
|
|
1108
|
+
langSub: 'Follow the host interface language',
|
|
1035
1109
|
surface: 'Panel surfaces',
|
|
1036
|
-
surfaceHint: 'Whether the sidebars sit above or below the conversation area
|
|
1110
|
+
surfaceHint: 'Whether the sidebars sit above or below the conversation area.',
|
|
1037
1111
|
ok: '✓ Palette applied',
|
|
1038
1112
|
bad: '⚠ Some tokens did not apply (host may have renamed them); check the console',
|
|
1113
|
+
healed: 'Palette layer was dropped by the host; restored ',
|
|
1114
|
+
healedSuffix: 'x',
|
|
1039
1115
|
text: 'Strength',
|
|
1040
|
-
textHint: 'Tints body and secondary text toward the palette; all three levels
|
|
1116
|
+
textHint: 'Tints body and secondary text toward the palette; all three levels meet AA.',
|
|
1041
1117
|
sidebar: 'Sidebar surface',
|
|
1042
|
-
sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area
|
|
1118
|
+
sidebarHint: 'Makes the sidebar lighter (or deeper) than the conversation area.',
|
|
1119
|
+
lang: 'Language',
|
|
1120
|
+
},
|
|
1121
|
+
ja: {
|
|
1122
|
+
nav: 'カラーテーマ',
|
|
1123
|
+
hint: 'ライト/ダークとは独立です。変更は即時反映され、localStorage に保存されます。',
|
|
1124
|
+
md: 'Markdown の強調',
|
|
1125
|
+
mdHint: '見出しと太字にアクセント色を混ぜます。オフにすると本文と同じ色になります。',
|
|
1126
|
+
control: 'スイッチ / チェック',
|
|
1127
|
+
controlHint: 'ホストのスイッチ・チェックボックスはほぼ白黒です。オンにするとアクセント色になります。',
|
|
1128
|
+
scroll: 'スクロールバー',
|
|
1129
|
+
scrollHint: '既定はニュートラルグレー。「追従」でテーマの色相、「アクセント」でアクセント色になります。',
|
|
1130
|
+
scrollHintMono: 'モノクロには色相がないため、スクロールバーはグレーのままです。',
|
|
1131
|
+
rightbar: '右パネルの背景',
|
|
1132
|
+
rightbarHint: '右パネルは独自の背景を持たず、キャンバス色が見えています。',
|
|
1133
|
+
advanced: 'その他のオプション',
|
|
1134
|
+
textSub: '本文と強調の色づき具合',
|
|
1135
|
+
sidebarSub: '会話エリアとの段差',
|
|
1136
|
+
rightbarSub: 'パネルの背景',
|
|
1137
|
+
mdSub: '見出しと太字を色づけする',
|
|
1138
|
+
scrollSub: 'スクロールバーの色相',
|
|
1139
|
+
controlSub: 'スイッチとチェックの色',
|
|
1140
|
+
langSub: 'ホストの表示言語に合わせます',
|
|
1141
|
+
surface: 'パネル表面',
|
|
1142
|
+
surfaceHint: 'サイドバーが会話エリアより浮くか沈むかを決めます。',
|
|
1143
|
+
ok: '✓ 配色を適用しました',
|
|
1144
|
+
bad: '⚠ 一部の token が適用されていません(ホスト側で改名された可能性)。コンソールを確認してください',
|
|
1145
|
+
healed: '配色レイヤーがホストに破棄されたため、自動で復元しました(',
|
|
1146
|
+
healedSuffix: ' 回)',
|
|
1147
|
+
text: '強さ',
|
|
1148
|
+
textHint: '本文と副次テキストをテーマ色に寄せます。3 段階とも AA を満たします。',
|
|
1149
|
+
sidebar: '左サイドバーの背景',
|
|
1150
|
+
sidebarHint: 'サイドバーを会話エリアより明るく(または暗く)して段差を作ります。',
|
|
1151
|
+
lang: '言語',
|
|
1152
|
+
},
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/** 配色名与各档位的显示名:id 不变,只换文案。 */
|
|
1156
|
+
const LABELS = {
|
|
1157
|
+
'zh-CN': {
|
|
1158
|
+
scheme: { mono: '黑白', warm: '暖砂', indigo: '靛蓝', moss: '苔绿' },
|
|
1159
|
+
desc: { mono: '无色调 · 默认', warm: '米白 + 棕调', indigo: '冷白 + 蓝调', moss: '浅灰绿 + 绿调' },
|
|
1160
|
+
text: { neutral: '静', tinted: '标准', deep: '浓' },
|
|
1161
|
+
sidebar: { light: '更浅', match: '同色', deep: '更深' },
|
|
1162
|
+
rightbar: { sidebar: '同左栏', canvas: '跟随画布' },
|
|
1163
|
+
scroll: { neutral: '中性', tinted: '跟随', accent: '强调' },
|
|
1164
|
+
control: { accent: '主题色', mono: '黑白' },
|
|
1165
|
+
md: { on: '着色', off: '同正文' },
|
|
1166
|
+
lang: { auto: '自动' },
|
|
1167
|
+
},
|
|
1168
|
+
'zh-TW': {
|
|
1169
|
+
scheme: { mono: '黑白', warm: '暖砂', indigo: '靛藍', moss: '苔綠' },
|
|
1170
|
+
desc: { mono: '無色調 · 預設', warm: '米白 + 棕調', indigo: '冷白 + 藍調', moss: '淺灰綠 + 綠調' },
|
|
1171
|
+
text: { neutral: '靜', tinted: '標準', deep: '濃' },
|
|
1172
|
+
sidebar: { light: '更淺', match: '同色', deep: '更深' },
|
|
1173
|
+
rightbar: { sidebar: '同左欄', canvas: '跟隨畫布' },
|
|
1174
|
+
scroll: { neutral: '中性', tinted: '跟隨', accent: '強調' },
|
|
1175
|
+
control: { accent: '主題色', mono: '黑白' },
|
|
1176
|
+
md: { on: '著色', off: '同本文' },
|
|
1177
|
+
lang: { auto: '自動' },
|
|
1178
|
+
},
|
|
1179
|
+
en: {
|
|
1180
|
+
scheme: { mono: 'Mono', warm: 'Warm sand', indigo: 'Indigo', moss: 'Moss' },
|
|
1181
|
+
desc: { mono: 'Neutral · default', warm: 'Cream + brown', indigo: 'Cool white + blue', moss: 'Grey-green' },
|
|
1182
|
+
text: { neutral: 'Quiet', tinted: 'Standard', deep: 'Strong' },
|
|
1183
|
+
sidebar: { light: 'Lighter', match: 'Same', deep: 'Deeper' },
|
|
1184
|
+
rightbar: { sidebar: 'Match sidebar', canvas: 'Follow canvas' },
|
|
1185
|
+
scroll: { neutral: 'Neutral', tinted: 'Tint', accent: 'Accent' },
|
|
1186
|
+
control: { accent: 'Accent', mono: 'Mono' },
|
|
1187
|
+
md: { on: 'Tinted', off: 'Body colour' },
|
|
1188
|
+
lang: { auto: 'Auto' },
|
|
1189
|
+
},
|
|
1190
|
+
ja: {
|
|
1191
|
+
scheme: { mono: 'モノクロ', warm: 'ウォームサンド', indigo: 'インディゴ', moss: 'モスグリーン' },
|
|
1192
|
+
desc: { mono: '色なし · 既定', warm: 'クリーム + ブラウン', indigo: 'クールホワイト + ブルー', moss: 'グレーグリーン' },
|
|
1193
|
+
text: { neutral: '控えめ', tinted: '標準', deep: '強め' },
|
|
1194
|
+
sidebar: { light: '明るく', match: '同じ', deep: '暗く' },
|
|
1195
|
+
rightbar: { sidebar: 'サイドバーに合わせる', canvas: 'キャンバスに従う' },
|
|
1196
|
+
scroll: { neutral: 'ニュートラル', tinted: '追従', accent: 'アクセント' },
|
|
1197
|
+
control: { accent: 'アクセント', mono: 'モノクロ' },
|
|
1198
|
+
md: { on: '色づけ', off: '本文と同じ' },
|
|
1199
|
+
lang: { auto: '自動' },
|
|
1043
1200
|
},
|
|
1044
1201
|
}
|
|
1045
1202
|
|
|
@@ -1054,9 +1211,12 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1054
1211
|
const mdMode = React.useSyncExternalStore(props.subscribeMd, props.getMdMode)
|
|
1055
1212
|
const health = React.useSyncExternalStore(props.subscribeHealth, props.getHealth)
|
|
1056
1213
|
const [advanced, setAdvanced] = React.useState(false)
|
|
1057
|
-
const
|
|
1214
|
+
const locale = React.useSyncExternalStore(props.subscribeLocale, props.getLocale)
|
|
1215
|
+
const langPref = React.useSyncExternalStore(props.subscribeLocale, props.getLangPref)
|
|
1216
|
+
const txt = TEXT[locale] || TEXT['zh-CN']
|
|
1217
|
+
const lb = LABELS[locale] || LABELS['zh-CN']
|
|
1058
1218
|
|
|
1059
|
-
const seg = (modes, value, onPick) => h(
|
|
1219
|
+
const seg = (modes, value, onPick, labels) => h(
|
|
1060
1220
|
'div',
|
|
1061
1221
|
{ className: 'dsh-tp-seg', role: 'group' },
|
|
1062
1222
|
modes.map((mode) => h(
|
|
@@ -1068,7 +1228,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1068
1228
|
'aria-pressed': mode.id === value,
|
|
1069
1229
|
onClick: () => onPick(mode.id),
|
|
1070
1230
|
},
|
|
1071
|
-
mode.label,
|
|
1231
|
+
(labels && labels[mode.id]) || mode.label,
|
|
1072
1232
|
)),
|
|
1073
1233
|
)
|
|
1074
1234
|
|
|
@@ -1101,8 +1261,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1101
1261
|
h('span', { className: 'dsh-tp-thumb-side' }),
|
|
1102
1262
|
h('span', { className: 'dsh-tp-thumb-main' }, h('b', null), h('b', null), h('b', null))),
|
|
1103
1263
|
h('span', { className: 'dsh-tp-card-meta' },
|
|
1104
|
-
h('span', { className: 'dsh-tp-card-name' }, scheme.label),
|
|
1105
|
-
h('span', { className: 'dsh-tp-card-desc' }, scheme.desc)),
|
|
1264
|
+
h('span', { className: 'dsh-tp-card-name' }, lb.scheme[scheme.id] || scheme.label),
|
|
1265
|
+
h('span', { className: 'dsh-tp-card-desc' }, lb.desc[scheme.id] || scheme.desc)),
|
|
1106
1266
|
h('span', { className: 'dsh-tp-check', 'aria-hidden': 'true' }, '✓'),
|
|
1107
1267
|
)
|
|
1108
1268
|
}
|
|
@@ -1115,14 +1275,14 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1115
1275
|
h('span', { className: 'dsh-tp-badge' }, 'v' + PLUGIN_VERSION)),
|
|
1116
1276
|
h('p', { className: 'dsh-tp-sub' }, txt.hint),
|
|
1117
1277
|
h('div', { className: 'dsh-tp-grid' }, SCHEMES.map(card)),
|
|
1118
|
-
row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode)),
|
|
1119
|
-
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode)),
|
|
1120
|
-
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode)),
|
|
1278
|
+
row(txt.text, txt.textSub, seg(TEXT_MODES, textMode, props.setTextMode, lb.text)),
|
|
1279
|
+
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode, lb.sidebar)),
|
|
1280
|
+
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode, lb.rightbar)),
|
|
1121
1281
|
health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
|
|
1122
1282
|
health && health.ok && health.reapplies > 0 && h(
|
|
1123
1283
|
'p',
|
|
1124
1284
|
{ className: 'dsh-tp-status' },
|
|
1125
|
-
|
|
1285
|
+
txt.healed + health.reapplies + txt.healedSuffix,
|
|
1126
1286
|
),
|
|
1127
1287
|
h('button', {
|
|
1128
1288
|
type: 'button',
|
|
@@ -1133,9 +1293,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1133
1293
|
advanced && h(
|
|
1134
1294
|
'div',
|
|
1135
1295
|
{ className: 'dsh-tp-adv' },
|
|
1136
|
-
row(txt.
|
|
1137
|
-
row(txt.
|
|
1138
|
-
row(txt.
|
|
1296
|
+
row(txt.lang, txt.langSub, seg(LANG_OPTIONS, langPref, props.setLangPref, lb.lang)),
|
|
1297
|
+
row(txt.md, txt.mdSub, seg(MD_MODES, mdMode, props.setMdMode, lb.md)),
|
|
1298
|
+
row(txt.scroll, current === 'mono' ? txt.scrollHintMono : txt.scrollSub, seg(SCROLL_MODES, scrollMode, props.setScrollMode, lb.scroll)),
|
|
1299
|
+
row(txt.control, txt.controlSub, seg(CONTROL_MODES, controlMode, props.setControlMode, lb.control)),
|
|
1139
1300
|
),
|
|
1140
1301
|
)
|
|
1141
1302
|
}
|
|
@@ -1162,6 +1323,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1162
1323
|
let scrollMode = readScrollStored()
|
|
1163
1324
|
let controlMode = readControlStored()
|
|
1164
1325
|
let mdMode = readMdStored()
|
|
1326
|
+
let langPref = readLangPref()
|
|
1327
|
+
const localeListeners = new Set()
|
|
1328
|
+
const currentLocale = () => pickLocale(langPref)
|
|
1329
|
+
const emitLocale = () => { for (const fn of localeListeners) { try { fn() } catch (err) { /* 忽略 */ } } }
|
|
1165
1330
|
const listeners = new Set()
|
|
1166
1331
|
const sidebarListeners = new Set()
|
|
1167
1332
|
const textListeners = new Set()
|
|
@@ -1250,9 +1415,18 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1250
1415
|
* 这一层就没了 —— 表现正是"配色自己跳回默认",而 localStorage 里的选择还在。
|
|
1251
1416
|
* 这里定期(以及窗口重新可见/获焦时)回读 body:token 不在了就把最后一层重放。
|
|
1252
1417
|
*/
|
|
1418
|
+
// 连续失败要退避:宿主真把 token 改名时,每 4 秒重放一次既刷日志、又会堆 override 层。
|
|
1419
|
+
const HEAL_BACKOFF = [4000, 15000, 60000, 180000, 600000]
|
|
1420
|
+
let healFails = 0
|
|
1421
|
+
let lastHealAt = 0
|
|
1422
|
+
let healDisabled = false
|
|
1423
|
+
|
|
1253
1424
|
const ensureApplied = (reason) => {
|
|
1254
|
-
if (lastTokens === null) return
|
|
1425
|
+
if (lastTokens === null || healDisabled) return
|
|
1255
1426
|
if (Date.now() - lastAppliedAt < 900) return
|
|
1427
|
+
const wait = HEAL_BACKOFF[Math.min(healFails, HEAL_BACKOFF.length - 1)]
|
|
1428
|
+
if (healFails > 0 && Date.now() - lastHealAt < wait) return
|
|
1429
|
+
lastHealAt = Date.now()
|
|
1256
1430
|
let missing = false
|
|
1257
1431
|
try {
|
|
1258
1432
|
const style = getComputedStyle(document.body)
|
|
@@ -1263,7 +1437,16 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1263
1437
|
} catch (err) {
|
|
1264
1438
|
missing = true
|
|
1265
1439
|
}
|
|
1266
|
-
if (!missing)
|
|
1440
|
+
if (!missing) {
|
|
1441
|
+
healFails = 0
|
|
1442
|
+
return
|
|
1443
|
+
}
|
|
1444
|
+
if (healFails >= HEAL_BACKOFF.length) {
|
|
1445
|
+
healDisabled = true
|
|
1446
|
+
console.error('[dsh-theme-palette] 配色层反复失效(' + reapplyCount + ' 次),已停止自动重放;请检查宿主 token 是否改名')
|
|
1447
|
+
return
|
|
1448
|
+
}
|
|
1449
|
+
healFails += 1
|
|
1267
1450
|
reapplyCount += 1
|
|
1268
1451
|
console.info('[dsh-theme-palette] 配色层不见了(' + reason + '),已重放第 ' + reapplyCount + ' 次')
|
|
1269
1452
|
applyAll('self-heal:' + reason)
|
|
@@ -1340,6 +1523,24 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1340
1523
|
applyAll()
|
|
1341
1524
|
emitMd()
|
|
1342
1525
|
}
|
|
1526
|
+
const getLangPref = () => langPref
|
|
1527
|
+
const getLocale = () => currentLocale()
|
|
1528
|
+
const subscribeLocale = (fn) => {
|
|
1529
|
+
localeListeners.add(fn)
|
|
1530
|
+
return () => { localeListeners.delete(fn) }
|
|
1531
|
+
}
|
|
1532
|
+
const setLangPref = (id) => {
|
|
1533
|
+
if (id !== 'auto' && LOCALES.indexOf(id) === -1) return
|
|
1534
|
+
langPref = id
|
|
1535
|
+
writeLangPref(id)
|
|
1536
|
+
emitLocale()
|
|
1537
|
+
}
|
|
1538
|
+
// 宿主切换界面语言时(locale 服务会改 documentElement.lang),设置页跟着重渲染。
|
|
1539
|
+
let langObserver = null
|
|
1540
|
+
try {
|
|
1541
|
+
langObserver = new MutationObserver(() => emitLocale())
|
|
1542
|
+
langObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })
|
|
1543
|
+
} catch (err) { /* 没有 MutationObserver 就只在打开设置页时读一次 */ }
|
|
1343
1544
|
|
|
1344
1545
|
// 开发期自动化接口:截图回归要从外部驱动「换配色 / 换明暗」,而这两件事
|
|
1345
1546
|
// 平时只能点界面。暴露一组 setter,让 CDP 的 Runtime.evaluate 能直接调用。
|
|
@@ -1354,6 +1555,8 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1354
1555
|
setScrollMode,
|
|
1355
1556
|
setControlMode,
|
|
1356
1557
|
getPreference: () => theme.getTheme().preference,
|
|
1558
|
+
setLangPref,
|
|
1559
|
+
locales: LOCALES,
|
|
1357
1560
|
setTheme: (id) => theme.setTheme(id),
|
|
1358
1561
|
schemes: SCHEMES.map((s) => s.id),
|
|
1359
1562
|
})
|
|
@@ -1375,7 +1578,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1375
1578
|
window.addEventListener('pageshow', healOnShow)
|
|
1376
1579
|
} catch (err) { /* 无 DOM 时跳过 */ }
|
|
1377
1580
|
try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
|
|
1378
|
-
try {
|
|
1581
|
+
try {
|
|
1582
|
+
healTimer = setInterval(() => ensureApplied('interval'), 4000)
|
|
1583
|
+
if (healTimer && typeof healTimer.unref === 'function') healTimer.unref()
|
|
1584
|
+
} catch (err) { /* 无定时器环境 */ }
|
|
1379
1585
|
|
|
1380
1586
|
ctx.effect(() => {
|
|
1381
1587
|
applyAll('boot')
|
|
@@ -1391,6 +1597,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1391
1597
|
window.removeEventListener('pageshow', healOnShow)
|
|
1392
1598
|
} catch (err) { /* 同上 */ }
|
|
1393
1599
|
if (healTimer !== null) { clearInterval(healTimer); healTimer = null }
|
|
1600
|
+
if (langObserver !== null) { langObserver.disconnect(); langObserver = null }
|
|
1394
1601
|
}
|
|
1395
1602
|
}, 'dsh-theme-palette: token layer + 右栏样式')
|
|
1396
1603
|
|
|
@@ -1402,7 +1609,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1402
1609
|
// 内置节顺序:account -10 / general 0 / models 10 / plugins 15 / agent-presets 20;
|
|
1403
1610
|
// 5 = 紧跟「通用」之后。
|
|
1404
1611
|
order: 5,
|
|
1405
|
-
label: () => (
|
|
1612
|
+
label: () => TEXT[pickLocale(readLangPref())].nav,
|
|
1406
1613
|
inject: () => ({
|
|
1407
1614
|
getCurrent,
|
|
1408
1615
|
subscribe,
|
|
@@ -1425,6 +1632,10 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1425
1632
|
getMdMode,
|
|
1426
1633
|
subscribeMd,
|
|
1427
1634
|
setMdMode,
|
|
1635
|
+
getLangPref,
|
|
1636
|
+
getLocale,
|
|
1637
|
+
subscribeLocale,
|
|
1638
|
+
setLangPref,
|
|
1428
1639
|
getHealth,
|
|
1429
1640
|
subscribeHealth,
|
|
1430
1641
|
}),
|