@ash-qw/dsh-theme-prts 2.5.5 → 2.6.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 +12 -0
- package/README.en.md +3 -2
- package/README.md +3 -2
- package/lib/client.js +135 -24
- package/package.json +2 -2
- package/src/client/conversation-control-adapter.js +14 -5
- package/src/client/index.js +5 -1
- package/src/client/native-preferences.js +8 -4
- package/src/client/preferences.js +24 -4
- package/src/client/theme-controller-v2.js +3 -2
- package/src/client/theme-controller.js +3 -2
- package/src/client/theme-settings-workbench.js +82 -9
- package/src/index.js +2 -1
- package/src/styles/theme-settings-workbench.css +52 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ash-qw/dsh-theme-prts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web and the official desktop client",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=20.9.0",
|
|
72
|
-
"dsh": "0.2.1-rc.2 || 0.2.1-rc.3"
|
|
72
|
+
"dsh": "0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.5"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@playwright/test": "1.55.1",
|
|
@@ -185,13 +185,22 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
185
185
|
const find=node=>{
|
|
186
186
|
if(activeModal && !activeModal.contains(node))return null
|
|
187
187
|
if (!node?.closest || node.closest('[data-prts-workspace-actions],[data-prts-session-actions]')) return null
|
|
188
|
-
const
|
|
189
|
-
const scheduleOpen=schedule && [...schedule.children].find(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
|
|
188
|
+
const container=node.closest('[data-presented-file],[data-changed-files],[data-tool="schedule_create"]')
|
|
190
189
|
const button=node.closest('button')
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
let cardTarget=null
|
|
191
|
+
if(container){
|
|
192
|
+
const action=node.closest('button,a[href],[role="button"],[role="link"],summary,input:not([type="hidden"]),select,textarea')
|
|
193
|
+
const nested=action && action!==container && container.contains(action)
|
|
194
|
+
// Disabled controls must not fall back to their enclosing card.
|
|
195
|
+
if(node.closest(':disabled,[aria-disabled="true"],[inert]'))return null
|
|
196
|
+
const hitLayer=nested && action.tagName==='BUTTON' && (
|
|
197
|
+
container.matches('[data-presented-file]') && hasClassSuffix(action,'_cardPreview') ||
|
|
198
|
+
action.parentElement===container && container.matches('[data-tool="schedule_create"]') && hasClassSuffix(action,'_cardOpen'))
|
|
199
|
+
const openable=!container.matches('[data-tool="schedule_create"]') || [...container.children].some(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
|
|
200
|
+
cardTarget=nested && !hitLayer ? action : openable ? container : null
|
|
201
|
+
}
|
|
193
202
|
const pendingAction=operation.contains(button) && isPendingInteractionAction(button) ? button : null
|
|
194
|
-
const candidate=
|
|
203
|
+
const candidate=cardTarget || pendingAction || node.closest(selector)
|
|
195
204
|
return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
|
|
196
205
|
}
|
|
197
206
|
const measure=(box)=>{bounds=box || target.getBoundingClientRect();targetArm=7+Math.max(0,Math.min(1,(Math.min(bounds.width,bounds.height)-36)/36))*5;boundsDirty=false}
|
package/src/client/index.js
CHANGED
|
@@ -223,7 +223,11 @@ export function applyPrtsPlugin(ctx, environment) {
|
|
|
223
223
|
persistAndApply(updatePreferenceValue(preferences, key, value))
|
|
224
224
|
},
|
|
225
225
|
onPreferencePreview(key, value) {
|
|
226
|
-
if (key === 'cornerColor'
|
|
226
|
+
if (key === 'cornerColor' || key === 'cornerColorLight') {
|
|
227
|
+
const properties = preferences.cornerColorLinked ? ['--prts-corner-color-light', '--prts-corner-color-dark'] : [key === 'cornerColorLight' ? '--prts-corner-color-light' : '--prts-corner-color-dark']
|
|
228
|
+
for (const property of properties) document.documentElement.style.setProperty(property, String(value))
|
|
229
|
+
return
|
|
230
|
+
}
|
|
227
231
|
if (key === 'particleTraversalSpeed') {
|
|
228
232
|
particleField.update(updatePreferenceValue(preferences, key, value))
|
|
229
233
|
return
|
|
@@ -28,11 +28,15 @@ export function createNativePreferences({ storage, onChange }) {
|
|
|
28
28
|
migrationAttempted = true
|
|
29
29
|
pending = { ...legacy, ...pending }
|
|
30
30
|
}
|
|
31
|
-
// Materialize
|
|
32
|
-
// cannot change a right-side preference that was never written by the Host.
|
|
31
|
+
// Materialize legacy shared values before either side can be edited independently.
|
|
33
32
|
if (s.value?.preferencesInitialized && writable(s)) {
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (s.value.cornerColorSource == null && s.value.cornerColorLinked === true) {
|
|
34
|
+
for (const key of ['cornerColor', 'cornerCustomColor']) {
|
|
35
|
+
if (s.value[key] !== host[key] && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const key of ['rightSidebarOpacity', 'rightSidebarBlur', 'cornerColorLight', 'cornerCustomColorLight', 'cornerColorSource']) {
|
|
39
|
+
if (s.value[key] == null && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
current = normalizePreferences({ ...host, ...inFlight, ...pending })
|
|
@@ -39,8 +39,12 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
39
39
|
bakerBlur: 8,
|
|
40
40
|
classicUserBlur: 8,
|
|
41
41
|
cursor: 'prts',
|
|
42
|
+
cornerColorLinked: false,
|
|
43
|
+
cornerColorSource: 'dark',
|
|
42
44
|
cornerColor: '#f0c800',
|
|
43
45
|
cornerCustomColor: '#f0c800',
|
|
46
|
+
cornerColorLight: '#f0c800',
|
|
47
|
+
cornerCustomColorLight: '#f0c800',
|
|
44
48
|
conversationParticleDensity: 'sparse',
|
|
45
49
|
heroParticleDensity: 'light',
|
|
46
50
|
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
@@ -107,7 +111,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
107
111
|
background: Object.freeze(['texture', 'backgroundMode', 'backgroundDim', 'heroContrast', 'sidebarOpacity', 'rightSidebarOpacity', 'railOpacity', 'bakerOpacity', 'classicUserOpacity', 'sidebarBlur', 'rightSidebarBlur', 'railBlur', 'bakerBlur', 'classicUserBlur']),
|
|
108
112
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
109
113
|
material: Object.freeze(['glass', 'composerGlass']),
|
|
110
|
-
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor', 'cornerColor', 'cornerCustomColor']),
|
|
114
|
+
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor', 'cornerColorLinked', 'cornerColorSource', 'cornerColor', 'cornerCustomColor', 'cornerColorLight', 'cornerCustomColorLight']),
|
|
111
115
|
aiTools: Object.freeze(['aiToolsEnabled']),
|
|
112
116
|
navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
|
|
113
117
|
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'bakerLoading', 'conversationEmblem']),
|
|
@@ -231,8 +235,12 @@ export function normalizePreferences(value) {
|
|
|
231
235
|
backgroundDim: typeof input.backgroundDim === 'number' && Number.isFinite(input.backgroundDim) ? Math.min(.9, Math.max(0, input.backgroundDim)) : .45,
|
|
232
236
|
composerGlass: booleanOr(input.composerGlass, DEFAULT_PREFERENCES.composerGlass),
|
|
233
237
|
bootAnimation: booleanOr(input.bootAnimation, DEFAULT_PREFERENCES.bootAnimation),
|
|
238
|
+
cornerColorLinked: booleanOr(input.cornerColorLinked, false),
|
|
239
|
+
cornerColorSource: input.cornerColorSource === 'light' ? 'light' : 'dark',
|
|
234
240
|
cornerCustomColor: colorOr(input.cornerCustomColor, DEFAULT_PREFERENCES.cornerCustomColor),
|
|
235
241
|
cornerColor: colorOr(input.cornerColor, DEFAULT_PREFERENCES.cornerColor),
|
|
242
|
+
cornerColorLight: colorOr(input.cornerColorLight, colorOr(input.cornerColor, DEFAULT_PREFERENCES.cornerColor)),
|
|
243
|
+
cornerCustomColorLight: colorOr(input.cornerCustomColorLight, colorOr(input.cornerCustomColor, DEFAULT_PREFERENCES.cornerCustomColor)),
|
|
236
244
|
cursor: input.cursor === 'system' ? 'system' : DEFAULT_PREFERENCES.cursor,
|
|
237
245
|
conversationParticleDensity: enumOr(legacyDensities.conversation, PARTICLE_DENSITIES, particlePreset.conversationParticleDensity),
|
|
238
246
|
heroParticleDensity: enumOr(legacyDensities.hero, PARTICLE_DENSITIES, particlePreset.heroParticleDensity),
|
|
@@ -263,6 +271,11 @@ export function normalizePreferences(value) {
|
|
|
263
271
|
bakerGroupAvatars: booleanOr(input.bakerGroupAvatars, DEFAULT_PREFERENCES.bakerGroupAvatars),
|
|
264
272
|
conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
|
|
265
273
|
}
|
|
274
|
+
// Preserve the active color from the interim shared-palette format on upgrade.
|
|
275
|
+
if (input.cornerColorSource == null && input.cornerColorLinked === true) {
|
|
276
|
+
normalized.cornerColor = colorOr(input.cornerColorShared, normalized.cornerColor)
|
|
277
|
+
normalized.cornerCustomColor = colorOr(input.cornerCustomColorShared, normalized.cornerCustomColor)
|
|
278
|
+
}
|
|
266
279
|
normalized.preset = matchedVisualPreset(normalized)
|
|
267
280
|
return normalized
|
|
268
281
|
}
|
|
@@ -287,9 +300,16 @@ export function updatePreferenceValue(value, key, nextValue) {
|
|
|
287
300
|
if (key === 'preset') return applyVisualPreset(value, nextValue)
|
|
288
301
|
if (key === 'particleDetail') return applyParticleDetail(value, nextValue)
|
|
289
302
|
const current = normalizePreferences(value)
|
|
290
|
-
if (key === '
|
|
291
|
-
const
|
|
292
|
-
return normalizePreferences({ ...current,
|
|
303
|
+
if (key === 'cornerColorLink') {
|
|
304
|
+
const enabled = nextValue === 'light' || nextValue === 'dark'
|
|
305
|
+
return normalizePreferences({ ...current, cornerColorLinked: enabled,
|
|
306
|
+
...(enabled ? { cornerColorSource: nextValue } : {}) })
|
|
307
|
+
}
|
|
308
|
+
if (['cornerColor', 'cornerColorLight', 'cornerCustomColor', 'cornerCustomColorLight'].includes(key)) {
|
|
309
|
+
const color = colorOr(nextValue, current[key])
|
|
310
|
+
const custom = key.startsWith('cornerCustom')
|
|
311
|
+
const colorKey = custom ? key.replace('cornerCustomColor', 'cornerColor') : key
|
|
312
|
+
return normalizePreferences({ ...current, [colorKey]: color, ...(custom ? { [key]: color } : {}) })
|
|
293
313
|
}
|
|
294
314
|
if (key === 'sessionFlowPalette') {
|
|
295
315
|
const palette = enumOr(nextValue, SESSION_FLOW_PALETTE_IDS, current.sessionFlowPalette)
|
|
@@ -19,7 +19,8 @@ const ROOT_ATTRIBUTES = [
|
|
|
19
19
|
'data-prts-background-mode',
|
|
20
20
|
]
|
|
21
21
|
const SESSION_FLOW_COLOR_PROPERTIES = {
|
|
22
|
-
'--prts-corner-color': 'cornerColor',
|
|
22
|
+
'--prts-corner-color-dark': 'cornerColor',
|
|
23
|
+
'--prts-corner-color-light': 'cornerColorLight',
|
|
23
24
|
'--prts-session-flow-back': 'sessionFlowColorBack',
|
|
24
25
|
'--prts-session-flow-core': 'sessionFlowColorCore',
|
|
25
26
|
'--prts-session-flow-front': 'sessionFlowColorFront',
|
|
@@ -766,7 +767,7 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
766
767
|
root.dataset.prtsSessionGlow = preferences.sessionGlow ? 'on' : 'off'
|
|
767
768
|
root.dataset.prtsSessionFlowPalette = preferences.sessionFlowPalette
|
|
768
769
|
root.dataset.prtsSessionFlowSpeed = String(preferences.sessionFlowSpeed)
|
|
769
|
-
for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences[key])
|
|
770
|
+
for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences.cornerColorLinked && (key === 'cornerColor' || key === 'cornerColorLight') ? preferences[preferences.cornerColorSource === 'light' ? 'cornerColorLight' : 'cornerColor'] : preferences[key])
|
|
770
771
|
root.dataset.prtsConversationStyle = preferences.conversationStyle
|
|
771
772
|
root.toggleAttribute('data-prts-baker-loading', preferences.bakerLoading === true)
|
|
772
773
|
root.dataset.prtsBackgroundMode = preferences.backgroundMode
|
|
@@ -14,7 +14,8 @@ const ROOT_ATTRIBUTES = [
|
|
|
14
14
|
'data-prts-conversation-style',
|
|
15
15
|
]
|
|
16
16
|
const SESSION_FLOW_COLOR_PROPERTIES = {
|
|
17
|
-
'--prts-corner-color': 'cornerColor',
|
|
17
|
+
'--prts-corner-color-dark': 'cornerColor',
|
|
18
|
+
'--prts-corner-color-light': 'cornerColorLight',
|
|
18
19
|
'--prts-session-flow-back': 'sessionFlowColorBack',
|
|
19
20
|
'--prts-session-flow-core': 'sessionFlowColorCore',
|
|
20
21
|
'--prts-session-flow-front': 'sessionFlowColorFront',
|
|
@@ -124,7 +125,7 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
124
125
|
root.dataset.prtsSessionGlow = preferences.sessionGlow ? 'on' : 'off'
|
|
125
126
|
root.dataset.prtsSessionFlowPalette = preferences.sessionFlowPalette
|
|
126
127
|
root.dataset.prtsSessionFlowSpeed = String(preferences.sessionFlowSpeed)
|
|
127
|
-
for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences[key])
|
|
128
|
+
for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences.cornerColorLinked && (key === 'cornerColor' || key === 'cornerColorLight') ? preferences[preferences.cornerColorSource === 'light' ? 'cornerColorLight' : 'cornerColor'] : preferences[key])
|
|
128
129
|
root.dataset.prtsConversationStyle = preferences.conversationStyle
|
|
129
130
|
refresh()
|
|
130
131
|
},
|
|
@@ -171,7 +171,7 @@ export function themeSettingsMarkup() {
|
|
|
171
171
|
<section data-prts-settings-common data-prts-settings-section="interaction" role="tabpanel" id="prts-page-interaction" aria-labelledby="prts-tab-interaction" tabindex="0" hidden>
|
|
172
172
|
<div data-prts-settings-common-grid>
|
|
173
173
|
${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
174
|
-
<div data-prts-setting-row="cornerColor"><span><strong>交互提示框颜色</strong><small data-prts-setting-note>部分按钮和卡片的四角提示颜色,危险操作保持红色。</small></span><div data-prts-corner-presets role="group" aria-label="交互提示框预设颜色">${[['黄色','#f0c800'],['白色','#ffffff'],['青色','#65ddb0'],['蓝色','#6eb7f3'],['紫色','#b487ef']].map(([name,color]) => `<button type="button" data-prts-corner-preset="${color}" aria-label="${name}" title="${name}" aria-pressed="false" style="--swatch:${color}"><i aria-hidden="true"></i></button>`).join('')}<button type="button" data-prts-corner-custom title="选择自定义颜色"><i data-prts-corner-preview aria-hidden="true"></i><span>自定义</span></button><input type="color" data-prts-corner-color aria-label="交互提示框颜色" value="#f0c800" hidden></div></div>
|
|
174
|
+
<div data-prts-setting-row="cornerColor"><span><span data-prts-corner-heading><strong>交互提示框颜色</strong><span data-prts-corner-modes role="group" aria-label="编辑配色模式"><button type="button" data-prts-corner-mode="light" aria-pressed="false">☀ 日间</button><button type="button" data-prts-corner-mode="dark" aria-pressed="false">☾ 夜间</button></span><button type="button" data-prts-corner-sync role="switch" aria-checked="false" title="开启后,日间和夜间共用颜色"><span>日夜共用</span><i aria-hidden="true"></i></button></span><small data-prts-setting-note>部分按钮和卡片的四角提示颜色,危险操作保持红色。</small><small data-prts-setting-note>开启后,日夜都使用当前选定模式的配色,修改也保存到该模式;关闭后各用各的,另一套配色不变。</small></span><div data-prts-corner-presets role="group" aria-label="交互提示框预设颜色">${[['黄色','#f0c800'],['白色','#ffffff'],['青色','#65ddb0'],['蓝色','#6eb7f3'],['紫色','#b487ef']].map(([name,color]) => `<button type="button" data-prts-corner-preset="${color}" aria-label="${name}" title="${name}" aria-pressed="false" style="--swatch:${color}"><i aria-hidden="true"></i></button>`).join('')}<span data-prts-corner-picker><button type="button" data-prts-corner-custom aria-expanded="false" title="选择自定义颜色"><i data-prts-corner-preview aria-hidden="true"></i><span>自定义</span></button><input type="color" data-prts-corner-color aria-label="交互提示框颜色" value="#f0c800" tabindex="-1"><span data-prts-color-editor hidden role="group" aria-label="自定义提示框颜色"><span data-prts-color-editor-title>自定义颜色</span><span data-prts-color-sample></span><label data-prts-color-hex-label>HEX<input data-prts-color-hex aria-label="HEX 颜色" maxlength="7" spellcheck="false"></label><label data-prts-color-channel><span>R</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="0" aria-label="R 通道"><output>0</output></label><label data-prts-color-channel><span>G</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="1" aria-label="G 通道"><output>0</output></label><label data-prts-color-channel><span>B</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="2" aria-label="B 通道"><output>0</output></label><span data-prts-color-actions><button type="button" data-prts-color-cancel>取消</button><button type="button" data-prts-color-apply>应用颜色</button></span></span></span></div></div>
|
|
175
175
|
</div>
|
|
176
176
|
</section>
|
|
177
177
|
<section data-prts-settings-section="ai-tools" data-prts-ai-tools role="tabpanel" id="prts-page-ai-tools" aria-labelledby="prts-tab-ai-tools" tabindex="0" hidden>
|
|
@@ -285,6 +285,35 @@ export function createThemeSettingsOverlay({
|
|
|
285
285
|
const nativeModal = typeof panel?.showModal === 'function' && typeof panel?.close === 'function'
|
|
286
286
|
let isOpen = false
|
|
287
287
|
let previousFocus
|
|
288
|
+
let colorDraft = '#f0c800'
|
|
289
|
+
function closeColorEditor(restoreFocus = false) {
|
|
290
|
+
const editor = panel?.querySelector('[data-prts-color-editor]')
|
|
291
|
+
if (!editor || editor.hidden) return
|
|
292
|
+
editor.hidden = true
|
|
293
|
+
panel.querySelector('[data-prts-corner-custom]').setAttribute('aria-expanded', 'false')
|
|
294
|
+
onPreferencePreview(cornerKey(), currentPreferences[cornerKey()])
|
|
295
|
+
if (restoreFocus) panel.querySelector('[data-prts-corner-custom]').focus()
|
|
296
|
+
}
|
|
297
|
+
function renderColorDraft() {
|
|
298
|
+
const editor = panel.querySelector('[data-prts-color-editor]')
|
|
299
|
+
editor.querySelector('[data-prts-color-sample]').style.background = colorDraft
|
|
300
|
+
const hex = editor.querySelector('[data-prts-color-hex]')
|
|
301
|
+
hex.value = colorDraft.toUpperCase()
|
|
302
|
+
hex.removeAttribute('aria-invalid')
|
|
303
|
+
const channels = [1, 3, 5].map(start => parseInt(colorDraft.slice(start, start + 2), 16))
|
|
304
|
+
for (const slider of editor.querySelectorAll('[data-prts-color-channel-index]')) {
|
|
305
|
+
const index = Number(slider.dataset.prtsColorChannelIndex)
|
|
306
|
+
slider.value = channels[index]
|
|
307
|
+
slider.nextElementSibling.value = channels[index]
|
|
308
|
+
const end = [...channels], start = [...channels]
|
|
309
|
+
start[index] = 0; end[index] = 255
|
|
310
|
+
slider.style.background = 'linear-gradient(to right,rgb(' + start.join(',') + '),rgb(' + end.join(',') + '))'
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let cornerEditMode = 'dark'
|
|
314
|
+
const cornerMode = () => currentPreferences?.cornerColorLinked ? currentPreferences.cornerColorSource : cornerEditMode
|
|
315
|
+
const cornerKey = () => cornerMode() === 'light' ? 'cornerColorLight' : 'cornerColor'
|
|
316
|
+
const cornerCustomKey = () => cornerMode() === 'light' ? 'cornerCustomColorLight' : 'cornerCustomColor'
|
|
288
317
|
let currentPreferences
|
|
289
318
|
let currentStatus
|
|
290
319
|
let currentPersistence = { phase: 'idle', revision: 0 }
|
|
@@ -342,6 +371,8 @@ export function createThemeSettingsOverlay({
|
|
|
342
371
|
const value = Boolean(next)
|
|
343
372
|
if (value === isOpen) return isOpen
|
|
344
373
|
if (!value) {
|
|
374
|
+
closeColorEditor()
|
|
375
|
+
if (currentPreferences) onPreferencePreview(cornerKey(), currentPreferences[cornerKey()])
|
|
345
376
|
commitParticleTraversalSpeed()
|
|
346
377
|
commitSessionFlowSpeed()
|
|
347
378
|
commitSessionFlowColors()
|
|
@@ -351,6 +382,8 @@ export function createThemeSettingsOverlay({
|
|
|
351
382
|
document.documentElement.toggleAttribute('data-prts-settings-open', isOpen)
|
|
352
383
|
panel.setAttribute('aria-hidden', String(!isOpen))
|
|
353
384
|
if (isOpen) {
|
|
385
|
+
cornerEditMode = document.documentElement.getAttribute('data-prts-scheme') === 'light' ? 'light' : 'dark'
|
|
386
|
+
renderState()
|
|
354
387
|
previousFocus = document.activeElement
|
|
355
388
|
onOpen()
|
|
356
389
|
showPanel()
|
|
@@ -532,10 +565,18 @@ export function createThemeSettingsOverlay({
|
|
|
532
565
|
|
|
533
566
|
function renderState() {
|
|
534
567
|
if (!panel || !currentPreferences) return
|
|
568
|
+
panel.querySelector('[data-prts-corner-modes]').hidden = currentPreferences.cornerColorLinked
|
|
569
|
+
panel.querySelector('[data-prts-corner-sync]').setAttribute('aria-checked', String(currentPreferences.cornerColorLinked))
|
|
570
|
+
for (const mode of panel.querySelectorAll('[data-prts-corner-mode]')) mode.setAttribute('aria-pressed', String(mode.dataset.prtsCornerMode === cornerEditMode))
|
|
535
571
|
const cornerColor = panel.querySelector('[data-prts-corner-color]')
|
|
536
|
-
if (cornerColor) cornerColor.value = currentPreferences
|
|
537
|
-
panel.querySelector('[data-prts-corner-preview]')?.style.setProperty('--swatch', currentPreferences
|
|
538
|
-
|
|
572
|
+
if (cornerColor) cornerColor.value = currentPreferences[cornerCustomKey()]
|
|
573
|
+
panel.querySelector('[data-prts-corner-preview]')?.style.setProperty('--swatch', currentPreferences[cornerCustomKey()])
|
|
574
|
+
const customButton = panel.querySelector('[data-prts-corner-custom]')
|
|
575
|
+
const customApplied = currentPreferences[cornerKey()] === currentPreferences[cornerCustomKey()]
|
|
576
|
+
customButton.setAttribute('aria-pressed', String(customApplied))
|
|
577
|
+
customButton.title = customApplied ? '自定义颜色已应用,点击编辑' : '选择自定义颜色'
|
|
578
|
+
customButton.querySelector('span').textContent = '自定义'
|
|
579
|
+
for (const preset of panel.querySelectorAll('[data-prts-corner-preset]')) preset.setAttribute('aria-pressed', String(preset.dataset.prtsCornerPreset === currentPreferences[cornerKey()]))
|
|
539
580
|
for (const toggle of panel.querySelectorAll('[data-prts-setting-switch]')) {
|
|
540
581
|
const key = toggle.dataset.prtsSettingSwitch
|
|
541
582
|
const enabled = key === 'cursor' ? currentPreferences.cursor === 'prts'
|
|
@@ -628,10 +669,29 @@ export function createThemeSettingsOverlay({
|
|
|
628
669
|
|
|
629
670
|
function onPanelClick(event) {
|
|
630
671
|
if (event.target === panel) { setOpen(false); return }
|
|
672
|
+
if (!event.target?.closest?.('[data-prts-corner-picker]')) closeColorEditor()
|
|
631
673
|
const target = event.target?.closest?.('button')
|
|
632
674
|
if (!target) return
|
|
633
|
-
if (target.hasAttribute('data-prts-corner-
|
|
634
|
-
if (target.hasAttribute('data-prts-corner-
|
|
675
|
+
if (target.hasAttribute('data-prts-corner-mode')) { onPreferencePreview(cornerKey(), currentPreferences[cornerKey()]); cornerEditMode = target.dataset.prtsCornerMode; renderState(); return }
|
|
676
|
+
if (target.hasAttribute('data-prts-corner-sync')) { if (currentPreferences.cornerColorLinked) cornerEditMode = currentPreferences.cornerColorSource; onPreferenceChange('cornerColorLink', currentPreferences.cornerColorLinked ? false : cornerEditMode); return }
|
|
677
|
+
if (target.hasAttribute('data-prts-color-cancel')) { closeColorEditor(true); return }
|
|
678
|
+
if (target.hasAttribute('data-prts-color-apply')) {
|
|
679
|
+
if (panel.querySelector('[data-prts-color-hex]').getAttribute('aria-invalid') === 'true') return
|
|
680
|
+
onPreferenceChange(cornerCustomKey(), colorDraft); closeColorEditor(true); return
|
|
681
|
+
}
|
|
682
|
+
if (target.hasAttribute('data-prts-corner-custom')) {
|
|
683
|
+
const editor = panel.querySelector('[data-prts-color-editor]')
|
|
684
|
+
if (!editor.hidden) { closeColorEditor(); return }
|
|
685
|
+
colorDraft = currentPreferences[cornerCustomKey()]
|
|
686
|
+
renderColorDraft(); editor.hidden = false
|
|
687
|
+
editor.removeAttribute('data-open-below')
|
|
688
|
+
const scroller = panel.querySelector('[data-prts-theme-settings-scroll]')
|
|
689
|
+
if (editor.getBoundingClientRect().top < (scroller?.getBoundingClientRect().top ?? 0)) editor.setAttribute('data-open-below', '')
|
|
690
|
+
target.setAttribute('aria-expanded', 'true')
|
|
691
|
+
editor.querySelector('[data-prts-color-hex]').focus()
|
|
692
|
+
return
|
|
693
|
+
}
|
|
694
|
+
if (target.hasAttribute('data-prts-corner-preset')) { onPreferenceChange(cornerKey(), target.dataset.prtsCornerPreset); return }
|
|
635
695
|
if (target.hasAttribute('data-prts-settings-jump')) {
|
|
636
696
|
selectSection(target.dataset.prtsSettingsJump)
|
|
637
697
|
return
|
|
@@ -670,7 +730,20 @@ export function createThemeSettingsOverlay({
|
|
|
670
730
|
|
|
671
731
|
function onPanelInput(event) {
|
|
672
732
|
const target = event.target
|
|
673
|
-
if (target?.matches?.('[data-prts-
|
|
733
|
+
if (target?.matches?.('[data-prts-color-channel-index], [data-prts-color-hex]')) {
|
|
734
|
+
if (target.hasAttribute('data-prts-color-hex')) {
|
|
735
|
+
const value = target.value.trim()
|
|
736
|
+
if (!/^#?[0-9a-f]{6}$/i.test(value)) { target.setAttribute('aria-invalid', 'true'); return }
|
|
737
|
+
colorDraft = (value.startsWith('#') ? value : '#' + value).toLowerCase()
|
|
738
|
+
} else {
|
|
739
|
+
const channels = [...panel.querySelectorAll('[data-prts-color-channel-index]')].map(input => Number(input.value).toString(16).padStart(2, '0'))
|
|
740
|
+
colorDraft = '#' + channels.join('')
|
|
741
|
+
}
|
|
742
|
+
renderColorDraft()
|
|
743
|
+
onPreferencePreview(cornerKey(), colorDraft)
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
if (target?.matches?.('[data-prts-corner-color]')) { onPreferencePreview(cornerKey(), target.value); return }
|
|
674
747
|
if (target?.matches?.('[data-prts-preview-reply-limit]')) { onPreferenceChange('previewReplyLimit', Number(target.value)); return }
|
|
675
748
|
if (target?.matches?.('input[data-prts-setting-color]')) {
|
|
676
749
|
const key = target.dataset.prtsSettingKey
|
|
@@ -713,7 +786,7 @@ export function createThemeSettingsOverlay({
|
|
|
713
786
|
selectSection(tabs[next].dataset.prtsSettingsJump, true)
|
|
714
787
|
return
|
|
715
788
|
}
|
|
716
|
-
if (event.key === 'Escape') { event.preventDefault(); setOpen(false); return }
|
|
789
|
+
if (event.key === 'Escape') { event.preventDefault(); if (!panel.querySelector('[data-prts-color-editor]').hidden) { event.stopPropagation(); closeColorEditor(true) } else setOpen(false); return }
|
|
717
790
|
if (nativeModal || event.key !== 'Tab') return
|
|
718
791
|
const nodes = focusable()
|
|
719
792
|
if (!nodes.length) return
|
|
@@ -733,7 +806,7 @@ export function createThemeSettingsOverlay({
|
|
|
733
806
|
const renderMediaState = () => renderState()
|
|
734
807
|
const commitFromControl = event => {
|
|
735
808
|
const target = event.target
|
|
736
|
-
if (target?.matches?.('[data-prts-corner-color]')) { if (event.type === 'change') onPreferenceChange(
|
|
809
|
+
if (target?.matches?.('[data-prts-corner-color]')) { if (event.type === 'change') onPreferenceChange(cornerCustomKey(), target.value); return }
|
|
737
810
|
if (target?.matches?.('input[data-prts-setting-color]')) {
|
|
738
811
|
if (event.type === 'change' || event.type === 'focusout') commitSessionFlowColor(target.dataset.prtsSettingKey)
|
|
739
812
|
return
|
package/src/index.js
CHANGED
|
@@ -12,9 +12,10 @@ export const PACKAGE_ID = '@ash-qw/dsh-theme-prts'
|
|
|
12
12
|
export const THEME_PLUGIN_ID = 'dsh-theme-prts'
|
|
13
13
|
export const inject = ['tools', 'attachments', 'connection', 'webServer']
|
|
14
14
|
|
|
15
|
+
// Leave new palette fields absent until normalization can inherit the legacy color.
|
|
15
16
|
export const Config = Schema.object(Object.fromEntries([
|
|
16
17
|
...Object.entries(DEFAULT_PREFERENCES).map(([key, value]) => [key,
|
|
17
|
-
(typeof value === 'boolean' ? Schema.boolean() : typeof value === 'number' ? Schema.number() : Schema.string()).default(value).volatile(),
|
|
18
|
+
(typeof value === 'boolean' ? Schema.boolean() : typeof value === 'number' ? Schema.number() : Schema.string()).default(['cornerColorLight', 'cornerCustomColorLight', 'cornerColorSource'].includes(key) ? undefined : value).volatile(),
|
|
18
19
|
]),
|
|
19
20
|
['preferencesInitialized', Schema.boolean().default(false).volatile()],
|
|
20
21
|
]))
|
|
@@ -1043,5 +1043,55 @@ html[data-dsh-prts] [data-prts-corner-presets] { display:flex;flex-wrap:wrap;gap
|
|
|
1043
1043
|
& > button[aria-pressed="true"]::after { content:"";position:absolute;width:8px;height:4px;border-left:2px solid #172025;border-bottom:2px solid #172025;transform:translateY(-1px) rotate(-45deg); }
|
|
1044
1044
|
& > button:focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
|
|
1045
1045
|
}
|
|
1046
|
-
html[data-dsh-prts] [data-prts-corner-presets]
|
|
1047
|
-
html[data-dsh-prts] [data-prts-corner-presets]
|
|
1046
|
+
html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom] { display:flex;align-items:center;gap:8px;width:auto;height:32px;margin-left:8px;padding:5px 10px;border:1px solid var(--prts-line-strong);border-radius:4px;background:var(--prts-panel-raised);color:var(--prts-ink);font:12px/1 var(--prts-body);white-space:nowrap; }
|
|
1047
|
+
html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom]:is(:hover,:focus-visible) { border-color:var(--prts-ink);background:color-mix(in srgb,var(--prts-ink) 8%,var(--prts-panel-raised)); }
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
/* Resolve palette in CSS so app scheme changes never require pointer work. */
|
|
1051
|
+
html[data-dsh-prts] { --prts-corner-color: var(--prts-corner-color-dark, #f0c800); }
|
|
1052
|
+
html[data-dsh-prts][data-prts-scheme="light"] { --prts-corner-color: var(--prts-corner-color-light, #f0c800); }
|
|
1053
|
+
[data-prts-corner-heading] { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
|
|
1054
|
+
[data-prts-corner-modes] { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--prts-line-strong); border-radius: 6px; }
|
|
1055
|
+
[data-prts-corner-modes] button { padding: 4px 7px; border: 0; border-radius: 3px; background: transparent; color: inherit; font-size: 11px; opacity: .6; cursor: pointer; }
|
|
1056
|
+
[data-prts-corner-modes] button[aria-pressed="true"] { background: color-mix(in srgb, currentColor 12%, transparent); opacity: 1; }
|
|
1057
|
+
[data-prts-corner-picker] { position: relative; display: inline-flex; }
|
|
1058
|
+
[data-prts-corner-picker] input[type="color"] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
|
|
1059
|
+
html[data-dsh-prts] [data-prts-setting-row="cornerColor"] > span { flex-basis: 100%; }
|
|
1060
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-heading] { display: flex; align-items: center; gap: 12px; }
|
|
1061
|
+
html[data-dsh-prts] [data-prts-corner-heading] strong { font: 550 var(--prts-settings-text)/1.5 var(--prts-body); }
|
|
1062
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes] { display: inline-flex; }
|
|
1063
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes] button { font: 11px/1.4 var(--prts-body); }
|
|
1064
|
+
|
|
1065
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] { margin-left: auto; padding: 4px 7px; border: 0; border-radius: 4px; background: transparent; color: var(--prts-muted); font: 11px/1.4 var(--prts-body); cursor: pointer; }
|
|
1066
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync]:hover { color: var(--prts-ink); background: var(--prts-panel-raised); }
|
|
1067
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor] { position: absolute; z-index: 10; right: 0; bottom: calc(100% + 10px); width: 252px; padding: 16px; display: grid; gap: 12px; border: 1px solid var(--prts-line-strong); border-radius: 10px; background: var(--prts-panel-raised); color: var(--prts-ink); box-shadow: 0 12px 36px #0005; cursor: auto; }
|
|
1068
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor][hidden] { display: none; }
|
|
1069
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor-title] { font: 550 12px/1.4 var(--prts-body); }
|
|
1070
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-sample] { display: block; height: 44px; border-radius: 6px; box-shadow: inset 0 0 0 1px #8884; }
|
|
1071
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex-label] { display: flex; align-items: center; gap: 12px; font: 11px/1.5 var(--prts-mono); color: var(--prts-muted); }
|
|
1072
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex] { min-width: 0; width: 100%; padding: 6px 8px; border: 1px solid var(--prts-line-strong); border-radius: 4px; background: var(--prts-canvas); color: var(--prts-ink); font: 12px/1.4 var(--prts-mono); }
|
|
1073
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex][aria-invalid="true"] { border-color: var(--prts-red); }
|
|
1074
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-channel] { display: grid; grid-template-columns: 12px 1fr 25px; gap: 10px; align-items: center; font: 11px/1.5 var(--prts-mono); color: var(--prts-muted); }
|
|
1075
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-channel-index] { appearance: none; width: 100%; min-width: 0; height: 8px; margin: 0; padding: 0; border: 0; border-radius: 8px; cursor: pointer; }
|
|
1076
|
+
html[data-dsh-prts] [data-prts-color-channel-index]::-webkit-slider-thumb { appearance: none; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%; background: #283036; box-shadow: 0 1px 4px #0008; }
|
|
1077
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-actions] { display: flex; justify-content: flex-end; gap: 8px; padding-top: 3px; }
|
|
1078
|
+
html[data-dsh-prts] [data-prts-color-actions] button { padding: 6px 10px; border: 1px solid var(--prts-line-strong); border-radius: 4px; color: var(--prts-ink); background: transparent; font: 12px/1.4 var(--prts-body); cursor: pointer; }
|
|
1079
|
+
html[data-dsh-prts] [data-prts-color-actions] button:hover { background: color-mix(in srgb,var(--prts-ink) 10%,transparent); }
|
|
1080
|
+
html[data-dsh-prts] [data-prts-color-actions] [data-prts-color-apply] { background: var(--prts-ink); color: var(--prts-canvas); }
|
|
1081
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
|
|
1082
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] > span { width: 100%; }
|
|
1083
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-presets] { justify-content: flex-start; }
|
|
1084
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor] { max-width: calc(100vw - 72px); max-height: 310px; overflow-y: auto; }
|
|
1085
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor][data-open-below] { bottom: auto; top: calc(100% + 10px); }
|
|
1086
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] { flex-wrap: nowrap; }
|
|
1087
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] > span { flex: none; width: auto; }
|
|
1088
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes][hidden] { display: none; }
|
|
1089
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
|
|
1090
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] i { display: block; position: relative; width: 28px; height: 16px; border-radius: 10px; background: var(--prts-line-strong); }
|
|
1091
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] i::after { content: ""; position: absolute; left: 3px; top: 3px; width: 10px; height: 10px; border-radius: 50%; background: var(--prts-ink); }
|
|
1092
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync][aria-checked="true"] i { background: var(--prts-yellow); }
|
|
1093
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync][aria-checked="true"] i::after { transform: translateX(12px); background: var(--prts-canvas); }
|
|
1094
|
+
html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom][aria-pressed="true"] { border-color: var(--prts-ink); background: color-mix(in srgb,var(--prts-ink) 10%,var(--prts-panel-raised)); }
|
|
1095
|
+
|
|
1096
|
+
html[data-dsh-prts] [data-prts-corner-custom] [data-prts-corner-preview] { position: relative; }
|
|
1097
|
+
html[data-dsh-prts] [data-prts-corner-custom][aria-pressed="true"] [data-prts-corner-preview]::after { content: ""; position: absolute; left: 4px; top: 5px; width: 8px; height: 4px; border-left: 2px solid #172025; border-bottom: 2px solid #172025; transform: rotate(-45deg); }
|