@anchrd/intel-ui 0.36.0 → 0.38.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 +2 -2
- package/src/app/app-tree/app-tree.tsx +15 -6
- package/src/app/view-toggle/view-toggle.tsx +7 -2
- package/src/board/board-data/board-data.ts +169 -0
- package/src/board/board-data/board-data.types.ts +29 -0
- package/src/board/board-kanban/board-kanban.ts +108 -0
- package/src/board/board-kanban/board-kanban.tsx +295 -0
- package/src/board/board-panel/board-panel.tsx +74 -0
- package/src/board/board-table/board-table.ts +135 -0
- package/src/board/board-table/board-table.tsx +199 -0
- package/src/data/intel-data-provider/intel-data-provider.ts +50 -0
- package/src/data/intel-data-provider/intel-data-provider.types.ts +22 -0
- package/src/document-link/document-link.tsx +0 -18
- package/src/editor-schema/editor-schema.ts +21 -0
- package/src/flows/flows.tsx +11 -1
- package/src/frontmatter/frontmatter-markdown.ts +63 -0
- package/src/frontmatter/frontmatter.tsx +184 -0
- package/src/i18n/de.json +389 -363
- package/src/i18n/en.json +389 -363
- package/src/i18n/es.json +389 -363
- package/src/kind-icon.ts +17 -1
- package/src/node-editor/node-editor.tsx +37 -29
- package/src/nodes/nodes.tsx +3 -0
- package/src/resource-menu/resource-menu.tsx +12 -38
- package/src/title-row/title-row.tsx +12 -1
package/src/kind-icon.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CircleCheck,
|
|
3
|
+
FileText,
|
|
4
|
+
Folder,
|
|
5
|
+
KanbanSquare,
|
|
6
|
+
type LucideIcon,
|
|
7
|
+
Paperclip,
|
|
8
|
+
Table,
|
|
9
|
+
Workflow,
|
|
10
|
+
} from "lucide-react";
|
|
2
11
|
import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
3
12
|
|
|
4
13
|
/**
|
|
@@ -16,4 +25,11 @@ export const kindIcons: Record<TreeEntry["kind"], LucideIcon> = {
|
|
|
16
25
|
attachment: Paperclip,
|
|
17
26
|
table: Table,
|
|
18
27
|
flow: Workflow,
|
|
28
|
+
// ⚠️ A board draws as a board, never as a folder, although it behaves like one in the tree. The
|
|
29
|
+
// icon is the ONLY thing that tells a reader the row will not open (D66, #376) — the chevron is
|
|
30
|
+
// absent, and an absent chevron looks exactly like an empty folder.
|
|
31
|
+
board: KanbanSquare,
|
|
32
|
+
// A task has an icon although the tree never draws one: the folder table, search results and the
|
|
33
|
+
// relation graph all reach a task by its own address, and each of them asks this map.
|
|
34
|
+
task: CircleCheck,
|
|
19
35
|
};
|
|
@@ -14,13 +14,14 @@ import { ActionSlot } from "@/app/action-slot/action-slot.tsx";
|
|
|
14
14
|
import { BlockNoteView, defaultSlashMenuItems } from "@/blocknote-view/blocknote-view.tsx";
|
|
15
15
|
import { IntelRequestError } from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
16
16
|
import type { IntelDataProvider } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
17
|
+
import { DocumentLinkProvider, documentLinkIds } from "@/document-link/document-link.tsx";
|
|
17
18
|
import {
|
|
18
|
-
DocumentLinkProvider,
|
|
19
|
-
documentLinkIds,
|
|
20
19
|
type IntelEditor,
|
|
21
20
|
type IntelEditorPartialBlock,
|
|
22
21
|
intelEditorSchema,
|
|
23
|
-
} from "@/
|
|
22
|
+
} from "@/editor-schema/editor-schema.ts";
|
|
23
|
+
import { FrontmatterProvider } from "@/frontmatter/frontmatter.tsx";
|
|
24
|
+
import { blocksToMarkdown, markdownToBlocks } from "@/frontmatter/frontmatter-markdown.ts";
|
|
24
25
|
import type { I18n } from "@/i18n/i18n.types.ts";
|
|
25
26
|
import { Modal } from "@/modal/modal.tsx";
|
|
26
27
|
import { SaveButton, UnsavedChangesGuard } from "@/save-button/save-button.tsx";
|
|
@@ -135,7 +136,7 @@ export function NodeEditor({
|
|
|
135
136
|
format: "blocknote",
|
|
136
137
|
schemaVersion: 1,
|
|
137
138
|
blocks,
|
|
138
|
-
markdown: await editor
|
|
139
|
+
markdown: await blocksToMarkdown(editor, blocks),
|
|
139
140
|
});
|
|
140
141
|
const node = await data.saveNode({
|
|
141
142
|
nodeId: document.node.id,
|
|
@@ -163,7 +164,7 @@ export function NodeEditor({
|
|
|
163
164
|
|
|
164
165
|
useEffect(() => {
|
|
165
166
|
if (!document.content || initialBlocks) return;
|
|
166
|
-
const blocks = editor
|
|
167
|
+
const blocks = markdownToBlocks(editor, document.content);
|
|
167
168
|
editor.replaceBlocks(editor.document, blocks);
|
|
168
169
|
// ⚠️ Loading the content IS a change to the editor, and `onChange` reports it as one. This is
|
|
169
170
|
// the line that keeps a document nobody has touched from asking to be saved: what was just
|
|
@@ -207,33 +208,40 @@ export function NodeEditor({
|
|
|
207
208
|
unresolvedLabel: i18n.t("node.link.unresolved"),
|
|
208
209
|
}}
|
|
209
210
|
>
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
setLinkedIds(documentLinkIds(editor.document));
|
|
211
|
+
<FrontmatterProvider
|
|
212
|
+
value={{
|
|
213
|
+
summary: i18n.t("node.frontmatter.summary"),
|
|
214
|
+
expand: i18n.t("node.frontmatter.toggle"),
|
|
215
215
|
}}
|
|
216
216
|
>
|
|
217
|
-
|
|
217
|
+
<BlockNoteView
|
|
218
|
+
editor={editor}
|
|
219
|
+
onChange={() => {
|
|
220
|
+
settle();
|
|
221
|
+
setLinkedIds(documentLinkIds(editor.document));
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
{/* The `/` line BlockNote already has, with one item added. A second way to reach the
|
|
218
225
|
same thing would be the second way #41 exists to remove. */}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
<SuggestionMenuController
|
|
227
|
+
triggerCharacter="/"
|
|
228
|
+
getItems={async (query) =>
|
|
229
|
+
filterSuggestionItems(
|
|
230
|
+
[
|
|
231
|
+
...defaultSlashMenuItems(editor),
|
|
232
|
+
{
|
|
233
|
+
title: i18n.t("node.link.insert"),
|
|
234
|
+
group: i18n.t("node.link.group"),
|
|
235
|
+
icon: <Link2 aria-hidden="true" className="size-4" />,
|
|
236
|
+
onItemClick: () => setPicking(true),
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
query,
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
243
|
+
</BlockNoteView>
|
|
244
|
+
</FrontmatterProvider>
|
|
237
245
|
</DocumentLinkProvider>
|
|
238
246
|
</TitleRowScrollArea>
|
|
239
247
|
{picking ? (
|
package/src/nodes/nodes.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { Download, Paperclip } from "lucide-react";
|
|
|
5
5
|
import { lazy, Suspense } from "react";
|
|
6
6
|
import { treeLevelKey } from "@/app/tree-move/tree-move.tsx";
|
|
7
7
|
import { ViewToggle } from "@/app/view-toggle/view-toggle.tsx";
|
|
8
|
+
import { BoardPanel } from "@/board/board-panel/board-panel.tsx";
|
|
8
9
|
import { RefusalNotice } from "@/data/request-refusal/refusal-notice.tsx";
|
|
9
10
|
import { refusalOf } from "@/data/request-refusal/request-refusal.ts";
|
|
10
11
|
import { FolderContents } from "@/folder-contents/folder-contents.tsx";
|
|
@@ -160,6 +161,8 @@ export function Nodes() {
|
|
|
160
161
|
<AttachmentPanel node={selected} />
|
|
161
162
|
) : selected.kind === "table" ? (
|
|
162
163
|
<NodeTablePanel node={selected} />
|
|
164
|
+
) : selected.kind === "board" ? (
|
|
165
|
+
<BoardPanel node={selected} />
|
|
163
166
|
) : document.data ? (
|
|
164
167
|
<Suspense
|
|
165
168
|
fallback={
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
ShieldCheck,
|
|
19
19
|
} from "lucide-react";
|
|
20
20
|
import type * as React from "react";
|
|
21
|
-
import {
|
|
21
|
+
import { useState } from "react";
|
|
22
22
|
import { AccessSummary } from "@/access-summary/access-summary.tsx";
|
|
23
23
|
import { allTreeLevelsKey, moveErrorKey, useTreeMove } from "@/app/tree-move/tree-move.tsx";
|
|
24
24
|
import {
|
|
@@ -154,11 +154,6 @@ export function ResourceMenu({
|
|
|
154
154
|
// nowhere else: a person had strictly less reach on their own table than a model did.
|
|
155
155
|
const isTable = node !== null && node.kind === "table";
|
|
156
156
|
const bundleImport = useBundleImport({ where: title });
|
|
157
|
-
// ⚠️ Per instance, not a constant: the folder table renders one menu PER ROW, so a fixed id would
|
|
158
|
-
// put the same `id` in the document twenty times and every entry's name would resolve to the
|
|
159
|
-
// first one's.
|
|
160
|
-
const exportLabelId = useId();
|
|
161
|
-
const exportHintId = useId();
|
|
162
157
|
|
|
163
158
|
// Everything a change here can make stale. The row sits in one level of the tree, the open screen
|
|
164
159
|
// reads the record, and the flat collections behind the search, the link picker and the relation
|
|
@@ -323,38 +318,17 @@ export function ResourceMenu({
|
|
|
323
318
|
(#534). That is the export's gap on every kind it can hit, not something this entry
|
|
324
319
|
was covering — which is why it is fixed there and not by keeping a second entry.
|
|
325
320
|
|
|
326
|
-
⚠️ The
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
behind" would expect the opposite of what happens. `Excluded` in `bundle.ts` names it
|
|
338
|
-
with a word of its own for the same reason.
|
|
339
|
-
|
|
340
|
-
⚠️ It is the entry's DESCRIPTION, not part of its name: `aria-labelledby` keeps the
|
|
341
|
-
name at the one word every other entry here uses, and `aria-describedby` is what a
|
|
342
|
-
screen reader reads after it. Folding a whole sentence into the name would make this
|
|
343
|
-
the only entry in the menu somebody has to listen through to know what it does — and
|
|
344
|
-
the sentence is a condition of the action, which is what a description is for. */}
|
|
345
|
-
<DropdownMenuItem
|
|
346
|
-
className="items-start"
|
|
347
|
-
aria-labelledby={exportLabelId}
|
|
348
|
-
aria-describedby={exportHintId}
|
|
349
|
-
onSelect={() => exportBundle.mutate()}
|
|
350
|
-
>
|
|
351
|
-
<FolderDown aria-hidden="true" className="mt-0.5" />
|
|
352
|
-
<span className="flex flex-col gap-0.5">
|
|
353
|
-
<span id={exportLabelId}>{i18n.t("resource.export")}</span>
|
|
354
|
-
<span id={exportHintId} className="text-xs text-muted-foreground">
|
|
355
|
-
{i18n.t("resource.exportExcludes")}
|
|
356
|
-
</span>
|
|
357
|
-
</span>
|
|
321
|
+
⚠️ The entry carried a three-clause description under its name until 2026-08-19, put
|
|
322
|
+
there by #433/ADR-0006 so that an irreversible loss was readable BEFORE the gesture: a
|
|
323
|
+
bundle takes one version per node, shares and runs stay behind, and a published flow
|
|
324
|
+
travels as its published graph (#585). Jack removed it — it was the only entry running
|
|
325
|
+
over two lines and it pushed the menu apart. What the reader now gets instead is
|
|
326
|
+
`excluded` in the zip's `manifest.json` and `BundleImportResult.excluded` after an
|
|
327
|
+
import, both of which are read only once the zip exists. That is a deliberate trade,
|
|
328
|
+
recorded in ADR-0006 and in #636, and NOT a leftover to be restored. */}
|
|
329
|
+
<DropdownMenuItem onSelect={() => exportBundle.mutate()}>
|
|
330
|
+
<FolderDown aria-hidden="true" />
|
|
331
|
+
{i18n.t("resource.export")}
|
|
358
332
|
</DropdownMenuItem>
|
|
359
333
|
{/* The other half of the same round trip, next to it rather than in the tree's plus
|
|
360
334
|
(#346): one word in the menu, both sources under it. */}
|
|
@@ -69,11 +69,22 @@ export function TitleRow({
|
|
|
69
69
|
title,
|
|
70
70
|
description,
|
|
71
71
|
target,
|
|
72
|
+
separated,
|
|
72
73
|
children,
|
|
73
74
|
}: {
|
|
74
75
|
title: string;
|
|
75
76
|
description?: string | null;
|
|
76
77
|
target: ResourceTarget;
|
|
78
|
+
/**
|
|
79
|
+
* A separator that does NOT wait for a scroll.
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ For a surface where scrolling cannot happen, and that is the only case it is for (#634). The
|
|
82
|
+
* edge below this row is normally `scrolled`, set by `TitleRowScrollArea` — which is right where
|
|
83
|
+
* content moves under the row. The flow canvas does not scroll, it pans: nothing ever sets
|
|
84
|
+
* `scrolled`, so the row sat on the dark canvas with no edge at all. Do not pass this where the
|
|
85
|
+
* content does scroll; a permanent edge there would replace a signal with decoration.
|
|
86
|
+
*/
|
|
87
|
+
separated?: boolean;
|
|
77
88
|
children?: ReactNode;
|
|
78
89
|
}) {
|
|
79
90
|
const scroll = useContext(TitleRowScrollContext);
|
|
@@ -88,7 +99,7 @@ export function TitleRow({
|
|
|
88
99
|
data-scrolled={scroll?.scrolled || undefined}
|
|
89
100
|
className={cn(
|
|
90
101
|
"relative z-10 flex shrink-0 items-start justify-between gap-5 px-6 py-4 transition-shadow",
|
|
91
|
-
scroll?.scrolled && "shadow-[inset_0_-1px_0_var(--border)]",
|
|
102
|
+
(separated || scroll?.scrolled) && "shadow-[inset_0_-1px_0_var(--border)]",
|
|
92
103
|
)}
|
|
93
104
|
>
|
|
94
105
|
<div className="flex min-w-0 items-center gap-3">
|