@ecsplico/editor 2.0.0 → 2.1.1
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/CHANGELOG.md +37 -0
- package/README.md +4 -3
- package/dist/adapters/anchors.d.ts +80 -0
- package/dist/adapters/anchors.js +270 -0
- package/dist/adapters/anchors.js.map +1 -0
- package/dist/adapters/comments/client.d.ts +64 -0
- package/dist/adapters/comments/client.js +536 -0
- package/dist/adapters/comments/client.js.map +1 -0
- package/dist/adapters/comments/drizzle.d.ts +430 -0
- package/dist/adapters/comments/drizzle.js +236 -0
- package/dist/adapters/comments/drizzle.js.map +1 -0
- package/dist/adapters/comments/errors.d.ts +9 -0
- package/dist/adapters/comments/errors.js +19 -0
- package/dist/adapters/comments/errors.js.map +1 -0
- package/dist/adapters/comments/json.d.ts +19 -0
- package/dist/adapters/comments/json.js +94 -0
- package/dist/adapters/comments/json.js.map +1 -0
- package/dist/adapters/comments/record.js +78 -0
- package/dist/adapters/comments/record.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +23 -0
- package/dist/adapters/comments/server.js +102 -0
- package/dist/adapters/comments/server.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +109 -0
- package/dist/adapters/documents.js +108 -0
- package/dist/adapters/documents.js.map +1 -0
- package/dist/adapters/drizzle.d.ts +293 -0
- package/dist/adapters/drizzle.js +157 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/files.js +30 -0
- package/dist/adapters/files.js.map +1 -0
- package/dist/adapters/forge.d.ts +58 -0
- package/dist/adapters/forge.js +91 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +0 -0
- package/dist/adapters/json.d.ts +24 -0
- package/dist/adapters/json.js +128 -0
- package/dist/adapters/json.js.map +1 -0
- package/dist/adapters/nitro.d.ts +61 -0
- package/dist/adapters/nitro.js +212 -0
- package/dist/adapters/nitro.js.map +1 -0
- package/dist/adapters/routes.js +16 -0
- package/dist/adapters/routes.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -0
- package/dist/adapters/shared.js +32 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/vue.d.ts +67 -0
- package/dist/adapters/vue.js +187 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/doc-anchors.d.ts +5 -0
- package/dist/core/doc-anchors.js +4 -3
- package/dist/core/doc-anchors.js.map +1 -1
- package/dist/core/editor-ui.js +57 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/surface-session.js +139 -0
- package/dist/core/surface-session.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js.map +1 -1
- package/dist/features/ai/agent-ops.d.ts +1 -1
- package/dist/features/ai/agent-ops.js +9 -6
- package/dist/features/ai/agent-ops.js.map +1 -1
- package/dist/features/ai/ai-apply.d.ts +1 -1
- package/dist/features/ai/ai-review.js +10 -1
- package/dist/features/ai/ai-review.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +78 -252
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.js +1 -0
- package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat.d.ts +3 -2
- package/dist/features/ai/document-chat.js +16 -19
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/index.d.ts +5 -3
- package/dist/features/ai/index.js +4 -2
- package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
- package/dist/features/ai/protocol/agent-ops.js +65 -39
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +89 -0
- package/dist/features/ai/protocol/block-lines.js +177 -0
- package/dist/features/ai/protocol/block-lines.js.map +1 -0
- package/dist/features/ai/protocol/chat.js +10 -10
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/index.js +3 -2
- package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
- package/dist/features/ai/react/AiSelectionMenu.js +64 -255
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -0
- package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
- package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
- package/dist/features/ai/selection-flow.d.ts +133 -0
- package/dist/features/ai/selection-flow.js +283 -0
- package/dist/features/ai/selection-flow.js.map +1 -0
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-document-chat.d.ts +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/feature.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
- package/dist/features/comments/components/CommentBubble.js +1 -1
- package/dist/features/comments/components/CommentComposer.js +1 -1
- package/dist/features/comments/components/CommentThreadView.js +5 -2
- package/dist/features/comments/components/CommentThreadView.js.map +1 -1
- package/dist/features/comments/components/CommentsMargin.js +108 -230
- package/dist/features/comments/components/CommentsMargin.js.map +1 -1
- package/dist/features/comments/components/CommentsOverlay.js +1 -1
- package/dist/features/comments/feature.js +1 -1
- package/dist/features/comments/index.d.ts +4 -2
- package/dist/features/comments/index.js +4 -2
- package/dist/features/comments/margin-controller.d.ts +44 -0
- package/dist/features/comments/margin-controller.js +132 -0
- package/dist/features/comments/margin-controller.js.map +1 -0
- package/dist/features/comments/margin-layout.d.ts +55 -0
- package/dist/features/comments/margin-layout.js +79 -0
- package/dist/features/comments/margin-layout.js.map +1 -0
- package/dist/features/comments/model.d.ts +8 -0
- package/dist/features/comments/model.js +60 -34
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentBubble.js +1 -1
- package/dist/features/comments/react/CommentComposer.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +2 -2
- package/dist/features/comments/react/CommentThreadView.js.map +1 -1
- package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
- package/dist/features/comments/react/CommentsMargin.js +85 -251
- package/dist/features/comments/react/CommentsMargin.js.map +1 -1
- package/dist/features/comments/react/CommentsOverlay.js +1 -1
- package/dist/features/comments/react/use-comments.js +1 -1
- package/dist/features/comments/react/use-margin-layout.js +45 -0
- package/dist/features/comments/react/use-margin-layout.js.map +1 -0
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +23 -0
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js +40 -0
- package/dist/features/comments/snapshot-equality.js.map +1 -0
- package/dist/features/comments/thread-anchor.js +33 -0
- package/dist/features/comments/thread-anchor.js.map +1 -0
- package/dist/features/comments/use-comments.d.ts +3 -1
- package/dist/features/comments/use-comments.js +1 -0
- package/dist/features/comments/use-comments.js.map +1 -1
- package/dist/features/comments/use-margin-layout.js +29 -0
- package/dist/features/comments/use-margin-layout.js.map +1 -0
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +21 -0
- package/dist/features/composites/accepted-children.js +126 -0
- package/dist/features/composites/accepted-children.js.map +1 -0
- package/dist/features/composites/components/CompositeDetails.js +1 -1
- package/dist/features/composites/components/CompositeInspector.js +38 -164
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/feature.d.ts +2 -1
- package/dist/features/composites/feature.js +8 -2
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/index.d.ts +5 -3
- package/dist/features/composites/index.js +8 -6
- package/dist/features/composites/inspector-model.d.ts +97 -0
- package/dist/features/composites/inspector-model.js +255 -0
- package/dist/features/composites/inspector-model.js.map +1 -0
- package/dist/features/composites/kinds.d.ts +11 -0
- package/dist/features/composites/kinds.js +17 -2
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/outline.d.ts +18 -5
- package/dist/features/composites/outline.js +44 -12
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
- package/dist/features/composites/react/CompositeInspector.js +21 -180
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
- package/dist/features/composites/react/CompositeView.js +1 -1
- package/dist/features/composites/react/use-composite-inspector.js +29 -0
- package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/schema.d.ts +2 -2
- package/dist/features/composites/schema.js +2 -2
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/use-composite-anchors.d.ts +1 -1
- package/dist/features/composites/use-composite-inspector.js +41 -0
- package/dist/features/composites/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/use-composite-rows.d.ts +1 -1
- package/dist/features/media/index.d.ts +1 -1
- package/dist/features/media/vue.d.ts +1 -1
- package/dist/features/mentions/index.d.ts +3 -3
- package/dist/features/mentions/index.js +3 -3
- package/dist/features/mentions/schema.d.ts +1 -1
- package/dist/features/mentions/schema.js +1 -1
- package/dist/features/themes/index.d.ts +1 -1
- package/dist/features/themes/index.js +1 -1
- package/dist/features/transcript/feature.js +1 -1
- package/dist/features/transcript/index.js +2 -2
- package/dist/i18n/de/ai.js +2 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +8 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/tree.js +14 -1
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +22 -0
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +8 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/tree.js +14 -1
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +2 -2
- package/dist/integrations/composite-ai/index.js +2 -2
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.d.ts +1 -1
- package/dist/markdown/MarkdownSurface.js +2 -2
- package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
- package/dist/markdown/index.d.ts +2 -2
- package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
- package/dist/react/EditorSurface.d.ts +1 -1
- package/dist/react/EditorSurface.js +43 -149
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.js +3 -40
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/schema/advanced.js +2 -2
- package/dist/server/adapter.d.ts +2 -2
- package/dist/server/collab.js +1 -1
- package/dist/server/handlers/ai.d.ts +4 -2
- package/dist/server/handlers/ai.js +11 -9
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +1 -1
- package/dist/server/handlers/editor.d.ts +1 -1
- package/dist/server/handlers/editor.js +2 -2
- package/dist/server/host.d.ts +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/styles/tokens.css +1 -1
- package/dist/tree/DocumentTree.js +159 -387
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +37 -0
- package/dist/tree/drop-rules.js.map +1 -0
- package/dist/tree/react/DocumentTree.d.ts +2 -2
- package/dist/tree/react/DocumentTree.js +135 -397
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/react/use-tree-model.js +129 -0
- package/dist/tree/react/use-tree-model.js.map +1 -0
- package/dist/tree/react.js +1 -1
- package/dist/tree/tree-host.js +100 -0
- package/dist/tree/tree-host.js.map +1 -0
- package/dist/tree/tree-items.js +22 -43
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +580 -0
- package/dist/tree/tree-model.js.map +1 -0
- package/dist/tree/tree-rows.js +77 -0
- package/dist/tree/tree-rows.js.map +1 -0
- package/dist/tree/use-tree-model.js +94 -0
- package/dist/tree/use-tree-model.js.map +1 -0
- package/dist/tree/vue.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
- package/dist/vue/SimpleEditor.vue.d.ts +2 -2
- package/dist/vue/components/EditorSurface.js +27 -87
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
- package/dist/vue/components/EditorToolbar.js +7 -44
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/context.d.ts +1 -1
- package/dist/vue/context.js +1 -1
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/use-editor-ui.d.ts +1 -1
- package/dist/vue/use-notifier.d.ts +1 -1
- package/docs/integration.md +97 -4
- package/docs/llms.md +9 -1
- package/docs/usage.md +10 -5
- package/package.json +67 -5
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { blockIdOf, findBlockById } from "../../core/schema/block-ids.js";
|
|
2
|
+
import { kindAccepts } from "./kinds.js";
|
|
3
|
+
import { compositeKindsOf } from "./nodes.js";
|
|
4
|
+
import { nestingReason, outermostComposites } from "./accepted-children.js";
|
|
2
5
|
//#region src/features/composites/outline.ts
|
|
3
6
|
/**
|
|
4
7
|
* The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the
|
|
@@ -34,23 +37,52 @@ function compositeOutline(doc, hiddenKinds, untitled) {
|
|
|
34
37
|
collect(doc, 0, roots);
|
|
35
38
|
return roots;
|
|
36
39
|
}
|
|
40
|
+
const refused = (reason) => ({
|
|
41
|
+
ok: false,
|
|
42
|
+
reason
|
|
43
|
+
});
|
|
37
44
|
/**
|
|
38
|
-
* Whether block `id`
|
|
39
|
-
*
|
|
45
|
+
* Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when
|
|
46
|
+
* not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind
|
|
47
|
+
* does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,
|
|
48
|
+
* unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not
|
|
49
|
+
* checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.
|
|
40
50
|
*/
|
|
41
|
-
function
|
|
51
|
+
function moveIntoVerdict(editor, id, targetId, t) {
|
|
42
52
|
const { doc } = editor.state;
|
|
43
53
|
const block = findBlockById(doc, id);
|
|
44
|
-
if (!block) return
|
|
45
|
-
if (targetId === null) return true;
|
|
54
|
+
if (!block) return refused(t("composites.move.blockGone"));
|
|
55
|
+
if (targetId === null) return { ok: true };
|
|
46
56
|
const target = findBlockById(doc, targetId);
|
|
47
|
-
if (!target
|
|
48
|
-
|
|
57
|
+
if (!target) return refused(t("composites.move.targetGone"));
|
|
58
|
+
if (target.node.type.name !== "composite") return refused(t("composites.move.targetNotBlock"));
|
|
59
|
+
if (target.pos === block.pos) return refused(t("composites.move.intoSelf"));
|
|
60
|
+
if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t("composites.move.intoOwn"));
|
|
61
|
+
const $block = doc.resolve(block.pos);
|
|
62
|
+
let parentPos = -1;
|
|
63
|
+
for (let depth = $block.depth; depth > 0; depth--) if ($block.node(depth).type.name === "composite") {
|
|
64
|
+
parentPos = $block.before(depth);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (parentPos === target.pos) return { ok: true };
|
|
68
|
+
const kinds = compositeKindsOf(editor);
|
|
69
|
+
const targetKind = String(target.node.attrs.kind);
|
|
70
|
+
for (const child of outermostComposites(block.node)) {
|
|
71
|
+
const childKind = String(child.attrs.kind);
|
|
72
|
+
if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t));
|
|
73
|
+
}
|
|
74
|
+
return { ok: true };
|
|
75
|
+
}
|
|
76
|
+
/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */
|
|
77
|
+
const untranslated = (key) => key;
|
|
78
|
+
/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */
|
|
79
|
+
function canMoveBlockInto(editor, id, targetId) {
|
|
80
|
+
return moveIntoVerdict(editor, id, targetId, untranslated).ok;
|
|
49
81
|
}
|
|
50
82
|
/**
|
|
51
|
-
* Moves block `id` to the end of the body of the
|
|
52
|
-
* transaction, i.e. one undo step. `false` when nothing moved:
|
|
53
|
-
*
|
|
83
|
+
* Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single
|
|
84
|
+
* transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor
|
|
85
|
+
* refused the transaction).
|
|
54
86
|
*/
|
|
55
87
|
function moveBlockInto(editor, id, targetId) {
|
|
56
88
|
if (!canMoveBlockInto(editor, id, targetId)) return false;
|
|
@@ -61,9 +93,9 @@ function moveBlockInto(editor, id, targetId) {
|
|
|
61
93
|
tr.delete(block.pos, block.pos + block.node.nodeSize);
|
|
62
94
|
tr.insert(tr.mapping.map(insertPos), block.node);
|
|
63
95
|
editor.view.dispatch(tr.scrollIntoView());
|
|
64
|
-
return
|
|
96
|
+
return editor.state.doc !== doc;
|
|
65
97
|
}
|
|
66
98
|
//#endregion
|
|
67
|
-
export { canMoveBlockInto, compositeOutline, moveBlockInto };
|
|
99
|
+
export { canMoveBlockInto, compositeOutline, moveBlockInto, moveIntoVerdict };
|
|
68
100
|
|
|
69
101
|
//# sourceMappingURL=outline.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n title: string\n /** The composite has a description (the summary). */\n hasSummary: boolean\n children: OutlineNode[]\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the\n * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: node.firstChild?.textContent.trim() || untitled,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/**\n * Whether block `id`
|
|
1
|
+
{"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { nestingReason, outermostComposites } from './accepted-children'\nimport { kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n title: string\n /** The composite has a description (the summary). */\n hasSummary: boolean\n children: OutlineNode[]\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the\n * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: node.firstChild?.textContent.trim() || untitled,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */\nexport type MoveVerdict = { ok: true } | { ok: false, reason: string }\n\nconst refused = (reason: string): MoveVerdict => ({ ok: false, reason })\n\n/**\n * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when\n * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind\n * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,\n * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not\n * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.\n */\nexport function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return refused(t('composites.move.blockGone'))\n if (targetId === null) return { ok: true }\n const target = findBlockById(doc, targetId)\n if (!target) return refused(t('composites.move.targetGone'))\n if (target.node.type.name !== 'composite') return refused(t('composites.move.targetNotBlock'))\n if (target.pos === block.pos) return refused(t('composites.move.intoSelf'))\n if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t('composites.move.intoOwn'))\n\n // The nearest Baustein around the block: when it is the target, the block only moves within it.\n const $block = doc.resolve(block.pos)\n let parentPos = -1\n for (let depth = $block.depth; depth > 0; depth--) {\n if ($block.node(depth).type.name === 'composite') {\n parentPos = $block.before(depth)\n break\n }\n }\n if (parentPos === target.pos) return { ok: true }\n\n const kinds = compositeKindsOf(editor)\n const targetKind = String(target.node.attrs.kind)\n for (const child of outermostComposites(block.node)) {\n const childKind = String(child.attrs.kind)\n if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t))\n }\n return { ok: true }\n}\n\n/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */\nconst untranslated: Translate = key => key\n\n/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n return moveIntoVerdict(editor, id, targetId, untranslated).ok\n}\n\n/**\n * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor\n * refused the transaction).\n */\nexport function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n if (!canMoveBlockInto(editor, id, targetId)) return false\n const { doc, tr } = editor.state\n const block = findBlockById(doc, id)!\n const target = targetId === null ? null : findBlockById(doc, targetId)!\n // A composite's body is its last children, so its end is just before the composite's closing token.\n const insertPos = target ? target.pos + target.node.nodeSize - 1 : doc.content.size\n tr.delete(block.pos, block.pos + block.node.nodeSize)\n tr.insert(tr.mapping.map(insertPos), block.node)\n editor.view.dispatch(tr.scrollIntoView())\n return editor.state.doc !== doc\n}\n"],"mappings":";;;;;;;;;;AA2BA,SAAgB,iBAAiB,KAAa,aAAkC,UAAiC;CAC/G,SAAS,QAAQ,QAAgB,OAAe,MAAqB;EACnE,OAAO,SAAS,MAAM,WAAW;GAC/B,MAAM,MAAM,QAAQ;GACpB,IAAI,KAAK,KAAK,SAAS,aAAa;IAClC,MAAM,OAAO,OAAO,KAAK,MAAM,IAAI;IACnC,MAAM,WAA0B,CAAC;IACjC,QAAQ,MAAM,MAAM,GAAG,QAAQ;IAC/B,IAAI,YAAY,IAAI,IAAI,GAAG;KACzB,KAAK,KAAK,GAAG,QAAQ;KACrB;IACF;IACA,MAAM,KAAK,UAAU,IAAI;IACzB,KAAK,KAAK;KACR,KAAK,MAAM,OAAO;KAClB;KACA;KACA;KACA,OAAO,KAAK,YAAY,YAAY,KAAK,KAAK;KAC9C,aAAa,KAAK,WAAW,CAAC,CAAC,EAAE,QAAQ,QAAQ,KAAK;KACtD;IACF,CAAC;GACH,OACK,IAAI,CAAC,KAAK,eAAe,CAAC,KAAK,QAElC,QAAQ,MAAM,MAAM,GAAG,IAAI;EAE/B,CAAC;CACH;CACA,MAAM,QAAuB,CAAC;CAC9B,QAAQ,KAAK,GAAG,KAAK;CACrB,OAAO;AACT;AAKA,MAAM,WAAW,YAAiC;CAAE,IAAI;CAAO;AAAO;;;;;;;;AAStE,SAAgB,gBAAgB,QAAgB,IAAY,UAAyB,GAA2B;CAC9G,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,QAAQ,cAAc,KAAK,EAAE;CACnC,IAAI,CAAC,OAAO,OAAO,QAAQ,EAAE,2BAA2B,CAAC;CACzD,IAAI,aAAa,MAAM,OAAO,EAAE,IAAI,KAAK;CACzC,MAAM,SAAS,cAAc,KAAK,QAAQ;CAC1C,IAAI,CAAC,QAAQ,OAAO,QAAQ,EAAE,4BAA4B,CAAC;CAC3D,IAAI,OAAO,KAAK,KAAK,SAAS,aAAa,OAAO,QAAQ,EAAE,gCAAgC,CAAC;CAC7F,IAAI,OAAO,QAAQ,MAAM,KAAK,OAAO,QAAQ,EAAE,0BAA0B,CAAC;CAC1E,IAAI,OAAO,MAAM,MAAM,OAAO,OAAO,MAAM,MAAM,MAAM,MAAM,KAAK,UAAU,OAAO,QAAQ,EAAE,yBAAyB,CAAC;CAGvH,MAAM,SAAS,IAAI,QAAQ,MAAM,GAAG;CACpC,IAAI,YAAY;CAChB,KAAK,IAAI,QAAQ,OAAO,OAAO,QAAQ,GAAG,SACxC,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,SAAS,aAAa;EAChD,YAAY,OAAO,OAAO,KAAK;EAC/B;CACF;CAEF,IAAI,cAAc,OAAO,KAAK,OAAO,EAAE,IAAI,KAAK;CAEhD,MAAM,QAAQ,iBAAiB,MAAM;CACrC,MAAM,aAAa,OAAO,OAAO,KAAK,MAAM,IAAI;CAChD,KAAK,MAAM,SAAS,oBAAoB,MAAM,IAAI,GAAG;EACnD,MAAM,YAAY,OAAO,MAAM,MAAM,IAAI;EACzC,IAAI,CAAC,YAAY,OAAO,YAAY,SAAS,GAAG,OAAO,QAAQ,cAAc,OAAO,YAAY,WAAW,CAAC,CAAC;CAC/G;CACA,OAAO,EAAE,IAAI,KAAK;AACpB;;AAGA,MAAM,gBAA0B,QAAO;;AAGvC,SAAgB,iBAAiB,QAAgB,IAAY,UAAkC;CAC7F,OAAO,gBAAgB,QAAQ,IAAI,UAAU,YAAY,CAAC,CAAC;AAC7D;;;;;;AAOA,SAAgB,cAAc,QAAgB,IAAY,UAAkC;CAC1F,IAAI,CAAC,iBAAiB,QAAQ,IAAI,QAAQ,GAAG,OAAO;CACpD,MAAM,EAAE,KAAK,OAAO,OAAO;CAC3B,MAAM,QAAQ,cAAc,KAAK,EAAE;CACnC,MAAM,SAAS,aAAa,OAAO,OAAO,cAAc,KAAK,QAAQ;CAErE,MAAM,YAAY,SAAS,OAAO,MAAM,OAAO,KAAK,WAAW,IAAI,IAAI,QAAQ;CAC/E,GAAG,OAAO,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK,QAAQ;CACpD,GAAG,OAAO,GAAG,QAAQ,IAAI,SAAS,GAAG,MAAM,IAAI;CAC/C,OAAO,KAAK,SAAS,GAAG,eAAe,CAAC;CACxC,OAAO,OAAO,MAAM,QAAQ;AAC9B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { compositeKindsOf } from "../nodes.js";
|
|
1
2
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
3
|
import { Icon } from "../../../react/Icon.js";
|
|
3
4
|
import { Button } from "../../../react/ui/button.js";
|
|
4
|
-
import { compositeKindsOf } from "../nodes.js";
|
|
5
5
|
import { compositeOfRow } from "../rows.js";
|
|
6
6
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
7
7
|
import { CompositeFieldEditor } from "./CompositeFieldEditor.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
2
1
|
import { Editor } from "@tiptap/core";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
3
|
//#region src/features/composites/react/CompositeInspector.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Side panel with the metadata of the document *or* of the Baustein under the caret — one at a time — connected to
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { metadataOf } from "../../../core/metadata.js";
|
|
2
|
-
import { METADATA_LIST } from "../../../core/metadata-types.js";
|
|
3
1
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
4
2
|
import { cn } from "../../../react/ui/utils.js";
|
|
5
3
|
import { Icon } from "../../../react/Icon.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { normalizeTagName } from "../../../core/tag-name.js";
|
|
5
|
+
import { compositeFieldText } from "../inspector-model.js";
|
|
8
6
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../../../react/ui/popover.js";
|
|
9
7
|
import { Input } from "../../../react/ui/input.js";
|
|
10
8
|
import { Textarea } from "../../../react/ui/textarea.js";
|
|
11
9
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
12
10
|
import { Label } from "../../../react/ui/label.js";
|
|
13
11
|
import { Badge } from "../../../react/ui/badge.js";
|
|
14
|
-
import { normalizeTagName } from "../../../core/tag-name.js";
|
|
15
12
|
import { Command, CommandInput, CommandItem, CommandList } from "../../../react/ui/command.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
13
|
+
import { useCompositeInspector } from "./use-composite-inspector.js";
|
|
14
|
+
import { useEffect, useId, useState } from "react";
|
|
18
15
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
16
|
//#region src/features/composites/react/CompositeInspector.tsx
|
|
20
17
|
const HEX_COLOR = /^#[0-9a-f]{3,8}$/i;
|
|
@@ -77,166 +74,10 @@ function MetaFieldInput({ field, value, readOnly, onChange }) {
|
|
|
77
74
|
*/
|
|
78
75
|
function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
79
76
|
const { t } = useEditorI18n();
|
|
80
|
-
const {
|
|
81
|
-
const
|
|
82
|
-
editorRef.current = editor;
|
|
83
|
-
const focusedRef = useRef(focused);
|
|
84
|
-
focusedRef.current = focused;
|
|
85
|
-
const [caretIn, setCaretIn] = useState("document");
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (documentFocused) setCaretIn("document");
|
|
88
|
-
}, [documentFocused]);
|
|
89
|
-
const section = caretIn === "editor" && focused ? "composite" : "document";
|
|
90
|
-
const [editable, setEditable] = useState(() => editor?.isEditable ?? false);
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
setEditable(editor?.isEditable ?? false);
|
|
93
|
-
if (!editor) return;
|
|
94
|
-
const sync = () => setEditable(editor.isEditable);
|
|
95
|
-
const toEditor = () => setCaretIn("editor");
|
|
96
|
-
editor.on("transaction", sync);
|
|
97
|
-
editor.on("focus", toEditor);
|
|
98
|
-
return () => {
|
|
99
|
-
editor.off("transaction", sync);
|
|
100
|
-
editor.off("focus", toEditor);
|
|
101
|
-
};
|
|
102
|
-
}, [editor]);
|
|
103
|
-
const summarized = useMemo(() => rows.filter((row) => row.description.trim()).length, [rows]);
|
|
104
|
-
const kindItems = useMemo(() => editor ? compositeKindsOf(editor).map((kind) => ({
|
|
105
|
-
value: kind.id,
|
|
106
|
-
label: kind.label,
|
|
107
|
-
icon: kind.icon
|
|
108
|
-
})) : [], [editor]);
|
|
109
|
-
function onKind(kind) {
|
|
110
|
-
const row = focused;
|
|
111
|
-
const found = editor && row ? compositeOfRow(editor.state.doc, row) : null;
|
|
112
|
-
if (!editor || !found || kind === row?.kind) return;
|
|
113
|
-
editor.commands.setCompositeKind(kind, found.pos);
|
|
114
|
-
}
|
|
115
|
-
const [draft, setDraft] = useState("");
|
|
116
|
-
const draftRef = useRef("");
|
|
117
|
-
const base = useRef("");
|
|
118
|
-
const dirty = useRef(false);
|
|
119
|
-
const pendingRow = useRef(null);
|
|
120
|
-
const timer = useRef(void 0);
|
|
121
|
-
const showDraft = useCallback((text) => {
|
|
122
|
-
draftRef.current = text;
|
|
123
|
-
setDraft(text);
|
|
124
|
-
}, []);
|
|
125
|
-
const loadDescription = useCallback(() => {
|
|
126
|
-
dirty.current = false;
|
|
127
|
-
pendingRow.current = null;
|
|
128
|
-
base.current = focusedRef.current?.description ?? "";
|
|
129
|
-
showDraft(base.current);
|
|
130
|
-
}, [showDraft]);
|
|
131
|
-
/** Writes the pending draft; `false` when there was nothing to write or a peer changed the description first. */
|
|
132
|
-
const commitDescription = useCallback(() => {
|
|
133
|
-
clearTimeout(timer.current);
|
|
134
|
-
if (!dirty.current || !pendingRow.current) return false;
|
|
135
|
-
dirty.current = false;
|
|
136
|
-
const row = pendingRow.current;
|
|
137
|
-
pendingRow.current = null;
|
|
138
|
-
const instance = editorRef.current;
|
|
139
|
-
if (!instance || instance.isDestroyed || !instance.isEditable) return false;
|
|
140
|
-
if (!setCompositeDescription(instance, row, draftRef.current, base.current)) return false;
|
|
141
|
-
base.current = compositeDescriptionText(draftRef.current);
|
|
142
|
-
return true;
|
|
143
|
-
}, []);
|
|
144
|
-
function onDescriptionInput(value) {
|
|
145
|
-
showDraft(value);
|
|
146
|
-
dirty.current = true;
|
|
147
|
-
pendingRow.current ??= focusedRef.current;
|
|
148
|
-
clearTimeout(timer.current);
|
|
149
|
-
timer.current = setTimeout(commitDescription, 600);
|
|
150
|
-
}
|
|
151
|
-
function onDescriptionBlur() {
|
|
152
|
-
if (commitDescription()) showDraft(base.current);
|
|
153
|
-
else loadDescription();
|
|
154
|
-
}
|
|
155
|
-
const focusedKey = focused?.key;
|
|
156
|
-
useEffect(() => {
|
|
157
|
-
loadDescription();
|
|
158
|
-
return () => {
|
|
159
|
-
commitDescription();
|
|
160
|
-
};
|
|
161
|
-
}, [
|
|
162
|
-
focusedKey,
|
|
163
|
-
loadDescription,
|
|
164
|
-
commitDescription
|
|
165
|
-
]);
|
|
166
|
-
const description = focused?.description;
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
if (dirty.current) {
|
|
169
|
-
if (description !== base.current && !commitDescription()) loadDescription();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (description !== base.current) loadDescription();
|
|
173
|
-
}, [
|
|
174
|
-
description,
|
|
175
|
-
loadDescription,
|
|
176
|
-
commitDescription
|
|
177
|
-
]);
|
|
178
|
-
const previousSection = useRef(section);
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
if (previousSection.current === "composite" && section !== "composite") commitDescription();
|
|
181
|
-
previousSection.current = section;
|
|
182
|
-
}, [section, commitDescription]);
|
|
183
|
-
const uid = useId();
|
|
184
|
-
const [term, setTerm] = useState("");
|
|
185
|
-
const termRef = useRef("");
|
|
186
|
-
const [tagItems, setTagItems] = useState([]);
|
|
77
|
+
const { state, inspector } = useCompositeInspector(editor, documentFocused);
|
|
78
|
+
const { section, editable, draft, searchTerm: term, tagItems, kindOptions, summarized, hasSummary, rows, tags, kinds, mentions, focused } = state;
|
|
187
79
|
const [tagsOpen, setTagsOpen] = useState(false);
|
|
188
|
-
const
|
|
189
|
-
const pendingSearch = useRef(void 0);
|
|
190
|
-
const scheduleSearch = useCallback(() => {
|
|
191
|
-
clearTimeout(searchTimer.current);
|
|
192
|
-
searchTimer.current = setTimeout(async () => {
|
|
193
|
-
const instance = editorRef.current;
|
|
194
|
-
if (!instance) return;
|
|
195
|
-
pendingSearch.current?.abort();
|
|
196
|
-
const controller = pendingSearch.current = new AbortController();
|
|
197
|
-
try {
|
|
198
|
-
const found = await metadataOf(instance).search(METADATA_LIST.hashtags, {
|
|
199
|
-
query: termRef.current,
|
|
200
|
-
editor: instance,
|
|
201
|
-
signal: controller.signal
|
|
202
|
-
});
|
|
203
|
-
if (!controller.signal.aborted) setTagItems(found.map((item) => item.label));
|
|
204
|
-
} catch (error) {
|
|
205
|
-
if (!controller.signal.aborted) console.warn("Tag-Vorschläge konnten nicht geladen werden", error);
|
|
206
|
-
}
|
|
207
|
-
}, 150);
|
|
208
|
-
}, []);
|
|
209
|
-
function changeTerm(value) {
|
|
210
|
-
termRef.current = value;
|
|
211
|
-
setTerm(value);
|
|
212
|
-
scheduleSearch();
|
|
213
|
-
}
|
|
214
|
-
useEffect(() => () => {
|
|
215
|
-
clearTimeout(searchTimer.current);
|
|
216
|
-
pendingSearch.current?.abort();
|
|
217
|
-
}, []);
|
|
218
|
-
function onTags(next) {
|
|
219
|
-
if (editor && focused) setCompositeTags(editor, focused, next);
|
|
220
|
-
}
|
|
221
|
-
function onCreateTag(raw) {
|
|
222
|
-
const row = focused;
|
|
223
|
-
const name = normalizeTagName(raw);
|
|
224
|
-
changeTerm("");
|
|
225
|
-
if (!editor || !row || !name || row.tags.includes(name)) return;
|
|
226
|
-
setCompositeTags(editor, row, [...row.tags, name]);
|
|
227
|
-
if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, {
|
|
228
|
-
id: name,
|
|
229
|
-
label: name
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
function fieldText(field) {
|
|
233
|
-
const value = focused?.meta[field.key];
|
|
234
|
-
return typeof value === "string" || typeof value === "number" ? String(value) : "";
|
|
235
|
-
}
|
|
236
|
-
function onField(field, value) {
|
|
237
|
-
if (editor?.isEditable && focused) setCompositeMetaField(editor, focused, field.key, value);
|
|
238
|
-
}
|
|
239
|
-
const hasSummary = !!focused?.description.trim();
|
|
80
|
+
const uid = useId();
|
|
240
81
|
return /* @__PURE__ */ jsx("aside", {
|
|
241
82
|
className: cn("flex h-full min-h-0 flex-col overflow-y-auto text-sm", className),
|
|
242
83
|
"aria-label": t("composites.inspector.label"),
|
|
@@ -341,14 +182,14 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
341
182
|
label: t("composites.inspector.kind"),
|
|
342
183
|
children: /* @__PURE__ */ jsxs(Select, {
|
|
343
184
|
value: focused.kind,
|
|
344
|
-
onValueChange:
|
|
185
|
+
onValueChange: inspector.setKind,
|
|
345
186
|
disabled: !editable,
|
|
346
187
|
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
347
188
|
size: "sm",
|
|
348
189
|
className: "w-full",
|
|
349
190
|
"aria-label": t("composites.kind.select"),
|
|
350
191
|
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
351
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children:
|
|
192
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: kindOptions.map((item) => /* @__PURE__ */ jsxs(SelectItem, {
|
|
352
193
|
value: item.value,
|
|
353
194
|
children: [/* @__PURE__ */ jsx(Icon, { name: item.icon }), item.label]
|
|
354
195
|
}, item.value)) })]
|
|
@@ -373,8 +214,8 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
373
214
|
placeholder: t("composites.inspector.description"),
|
|
374
215
|
"aria-label": t("composites.inspector.description"),
|
|
375
216
|
readOnly: !editable || !focused.descriptionTextOnly,
|
|
376
|
-
onChange: (event) =>
|
|
377
|
-
onBlur:
|
|
217
|
+
onChange: (event) => inspector.inputDescription(event.target.value),
|
|
218
|
+
onBlur: inspector.blurDescription
|
|
378
219
|
})
|
|
379
220
|
}),
|
|
380
221
|
/* @__PURE__ */ jsx(FormField, {
|
|
@@ -383,7 +224,7 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
383
224
|
open: tagsOpen,
|
|
384
225
|
onOpenChange: (isOpen) => {
|
|
385
226
|
setTagsOpen(isOpen);
|
|
386
|
-
if (isOpen)
|
|
227
|
+
if (isOpen) inspector.searchTags();
|
|
387
228
|
},
|
|
388
229
|
children: [/* @__PURE__ */ jsx(PopoverAnchor, {
|
|
389
230
|
asChild: true,
|
|
@@ -405,7 +246,7 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
405
246
|
className: "inline-flex cursor-pointer items-center rounded-full outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed",
|
|
406
247
|
"aria-label": t("composites.tag.remove", { tag }),
|
|
407
248
|
disabled: !editable,
|
|
408
|
-
onClick: () =>
|
|
249
|
+
onClick: () => inspector.setTags(focused.tags.filter((item) => item !== tag)),
|
|
409
250
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
410
251
|
name: "i-lucide-x",
|
|
411
252
|
className: "size-3"
|
|
@@ -433,23 +274,23 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
433
274
|
shouldFilter: false,
|
|
434
275
|
children: [/* @__PURE__ */ jsx(CommandInput, {
|
|
435
276
|
value: term,
|
|
436
|
-
onValueChange:
|
|
277
|
+
onValueChange: inspector.setSearchTerm,
|
|
437
278
|
placeholder: t("composites.tags.label"),
|
|
438
279
|
"aria-label": t("composites.tags.label"),
|
|
439
280
|
onKeyDown: (event) => {
|
|
440
|
-
if (event.key === "Backspace" && !term && focused.tags.length)
|
|
281
|
+
if (event.key === "Backspace" && !term && focused.tags.length) inspector.setTags(focused.tags.slice(0, -1));
|
|
441
282
|
}
|
|
442
283
|
}), /* @__PURE__ */ jsxs(CommandList, { children: [tagItems.map((tag) => {
|
|
443
284
|
const selected = focused.tags.includes(tag);
|
|
444
285
|
return /* @__PURE__ */ jsx(CommandItem, {
|
|
445
286
|
value: tag,
|
|
446
287
|
"data-checked": selected,
|
|
447
|
-
onSelect: () =>
|
|
288
|
+
onSelect: () => inspector.setTags(selected ? focused.tags.filter((item) => item !== tag) : [...focused.tags, tag]),
|
|
448
289
|
children: tag
|
|
449
290
|
}, tag);
|
|
450
291
|
}), term.trim() && /* @__PURE__ */ jsxs(CommandItem, {
|
|
451
292
|
value: `__create__:${term}`,
|
|
452
|
-
onSelect: () =>
|
|
293
|
+
onSelect: () => inspector.createTag(term),
|
|
453
294
|
children: [/* @__PURE__ */ jsx(Icon, { name: "i-lucide-plus" }), t("composites.tags.create", { tag: normalizeTagName(term) || term })]
|
|
454
295
|
})] })]
|
|
455
296
|
})
|
|
@@ -459,8 +300,8 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
459
300
|
focused.fields.map((field) => /* @__PURE__ */ jsx(FormField, {
|
|
460
301
|
label: field.label,
|
|
461
302
|
children: field.type === "select" ? /* @__PURE__ */ jsxs(Select, {
|
|
462
|
-
value:
|
|
463
|
-
onValueChange: (value) =>
|
|
303
|
+
value: compositeFieldText(focused, field),
|
|
304
|
+
onValueChange: (value) => inspector.setField(field, value),
|
|
464
305
|
disabled: !editable,
|
|
465
306
|
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
466
307
|
size: "sm",
|
|
@@ -473,9 +314,9 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
|
|
|
473
314
|
}, option.value)) })]
|
|
474
315
|
}) : /* @__PURE__ */ jsx(MetaFieldInput, {
|
|
475
316
|
field,
|
|
476
|
-
value:
|
|
317
|
+
value: compositeFieldText(focused, field),
|
|
477
318
|
readOnly: !editable,
|
|
478
|
-
onChange: (value) =>
|
|
319
|
+
onChange: (value) => inspector.setField(field, value)
|
|
479
320
|
})
|
|
480
321
|
}, field.key)),
|
|
481
322
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositeInspector.js","names":[],"sources":["../../../../src/features/composites/react/CompositeInspector.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type CSSProperties, type ReactNode, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { METADATA_LIST } from '../../../core/metadata-types'\nimport { metadataOf } from '../../../core/metadata'\nimport { normalizeTagName } from '../../../core/tag-name'\nimport { Icon } from '../../../react/Icon'\nimport { Badge } from '../../../react/ui/badge'\nimport { Command, CommandInput, CommandItem, CommandList } from '../../../react/ui/command'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { compositeKindsOf } from '../nodes'\nimport { compositeDescriptionText, compositeOfRow, type CompositeRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from '../rows'\nimport { useCompositeRows } from './use-composite-rows'\n\nconst HEX_COLOR = /^#[0-9a-f]{3,8}$/i\n// Same tint as the chip in the document (`--mention-color`), only invalid colors fall back to the primary color.\nconst mentionStyle = (color: string | null) => ({ '--mention-color': color && HEX_COLOR.test(color) ? color : 'var(--sk-primary)' }) as CSSProperties\n\n/** A label (+ help) above its control; the counterpart of Nuxt UI's `UFormField` (size `sm`). */\nfunction FormField({ label, help, children }: { label: string, help?: string, children: ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n <Label className=\"text-xs font-medium text-foreground\">{label}</Label>\n {children}\n {help && <p className=\"text-xs text-muted-foreground\">{help}</p>}\n </div>\n )\n}\n\n/**\n * A meta field input that writes through on every change like the Vue `UInput`/`UTextarea` bound to the row. The\n * document is re-read asynchronously (once per frame), so a purely controlled input would snap back while typing:\n * it keeps the typed value locally and follows the document whenever the document's value changes.\n */\nfunction MetaFieldInput({ field, value, readOnly, onChange }: {\n field: CompositeMetaField\n value: string\n readOnly: boolean\n onChange: (value: string) => void\n}) {\n const [local, setLocal] = useState(value)\n useEffect(() => setLocal(value), [value])\n const set = (next: string) => {\n setLocal(next)\n onChange(next)\n }\n return field.type === 'textarea'\n ? (\n <Textarea\n value={local}\n aria-label={field.label}\n rows={2}\n className=\"max-h-40 min-h-12 w-full resize-none overflow-y-auto text-sm md:text-sm\"\n readOnly={readOnly}\n onChange={event => set(event.target.value)}\n />\n )\n : (\n <Input\n value={local}\n type={field.type}\n aria-label={field.label}\n className=\"h-7 w-full text-sm md:text-sm\"\n readOnly={readOnly}\n onChange={event => set(event.target.value)}\n />\n )\n}\n\n/**\n * Side panel with the metadata of the document *or* of the Baustein under the caret — one at a time — connected to\n * `editor`. Fills the height of its container and scrolls on its own; the width comes from the host. Open/close is\n * the host's business. The `document` prop carries host-provided document facts (title, folder, dates, presence) at\n * the top of the „Dokument“ section: render them as a `<dl>` with the same grid as below to look native.\n *\n * The „Baustein“ section shows while the caret is in a Baustein of the editor; the „Dokument“ section shows before\n * the editor was ever focused, while the caret is in editor text outside any Baustein, and while `documentFocused`.\n * `documentFocused`: the host's document-level input (its title field) has the focus. While it is true the panel\n * shows the „Dokument“ section and keeps showing it after the input blurs, until the caret returns to the editor.\n */\nexport function CompositeInspector({ editor, documentFocused, document, className }: {\n editor?: Editor\n documentFocused?: boolean\n /** Document facts known to the host only, rendered above the facts derived from the editor. */\n document?: ReactNode\n className?: string\n}) {\n const { t } = useEditorI18n()\n const { rows, tags, kinds, mentions, focused } = useCompositeRows(editor)\n\n // The commit paths below run from timers and effect cleanups: they read the latest editor and row through refs.\n const editorRef = useRef(editor)\n editorRef.current = editor\n const focusedRef = useRef(focused)\n focusedRef.current = focused\n\n // Where the caret was last: the host's document input or the editor. The panel keeps its section while it is clicked.\n const [caretIn, setCaretIn] = useState<'document' | 'editor'>('document')\n // Only the rising edge switches; a blur of the host's input changes nothing.\n useEffect(() => {\n if (documentFocused) setCaretIn('document')\n }, [documentFocused])\n // A deleted Baustein (`focused` null) falls back to the document section.\n const section: 'document' | 'composite' = caretIn === 'editor' && focused ? 'composite' : 'document'\n\n // `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\n const [editable, setEditable] = useState(() => editor?.isEditable ?? false)\n useEffect(() => {\n setEditable(editor?.isEditable ?? false)\n if (!editor) return\n const sync = () => setEditable(editor.isEditable)\n const toEditor = () => setCaretIn('editor')\n editor.on('transaction', sync)\n // Only `focus`, not `selectionUpdate`: a collaboration sync can move the selection while the host's input has the focus.\n editor.on('focus', toEditor)\n return () => {\n editor.off('transaction', sync)\n editor.off('focus', toEditor)\n }\n }, [editor])\n\n // --- Dokument ---\n const summarized = useMemo(() => rows.filter(row => row.description.trim()).length, [rows])\n\n // --- Baustein: kind ---\n const kindItems = useMemo(() => editor\n ? compositeKindsOf(editor).map(kind => ({ value: kind.id, label: kind.label, icon: kind.icon }))\n : [], [editor])\n\n function onKind(kind: string) {\n const row = focused\n const found = editor && row ? compositeOfRow(editor.state.doc, row) : null\n if (!editor || !found || kind === row?.kind) return\n editor.commands.setCompositeKind(kind, found.pos)\n }\n\n // --- Baustein: description ---\n // Local draft, committed 600 ms after typing pauses and on blur, so the re-read of the document never overwrites\n // the text (and moves the caret) while it is edited. `base` is the document text the draft started from: the write\n // only lands while the description still holds it, so a peer's change made meanwhile is never replaced.\n const [draft, setDraft] = useState('')\n const draftRef = useRef('')\n const base = useRef('')\n const dirty = useRef(false)\n const pendingRow = useRef<CompositeRow | null>(null)\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n const showDraft = useCallback((text: string) => {\n draftRef.current = text\n setDraft(text)\n }, [])\n\n const loadDescription = useCallback(() => {\n dirty.current = false\n pendingRow.current = null\n base.current = focusedRef.current?.description ?? ''\n showDraft(base.current)\n }, [showDraft])\n\n /** Writes the pending draft; `false` when there was nothing to write or a peer changed the description first. */\n const commitDescription = useCallback((): boolean => {\n clearTimeout(timer.current)\n if (!dirty.current || !pendingRow.current) return false\n dirty.current = false\n const row = pendingRow.current\n pendingRow.current = null\n const instance = editorRef.current\n if (!instance || instance.isDestroyed || !instance.isEditable) return false\n if (!setCompositeDescription(instance, row, draftRef.current, base.current)) return false\n base.current = compositeDescriptionText(draftRef.current)\n return true\n }, [])\n\n function onDescriptionInput(value: string) {\n showDraft(value)\n dirty.current = true\n pendingRow.current ??= focusedRef.current\n clearTimeout(timer.current)\n timer.current = setTimeout(commitDescription, 600)\n }\n\n // Show the text the document holds now: the normalized draft after a write, the live text otherwise (also after a\n // write refused because a peer changed the description first).\n function onDescriptionBlur() {\n if (commitDescription()) showDraft(base.current)\n else loadDescription()\n }\n\n // Another Baustein: load its description; the cleanup commits the draft of the previous one first (it still holds\n // that text) — also when the panel unmounts.\n const focusedKey = focused?.key\n useEffect(() => {\n loadDescription()\n return () => {\n commitDescription()\n }\n }, [focusedKey, loadDescription, commitDescription])\n // Every change of the description reaches an untouched draft, also while the textarea has focus. A draft with\n // unsaved typing keeps it; if a peer changed the description meanwhile, its commit is refused and the peer's text\n // is loaded instead.\n const description = focused?.description\n useEffect(() => {\n if (dirty.current) {\n if (description !== base.current && !commitDescription()) loadDescription()\n return\n }\n if (description !== base.current) loadDescription()\n }, [description, loadDescription, commitDescription])\n // Leaving the Baustein section: commit the draft typed in the panel.\n const previousSection = useRef(section)\n useEffect(() => {\n if (previousSection.current === 'composite' && section !== 'composite') commitDescription()\n previousSection.current = section\n }, [section, commitDescription])\n\n const uid = useId()\n\n // --- Baustein: tags (same behavior as `CompositeTagsEditor`) ---\n const [term, setTerm] = useState('')\n const termRef = useRef('')\n const [tagItems, setTagItems] = useState<string[]>([])\n const [tagsOpen, setTagsOpen] = useState(false)\n const searchTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const pendingSearch = useRef<AbortController | undefined>(undefined)\n\n const scheduleSearch = useCallback(() => {\n clearTimeout(searchTimer.current)\n searchTimer.current = setTimeout(async () => {\n const instance = editorRef.current\n if (!instance) return\n pendingSearch.current?.abort()\n const controller = pendingSearch.current = new AbortController()\n try {\n const found = await metadataOf(instance).search(METADATA_LIST.hashtags, { query: termRef.current, editor: instance, signal: controller.signal })\n if (!controller.signal.aborted) setTagItems(found.map(item => item.label))\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Tag-Vorschläge konnten nicht geladen werden', error)\n }\n }, 150)\n }, [])\n\n function changeTerm(value: string) {\n termRef.current = value\n setTerm(value)\n scheduleSearch()\n }\n\n useEffect(() => () => {\n clearTimeout(searchTimer.current)\n pendingSearch.current?.abort()\n }, [])\n\n function onTags(next: string[]) {\n if (editor && focused) setCompositeTags(editor, focused, next)\n }\n\n // „Neu Tag“ → `neu-tag`. A name that normalizes to an already chosen tag is ignored; only unknown names are reported as created.\n function onCreateTag(raw: string) {\n const row = focused\n const name = normalizeTagName(raw)\n changeTerm('')\n if (!editor || !row || !name || row.tags.includes(name)) return\n setCompositeTags(editor, row, [...row.tags, name])\n if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, { id: name, label: name })\n }\n\n // --- Baustein: fields ---\n function fieldText(field: CompositeMetaField): string {\n const value = focused?.meta[field.key]\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n }\n\n function onField(field: CompositeMetaField, value: unknown) {\n if (editor?.isEditable && focused) setCompositeMetaField(editor, focused, field.key, value)\n }\n\n const hasSummary = !!focused?.description.trim()\n\n return (\n <aside className={cn('flex h-full min-h-0 flex-col overflow-y-auto text-sm', className)} aria-label={t('composites.inspector.label')}>\n {section === 'document'\n ? (\n <section className=\"flex-1 p-4\" aria-labelledby={`${uid}-document`}>\n <h2 id={`${uid}-document`} className=\"mb-3 text-sm font-semibold text-foreground\">\n {t('composites.inspector.document')}\n </h2>\n <div className=\"flex flex-col gap-y-2\">\n {document}\n <dl className=\"grid grid-cols-[6.5rem_minmax(0,1fr)] items-start gap-x-3 gap-y-2\">\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.inspector.composites')}\n </dt>\n {kinds.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-col gap-y-1\">\n {kinds.map(kind => (\n <span key={kind.id} className=\"flex min-h-6 min-w-0 items-center gap-2\">\n <Icon name={kind.icon} className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-foreground\">{kind.label}</span>\n <span className=\"text-muted-foreground tabular-nums\">{kind.count}</span>\n </span>\n ))}\n <span className=\"min-h-6 leading-6 text-muted-foreground tabular-nums\">\n {t('composites.inspector.summarized', { n: summarized, total: rows.length })}\n </span>\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.tags.label')}\n </dt>\n {tags.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n {tags.map(tag => <Badge key={tag} variant=\"secondary\" className=\"max-w-full\">{`#${tag}`}</Badge>)}\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.inspector.mentions')}\n </dt>\n {mentions.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n {mentions.map(mention => (\n <span\n key={mention.id}\n className=\"inline-flex max-w-full items-center gap-1.5 truncate rounded-sm px-1.5 py-0.5 text-xs font-medium text-foreground\"\n style={{ ...mentionStyle(mention.color), background: 'color-mix(in oklab, var(--mention-color) 15%, transparent)' }}\n >\n <span className=\"size-1.5 shrink-0 rounded-full\" style={{ background: 'var(--mention-color)' }} />\n <span className=\"truncate\">@{mention.label}</span>\n </span>\n ))}\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n </dl>\n </div>\n </section>\n )\n : focused && (\n <section className=\"flex-1 p-4\" aria-labelledby={`${uid}-composite`}>\n <h2 id={`${uid}-composite`} className=\"mb-3 text-sm font-semibold text-foreground\">\n {t('composites.inspector.composite')}\n </h2>\n\n <div key={focused.key} className=\"flex flex-col gap-3\">\n <FormField label={t('composites.inspector.kind')}>\n <Select value={focused.kind} onValueChange={onKind} disabled={!editable}>\n <SelectTrigger size=\"sm\" className=\"w-full\" aria-label={t('composites.kind.select')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {kindItems.map(item => (\n <SelectItem key={item.value} value={item.value}>\n <Icon name={item.icon} />\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField label={t('composites.inspector.title')} help={editable ? t('composites.inspector.titleHelp') : undefined}>\n <p className={cn('min-h-6 truncate leading-6', focused.title ? 'text-foreground' : 'text-muted-foreground/70')} title={focused.title}>\n {focused.title || t('composites.inspector.untitled')}\n </p>\n </FormField>\n\n <FormField\n label={t('composites.inspector.description')}\n help={focused.descriptionTextOnly ? undefined : t('composites.inspector.descriptionHelp')}\n >\n <Textarea\n value={draft}\n rows={3}\n className=\"max-h-52 min-h-[4.5rem] w-full resize-none overflow-y-auto text-sm md:text-sm\"\n placeholder={t('composites.inspector.description')}\n aria-label={t('composites.inspector.description')}\n readOnly={!editable || !focused.descriptionTextOnly}\n onChange={event => onDescriptionInput(event.target.value)}\n onBlur={onDescriptionBlur}\n />\n </FormField>\n\n <FormField label={t('composites.tags.label')}>\n <Popover\n open={tagsOpen}\n onOpenChange={(isOpen) => {\n setTagsOpen(isOpen)\n if (isOpen) scheduleSearch()\n }}\n >\n <PopoverAnchor asChild>\n <div className={cn('flex min-h-7 w-full flex-wrap items-center gap-1 rounded-lg border border-input px-2 py-1 dark:bg-input/30', !editable && 'opacity-50')}>\n <Icon name=\"i-lucide-hash\" className=\"size-3.5 text-muted-foreground\" />\n {focused.tags.map(tag => (\n <Badge key={tag} variant=\"secondary\" className=\"max-w-full gap-0.5 pr-1\">\n <span className=\"truncate\">{tag}</span>\n <button\n type=\"button\"\n className=\"inline-flex cursor-pointer items-center rounded-full outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed\"\n aria-label={t('composites.tag.remove', { tag })}\n disabled={!editable}\n onClick={() => onTags(focused.tags.filter(item => item !== tag))}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n </Badge>\n ))}\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={tagsOpen}\n aria-label={t('composites.tags.label')}\n disabled={!editable}\n className=\"min-w-12 flex-1 cursor-text self-stretch rounded text-left text-sm text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed\"\n >\n {focused.tags.length ? '' : t('composites.tags.label')}\n </button>\n </PopoverTrigger>\n </div>\n </PopoverAnchor>\n <PopoverContent align=\"start\" className=\"w-64 gap-0 p-0\">\n {/* `shouldFilter` off: the list is the metadata source's answer to the search term. */}\n <Command shouldFilter={false}>\n <CommandInput\n value={term}\n onValueChange={changeTerm}\n placeholder={t('composites.tags.label')}\n aria-label={t('composites.tags.label')}\n onKeyDown={(event) => {\n if (event.key === 'Backspace' && !term && focused.tags.length) onTags(focused.tags.slice(0, -1))\n }}\n />\n <CommandList>\n {tagItems.map((tag) => {\n const selected = focused.tags.includes(tag)\n return (\n <CommandItem\n key={tag}\n value={tag}\n data-checked={selected}\n onSelect={() => onTags(selected ? focused.tags.filter(item => item !== tag) : [...focused.tags, tag])}\n >\n {tag}\n </CommandItem>\n )\n })}\n {term.trim() && (\n <CommandItem value={`__create__:${term}`} onSelect={() => onCreateTag(term)}>\n <Icon name=\"i-lucide-plus\" />\n {t('composites.tags.create', { tag: normalizeTagName(term) || term })}\n </CommandItem>\n )}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n </FormField>\n\n {focused.fields.map(field => (\n <FormField key={field.key} label={field.label}>\n {field.type === 'select'\n ? (\n <Select value={fieldText(field)} onValueChange={value => onField(field, value)} disabled={!editable}>\n <SelectTrigger size=\"sm\" className=\"w-full\" aria-label={field.label}>\n <SelectValue placeholder=\"–\" />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}\n </SelectContent>\n </Select>\n )\n : (\n <MetaFieldInput\n field={field}\n value={fieldText(field)}\n readOnly={!editable}\n onChange={value => onField(field, value)}\n />\n )}\n </FormField>\n ))}\n\n <div className=\"flex items-center gap-2 border-t border-border pt-3\">\n <Icon name=\"i-lucide-sparkles\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">{t('composites.inspector.aiStatus')}</span>\n <span className={cn('ml-auto inline-flex items-center gap-1.5', hasSummary ? 'text-foreground' : 'text-muted-foreground/70')}>\n <span className={cn('size-2 rounded-full', hasSummary ? 'bg-(--sk-success)' : 'bg-border')} />\n {hasSummary ? t('composites.inspector.summaryPresent') : t('composites.inspector.summaryMissing')}\n </span>\n </div>\n </div>\n </section>\n )}\n </aside>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompositeInspector.js","names":[],"sources":["../../../../src/features/composites/react/CompositeInspector.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type CSSProperties, type ReactNode, useEffect, useId, useState } from 'react'\nimport { normalizeTagName } from '../../../core/tag-name'\nimport { Icon } from '../../../react/Icon'\nimport { Badge } from '../../../react/ui/badge'\nimport { Command, CommandInput, CommandItem, CommandList } from '../../../react/ui/command'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { compositeFieldText } from '../inspector-model'\nimport { useCompositeInspector } from './use-composite-inspector'\n\nconst HEX_COLOR = /^#[0-9a-f]{3,8}$/i\n// Same tint as the chip in the document (`--mention-color`), only invalid colors fall back to the primary color.\nconst mentionStyle = (color: string | null) => ({ '--mention-color': color && HEX_COLOR.test(color) ? color : 'var(--sk-primary)' }) as CSSProperties\n\n/** A label (+ help) above its control; the counterpart of Nuxt UI's `UFormField` (size `sm`). */\nfunction FormField({ label, help, children }: { label: string, help?: string, children: ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n <Label className=\"text-xs font-medium text-foreground\">{label}</Label>\n {children}\n {help && <p className=\"text-xs text-muted-foreground\">{help}</p>}\n </div>\n )\n}\n\n/**\n * A meta field input that writes through on every change like the Vue `UInput`/`UTextarea` bound to the row. The\n * document is re-read asynchronously (once per frame), so a purely controlled input would snap back while typing:\n * it keeps the typed value locally and follows the document whenever the document's value changes.\n */\nfunction MetaFieldInput({ field, value, readOnly, onChange }: {\n field: CompositeMetaField\n value: string\n readOnly: boolean\n onChange: (value: string) => void\n}) {\n const [local, setLocal] = useState(value)\n useEffect(() => setLocal(value), [value])\n const set = (next: string) => {\n setLocal(next)\n onChange(next)\n }\n return field.type === 'textarea'\n ? (\n <Textarea\n value={local}\n aria-label={field.label}\n rows={2}\n className=\"max-h-40 min-h-12 w-full resize-none overflow-y-auto text-sm md:text-sm\"\n readOnly={readOnly}\n onChange={event => set(event.target.value)}\n />\n )\n : (\n <Input\n value={local}\n type={field.type}\n aria-label={field.label}\n className=\"h-7 w-full text-sm md:text-sm\"\n readOnly={readOnly}\n onChange={event => set(event.target.value)}\n />\n )\n}\n\n/**\n * Side panel with the metadata of the document *or* of the Baustein under the caret — one at a time — connected to\n * `editor`. Fills the height of its container and scrolls on its own; the width comes from the host. Open/close is\n * the host's business. The `document` prop carries host-provided document facts (title, folder, dates, presence) at\n * the top of the „Dokument“ section: render them as a `<dl>` with the same grid as below to look native.\n *\n * The „Baustein“ section shows while the caret is in a Baustein of the editor; the „Dokument“ section shows before\n * the editor was ever focused, while the caret is in editor text outside any Baustein, and while `documentFocused`.\n * `documentFocused`: the host's document-level input (its title field) has the focus. While it is true the panel\n * shows the „Dokument“ section and keeps showing it after the input blurs, until the caret returns to the editor.\n */\nexport function CompositeInspector({ editor, documentFocused, document, className }: {\n editor?: Editor\n documentFocused?: boolean\n /** Document facts known to the host only, rendered above the facts derived from the editor. */\n document?: ReactNode\n className?: string\n}) {\n const { t } = useEditorI18n()\n const { state, inspector } = useCompositeInspector(editor, documentFocused)\n const { section, editable, draft, searchTerm: term, tagItems, kindOptions, summarized, hasSummary, rows, tags, kinds, mentions, focused } = state\n const [tagsOpen, setTagsOpen] = useState(false)\n const uid = useId()\n\n return (\n <aside className={cn('flex h-full min-h-0 flex-col overflow-y-auto text-sm', className)} aria-label={t('composites.inspector.label')}>\n {section === 'document'\n ? (\n <section className=\"flex-1 p-4\" aria-labelledby={`${uid}-document`}>\n <h2 id={`${uid}-document`} className=\"mb-3 text-sm font-semibold text-foreground\">\n {t('composites.inspector.document')}\n </h2>\n <div className=\"flex flex-col gap-y-2\">\n {document}\n <dl className=\"grid grid-cols-[6.5rem_minmax(0,1fr)] items-start gap-x-3 gap-y-2\">\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.inspector.composites')}\n </dt>\n {kinds.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-col gap-y-1\">\n {kinds.map(kind => (\n <span key={kind.id} className=\"flex min-h-6 min-w-0 items-center gap-2\">\n <Icon name={kind.icon} className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-foreground\">{kind.label}</span>\n <span className=\"text-muted-foreground tabular-nums\">{kind.count}</span>\n </span>\n ))}\n <span className=\"min-h-6 leading-6 text-muted-foreground tabular-nums\">\n {t('composites.inspector.summarized', { n: summarized, total: rows.length })}\n </span>\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.tags.label')}\n </dt>\n {tags.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n {tags.map(tag => <Badge key={tag} variant=\"secondary\" className=\"max-w-full\">{`#${tag}`}</Badge>)}\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n\n <dt className=\"min-h-6 leading-6 text-muted-foreground\">\n {t('composites.inspector.mentions')}\n </dt>\n {mentions.length\n ? (\n <dd className=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n {mentions.map(mention => (\n <span\n key={mention.id}\n className=\"inline-flex max-w-full items-center gap-1.5 truncate rounded-sm px-1.5 py-0.5 text-xs font-medium text-foreground\"\n style={{ ...mentionStyle(mention.color), background: 'color-mix(in oklab, var(--mention-color) 15%, transparent)' }}\n >\n <span className=\"size-1.5 shrink-0 rounded-full\" style={{ background: 'var(--mention-color)' }} />\n <span className=\"truncate\">@{mention.label}</span>\n </span>\n ))}\n </dd>\n )\n : (\n <dd className=\"m-0 min-h-6 leading-6 text-muted-foreground/70\">\n {t('composites.inspector.none')}\n </dd>\n )}\n </dl>\n </div>\n </section>\n )\n : focused && (\n <section className=\"flex-1 p-4\" aria-labelledby={`${uid}-composite`}>\n <h2 id={`${uid}-composite`} className=\"mb-3 text-sm font-semibold text-foreground\">\n {t('composites.inspector.composite')}\n </h2>\n\n <div key={focused.key} className=\"flex flex-col gap-3\">\n <FormField label={t('composites.inspector.kind')}>\n <Select value={focused.kind} onValueChange={inspector.setKind} disabled={!editable}>\n <SelectTrigger size=\"sm\" className=\"w-full\" aria-label={t('composites.kind.select')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {kindOptions.map(item => (\n <SelectItem key={item.value} value={item.value}>\n <Icon name={item.icon} />\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField label={t('composites.inspector.title')} help={editable ? t('composites.inspector.titleHelp') : undefined}>\n <p className={cn('min-h-6 truncate leading-6', focused.title ? 'text-foreground' : 'text-muted-foreground/70')} title={focused.title}>\n {focused.title || t('composites.inspector.untitled')}\n </p>\n </FormField>\n\n <FormField\n label={t('composites.inspector.description')}\n help={focused.descriptionTextOnly ? undefined : t('composites.inspector.descriptionHelp')}\n >\n <Textarea\n value={draft}\n rows={3}\n className=\"max-h-52 min-h-[4.5rem] w-full resize-none overflow-y-auto text-sm md:text-sm\"\n placeholder={t('composites.inspector.description')}\n aria-label={t('composites.inspector.description')}\n readOnly={!editable || !focused.descriptionTextOnly}\n onChange={event => inspector.inputDescription(event.target.value)}\n onBlur={inspector.blurDescription}\n />\n </FormField>\n\n <FormField label={t('composites.tags.label')}>\n <Popover\n open={tagsOpen}\n onOpenChange={(isOpen) => {\n setTagsOpen(isOpen)\n if (isOpen) inspector.searchTags()\n }}\n >\n <PopoverAnchor asChild>\n <div className={cn('flex min-h-7 w-full flex-wrap items-center gap-1 rounded-lg border border-input px-2 py-1 dark:bg-input/30', !editable && 'opacity-50')}>\n <Icon name=\"i-lucide-hash\" className=\"size-3.5 text-muted-foreground\" />\n {focused.tags.map(tag => (\n <Badge key={tag} variant=\"secondary\" className=\"max-w-full gap-0.5 pr-1\">\n <span className=\"truncate\">{tag}</span>\n <button\n type=\"button\"\n className=\"inline-flex cursor-pointer items-center rounded-full outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed\"\n aria-label={t('composites.tag.remove', { tag })}\n disabled={!editable}\n onClick={() => inspector.setTags(focused.tags.filter(item => item !== tag))}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n </Badge>\n ))}\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={tagsOpen}\n aria-label={t('composites.tags.label')}\n disabled={!editable}\n className=\"min-w-12 flex-1 cursor-text self-stretch rounded text-left text-sm text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed\"\n >\n {focused.tags.length ? '' : t('composites.tags.label')}\n </button>\n </PopoverTrigger>\n </div>\n </PopoverAnchor>\n <PopoverContent align=\"start\" className=\"w-64 gap-0 p-0\">\n {/* `shouldFilter` off: the list is the metadata source's answer to the search term. */}\n <Command shouldFilter={false}>\n <CommandInput\n value={term}\n onValueChange={inspector.setSearchTerm}\n placeholder={t('composites.tags.label')}\n aria-label={t('composites.tags.label')}\n onKeyDown={(event) => {\n if (event.key === 'Backspace' && !term && focused.tags.length) inspector.setTags(focused.tags.slice(0, -1))\n }}\n />\n <CommandList>\n {tagItems.map((tag) => {\n const selected = focused.tags.includes(tag)\n return (\n <CommandItem\n key={tag}\n value={tag}\n data-checked={selected}\n onSelect={() => inspector.setTags(selected ? focused.tags.filter(item => item !== tag) : [...focused.tags, tag])}\n >\n {tag}\n </CommandItem>\n )\n })}\n {term.trim() && (\n <CommandItem value={`__create__:${term}`} onSelect={() => inspector.createTag(term)}>\n <Icon name=\"i-lucide-plus\" />\n {t('composites.tags.create', { tag: normalizeTagName(term) || term })}\n </CommandItem>\n )}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n </FormField>\n\n {focused.fields.map(field => (\n <FormField key={field.key} label={field.label}>\n {field.type === 'select'\n ? (\n <Select value={compositeFieldText(focused, field)} onValueChange={value => inspector.setField(field, value)} disabled={!editable}>\n <SelectTrigger size=\"sm\" className=\"w-full\" aria-label={field.label}>\n <SelectValue placeholder=\"–\" />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}\n </SelectContent>\n </Select>\n )\n : (\n <MetaFieldInput\n field={field}\n value={compositeFieldText(focused, field)}\n readOnly={!editable}\n onChange={value => inspector.setField(field, value)}\n />\n )}\n </FormField>\n ))}\n\n <div className=\"flex items-center gap-2 border-t border-border pt-3\">\n <Icon name=\"i-lucide-sparkles\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">{t('composites.inspector.aiStatus')}</span>\n <span className={cn('ml-auto inline-flex items-center gap-1.5', hasSummary ? 'text-foreground' : 'text-muted-foreground/70')}>\n <span className={cn('size-2 rounded-full', hasSummary ? 'bg-(--sk-success)' : 'bg-border')} />\n {hasSummary ? t('composites.inspector.summaryPresent') : t('composites.inspector.summaryMissing')}\n </span>\n </div>\n </div>\n </section>\n )}\n </aside>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompositeRow } from "../rows.js";
|
|
2
|
-
import { ReactNode } from "react";
|
|
3
2
|
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
4
|
//#region src/features/composites/react/CompositeStatusMenu.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Quick status change: the child (a button) opens a menu of the status field's options. Kinds without a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cn } from "../../../react/ui/utils.js";
|
|
2
1
|
import { compositeKind } from "../kinds.js";
|
|
3
2
|
import { compositeKindsOf } from "../nodes.js";
|
|
3
|
+
import { cn } from "../../../react/ui/utils.js";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { NodeViewContent, NodeViewWrapper } from "@tiptap/react";
|
|
6
6
|
//#region src/features/composites/react/CompositeView.tsx
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useStore } from "../../../react/use-store.js";
|
|
2
|
+
import { createCompositeInspector } from "../inspector-model.js";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
//#region src/features/composites/react/use-composite-inspector.ts
|
|
5
|
+
/**
|
|
6
|
+
* The inspector model of `editor` (see `createCompositeInspector`): re-renders when its state changes. Follows the
|
|
7
|
+
* editor when it changes (the previous model commits its pending description draft and is disposed); a missing
|
|
8
|
+
* editor yields empty state. `documentFocused` is forwarded to the model, also to the model of a new editor.
|
|
9
|
+
*/
|
|
10
|
+
function useCompositeInspector(editor, documentFocused) {
|
|
11
|
+
const [inspector, setInspector] = useState(() => createCompositeInspector(void 0));
|
|
12
|
+
const state = useStore(inspector.state);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const next = createCompositeInspector(editor);
|
|
15
|
+
setInspector(next);
|
|
16
|
+
return () => next.dispose();
|
|
17
|
+
}, [editor]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
inspector.setDocumentFocused(!!documentFocused);
|
|
20
|
+
}, [inspector, documentFocused]);
|
|
21
|
+
return {
|
|
22
|
+
state,
|
|
23
|
+
inspector
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { useCompositeInspector };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=use-composite-inspector.js.map
|