@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.
Files changed (278) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +4 -3
  3. package/dist/adapters/anchors.d.ts +80 -0
  4. package/dist/adapters/anchors.js +270 -0
  5. package/dist/adapters/anchors.js.map +1 -0
  6. package/dist/adapters/comments/client.d.ts +64 -0
  7. package/dist/adapters/comments/client.js +536 -0
  8. package/dist/adapters/comments/client.js.map +1 -0
  9. package/dist/adapters/comments/drizzle.d.ts +430 -0
  10. package/dist/adapters/comments/drizzle.js +236 -0
  11. package/dist/adapters/comments/drizzle.js.map +1 -0
  12. package/dist/adapters/comments/errors.d.ts +9 -0
  13. package/dist/adapters/comments/errors.js +19 -0
  14. package/dist/adapters/comments/errors.js.map +1 -0
  15. package/dist/adapters/comments/json.d.ts +19 -0
  16. package/dist/adapters/comments/json.js +94 -0
  17. package/dist/adapters/comments/json.js.map +1 -0
  18. package/dist/adapters/comments/record.js +78 -0
  19. package/dist/adapters/comments/record.js.map +1 -0
  20. package/dist/adapters/comments/server.d.ts +23 -0
  21. package/dist/adapters/comments/server.js +102 -0
  22. package/dist/adapters/comments/server.js.map +1 -0
  23. package/dist/adapters/comments/types.d.ts +109 -0
  24. package/dist/adapters/documents.js +108 -0
  25. package/dist/adapters/documents.js.map +1 -0
  26. package/dist/adapters/drizzle.d.ts +293 -0
  27. package/dist/adapters/drizzle.js +157 -0
  28. package/dist/adapters/drizzle.js.map +1 -0
  29. package/dist/adapters/files.js +30 -0
  30. package/dist/adapters/files.js.map +1 -0
  31. package/dist/adapters/forge.d.ts +58 -0
  32. package/dist/adapters/forge.js +91 -0
  33. package/dist/adapters/forge.js.map +1 -0
  34. package/dist/adapters/index.d.ts +13 -0
  35. package/dist/adapters/index.js +0 -0
  36. package/dist/adapters/json.d.ts +24 -0
  37. package/dist/adapters/json.js +128 -0
  38. package/dist/adapters/json.js.map +1 -0
  39. package/dist/adapters/nitro.d.ts +61 -0
  40. package/dist/adapters/nitro.js +212 -0
  41. package/dist/adapters/nitro.js.map +1 -0
  42. package/dist/adapters/routes.js +16 -0
  43. package/dist/adapters/routes.js.map +1 -0
  44. package/dist/adapters/shared.d.ts +37 -0
  45. package/dist/adapters/shared.js +32 -0
  46. package/dist/adapters/shared.js.map +1 -0
  47. package/dist/adapters/vue.d.ts +67 -0
  48. package/dist/adapters/vue.js +187 -0
  49. package/dist/adapters/vue.js.map +1 -0
  50. package/dist/code/CodeSurface.vue.d.ts +3 -3
  51. package/dist/core/doc-anchors.d.ts +5 -0
  52. package/dist/core/doc-anchors.js +4 -3
  53. package/dist/core/doc-anchors.js.map +1 -1
  54. package/dist/core/editor-ui.js +57 -1
  55. package/dist/core/editor-ui.js.map +1 -1
  56. package/dist/core/surface-session.js +139 -0
  57. package/dist/core/surface-session.js.map +1 -0
  58. package/dist/document/DocumentSurface.d.ts +1 -1
  59. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  60. package/dist/features/ai/agent-op-review.d.ts +2 -2
  61. package/dist/features/ai/agent-op-review.js.map +1 -1
  62. package/dist/features/ai/agent-ops.d.ts +1 -1
  63. package/dist/features/ai/agent-ops.js +9 -6
  64. package/dist/features/ai/agent-ops.js.map +1 -1
  65. package/dist/features/ai/ai-apply.d.ts +1 -1
  66. package/dist/features/ai/ai-review.js +10 -1
  67. package/dist/features/ai/ai-review.js.map +1 -1
  68. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  69. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  71. package/dist/features/ai/components/AiSidebar.js +2 -2
  72. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  73. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  74. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  75. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  76. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  77. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  78. package/dist/features/ai/document-chat.d.ts +3 -2
  79. package/dist/features/ai/document-chat.js +16 -19
  80. package/dist/features/ai/document-chat.js.map +1 -1
  81. package/dist/features/ai/index.d.ts +5 -3
  82. package/dist/features/ai/index.js +4 -2
  83. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  84. package/dist/features/ai/protocol/agent-ops.js +65 -39
  85. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  86. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  87. package/dist/features/ai/protocol/block-lines.js +177 -0
  88. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  89. package/dist/features/ai/protocol/chat.js +10 -10
  90. package/dist/features/ai/protocol/chat.js.map +1 -1
  91. package/dist/features/ai/protocol/index.d.ts +3 -2
  92. package/dist/features/ai/protocol/index.js +3 -2
  93. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  94. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  95. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  96. package/dist/features/ai/react/AiSidebar.js +2 -2
  97. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  98. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  99. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  100. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  101. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  102. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  103. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  104. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  105. package/dist/features/ai/selection-flow.d.ts +133 -0
  106. package/dist/features/ai/selection-flow.js +283 -0
  107. package/dist/features/ai/selection-flow.js.map +1 -0
  108. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  109. package/dist/features/ai/use-document-chat.d.ts +1 -1
  110. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  111. package/dist/features/authors/feature.js +1 -1
  112. package/dist/features/authors/index.js +1 -1
  113. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  114. package/dist/features/comments/components/CommentBubble.js +1 -1
  115. package/dist/features/comments/components/CommentComposer.js +1 -1
  116. package/dist/features/comments/components/CommentThreadView.js +5 -2
  117. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  118. package/dist/features/comments/components/CommentsMargin.js +108 -230
  119. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  120. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  121. package/dist/features/comments/feature.js +1 -1
  122. package/dist/features/comments/index.d.ts +4 -2
  123. package/dist/features/comments/index.js +4 -2
  124. package/dist/features/comments/margin-controller.d.ts +44 -0
  125. package/dist/features/comments/margin-controller.js +132 -0
  126. package/dist/features/comments/margin-controller.js.map +1 -0
  127. package/dist/features/comments/margin-layout.d.ts +55 -0
  128. package/dist/features/comments/margin-layout.js +79 -0
  129. package/dist/features/comments/margin-layout.js.map +1 -0
  130. package/dist/features/comments/model.d.ts +8 -0
  131. package/dist/features/comments/model.js +60 -34
  132. package/dist/features/comments/model.js.map +1 -1
  133. package/dist/features/comments/react/CommentBubble.js +1 -1
  134. package/dist/features/comments/react/CommentComposer.js +1 -1
  135. package/dist/features/comments/react/CommentThreadView.js +2 -2
  136. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  137. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  138. package/dist/features/comments/react/CommentsMargin.js +85 -251
  139. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  140. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  141. package/dist/features/comments/react/use-comments.js +1 -1
  142. package/dist/features/comments/react/use-margin-layout.js +45 -0
  143. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  144. package/dist/features/comments/react.d.ts +1 -1
  145. package/dist/features/comments/schema.d.ts +23 -0
  146. package/dist/features/comments/schema.js.map +1 -1
  147. package/dist/features/comments/snapshot-equality.js +40 -0
  148. package/dist/features/comments/snapshot-equality.js.map +1 -0
  149. package/dist/features/comments/thread-anchor.js +33 -0
  150. package/dist/features/comments/thread-anchor.js.map +1 -0
  151. package/dist/features/comments/use-comments.d.ts +3 -1
  152. package/dist/features/comments/use-comments.js +1 -0
  153. package/dist/features/comments/use-comments.js.map +1 -1
  154. package/dist/features/comments/use-margin-layout.js +29 -0
  155. package/dist/features/comments/use-margin-layout.js.map +1 -0
  156. package/dist/features/comments/vue.d.ts +1 -1
  157. package/dist/features/composites/accepted-children.d.ts +21 -0
  158. package/dist/features/composites/accepted-children.js +126 -0
  159. package/dist/features/composites/accepted-children.js.map +1 -0
  160. package/dist/features/composites/components/CompositeDetails.js +1 -1
  161. package/dist/features/composites/components/CompositeInspector.js +38 -164
  162. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  163. package/dist/features/composites/feature.d.ts +2 -1
  164. package/dist/features/composites/feature.js +8 -2
  165. package/dist/features/composites/feature.js.map +1 -1
  166. package/dist/features/composites/index.d.ts +5 -3
  167. package/dist/features/composites/index.js +8 -6
  168. package/dist/features/composites/inspector-model.d.ts +97 -0
  169. package/dist/features/composites/inspector-model.js +255 -0
  170. package/dist/features/composites/inspector-model.js.map +1 -0
  171. package/dist/features/composites/kinds.d.ts +11 -0
  172. package/dist/features/composites/kinds.js +17 -2
  173. package/dist/features/composites/kinds.js.map +1 -1
  174. package/dist/features/composites/outline.d.ts +18 -5
  175. package/dist/features/composites/outline.js +44 -12
  176. package/dist/features/composites/outline.js.map +1 -1
  177. package/dist/features/composites/react/CompositeDetails.js +1 -1
  178. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  179. package/dist/features/composites/react/CompositeInspector.js +21 -180
  180. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  181. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  182. package/dist/features/composites/react/CompositeView.js +1 -1
  183. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  184. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  185. package/dist/features/composites/schema.d.ts +2 -2
  186. package/dist/features/composites/schema.js +2 -2
  187. package/dist/features/composites/schema.js.map +1 -1
  188. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  189. package/dist/features/composites/use-composite-inspector.js +41 -0
  190. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  191. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  192. package/dist/features/media/index.d.ts +1 -1
  193. package/dist/features/media/vue.d.ts +1 -1
  194. package/dist/features/mentions/index.d.ts +3 -3
  195. package/dist/features/mentions/index.js +3 -3
  196. package/dist/features/mentions/schema.d.ts +1 -1
  197. package/dist/features/mentions/schema.js +1 -1
  198. package/dist/features/themes/index.d.ts +1 -1
  199. package/dist/features/themes/index.js +1 -1
  200. package/dist/features/transcript/feature.js +1 -1
  201. package/dist/features/transcript/index.js +2 -2
  202. package/dist/i18n/de/ai.js +2 -0
  203. package/dist/i18n/de/ai.js.map +1 -1
  204. package/dist/i18n/de/composites.js +8 -1
  205. package/dist/i18n/de/composites.js.map +1 -1
  206. package/dist/i18n/de/tree.js +14 -1
  207. package/dist/i18n/de/tree.js.map +1 -1
  208. package/dist/i18n/de.d.ts +22 -0
  209. package/dist/i18n/en/ai.js +2 -0
  210. package/dist/i18n/en/ai.js.map +1 -1
  211. package/dist/i18n/en/composites.js +8 -1
  212. package/dist/i18n/en/composites.js.map +1 -1
  213. package/dist/i18n/en/tree.js +14 -1
  214. package/dist/i18n/en/tree.js.map +1 -1
  215. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  216. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  217. package/dist/integrations/composite-ai/index.js +2 -2
  218. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  219. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  220. package/dist/markdown/MarkdownSurface.js +2 -2
  221. package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
  222. package/dist/markdown/index.d.ts +2 -2
  223. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  224. package/dist/react/EditorSurface.d.ts +1 -1
  225. package/dist/react/EditorSurface.js +43 -149
  226. package/dist/react/EditorSurface.js.map +1 -1
  227. package/dist/react/EditorToolbar.js +3 -40
  228. package/dist/react/EditorToolbar.js.map +1 -1
  229. package/dist/schema/advanced.js +2 -2
  230. package/dist/server/adapter.d.ts +2 -2
  231. package/dist/server/collab.js +1 -1
  232. package/dist/server/handlers/ai.d.ts +4 -2
  233. package/dist/server/handlers/ai.js +11 -9
  234. package/dist/server/handlers/ai.js.map +1 -1
  235. package/dist/server/handlers/context.d.ts +1 -1
  236. package/dist/server/handlers/editor.d.ts +1 -1
  237. package/dist/server/handlers/editor.js +2 -2
  238. package/dist/server/host.d.ts +1 -1
  239. package/dist/server/index.d.ts +2 -2
  240. package/dist/settings/index.d.ts +1 -1
  241. package/dist/styles/tokens.css +1 -1
  242. package/dist/tree/DocumentTree.js +159 -387
  243. package/dist/tree/DocumentTree.js.map +1 -1
  244. package/dist/tree/drop-rules.js +37 -0
  245. package/dist/tree/drop-rules.js.map +1 -0
  246. package/dist/tree/react/DocumentTree.d.ts +2 -2
  247. package/dist/tree/react/DocumentTree.js +135 -397
  248. package/dist/tree/react/DocumentTree.js.map +1 -1
  249. package/dist/tree/react/use-tree-model.js +129 -0
  250. package/dist/tree/react/use-tree-model.js.map +1 -0
  251. package/dist/tree/react.js +1 -1
  252. package/dist/tree/tree-host.js +100 -0
  253. package/dist/tree/tree-host.js.map +1 -0
  254. package/dist/tree/tree-items.js +22 -43
  255. package/dist/tree/tree-items.js.map +1 -1
  256. package/dist/tree/tree-model.js +580 -0
  257. package/dist/tree/tree-model.js.map +1 -0
  258. package/dist/tree/tree-rows.js +77 -0
  259. package/dist/tree/tree-rows.js.map +1 -0
  260. package/dist/tree/use-tree-model.js +94 -0
  261. package/dist/tree/use-tree-model.js.map +1 -0
  262. package/dist/tree/vue.js +1 -1
  263. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  264. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  265. package/dist/vue/components/EditorSurface.js +27 -87
  266. package/dist/vue/components/EditorSurface.js.map +1 -1
  267. package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
  268. package/dist/vue/components/EditorToolbar.js +7 -44
  269. package/dist/vue/components/EditorToolbar.js.map +1 -1
  270. package/dist/vue/context.d.ts +1 -1
  271. package/dist/vue/context.js +1 -1
  272. package/dist/vue/context.js.map +1 -1
  273. package/dist/vue/use-editor-ui.d.ts +1 -1
  274. package/dist/vue/use-notifier.d.ts +1 -1
  275. package/docs/integration.md +97 -4
  276. package/docs/llms.md +9 -1
  277. package/docs/usage.md +10 -5
  278. 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` can move into the composite `targetId` (`null` = the end of the document): both exist, the target
39
- * is a composite and neither the block itself nor inside it.
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 canMoveBlockInto(editor, id, targetId) {
51
+ function moveIntoVerdict(editor, id, targetId, t) {
42
52
  const { doc } = editor.state;
43
53
  const block = findBlockById(doc, id);
44
- if (!block) return false;
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 || target.node.type.name !== "composite") return false;
48
- return target.pos < block.pos || target.pos >= block.pos + block.node.nodeSize;
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 composite `targetId` (`null` = the end of the document) in a single
52
- * transaction, i.e. one undo step. `false` when nothing moved: a block is missing, the target is not a composite, or it
53
- * is the block itself or lies inside it.
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 true;
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` can move into the composite `targetId` (`null` = the end of the document): both exist, the target\n * is a composite and neither the block itself nor inside it.\n */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return false\n if (targetId === null) return true\n const target = findBlockById(doc, targetId)\n if (!target || target.node.type.name !== 'composite') return false\n return target.pos < block.pos || target.pos >= block.pos + block.node.nodeSize\n}\n\n/**\n * Moves block `id` to the end of the body of the composite `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: a block is missing, the target is not a composite, or it\n * is the block itself or lies inside it.\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 true\n}\n"],"mappings":";;;;;;;AAuBA,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;;;;;AAMA,SAAgB,iBAAiB,QAAgB,IAAY,UAAkC;CAC7F,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,QAAQ,cAAc,KAAK,EAAE;CACnC,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,aAAa,MAAM,OAAO;CAC9B,MAAM,SAAS,cAAc,KAAK,QAAQ;CAC1C,IAAI,CAAC,UAAU,OAAO,KAAK,KAAK,SAAS,aAAa,OAAO;CAC7D,OAAO,OAAO,MAAM,MAAM,OAAO,OAAO,OAAO,MAAM,MAAM,MAAM,KAAK;AACxE;;;;;;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;AACT"}
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":"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"}
@@ -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 { compositeKindsOf } from "../nodes.js";
7
- import { compositeDescriptionText, compositeOfRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from "../rows.js";
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 { useCompositeRows } from "./use-composite-rows.js";
17
- import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
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 { rows, tags, kinds, mentions, focused } = useCompositeRows(editor);
81
- const editorRef = useRef(editor);
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 searchTimer = useRef(void 0);
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: onKind,
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: kindItems.map((item) => /* @__PURE__ */ jsxs(SelectItem, {
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) => onDescriptionInput(event.target.value),
377
- onBlur: onDescriptionBlur
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) scheduleSearch();
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: () => onTags(focused.tags.filter((item) => item !== tag)),
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: changeTerm,
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) onTags(focused.tags.slice(0, -1));
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: () => onTags(selected ? focused.tags.filter((item) => item !== tag) : [...focused.tags, tag]),
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: () => onCreateTag(term),
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: fieldText(field),
463
- onValueChange: (value) => onField(field, 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: fieldText(field),
317
+ value: compositeFieldText(focused, field),
477
318
  readOnly: !editable,
478
- onChange: (value) => onField(field, 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