@skillcdn/console 0.1.11 → 0.1.12
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 +3 -2
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +2 -2
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.js +2 -2
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/people.js +2 -2
- 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/runs.js +2 -2
- package/dist/components/runs.js.map +1 -1
- package/dist/components/shell.js +2 -2
- package/dist/components/shell.js.map +1 -1
- package/dist/components/task-form.js +2 -2
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +9 -4
- package/dist/components/ui.js.map +1 -1
- package/dist/console.d.ts +38 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +50 -5
- package/dist/console.js.map +1 -1
- package/dist/i18n/en.d.ts +5 -5
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +6 -6
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +6 -6
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +15 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +17 -0
- package/dist/router.js.map +1 -1
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +2 -2
- package/src/components/board.tsx +11 -2
- package/src/components/calendar.tsx +2 -2
- package/src/components/people.tsx +5 -5
- package/src/components/projects.tsx +5 -3
- package/src/components/runs.tsx +2 -2
- package/src/components/shell.tsx +3 -3
- package/src/components/task-form.tsx +2 -2
- package/src/components/ui.tsx +14 -5
- package/src/console.tsx +104 -5
- package/src/i18n/en.ts +7 -6
- package/src/i18n/ko.ts +7 -6
- package/src/index.ts +5 -0
- package/src/router.ts +26 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PersonRole, RestPerson } from "../api.js";
|
|
2
2
|
import { useWords } from "../i18n/index.js";
|
|
3
3
|
import { PERSON_ROLES } from "../vocabulary.js";
|
|
4
|
-
import { PersonChip, RoleBadge } from "./ui.js";
|
|
4
|
+
import { PersonChip, personName, RoleBadge } from "./ui.js";
|
|
5
5
|
|
|
6
6
|
// The people of the board, with what each may do. An administrator changes roles here; a
|
|
7
7
|
// member reads. Takes its data as props and nothing from the network.
|
|
@@ -24,15 +24,15 @@ export function PeopleList(props: PeopleListProps) {
|
|
|
24
24
|
{props.people.map((person) => (
|
|
25
25
|
<li key={person.id} className="sc-person-row">
|
|
26
26
|
<PersonChip person={person} size="md" />
|
|
27
|
-
<span className="sc-person-name">
|
|
28
|
-
{person.name
|
|
29
|
-
{props.me?.id === person.id && <span
|
|
27
|
+
<span className="sc-person-name sc-muted">
|
|
28
|
+
{person.name === null ? "" : person.login}
|
|
29
|
+
{props.me?.id === person.id && <span> {words.common.you}</span>}
|
|
30
30
|
</span>
|
|
31
31
|
{props.onChangeRole === undefined ? (
|
|
32
32
|
<RoleBadge role={person.role} />
|
|
33
33
|
) : (
|
|
34
34
|
<label className="sc-field sc-field-inline">
|
|
35
|
-
<span className="sc-visually-hidden">{words.people.roleOf(person
|
|
35
|
+
<span className="sc-visually-hidden">{words.people.roleOf(personName(person))}</span>
|
|
36
36
|
<select
|
|
37
37
|
className="sc-input"
|
|
38
38
|
value={person.role}
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type RestProjectPatch,
|
|
24
24
|
} from "../api.js";
|
|
25
25
|
import { useWords } from "../i18n/index.js";
|
|
26
|
-
import { Badge, Button, cx, PersonChip, Time } from "./ui.js";
|
|
26
|
+
import { Badge, Button, cx, PersonChip, personName, Time } from "./ui.js";
|
|
27
27
|
|
|
28
28
|
/** A project's mark: its symbol on its colour, so that projects tell apart at a glance. */
|
|
29
29
|
export function ProjectMark(props: {
|
|
@@ -392,7 +392,9 @@ export function MemberList(props: MemberListProps) {
|
|
|
392
392
|
<ProjectRoleBadge role={member.role} />
|
|
393
393
|
) : (
|
|
394
394
|
<label className="sc-field sc-field-inline">
|
|
395
|
-
<span className="sc-visually-hidden">
|
|
395
|
+
<span className="sc-visually-hidden">
|
|
396
|
+
{members.roleOf(personName(member.person))}
|
|
397
|
+
</span>
|
|
396
398
|
<select
|
|
397
399
|
className="sc-input"
|
|
398
400
|
value={member.role}
|
|
@@ -446,7 +448,7 @@ export function MemberList(props: MemberListProps) {
|
|
|
446
448
|
<option value="">{members.chooseSomeone}</option>
|
|
447
449
|
{candidates.map((person) => (
|
|
448
450
|
<option key={person.id} value={person.id}>
|
|
449
|
-
{person
|
|
451
|
+
{personName(person)}
|
|
450
452
|
</option>
|
|
451
453
|
))}
|
|
452
454
|
</select>
|
package/src/components/runs.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import type { RestArtifact, RestRun, RunStatus } from "../api.js";
|
|
3
3
|
import { useWords } from "../i18n/index.js";
|
|
4
4
|
import { Markdown } from "./markdown.js";
|
|
5
|
-
import { Badge, Button, formatBytes, PersonChip, Time } from "./ui.js";
|
|
5
|
+
import { Badge, Button, formatBytes, PersonChip, personName, Time } from "./ui.js";
|
|
6
6
|
|
|
7
7
|
// A run: one agent at work on one task for one person, with what it reported, what it handed
|
|
8
8
|
// in, and what it waits for. Takes its data as props and nothing from the network.
|
|
@@ -49,7 +49,7 @@ export function RunCard(props: RunCardProps) {
|
|
|
49
49
|
const { run } = props;
|
|
50
50
|
const open = run.status === "running" || run.status === "waiting";
|
|
51
51
|
return (
|
|
52
|
-
<article className="sc-run" aria-label={words.label(run.agent, run.person
|
|
52
|
+
<article className="sc-run" aria-label={words.label(run.agent, personName(run.person))}>
|
|
53
53
|
<header className="sc-run-header">
|
|
54
54
|
<span className="sc-run-agent">{run.agent}</span>
|
|
55
55
|
<RunStatusBadge status={run.status} />
|
package/src/components/shell.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode, useEffect, useRef } from "react";
|
|
2
2
|
import type { RestPerson } from "../api.js";
|
|
3
3
|
import { useWords } from "../i18n/index.js";
|
|
4
|
-
import { Avatar, cx } from "./ui.js";
|
|
4
|
+
import { Avatar, cx, personName } from "./ui.js";
|
|
5
5
|
|
|
6
6
|
// The frame every page sits in (ADR-0010): a header that names the workspace and the project the
|
|
7
7
|
// page is on, with the person's menu at its end; a row of tabs below it with the pages of the
|
|
@@ -109,9 +109,9 @@ function PersonMenu(props: {
|
|
|
109
109
|
const { person } = props;
|
|
110
110
|
return (
|
|
111
111
|
<details className="sc-menu" ref={details}>
|
|
112
|
-
<summary className="sc-menu-summary" aria-label={words.shell.menu(person
|
|
112
|
+
<summary className="sc-menu-summary" aria-label={words.shell.menu(personName(person))}>
|
|
113
113
|
<Avatar person={person} size="sm" />
|
|
114
|
-
<span className="sc-person-login">{person
|
|
114
|
+
<span className="sc-person-login">{personName(person)}</span>
|
|
115
115
|
<svg className="sc-menu-caret" viewBox="0 0 16 16" aria-hidden="true">
|
|
116
116
|
<path
|
|
117
117
|
d="M4 6l4 4 4-4"
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
TASK_STATES,
|
|
15
15
|
} from "../api.js";
|
|
16
16
|
import { useWords } from "../i18n/index.js";
|
|
17
|
-
import { Button } from "./ui.js";
|
|
17
|
+
import { Button, personName } from "./ui.js";
|
|
18
18
|
|
|
19
19
|
// Writing a task, or changing one. The bounds are the package's, said before a request is made.
|
|
20
20
|
// The plan is two days (ADR-0019); what happened to the task is the board's to keep, not a
|
|
@@ -151,7 +151,7 @@ export function TaskForm(props: TaskFormProps) {
|
|
|
151
151
|
<option value="">{words.common.nobody}</option>
|
|
152
152
|
{props.people.map((person) => (
|
|
153
153
|
<option key={person.id} value={person.id}>
|
|
154
|
-
{person
|
|
154
|
+
{personName(person)}
|
|
155
155
|
</option>
|
|
156
156
|
))}
|
|
157
157
|
</select>
|
package/src/components/ui.tsx
CHANGED
|
@@ -74,13 +74,22 @@ export function Callout(props: {
|
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** What a person is called on the pages: the name the host gives, or the login where there is none. */
|
|
78
|
+
export function personName(person: {
|
|
79
|
+
readonly login: string;
|
|
80
|
+
readonly name: string | null;
|
|
81
|
+
}): string {
|
|
82
|
+
const name = person.name?.trim() ?? "";
|
|
83
|
+
return name === "" ? person.login : name;
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
/**
|
|
78
87
|
* A person's picture as the git host serves it: loaded lazily, without a referrer, and gone as
|
|
79
|
-
* a whole when there is none or it cannot be loaded, leaving the initial of the
|
|
88
|
+
* a whole when there is none or it cannot be loaded, leaving the initial of the name instead.
|
|
80
89
|
*/
|
|
81
90
|
export function Avatar(props: { readonly person: RestPerson; readonly size?: "sm" | "md" | "lg" }) {
|
|
82
91
|
const size = props.size ?? "md";
|
|
83
|
-
const initial = props.person.
|
|
92
|
+
const initial = personName(props.person).slice(0, 1).toUpperCase();
|
|
84
93
|
if (props.person.avatar === null) {
|
|
85
94
|
return (
|
|
86
95
|
<span
|
|
@@ -103,12 +112,12 @@ export function Avatar(props: { readonly person: RestPerson; readonly size?: "sm
|
|
|
103
112
|
);
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
/** A person, as a picture and
|
|
115
|
+
/** A person, as a picture and their name, wherever one is named; the login is there on hover. */
|
|
107
116
|
export function PersonChip(props: { readonly person: RestPerson; readonly size?: "sm" | "md" }) {
|
|
108
117
|
return (
|
|
109
|
-
<span className="sc-person" title={props.person.
|
|
118
|
+
<span className="sc-person" title={props.person.login}>
|
|
110
119
|
<Avatar person={props.person} size={props.size ?? "sm"} />
|
|
111
|
-
<span className="sc-person-login">{props.person
|
|
120
|
+
<span className="sc-person-login">{personName(props.person)}</span>
|
|
112
121
|
</span>
|
|
113
122
|
);
|
|
114
123
|
}
|
package/src/console.tsx
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
StrictMode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
2
10
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
11
|
import {
|
|
4
12
|
type ConsoleClient,
|
|
@@ -50,7 +58,7 @@ import { TaskForm } from "./components/task-form.js";
|
|
|
50
58
|
import { TaskList } from "./components/task-list.js";
|
|
51
59
|
import { TaskView, type TaskViewProps } from "./components/task-view.js";
|
|
52
60
|
import { NewToken, TokenForm, TokenList, type TokenListProps } from "./components/tokens.js";
|
|
53
|
-
import { Button, Callout, EmptyState, Spinner } from "./components/ui.js";
|
|
61
|
+
import { Button, Callout, EmptyState, personName, Spinner } from "./components/ui.js";
|
|
54
62
|
import { WorkList, type WorkListProps } from "./components/work.js";
|
|
55
63
|
import { ConnectPage } from "./connect-page.js";
|
|
56
64
|
import { type ConsoleData, useConsoleData } from "./data.js";
|
|
@@ -61,11 +69,13 @@ import {
|
|
|
61
69
|
DEFAULT_LANGUAGES,
|
|
62
70
|
LanguageContext,
|
|
63
71
|
type LanguagePack,
|
|
72
|
+
type Messages,
|
|
64
73
|
useWords,
|
|
65
74
|
} from "./i18n/index.js";
|
|
66
75
|
import {
|
|
67
76
|
boardHref,
|
|
68
77
|
calendarHref,
|
|
78
|
+
customPageHref,
|
|
69
79
|
decisionHref,
|
|
70
80
|
docHref,
|
|
71
81
|
goalHref,
|
|
@@ -128,6 +138,52 @@ export const DEFAULT_COMPONENTS: ConsoleComponents = {
|
|
|
128
138
|
CategoryList,
|
|
129
139
|
};
|
|
130
140
|
|
|
141
|
+
/**
|
|
142
|
+
* A page a console adds of its own, shown as a tab beside the console's: on the workspace, or
|
|
143
|
+
* on every project. The address is `/pages/<key>`, or `/projects/<project>/pages/<key>`.
|
|
144
|
+
*/
|
|
145
|
+
export interface CustomPage {
|
|
146
|
+
/** What the address says, and what tells the page apart: lowercase letters, digits and hyphens. */
|
|
147
|
+
readonly key: string;
|
|
148
|
+
/** What the tab says: one word for every language, or a word by language tag, `en` standing in for a tag without one. */
|
|
149
|
+
readonly label: string | Readonly<Record<string, string>>;
|
|
150
|
+
/** Where the tab is: among the workspace's pages, or among each project's. */
|
|
151
|
+
readonly level: "workspace" | "project";
|
|
152
|
+
/** Whether the page takes the whole width of the window, as the board does. */
|
|
153
|
+
readonly wide?: boolean | undefined;
|
|
154
|
+
/** What the page shows, given what the default pages are given. */
|
|
155
|
+
readonly render: (context: CustomPageContext) => ReactNode;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** What a page of a console's own is given: the data the default pages read, and the means they use. */
|
|
159
|
+
export interface CustomPageContext {
|
|
160
|
+
/** The workspace and, on a project, the project the page is on, kept current; with the actions. */
|
|
161
|
+
readonly data: ConsoleData;
|
|
162
|
+
/** The project the page is on, or nothing on the workspace. */
|
|
163
|
+
readonly project: RestProject | undefined;
|
|
164
|
+
/** The client, for what the data does not carry. */
|
|
165
|
+
readonly client: ConsoleClient;
|
|
166
|
+
readonly navigation: Navigation;
|
|
167
|
+
/** The words of the language the pages speak, and its tag. */
|
|
168
|
+
readonly words: Messages;
|
|
169
|
+
readonly language: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** What the tab of a page of one's own says in a language: its word for the tag, for the language alone, for English, or the first it has. */
|
|
173
|
+
export function customPageLabel(page: CustomPage, tag: string): string {
|
|
174
|
+
if (typeof page.label === "string") {
|
|
175
|
+
return page.label;
|
|
176
|
+
}
|
|
177
|
+
const language = tag.split("-")[0] ?? tag;
|
|
178
|
+
return (
|
|
179
|
+
page.label[tag] ??
|
|
180
|
+
page.label[language] ??
|
|
181
|
+
page.label.en ??
|
|
182
|
+
Object.values(page.label)[0] ??
|
|
183
|
+
page.key
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
export interface ConsoleConfig {
|
|
132
188
|
/** The origin of the console's API. Left out, the page's own origin. */
|
|
133
189
|
readonly baseUrl?: string | undefined;
|
|
@@ -141,6 +197,8 @@ export interface ConsoleConfig {
|
|
|
141
197
|
readonly brand?: Brand | undefined;
|
|
142
198
|
/** The client to talk to the server with; made from `baseUrl` when left out. For tests. */
|
|
143
199
|
readonly client?: ConsoleClient | undefined;
|
|
200
|
+
/** Pages of the console's own, as tabs beside the default ones. */
|
|
201
|
+
readonly pages?: readonly CustomPage[] | undefined;
|
|
144
202
|
}
|
|
145
203
|
|
|
146
204
|
export interface ConsoleApp {
|
|
@@ -1047,7 +1105,9 @@ function PersonAgentsPage(props: {
|
|
|
1047
1105
|
return (
|
|
1048
1106
|
<>
|
|
1049
1107
|
<div className="sc-page-head">
|
|
1050
|
-
<h1 className="sc-page-title">
|
|
1108
|
+
<h1 className="sc-page-title">
|
|
1109
|
+
{words.people.agentsOf(person === undefined ? "?" : personName(person))}
|
|
1110
|
+
</h1>
|
|
1051
1111
|
<LinkButton href={PATHS.people}>{words.nav.people}</LinkButton>
|
|
1052
1112
|
</div>
|
|
1053
1113
|
<p className="sc-lead">
|
|
@@ -1259,6 +1319,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1259
1319
|
const client = config.client ?? createClient({ baseUrl: config.baseUrl ?? "" });
|
|
1260
1320
|
const fallbackTitle = config.title ?? "Console";
|
|
1261
1321
|
const languages = config.languages ?? DEFAULT_LANGUAGES;
|
|
1322
|
+
const pages = config.pages ?? [];
|
|
1262
1323
|
|
|
1263
1324
|
function App(props: { readonly initialPath?: string | undefined }) {
|
|
1264
1325
|
const { location, navigation } = useNavigation(props.initialPath);
|
|
@@ -1363,6 +1424,16 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1363
1424
|
label: words.nav.categories,
|
|
1364
1425
|
current: route.name === "categories",
|
|
1365
1426
|
},
|
|
1427
|
+
...pages
|
|
1428
|
+
.filter((candidate) => candidate.level === "workspace")
|
|
1429
|
+
.map((candidate) => ({
|
|
1430
|
+
href: customPageHref(candidate.key),
|
|
1431
|
+
label: customPageLabel(candidate, pack.tag),
|
|
1432
|
+
current:
|
|
1433
|
+
route.name === "custom" &&
|
|
1434
|
+
route.level === "workspace" &&
|
|
1435
|
+
route.page === candidate.key,
|
|
1436
|
+
})),
|
|
1366
1437
|
];
|
|
1367
1438
|
const nav: NavItem[] =
|
|
1368
1439
|
projectKey === undefined
|
|
@@ -1423,8 +1494,27 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1423
1494
|
},
|
|
1424
1495
|
]
|
|
1425
1496
|
: []),
|
|
1497
|
+
...pages
|
|
1498
|
+
.filter((candidate) => candidate.level === "project")
|
|
1499
|
+
.map((candidate) => ({
|
|
1500
|
+
href: customPageHref(candidate.key, projectKey),
|
|
1501
|
+
label: customPageLabel(candidate, pack.tag),
|
|
1502
|
+
current:
|
|
1503
|
+
route.name === "custom" &&
|
|
1504
|
+
route.level === "project" &&
|
|
1505
|
+
route.page === candidate.key,
|
|
1506
|
+
})),
|
|
1426
1507
|
];
|
|
1427
|
-
|
|
1508
|
+
/** A page of the console's own at the address, or nothing there. */
|
|
1509
|
+
const customPageOf = (key: string, level: CustomPage["level"]): ReactNode => {
|
|
1510
|
+
const found = pages.find((candidate) => candidate.key === key && candidate.level === level);
|
|
1511
|
+
return found === undefined ? (
|
|
1512
|
+
<NothingHere />
|
|
1513
|
+
) : (
|
|
1514
|
+
found.render({ data, project, client, navigation, words, language: pack.tag })
|
|
1515
|
+
);
|
|
1516
|
+
};
|
|
1517
|
+
let page: ReactNode;
|
|
1428
1518
|
if (!data.loaded || route.name === "moved") {
|
|
1429
1519
|
page = (
|
|
1430
1520
|
<div className="sc-loading">
|
|
@@ -1442,6 +1532,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1442
1532
|
making={route.name === "new-project"}
|
|
1443
1533
|
/>
|
|
1444
1534
|
);
|
|
1535
|
+
} else if (route.name === "custom" && route.level === "workspace") {
|
|
1536
|
+
page = customPageOf(route.page, "workspace");
|
|
1445
1537
|
} else if (projectKey === undefined) {
|
|
1446
1538
|
page = (
|
|
1447
1539
|
<WorkspacePage
|
|
@@ -1465,6 +1557,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1465
1557
|
<Spinner label={words.common.loadingProject} />
|
|
1466
1558
|
</div>
|
|
1467
1559
|
);
|
|
1560
|
+
} else if (route.name === "custom") {
|
|
1561
|
+
page = customPageOf(route.page, "project");
|
|
1468
1562
|
} else {
|
|
1469
1563
|
page = (
|
|
1470
1564
|
<ProjectPage
|
|
@@ -1507,7 +1601,12 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1507
1601
|
}}
|
|
1508
1602
|
person={data.me.person}
|
|
1509
1603
|
live={projectKey === undefined ? undefined : data.live}
|
|
1510
|
-
wide={
|
|
1604
|
+
wide={
|
|
1605
|
+
route.name === "board" ||
|
|
1606
|
+
route.name === "calendar" ||
|
|
1607
|
+
(route.name === "custom" &&
|
|
1608
|
+
pages.some((candidate) => candidate.key === route.page && candidate.wide === true))
|
|
1609
|
+
}
|
|
1511
1610
|
onNavigate={(href) => navigation.go(href)}
|
|
1512
1611
|
onSignOut={viaToken ? undefined : () => void data.actions.signOut()}
|
|
1513
1612
|
>
|
package/src/i18n/en.ts
CHANGED
|
@@ -122,7 +122,7 @@ export const en = {
|
|
|
122
122
|
live: "Live",
|
|
123
123
|
reconnecting: "Reconnecting",
|
|
124
124
|
signOut: "Sign out",
|
|
125
|
-
menu: (
|
|
125
|
+
menu: (name: string) => `${name}: menu`,
|
|
126
126
|
},
|
|
127
127
|
|
|
128
128
|
signIn: {
|
|
@@ -178,7 +178,7 @@ export const en = {
|
|
|
178
178
|
leadRoles:
|
|
179
179
|
"An owner configures the project; a member works on it. A workspace administrator is an owner of every project.",
|
|
180
180
|
since: "since",
|
|
181
|
-
roleOf: (
|
|
181
|
+
roleOf: (name: string) => `Role of ${name}`,
|
|
182
182
|
addAria: "Add a member",
|
|
183
183
|
person: "Person",
|
|
184
184
|
chooseSomeone: "Choose someone",
|
|
@@ -533,7 +533,7 @@ export const en = {
|
|
|
533
533
|
answerIt: "answer it",
|
|
534
534
|
reports: "Reports",
|
|
535
535
|
handedIn: "Handed in",
|
|
536
|
-
label: (agent: string,
|
|
536
|
+
label: (agent: string, name: string) => `${agent} for ${name}`,
|
|
537
537
|
},
|
|
538
538
|
|
|
539
539
|
skills: {
|
|
@@ -625,9 +625,9 @@ export const en = {
|
|
|
625
625
|
title: "People",
|
|
626
626
|
list: "People",
|
|
627
627
|
lead: "Everyone who has signed in. An administrator configures the workspace and says what each person is; a member works in the projects they are in. What a person may do, their agents may do.",
|
|
628
|
-
roleOf: (
|
|
628
|
+
roleOf: (name: string) => `Role of ${name}`,
|
|
629
629
|
agents: "Agents",
|
|
630
|
-
agentsOf: (
|
|
630
|
+
agentsOf: (name: string) => `Agents of ${name}`,
|
|
631
631
|
agentsLead: (name: string) =>
|
|
632
632
|
`What works here as ${name}, each with a token of its own. Disconnecting one takes its token away at once.`,
|
|
633
633
|
loadingAgents: "Loading the agents",
|
|
@@ -711,7 +711,8 @@ export const en = {
|
|
|
711
711
|
words: {
|
|
712
712
|
title: "Connecting an agent",
|
|
713
713
|
tellYourAgent: "Tell your agent, in its own chat:",
|
|
714
|
-
quote: (origin: string) =>
|
|
714
|
+
quote: (origin: string) =>
|
|
715
|
+
`Install @skillcdn/console from npm, run console login --url ${origin}, and tell me the address and the code it shows.`,
|
|
715
716
|
doesTheRest:
|
|
716
717
|
"An agent that can run commands does the rest: it installs the command, connects, and shows you an address with a code.",
|
|
717
718
|
thenApprove:
|
package/src/i18n/ko.ts
CHANGED
|
@@ -95,7 +95,7 @@ export const ko: Messages = {
|
|
|
95
95
|
live: "실시간",
|
|
96
96
|
reconnecting: "다시 연결하는 중",
|
|
97
97
|
signOut: "로그아웃",
|
|
98
|
-
menu: (
|
|
98
|
+
menu: (name) => `${name}: 메뉴`,
|
|
99
99
|
},
|
|
100
100
|
|
|
101
101
|
signIn: {
|
|
@@ -150,7 +150,7 @@ export const ko: Messages = {
|
|
|
150
150
|
leadRoles:
|
|
151
151
|
"소유자는 프로젝트를 설정하고, 멤버는 프로젝트에서 일합니다. 워크스페이스 관리자는 모든 프로젝트의 소유자입니다.",
|
|
152
152
|
since: "부터",
|
|
153
|
-
roleOf: (
|
|
153
|
+
roleOf: (name) => `${name}의 역할`,
|
|
154
154
|
addAria: "멤버 추가",
|
|
155
155
|
person: "사람",
|
|
156
156
|
chooseSomeone: "선택하세요",
|
|
@@ -501,7 +501,7 @@ export const ko: Messages = {
|
|
|
501
501
|
answerIt: "답변하기",
|
|
502
502
|
reports: "보고",
|
|
503
503
|
handedIn: "제출물",
|
|
504
|
-
label: (agent,
|
|
504
|
+
label: (agent, name) => `${name}을(를) 위한 ${agent}`,
|
|
505
505
|
},
|
|
506
506
|
|
|
507
507
|
skills: {
|
|
@@ -593,9 +593,9 @@ export const ko: Messages = {
|
|
|
593
593
|
title: "사람들",
|
|
594
594
|
list: "사람들",
|
|
595
595
|
lead: "로그인한 모든 사람. 관리자는 워크스페이스를 설정하고 각 사람의 역할을 정합니다. 멤버는 속한 프로젝트에서 일합니다. 사람이 할 수 있는 일은 그 사람의 에이전트도 할 수 있습니다.",
|
|
596
|
-
roleOf: (
|
|
596
|
+
roleOf: (name) => `${name}의 역할`,
|
|
597
597
|
agents: "에이전트",
|
|
598
|
-
agentsOf: (
|
|
598
|
+
agentsOf: (name) => `${name}의 에이전트`,
|
|
599
599
|
agentsLead: (name) =>
|
|
600
600
|
`${name}(으)로 여기서 일하는 것들입니다. 각각 고유한 토큰을 가집니다. 연결을 끊으면 토큰이 즉시 사라집니다.`,
|
|
601
601
|
loadingAgents: "에이전트를 불러오는 중",
|
|
@@ -678,7 +678,8 @@ export const ko: Messages = {
|
|
|
678
678
|
words: {
|
|
679
679
|
title: "에이전트 연결하기",
|
|
680
680
|
tellYourAgent: "에이전트의 대화창에서 이렇게 말하세요:",
|
|
681
|
-
quote: (origin) =>
|
|
681
|
+
quote: (origin) =>
|
|
682
|
+
`npm으로 @skillcdn/console을 설치하고, console login --url ${origin} 을 실행한 뒤 나오는 주소와 코드를 알려줘.`,
|
|
682
683
|
doesTheRest:
|
|
683
684
|
"명령을 실행할 수 있는 에이전트는 나머지를 알아서 합니다. 명령을 설치하고, 연결하고, 코드가 있는 주소를 보여 줍니다.",
|
|
684
685
|
thenApprove:
|
package/src/index.ts
CHANGED
|
@@ -127,6 +127,7 @@ export {
|
|
|
127
127
|
PRIORITY_LABELS,
|
|
128
128
|
PriorityBadge,
|
|
129
129
|
ProgressBar,
|
|
130
|
+
personName,
|
|
130
131
|
ROLE_LABELS,
|
|
131
132
|
RoleBadge,
|
|
132
133
|
Spinner,
|
|
@@ -140,7 +141,10 @@ export {
|
|
|
140
141
|
type ConsoleApp,
|
|
141
142
|
type ConsoleComponents,
|
|
142
143
|
type ConsoleConfig,
|
|
144
|
+
type CustomPage,
|
|
145
|
+
type CustomPageContext,
|
|
143
146
|
createConsole,
|
|
147
|
+
customPageLabel,
|
|
144
148
|
DEFAULT_COMPONENTS,
|
|
145
149
|
} from "./console.js";
|
|
146
150
|
export { type ConsoleActions, type ConsoleData, useConsoleData } from "./data.js";
|
|
@@ -180,6 +184,7 @@ export {
|
|
|
180
184
|
boardHref,
|
|
181
185
|
calendarHref,
|
|
182
186
|
connectHref,
|
|
187
|
+
customPageHref,
|
|
183
188
|
type DocsView,
|
|
184
189
|
decisionHref,
|
|
185
190
|
docHref,
|
package/src/router.ts
CHANGED
|
@@ -84,6 +84,14 @@ export type Route =
|
|
|
84
84
|
| { readonly name: "connect"; readonly code: string | undefined }
|
|
85
85
|
/** An address that moved: the page takes the person to `to` in its place. */
|
|
86
86
|
| { readonly name: "moved"; readonly to: string }
|
|
87
|
+
/** A page a console adds of its own (`createConsole({ pages })`): on the workspace, or on a project. */
|
|
88
|
+
| { readonly name: "custom"; readonly level: "workspace"; readonly page: string }
|
|
89
|
+
| {
|
|
90
|
+
readonly name: "custom";
|
|
91
|
+
readonly level: "project";
|
|
92
|
+
readonly project: string;
|
|
93
|
+
readonly page: string;
|
|
94
|
+
}
|
|
87
95
|
| { readonly name: "not-found" };
|
|
88
96
|
|
|
89
97
|
/** The pages of a project, after its key. */
|
|
@@ -112,6 +120,8 @@ export const PATHS = {
|
|
|
112
120
|
newToken: "/agents/new",
|
|
113
121
|
/** The categories tasks are sorted into, kept by an administrator. */
|
|
114
122
|
categories: "/categories",
|
|
123
|
+
/** Where the pages a console adds of its own are: `/pages/<key>`, and `/projects/<key>/pages/<page>` on a project. */
|
|
124
|
+
pages: "/pages",
|
|
115
125
|
/** Where an agent's connection is approved (ADR-0011): `/connect/<code>`, or the code typed. */
|
|
116
126
|
connect: "/connect",
|
|
117
127
|
} as const;
|
|
@@ -223,6 +233,12 @@ export function matchRoute(pathname: string, search = ""): Route {
|
|
|
223
233
|
? { name: "person-agents", id }
|
|
224
234
|
: NOT_FOUND;
|
|
225
235
|
}
|
|
236
|
+
if (path.startsWith(`${PATHS.pages}/`)) {
|
|
237
|
+
const [page, ...more] = path.slice(PATHS.pages.length + 1).split("/");
|
|
238
|
+
return page !== undefined && PROJECT_KEY.test(page) && more.length === 0
|
|
239
|
+
? { name: "custom", level: "workspace", page }
|
|
240
|
+
: NOT_FOUND;
|
|
241
|
+
}
|
|
226
242
|
if (!path.startsWith(`${PATHS.project}/`)) {
|
|
227
243
|
return NOT_FOUND;
|
|
228
244
|
}
|
|
@@ -242,6 +258,11 @@ export function matchRoute(pathname: string, search = ""): Route {
|
|
|
242
258
|
const view = docsViewOf(params, docPath);
|
|
243
259
|
return view === undefined ? NOT_FOUND : { name: "docs", project, path: docPath, view };
|
|
244
260
|
}
|
|
261
|
+
if (page === "pages") {
|
|
262
|
+
return third !== undefined && PROJECT_KEY.test(third) && rest.length === 0
|
|
263
|
+
? { name: "custom", level: "project", project, page: third }
|
|
264
|
+
: NOT_FOUND;
|
|
265
|
+
}
|
|
245
266
|
if (rest.length > 0) {
|
|
246
267
|
return NOT_FOUND;
|
|
247
268
|
}
|
|
@@ -363,6 +384,11 @@ export function calendarHref(key: string, month?: string, filter: BoardFilter =
|
|
|
363
384
|
return parts.length === 0 ? base : `${base}?${parts.join("&")}`;
|
|
364
385
|
}
|
|
365
386
|
|
|
387
|
+
/** Where a page a console adds of its own is: on the workspace, or on a project when its key is given. */
|
|
388
|
+
export function customPageHref(page: string, project?: string): string {
|
|
389
|
+
return project === undefined ? `${PATHS.pages}/${page}` : `${projectHref(project)}/pages/${page}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
366
392
|
/** Where a goal is set: the form. */
|
|
367
393
|
export function newGoalHref(key: string): string {
|
|
368
394
|
return `${PATHS.project}/${key}/roadmap/new`;
|