@linxin666/dsh-client-ui-task-board 0.1.9 → 0.1.11
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 +46 -8
- package/lib/client.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/settings-form.d.ts +9 -1
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +31 -1
- package/src/client/board.module.css +8 -4
- package/src/client/locales.ts +2 -0
- package/src/client/settings-card.module.css +7 -0
- package/src/client/settings-form.ts +11 -2
- package/src/client/sidebar-entry.ts +9 -5
|
@@ -29,7 +29,7 @@ export interface PluginSettingsCardProps {
|
|
|
29
29
|
/**
|
|
30
30
|
* Render one plugin settings card.
|
|
31
31
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
32
|
-
* @returns the card, or nothing
|
|
32
|
+
* @returns the card, or nothing while the namespace is still loading.
|
|
33
33
|
*/
|
|
34
34
|
export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
|
|
35
35
|
/** Props every field control needs regardless of its value type. */
|
|
@@ -37,8 +37,16 @@ export interface FieldState {
|
|
|
37
37
|
}
|
|
38
38
|
/** Form state every plugin settings card shares. */
|
|
39
39
|
export interface CardShell {
|
|
40
|
-
/** False while the namespace is
|
|
40
|
+
/** False while the namespace is still loading; the card renders nothing. */
|
|
41
41
|
available: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the namespace is actually served to this client. False when the
|
|
44
|
+
* Host deployment does not expose it (e.g. the official apiproxy settings
|
|
45
|
+
* allowlist omits third-party namespaces): the card renders an explanation
|
|
46
|
+
* instead of its form, so a missing namespace never looks like a missing
|
|
47
|
+
* plugin.
|
|
48
|
+
*/
|
|
49
|
+
exposed: boolean;
|
|
42
50
|
/** Whether the Host document accepts writes. */
|
|
43
51
|
writable: boolean;
|
|
44
52
|
/** Whether the form holds edits that a save would write. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence, task details, and real execution through dsh sessions (session.prompt). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -33,7 +33,7 @@ export interface PluginSettingsCardProps {
|
|
|
33
33
|
/**
|
|
34
34
|
* Render one plugin settings card.
|
|
35
35
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
36
|
-
* @returns the card, or nothing
|
|
36
|
+
* @returns the card, or nothing while the namespace is still loading.
|
|
37
37
|
*/
|
|
38
38
|
export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
39
39
|
const [open, setOpen] = useState(false)
|
|
@@ -41,6 +41,36 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
41
41
|
if (!state.available) return null
|
|
42
42
|
const title = props.t(props.titleKey)
|
|
43
43
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
44
|
+
// The namespace exists but the Host does not serve it to this client (the
|
|
45
|
+
// official settings allowlist omits third-party namespaces): show a card
|
|
46
|
+
// that explains the gap instead of vanishing, so a missing card never
|
|
47
|
+
// reads as a missing plugin.
|
|
48
|
+
if (!state.exposed) {
|
|
49
|
+
return (
|
|
50
|
+
<li className={css.card}>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className={css.header}
|
|
54
|
+
aria-expanded={open}
|
|
55
|
+
aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}
|
|
56
|
+
onClick={() => { setOpen(!open) }}
|
|
57
|
+
>
|
|
58
|
+
<span className={css.headText}>
|
|
59
|
+
<span className={css.name}>{title}</span>
|
|
60
|
+
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
61
|
+
</span>
|
|
62
|
+
<span className={open ? css.chevronOpen : css.chevron}>▾</span>
|
|
63
|
+
</button>
|
|
64
|
+
{open
|
|
65
|
+
? (
|
|
66
|
+
<div className={css.body}>
|
|
67
|
+
<p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
: null}
|
|
71
|
+
</li>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
44
74
|
return (
|
|
45
75
|
<li className={css.card}>
|
|
46
76
|
<button
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
position: absolute;
|
|
17
17
|
inset: 0;
|
|
18
18
|
display: none;
|
|
19
|
-
z-index:
|
|
19
|
+
z-index: 60;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/* The center column is single-occupant; the :not() guards keep the two
|
|
@@ -27,9 +27,13 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-v
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/* While the board is active, the conversation content underneath is hidden
|
|
30
|
-
(it stays mounted and stateful).
|
|
30
|
+
(it stays mounted and stateful). The !important is required: the dsh shell
|
|
31
|
+
(0.1.0-rc.6) wraps the conversation view in a node with an inline
|
|
32
|
+
`display: contents`, and inline styles beat a plain stylesheet rule. Without
|
|
33
|
+
it the composer (input card) stays visible at the bottom and paints over the
|
|
34
|
+
board modals' footer, hiding the run/delete buttons (issue #76). */
|
|
31
35
|
html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane='conversation'] > :not([data-dsh-taskboard-view]) {
|
|
32
|
-
display: none;
|
|
36
|
+
display: none !important;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/* --- sidebar entry row ------------------------------------------------------- */
|
|
@@ -426,7 +430,7 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane='conversat
|
|
|
426
430
|
.modalBackdrop {
|
|
427
431
|
position: fixed;
|
|
428
432
|
inset: 0;
|
|
429
|
-
z-index:
|
|
433
|
+
z-index: 1300;
|
|
430
434
|
display: flex;
|
|
431
435
|
align-items: center;
|
|
432
436
|
justify-content: center;
|
package/src/client/locales.ts
CHANGED
|
@@ -85,6 +85,7 @@ export const zh = {
|
|
|
85
85
|
'settings.off': '关',
|
|
86
86
|
'settings.overridden': '已覆盖',
|
|
87
87
|
'settings.reset': '恢复默认',
|
|
88
|
+
'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',
|
|
88
89
|
'settings.readOnly': '当前部署的设置只读。',
|
|
89
90
|
'settings.expand': '展开设置',
|
|
90
91
|
'settings.collapse': '收起设置',
|
|
@@ -176,6 +177,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
176
177
|
'settings.off': 'Off',
|
|
177
178
|
'settings.overridden': 'Overridden',
|
|
178
179
|
'settings.reset': 'Reset to default',
|
|
180
|
+
'settings.notExposed': 'This DSH version does not expose this plugin\'s settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy\'s WEB_SETTINGS_NAMESPACES allowlist and restart.',
|
|
179
181
|
'settings.readOnly': 'This deployment stores settings read-only.',
|
|
180
182
|
'settings.expand': 'Show settings',
|
|
181
183
|
'settings.collapse': 'Hide settings',
|
|
@@ -40,8 +40,16 @@ export interface FieldState {
|
|
|
40
40
|
|
|
41
41
|
/** Form state every plugin settings card shares. */
|
|
42
42
|
export interface CardShell {
|
|
43
|
-
/** False while the namespace is
|
|
43
|
+
/** False while the namespace is still loading; the card renders nothing. */
|
|
44
44
|
available: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Whether the namespace is actually served to this client. False when the
|
|
47
|
+
* Host deployment does not expose it (e.g. the official apiproxy settings
|
|
48
|
+
* allowlist omits third-party namespaces): the card renders an explanation
|
|
49
|
+
* instead of its form, so a missing namespace never looks like a missing
|
|
50
|
+
* plugin.
|
|
51
|
+
*/
|
|
52
|
+
exposed: boolean
|
|
45
53
|
/** Whether the Host document accepts writes. */
|
|
46
54
|
writable: boolean
|
|
47
55
|
/** Whether the form holds edits that a save would write. */
|
|
@@ -157,7 +165,8 @@ export class CardForm<T> {
|
|
|
157
165
|
const snapshot = this.scope.getSnapshot()
|
|
158
166
|
const plan = this.plan()
|
|
159
167
|
return {
|
|
160
|
-
available: snapshot.status
|
|
168
|
+
available: snapshot.status !== 'loading',
|
|
169
|
+
exposed: snapshot.status === 'ready',
|
|
161
170
|
writable: snapshot.writable,
|
|
162
171
|
dirty: plan.length > 0,
|
|
163
172
|
invalid: plan.some(item => item.run === undefined),
|
|
@@ -139,11 +139,15 @@ export function mountSidebarEntry(controller: BoardController): () => void {
|
|
|
139
139
|
}
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
-
// Reflect the board's open state on the row (active highlight).
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
// Reflect the board's open state on the row (active highlight). Note: assigning
|
|
143
|
+
// undefined to dataset.active materializes data-active="undefined" and keeps the
|
|
144
|
+
// row permanently highlighted — delete the attribute instead.
|
|
145
|
+
const syncActive = () => {
|
|
146
|
+
if (controller.getSnapshot().boardOpen) entry.dataset.active = 'true'
|
|
147
|
+
else delete entry.dataset.active
|
|
148
|
+
}
|
|
149
|
+
const unsubscribe = controller.subscribe(syncActive)
|
|
150
|
+
syncActive()
|
|
147
151
|
|
|
148
152
|
tryPlace()
|
|
149
153
|
|