@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/SKILL.md +147 -0
- package/cordis.patch.yml +5 -0
- package/i18n/zh.json +10 -0
- package/lib/client.js +15976 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +5724 -0
- package/lib/types/api.d.ts +574 -0
- package/lib/types/client/KnowledgeAudit.d.ts +39 -0
- package/lib/types/client/KnowledgeContextMenu.d.ts +96 -0
- package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
- package/lib/types/client/KnowledgeFileTree.d.ts +88 -0
- package/lib/types/client/KnowledgeHome.d.ts +79 -0
- package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
- package/lib/types/client/KnowledgeListing.d.ts +160 -0
- package/lib/types/client/KnowledgeLoading.d.ts +41 -0
- package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
- package/lib/types/client/KnowledgePanel.d.ts +58 -0
- package/lib/types/client/KnowledgePermissionOverview.d.ts +99 -0
- package/lib/types/client/KnowledgePermissions.d.ts +47 -0
- package/lib/types/client/KnowledgeReferencePicker.d.ts +65 -0
- package/lib/types/client/KnowledgeSearch.d.ts +83 -0
- package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
- package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
- package/lib/types/client/KnowledgeSettings.d.ts +96 -0
- package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
- package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
- package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
- package/lib/types/client/KnowledgeTrash.d.ts +62 -0
- package/lib/types/client/MenuGlyphs.d.ts +37 -0
- package/lib/types/client/departmentFolders.d.ts +57 -0
- package/lib/types/client/deployTarget.d.ts +34 -0
- package/lib/types/client/index.d.ts +27 -0
- package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
- package/lib/types/client/locales.d.ts +438 -0
- package/lib/types/client/openInSidebar.d.ts +62 -0
- package/lib/types/client/ownerLabel.d.ts +39 -0
- package/lib/types/client/permissionRows.d.ts +94 -0
- package/lib/types/client/roleSummary.d.ts +47 -0
- package/lib/types/client/saveAudit.d.ts +87 -0
- package/lib/types/client/useFavorites.d.ts +49 -0
- package/lib/types/client/usePinnedPaths.d.ts +39 -0
- package/lib/types/client/useTreeState.d.ts +46 -0
- package/lib/types/client/useViewMode.d.ts +39 -0
- package/lib/types/deploy.d.ts +209 -0
- package/lib/types/extract.d.ts +144 -0
- package/lib/types/index.d.ts +846 -0
- package/lib/types/permissions.d.ts +237 -0
- package/lib/types/protocol.d.ts +229 -0
- package/lib/types/remote.d.ts +105 -0
- package/lib/types/trash.d.ts +142 -0
- package/lib/types/viewMetadata.d.ts +20 -0
- package/lib/types/views.d.ts +923 -0
- package/package.json +109 -4
- package/skills/kb-organize/SKILL.md +365 -0
- package/skills/kb-organize/i18n/zh.json +10 -0
- package/README.md +0 -3
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row context menu for the knowledge tree.
|
|
3
|
+
*
|
|
4
|
+
* A right-click on a row used to jump straight to the permission surface, which meant a
|
|
5
|
+
* reader looking for "save this file" got an operator's dialog instead. The menu is the
|
|
6
|
+
* affordance a file list is expected to have, and it is built on the product's own `Menu`
|
|
7
|
+
* primitive rather than a hand-rolled popover: keyboard walking, Escape, outside click,
|
|
8
|
+
* and post-selection focus return are the primitive's behaviour, and a local copy of that
|
|
9
|
+
* would drift.
|
|
10
|
+
*
|
|
11
|
+
* The menu is positioned from the pointer rather than from an anchor element, which is
|
|
12
|
+
* what the primitive's `getAnchorRect` hook is for: a context menu opens where the reader
|
|
13
|
+
* clicked, not below the row's own control.
|
|
14
|
+
*
|
|
15
|
+
* Every action here is a call to a route that already exists. Sharing is `grant`, the
|
|
16
|
+
* star is `favorites.set`, and paste dispatches on what the clipboard holds: a cut pastes
|
|
17
|
+
* through `move`, a copy through `copy`. Both resolve the destination from the entry's own
|
|
18
|
+
* name, so a paste cannot rename; both carry a directory's whole subtree. The clipboard is
|
|
19
|
+
* a client-side pair of paths and a flag -- nothing about a pending paste is worth a
|
|
20
|
+
* server-side record, and a clipboard that survived a reload would be a paste the reader
|
|
21
|
+
* could no longer see they had armed.
|
|
22
|
+
*
|
|
23
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
24
|
+
*/
|
|
25
|
+
import { type ShareDialogCopy } from './KnowledgeShareDialog.tsx';
|
|
26
|
+
/** One row the menu can act on. */
|
|
27
|
+
export interface ContextTarget {
|
|
28
|
+
/** Path relative to the knowledge root. */
|
|
29
|
+
relPath: string;
|
|
30
|
+
/** Display name, used as the rename dialog's starting value. */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Whether the row is a directory (renaming one renames the directory). */
|
|
33
|
+
isDir: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The clipboard the copy/cut entries fill. */
|
|
36
|
+
export interface TreeClipboard {
|
|
37
|
+
/** The path that was copied or cut. */
|
|
38
|
+
relPath: string;
|
|
39
|
+
/** Its display name, so the paste label can name the entry. */
|
|
40
|
+
name: string;
|
|
41
|
+
/** True when the source is removed on paste (cut); false for a copy request. */
|
|
42
|
+
cut: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Props for the context menu. */
|
|
45
|
+
export interface KnowledgeContextMenuProps {
|
|
46
|
+
/** The row the menu is open on, or null when closed. */
|
|
47
|
+
target: ContextTarget | null;
|
|
48
|
+
/** Where the pointer was, in viewport coordinates. */
|
|
49
|
+
at: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
} | null;
|
|
53
|
+
/** Copy resolver for this package's namespace. */
|
|
54
|
+
t: (key: string) => string;
|
|
55
|
+
/** Copy for the share dialog, which carries its own key set. */
|
|
56
|
+
shareCopy: ShareDialogCopy;
|
|
57
|
+
/** Close the menu. */
|
|
58
|
+
onClose: () => void;
|
|
59
|
+
/** Open the permission surface for this path. */
|
|
60
|
+
onGrant: (path: string) => void;
|
|
61
|
+
/** Toggle the pin state for this path. */
|
|
62
|
+
onPin: (path: string, pinned: boolean) => void;
|
|
63
|
+
/** Whether this path is currently pinned. */
|
|
64
|
+
pinned: boolean;
|
|
65
|
+
/** Toggle the favourite state for this path. */
|
|
66
|
+
onFavorite: (path: string, favorite: boolean) => void;
|
|
67
|
+
/** Whether this path is currently favourited. */
|
|
68
|
+
favorite: boolean;
|
|
69
|
+
/** Insert a reference to this path into the conversation. */
|
|
70
|
+
onReference: (path: string, isDir: boolean) => void;
|
|
71
|
+
/** Called after a rename succeeds, so the page reloads the tree. */
|
|
72
|
+
onRenamed: () => void;
|
|
73
|
+
/** Called after a delete-to-trash succeeds, so the page reloads the tree. */
|
|
74
|
+
onDeleted: () => void;
|
|
75
|
+
/** What the clipboard currently holds, or null when it is empty. */
|
|
76
|
+
clipboard: TreeClipboard | null;
|
|
77
|
+
/** Put one entry on the clipboard. Recording only; nothing moves until paste. */
|
|
78
|
+
onClipboard: (entry: TreeClipboard | null) => void;
|
|
79
|
+
/**
|
|
80
|
+
* The directory a paste would land in.
|
|
81
|
+
*
|
|
82
|
+
* For a directory row the target is that directory; for a file it is the file's
|
|
83
|
+
* parent. The menu states it so a paste never lands somewhere the reader cannot see,
|
|
84
|
+
* and `null` means the destination is unknown, which disables the entry rather than
|
|
85
|
+
* guessing.
|
|
86
|
+
*/
|
|
87
|
+
pasteInto: string | null;
|
|
88
|
+
/** Called after a paste succeeds, so the page reloads both affected levels. */
|
|
89
|
+
onMoved: () => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The knowledge tree's row context menu.
|
|
93
|
+
* @param props - the target row, the pointer position, and the actions.
|
|
94
|
+
* @returns The menu, or nothing when it is closed.
|
|
95
|
+
*/
|
|
96
|
+
export declare function KnowledgeContextMenu(props: KnowledgeContextMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One official dialog behind the Home surface's three create/upload actions, plus the
|
|
3
|
+
* destination picker and name field they all share.
|
|
4
|
+
*
|
|
5
|
+
* The reference had a dropdown per card whose rows opened a browser `window.prompt` for
|
|
6
|
+
* a name and dropped the result into `global/`. Two things were wrong with that: a
|
|
7
|
+
* prompt cannot show where the file will land, and it cannot offer to change it -- so
|
|
8
|
+
* every new document silently published to the one bucket everybody reads.
|
|
9
|
+
*
|
|
10
|
+
* All three actions therefore share one dialog, because they ask the same two questions
|
|
11
|
+
* (what is it called, and where does it go) and differ only in what they write: `create`
|
|
12
|
+
* for a document, `create` with `kind: 'directory'` for a folder, and `upload` per file
|
|
13
|
+
* for an upload. Keeping them together means the validation, the destination default and
|
|
14
|
+
* the keyboard contract cannot drift apart between them.
|
|
15
|
+
*
|
|
16
|
+
* The dialog is a `Modal` and the fields are `Input`/`Button` from the primitives -- no
|
|
17
|
+
* hand-built layer -- so Escape, the mask, focus return and the card chrome all come
|
|
18
|
+
* from the same implementation every other DSH dialog uses. The directory picker reads
|
|
19
|
+
* the live tree one level at a time through the Host's own listing route (`KB_API.list`),
|
|
20
|
+
* so it can never offer a directory the caller may not read, and it takes a
|
|
21
|
+
* `defaultPath` chosen by {@link defaultDestination} rather than guessing.
|
|
22
|
+
*
|
|
23
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Where a create or upload should land when the reader has not said otherwise.
|
|
27
|
+
*
|
|
28
|
+
* The order is the whole point of this function, and it is deliberately not
|
|
29
|
+
* "global first":
|
|
30
|
+
*
|
|
31
|
+
* 1. The directory the reader is looking at. If they navigated into
|
|
32
|
+
* `department/OPS/specs` and then pressed New, that is where they mean -- a
|
|
33
|
+
* create that jumps elsewhere makes the tree they are reading a lie.
|
|
34
|
+
* 2. The reader's own personal directory. Without a current directory this is the
|
|
35
|
+
* only bucket that is theirs by construction, so it is the safe default: a file
|
|
36
|
+
* created there cannot surprise a colleague.
|
|
37
|
+
* 3. `global`, last, and only as a last resort. It is the shared bucket, so
|
|
38
|
+
* defaulting to it is how a private draft becomes a team announcement.
|
|
39
|
+
*
|
|
40
|
+
* @param currentDir - the tree-relative directory the reader is browsing, if any.
|
|
41
|
+
* @param userId - the caller's DataOps id, from the status route.
|
|
42
|
+
* @returns A tree-relative directory that exists or is expected to.
|
|
43
|
+
*/
|
|
44
|
+
export declare function defaultDestination(currentDir: string | undefined, userId: string | undefined): string;
|
|
45
|
+
/** Props for the destination picker. */
|
|
46
|
+
export interface KnowledgeDestinationPickerProps {
|
|
47
|
+
/** Tree-relative directory currently selected. */
|
|
48
|
+
value: string;
|
|
49
|
+
/** Called with a new tree-relative directory. */
|
|
50
|
+
onChange: (path: string) => void;
|
|
51
|
+
/** Locale-bound copy resolver. */
|
|
52
|
+
t: (key: string) => string;
|
|
53
|
+
/** Disables the control while the dialog is submitting. */
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A breadcrumb-style directory chooser: the current path, a way back up, and the
|
|
58
|
+
* subdirectories of the level being shown.
|
|
59
|
+
* @param props - the selected path and its change handler.
|
|
60
|
+
* @returns The picker.
|
|
61
|
+
*/
|
|
62
|
+
export declare function KnowledgeDestinationPicker(props: KnowledgeDestinationPickerProps): import("react").JSX.Element;
|
|
63
|
+
/** Props for the name field, shared by the document and folder forms. */
|
|
64
|
+
export interface KnowledgeNameFieldProps {
|
|
65
|
+
/** What the reader has typed so far. */
|
|
66
|
+
value: string;
|
|
67
|
+
/** Called on every keystroke. */
|
|
68
|
+
onChange: (next: string) => void;
|
|
69
|
+
/** Locale-bound copy resolver. */
|
|
70
|
+
t: (key: string) => string;
|
|
71
|
+
/** Placeholder naming the kind being created. */
|
|
72
|
+
placeholder: string;
|
|
73
|
+
/** Set while submitting. */
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
/** A validation message to show under the field. */
|
|
76
|
+
error?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Reject a name this tree cannot store.
|
|
80
|
+
*
|
|
81
|
+
* The rule mirrors the Host's `isContainedRelPath`: a name may not be empty, may not
|
|
82
|
+
* contain a separator (that would silently make the "name" a path), and may not be
|
|
83
|
+
* a traversal segment. Rejecting it here turns a refused request into an inline
|
|
84
|
+
* sentence before the reader presses Create.
|
|
85
|
+
* @param name - the candidate name, without any directory part.
|
|
86
|
+
* @returns A locale key naming the problem, or undefined when the name is fine.
|
|
87
|
+
*/
|
|
88
|
+
export declare function validateEntryName(name: string): 'createNameEmpty' | 'createNameSeparator' | 'createNameTraversal' | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The name field with its inline validation.
|
|
91
|
+
* @param props - the field's state.
|
|
92
|
+
* @returns The labelled input.
|
|
93
|
+
*/
|
|
94
|
+
export declare function KnowledgeNameField(props: KnowledgeNameFieldProps): import("react").JSX.Element;
|
|
95
|
+
/**
|
|
96
|
+
* Which of the three flows the dialog is running.
|
|
97
|
+
*
|
|
98
|
+
* `upload-file` and `upload-folder` differ only in how the browser's picker is
|
|
99
|
+
* configured (`webkitdirectory`), not in what the dialog asks; the destination is a
|
|
100
|
+
* single choice for the whole selection, which is what "choose where these go"
|
|
101
|
+
* means.
|
|
102
|
+
*/
|
|
103
|
+
export type CreateMode = 'new-doc' | 'new-folder' | 'upload-file' | 'upload-folder';
|
|
104
|
+
/** One file a reader chose, with the path it should land at. */
|
|
105
|
+
export interface PendingUpload {
|
|
106
|
+
/** The file as the browser handed it over. */
|
|
107
|
+
file: File;
|
|
108
|
+
/** Its name relative to the picked root: a folder upload keeps its shape. */
|
|
109
|
+
relative: string;
|
|
110
|
+
}
|
|
111
|
+
/** Props for the dialog. */
|
|
112
|
+
export interface KnowledgeCreateDialogProps {
|
|
113
|
+
/** Whether the dialog is showing. */
|
|
114
|
+
open: boolean;
|
|
115
|
+
/** Which flow to run. */
|
|
116
|
+
mode: CreateMode;
|
|
117
|
+
/** Close without writing anything. */
|
|
118
|
+
onClose: () => void;
|
|
119
|
+
/** The directory the dialog should start in, from {@link defaultDestination}. */
|
|
120
|
+
defaultPath: string;
|
|
121
|
+
/** Locale-bound copy resolver. */
|
|
122
|
+
t: (key: string) => string;
|
|
123
|
+
/**
|
|
124
|
+
* Report the outcome and refresh the caller's listing.
|
|
125
|
+
* @param message - a locale-bound sentence to show under the cards.
|
|
126
|
+
*/
|
|
127
|
+
onDone: (message: string) => void;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The create/upload dialog.
|
|
131
|
+
* @param props - the flow, its starting directory, and the outcome channel.
|
|
132
|
+
* @returns The dialog, or null while closed.
|
|
133
|
+
*/
|
|
134
|
+
export declare function KnowledgeCreateDialog(props: KnowledgeCreateDialogProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The knowledge tree, drawn by the product's own file explorer.
|
|
3
|
+
*
|
|
4
|
+
* The listing next door is a hand-built copy of `dsh-better-sidebar`'s explorer: its CSS
|
|
5
|
+
* module says so ("Copied from the file explorer in dsh-better-sidebar", with each metric
|
|
6
|
+
* measured against `.explorerRow`). A copy drifts -- the explorer gains sort controls,
|
|
7
|
+
* inline rename, an upload strip, git tones, an open-with submenu, none of which arrive
|
|
8
|
+
* in a copy -- so this component renders the REAL explorer instead, pointed at the
|
|
9
|
+
* knowledge root.
|
|
10
|
+
*
|
|
11
|
+
* That is possible because `/workspace/kb` is a symlink into the caller's view: the
|
|
12
|
+
* explorer's `cwd` is a plain absolute path, so pointing it there reads the knowledge tree
|
|
13
|
+
* through exactly the machinery that reads any other workspace directory. Measured before
|
|
14
|
+
* writing this: `POST /sidebar/api/fs.trees {cwd:'/workspace/kb'}` answers the tree's
|
|
15
|
+
* entries, so no new route and no second file API are involved.
|
|
16
|
+
*
|
|
17
|
+
* What the explorer does NOT know about is the knowledge base's own metadata files
|
|
18
|
+
* (`.kb-audit.jsonl`, `.kb-settings.json`, `.kb-tree-state.json`, `.pinned.json`,
|
|
19
|
+
* `.view-mode.json`, `.kb-favorites.json`, `.kb-trash.json`). They live in the view so
|
|
20
|
+
* every user's copy carries them, and they are neither knowledge documents nor anything
|
|
21
|
+
* a reader should rename. They are filtered with the `exclude` prop, which the host
|
|
22
|
+
* applies to the listing -- so they never arrive rather than being hidden after the fact.
|
|
23
|
+
*
|
|
24
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
25
|
+
*/
|
|
26
|
+
import type { UploadItem } from 'dsh-better-sidebar/src/client/upload.ts';
|
|
27
|
+
import { type DepartmentPath } from './departmentFolders.ts';
|
|
28
|
+
export interface KnowledgeFileTreeProps {
|
|
29
|
+
/** The conversation this tree belongs to; the explorer keys its fs requests by it. */
|
|
30
|
+
sessionId: string;
|
|
31
|
+
/** Absolute path of the knowledge root inside the workspace. */
|
|
32
|
+
root: string;
|
|
33
|
+
/** Directories the reader has expanded, by absolute path. */
|
|
34
|
+
expanded: readonly string[];
|
|
35
|
+
/** Files highlighted by a "show in folder" reveal, by absolute path. */
|
|
36
|
+
revealed: readonly string[];
|
|
37
|
+
/** Toggle one directory open or closed. */
|
|
38
|
+
onToggle: (path: string) => void;
|
|
39
|
+
/** Open one file for preview. */
|
|
40
|
+
onOpenFile: (path: string) => void;
|
|
41
|
+
/** Insert a reference to one path into the conversation draft. */
|
|
42
|
+
onReferenceFile: (path: string, isDir: boolean) => void;
|
|
43
|
+
/** Upload files into a directory (absolute, inside the knowledge root). */
|
|
44
|
+
onUploadRequest: (dir: string, items: UploadItem[]) => void;
|
|
45
|
+
/** True while an upload is in flight. */
|
|
46
|
+
busy: boolean;
|
|
47
|
+
/** Bump to reload the visible set after a change made elsewhere. */
|
|
48
|
+
refreshTick: number;
|
|
49
|
+
/** Called after the explorer's own inline rename lands. */
|
|
50
|
+
onPathRenamed?: (oldPath: string, newPath: string) => void;
|
|
51
|
+
/** Called after the explorer's own delete lands. */
|
|
52
|
+
onPathDeleted?: (path: string, isDir: boolean) => void;
|
|
53
|
+
/** Whether the owning surface is on screen. */
|
|
54
|
+
visible?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Say the three buckets in the reader's language.
|
|
57
|
+
*
|
|
58
|
+
* `global` / `department` / `personal` are the folders' names on disk, and they have
|
|
59
|
+
* to stay that way: they are path segments the deployment shares and the caller's
|
|
60
|
+
* view is built from them. What the reader is being told by that folder is not its
|
|
61
|
+
* path but its AUDIENCE -- who may see what is inside -- so the row prints the
|
|
62
|
+
* audience and the path is left alone.
|
|
63
|
+
*
|
|
64
|
+
* Keyed by the tree-relative path, so the map can be built from copy alone.
|
|
65
|
+
*/
|
|
66
|
+
bucketLabels?: Readonly<Record<string, string>>;
|
|
67
|
+
/**
|
|
68
|
+
* The reader's own organization chain, so department folders print their NAME.
|
|
69
|
+
*
|
|
70
|
+
* The department bucket stores folders by code (`nanning-psb`), because a code is a
|
|
71
|
+
* stable slug: renaming a department cannot move a document. The reader, though,
|
|
72
|
+
* knows their organization by name, so those same rows have to print
|
|
73
|
+
* 「南宁市公安局」 rather than the slug.
|
|
74
|
+
*
|
|
75
|
+
* Only the caller's own chain is translated, and only within the department bucket.
|
|
76
|
+
* A peer department's folder keeps printing its code: this build has no name for it,
|
|
77
|
+
* and the whole organization tree is readable only by an administrator. Deriving a
|
|
78
|
+
* "name" from the code would put an invented label in front of the reader, which is
|
|
79
|
+
* worse than the code they can at least match against their own department.
|
|
80
|
+
*/
|
|
81
|
+
departmentPath?: DepartmentPath;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The knowledge tree, rendered by the product's file explorer.
|
|
85
|
+
* @param props - the root, the reader's expansion state, and the actions.
|
|
86
|
+
* @returns The explorer's tree, rooted at the knowledge directory.
|
|
87
|
+
*/
|
|
88
|
+
export declare function KnowledgeFileTree(props: KnowledgeFileTreeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Home surface: the page's landing view.
|
|
3
|
+
*
|
|
4
|
+
* Three ways to start, over a tabbed list of what the reader already has. The tabs
|
|
5
|
+
* select the SET, and the table shows that set's rows; nothing here invents data,
|
|
6
|
+
* so a tab with no rows says so rather than filling the table with placeholders.
|
|
7
|
+
*
|
|
8
|
+
* The listing comes from the same route the tree uses. Categorising a document as
|
|
9
|
+
* "owned by me" or "shared with me" would need an owner per file, and the
|
|
10
|
+
* knowledge tree carries none: it is a directory of documents whose authorization
|
|
11
|
+
* lives in DataOps grants. So the tabs narrow by what the tree can actually
|
|
12
|
+
* answer, and the Owner column is drawn from the grant data only when a caller
|
|
13
|
+
* supplies it. Filling that column with a guess would be worse than leaving it
|
|
14
|
+
* blank.
|
|
15
|
+
*
|
|
16
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
17
|
+
*/
|
|
18
|
+
/** Props for the Home surface. */
|
|
19
|
+
export interface KnowledgeHomeProps {
|
|
20
|
+
/** Locale-bound copy resolver. */
|
|
21
|
+
t: (key: string) => string;
|
|
22
|
+
/** Locale tag, so dates match the reader's language. */
|
|
23
|
+
locale?: string;
|
|
24
|
+
/** Disabled when the tree is not mounted, so the cards cannot promise a tree. */
|
|
25
|
+
mountPoint: string;
|
|
26
|
+
/** Open one document in the preview. */
|
|
27
|
+
onOpen: (path: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* The caller's DataOps id, when the status route reported one.
|
|
30
|
+
*
|
|
31
|
+
* Used only to choose where a create lands by default: with no current directory
|
|
32
|
+
* the reader's own `personal/<id>` folder is the one place a new draft cannot
|
|
33
|
+
* surprise a colleague, so it is preferred over `global`.
|
|
34
|
+
*/
|
|
35
|
+
callerId?: string;
|
|
36
|
+
/** The caller's own department code, so the owner cell can name it instead of a slug. */
|
|
37
|
+
departmentCode?: string;
|
|
38
|
+
/** The caller's own department name; the code stands in when this is absent. */
|
|
39
|
+
departmentName?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The tab whose card should take focus on mount.
|
|
42
|
+
*
|
|
43
|
+
* Set when a reader arrives here from the tree's empty state, which is asking
|
|
44
|
+
* them to start a document: the blank-page card is the answer to that question,
|
|
45
|
+
* so it is the one that should be reachable by keyboard immediately.
|
|
46
|
+
*/
|
|
47
|
+
focusNew?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Star or unstar one row.
|
|
50
|
+
*
|
|
51
|
+
* The Home table is the surface a reader actually starts from, so the context menu
|
|
52
|
+
* lives here as well as on the tree. Omitted by a caller with no favourites store,
|
|
53
|
+
* which keeps the menu's star entry out rather than offering a write that cannot land.
|
|
54
|
+
*/
|
|
55
|
+
onFavorite?: (path: string, favorite: boolean) => void;
|
|
56
|
+
/** Whether one path is currently favourited, so the row can draw its star. */
|
|
57
|
+
isFavorite?: (path: string) => boolean;
|
|
58
|
+
/** Open the permission surface for one path. */
|
|
59
|
+
onGrant?: (path: string) => void;
|
|
60
|
+
/** Insert a reference to this path into the conversation. */
|
|
61
|
+
onReference?: (path: string, isDir: boolean) => void;
|
|
62
|
+
/** Pin or unpin one row, offered by the same menu the tree uses. */
|
|
63
|
+
onPin?: (path: string, pinned: boolean) => void;
|
|
64
|
+
/** Whether one path is currently pinned. */
|
|
65
|
+
isPinned?: (path: string) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Called after any menu action that changed the tree.
|
|
68
|
+
*
|
|
69
|
+
* The table holds its own rows, so a rename/delete/paste has to be followed by a
|
|
70
|
+
* re-read; the page owns the tick that triggers it.
|
|
71
|
+
*/
|
|
72
|
+
onTreeChanged?: () => void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Render the Home surface.
|
|
76
|
+
* @param props - copy resolver, mount state, and the open callback.
|
|
77
|
+
* @returns The Home view.
|
|
78
|
+
*/
|
|
79
|
+
export declare function KnowledgeHome(props: KnowledgeHomeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The indexing surface of the management page.
|
|
3
|
+
*
|
|
4
|
+
* There is no separate index. Retrieval reads the mounted tree directly with grep
|
|
5
|
+
* and read, so a "rebuild" is a recount of what is mounted, and it answers
|
|
6
|
+
* synchronously. Presenting it as a background job would invent a queue the plugin
|
|
7
|
+
* does not have, so this surface says what it is in its own copy instead of
|
|
8
|
+
* implying an index process.
|
|
9
|
+
*
|
|
10
|
+
* The table reports per-directory facts the Host derived from the filesystem:
|
|
11
|
+
* how many files are below each directory, when the newest one changed, and
|
|
12
|
+
* whether the caller can read it. Those are measurements, not estimates, and a
|
|
13
|
+
* directory whose numbers are not known is absent rather than shown as zero.
|
|
14
|
+
*
|
|
15
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
16
|
+
*/
|
|
17
|
+
import { type ReactNode } from 'react';
|
|
18
|
+
import { type KnowledgeStatusView } from '../api.ts';
|
|
19
|
+
/** One directory's measured shape, as the Host reports it. */
|
|
20
|
+
export interface IndexDirectory {
|
|
21
|
+
/** Path relative to the mount point; empty string means the root itself. */
|
|
22
|
+
relPath: string;
|
|
23
|
+
/** Files below this directory, counted recursively. */
|
|
24
|
+
fileCount: number;
|
|
25
|
+
/** Newest modification instant in Unix ms, absent when nothing could be read. */
|
|
26
|
+
mtimeMs?: number;
|
|
27
|
+
/** Whether this directory's own entries could be listed. */
|
|
28
|
+
readable: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether some part of the subtree could not be read, so `fileCount` is a lower
|
|
31
|
+
* bound rather than a total.
|
|
32
|
+
*
|
|
33
|
+
* Absent from an older Host, which is why it is optional here: a missing flag is
|
|
34
|
+
* treated as "not known to be partial" rather than as "complete", so the column
|
|
35
|
+
* never claims more than the Host measured.
|
|
36
|
+
*/
|
|
37
|
+
partial?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Props for the indexing surface. */
|
|
40
|
+
export interface KnowledgeIndexingProps {
|
|
41
|
+
/**
|
|
42
|
+
* Publishes this page's primary control to the panel header.
|
|
43
|
+
*
|
|
44
|
+
* The header is the only band at the top of the page, so a page-level action has
|
|
45
|
+
* to be handed up rather than drawn in a second title row.
|
|
46
|
+
*/
|
|
47
|
+
onAction?: (action: ReactNode) => void;
|
|
48
|
+
/** Status the page already loaded, used for the first paint. */
|
|
49
|
+
status: KnowledgeStatusView;
|
|
50
|
+
/** Called after a rescan, so the page can refresh what it shows. */
|
|
51
|
+
onChanged: () => void;
|
|
52
|
+
/** Locale-bound copy for this package's namespace. */
|
|
53
|
+
t: (key: string) => string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Render the index state as a per-directory table.
|
|
57
|
+
* @param props - the loaded status and a refresh callback.
|
|
58
|
+
* @returns The indexing surface.
|
|
59
|
+
*/
|
|
60
|
+
export declare function KnowledgeIndexing(props: KnowledgeIndexingProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A directory listing with the modification time each entry carries.
|
|
3
|
+
*
|
|
4
|
+
* The row is the file explorer's own row from `dsh-better-sidebar`, copied from
|
|
5
|
+
* `src/client/FileTree.tsx` (`FileRow`/`DirRow`) and `src/client/sidebar.module.css`
|
|
6
|
+
* (`.explorerRow`): the same `div[role=button]`, the same 14px glyph, the same
|
|
7
|
+
* ellipsised name, the same trailing modified time that yields its right edge to
|
|
8
|
+
* the same hover-revealed `@` reference button, and the same `depth * 22 + 6`
|
|
9
|
+
* indent. A reader sees that tree in this window's right sidebar, so a second
|
|
10
|
+
* design beside it reads as bolted on -- which is why this is a copy rather than
|
|
11
|
+
* a re-drawing.
|
|
12
|
+
*
|
|
13
|
+
* What the borrowed tree cannot carry is why this list exists. Its host entry
|
|
14
|
+
* shape (`FsEntry`: name, path, isDir, hidden, isSymlink, broken) has no
|
|
15
|
+
* timestamp, so a row built from it can never show when a document last changed;
|
|
16
|
+
* and its rows decide visibility from a shared curated store rather than from
|
|
17
|
+
* per-user grants. This panel reads the same directories through the plugin's own
|
|
18
|
+
* route, which stats each child, and its row carries a right-click into the grant
|
|
19
|
+
* form the tree has no place for.
|
|
20
|
+
*
|
|
21
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
22
|
+
*/
|
|
23
|
+
import { type ReactNode } from 'react';
|
|
24
|
+
/** Props for the listing panel. */
|
|
25
|
+
export interface KnowledgeListingProps {
|
|
26
|
+
/** Locale-bound copy resolver. */
|
|
27
|
+
t: (key: string) => string;
|
|
28
|
+
/** Called with a chosen path so the page can show its preview. */
|
|
29
|
+
onOpen: (path: string) => void;
|
|
30
|
+
/** Re-read trigger, so a rescan from elsewhere refreshes this too. */
|
|
31
|
+
refreshTick?: number;
|
|
32
|
+
/** Locale tag, so dates match the reader's language. */
|
|
33
|
+
locale?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Open the permission surface for one path.
|
|
36
|
+
*
|
|
37
|
+
* Exists because the borrowed tree's context menu is built inside it with no way
|
|
38
|
+
* to add a row, so the grant entry lives here instead: this is the plugin's own
|
|
39
|
+
* listing, where a right-click can mean what the knowledge base needs it to mean.
|
|
40
|
+
*/
|
|
41
|
+
onGrant?: (path: string) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Insert one row's reference into the conversation draft.
|
|
44
|
+
*
|
|
45
|
+
* The borrowed tree reveals this affordance on hover; the listing mirrors that so
|
|
46
|
+
* a reader gets the same `@` action from either tree.
|
|
47
|
+
*/
|
|
48
|
+
onReference?: (path: string, isDir: boolean) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Pin or unpin one row.
|
|
51
|
+
*
|
|
52
|
+
* Omitted by a caller that has no pin store, which is how the search results list
|
|
53
|
+
* stays free of an action that would only make sense inside the tree.
|
|
54
|
+
*/
|
|
55
|
+
onPin?: (path: string, pinned: boolean) => void;
|
|
56
|
+
/** Whether one path is currently pinned, so the row can mark it. */
|
|
57
|
+
isPinned?: (path: string) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Star or unstar one row.
|
|
60
|
+
*
|
|
61
|
+
* Omitted by a caller with no favourites store, which is how the search results list
|
|
62
|
+
* stays free of an entry that would only make sense inside the tree. A folder is
|
|
63
|
+
* favoured the same way a file is: the star marks a row, not a document.
|
|
64
|
+
*/
|
|
65
|
+
onFavorite?: (path: string, favorite: boolean) => void;
|
|
66
|
+
/** Whether one path is currently favourited, so the row can draw its star. */
|
|
67
|
+
isFavorite?: (path: string) => boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The path a paste would land in, given the row this menu was opened on.
|
|
70
|
+
*
|
|
71
|
+
* A directory row pastes INTO itself; a file row pastes beside itself. Kept a
|
|
72
|
+
* callback rather than derived in the menu because only the listing knows whether a
|
|
73
|
+
* row is the tree's root, which has no parent to paste beside.
|
|
74
|
+
*/
|
|
75
|
+
pasteTargetFor?: (relPath: string, isDir: boolean) => string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Called after a menu-driven delete or paste succeeded.
|
|
78
|
+
*
|
|
79
|
+
* Both change paths this component is currently holding -- a delete removes one, a
|
|
80
|
+
* paste adds one and empties another -- so the page has to reload rather than the
|
|
81
|
+
* component guessing which levels moved.
|
|
82
|
+
*/
|
|
83
|
+
onTreeChanged?: () => void;
|
|
84
|
+
/**
|
|
85
|
+
* Called after a rename succeeded, so the page reloads the tree.
|
|
86
|
+
*
|
|
87
|
+
* The listing cannot reload itself: it reads through `refreshTick`, which the page
|
|
88
|
+
* owns, and a rename changes the paths this component is currently holding.
|
|
89
|
+
*/
|
|
90
|
+
onRenamed?: () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Render one row's action as "unpin" rather than "pin".
|
|
93
|
+
*
|
|
94
|
+
* The pinned band lists paths that are already pinned, so every row there offers
|
|
95
|
+
* the opposite action; deriving it from a flag keeps one row shape for both bands.
|
|
96
|
+
*/
|
|
97
|
+
unpinOnly?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Narrow the tree to rows whose name contains this text.
|
|
100
|
+
*
|
|
101
|
+
* Filtering happens over the rows already read, so it costs no request: the tree
|
|
102
|
+
* section header's search is the in-place narrowing, while the rail's field is the
|
|
103
|
+
* cross-directory search. A filter that matched nothing read as an empty tree, so
|
|
104
|
+
* the two empty states are kept apart.
|
|
105
|
+
*/
|
|
106
|
+
filter?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Start with every directory expanded.
|
|
109
|
+
*
|
|
110
|
+
* Used by the pinned band, where a pinned folder is only useful if its contents
|
|
111
|
+
* are already visible.
|
|
112
|
+
*/
|
|
113
|
+
expandAll?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* The action offered when the tree genuinely holds no documents.
|
|
116
|
+
*
|
|
117
|
+
* Distinct from a filter that matched nothing: an empty tree wants a way to make
|
|
118
|
+
* a document, while an unmatched filter wants a different search term.
|
|
119
|
+
*/
|
|
120
|
+
emptyAction?: ReactNode;
|
|
121
|
+
/**
|
|
122
|
+
* Move one entry into a directory, or pin it when the target is the pin band.
|
|
123
|
+
*
|
|
124
|
+
* Omitted by a caller that must stay read-only, which is how the search results
|
|
125
|
+
* list keeps its rows free of a gesture the Host would refuse there.
|
|
126
|
+
*/
|
|
127
|
+
onMove?: (from: string, toDirectory: string) => void;
|
|
128
|
+
/**
|
|
129
|
+
* The layout to render. `list` is the tree; `grid` is the document cards.
|
|
130
|
+
*
|
|
131
|
+
* Defaults to the tree so a caller that does not persist a preference behaves
|
|
132
|
+
* exactly as it did before the card view existed.
|
|
133
|
+
*/
|
|
134
|
+
viewMode?: 'list' | 'grid';
|
|
135
|
+
/**
|
|
136
|
+
* Enter multi-select. A callback rather than internal state because the batch
|
|
137
|
+
* bar's actions are the host's (they move and pin through the same routes a
|
|
138
|
+
* single drag uses), so the selection has to be visible to it.
|
|
139
|
+
*/
|
|
140
|
+
onSelectionChange?: (paths: readonly string[]) => void;
|
|
141
|
+
/** The current selection, so a batch action can act on it. */
|
|
142
|
+
selection?: readonly string[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The drag payload's MIME type.
|
|
146
|
+
*
|
|
147
|
+
* Exported because the drop target that pins is NOT in this component: the pin
|
|
148
|
+
* band lives in the panel, and the two have to agree on the name of the thing
|
|
149
|
+
* being carried. The native drag payload is used rather than lifted React state
|
|
150
|
+
* for exactly that reason -- it crosses the component boundary the gesture
|
|
151
|
+
* crosses, without the panel having to hold a second copy of the tree's state.
|
|
152
|
+
*/
|
|
153
|
+
export declare const KB_DRAG_MIME = "application/x-dsh-kb-path";
|
|
154
|
+
/**
|
|
155
|
+
* Render the listing for the mount's top level, and one level per expanded
|
|
156
|
+
* directory.
|
|
157
|
+
* @param props - copy resolver and the open callback.
|
|
158
|
+
* @returns The listing panel.
|
|
159
|
+
*/
|
|
160
|
+
export declare function KnowledgeListing(props: KnowledgeListingProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loading state, as a book whose page turns.
|
|
3
|
+
*
|
|
4
|
+
* The knowledge base is a library of documents, so a turning page says what is
|
|
5
|
+
* happening in the product's own terms rather than with a generic spinner. The
|
|
6
|
+
* animation is CSS and inline SVG: no animation library, because this plugin ships
|
|
7
|
+
* as one bundled file and a dependency would grow it and add a supply-chain surface
|
|
8
|
+
* for a decoration.
|
|
9
|
+
*
|
|
10
|
+
* The motion is a real 3D turn rather than a sequence of drawn frames: the leaf is a
|
|
11
|
+
* zero-thickness plane inside a `perspective` container, rotated about the spine with
|
|
12
|
+
* `rotateY`. That keeps the whole effect to two keyframes and makes the direction of
|
|
13
|
+
* travel read correctly at any moment, which a frame sequence does not.
|
|
14
|
+
*
|
|
15
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
16
|
+
*/
|
|
17
|
+
/** Props for the loading surface. */
|
|
18
|
+
export interface KnowledgeLoadingProps {
|
|
19
|
+
/**
|
|
20
|
+
* What is being loaded, in the reader's language.
|
|
21
|
+
*
|
|
22
|
+
* Optional because a card or a table cell may have no label of its own; the
|
|
23
|
+
* animation then stands alone and the accessible name falls back to the shared
|
|
24
|
+
* loading sentence.
|
|
25
|
+
*/
|
|
26
|
+
label?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Accessible name for the animation itself.
|
|
29
|
+
*
|
|
30
|
+
* Required rather than derived: the animation is decorative, so it must either be
|
|
31
|
+
* hidden from assistive technology or carry a name, and hiding it silently would
|
|
32
|
+
* leave a screen-reader user with no announcement that anything is loading.
|
|
33
|
+
*/
|
|
34
|
+
announce?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Render the book-turning loading animation.
|
|
38
|
+
* @param props - the optional label and the accessible announcement.
|
|
39
|
+
* @returns The centred loading surface.
|
|
40
|
+
*/
|
|
41
|
+
export declare function KnowledgeLoading(props: KnowledgeLoadingProps): import("react").JSX.Element;
|