@linxin666/dsh-client-ui-task-board 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +53 -19
- package/lib/client.js.map +1 -1
- package/lib/types/client/TaskBoardSettingsCard.d.ts +5 -0
- package/lib/types/client/settings-form.d.ts +8 -0
- package/package.json +1 -1
- package/src/client/TaskBoardSettingsCard.tsx +8 -0
- package/src/client/board/TaskBoard.tsx +9 -7
- package/src/client/board.module.css +68 -1
- package/src/client/index.ts +13 -7
- package/src/client/locales.ts +1 -1
- package/src/client/settings-form.ts +15 -1
- package/src/core/controller.ts +4 -0
|
@@ -40,6 +40,11 @@ export declare class TaskBoardSettingsCardController {
|
|
|
40
40
|
* @returns the card's snapshot and its form actions.
|
|
41
41
|
*/
|
|
42
42
|
inject(): TaskBoardSettingsCardFace;
|
|
43
|
+
/**
|
|
44
|
+
* Release the card's scope subscription and bound stores; the slot
|
|
45
|
+
* disposer calls this on teardown.
|
|
46
|
+
*/
|
|
47
|
+
dispose(): void;
|
|
43
48
|
}
|
|
44
49
|
/** Props the renderer binds for the task-board card. */
|
|
45
50
|
export type TaskBoardSettingsCardProps = PropsRuntime<'web-ui.plugin.item'> & PropsLocale<'task-board'> & InjectFace<TaskBoardSettingsCardFace>;
|
|
@@ -149,11 +149,19 @@ export declare class CardForm<T> {
|
|
|
149
149
|
private readonly specs;
|
|
150
150
|
private readonly staged;
|
|
151
151
|
private readonly listeners;
|
|
152
|
+
/** The scope subscription installed in the constructor; released by dispose(). */
|
|
153
|
+
private readonly disposeScope;
|
|
154
|
+
private disposed;
|
|
152
155
|
private saving;
|
|
153
156
|
private failed;
|
|
154
157
|
private failedReason;
|
|
155
158
|
/** @param scope - the bound settings scope for this card's namespace. */
|
|
156
159
|
constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
|
|
160
|
+
/**
|
|
161
|
+
* Release the scope subscription and every bound store listener. The card
|
|
162
|
+
* must call this on teardown; later calls are no-ops.
|
|
163
|
+
*/
|
|
164
|
+
dispose(): void;
|
|
157
165
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
158
166
|
bind<S>(project: () => S): SnapshotStore<S>;
|
|
159
167
|
/** Read the card-level state: what the Host serves, and what a save would do. */
|
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.19",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -63,6 +63,14 @@ export class TaskBoardSettingsCardController {
|
|
|
63
63
|
inject(): TaskBoardSettingsCardFace {
|
|
64
64
|
return { hooks: { taskBoardSettingsCard: this.store }, ...this.form.actions() }
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Release the card's scope subscription and bound stores; the slot
|
|
69
|
+
* disposer calls this on teardown.
|
|
70
|
+
*/
|
|
71
|
+
dispose(): void {
|
|
72
|
+
this.form.dispose()
|
|
73
|
+
}
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/** Props the renderer binds for the task-board card. */
|
|
@@ -60,6 +60,15 @@ export function TaskBoard({ controller }: { controller: BoardController }) {
|
|
|
60
60
|
return (
|
|
61
61
|
<div className={css.board} data-dsh-taskboard-board="">
|
|
62
62
|
<header className={css.boardHeader}>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
className={`${css.ghostButton} ${css.backButton}`}
|
|
66
|
+
aria-label={t('board.close')}
|
|
67
|
+
onClick={() => { controller.closeBoard() }}
|
|
68
|
+
>
|
|
69
|
+
<span aria-hidden="true">‹</span>
|
|
70
|
+
<span>{t('board.close')}</span>
|
|
71
|
+
</button>
|
|
63
72
|
<h2 className={css.boardTitle}>{t('board.title')}</h2>
|
|
64
73
|
<input
|
|
65
74
|
className={css.search}
|
|
@@ -85,13 +94,6 @@ export function TaskBoard({ controller }: { controller: BoardController }) {
|
|
|
85
94
|
>
|
|
86
95
|
+ {t('board.new')}
|
|
87
96
|
</button>
|
|
88
|
-
<button
|
|
89
|
-
type="button"
|
|
90
|
-
className={css.ghostButton}
|
|
91
|
-
onClick={() => { controller.closeBoard() }}
|
|
92
|
-
>
|
|
93
|
-
{t('board.close')}
|
|
94
|
-
</button>
|
|
95
97
|
</header>
|
|
96
98
|
|
|
97
99
|
<div className={css.columns}>
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
inset: 0;
|
|
19
19
|
display: none;
|
|
20
20
|
z-index: 60;
|
|
21
|
+
container-name: task-board-view;
|
|
22
|
+
container-type: inline-size;
|
|
21
23
|
/* Opaque backdrop: the conversation subtree stays mounted under the view.
|
|
22
24
|
Even if the hide rule below were defeated by a stronger host inline
|
|
23
25
|
style, this background keeps sticky code-block banners (host z-index 6)
|
|
@@ -103,6 +105,7 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*='centerCol']
|
|
|
103
105
|
.board {
|
|
104
106
|
display: flex;
|
|
105
107
|
flex-direction: column;
|
|
108
|
+
box-sizing: border-box;
|
|
106
109
|
height: 100%;
|
|
107
110
|
min-width: 0;
|
|
108
111
|
min-height: 0;
|
|
@@ -128,6 +131,12 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*='centerCol']
|
|
|
128
131
|
white-space: nowrap;
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
.backButton {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 4px;
|
|
138
|
+
}
|
|
139
|
+
|
|
131
140
|
.search {
|
|
132
141
|
flex: 0 1 260px;
|
|
133
142
|
min-width: 120px;
|
|
@@ -148,10 +157,38 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*='centerCol']
|
|
|
148
157
|
|
|
149
158
|
.columns {
|
|
150
159
|
display: grid;
|
|
151
|
-
grid-
|
|
160
|
+
grid-auto-flow: column;
|
|
161
|
+
grid-auto-columns: minmax(220px, 1fr);
|
|
152
162
|
gap: 12px;
|
|
153
163
|
flex: 1;
|
|
154
164
|
min-height: 0;
|
|
165
|
+
overflow-x: auto;
|
|
166
|
+
overflow-y: hidden;
|
|
167
|
+
overscroll-behavior-inline: contain;
|
|
168
|
+
padding-bottom: 6px;
|
|
169
|
+
scrollbar-color: var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);
|
|
170
|
+
scrollbar-width: thin;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.columns::-webkit-scrollbar {
|
|
174
|
+
height: 10px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.columns::-webkit-scrollbar-track {
|
|
178
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
179
|
+
border-radius: 999px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.columns::-webkit-scrollbar-thumb {
|
|
183
|
+
background: var(--dsw-alias-border-l3);
|
|
184
|
+
background-clip: content-box;
|
|
185
|
+
border: 2px solid transparent;
|
|
186
|
+
border-radius: 999px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.columns::-webkit-scrollbar-thumb:hover {
|
|
190
|
+
background: var(--dsw-alias-border-l4);
|
|
191
|
+
background-clip: content-box;
|
|
155
192
|
}
|
|
156
193
|
|
|
157
194
|
.column {
|
|
@@ -770,6 +807,36 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*='centerCol']
|
|
|
770
807
|
color: var(--dsw-alias-label-tertiary);
|
|
771
808
|
}
|
|
772
809
|
|
|
810
|
+
/* --- constrained board containers ------------------------------------------ */
|
|
811
|
+
|
|
812
|
+
@container task-board-view (max-width: 720px) {
|
|
813
|
+
.board {
|
|
814
|
+
gap: 10px;
|
|
815
|
+
padding: 10px;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.columns {
|
|
819
|
+
gap: 10px;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
@container task-board-view (max-width: 600px) {
|
|
824
|
+
.boardHeader {
|
|
825
|
+
flex-wrap: wrap;
|
|
826
|
+
gap: 8px;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.search {
|
|
830
|
+
flex: 1 1 calc(100% - 72px);
|
|
831
|
+
min-width: 0;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.boardHeader > button {
|
|
835
|
+
flex: 1 1 0;
|
|
836
|
+
min-width: max-content;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
773
840
|
/* -----------------------------------------------------------------------------
|
|
774
841
|
* Polish pass: complete interaction states (hover / active / focus-visible /
|
|
775
842
|
* disabled), unified 120ms motion, and reduced-motion fallback.
|
package/src/client/index.ts
CHANGED
|
@@ -92,13 +92,19 @@ export function apply(ctx: ClientContext): void {
|
|
|
92
92
|
const binder = ctx.get('webUiSettings') ?? ctx.settingsScope
|
|
93
93
|
const settingsScope = binder.bind<TaskBoardSettings>({ namespace: TASK_BOARD_NS })
|
|
94
94
|
const settingsCard = new TaskBoardSettingsCardController(settingsScope)
|
|
95
|
-
ctx.slots.inject('web-ui.plugin.item', () =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
ctx.slots.inject('web-ui.plugin.item', () => {
|
|
96
|
+
const unregister = ctx.slots.register({
|
|
97
|
+
name: 'web-ui.plugin.item',
|
|
98
|
+
id: 'task-board',
|
|
99
|
+
order: 110,
|
|
100
|
+
locale: NS,
|
|
101
|
+
inject: () => settingsCard.inject(),
|
|
102
|
+
}, TaskBoardSettingsCard)
|
|
103
|
+
return () => {
|
|
104
|
+
settingsCard.dispose()
|
|
105
|
+
unregister()
|
|
106
|
+
}
|
|
107
|
+
})
|
|
102
108
|
|
|
103
109
|
// The sidebar entry and board view mount once the settings scope settles;
|
|
104
110
|
// while the scope is still loading, the composition default is unknown, so
|
package/src/client/locales.ts
CHANGED
|
@@ -234,6 +234,9 @@ export class CardForm<T> {
|
|
|
234
234
|
private readonly specs: Map<string, FieldSpec>
|
|
235
235
|
private readonly staged = new Map<string, StagedEdit>()
|
|
236
236
|
private readonly listeners = new Set<() => void>()
|
|
237
|
+
/** The scope subscription installed in the constructor; released by dispose(). */
|
|
238
|
+
private readonly disposeScope: () => void
|
|
239
|
+
private disposed = false
|
|
237
240
|
private saving = false
|
|
238
241
|
private failed = false
|
|
239
242
|
private failedReason: string | undefined
|
|
@@ -244,7 +247,18 @@ export class CardForm<T> {
|
|
|
244
247
|
specs: FieldSpec[],
|
|
245
248
|
) {
|
|
246
249
|
this.specs = new Map(specs.map(spec => [spec.field, spec]))
|
|
247
|
-
scope.subscribe(() => { this.publish() })
|
|
250
|
+
this.disposeScope = scope.subscribe(() => { this.publish() })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Release the scope subscription and every bound store listener. The card
|
|
255
|
+
* must call this on teardown; later calls are no-ops.
|
|
256
|
+
*/
|
|
257
|
+
dispose(): void {
|
|
258
|
+
if (this.disposed) return
|
|
259
|
+
this.disposed = true
|
|
260
|
+
this.disposeScope()
|
|
261
|
+
this.listeners.clear()
|
|
248
262
|
}
|
|
249
263
|
|
|
250
264
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
package/src/core/controller.ts
CHANGED
|
@@ -89,6 +89,10 @@ export function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undef
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function randomUuid(): string {
|
|
92
|
+
const randomUUID = globalThis.crypto?.randomUUID
|
|
93
|
+
if (randomUUID !== undefined) {
|
|
94
|
+
return randomUUID.call(globalThis.crypto!)
|
|
95
|
+
}
|
|
92
96
|
const bytes = globalThis.crypto?.getRandomValues(new Uint8Array(16))
|
|
93
97
|
if (bytes === undefined) {
|
|
94
98
|
// Non-secure fallback (tests, odd environments).
|