@fonlan/dsh-quick-commands 0.1.5 → 0.1.7
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/lib/client.js +182 -52
- package/lib/client.js.map +1 -1
- package/lib/index.js +48 -23
- package/lib/types/index.d.ts +4 -2
- package/lib/types/server/settings.d.ts +14 -4
- package/lib/types/shared/contract.d.ts +7 -0
- package/package.json +17 -16
- package/src/client/header.tsx +15 -12
- package/src/client/icons.tsx +106 -0
- package/src/client/index.tsx +15 -14
- package/src/client/locales.ts +7 -7
- package/src/client/quick-commands.css +1 -191
- package/src/client/settings-section.css +163 -0
- package/src/client/settings-section.tsx +192 -0
- package/src/index.ts +11 -4
- package/src/server/settings.ts +74 -29
- package/src/shared/contract.ts +10 -0
- package/src/client/settings-card.tsx +0 -209
package/src/client/header.tsx
CHANGED
|
@@ -8,20 +8,18 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
10
10
|
import { createPortal } from 'react-dom'
|
|
11
|
-
import {
|
|
12
|
-
IconPlayOutline16,
|
|
13
|
-
IconCloseOutline16,
|
|
14
|
-
IconStopFill16,
|
|
15
|
-
} from '@deepseek-ai/dsh-client-ui-primitives'
|
|
11
|
+
import { PlayIcon, CloseIcon, StopIcon } from './icons'
|
|
16
12
|
import { quickApi } from './api'
|
|
17
13
|
import { useRunPopover, openMenu, closeMenu, openRun, closeRun } from './run-state'
|
|
18
|
-
import type
|
|
14
|
+
import { anchorOf, type QuickPopupSize } from '../shared/contract'
|
|
19
15
|
|
|
20
16
|
/** Workspace row shape the standard useWorkspaces hook exposes. */
|
|
21
17
|
interface WorkspaceRow {
|
|
22
18
|
workspaceId: string
|
|
23
19
|
path: string
|
|
24
20
|
title: string
|
|
21
|
+
/** Session membership; the canonical link DSH itself resolves Workspaces by. */
|
|
22
|
+
sessionIds?: string[]
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/** Session list state subset we read (cwd + id). */
|
|
@@ -91,7 +89,12 @@ function useCurrentWorkspace(
|
|
|
91
89
|
): { workspace: WorkspaceRow | undefined; cwd: string | undefined } {
|
|
92
90
|
const row = useSessions?.((s) => s.byId[sessionId]) as SessionRow | undefined
|
|
93
91
|
const workspaces = useWorkspaces?.((s) => s.items) as WorkspaceRow[] | undefined
|
|
94
|
-
|
|
92
|
+
const items = workspaces ?? []
|
|
93
|
+
// Membership first (how DSH core resolves a session's Workspace, and the only
|
|
94
|
+
// link that survives when a session runs outside the Workspace root), then
|
|
95
|
+
// the longest cwd prefix as the fallback for hosts without membership data.
|
|
96
|
+
const byMembership = items.find((ws) => ws.sessionIds?.includes(sessionId) === true)
|
|
97
|
+
return { workspace: byMembership ?? matchWorkspace(row?.cwd, items), cwd: row?.cwd }
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
/** Header icon button + command menu + live output popup. */
|
|
@@ -159,7 +162,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
|
|
|
159
162
|
const own = entries.find((e) => e.workspaceId === workspace?.workspaceId)
|
|
160
163
|
setCommands(own === undefined ? [] : own.commands)
|
|
161
164
|
setMenuRemoteHost(own?.remoteHost ?? null)
|
|
162
|
-
setAnchor(settings.popupAnchor)
|
|
165
|
+
setAnchor(anchorOf(settings.popupAnchor))
|
|
163
166
|
setPopupSize(settings.popupSize)
|
|
164
167
|
setMenuLoadError(null)
|
|
165
168
|
} catch (e) {
|
|
@@ -201,7 +204,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
|
|
|
201
204
|
disabled={workspace === undefined}
|
|
202
205
|
onClick={() => { popover.menuOpen ? closeMenu() : openMenu() }}
|
|
203
206
|
>
|
|
204
|
-
<
|
|
207
|
+
<PlayIcon size={15} />
|
|
205
208
|
</button>
|
|
206
209
|
|
|
207
210
|
{popover.menuOpen && workspace !== undefined && menuPosition !== null && createPortal(
|
|
@@ -216,7 +219,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
|
|
|
216
219
|
{menuRemoteHost !== null && <span className="qc-menu-remote" title={`SSH · ${menuRemoteHost}`}>SSH</span>}
|
|
217
220
|
<span className="qc-menu-ws">{workspace.title}</span>
|
|
218
221
|
<button type="button" className="qc-menu-close" aria-label={t('runClose')} onClick={closeMenu}>
|
|
219
|
-
<
|
|
222
|
+
<CloseIcon size={12} />
|
|
220
223
|
</button>
|
|
221
224
|
</div>
|
|
222
225
|
{commands.length === 0 && (
|
|
@@ -495,7 +498,7 @@ function RunPopup(props: {
|
|
|
495
498
|
aria-label={t('runKill')}
|
|
496
499
|
onClick={() => void kill()}
|
|
497
500
|
>
|
|
498
|
-
<
|
|
501
|
+
<StopIcon size={14} />
|
|
499
502
|
</button>
|
|
500
503
|
<button
|
|
501
504
|
type="button"
|
|
@@ -505,7 +508,7 @@ function RunPopup(props: {
|
|
|
505
508
|
onPointerUp={onClosePointerUp}
|
|
506
509
|
onClick={closeOutput}
|
|
507
510
|
>
|
|
508
|
-
<
|
|
511
|
+
<CloseIcon size={14} />
|
|
509
512
|
</button>
|
|
510
513
|
</header>
|
|
511
514
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon compatibility layer over `@deepseek-ai/dsh-client-ui-primitives`.
|
|
3
|
+
*
|
|
4
|
+
* DSH renamed this package's icon exports in 0.1.7: the size-suffixed names
|
|
5
|
+
* (`IconPlayOutline16`) became weight-suffixed ones (`IconPlayOutlineRegular` /
|
|
6
|
+
* `IconPlayOutlineMedium`, matching what the official header plugins use).
|
|
7
|
+
*
|
|
8
|
+
* A client bundle that statically imports a name the host no longer exports
|
|
9
|
+
* receives `undefined`; rendering that element throws, the slot's per-entry
|
|
10
|
+
* error boundary swallows the whole entry, and the surface silently vanishes —
|
|
11
|
+
* exactly how the header ▶ button disappeared on 0.1.7.
|
|
12
|
+
*
|
|
13
|
+
* Resolving by name at runtime (new scheme → legacy scheme → bundled artwork)
|
|
14
|
+
* keeps every surface rendering across DSH alphas. The bundled artwork is only
|
|
15
|
+
* a last resort, so a future rename can never blank a control again.
|
|
16
|
+
*/
|
|
17
|
+
import type { ReactElement, ReactNode } from 'react'
|
|
18
|
+
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
|
|
19
|
+
|
|
20
|
+
/** Props both naming schemes accept (the legacy exports ignore extra fields). */
|
|
21
|
+
export interface IconProps {
|
|
22
|
+
/** Rendered square size in CSS pixels. */
|
|
23
|
+
size?: number
|
|
24
|
+
className?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type IconComponent = (props: IconProps) => ReactElement
|
|
28
|
+
|
|
29
|
+
const ICON_EXPORTS = primitives as unknown as Record<string, IconComponent | undefined>
|
|
30
|
+
|
|
31
|
+
/** First export name that exists on the running host, else the bundled artwork. */
|
|
32
|
+
function pickIcon(exportNames: readonly string[], fallback: IconComponent): IconComponent {
|
|
33
|
+
for (const name of exportNames) {
|
|
34
|
+
const candidate = ICON_EXPORTS[name]
|
|
35
|
+
if (typeof candidate === 'function') return candidate
|
|
36
|
+
}
|
|
37
|
+
return fallback
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Wrap one locally drawn 16×16 artwork as an icon component. */
|
|
41
|
+
function localIcon(children: ReactNode): IconComponent {
|
|
42
|
+
return function LocalIcon({ size = 16, className }: IconProps): ReactElement {
|
|
43
|
+
return (
|
|
44
|
+
<svg
|
|
45
|
+
width={size}
|
|
46
|
+
height={size}
|
|
47
|
+
viewBox="0 0 16 16"
|
|
48
|
+
fill="none"
|
|
49
|
+
className={className}
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</svg>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Run/play glyph for the header quick-run button. */
|
|
59
|
+
export const PlayIcon = pickIcon(
|
|
60
|
+
['IconPlayOutlineRegular', 'IconPlayOutlineMedium', 'IconPlayOutline16'],
|
|
61
|
+
localIcon(
|
|
62
|
+
<>
|
|
63
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" />
|
|
64
|
+
<path d="M6.6 5.9 10.4 8l-3.8 2.1Z" fill="currentColor" />
|
|
65
|
+
</>,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
/** Dismiss glyph (menu header and popup close). */
|
|
70
|
+
export const CloseIcon = pickIcon(
|
|
71
|
+
['IconCloseOutlineRegular', 'IconCloseOutlineMedium', 'IconCloseOutline16'],
|
|
72
|
+
localIcon(
|
|
73
|
+
<path d="M4.2 4.2 11.8 11.8M11.8 4.2 4.2 11.8" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />,
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/** Stop/kill glyph for a running command. */
|
|
78
|
+
export const StopIcon = pickIcon(
|
|
79
|
+
['IconStopFillRegular', 'IconStopFillMedium', 'IconStopFill16'],
|
|
80
|
+
localIcon(<rect x="4.6" y="4.6" width="6.8" height="6.8" rx="1.6" fill="currentColor" />),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
/** Add-row glyph in the settings page. */
|
|
84
|
+
export const PlusIcon = pickIcon(
|
|
85
|
+
['IconPlusOutlineRegular', 'IconPlusOutlineMedium', 'IconPlusOutline16'],
|
|
86
|
+
localIcon(
|
|
87
|
+
<path d="M8 3.4v9.2M3.4 8h9.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
/** Remove-command glyph in the settings page. */
|
|
92
|
+
export const TrashIcon = pickIcon(
|
|
93
|
+
['IconTrashOutlineRegular', 'IconTrashOutlineMedium', 'IconTrashOutline16'],
|
|
94
|
+
localIcon(
|
|
95
|
+
<>
|
|
96
|
+
<path d="M3.4 4.4h9.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
|
97
|
+
<path
|
|
98
|
+
d="M6.4 4.4V3.1h3.2v1.3M4.9 4.4l.6 8.5h5l.6-8.5"
|
|
99
|
+
stroke="currentColor"
|
|
100
|
+
strokeWidth="1.2"
|
|
101
|
+
strokeLinecap="round"
|
|
102
|
+
strokeLinejoin="round"
|
|
103
|
+
/>
|
|
104
|
+
</>,
|
|
105
|
+
),
|
|
106
|
+
)
|
package/src/client/index.tsx
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* @fonlan/dsh-quick-commands client half.
|
|
3
3
|
*
|
|
4
4
|
* Registers:
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* - "settings.section" page (id "quick-commands", order 330) — per-workspace
|
|
6
|
+
* command CRUD + popup anchor preference, rendered as its own page in the
|
|
7
|
+
* settings sidebar.
|
|
8
|
+
* - "conversation.session.header.utilities" entry (id "quick-commands",
|
|
8
9
|
* order -1 → left of the Session log pill) — the quick-run ▶ button, command
|
|
9
10
|
* menu, and live output popup.
|
|
10
11
|
*/
|
|
@@ -12,9 +13,8 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
12
13
|
type ClientContext = Context
|
|
13
14
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
14
15
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
15
|
-
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
|
16
16
|
import { QuickCommandsHeaderAction } from './header'
|
|
17
|
-
import {
|
|
17
|
+
import { QuickCommandsSettingsSection } from './settings-section'
|
|
18
18
|
import { LOCALE_NS, zh, en } from './locales'
|
|
19
19
|
import './quick-commands.css'
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ import './quick-commands.css'
|
|
|
22
22
|
interface Slots {
|
|
23
23
|
inject(name: string, callback: () => unknown): unknown
|
|
24
24
|
register(
|
|
25
|
-
def: { name: string;
|
|
25
|
+
def: { name: string; id?: string; order?: number; label?: string | (() => string); locale?: string },
|
|
26
26
|
component: unknown,
|
|
27
27
|
): unknown
|
|
28
28
|
}
|
|
@@ -36,16 +36,17 @@ export function apply(ctx: ClientContext): void {
|
|
|
36
36
|
const t = ctx.locale.bind(LOCALE_NS)
|
|
37
37
|
const slots = (ctx as unknown as { slots: Slots }).slots
|
|
38
38
|
|
|
39
|
-
// The plugin's own
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
slots.inject('settings.plugin.item', () =>
|
|
39
|
+
// The plugin's own settings page (设置 → 侧栏「快捷命令」) rides the
|
|
40
|
+
// "quick-commands" locale namespace: a "settings.section" list entry gives it
|
|
41
|
+
// one row in the settings sidebar and renders the page in the content column.
|
|
42
|
+
slots.inject('settings.section', () =>
|
|
44
43
|
slots.register({
|
|
45
|
-
name: 'settings.
|
|
46
|
-
|
|
44
|
+
name: 'settings.section',
|
|
45
|
+
id: 'quick-commands',
|
|
46
|
+
order: 330,
|
|
47
|
+
label: () => t('settingsTitle'),
|
|
47
48
|
locale: LOCALE_NS,
|
|
48
|
-
},
|
|
49
|
+
}, QuickCommandsSettingsSection as never),
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
// Session-header util: order -1 renders LEFT of the Session log pill
|
package/src/client/locales.ts
CHANGED
|
@@ -37,10 +37,10 @@ export const zh = {
|
|
|
37
37
|
runLost: '运行记录已失效',
|
|
38
38
|
runNotFound: '工作区或命令未找到',
|
|
39
39
|
|
|
40
|
-
// settings
|
|
40
|
+
// settings page (设置 → 侧栏「快捷命令」)
|
|
41
|
+
settingsTitle: '快捷命令',
|
|
41
42
|
cardTitle: '快速命令',
|
|
42
|
-
|
|
43
|
-
collapseOrExpand: '展开 / 收起',
|
|
43
|
+
sectionSub: '为每个工作区配置常用命令,会话头部一键运行',
|
|
44
44
|
cardIntro: '为每个工作区配置常用命令,运行于工作区目录。支持占位符:{workspace} 工作区路径,{cwd} 会话目录,{title} 工作区标题。',
|
|
45
45
|
cardWorkspaces: '工作区',
|
|
46
46
|
cardNoWorkspaces: '没有工作区。请在侧边栏先添加工作区。',
|
|
@@ -54,9 +54,9 @@ export const zh = {
|
|
|
54
54
|
cardAnchor: '输出弹窗位置',
|
|
55
55
|
cardAnchorCorner: '右下角',
|
|
56
56
|
cardAnchorButton: '锚定按钮下方',
|
|
57
|
-
}
|
|
57
|
+
} as const
|
|
58
58
|
|
|
59
|
-
export const en = {
|
|
59
|
+
export const en: Record<keyof typeof zh, string> = {
|
|
60
60
|
buttonTitle: 'Quick commands',
|
|
61
61
|
buttonTooltip: 'Quick commands (current workspace)',
|
|
62
62
|
noWorkspace: 'This session is not in any configured workspace',
|
|
@@ -87,9 +87,9 @@ export const en = {
|
|
|
87
87
|
runLost: 'Run record expired',
|
|
88
88
|
runNotFound: 'Workspace or command not found',
|
|
89
89
|
|
|
90
|
+
settingsTitle: 'Quick Commands',
|
|
90
91
|
cardTitle: 'Quick commands',
|
|
91
|
-
|
|
92
|
-
collapseOrExpand: 'Expand / collapse',
|
|
92
|
+
sectionSub: 'Configure common commands per workspace, run them from the session header',
|
|
93
93
|
cardIntro: 'Configure common commands per workspace; they run in the workspace directory. Placeholders: {workspace} workspace path, {cwd} session cwd, {title} workspace title.',
|
|
94
94
|
cardWorkspaces: 'Workspaces',
|
|
95
95
|
cardNoWorkspaces: 'No workspaces. Add one in the sidebar first.',
|
|
@@ -370,196 +370,6 @@
|
|
|
370
370
|
opacity: 1;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
/* ── settings card (设置 → 插件配置) — mirrors official PluginCard disclosure ── */
|
|
374
|
-
.qc-settings-card {
|
|
375
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
376
|
-
background: var(--dsw-alias-bg-layer-2, #1f1f21);
|
|
377
|
-
border-radius: 12px;
|
|
378
|
-
list-style: none;
|
|
379
|
-
transition: border-color .16s, background .16s;
|
|
380
|
-
}
|
|
381
|
-
.qc-settings-card:hover {
|
|
382
|
-
border-color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
383
|
-
}
|
|
384
|
-
.qc-settings-card-open {
|
|
385
|
-
background: var(--dsw-alias-bg-layer-1, #27272a);
|
|
386
|
-
border-color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
387
|
-
}
|
|
388
|
-
.qc-settings-header {
|
|
389
|
-
appearance: none;
|
|
390
|
-
width: 100%;
|
|
391
|
-
font: inherit;
|
|
392
|
-
color: inherit;
|
|
393
|
-
text-align: left;
|
|
394
|
-
cursor: pointer;
|
|
395
|
-
background: transparent;
|
|
396
|
-
border: 0;
|
|
397
|
-
border-radius: 12px;
|
|
398
|
-
align-items: center;
|
|
399
|
-
gap: 12px;
|
|
400
|
-
padding: 14px 16px;
|
|
401
|
-
display: flex;
|
|
402
|
-
}
|
|
403
|
-
.qc-settings-header:focus-visible {
|
|
404
|
-
outline: 2px solid var(--dsw-alias-brand-primary, #4a9eff);
|
|
405
|
-
outline-offset: -2px;
|
|
406
|
-
}
|
|
407
|
-
.qc-settings-head-text {
|
|
408
|
-
flex-direction: column;
|
|
409
|
-
flex: 1;
|
|
410
|
-
gap: 4px;
|
|
411
|
-
min-width: 0;
|
|
412
|
-
display: flex;
|
|
413
|
-
}
|
|
414
|
-
.qc-settings-name {
|
|
415
|
-
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
416
|
-
font-size: 15px;
|
|
417
|
-
font-weight: 600;
|
|
418
|
-
line-height: 1.4;
|
|
419
|
-
}
|
|
420
|
-
.qc-settings-description {
|
|
421
|
-
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
422
|
-
font-size: 13px;
|
|
423
|
-
line-height: 1.5;
|
|
424
|
-
}
|
|
425
|
-
.qc-settings-chevron {
|
|
426
|
-
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
427
|
-
flex: none;
|
|
428
|
-
transition: transform .16s;
|
|
429
|
-
}
|
|
430
|
-
.qc-settings-chevron-open {
|
|
431
|
-
transform: rotate(180deg);
|
|
432
|
-
}
|
|
433
|
-
.qc-settings-body {
|
|
434
|
-
border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
435
|
-
margin: 0 16px;
|
|
436
|
-
padding-bottom: 12px;
|
|
437
|
-
display: flex;
|
|
438
|
-
flex-direction: column;
|
|
439
|
-
gap: 10px;
|
|
440
|
-
}
|
|
441
|
-
.qc-settings-intro {
|
|
442
|
-
font-size: 12px;
|
|
443
|
-
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
444
|
-
margin: 8px 0 0;
|
|
445
|
-
line-height: 1.5;
|
|
446
|
-
}
|
|
447
|
-
.qc-settings-anchor {
|
|
448
|
-
display: flex;
|
|
449
|
-
align-items: center;
|
|
450
|
-
gap: 12px;
|
|
451
|
-
font-size: 12px;
|
|
452
|
-
}
|
|
453
|
-
.qc-settings-anchor-label {
|
|
454
|
-
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
455
|
-
font-weight: 500;
|
|
456
|
-
}
|
|
457
|
-
.qc-settings-radio {
|
|
458
|
-
display: inline-flex;
|
|
459
|
-
align-items: center;
|
|
460
|
-
gap: 4px;
|
|
461
|
-
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
462
|
-
cursor: pointer;
|
|
463
|
-
}
|
|
464
|
-
.qc-settings-error {
|
|
465
|
-
font-size: 12px;
|
|
466
|
-
color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
467
|
-
}
|
|
468
|
-
.qc-settings-empty {
|
|
469
|
-
font-size: 12px;
|
|
470
|
-
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
471
|
-
margin: 0;
|
|
472
|
-
}
|
|
473
|
-
.qc-settings-workspace {
|
|
474
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
475
|
-
border-radius: 10px;
|
|
476
|
-
padding: 10px;
|
|
477
|
-
display: flex;
|
|
478
|
-
flex-direction: column;
|
|
479
|
-
gap: 8px;
|
|
480
|
-
}
|
|
481
|
-
.qc-settings-workspace-head {
|
|
482
|
-
display: flex;
|
|
483
|
-
align-items: baseline;
|
|
484
|
-
gap: 8px;
|
|
485
|
-
}
|
|
486
|
-
.qc-settings-workspace-title {
|
|
487
|
-
font-size: 13px;
|
|
488
|
-
font-weight: 600;
|
|
489
|
-
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
490
|
-
}
|
|
491
|
-
.qc-settings-workspace-path {
|
|
492
|
-
font-size: 11px;
|
|
493
|
-
font-family: var(--dsw-font-family-mono, monospace);
|
|
494
|
-
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
495
|
-
overflow: hidden;
|
|
496
|
-
text-overflow: ellipsis;
|
|
497
|
-
white-space: nowrap;
|
|
498
|
-
}
|
|
499
|
-
.qc-settings-no-commands {
|
|
500
|
-
font-size: 12px;
|
|
501
|
-
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
502
|
-
margin: 0;
|
|
503
|
-
}
|
|
504
|
-
.qc-settings-command {
|
|
505
|
-
display: flex;
|
|
506
|
-
gap: 6px;
|
|
507
|
-
align-items: center;
|
|
508
|
-
}
|
|
509
|
-
.qc-settings-field {
|
|
510
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
511
|
-
border-radius: 8px;
|
|
512
|
-
background: var(--dsw-alias-bg-layer-1, #2a2a2d);
|
|
513
|
-
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
514
|
-
font-size: 12px;
|
|
515
|
-
padding: 6px 8px;
|
|
516
|
-
outline: none;
|
|
517
|
-
}
|
|
518
|
-
.qc-settings-field::placeholder {
|
|
519
|
-
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
520
|
-
}
|
|
521
|
-
.qc-settings-field:focus {
|
|
522
|
-
border-color: var(--dsw-alias-brand-primary, #4a9eff);
|
|
523
|
-
}
|
|
524
|
-
.qc-settings-field-name {
|
|
525
|
-
width: 160px;
|
|
526
|
-
flex: none;
|
|
527
|
-
}
|
|
528
|
-
.qc-settings-field-command {
|
|
529
|
-
flex: 1;
|
|
530
|
-
font-family: var(--dsw-font-family-mono, monospace);
|
|
531
|
-
}
|
|
532
|
-
.qc-settings-icon-btn {
|
|
533
|
-
border: 0;
|
|
534
|
-
background: transparent;
|
|
535
|
-
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
536
|
-
cursor: pointer;
|
|
537
|
-
padding: 4px;
|
|
538
|
-
display: inline-flex;
|
|
539
|
-
align-items: center;
|
|
540
|
-
border-radius: 6px;
|
|
541
|
-
}
|
|
542
|
-
.qc-settings-icon-btn:hover {
|
|
543
|
-
background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
|
|
544
|
-
color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
545
|
-
}
|
|
546
|
-
.qc-settings-add-btn {
|
|
547
|
-
align-self: flex-start;
|
|
548
|
-
display: inline-flex;
|
|
549
|
-
align-items: center;
|
|
550
|
-
gap: 4px;
|
|
551
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
552
|
-
border-radius: 8px;
|
|
553
|
-
background: transparent;
|
|
554
|
-
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
555
|
-
font-size: 12px;
|
|
556
|
-
padding: 4px 10px;
|
|
557
|
-
cursor: pointer;
|
|
558
|
-
}
|
|
559
|
-
.qc-settings-add-btn:hover {
|
|
560
|
-
background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
373
|
/* ── mobile fallback (dsh-mobile-nav) ──────────────────────────────────────
|
|
564
374
|
The dsh-mobile-nav plugin (≤1023px) hides the ENTIRE header utilities seat
|
|
565
375
|
with `[data-mobile-nav="frame"] [data-phase] header > :first-child >
|
|
@@ -605,4 +415,4 @@
|
|
|
605
415
|
min-height: 44px;
|
|
606
416
|
justify-content: center;
|
|
607
417
|
}
|
|
608
|
-
}
|
|
418
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles for the quick-commands settings page.
|
|
3
|
+
*
|
|
4
|
+
* The page occupies the settings panel content column: a static header
|
|
5
|
+
* (title plus a one-line summary) over the always-visible command editor.
|
|
6
|
+
* Inner controls follow the built-in settings language.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* page layout */
|
|
10
|
+
|
|
11
|
+
.qc-page {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: 16px;
|
|
15
|
+
max-width: 720px;
|
|
16
|
+
}
|
|
17
|
+
.qc-page-head {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 6px;
|
|
21
|
+
}
|
|
22
|
+
.qc-page-title {
|
|
23
|
+
margin: 0;
|
|
24
|
+
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
25
|
+
font-size: 15px;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
line-height: 1.4;
|
|
28
|
+
}
|
|
29
|
+
.qc-page-sub {
|
|
30
|
+
margin: 0;
|
|
31
|
+
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
32
|
+
font-size: 13px;
|
|
33
|
+
line-height: 1.5;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* body: workspace / command-set editor */
|
|
37
|
+
|
|
38
|
+
.qc-settings-body {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: 10px;
|
|
42
|
+
}
|
|
43
|
+
.qc-settings-intro {
|
|
44
|
+
font-size: 12px;
|
|
45
|
+
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
46
|
+
margin: 0;
|
|
47
|
+
line-height: 1.5;
|
|
48
|
+
}
|
|
49
|
+
.qc-settings-anchor {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 12px;
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
}
|
|
55
|
+
.qc-settings-anchor-label {
|
|
56
|
+
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
}
|
|
59
|
+
.qc-settings-radio {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 4px;
|
|
63
|
+
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
}
|
|
66
|
+
.qc-settings-error {
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
69
|
+
}
|
|
70
|
+
.qc-settings-empty {
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
73
|
+
margin: 0;
|
|
74
|
+
}
|
|
75
|
+
.qc-settings-workspace {
|
|
76
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
77
|
+
border-radius: 10px;
|
|
78
|
+
padding: 10px;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
}
|
|
83
|
+
.qc-settings-workspace-head {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: baseline;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
}
|
|
88
|
+
.qc-settings-workspace-title {
|
|
89
|
+
font-size: 13px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
92
|
+
}
|
|
93
|
+
.qc-settings-workspace-path {
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
font-family: var(--dsw-font-family-mono, monospace);
|
|
96
|
+
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
text-overflow: ellipsis;
|
|
99
|
+
white-space: nowrap;
|
|
100
|
+
}
|
|
101
|
+
.qc-settings-no-commands {
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
104
|
+
margin: 0;
|
|
105
|
+
}
|
|
106
|
+
.qc-settings-command {
|
|
107
|
+
display: flex;
|
|
108
|
+
gap: 6px;
|
|
109
|
+
align-items: center;
|
|
110
|
+
}
|
|
111
|
+
.qc-settings-field {
|
|
112
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
background: var(--dsw-alias-bg-layer-1, #2a2a2d);
|
|
115
|
+
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
padding: 6px 8px;
|
|
118
|
+
outline: none;
|
|
119
|
+
}
|
|
120
|
+
.qc-settings-field::placeholder {
|
|
121
|
+
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
122
|
+
}
|
|
123
|
+
.qc-settings-field:focus {
|
|
124
|
+
border-color: var(--dsw-alias-brand-primary, #4a9eff);
|
|
125
|
+
}
|
|
126
|
+
.qc-settings-field-name {
|
|
127
|
+
width: 160px;
|
|
128
|
+
flex: none;
|
|
129
|
+
}
|
|
130
|
+
.qc-settings-field-command {
|
|
131
|
+
flex: 1;
|
|
132
|
+
font-family: var(--dsw-font-family-mono, monospace);
|
|
133
|
+
}
|
|
134
|
+
.qc-settings-icon-btn {
|
|
135
|
+
border: 0;
|
|
136
|
+
background: transparent;
|
|
137
|
+
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
padding: 4px;
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
border-radius: 6px;
|
|
143
|
+
}
|
|
144
|
+
.qc-settings-icon-btn:hover {
|
|
145
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
|
|
146
|
+
color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
147
|
+
}
|
|
148
|
+
.qc-settings-add-btn {
|
|
149
|
+
align-self: flex-start;
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 4px;
|
|
153
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
154
|
+
border-radius: 8px;
|
|
155
|
+
background: transparent;
|
|
156
|
+
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
157
|
+
font-size: 12px;
|
|
158
|
+
padding: 4px 10px;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
}
|
|
161
|
+
.qc-settings-add-btn:hover {
|
|
162
|
+
background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
|
|
163
|
+
}
|