@skillcdn/console 0.3.2 → 0.4.1
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.md +4 -3
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +132 -18
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +6 -4
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +72 -21
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +7 -2
- package/dist/cli/pack.d.ts.map +1 -1
- package/dist/cli/pack.js +16 -2
- package/dist/cli/pack.js.map +1 -1
- package/dist/client.d.ts +19 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +12 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +52 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +4 -0
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +61 -22
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-form.d.ts +12 -4
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -6
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +4 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +9 -6
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -3
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +63 -8
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +6 -3
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/my-decisions.d.ts +20 -0
- package/dist/components/my-decisions.d.ts.map +1 -0
- package/dist/components/my-decisions.js +36 -0
- package/dist/components/my-decisions.js.map +1 -0
- package/dist/components/pack-connect.d.ts +3 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +4 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/pack-token.d.ts +3 -1
- package/dist/components/pack-token.d.ts.map +1 -1
- package/dist/components/pack-token.js +1 -1
- package/dist/components/pack-token.js.map +1 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +14 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +3 -3
- package/dist/components/projects.js.map +1 -1
- package/dist/components/remote-pictures.d.ts +6 -0
- package/dist/components/remote-pictures.d.ts.map +1 -0
- package/dist/components/remote-pictures.js +8 -0
- package/dist/components/remote-pictures.js.map +1 -0
- package/dist/components/roadmap.js +1 -1
- package/dist/components/roadmap.js.map +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +1 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +36 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/sign-in.d.ts.map +1 -1
- package/dist/components/sign-in.js +49 -2
- package/dist/components/sign-in.js.map +1 -1
- package/dist/components/task-list.js +1 -1
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -2
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +13 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +21 -14
- package/dist/components/ui.js.map +1 -1
- package/dist/console.css +1686 -425
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +83 -13
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +15 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +18 -19
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +48 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +50 -3
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +47 -4
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +14 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +14 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +9 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +5 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +5 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.js +1 -1
- package/dist/rules.js.map +1 -1
- package/dist/schemas.d.ts +272 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +33 -8
- package/dist/schemas.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +3 -2
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +11 -10
- package/docs/specs/rest.md +33 -17
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +5 -5
- package/skills/building-a-console/references/the-default-console.md +4 -4
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/working-the-board/SKILL.md +6 -4
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +1 -1
- package/src/api.ts +10 -0
- package/src/cli/cli.ts +162 -19
- package/src/cli/format.ts +11 -4
- package/src/cli/help.ts +79 -20
- package/src/cli/pack.ts +17 -1
- package/src/client.ts +51 -2
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +114 -21
- package/src/components/calendar.tsx +207 -102
- package/src/components/decision-form.tsx +71 -23
- package/src/components/decision-list.tsx +30 -3
- package/src/components/documents.tsx +27 -11
- package/src/components/event-feed.tsx +116 -33
- package/src/components/markdown.tsx +6 -3
- package/src/components/my-decisions.tsx +138 -0
- package/src/components/pack-connect.tsx +10 -7
- package/src/components/pack-token.tsx +4 -1
- package/src/components/people.tsx +20 -8
- package/src/components/projects.tsx +7 -5
- package/src/components/remote-pictures.ts +19 -0
- package/src/components/roadmap.tsx +1 -1
- package/src/components/runs.tsx +5 -1
- package/src/components/shell.tsx +36 -1
- package/src/components/sign-in.tsx +114 -13
- package/src/components/task-list.tsx +1 -1
- package/src/components/task-view.tsx +48 -47
- package/src/components/ui.tsx +70 -14
- package/src/console.tsx +153 -32
- package/src/data.ts +37 -20
- package/src/i18n/en.ts +53 -3
- package/src/i18n/ko.ts +50 -4
- package/src/index.ts +3 -0
- package/src/limits.ts +14 -0
- package/src/router.ts +18 -1
- package/src/routes.ts +5 -1
- package/src/rules.ts +1 -1
- package/src/schemas.ts +37 -8
- package/src/styles/console.css +1686 -425
- package/src/web.ts +3 -2
package/src/cli/pack.ts
CHANGED
|
@@ -264,11 +264,20 @@ export function joinFolder(
|
|
|
264
264
|
if (first === undefined || last === undefined) {
|
|
265
265
|
return undefined;
|
|
266
266
|
}
|
|
267
|
+
// A file's path answers its outline (SkillCDN 0.4), its headings paged as a folder's entries are.
|
|
268
|
+
const outline =
|
|
269
|
+
first.outline === undefined
|
|
270
|
+
? undefined
|
|
271
|
+
: {
|
|
272
|
+
...first.outline,
|
|
273
|
+
headings: results.flatMap((result) => result.outline?.headings ?? []),
|
|
274
|
+
};
|
|
267
275
|
return {
|
|
268
276
|
...first,
|
|
269
277
|
mount: last.mount,
|
|
270
278
|
entries: results.flatMap((result) => result.entries),
|
|
271
279
|
nextCursor: last.nextCursor,
|
|
280
|
+
...(outline === undefined ? {} : { outline }),
|
|
272
281
|
};
|
|
273
282
|
}
|
|
274
283
|
|
|
@@ -304,10 +313,16 @@ export function formatSkillFind(found: RestSkillFind, summary: PackSummary | und
|
|
|
304
313
|
return renderFindResult(findResultOf(found.read, summary), CONSOLE_WORDS);
|
|
305
314
|
}
|
|
306
315
|
|
|
307
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* The pages of a file, as the console answered them, as the file whole; or the folder the path
|
|
318
|
+
* named. With `requested`, the one page is a section read as asked (`--offset`, `--limit`): the
|
|
319
|
+
* result carries the length asked for, so that the renderer says where the file continues rather
|
|
320
|
+
* than asking for the rest.
|
|
321
|
+
*/
|
|
308
322
|
export function joinFile(
|
|
309
323
|
pages: readonly RestSkillFile[],
|
|
310
324
|
summary: PackSummary,
|
|
325
|
+
requested?: number,
|
|
311
326
|
): FileResult | DirectoryResult | undefined {
|
|
312
327
|
const reads = pages.flatMap((page) => (page.read === null ? [] : [page.read]));
|
|
313
328
|
const first = reads[0];
|
|
@@ -331,6 +346,7 @@ export function joinFile(
|
|
|
331
346
|
content: files.map((file) => file.content).join(""),
|
|
332
347
|
nextOffset: closing.nextOffset,
|
|
333
348
|
totalLength: closing.totalLength,
|
|
349
|
+
...(requested === undefined ? {} : { requestedLength: requested }),
|
|
334
350
|
};
|
|
335
351
|
}
|
|
336
352
|
|
package/src/client.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type RestMemberPatch,
|
|
45
45
|
type RestMembers,
|
|
46
46
|
type RestMePatch,
|
|
47
|
+
type RestMyDecisions,
|
|
47
48
|
type RestPackConnect,
|
|
48
49
|
type RestPackConnectStarted,
|
|
49
50
|
type RestPackTokenInput,
|
|
@@ -101,6 +102,7 @@ import {
|
|
|
101
102
|
restMemberSchema,
|
|
102
103
|
restMembersSchema,
|
|
103
104
|
restMeSchema,
|
|
105
|
+
restMyDecisionsSchema,
|
|
104
106
|
restPackConnectSchema,
|
|
105
107
|
restPackConnectStartedSchema,
|
|
106
108
|
restPeopleSchema,
|
|
@@ -206,6 +208,8 @@ export interface EventFilter {
|
|
|
206
208
|
readonly run?: string | undefined;
|
|
207
209
|
readonly decision?: string | undefined;
|
|
208
210
|
readonly document?: string | undefined;
|
|
211
|
+
/** True for the newest events, oldest first, and whether there are earlier ones; `after` is not looked at then. */
|
|
212
|
+
readonly latest?: boolean | undefined;
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
/** One page of a listing: how many items, from which one on; the server's bounds apply. */
|
|
@@ -362,8 +366,14 @@ export interface ProjectClient {
|
|
|
362
366
|
removePackToken(): Promise<RestProject>;
|
|
363
367
|
/** Asks the deployment to connect the project's pack with a code a person approves (ADR-0036); for an owner. */
|
|
364
368
|
connectPackToken(): Promise<RestPackConnectStarted>;
|
|
365
|
-
/** How that connection stands
|
|
369
|
+
/** How that connection stands, read and changed nothing: pending, ended, or none; for an owner. */
|
|
366
370
|
packTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
371
|
+
/**
|
|
372
|
+
* Asks the console to claim the token at the deployment (ADR-0046): pending while the person
|
|
373
|
+
* has not approved, connected once, the token kept and the project as it stands, or ended;
|
|
374
|
+
* for an owner.
|
|
375
|
+
*/
|
|
376
|
+
claimPackTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
367
377
|
/** Gives the connection up; for an owner. */
|
|
368
378
|
cancelPackTokenConnection(): Promise<void>;
|
|
369
379
|
/** The rules that hold in the project (ADR-0023): the workspace's, and what the project adds. */
|
|
@@ -412,6 +422,7 @@ export interface ConsoleClient {
|
|
|
412
422
|
removeWorkspacePackToken(): Promise<RestWorkspace>;
|
|
413
423
|
connectWorkspacePackToken(): Promise<RestPackConnectStarted>;
|
|
414
424
|
workspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
425
|
+
claimWorkspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
415
426
|
cancelWorkspacePackTokenConnection(): Promise<void>;
|
|
416
427
|
/** The people the asker may see: everyone, or those they share a project with (ADR-0035). */
|
|
417
428
|
people(signal?: AbortSignal): Promise<RestPeople>;
|
|
@@ -434,6 +445,8 @@ export interface ConsoleClient {
|
|
|
434
445
|
project(key: string): ProjectClient;
|
|
435
446
|
/** The workspace's own events, the ones about no project, after event number `after`. */
|
|
436
447
|
events(after: number, signal?: AbortSignal): Promise<RestEvents>;
|
|
448
|
+
/** Where a picture from elsewhere is read through the console (ADR-0048), given the https URL that names it. */
|
|
449
|
+
pictureUrl(url: string): string;
|
|
437
450
|
/** The categories tasks are sorted into (ADR-0019), by name. */
|
|
438
451
|
categories(signal?: AbortSignal): Promise<RestCategories>;
|
|
439
452
|
/** The workspace's rules (ADR-0023): the console's own until an administrator writes the organization's. */
|
|
@@ -449,8 +462,13 @@ export interface ConsoleClient {
|
|
|
449
462
|
updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
|
|
450
463
|
/** Removes a category no task uses; for an administrator signed in. */
|
|
451
464
|
removeCategory(key: string): Promise<void>;
|
|
452
|
-
/** What is on the asker's plate across the projects they may see: their open tasks, and the decisions waiting. */
|
|
465
|
+
/** What is on the asker's plate across the projects they may see: their open tasks, and the decisions waiting for them. */
|
|
453
466
|
work(signal?: AbortSignal): Promise<RestWork>;
|
|
467
|
+
/** The decisions addressed to the asker (ADR-0042) across the projects they may see, or with `raised` those they raised for someone else: a page, the waiting first; or only those. */
|
|
468
|
+
myDecisions(
|
|
469
|
+
filter?: { readonly open?: boolean; readonly raised?: boolean } & Page,
|
|
470
|
+
signal?: AbortSignal,
|
|
471
|
+
): Promise<RestMyDecisions>;
|
|
454
472
|
/** Another person's plate, within what the asker may see: for an administrator, or an owner of a project they are in. */
|
|
455
473
|
personWork(personId: string, signal?: AbortSignal): Promise<RestPersonWork>;
|
|
456
474
|
/** The tokens of whoever asks. */
|
|
@@ -745,6 +763,7 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
745
763
|
"GET",
|
|
746
764
|
withQuery(at("events"), {
|
|
747
765
|
after: String(after),
|
|
766
|
+
latest: filter.latest === true ? "true" : undefined,
|
|
748
767
|
task: filter.task,
|
|
749
768
|
run: filter.run,
|
|
750
769
|
decision: filter.decision,
|
|
@@ -821,6 +840,14 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
821
840
|
restPackConnectSchema,
|
|
822
841
|
signal,
|
|
823
842
|
),
|
|
843
|
+
claimPackTokenConnection: (signal) =>
|
|
844
|
+
request(
|
|
845
|
+
"POST",
|
|
846
|
+
`${base}${at()}/pack-token/connect/claim`,
|
|
847
|
+
undefined,
|
|
848
|
+
restPackConnectSchema,
|
|
849
|
+
signal,
|
|
850
|
+
),
|
|
824
851
|
cancelPackTokenConnection: () =>
|
|
825
852
|
request("DELETE", `${base}${at()}/pack-token/connect`, undefined, undefined),
|
|
826
853
|
rules: (signal) =>
|
|
@@ -924,6 +951,14 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
924
951
|
restWorkspacePackConnectSchema,
|
|
925
952
|
signal,
|
|
926
953
|
),
|
|
954
|
+
claimWorkspacePackTokenConnection: (signal) =>
|
|
955
|
+
request(
|
|
956
|
+
"POST",
|
|
957
|
+
`${base}${REST_ROUTES.workspacePackToken}/connect/claim`,
|
|
958
|
+
undefined,
|
|
959
|
+
restWorkspacePackConnectSchema,
|
|
960
|
+
signal,
|
|
961
|
+
),
|
|
927
962
|
cancelWorkspacePackTokenConnection: () =>
|
|
928
963
|
request("DELETE", `${base}${REST_ROUTES.workspacePackToken}/connect`, undefined, undefined),
|
|
929
964
|
updatePerson: (id, patch) =>
|
|
@@ -945,6 +980,7 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
945
980
|
restEventsSchema,
|
|
946
981
|
signal,
|
|
947
982
|
),
|
|
983
|
+
pictureUrl: (url) => withQuery(REST_ROUTES.pictures, { src: url }),
|
|
948
984
|
categories: (signal) =>
|
|
949
985
|
request("GET", `${base}${REST_ROUTES.categories}`, undefined, restCategoriesSchema, signal),
|
|
950
986
|
rules: (signal) =>
|
|
@@ -961,6 +997,19 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
961
997
|
removeCategory: (key) => request("DELETE", `${base}${categoryPath(key)}`, undefined, undefined),
|
|
962
998
|
work: (signal) =>
|
|
963
999
|
request("GET", `${base}${REST_ROUTES.work}`, undefined, restWorkSchema, signal),
|
|
1000
|
+
myDecisions: (filter = {}, signal) =>
|
|
1001
|
+
request(
|
|
1002
|
+
"GET",
|
|
1003
|
+
withQuery(REST_ROUTES.decisions, {
|
|
1004
|
+
open: flag(filter.open),
|
|
1005
|
+
raised: filter.raised === true ? "true" : undefined,
|
|
1006
|
+
limit: number(filter.limit),
|
|
1007
|
+
offset: number(filter.offset),
|
|
1008
|
+
}),
|
|
1009
|
+
undefined,
|
|
1010
|
+
restMyDecisionsSchema,
|
|
1011
|
+
signal,
|
|
1012
|
+
),
|
|
964
1013
|
personWork: (personId, signal) =>
|
|
965
1014
|
request("GET", `${base}${personWorkPath(personId)}`, undefined, restPersonWorkSchema, signal),
|
|
966
1015
|
tokens: (signal) =>
|
package/src/compat.ts
CHANGED
|
@@ -113,7 +113,7 @@ export interface ClientPolicy {
|
|
|
113
113
|
* last changed what the command reads or sends (ADR-0038), whose changelog entry begins with
|
|
114
114
|
* `Breaking:`; `pnpm check` holds it to a version the package has, or is about to have.
|
|
115
115
|
*/
|
|
116
|
-
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.
|
|
116
|
+
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.1" };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* The oldest console this command works with (ADR-0038): raised by the release that last
|
|
@@ -122,7 +122,7 @@ export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.3.2" };
|
|
|
122
122
|
* itself, naming the command to install instead; `pnpm check` holds it as it holds the
|
|
123
123
|
* console's floor.
|
|
124
124
|
*/
|
|
125
|
-
export const CONSOLE_AT_LEAST = "0.
|
|
125
|
+
export const CONSOLE_AT_LEAST = "0.4.1";
|
|
126
126
|
|
|
127
127
|
/** `common` from `atLeast` on, where that floor is within it; `common` as it is otherwise. */
|
|
128
128
|
function narrowed(common: VersionRange, atLeast: string | undefined): VersionRange {
|
package/src/components/board.tsx
CHANGED
|
@@ -59,8 +59,17 @@ export interface BoardProps {
|
|
|
59
59
|
readonly onEdit?: ((task: RestTask) => void) | undefined;
|
|
60
60
|
/** Called when a task is put away from its card's menu (ADR-0029, ADR-0030). Left out, the menu offers nothing of it. */
|
|
61
61
|
readonly onArchive?: ((task: RestTask) => void) | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The columns folded to a strip until a person opens them, with their count and the way to open
|
|
64
|
+
* them; a folded column still takes a card dragged onto it. What is finished, by default, so that
|
|
65
|
+
* the work at hand has the room; `[]` folds none.
|
|
66
|
+
*/
|
|
67
|
+
readonly folded?: readonly TaskState[] | undefined;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/** The columns a board folds unless told otherwise: the work that is over. */
|
|
71
|
+
export const FOLDED_STATES: readonly TaskState[] = ["done", "dropped"];
|
|
72
|
+
|
|
64
73
|
/** How far a mouse travels on a card before the press is a drag and not a click. */
|
|
65
74
|
const DRAG_SLACK = 4;
|
|
66
75
|
/**
|
|
@@ -160,7 +169,7 @@ export function TaskCard(props: TaskCardProps) {
|
|
|
160
169
|
<DueBadge dueOn={task.dueOn} state={task.state} today={props.today} />
|
|
161
170
|
)}
|
|
162
171
|
{task.openDecisions > 0 && (
|
|
163
|
-
<Badge tone="warning" title={words.board.decisionsTitle}>
|
|
172
|
+
<Badge tone="warning" kind="count" title={words.board.decisionsTitle}>
|
|
164
173
|
{words.board.decisions(task.openDecisions)}
|
|
165
174
|
</Badge>
|
|
166
175
|
)}
|
|
@@ -364,6 +373,19 @@ export function Board(props: BoardProps) {
|
|
|
364
373
|
const { onMove } = props;
|
|
365
374
|
const board = useRef<HTMLDivElement>(null);
|
|
366
375
|
const [drag, setDrag] = useState<Drag | null>(null);
|
|
376
|
+
// The foldable columns a person has opened on this board; the others of them stay folded.
|
|
377
|
+
const [opened, setOpened] = useState<ReadonlySet<TaskState>>(() => new Set());
|
|
378
|
+
const foldable = props.folded ?? FOLDED_STATES;
|
|
379
|
+
const toggle = (state: TaskState) =>
|
|
380
|
+
setOpened((before) => {
|
|
381
|
+
const after = new Set(before);
|
|
382
|
+
if (after.has(state)) {
|
|
383
|
+
after.delete(state);
|
|
384
|
+
} else {
|
|
385
|
+
after.add(state);
|
|
386
|
+
}
|
|
387
|
+
return after;
|
|
388
|
+
});
|
|
367
389
|
// What the pointer's release reads, so that the move is made outside a state update.
|
|
368
390
|
const current = useRef<Drag | null>(null);
|
|
369
391
|
current.current = drag;
|
|
@@ -511,38 +533,65 @@ export function Board(props: BoardProps) {
|
|
|
511
533
|
>
|
|
512
534
|
{TASK_STATES.map((state) => {
|
|
513
535
|
const tasks = props.tasks.filter((task) => task.state === state).sort(byDue);
|
|
536
|
+
const name = words.vocabulary.state[state];
|
|
537
|
+
const canFold = foldable.includes(state);
|
|
538
|
+
const folded = canFold && !opened.has(state);
|
|
514
539
|
return (
|
|
515
540
|
<section
|
|
516
541
|
key={state}
|
|
517
542
|
className={cx(
|
|
518
543
|
"sc-column",
|
|
519
544
|
`sc-column-${state}`,
|
|
545
|
+
folded && "sc-column-folded",
|
|
520
546
|
dragging?.over === state && "sc-column-over",
|
|
521
547
|
)}
|
|
522
|
-
aria-label={
|
|
548
|
+
aria-label={name}
|
|
523
549
|
data-column={state}
|
|
524
550
|
>
|
|
525
551
|
<header className="sc-column-header">
|
|
526
|
-
<h2 className="sc-column-title">{
|
|
552
|
+
<h2 className="sc-column-title">{name}</h2>
|
|
527
553
|
<span className="sc-column-count">{tasks.length}</span>
|
|
554
|
+
{canFold && (
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
className="sc-column-toggle"
|
|
558
|
+
aria-expanded={!folded}
|
|
559
|
+
aria-label={folded ? words.board.unfold(name) : words.board.fold(name)}
|
|
560
|
+
title={folded ? words.board.unfold(name) : words.board.fold(name)}
|
|
561
|
+
onClick={() => toggle(state)}
|
|
562
|
+
>
|
|
563
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
564
|
+
<path
|
|
565
|
+
d={folded ? "M6 4l4 4-4 4" : "M10 4l-4 4 4 4"}
|
|
566
|
+
fill="none"
|
|
567
|
+
stroke="currentColor"
|
|
568
|
+
strokeWidth="1.5"
|
|
569
|
+
strokeLinecap="round"
|
|
570
|
+
strokeLinejoin="round"
|
|
571
|
+
/>
|
|
572
|
+
</svg>
|
|
573
|
+
</button>
|
|
574
|
+
)}
|
|
528
575
|
</header>
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
576
|
+
{!folded && (
|
|
577
|
+
<div className="sc-column-body">
|
|
578
|
+
{tasks.map((task) => (
|
|
579
|
+
<TaskCard
|
|
580
|
+
key={task.id}
|
|
581
|
+
task={task}
|
|
582
|
+
href={props.taskHref(task)}
|
|
583
|
+
onOpen={props.onOpen}
|
|
584
|
+
today={props.today}
|
|
585
|
+
onPress={onMove === undefined ? undefined : press}
|
|
586
|
+
dragging={dragging?.task.id === task.id}
|
|
587
|
+
editHref={props.editHref?.(task)}
|
|
588
|
+
onEdit={props.onEdit}
|
|
589
|
+
onArchive={props.onArchive}
|
|
590
|
+
/>
|
|
591
|
+
))}
|
|
592
|
+
{tasks.length === 0 && state === "idea" && props.empty}
|
|
593
|
+
</div>
|
|
594
|
+
)}
|
|
546
595
|
</section>
|
|
547
596
|
);
|
|
548
597
|
})}
|
|
@@ -571,13 +620,43 @@ export function Board(props: BoardProps) {
|
|
|
571
620
|
);
|
|
572
621
|
}
|
|
573
622
|
|
|
574
|
-
/** What
|
|
623
|
+
/** What a task is found by: its title, its number, its owner and its workers, its category. */
|
|
624
|
+
function wordsOf(task: RestTask): string {
|
|
625
|
+
return [
|
|
626
|
+
task.title,
|
|
627
|
+
`#${task.number}`,
|
|
628
|
+
personName(task.owner),
|
|
629
|
+
task.owner.login,
|
|
630
|
+
...task.workers.flatMap((worker) => [personName(worker), worker.login]),
|
|
631
|
+
task.category?.name ?? "",
|
|
632
|
+
]
|
|
633
|
+
.join("\n")
|
|
634
|
+
.toLowerCase();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* What the filter keeps of the board: a category or none, a goal or none, a person, one's own,
|
|
639
|
+
* and the tasks that contain every word typed, whatever the case.
|
|
640
|
+
*/
|
|
575
641
|
export function filterTasks(
|
|
576
642
|
tasks: readonly RestTask[],
|
|
577
643
|
filter: BoardFilter,
|
|
578
644
|
me?: RestPerson | undefined,
|
|
579
645
|
): RestTask[] {
|
|
646
|
+
const wanted =
|
|
647
|
+
filter.words === undefined
|
|
648
|
+
? []
|
|
649
|
+
: filter.words
|
|
650
|
+
.toLowerCase()
|
|
651
|
+
.split(/\s+/)
|
|
652
|
+
.filter((word) => word.length > 0);
|
|
580
653
|
return tasks.filter((task) => {
|
|
654
|
+
if (wanted.length > 0) {
|
|
655
|
+
const found = wordsOf(task);
|
|
656
|
+
if (!wanted.every((word) => found.includes(word))) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
581
660
|
if (filter.category !== undefined && (task.category?.key ?? "") !== filter.category) {
|
|
582
661
|
return false;
|
|
583
662
|
}
|
|
@@ -633,6 +712,20 @@ export function BoardFilters(props: BoardFiltersProps) {
|
|
|
633
712
|
aria-label={words.aria}
|
|
634
713
|
onSubmit={(event) => event.preventDefault()}
|
|
635
714
|
>
|
|
715
|
+
<label className="sc-field sc-field-inline sc-filters-words">
|
|
716
|
+
<span className="sc-visually-hidden">{words.words}</span>
|
|
717
|
+
<input
|
|
718
|
+
className="sc-input"
|
|
719
|
+
type="search"
|
|
720
|
+
placeholder={words.words}
|
|
721
|
+
value={filter.words ?? ""}
|
|
722
|
+
onChange={(event) =>
|
|
723
|
+
event.target.value.trim() === ""
|
|
724
|
+
? change({}, "words")
|
|
725
|
+
: change({ words: event.target.value })
|
|
726
|
+
}
|
|
727
|
+
/>
|
|
728
|
+
</label>
|
|
636
729
|
<label className="sc-field sc-field-inline">
|
|
637
730
|
<span className="sc-visually-hidden">{words.anyCategory}</span>
|
|
638
731
|
<select
|