@anchrd/intel-ui 0.4.0 → 0.6.0
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/package.json +1 -1
- package/src/app/action-slot/action-slot.tsx +27 -0
- package/src/app/app-sidebar/app-sidebar.tsx +39 -24
- package/src/app/app-tree/app-tree.tsx +332 -60
- package/src/app/app.tsx +31 -5
- package/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +2 -1
- package/src/app/tree-move/tree-move.tsx +331 -0
- package/src/app/user-footer/user-footer.tsx +73 -46
- package/src/app/view-toggle/view-toggle.tsx +77 -0
- package/src/blocknote-view/blocknote-view.tsx +19 -2
- package/src/branding/favicon.default.svg +2 -2
- package/src/branding/favicon.svg +2 -2
- package/src/components/ui/dropdown-menu.tsx +78 -0
- package/src/data/intel-data-provider/intel-data-provider.ts +135 -57
- package/src/data/intel-data-provider/intel-data-provider.types.ts +51 -13
- package/src/document-link/document-link.tsx +132 -0
- package/src/flow-runs/flow-runs.tsx +225 -0
- package/src/flows/flows.tsx +665 -271
- package/src/flows/node-icon/node-icon.ts +28 -0
- package/src/flows/node-palette/node-palette.tsx +200 -0
- package/src/flows/node-palette/node-palette.types.ts +15 -0
- package/src/graph-pane/graph-pane.tsx +44 -0
- package/src/i18n/en.json +144 -29
- package/src/knowledge/knowledge.tsx +91 -367
- package/src/knowledge-editor/knowledge-editor.tsx +169 -21
- package/src/knowledge-graph/knowledge-graph.ts +26 -24
- package/src/knowledge-graph/knowledge-graph.tsx +33 -24
- package/src/knowledge-table/knowledge-table.tsx +141 -0
- package/src/main.tsx +2 -2
- package/src/resource-menu/resource-menu.tsx +615 -0
- package/src/router/selection-search.ts +27 -3
- package/src/save-button/save-button.tsx +103 -0
- package/src/styles.css +37 -0
- package/src/theme/theme.ts +24 -0
- package/src/title-row/title-row.tsx +49 -0
- package/src/tools/tools.tsx +57 -38
- package/src/app/header-actions/header-actions.tsx +0 -15
|
@@ -1,33 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ContextPolicy,
|
|
3
|
-
KnowledgeLinkRelation,
|
|
4
|
-
KnowledgeNode,
|
|
5
|
-
ResourceRole,
|
|
6
|
-
} from "@anchrd/intel-contract";
|
|
1
|
+
import type { KnowledgeNode } from "@anchrd/intel-contract";
|
|
7
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
3
|
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
9
|
-
import {
|
|
10
|
-
Archive,
|
|
11
|
-
Download,
|
|
12
|
-
History,
|
|
13
|
-
Link2,
|
|
14
|
-
Network,
|
|
15
|
-
Paperclip,
|
|
16
|
-
Share2,
|
|
17
|
-
Trash2,
|
|
18
|
-
} from "lucide-react";
|
|
4
|
+
import { Download, History, Paperclip } from "lucide-react";
|
|
19
5
|
import { lazy, Suspense, useState } from "react";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
6
|
+
import { ActionSlot } from "@/app/action-slot/action-slot.tsx";
|
|
7
|
+
import { ViewToggle } from "@/app/view-toggle/view-toggle.tsx";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
9
|
+
import { GraphPane } from "@/graph-pane/graph-pane.tsx";
|
|
10
|
+
import { KnowledgeTablePanel } from "@/knowledge-table/knowledge-table.tsx";
|
|
22
11
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
23
|
-
import { selectedFrom } from "@/router/selection-search.ts";
|
|
12
|
+
import { graphViewFrom, selectedFrom } from "@/router/selection-search.ts";
|
|
13
|
+
import { TitleRow } from "@/title-row/title-row.tsx";
|
|
24
14
|
|
|
25
15
|
const KnowledgeEditor = lazy(async () => ({
|
|
26
16
|
default: (await import("@/knowledge-editor/knowledge-editor.tsx")).KnowledgeEditor,
|
|
27
17
|
}));
|
|
28
|
-
const KnowledgeGraphView = lazy(async () => ({
|
|
29
|
-
default: (await import("@/knowledge-graph/knowledge-graph.tsx")).KnowledgeGraphView,
|
|
30
|
-
}));
|
|
31
18
|
|
|
32
19
|
export function Knowledge() {
|
|
33
20
|
const { data, i18n } = useIntelRouterContext();
|
|
@@ -35,73 +22,56 @@ export function Knowledge() {
|
|
|
35
22
|
const navigate = useNavigate();
|
|
36
23
|
// The tree in the sidebar and the header search both name what to open through `?select=`, so this
|
|
37
24
|
// screen has no selection of its own to keep in step with them.
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const graph = useQuery({
|
|
44
|
-
queryKey: ["knowledge-graph"],
|
|
45
|
-
queryFn: () => data.getKnowledgeGraph(),
|
|
46
|
-
// The link dialog needs candidates to link to, and the graph is the one authorized list of
|
|
47
|
-
// nodes the UI can ask for without walking the whole tree again.
|
|
48
|
-
enabled: graphOpen || linksOpen,
|
|
25
|
+
const selection = useRouterState({
|
|
26
|
+
select: (state) => ({
|
|
27
|
+
id: selectedFrom(state.location.search),
|
|
28
|
+
graph: graphViewFrom(state.location.search),
|
|
29
|
+
}),
|
|
49
30
|
});
|
|
31
|
+
const selectedId = selection.id;
|
|
32
|
+
const [versionsOpen, setVersionsOpen] = useState(false);
|
|
50
33
|
const document = useQuery({
|
|
51
34
|
queryKey: ["knowledge", selectedId],
|
|
52
35
|
queryFn: () => data.getKnowledge(selectedId ?? ""),
|
|
53
36
|
enabled: Boolean(selectedId),
|
|
54
37
|
});
|
|
55
38
|
const selected = document.data?.node ?? null;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}),
|
|
64
|
-
onSuccess: async (node) => {
|
|
65
|
-
// The row leaves the tree, so the level it sat in is what has to be re-read — and the screen
|
|
66
|
-
// has nothing left to show.
|
|
67
|
-
await queryClient.invalidateQueries({ queryKey: ["tree", node.parentId] });
|
|
68
|
-
await navigate({ to: "/knowledge", search: {} });
|
|
69
|
-
},
|
|
39
|
+
// A folder shows its contents as a graph, and so does the root of the tree (#19). A document has
|
|
40
|
+
// nothing to draw — which is why the switch is not offered on one rather than offered and empty.
|
|
41
|
+
const graphable = selectedId === null || selected?.kind === "folder";
|
|
42
|
+
const relations = useQuery({
|
|
43
|
+
queryKey: ["relation-graph", "folder", selectedId],
|
|
44
|
+
queryFn: () => data.getRelationGraph({ of: "folder", folderId: selectedId }),
|
|
45
|
+
enabled: graphable && selection.graph,
|
|
70
46
|
});
|
|
71
|
-
const setContextPolicy = useMutation({
|
|
72
|
-
mutationFn: ({ node, contextPolicy }: { node: KnowledgeNode; contextPolicy: ContextPolicy }) =>
|
|
73
|
-
data.updateKnowledge({
|
|
74
|
-
nodeId: node.id,
|
|
75
|
-
baseUpdatedAt: node.updatedAt,
|
|
76
|
-
contextPolicy,
|
|
77
|
-
idempotencyKey: crypto.randomUUID(),
|
|
78
|
-
}),
|
|
79
|
-
onSuccess: async (node) => {
|
|
80
|
-
await queryClient.invalidateQueries({ queryKey: ["knowledge", node.id] });
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
|
|
84
47
|
return (
|
|
85
48
|
<div className="flex h-full min-h-0 flex-col">
|
|
86
49
|
{/* The screen owns the one action that belongs to Knowledge as a whole. Creating things is
|
|
87
|
-
the tree's, in the sidebar, at the place a new thing is meant to go.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{i18n.t("knowledge.graph")}
|
|
96
|
-
</button>
|
|
97
|
-
</HeaderActions>
|
|
98
|
-
{archive.isError || setContextPolicy.isError ? (
|
|
99
|
-
<p role="alert" className="border-b px-8 py-3 text-sm text-destructive">
|
|
100
|
-
{i18n.t("knowledge.operationFailed")}
|
|
101
|
-
</p>
|
|
50
|
+
the tree's, in the sidebar, at the place a new thing is meant to go.
|
|
51
|
+
|
|
52
|
+
⚠️ Mounted only where there is a graph to switch to. A switch on a document would point at
|
|
53
|
+
nothing, and the header is where that gets noticed first (#19). */}
|
|
54
|
+
{graphable ? (
|
|
55
|
+
<ActionSlot>
|
|
56
|
+
<ViewToggle />
|
|
57
|
+
</ActionSlot>
|
|
102
58
|
) : null}
|
|
103
59
|
<section className="relative flex min-h-0 min-w-0 flex-1 flex-col bg-card">
|
|
104
|
-
{
|
|
60
|
+
{/* ⚠️ Only the root draws its graph on its own. A folder's graph hangs *under* the folder's
|
|
61
|
+
title line rather than in place of it (#58): the line is the one place its menu lives,
|
|
62
|
+
and a folder that could only be renamed while the graph happened to be switched off
|
|
63
|
+
would be a folder one cannot rename. */}
|
|
64
|
+
{selectedId === null && selection.graph ? (
|
|
65
|
+
<GraphPane
|
|
66
|
+
query={relations}
|
|
67
|
+
select={(node) =>
|
|
68
|
+
void navigate({
|
|
69
|
+
to: node.kind === "flow" ? "/flows" : "/knowledge",
|
|
70
|
+
search: { select: node.id },
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
) : !selectedId ? (
|
|
105
75
|
<div className="grid flex-1 place-items-center p-8 text-center text-sm text-muted-foreground">
|
|
106
76
|
{i18n.t("knowledge.select")}
|
|
107
77
|
</div>
|
|
@@ -122,72 +92,56 @@ export function Knowledge() {
|
|
|
122
92
|
</div>
|
|
123
93
|
) : (
|
|
124
94
|
<>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
onClick={() => setVersionsOpen((value) => !value)}
|
|
169
|
-
aria-label={i18n.t("knowledge.versions")}
|
|
170
|
-
className="rounded-md p-2 outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
171
|
-
>
|
|
172
|
-
<History aria-hidden="true" className="size-4" />
|
|
173
|
-
</button>
|
|
174
|
-
)}
|
|
175
|
-
<button
|
|
176
|
-
type="button"
|
|
177
|
-
onClick={() => archive.mutate(selected)}
|
|
178
|
-
aria-label={i18n.t("knowledge.archive")}
|
|
179
|
-
className="rounded-md p-2 text-destructive outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
180
|
-
>
|
|
181
|
-
<Archive aria-hidden="true" className="size-4" />
|
|
182
|
-
</button>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
{selected.kind === "folder" ? (
|
|
95
|
+
{/* The document's own line: its name, and beside it only what acts on this one
|
|
96
|
+
document. What the four loose icons used to do is now one menu (#24) — and since #58
|
|
97
|
+
this line is the only place that menu appears at all, for every kind including a
|
|
98
|
+
folder, whose whole screen is this line.
|
|
99
|
+
⚠️ The order of the right-hand group is `TitleRow`'s (#53), not this screen's: the
|
|
100
|
+
version button is handed over as a child and lands before the menu whatever else
|
|
101
|
+
shows up later.
|
|
102
|
+
⚠️ The view switch is deliberately NOT here: it changes how the current area is
|
|
103
|
+
shown, not the document, and it stays beside the breadcrumb where the area is
|
|
104
|
+
named. */}
|
|
105
|
+
<TitleRow
|
|
106
|
+
title={selected.title}
|
|
107
|
+
description={selected.description}
|
|
108
|
+
target={{ type: "knowledge", node: selected }}
|
|
109
|
+
>
|
|
110
|
+
{selected.kind !== "folder" && (
|
|
111
|
+
<TooltipProvider delayDuration={300}>
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={() => setVersionsOpen((value) => !value)}
|
|
116
|
+
aria-label={i18n.t("knowledge.versions")}
|
|
117
|
+
aria-expanded={versionsOpen}
|
|
118
|
+
className="inline-flex size-8 items-center justify-center rounded-md border bg-background outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring"
|
|
119
|
+
>
|
|
120
|
+
<History aria-hidden="true" className="size-4" />
|
|
121
|
+
</TooltipTrigger>
|
|
122
|
+
<TooltipContent>{i18n.t("knowledge.versions")}</TooltipContent>
|
|
123
|
+
</Tooltip>
|
|
124
|
+
</TooltipProvider>
|
|
125
|
+
)}
|
|
126
|
+
</TitleRow>
|
|
127
|
+
{graphable && selection.graph ? (
|
|
128
|
+
<GraphPane
|
|
129
|
+
query={relations}
|
|
130
|
+
select={(node) =>
|
|
131
|
+
void navigate({
|
|
132
|
+
to: node.kind === "flow" ? "/flows" : "/knowledge",
|
|
133
|
+
search: { select: node.id },
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
) : selected.kind === "folder" ? (
|
|
186
138
|
<div className="grid flex-1 place-items-center p-8 text-sm text-muted-foreground">
|
|
187
139
|
{i18n.t("knowledge.folderHelp")}
|
|
188
140
|
</div>
|
|
189
141
|
) : selected.kind === "attachment" ? (
|
|
190
142
|
<AttachmentPanel node={selected} />
|
|
143
|
+
) : selected.kind === "table" ? (
|
|
144
|
+
<KnowledgeTablePanel node={selected} />
|
|
191
145
|
) : document.data ? (
|
|
192
146
|
<Suspense
|
|
193
147
|
fallback={
|
|
@@ -211,43 +165,6 @@ export function Knowledge() {
|
|
|
211
165
|
</>
|
|
212
166
|
)}
|
|
213
167
|
</section>
|
|
214
|
-
{sharing && selected && <ShareKnowledge node={selected} close={() => setSharing(false)} />}
|
|
215
|
-
{linksOpen && selected && (
|
|
216
|
-
<KnowledgeLinks
|
|
217
|
-
node={selected}
|
|
218
|
-
nodes={graph.data?.nodes ?? []}
|
|
219
|
-
close={() => setLinksOpen(false)}
|
|
220
|
-
/>
|
|
221
|
-
)}
|
|
222
|
-
{graphOpen && !graph.data && (
|
|
223
|
-
<Modal title={i18n.t("knowledge.graph")} close={() => setGraphOpen(false)}>
|
|
224
|
-
<p role={graph.isError ? "alert" : undefined} className="text-sm text-muted-foreground">
|
|
225
|
-
{graph.isError ? i18n.t("knowledge.graphFailed") : i18n.t("common.loading")}
|
|
226
|
-
</p>
|
|
227
|
-
{graph.isError && (
|
|
228
|
-
<button
|
|
229
|
-
type="button"
|
|
230
|
-
onClick={() => void graph.refetch()}
|
|
231
|
-
className="mt-4 rounded-md border px-3 py-2 text-sm outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
232
|
-
>
|
|
233
|
-
{i18n.t("common.retry")}
|
|
234
|
-
</button>
|
|
235
|
-
)}
|
|
236
|
-
</Modal>
|
|
237
|
-
)}
|
|
238
|
-
{graphOpen && graph.data && (
|
|
239
|
-
<Suspense fallback={null}>
|
|
240
|
-
<KnowledgeGraphView
|
|
241
|
-
data={graph.data}
|
|
242
|
-
i18n={i18n}
|
|
243
|
-
close={() => setGraphOpen(false)}
|
|
244
|
-
select={(nodeId) => {
|
|
245
|
-
setGraphOpen(false);
|
|
246
|
-
void navigate({ to: "/knowledge", search: { select: nodeId } });
|
|
247
|
-
}}
|
|
248
|
-
/>
|
|
249
|
-
</Suspense>
|
|
250
|
-
)}
|
|
251
168
|
</div>
|
|
252
169
|
);
|
|
253
170
|
}
|
|
@@ -292,199 +209,6 @@ function AttachmentPanel({ node }: { node: KnowledgeNode }) {
|
|
|
292
209
|
);
|
|
293
210
|
}
|
|
294
211
|
|
|
295
|
-
function KnowledgeLinks({
|
|
296
|
-
node,
|
|
297
|
-
nodes,
|
|
298
|
-
close,
|
|
299
|
-
}: {
|
|
300
|
-
node: KnowledgeNode;
|
|
301
|
-
nodes: KnowledgeNode[];
|
|
302
|
-
close(): void;
|
|
303
|
-
}) {
|
|
304
|
-
const { data, i18n } = useIntelRouterContext();
|
|
305
|
-
const queryClient = useQueryClient();
|
|
306
|
-
const [targetNodeId, setTargetNodeId] = useState("");
|
|
307
|
-
const [relation, setRelation] = useState<KnowledgeLinkRelation>("related");
|
|
308
|
-
const links = useQuery({
|
|
309
|
-
queryKey: ["knowledge-links", node.id],
|
|
310
|
-
queryFn: () => data.listKnowledgeLinks(node.id),
|
|
311
|
-
});
|
|
312
|
-
const create = useMutation({
|
|
313
|
-
mutationFn: () =>
|
|
314
|
-
data.createKnowledgeLink({
|
|
315
|
-
sourceNodeId: node.id,
|
|
316
|
-
targetNodeId,
|
|
317
|
-
relation,
|
|
318
|
-
label: null,
|
|
319
|
-
idempotencyKey: crypto.randomUUID(),
|
|
320
|
-
}),
|
|
321
|
-
onSuccess: async () => {
|
|
322
|
-
setTargetNodeId("");
|
|
323
|
-
await Promise.all([
|
|
324
|
-
queryClient.invalidateQueries({ queryKey: ["knowledge-links", node.id] }),
|
|
325
|
-
queryClient.invalidateQueries({ queryKey: ["knowledge-graph"] }),
|
|
326
|
-
]);
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
const remove = useMutation({
|
|
330
|
-
mutationFn: (linkId: string) =>
|
|
331
|
-
data.deleteKnowledgeLink({
|
|
332
|
-
sourceNodeId: node.id,
|
|
333
|
-
linkId,
|
|
334
|
-
idempotencyKey: crypto.randomUUID(),
|
|
335
|
-
}),
|
|
336
|
-
onSuccess: async () => {
|
|
337
|
-
await Promise.all([
|
|
338
|
-
queryClient.invalidateQueries({ queryKey: ["knowledge-links", node.id] }),
|
|
339
|
-
queryClient.invalidateQueries({ queryKey: ["knowledge-graph"] }),
|
|
340
|
-
]);
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
const titles = new Map(nodes.map((candidate) => [candidate.id, candidate.title]));
|
|
344
|
-
|
|
345
|
-
return (
|
|
346
|
-
<Modal title={i18n.t("knowledge.links")} close={close}>
|
|
347
|
-
<ul className="mb-5 max-h-48 space-y-2 overflow-y-auto">
|
|
348
|
-
{links.data?.items.map((link) => {
|
|
349
|
-
const outgoing = link.sourceNodeId === node.id;
|
|
350
|
-
const otherId = outgoing ? link.targetNodeId : link.sourceNodeId;
|
|
351
|
-
return (
|
|
352
|
-
<li
|
|
353
|
-
key={link.id}
|
|
354
|
-
className="flex items-center justify-between gap-3 rounded-md border p-3"
|
|
355
|
-
>
|
|
356
|
-
<div className="min-w-0 text-sm">
|
|
357
|
-
<span className="block truncate font-medium">{titles.get(otherId) ?? otherId}</span>
|
|
358
|
-
<span className="text-xs text-muted-foreground">
|
|
359
|
-
{outgoing ? "→" : "←"} {link.label ?? link.relation.replaceAll("_", " ")}
|
|
360
|
-
</span>
|
|
361
|
-
</div>
|
|
362
|
-
{outgoing && (
|
|
363
|
-
<button
|
|
364
|
-
type="button"
|
|
365
|
-
onClick={() => remove.mutate(link.id)}
|
|
366
|
-
aria-label={i18n.t("knowledge.deleteLink")}
|
|
367
|
-
className="rounded-md p-2 text-destructive outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
368
|
-
>
|
|
369
|
-
<Trash2 aria-hidden="true" className="size-4" />
|
|
370
|
-
</button>
|
|
371
|
-
)}
|
|
372
|
-
</li>
|
|
373
|
-
);
|
|
374
|
-
})}
|
|
375
|
-
{links.data?.items.length === 0 && (
|
|
376
|
-
<li className="text-sm text-muted-foreground">{i18n.t("knowledge.noLinks")}</li>
|
|
377
|
-
)}
|
|
378
|
-
</ul>
|
|
379
|
-
<form
|
|
380
|
-
className="space-y-4 border-t pt-5"
|
|
381
|
-
onSubmit={(event) => {
|
|
382
|
-
event.preventDefault();
|
|
383
|
-
create.mutate();
|
|
384
|
-
}}
|
|
385
|
-
>
|
|
386
|
-
<label className="block text-sm font-medium">
|
|
387
|
-
{i18n.t("knowledge.linkTarget")}
|
|
388
|
-
<select
|
|
389
|
-
required
|
|
390
|
-
value={targetNodeId}
|
|
391
|
-
onChange={(event) => setTargetNodeId(event.target.value)}
|
|
392
|
-
className="mt-2 w-full rounded-md border bg-background px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
393
|
-
>
|
|
394
|
-
<option value="">{i18n.t("knowledge.selectLinkTarget")}</option>
|
|
395
|
-
{nodes
|
|
396
|
-
.filter((candidate) => candidate.id !== node.id)
|
|
397
|
-
.map((candidate) => (
|
|
398
|
-
<option key={candidate.id} value={candidate.id}>
|
|
399
|
-
{candidate.title}
|
|
400
|
-
</option>
|
|
401
|
-
))}
|
|
402
|
-
</select>
|
|
403
|
-
</label>
|
|
404
|
-
<label className="block text-sm font-medium">
|
|
405
|
-
{i18n.t("knowledge.relation")}
|
|
406
|
-
<select
|
|
407
|
-
value={relation}
|
|
408
|
-
onChange={(event) => setRelation(event.target.value as KnowledgeLinkRelation)}
|
|
409
|
-
className="mt-2 w-full rounded-md border bg-background px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
410
|
-
>
|
|
411
|
-
{(["related", "references", "depends_on", "implements"] as const).map((value) => (
|
|
412
|
-
<option key={value} value={value}>
|
|
413
|
-
{i18n.t(`knowledge.relation.${value}`)}
|
|
414
|
-
</option>
|
|
415
|
-
))}
|
|
416
|
-
</select>
|
|
417
|
-
</label>
|
|
418
|
-
<button
|
|
419
|
-
type="submit"
|
|
420
|
-
disabled={!targetNodeId || create.isPending}
|
|
421
|
-
className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground outline-none hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
422
|
-
>
|
|
423
|
-
{i18n.t("knowledge.createLink")}
|
|
424
|
-
</button>
|
|
425
|
-
</form>
|
|
426
|
-
</Modal>
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function ShareKnowledge({ node, close }: { node: KnowledgeNode; close(): void }) {
|
|
431
|
-
const { data, i18n } = useIntelRouterContext();
|
|
432
|
-
const [email, setEmail] = useState("");
|
|
433
|
-
const [role, setRole] = useState<ResourceRole>("viewer");
|
|
434
|
-
const mutation = useMutation({
|
|
435
|
-
mutationFn: () =>
|
|
436
|
-
data.shareKnowledge({
|
|
437
|
-
resourceId: node.id,
|
|
438
|
-
principal: { type: "email", email },
|
|
439
|
-
role,
|
|
440
|
-
expiresAt: null,
|
|
441
|
-
idempotencyKey: crypto.randomUUID(),
|
|
442
|
-
}),
|
|
443
|
-
onSuccess: close,
|
|
444
|
-
});
|
|
445
|
-
return (
|
|
446
|
-
<Modal title={i18n.t("knowledge.share")} close={close}>
|
|
447
|
-
<form
|
|
448
|
-
onSubmit={(event) => {
|
|
449
|
-
event.preventDefault();
|
|
450
|
-
mutation.mutate();
|
|
451
|
-
}}
|
|
452
|
-
className="space-y-4"
|
|
453
|
-
>
|
|
454
|
-
<label className="block text-sm font-medium">
|
|
455
|
-
{i18n.t("knowledge.email")}
|
|
456
|
-
<input
|
|
457
|
-
type="email"
|
|
458
|
-
required
|
|
459
|
-
value={email}
|
|
460
|
-
onChange={(event) => setEmail(event.target.value)}
|
|
461
|
-
className="mt-2 w-full rounded-md border bg-background px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
462
|
-
/>
|
|
463
|
-
</label>
|
|
464
|
-
<label className="block text-sm font-medium">
|
|
465
|
-
{i18n.t("knowledge.role")}
|
|
466
|
-
<select
|
|
467
|
-
value={role}
|
|
468
|
-
onChange={(event) => setRole(event.target.value as ResourceRole)}
|
|
469
|
-
className="mt-2 w-full rounded-md border bg-background px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
470
|
-
>
|
|
471
|
-
<option value="viewer">{i18n.t("knowledge.viewer")}</option>
|
|
472
|
-
<option value="editor">{i18n.t("knowledge.editor")}</option>
|
|
473
|
-
<option value="manager">{i18n.t("knowledge.manager")}</option>
|
|
474
|
-
</select>
|
|
475
|
-
</label>
|
|
476
|
-
<button
|
|
477
|
-
type="submit"
|
|
478
|
-
disabled={mutation.isPending}
|
|
479
|
-
className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground outline-none hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
480
|
-
>
|
|
481
|
-
{i18n.t("knowledge.shareAction")}
|
|
482
|
-
</button>
|
|
483
|
-
</form>
|
|
484
|
-
</Modal>
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
212
|
function VersionHistory({ nodeId, close }: { nodeId: string; close(): void }) {
|
|
489
213
|
const { data, i18n } = useIntelRouterContext();
|
|
490
214
|
const versions = useQuery({
|