@skillcdn/console 0.1.8 → 0.1.10
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 +5 -5
- package/dist/api.d.ts +5 -5
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +255 -10
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +9 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +80 -0
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +62 -7
- package/dist/cli/help.js.map +1 -1
- package/dist/client.d.ts +31 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -3
- package/dist/client.js.map +1 -1
- package/dist/components/board.d.ts +27 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +75 -7
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +34 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +96 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/categories.d.ts +13 -0
- package/dist/components/categories.d.ts.map +1 -0
- package/dist/components/categories.js +52 -0
- package/dist/components/categories.js.map +1 -0
- package/dist/components/decision-list.d.ts +10 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +14 -1
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +12 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts +35 -0
- package/dist/components/goals.d.ts.map +1 -0
- package/dist/components/goals.js +45 -0
- package/dist/components/goals.js.map +1 -0
- package/dist/components/task-form.d.ts +5 -1
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +13 -1
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-list.d.ts +16 -0
- package/dist/components/task-list.d.ts.map +1 -0
- package/dist/components/task-list.js +14 -0
- package/dist/components/task-list.js.map +1 -0
- package/dist/components/task-view.d.ts +9 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +10 -4
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +30 -1
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +60 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +10 -0
- package/dist/components/work.d.ts.map +1 -0
- package/dist/components/work.js +29 -0
- package/dist/components/work.js.map +1 -0
- package/dist/console.css +511 -0
- package/dist/console.d.ts +8 -0
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +124 -8
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +14 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +36 -2
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +39 -0
- package/dist/dates.d.ts.map +1 -0
- package/dist/dates.js +120 -0
- package/dist/dates.js.map +1 -0
- package/dist/i18n/en.d.ts +162 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +174 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +169 -1
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +5 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +5 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +50 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +74 -1
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +7 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +9 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +366 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +112 -3
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +15 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +26 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +11 -4
- package/docs/specs/rest.md +33 -8
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +4 -4
- package/skills/building-a-console/references/the-default-console.md +1 -1
- package/skills/deploying-a-console/SKILL.md +11 -10
- package/skills/deploying-a-console/assets/deployer-policy.json +13 -12
- package/skills/deploying-a-console/assets/secrets.example.json +4 -4
- package/skills/deploying-a-console/references/access.md +10 -9
- package/skills/deploying-a-console/references/resources.md +107 -50
- package/skills/deploying-a-console/references/sizing.md +1 -1
- package/skills/deploying-a-console/scripts/store-secrets.mjs +1 -15
- package/skills/working-the-board/SKILL.md +7 -6
- package/src/api.ts +30 -0
- package/src/cli/cli.ts +311 -10
- package/src/cli/format.ts +88 -0
- package/src/cli/help.ts +69 -7
- package/src/client.ts +70 -2
- package/src/components/board.tsx +205 -12
- package/src/components/calendar.tsx +255 -0
- package/src/components/categories.tsx +209 -0
- package/src/components/decision-list.tsx +62 -5
- package/src/components/event-feed.tsx +12 -0
- package/src/components/goals.tsx +264 -0
- package/src/components/task-form.tsx +78 -1
- package/src/components/task-list.tsx +61 -0
- package/src/components/task-view.tsx +74 -3
- package/src/components/ui.tsx +116 -1
- package/src/components/work.tsx +83 -0
- package/src/console.tsx +337 -4
- package/src/data.ts +63 -8
- package/src/dates.ts +143 -0
- package/src/i18n/en.ts +184 -1
- package/src/i18n/ko.ts +175 -1
- package/src/index.ts +58 -1
- package/src/limits.ts +5 -0
- package/src/router.ts +119 -6
- package/src/routes.ts +10 -0
- package/src/schemas.ts +141 -1
- package/src/styles/console.css +511 -0
- package/src/vocabulary.ts +33 -0
package/src/data.ts
CHANGED
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
EVENT_KINDS,
|
|
7
7
|
type FileUpload,
|
|
8
8
|
type RestAnswerInput,
|
|
9
|
+
type RestCategory,
|
|
10
|
+
type RestCategoryInput,
|
|
11
|
+
type RestCategoryPatch,
|
|
9
12
|
type RestConnectRequest,
|
|
10
13
|
type RestDecision,
|
|
11
14
|
type RestDecisionInput,
|
|
@@ -14,6 +17,9 @@ import {
|
|
|
14
17
|
type RestDocumentInput,
|
|
15
18
|
type RestDocuments,
|
|
16
19
|
type RestEvent,
|
|
20
|
+
type RestGoal,
|
|
21
|
+
type RestGoalInput,
|
|
22
|
+
type RestGoalPatch,
|
|
17
23
|
type RestMe,
|
|
18
24
|
type RestMember,
|
|
19
25
|
type RestMemberInput,
|
|
@@ -35,6 +41,7 @@ import {
|
|
|
35
41
|
type RestTokens,
|
|
36
42
|
type RestVersion,
|
|
37
43
|
type RestVersions,
|
|
44
|
+
type RestWork,
|
|
38
45
|
restEventSchema,
|
|
39
46
|
} from "./api.js";
|
|
40
47
|
|
|
@@ -64,6 +71,15 @@ export interface ConsoleActions {
|
|
|
64
71
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
65
72
|
/** Grows a decision's record: its context, or what followed. */
|
|
66
73
|
updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
|
|
74
|
+
/** Sets a goal on the roadmap of the project the page is on; for an owner. */
|
|
75
|
+
createGoal(input: RestGoalInput): Promise<RestGoal>;
|
|
76
|
+
updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
|
|
77
|
+
/** What is on the person's plate across their projects, read when the page asks. */
|
|
78
|
+
work(signal?: AbortSignal): Promise<RestWork>;
|
|
79
|
+
/** The categories tasks are sorted into: kept by an administrator. */
|
|
80
|
+
createCategory(input: RestCategoryInput): Promise<RestCategory>;
|
|
81
|
+
updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
|
|
82
|
+
removeCategory(key: string): Promise<void>;
|
|
67
83
|
/** A folder's pages and folders, or the pages a search finds, in the project the page is on. */
|
|
68
84
|
documents(filter?: DocumentFilter, signal?: AbortSignal): Promise<RestDocuments>;
|
|
69
85
|
document(path: string, signal?: AbortSignal): Promise<RestDocument>;
|
|
@@ -104,6 +120,8 @@ export interface ConsoleData {
|
|
|
104
120
|
/** The projects the person may see, by name. */
|
|
105
121
|
readonly projects: readonly RestProject[];
|
|
106
122
|
readonly people: readonly RestPerson[];
|
|
123
|
+
/** The categories tasks are sorted into, by name. */
|
|
124
|
+
readonly categories: readonly RestCategory[];
|
|
107
125
|
/** The tokens of whoever is signed in, newest first. */
|
|
108
126
|
readonly tokens: readonly RestToken[];
|
|
109
127
|
/** The project the page is on, once loaded; `undefined` while loading, or when the page is on none. */
|
|
@@ -116,6 +134,8 @@ export interface ConsoleData {
|
|
|
116
134
|
readonly live: boolean;
|
|
117
135
|
readonly tasks: readonly RestTask[];
|
|
118
136
|
readonly decisions: readonly RestDecision[];
|
|
137
|
+
/** The roadmap of the project: its goals, the active first. */
|
|
138
|
+
readonly goals: readonly RestGoal[];
|
|
119
139
|
/** The runs of the project, newest first. */
|
|
120
140
|
readonly runs: readonly RestRun[];
|
|
121
141
|
/** The project's events, oldest first. */
|
|
@@ -161,6 +181,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
161
181
|
const [loaded, setLoaded] = useState(false);
|
|
162
182
|
const [projects, setProjects] = useState<readonly RestProject[]>([]);
|
|
163
183
|
const [people, setPeople] = useState<readonly RestPerson[]>([]);
|
|
184
|
+
const [categories, setCategories] = useState<readonly RestCategory[]>([]);
|
|
164
185
|
const [tokens, setTokens] = useState<readonly RestToken[]>([]);
|
|
165
186
|
const [project, setProject] = useState<RestProject | undefined>(undefined);
|
|
166
187
|
const [projectLoaded, setProjectLoaded] = useState(false);
|
|
@@ -168,6 +189,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
168
189
|
const [live, setLive] = useState(false);
|
|
169
190
|
const [tasks, setTasks] = useState<readonly RestTask[]>([]);
|
|
170
191
|
const [decisions, setDecisions] = useState<readonly RestDecision[]>([]);
|
|
192
|
+
const [goals, setGoals] = useState<readonly RestGoal[]>([]);
|
|
171
193
|
const [runs, setRuns] = useState<readonly RestRun[]>([]);
|
|
172
194
|
const [events, setEvents] = useState<readonly RestEvent[]>([]);
|
|
173
195
|
const [members, setMembers] = useState<readonly RestMember[]>([]);
|
|
@@ -181,15 +203,17 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
181
203
|
/** Loads the workspace's lists again. What fails stays as it was. */
|
|
182
204
|
const refreshWorkspace = useCallback(
|
|
183
205
|
async (signal?: AbortSignal) => {
|
|
184
|
-
const [nextProjects, nextPeople] = await Promise.all([
|
|
206
|
+
const [nextProjects, nextPeople, nextCategories] = await Promise.all([
|
|
185
207
|
client.projects(signal),
|
|
186
208
|
client.people(signal),
|
|
209
|
+
client.categories(signal),
|
|
187
210
|
]);
|
|
188
211
|
if (signal?.aborted === true) {
|
|
189
212
|
return;
|
|
190
213
|
}
|
|
191
214
|
setProjects(nextProjects.items);
|
|
192
215
|
setPeople(nextPeople.items);
|
|
216
|
+
setCategories(nextCategories.items);
|
|
193
217
|
},
|
|
194
218
|
[client],
|
|
195
219
|
);
|
|
@@ -201,19 +225,22 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
201
225
|
return;
|
|
202
226
|
}
|
|
203
227
|
const scope = client.project(projectKey);
|
|
204
|
-
const [nextProject, nextTasks, nextDecisions, nextRuns, nextMembers] =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
const [nextProject, nextTasks, nextDecisions, nextGoals, nextRuns, nextMembers] =
|
|
229
|
+
await Promise.all([
|
|
230
|
+
scope.get(signal),
|
|
231
|
+
scope.tasks(undefined, signal),
|
|
232
|
+
scope.decisions(undefined, signal),
|
|
233
|
+
scope.goals(signal),
|
|
234
|
+
scope.runs(undefined, signal),
|
|
235
|
+
scope.members(signal),
|
|
236
|
+
]);
|
|
211
237
|
if (signal?.aborted === true) {
|
|
212
238
|
return;
|
|
213
239
|
}
|
|
214
240
|
setProject(nextProject);
|
|
215
241
|
setTasks(nextTasks.items);
|
|
216
242
|
setDecisions(nextDecisions.items);
|
|
243
|
+
setGoals(nextGoals.items);
|
|
217
244
|
setRuns(nextRuns.items);
|
|
218
245
|
setMembers(nextMembers.items);
|
|
219
246
|
},
|
|
@@ -274,6 +301,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
274
301
|
setProjectError(undefined);
|
|
275
302
|
setTasks([]);
|
|
276
303
|
setDecisions([]);
|
|
304
|
+
setGoals([]);
|
|
277
305
|
setRuns([]);
|
|
278
306
|
setEvents([]);
|
|
279
307
|
setMembers([]);
|
|
@@ -411,6 +439,31 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
411
439
|
);
|
|
412
440
|
return decision;
|
|
413
441
|
},
|
|
442
|
+
async createGoal(input) {
|
|
443
|
+
const goal = await scope().createGoal(input);
|
|
444
|
+
await refreshProject();
|
|
445
|
+
return goal;
|
|
446
|
+
},
|
|
447
|
+
async updateGoal(id, patch) {
|
|
448
|
+
const goal = await scope().updateGoal(id, patch);
|
|
449
|
+
setGoals((current) => current.map((candidate) => (candidate.id === id ? goal : candidate)));
|
|
450
|
+
return goal;
|
|
451
|
+
},
|
|
452
|
+
work: (signal) => client.work(signal),
|
|
453
|
+
async createCategory(input) {
|
|
454
|
+
const category = await client.createCategory(input);
|
|
455
|
+
setCategories((await client.categories()).items);
|
|
456
|
+
return category;
|
|
457
|
+
},
|
|
458
|
+
async updateCategory(key, patch) {
|
|
459
|
+
const category = await client.updateCategory(key, patch);
|
|
460
|
+
setCategories((await client.categories()).items);
|
|
461
|
+
return category;
|
|
462
|
+
},
|
|
463
|
+
async removeCategory(key) {
|
|
464
|
+
await client.removeCategory(key);
|
|
465
|
+
setCategories((current) => current.filter((category) => category.key !== key));
|
|
466
|
+
},
|
|
414
467
|
documents: (filter, signal) => scope().documents(filter, signal),
|
|
415
468
|
document: (path, signal) => scope().document(path, signal),
|
|
416
469
|
writeDocument: (path, input) => scope().writeDocument(path, input),
|
|
@@ -476,6 +529,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
476
529
|
loaded,
|
|
477
530
|
projects,
|
|
478
531
|
people,
|
|
532
|
+
categories,
|
|
479
533
|
tokens,
|
|
480
534
|
project,
|
|
481
535
|
projectLoaded,
|
|
@@ -483,6 +537,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
483
537
|
live,
|
|
484
538
|
tasks,
|
|
485
539
|
decisions,
|
|
540
|
+
goals,
|
|
486
541
|
runs,
|
|
487
542
|
events,
|
|
488
543
|
members,
|
package/src/dates.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Days, as the plan is made in (ADR-0019): `2026-10-20`, read and written as the strings they
|
|
2
|
+
// are, so that a deadline is the same day wherever the people are. The browser's own clock says
|
|
3
|
+
// which day is today; nothing here looks at it unless asked.
|
|
4
|
+
|
|
5
|
+
const DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
6
|
+
|
|
7
|
+
/** Whether a string is a day as the API spells one. */
|
|
8
|
+
export function isDay(value: string | null | undefined): value is string {
|
|
9
|
+
return value !== null && value !== undefined && DAY.test(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A day from a local date: the date the browser's clock shows, not the UTC one. */
|
|
13
|
+
export function dayOf(date: Date): string {
|
|
14
|
+
const year = String(date.getFullYear()).padStart(4, "0");
|
|
15
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
16
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
17
|
+
return `${year}-${month}-${day}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Today, by the browser's clock. */
|
|
21
|
+
export function today(now: Date = new Date()): string {
|
|
22
|
+
return dayOf(now);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A local date at noon of the day, so that a daylight-saving change never moves it a day. */
|
|
26
|
+
export function dateOf(day: string): Date {
|
|
27
|
+
const match = DAY.exec(day);
|
|
28
|
+
if (match === null) {
|
|
29
|
+
return new Date(Number.NaN);
|
|
30
|
+
}
|
|
31
|
+
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), 12);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The day `count` days after `day`; negative counts go back. */
|
|
35
|
+
export function addDays(day: string, count: number): string {
|
|
36
|
+
const date = dateOf(day);
|
|
37
|
+
date.setDate(date.getDate() + count);
|
|
38
|
+
return dayOf(date);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** How many days `to` is after `from`; negative when it is before. */
|
|
42
|
+
export function daysBetween(from: string, to: string): number {
|
|
43
|
+
const millis = dateOf(to).getTime() - dateOf(from).getTime();
|
|
44
|
+
return Math.round(millis / 86_400_000);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The month a day is in: `2026-10`. */
|
|
48
|
+
export function monthOf(day: string): string {
|
|
49
|
+
return day.slice(0, 7);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether a string is a month as the calendar's address spells one. */
|
|
53
|
+
export function isMonth(value: string | null | undefined): value is string {
|
|
54
|
+
return value !== null && value !== undefined && /^\d{4}-(0[1-9]|1[0-2])$/.test(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The month `count` months after `month`; negative counts go back. */
|
|
58
|
+
export function addMonths(month: string, count: number): string {
|
|
59
|
+
const year = Number(month.slice(0, 4));
|
|
60
|
+
const index = Number(month.slice(5, 7)) - 1 + count;
|
|
61
|
+
const date = new Date(year, index, 1, 12);
|
|
62
|
+
return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The weekday a week begins on where the language is spoken: 1 for Monday, 7 for Sunday. */
|
|
66
|
+
export function firstDayOfWeek(language: string): number {
|
|
67
|
+
try {
|
|
68
|
+
const locale = new Intl.Locale(language) as Intl.Locale & {
|
|
69
|
+
readonly weekInfo?: { readonly firstDay: number };
|
|
70
|
+
readonly getWeekInfo?: () => { readonly firstDay: number };
|
|
71
|
+
};
|
|
72
|
+
const info = locale.getWeekInfo?.() ?? locale.weekInfo;
|
|
73
|
+
if (info !== undefined && info.firstDay >= 1 && info.firstDay <= 7) {
|
|
74
|
+
return info.firstDay;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// A tag the browser does not know: the week begins on Monday.
|
|
78
|
+
}
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One cell of a month's grid: the day, and whether it is of the month shown or a neighbour's. */
|
|
83
|
+
export interface GridDay {
|
|
84
|
+
readonly day: string;
|
|
85
|
+
readonly inMonth: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The weeks of a month as a calendar shows them: full weeks, beginning on `firstDay` (1 for
|
|
90
|
+
* Monday, 7 for Sunday), with the neighbouring months' days filling the first and the last.
|
|
91
|
+
*/
|
|
92
|
+
export function monthGrid(month: string, firstDay = 1): GridDay[][] {
|
|
93
|
+
const first = `${month}-01`;
|
|
94
|
+
const start = dateOf(first);
|
|
95
|
+
// getDay: 0 for Sunday; the grid counts Monday as 1 and Sunday as 7.
|
|
96
|
+
const weekday = start.getDay() === 0 ? 7 : start.getDay();
|
|
97
|
+
const back = (weekday - firstDay + 7) % 7;
|
|
98
|
+
let cursor = addDays(first, -back);
|
|
99
|
+
const weeks: GridDay[][] = [];
|
|
100
|
+
do {
|
|
101
|
+
const week: GridDay[] = [];
|
|
102
|
+
for (let column = 0; column < 7; column += 1) {
|
|
103
|
+
week.push({ day: cursor, inMonth: monthOf(cursor) === month });
|
|
104
|
+
cursor = addDays(cursor, 1);
|
|
105
|
+
}
|
|
106
|
+
weeks.push(week);
|
|
107
|
+
} while (monthOf(cursor) === month);
|
|
108
|
+
return weeks;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The weekdays in the order a week is shown, from `firstDay`, as the grid counts them. */
|
|
112
|
+
export function weekdays(firstDay = 1): number[] {
|
|
113
|
+
return Array.from({ length: 7 }, (_, index) => ((firstDay - 1 + index) % 7) + 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A day as people read one in a language: the month and the day, with the year when it is not this year's. */
|
|
117
|
+
export function formatDay(day: string, language?: string, now: Date = new Date()): string {
|
|
118
|
+
const date = dateOf(day);
|
|
119
|
+
if (Number.isNaN(date.getTime())) {
|
|
120
|
+
return day;
|
|
121
|
+
}
|
|
122
|
+
return date.toLocaleDateString(language, {
|
|
123
|
+
month: "short",
|
|
124
|
+
day: "numeric",
|
|
125
|
+
...(date.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A month as people read one: the month and the year. */
|
|
130
|
+
export function formatMonth(month: string, language?: string): string {
|
|
131
|
+
const date = dateOf(`${month}-01`);
|
|
132
|
+
if (Number.isNaN(date.getTime())) {
|
|
133
|
+
return month;
|
|
134
|
+
}
|
|
135
|
+
return date.toLocaleDateString(language, { month: "long", year: "numeric" });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A weekday as people read one, short, as the grid counts them: 1 for Monday, 7 for Sunday. */
|
|
139
|
+
export function formatWeekday(weekday: number, language?: string): string {
|
|
140
|
+
// The week of 2024-01-01 began on a Monday.
|
|
141
|
+
const date = new Date(2024, 0, weekday, 12);
|
|
142
|
+
return date.toLocaleDateString(language, { weekday: "short" });
|
|
143
|
+
}
|
package/src/i18n/en.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SignInFailure } from "../routes.js";
|
|
2
2
|
import type {
|
|
3
|
+
CategoryColor,
|
|
4
|
+
GoalState,
|
|
3
5
|
PersonRole,
|
|
4
6
|
ProjectRole,
|
|
5
7
|
ProjectVisibility,
|
|
@@ -47,6 +49,22 @@ export const en = {
|
|
|
47
49
|
failed: "Failed",
|
|
48
50
|
abandoned: "Abandoned",
|
|
49
51
|
} satisfies Record<RunStatus, string>,
|
|
52
|
+
goalState: {
|
|
53
|
+
planned: "Planned",
|
|
54
|
+
active: "Active",
|
|
55
|
+
done: "Reached",
|
|
56
|
+
dropped: "Dropped",
|
|
57
|
+
} satisfies Record<GoalState, string>,
|
|
58
|
+
categoryColor: {
|
|
59
|
+
gray: "Gray",
|
|
60
|
+
blue: "Blue",
|
|
61
|
+
green: "Green",
|
|
62
|
+
orange: "Orange",
|
|
63
|
+
pink: "Pink",
|
|
64
|
+
purple: "Purple",
|
|
65
|
+
teal: "Teal",
|
|
66
|
+
yellow: "Yellow",
|
|
67
|
+
} satisfies Record<CategoryColor, string>,
|
|
50
68
|
},
|
|
51
69
|
|
|
52
70
|
common: {
|
|
@@ -81,9 +99,13 @@ export const en = {
|
|
|
81
99
|
|
|
82
100
|
nav: {
|
|
83
101
|
projects: "Projects",
|
|
102
|
+
work: "My work",
|
|
84
103
|
people: "People",
|
|
85
104
|
agents: "Agents",
|
|
105
|
+
categories: "Categories",
|
|
86
106
|
board: "Board",
|
|
107
|
+
roadmap: "Roadmap",
|
|
108
|
+
calendar: "Calendar",
|
|
87
109
|
docs: "Docs",
|
|
88
110
|
decisions: "Decisions",
|
|
89
111
|
feed: "Feed",
|
|
@@ -187,7 +209,25 @@ export const en = {
|
|
|
187
209
|
decisionsTitle: "Decisions waiting for a person",
|
|
188
210
|
agentAtWork: "agent at work",
|
|
189
211
|
agentAtWorkTitle: "An agent is at work on it",
|
|
190
|
-
|
|
212
|
+
agentSince: (agent: string, since: string) => `${agent}, at work since ${since}`,
|
|
213
|
+
filters: {
|
|
214
|
+
aria: "Narrow the board",
|
|
215
|
+
everyone: "Everyone",
|
|
216
|
+
mine: "Mine",
|
|
217
|
+
anyCategory: "Any category",
|
|
218
|
+
noCategory: "No category",
|
|
219
|
+
anyGoal: "Any goal",
|
|
220
|
+
noGoal: "No goal",
|
|
221
|
+
clear: "Show everything",
|
|
222
|
+
showing: (n: number, of: number) => `${n} of ${count(of, "task", "tasks")}`,
|
|
223
|
+
},
|
|
224
|
+
due: {
|
|
225
|
+
overdue: (days: number) => `${count(days, "day", "days")} overdue`,
|
|
226
|
+
today: "Due today",
|
|
227
|
+
tomorrow: "Due tomorrow",
|
|
228
|
+
inDays: (days: number) => `Due in ${count(days, "day", "days")}`,
|
|
229
|
+
on: (day: string) => `Due ${day}`,
|
|
230
|
+
},
|
|
191
231
|
},
|
|
192
232
|
|
|
193
233
|
task: {
|
|
@@ -213,6 +253,19 @@ export const en = {
|
|
|
213
253
|
history: "What happened",
|
|
214
254
|
subtasks: "Subtasks",
|
|
215
255
|
partOfIt: "Part of it",
|
|
256
|
+
category: "Category",
|
|
257
|
+
goal: "Goal",
|
|
258
|
+
plan: "Plan",
|
|
259
|
+
planned: (from: string | undefined, to: string | undefined) =>
|
|
260
|
+
from !== undefined && to !== undefined
|
|
261
|
+
? `${from} to ${to}`
|
|
262
|
+
: from !== undefined
|
|
263
|
+
? `from ${from}`
|
|
264
|
+
: to !== undefined
|
|
265
|
+
? `by ${to}`
|
|
266
|
+
: "",
|
|
267
|
+
started: "Started",
|
|
268
|
+
done: "Done",
|
|
216
269
|
form: {
|
|
217
270
|
title: "Title",
|
|
218
271
|
body: "Body",
|
|
@@ -221,6 +274,12 @@ export const en = {
|
|
|
221
274
|
priority: "Priority",
|
|
222
275
|
assignee: "Assignee",
|
|
223
276
|
partOf: "Part of",
|
|
277
|
+
category: "Category",
|
|
278
|
+
noCategory: "No category",
|
|
279
|
+
goal: "Goal",
|
|
280
|
+
noGoal: "No goal",
|
|
281
|
+
startsOn: "Starts on",
|
|
282
|
+
dueOn: "Due on",
|
|
224
283
|
links: "Links",
|
|
225
284
|
label: "Label",
|
|
226
285
|
addLink: "Add a link",
|
|
@@ -244,6 +303,10 @@ export const en = {
|
|
|
244
303
|
},
|
|
245
304
|
waiting: "Waiting",
|
|
246
305
|
answered: "Answered",
|
|
306
|
+
record: "The record",
|
|
307
|
+
recordLead:
|
|
308
|
+
"Every decision answered, newest first: what was asked, what was chosen, by whom. Open one for its context, its rationale and what followed.",
|
|
309
|
+
openRecord: "Open",
|
|
247
310
|
waitingForPerson: "Waiting for a person.",
|
|
248
311
|
options: "Options",
|
|
249
312
|
notePlaceholder: "A word about it, if any",
|
|
@@ -271,6 +334,112 @@ export const en = {
|
|
|
271
334
|
},
|
|
272
335
|
},
|
|
273
336
|
|
|
337
|
+
calendar: {
|
|
338
|
+
title: "Calendar",
|
|
339
|
+
lead: "The tasks by the day they are due. A task with a start is shown from it; one with no day waits at the bottom to be given one.",
|
|
340
|
+
grid: "The month",
|
|
341
|
+
previous: "The month before",
|
|
342
|
+
next: "The month after",
|
|
343
|
+
thisMonth: "This month",
|
|
344
|
+
nothingDue: "Nothing is due this month.",
|
|
345
|
+
overdue: "Overdue",
|
|
346
|
+
nothingOverdue: "Nothing is overdue.",
|
|
347
|
+
unscheduled: "No day set",
|
|
348
|
+
allScheduled: "Every open task has a day.",
|
|
349
|
+
starts: "starts",
|
|
350
|
+
workload: {
|
|
351
|
+
title: "Who has what",
|
|
352
|
+
lead: "The open tasks of each person, and what of them is late or due within the week.",
|
|
353
|
+
person: "Person",
|
|
354
|
+
open: "Open",
|
|
355
|
+
inProgress: "In progress",
|
|
356
|
+
dueSoon: "Due in 7 days",
|
|
357
|
+
overdue: "Overdue",
|
|
358
|
+
unassigned: "Nobody's yet",
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
roadmap: {
|
|
363
|
+
title: "Roadmap",
|
|
364
|
+
lead: "What the project works towards: each goal with the day it is meant to be reached by, and how far its tasks are. Progress is read from the board as it stands.",
|
|
365
|
+
setGoal: "Set a goal",
|
|
366
|
+
setGoalAria: "Set a goal",
|
|
367
|
+
editAria: "Change the goal",
|
|
368
|
+
one: "Goal",
|
|
369
|
+
all: "All goals",
|
|
370
|
+
empty: {
|
|
371
|
+
title: "No goal is set yet",
|
|
372
|
+
body: "An owner sets the first one: what to reach, and by when. Tasks are then written towards it.",
|
|
373
|
+
},
|
|
374
|
+
noSuch: {
|
|
375
|
+
title: "No such goal",
|
|
376
|
+
body: "It may belong to another project, or the link is wrong.",
|
|
377
|
+
},
|
|
378
|
+
onlyOwner: {
|
|
379
|
+
title: "Only an owner sets the roadmap",
|
|
380
|
+
body: "Ask one of the project's owners, or a workspace administrator.",
|
|
381
|
+
},
|
|
382
|
+
progress: (done: number, of: number) => `${done} of ${count(of, "task", "tasks")} done`,
|
|
383
|
+
inProgress: (n: number) => `${n} in progress`,
|
|
384
|
+
noTasks: "No task works towards it yet.",
|
|
385
|
+
tasks: "Tasks",
|
|
386
|
+
target: "Target",
|
|
387
|
+
noTarget: "No target day",
|
|
388
|
+
setBy: "Set by",
|
|
389
|
+
state: "State",
|
|
390
|
+
nothingMore: "Nothing more was written about it.",
|
|
391
|
+
form: {
|
|
392
|
+
title: "Title",
|
|
393
|
+
body: "What reaching it means",
|
|
394
|
+
bodyPlaceholder: "What is true once the goal is reached, in Markdown.",
|
|
395
|
+
state: "State",
|
|
396
|
+
dueOn: "Target day",
|
|
397
|
+
set: "Set the goal",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
work: {
|
|
402
|
+
title: "My work",
|
|
403
|
+
lead: "What is on your plate across your projects: the open tasks assigned to you, by the day they are due, and the decisions that wait for a person.",
|
|
404
|
+
loading: "Loading your work",
|
|
405
|
+
couldNotLoad: "Your work could not be loaded.",
|
|
406
|
+
empty: {
|
|
407
|
+
title: "Nothing is assigned to you",
|
|
408
|
+
body: "A task assigned to you in any project you may see shows up here.",
|
|
409
|
+
},
|
|
410
|
+
overdue: "Overdue",
|
|
411
|
+
thisWeek: "Due this week",
|
|
412
|
+
later: "Later",
|
|
413
|
+
noDay: "No day set",
|
|
414
|
+
decisions: "Decisions waiting for a person",
|
|
415
|
+
noDecisions: "No decision waits.",
|
|
416
|
+
inProject: (project: string) => `in ${project}`,
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
categories: {
|
|
420
|
+
title: "Categories",
|
|
421
|
+
lead: "The kinds of work tasks are sorted into, the same in every project. A task carries one, the board is narrowed by one, and an administrator keeps the list.",
|
|
422
|
+
onlyAdmin: "An administrator adds, changes and removes them; everyone else reads them here.",
|
|
423
|
+
list: "Categories",
|
|
424
|
+
add: "Add a category",
|
|
425
|
+
addAria: "Add a category",
|
|
426
|
+
empty: {
|
|
427
|
+
title: "No categories",
|
|
428
|
+
body: "An administrator adds the first one here.",
|
|
429
|
+
},
|
|
430
|
+
inUse: "A category tasks are sorted into cannot be removed until they are moved out of it.",
|
|
431
|
+
form: {
|
|
432
|
+
name: "Name",
|
|
433
|
+
key: "Key",
|
|
434
|
+
keyHint:
|
|
435
|
+
"Lowercase letters, digits and hyphens: what the command and the filters say. It does not change.",
|
|
436
|
+
color: "Colour",
|
|
437
|
+
add: "Add",
|
|
438
|
+
},
|
|
439
|
+
nameOf: (key: string) => `Name of ${key}`,
|
|
440
|
+
colorOf: (key: string) => `Colour of ${key}`,
|
|
441
|
+
},
|
|
442
|
+
|
|
274
443
|
feed: {
|
|
275
444
|
title: "What happened",
|
|
276
445
|
list: "What happened",
|
|
@@ -284,6 +453,8 @@ export const en = {
|
|
|
284
453
|
something: "something",
|
|
285
454
|
aQuestion: "a question",
|
|
286
455
|
aFile: "a file",
|
|
456
|
+
aGoal: "a goal",
|
|
457
|
+
aCategory: "a category",
|
|
287
458
|
thePage: (name: string) => `the page ${name}`.trimEnd(),
|
|
288
459
|
role: {
|
|
289
460
|
admin: "an administrator",
|
|
@@ -303,6 +474,12 @@ export const en = {
|
|
|
303
474
|
visibility: "who is a member",
|
|
304
475
|
skillsAddress: "the skills address",
|
|
305
476
|
outcome: "what followed",
|
|
477
|
+
category: "the category",
|
|
478
|
+
goalId: "the goal",
|
|
479
|
+
startsOn: "the start",
|
|
480
|
+
dueOn: "the due date",
|
|
481
|
+
state: "the state",
|
|
482
|
+
color: "the colour",
|
|
306
483
|
} as Readonly<Record<string, string>>,
|
|
307
484
|
event: {
|
|
308
485
|
personJoined: () => "joined the board",
|
|
@@ -315,6 +492,12 @@ export const en = {
|
|
|
315
492
|
memberChanged: (login: string, role: string, project: string) =>
|
|
316
493
|
`made ${login} ${role} of ${project}`,
|
|
317
494
|
memberRemoved: (login: string, project: string) => `removed ${login} from ${project}`,
|
|
495
|
+
categoryCreated: (category: string) => `made the category ${category}`,
|
|
496
|
+
categoryUpdated: (fields: string, category: string) =>
|
|
497
|
+
`changed ${fields} of the category ${category}`,
|
|
498
|
+
categoryRemoved: (category: string) => `removed the category ${category}`,
|
|
499
|
+
goalCreated: (goal: string) => `set the goal ${goal}`,
|
|
500
|
+
goalUpdated: (fields: string, goal: string) => `changed ${fields} of the goal ${goal}`,
|
|
318
501
|
taskCreated: (task: string) => `wrote ${task}`,
|
|
319
502
|
taskMoved: (task: string, from: string, to: string) => `moved ${task} from ${from} to ${to}`,
|
|
320
503
|
taskUpdated: (fields: string, task: string) => `changed ${fields} of ${task}`,
|