@ecsplico/editor 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/adapters/direct-ai.d.ts +36 -0
  3. package/dist/adapters/direct-ai.js +169 -0
  4. package/dist/adapters/direct-ai.js.map +1 -0
  5. package/dist/adapters/memory.js.map +1 -1
  6. package/dist/code/CodeSurface.vue.d.ts +1 -1
  7. package/dist/core/block-drag.js +3 -0
  8. package/dist/core/block-drag.js.map +1 -1
  9. package/dist/core/block-transfer.d.ts +44 -0
  10. package/dist/core/block-transfer.js +68 -0
  11. package/dist/core/block-transfer.js.map +1 -0
  12. package/dist/core/commands.js +5 -0
  13. package/dist/core/commands.js.map +1 -1
  14. package/dist/core/core-feature.d.ts +4 -1
  15. package/dist/core/core-feature.js +8 -3
  16. package/dist/core/core-feature.js.map +1 -1
  17. package/dist/core/editor-ui.d.ts +3 -1
  18. package/dist/core/editor-ui.js.map +1 -1
  19. package/dist/core/extension-points.d.ts +17 -1
  20. package/dist/core/feature.d.ts +6 -1
  21. package/dist/core/feature.js.map +1 -1
  22. package/dist/core/index.d.ts +4 -2
  23. package/dist/core/index.js +3 -2
  24. package/dist/core/schema/index.d.ts +24 -3
  25. package/dist/core/schema/index.js +21 -4
  26. package/dist/core/schema/index.js.map +1 -1
  27. package/dist/core/surface-session.js +3 -1
  28. package/dist/core/surface-session.js.map +1 -1
  29. package/dist/core/toolbar-items.js +1 -1
  30. package/dist/core/toolbar-items.js.map +1 -1
  31. package/dist/document/DocumentSurface.vue.d.ts +1 -1
  32. package/dist/features/ai/agent-op-review.js +1 -1
  33. package/dist/features/ai/agent-ops.js +2 -2
  34. package/dist/features/ai/ai-apply.js +1 -1
  35. package/dist/features/ai/assistant.js +1 -1
  36. package/dist/features/ai/components/AiSelectionMenu.js +2 -2
  37. package/dist/features/ai/components/AiSidebar.js +2 -2
  38. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  39. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  40. package/dist/features/ai/document-chat-model.js +1 -1
  41. package/dist/features/ai/document-chat.js +2 -2
  42. package/dist/features/ai/ghost-autocomplete.js +1 -1
  43. package/dist/features/ai/index.d.ts +4 -3
  44. package/dist/features/ai/index.js +6 -5
  45. package/dist/features/ai/protocol/complete.d.ts +19 -0
  46. package/dist/features/ai/protocol/complete.js +27 -1
  47. package/dist/features/ai/protocol/complete.js.map +1 -1
  48. package/dist/features/ai/protocol/generate.d.ts +95 -0
  49. package/dist/features/ai/protocol/generate.js +58 -0
  50. package/dist/features/ai/protocol/generate.js.map +1 -0
  51. package/dist/features/ai/protocol/index.d.ts +4 -3
  52. package/dist/features/ai/protocol/index.js +6 -5
  53. package/dist/features/ai/protocol/tasks.d.ts +3 -1
  54. package/dist/features/ai/protocol/tasks.js +74 -2
  55. package/dist/features/ai/protocol/tasks.js.map +1 -1
  56. package/dist/features/ai/react/AiSelectionMenu.js +2 -2
  57. package/dist/features/ai/react/AiSidebar.js +2 -2
  58. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  59. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  60. package/dist/features/ai/selection-flow.js +1 -1
  61. package/dist/features/ai/sidebar-flow.js +1 -1
  62. package/dist/features/block-ui/block-actions.d.ts +19 -3
  63. package/dist/features/block-ui/block-actions.js +49 -3
  64. package/dist/features/block-ui/block-actions.js.map +1 -1
  65. package/dist/features/block-ui/components/BlockActionsOverlay.js +24 -13
  66. package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
  67. package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +12 -5
  68. package/dist/features/block-ui/components/BlockUi.js +52 -20
  69. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  70. package/dist/features/block-ui/components/BlockUi.vue.d.ts +6 -4
  71. package/dist/features/block-ui/feature.d.ts +8 -2
  72. package/dist/features/block-ui/feature.js +7 -2
  73. package/dist/features/block-ui/feature.js.map +1 -1
  74. package/dist/features/block-ui/index.d.ts +3 -2
  75. package/dist/features/block-ui/index.js +2 -2
  76. package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +14 -7
  77. package/dist/features/block-ui/react/BlockActionsOverlay.js +25 -13
  78. package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
  79. package/dist/features/block-ui/react/BlockUi.d.ts +6 -4
  80. package/dist/features/block-ui/react/BlockUi.js +43 -15
  81. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  82. package/dist/features/block-ui/react.d.ts +4 -2
  83. package/dist/features/block-ui/react.js +3 -3
  84. package/dist/features/block-ui/react.js.map +1 -1
  85. package/dist/features/block-ui/vue.d.ts +4 -2
  86. package/dist/features/block-ui/vue.js +3 -3
  87. package/dist/features/block-ui/vue.js.map +1 -1
  88. package/dist/features/composites/baskets.d.ts +27 -0
  89. package/dist/features/composites/baskets.js +46 -0
  90. package/dist/features/composites/baskets.js.map +1 -0
  91. package/dist/features/composites/components/CompositeBasketMenu.js +97 -0
  92. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -0
  93. package/dist/features/composites/components/CompositeChrome.js +79 -23
  94. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  95. package/dist/features/composites/components/CompositeView.js +21 -2
  96. package/dist/features/composites/components/CompositeView.js.map +1 -1
  97. package/dist/features/composites/components/DerivationBadge.js +12 -3
  98. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  99. package/dist/features/composites/components/FocusChip.js +53 -0
  100. package/dist/features/composites/components/FocusChip.js.map +1 -0
  101. package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
  102. package/dist/features/composites/components/SettingsEditingTab.js +40 -4
  103. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  104. package/dist/features/composites/derivation.d.ts +2 -1
  105. package/dist/features/composites/derivation.js +5 -1
  106. package/dist/features/composites/derivation.js.map +1 -1
  107. package/dist/features/composites/derived-chrome.js +13 -3
  108. package/dist/features/composites/derived-chrome.js.map +1 -1
  109. package/dist/features/composites/feature.d.ts +7 -1
  110. package/dist/features/composites/feature.js +107 -23
  111. package/dist/features/composites/feature.js.map +1 -1
  112. package/dist/features/composites/focus.d.ts +38 -0
  113. package/dist/features/composites/focus.js +86 -0
  114. package/dist/features/composites/focus.js.map +1 -0
  115. package/dist/features/composites/index.d.ts +7 -3
  116. package/dist/features/composites/index.js +7 -3
  117. package/dist/features/composites/kinds.d.ts +6 -1
  118. package/dist/features/composites/kinds.js.map +1 -1
  119. package/dist/features/composites/merge.d.ts +24 -0
  120. package/dist/features/composites/merge.js +47 -0
  121. package/dist/features/composites/merge.js.map +1 -0
  122. package/dist/features/composites/meta-keys.d.ts +4 -0
  123. package/dist/features/composites/meta-keys.js +8 -4
  124. package/dist/features/composites/meta-keys.js.map +1 -1
  125. package/dist/features/composites/nodes.d.ts +15 -2
  126. package/dist/features/composites/nodes.js +61 -5
  127. package/dist/features/composites/nodes.js.map +1 -1
  128. package/dist/features/composites/outline.d.ts +4 -0
  129. package/dist/features/composites/outline.js +3 -0
  130. package/dist/features/composites/outline.js.map +1 -1
  131. package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
  132. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
  133. package/dist/features/composites/react/CompositeChrome.js +52 -4
  134. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  135. package/dist/features/composites/react/CompositeView.js +15 -2
  136. package/dist/features/composites/react/CompositeView.js.map +1 -1
  137. package/dist/features/composites/react/DerivationBadge.js +10 -1
  138. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  139. package/dist/features/composites/react/FocusChip.d.ts +11 -0
  140. package/dist/features/composites/react/FocusChip.js +50 -0
  141. package/dist/features/composites/react/FocusChip.js.map +1 -0
  142. package/dist/features/composites/react/SettingsEditingTab.js +36 -1
  143. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  144. package/dist/features/composites/react.d.ts +2 -1
  145. package/dist/features/composites/react.js +3 -1
  146. package/dist/features/composites/react.js.map +1 -1
  147. package/dist/features/composites/schema.d.ts +5 -3
  148. package/dist/features/composites/schema.js +8 -4
  149. package/dist/features/composites/schema.js.map +1 -1
  150. package/dist/features/composites/split.d.ts +62 -0
  151. package/dist/features/composites/split.js +172 -0
  152. package/dist/features/composites/split.js.map +1 -0
  153. package/dist/features/composites/vue.d.ts +4 -3
  154. package/dist/features/composites/vue.js +7 -5
  155. package/dist/features/composites/vue.js.map +1 -1
  156. package/dist/features/transfer/components/TransferMenu.js +93 -0
  157. package/dist/features/transfer/components/TransferMenu.js.map +1 -0
  158. package/dist/features/transfer/components/TransferMenu.vue.d.ts +15 -0
  159. package/dist/features/transfer/feature.d.ts +76 -0
  160. package/dist/features/transfer/feature.js +169 -0
  161. package/dist/features/transfer/feature.js.map +1 -0
  162. package/dist/features/transfer/index.d.ts +3 -0
  163. package/dist/features/transfer/index.js +3 -0
  164. package/dist/features/transfer/react/TransferMenu.d.ts +12 -0
  165. package/dist/features/transfer/react/TransferMenu.js +71 -0
  166. package/dist/features/transfer/react/TransferMenu.js.map +1 -0
  167. package/dist/features/transfer/react.d.ts +10 -0
  168. package/dist/features/transfer/react.js +11 -0
  169. package/dist/features/transfer/react.js.map +1 -0
  170. package/dist/features/transfer/transforms.d.ts +83 -0
  171. package/dist/features/transfer/transforms.js +137 -0
  172. package/dist/features/transfer/transforms.js.map +1 -0
  173. package/dist/features/transfer/vue.d.ts +9 -0
  174. package/dist/features/transfer/vue.js +11 -0
  175. package/dist/features/transfer/vue.js.map +1 -0
  176. package/dist/i18n/de/ai.js +4 -0
  177. package/dist/i18n/de/ai.js.map +1 -1
  178. package/dist/i18n/de/composites.js +21 -1
  179. package/dist/i18n/de/composites.js.map +1 -1
  180. package/dist/i18n/de/integrations.js +16 -0
  181. package/dist/i18n/de/integrations.js.map +1 -1
  182. package/dist/i18n/de/transfer.js +17 -0
  183. package/dist/i18n/de/transfer.js.map +1 -0
  184. package/dist/i18n/de/tree.js +7 -0
  185. package/dist/i18n/de/tree.js.map +1 -1
  186. package/dist/i18n/de.d.ts +57 -0
  187. package/dist/i18n/de.js +3 -1
  188. package/dist/i18n/de.js.map +1 -1
  189. package/dist/i18n/en/ai.js +4 -0
  190. package/dist/i18n/en/ai.js.map +1 -1
  191. package/dist/i18n/en/composites.js +21 -1
  192. package/dist/i18n/en/composites.js.map +1 -1
  193. package/dist/i18n/en/integrations.js +16 -0
  194. package/dist/i18n/en/integrations.js.map +1 -1
  195. package/dist/i18n/en/transfer.js +17 -0
  196. package/dist/i18n/en/transfer.js.map +1 -0
  197. package/dist/i18n/en/tree.js +7 -0
  198. package/dist/i18n/en/tree.js.map +1 -1
  199. package/dist/i18n/en.js +3 -1
  200. package/dist/i18n/en.js.map +1 -1
  201. package/dist/integrations/composite-ai/composite-fill.js +1 -1
  202. package/dist/integrations/composite-ai/composite-summary.js +1 -1
  203. package/dist/integrations/composite-ai/derive.d.ts +21 -0
  204. package/dist/integrations/composite-ai/derive.js +69 -47
  205. package/dist/integrations/composite-ai/derive.js.map +1 -1
  206. package/dist/integrations/composite-ai/index.d.ts +8 -3
  207. package/dist/integrations/composite-ai/index.js +50 -4
  208. package/dist/integrations/composite-ai/index.js.map +1 -1
  209. package/dist/integrations/composite-ai/outline-sanitize.d.ts +48 -0
  210. package/dist/integrations/composite-ai/outline-sanitize.js +175 -0
  211. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -0
  212. package/dist/integrations/composite-ai/split.d.ts +44 -0
  213. package/dist/integrations/composite-ai/split.js +187 -0
  214. package/dist/integrations/composite-ai/split.js.map +1 -0
  215. package/dist/integrations/composite-ai/transfer-transforms.d.ts +27 -0
  216. package/dist/integrations/composite-ai/transfer-transforms.js +153 -0
  217. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -0
  218. package/dist/integrations/transcript-ai/protocol.d.ts +13 -0
  219. package/dist/integrations/transcript-ai/protocol.js +49 -1
  220. package/dist/integrations/transcript-ai/protocol.js.map +1 -1
  221. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  222. package/dist/markdown/MarkdownSurface.vue.d.ts +1 -1
  223. package/dist/nuxt/module.d.ts +114 -0
  224. package/dist/presets/compose.d.ts +24 -8
  225. package/dist/presets/compose.js +117 -58
  226. package/dist/presets/compose.js.map +1 -1
  227. package/dist/react/compose.js +5 -1
  228. package/dist/react/compose.js.map +1 -1
  229. package/dist/react/composed-surface.d.ts +9 -0
  230. package/dist/react/composed-surface.js +4 -1
  231. package/dist/react/composed-surface.js.map +1 -1
  232. package/dist/server/ai.d.ts +2 -5
  233. package/dist/server/ai.js.map +1 -1
  234. package/dist/server/handlers/ai.d.ts +4 -75
  235. package/dist/server/handlers/ai.js +30 -94
  236. package/dist/server/handlers/ai.js.map +1 -1
  237. package/dist/server/handlers/editor.d.ts +1 -0
  238. package/dist/server/handlers/index.d.ts +3 -1
  239. package/dist/server/handlers/index.js +3 -1
  240. package/dist/server/index.d.ts +2 -1
  241. package/dist/settings/index.d.ts +8 -0
  242. package/dist/settings/index.js +16 -4
  243. package/dist/settings/index.js.map +1 -1
  244. package/dist/styles/composites.css +41 -0
  245. package/dist/tree/DocumentTree.js +246 -80
  246. package/dist/tree/DocumentTree.js.map +1 -1
  247. package/dist/tree/drop-rules.js +8 -0
  248. package/dist/tree/drop-rules.js.map +1 -1
  249. package/dist/tree/react/DocumentTree.js +164 -32
  250. package/dist/tree/react/DocumentTree.js.map +1 -1
  251. package/dist/tree/tree-host.js +41 -2
  252. package/dist/tree/tree-host.js.map +1 -1
  253. package/dist/tree/tree-items.js +53 -5
  254. package/dist/tree/tree-items.js.map +1 -1
  255. package/dist/tree/tree-model.js +122 -10
  256. package/dist/tree/tree-model.js.map +1 -1
  257. package/dist/tree/tree-rows.js +20 -7
  258. package/dist/tree/tree-rows.js.map +1 -1
  259. package/dist/tree/tree-view-model.d.ts +5 -0
  260. package/dist/tree/tree-view-model.js +13 -0
  261. package/dist/tree/tree-view-model.js.map +1 -1
  262. package/dist/vue/AdvancedEditor.js +6 -0
  263. package/dist/vue/AdvancedEditor.js.map +1 -1
  264. package/dist/vue/AdvancedEditor.vue.d.ts +9 -1
  265. package/dist/vue/compose.js +5 -1
  266. package/dist/vue/compose.js.map +1 -1
  267. package/package.json +21 -1
@@ -0,0 +1,172 @@
1
+ import { EDITOR_SETTINGS_DEFAULTS } from "../../settings/index.js";
2
+ import { kindAccepts } from "./kinds.js";
3
+ import { TITLE_HIDDEN_KEY } from "./meta-keys.js";
4
+ import { compositeAround, compositeKindsOf } from "./nodes.js";
5
+ import { Extension } from "@tiptap/core";
6
+ import { TextSelection } from "@tiptap/pm/state";
7
+ import { Fragment } from "@tiptap/pm/model";
8
+ //#region src/features/composites/split.ts
9
+ /** Longest generated title; a longer first sentence is cut and ends in `…`. */
10
+ const TITLE_MAX_CHARS = 60;
11
+ /**
12
+ * Groups `blocks` in order so that no group exceeds `targetChars` text characters, never splitting inside a block: a
13
+ * new group starts when adding the block would exceed the target and the group is not empty, so a single block longer
14
+ * than the target forms a group of its own.
15
+ */
16
+ function splitBodyByLength(blocks, targetChars) {
17
+ const groups = [];
18
+ let current = [];
19
+ let size = 0;
20
+ for (const block of blocks) {
21
+ const chars = block.textContent.length;
22
+ if (current.length && size + chars > targetChars) {
23
+ groups.push(current);
24
+ current = [];
25
+ size = 0;
26
+ }
27
+ current.push(block);
28
+ size += chars;
29
+ }
30
+ if (current.length) groups.push(current);
31
+ return groups;
32
+ }
33
+ /** Text of the first non-empty textblock in `blocks` (nested ones included), or ''. */
34
+ function firstText(blocks) {
35
+ let found = "";
36
+ for (const block of blocks) {
37
+ if (block.isTextblock) found = block.textContent.trim();
38
+ else block.descendants((node) => {
39
+ if (found) return false;
40
+ if (node.isTextblock) found = node.textContent.trim();
41
+ return !node.isTextblock;
42
+ });
43
+ if (found) return found;
44
+ }
45
+ return "";
46
+ }
47
+ /**
48
+ * Title for a part holding `blocks`: the first sentence of its first textblock (up to the first `.`, `!` or `?` that
49
+ * is followed by white space or ends the text; the mark itself is left out), at most 60 characters, `…` appended when
50
+ * cut. '' when the blocks hold no text.
51
+ */
52
+ function generatedTitle(blocks) {
53
+ const text = firstText(blocks).replace(/\s+/g, " ");
54
+ const end = text.search(/[.!?](?=\s|$)/);
55
+ const sentence = (end === -1 ? text : text.slice(0, end)).trim();
56
+ return sentence.length > TITLE_MAX_CHARS ? `${sentence.slice(0, TITLE_MAX_CHARS).trimEnd()}…` : sentence;
57
+ }
58
+ /**
59
+ * Kind of the sub-Bausteine a split of a `parentKind` Baustein creates: `defaultKind` when the parent accepts it, else
60
+ * the parent's own kind when it accepts itself, else null (the split is refused).
61
+ */
62
+ function childKindFor(kinds, parentKind, defaultKind) {
63
+ if (kindAccepts(kinds, parentKind, defaultKind)) return defaultKind;
64
+ if (kindAccepts(kinds, parentKind, parentKind)) return parentKind;
65
+ return null;
66
+ }
67
+ function chromeOf(composite) {
68
+ const chrome = [];
69
+ for (let i = 0; i < 3; i++) chrome.push(composite.child(i));
70
+ return chrome;
71
+ }
72
+ function bodyOf(composite) {
73
+ const body = [];
74
+ composite.forEach((child, _offset, index) => {
75
+ if (index >= 3) body.push(child);
76
+ });
77
+ return body;
78
+ }
79
+ /** A new Baustein of `kind` holding `group`, titled by `generatedTitle(group)`. */
80
+ function newPart(schema, kind, group, hideTitle) {
81
+ const { nodes } = schema;
82
+ const title = generatedTitle(group);
83
+ return nodes.composite.create({
84
+ kind,
85
+ meta: hideTitle ? { [TITLE_HIDDEN_KEY]: true } : {}
86
+ }, [
87
+ nodes.compositeTitle.create(null, title ? schema.text(title) : null),
88
+ nodes.compositeDescription.create(),
89
+ nodes.compositeTags.create(),
90
+ ...group.length ? group : [nodes.paragraph.create()]
91
+ ]);
92
+ }
93
+ /**
94
+ * The nodes that replace `composite` once its body is cut into `groups` (in order, each non-empty).
95
+ * - `children`: one node, `composite` with its own title, description, tags and meta whose body is one new Baustein of
96
+ * `childKind` per group.
97
+ * - `siblings`: `composite` itself keeping the first group, then one new Baustein of the same kind per further group.
98
+ * New parts carry a generated title, hidden when `hideTitles`, and no block id.
99
+ */
100
+ function splitComposite(schema, composite, groups, mode, childKind, hideTitles) {
101
+ const chrome = chromeOf(composite);
102
+ if (mode === "children") return [composite.copy(Fragment.from([...chrome, ...groups.map((group) => newPart(schema, childKind, group, hideTitles))]))];
103
+ const [first = [], ...rest] = groups;
104
+ return [composite.copy(Fragment.from([...chrome, ...first.length ? first : [schema.nodes.paragraph.create()]])), ...rest.map((group) => newPart(schema, composite.attrs.kind, group, hideTitles))];
105
+ }
106
+ /** The composite whose content starts at `pos` in `state`, or the caret's innermost one when `pos` is undefined. */
107
+ function targetComposite(state, pos) {
108
+ if (pos === void 0) return compositeAround(state.selection.$head);
109
+ const node = state.doc.nodeAt(pos);
110
+ return node?.type.name === "composite" ? {
111
+ pos,
112
+ node
113
+ } : null;
114
+ }
115
+ /** Position right in front of the body of `composite`, which starts at `pos`. */
116
+ function bodyStartOf(pos, composite) {
117
+ return pos + 1 + chromeOf(composite).reduce((size, child) => size + child.nodeSize, 0);
118
+ }
119
+ /** Position in front of the body of the last part among `nodes`, which replaced a composite starting at `pos`. */
120
+ function lastPartBodyStart(pos, nodes, mode) {
121
+ if (mode === "children") {
122
+ const parent = nodes[0];
123
+ const last = parent.lastChild;
124
+ return bodyStartOf(pos + parent.nodeSize - 1 - last.nodeSize, last);
125
+ }
126
+ const last = nodes[nodes.length - 1];
127
+ return bodyStartOf(pos + nodes.reduce((size, node) => size + node.nodeSize, 0) - last.nodeSize, last);
128
+ }
129
+ /** Client-only, no schema: the split commands of Bausteine. */
130
+ const CompositeSplit = Extension.create({
131
+ name: "compositeSplit",
132
+ addOptions: () => ({
133
+ defaultKind: () => EDITOR_SETTINGS_DEFAULTS.blocks.defaultKind,
134
+ targetChars: () => EDITOR_SETTINGS_DEFAULTS.blocks.splitTargetChars
135
+ }),
136
+ addCommands() {
137
+ /** The kind of the sub-Bausteine (`children`) or of the composite itself (`siblings`); null when a `children` split has no accepted kind. */
138
+ const partKind = (composite, mode) => mode === "siblings" ? composite.attrs.kind : childKindFor(compositeKindsOf(this.editor), composite.attrs.kind, this.options.defaultKind());
139
+ return {
140
+ splitCompositeAtCaret: ({ mode }) => ({ state, tr, dispatch }) => {
141
+ const { $head } = state.selection;
142
+ const found = compositeAround($head);
143
+ if (!found) return false;
144
+ const index = $head.index(found.depth) - 3;
145
+ if (index < 1) return false;
146
+ const kind = partKind(found.node, mode);
147
+ if (kind === null) return false;
148
+ if (dispatch) {
149
+ const body = bodyOf(found.node);
150
+ const nodes = splitComposite(state.schema, found.node, [body.slice(0, index), body.slice(index)], mode, kind, true);
151
+ tr.replaceWith(found.pos, found.pos + found.node.nodeSize, nodes);
152
+ tr.setSelection(TextSelection.near(tr.doc.resolve(lastPartBodyStart(found.pos, nodes, mode))));
153
+ }
154
+ return true;
155
+ },
156
+ splitCompositeByLength: ({ mode, targetChars, pos }) => ({ state, tr, dispatch }) => {
157
+ const found = targetComposite(state, pos);
158
+ if (!found) return false;
159
+ const groups = splitBodyByLength(bodyOf(found.node), targetChars ?? this.options.targetChars());
160
+ if (groups.length < 2) return false;
161
+ const kind = partKind(found.node, mode);
162
+ if (kind === null) return false;
163
+ if (dispatch) tr.replaceWith(found.pos, found.pos + found.node.nodeSize, splitComposite(state.schema, found.node, groups, mode, kind, true));
164
+ return true;
165
+ }
166
+ };
167
+ }
168
+ });
169
+ //#endregion
170
+ export { CompositeSplit, childKindFor, generatedTitle, splitBodyByLength, splitComposite };
171
+
172
+ //# sourceMappingURL=split.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"split.js","names":[],"sources":["../../../src/features/composites/split.ts"],"sourcesContent":["// Splitting a Baustein: at the caret or by length, into sibling Bausteine or into sub-Bausteine. The pure parts\n// (`splitBodyByLength`, `generatedTitle`, `childKindFor`, `splitComposite`) know nothing about the editor; the\n// `CompositeSplit` extension wraps them into commands. Generated parts get a title from their first sentence and hide it.\nimport { Extension } from '@tiptap/core'\nimport { Fragment, type Node as PMNode, type Schema } from '@tiptap/pm/model'\nimport { type EditorState, TextSelection } from '@tiptap/pm/state'\nimport { EDITOR_SETTINGS_DEFAULTS } from '../../settings'\nimport { type CompositeKind, kindAccepts } from './kinds'\nimport { TITLE_HIDDEN_KEY } from './meta-keys'\nimport { COMPOSITE_CHROME_COUNT, compositeAround, compositeKindsOf } from './nodes'\n\n/** Where the parts of a split go: next to the split Baustein (`siblings`) or into it as sub-Bausteine (`children`). */\nexport type SplitMode = 'siblings' | 'children'\n\n/** Longest generated title; a longer first sentence is cut and ends in `…`. */\nconst TITLE_MAX_CHARS = 60\n\n/**\n * Groups `blocks` in order so that no group exceeds `targetChars` text characters, never splitting inside a block: a\n * new group starts when adding the block would exceed the target and the group is not empty, so a single block longer\n * than the target forms a group of its own.\n */\nexport function splitBodyByLength(blocks: readonly PMNode[], targetChars: number): PMNode[][] {\n const groups: PMNode[][] = []\n let current: PMNode[] = []\n let size = 0\n for (const block of blocks) {\n const chars = block.textContent.length\n if (current.length && size + chars > targetChars) {\n groups.push(current)\n current = []\n size = 0\n }\n current.push(block)\n size += chars\n }\n if (current.length) groups.push(current)\n return groups\n}\n\n/** Text of the first non-empty textblock in `blocks` (nested ones included), or ''. */\nfunction firstText(blocks: readonly PMNode[]): string {\n let found = ''\n for (const block of blocks) {\n if (block.isTextblock) found = block.textContent.trim()\n else block.descendants((node) => {\n if (found) return false\n if (node.isTextblock) found = node.textContent.trim()\n return !node.isTextblock\n })\n if (found) return found\n }\n return ''\n}\n\n/**\n * Title for a part holding `blocks`: the first sentence of its first textblock (up to the first `.`, `!` or `?` that\n * is followed by white space or ends the text; the mark itself is left out), at most 60 characters, `…` appended when\n * cut. '' when the blocks hold no text.\n */\nexport function generatedTitle(blocks: readonly PMNode[]): string {\n const text = firstText(blocks).replace(/\\s+/g, ' ')\n const end = text.search(/[.!?](?=\\s|$)/)\n const sentence = (end === -1 ? text : text.slice(0, end)).trim()\n return sentence.length > TITLE_MAX_CHARS ? `${sentence.slice(0, TITLE_MAX_CHARS).trimEnd()}…` : sentence\n}\n\n/**\n * Kind of the sub-Bausteine a split of a `parentKind` Baustein creates: `defaultKind` when the parent accepts it, else\n * the parent's own kind when it accepts itself, else null (the split is refused).\n */\nexport function childKindFor(kinds: readonly CompositeKind[], parentKind: string, defaultKind: string): string | null {\n if (kindAccepts(kinds, parentKind, defaultKind)) return defaultKind\n if (kindAccepts(kinds, parentKind, parentKind)) return parentKind\n return null\n}\n\nfunction chromeOf(composite: PMNode): PMNode[] {\n const chrome: PMNode[] = []\n for (let i = 0; i < COMPOSITE_CHROME_COUNT; i++) chrome.push(composite.child(i))\n return chrome\n}\n\nfunction bodyOf(composite: PMNode): PMNode[] {\n const body: PMNode[] = []\n composite.forEach((child, _offset, index) => {\n if (index >= COMPOSITE_CHROME_COUNT) body.push(child)\n })\n return body\n}\n\n/** A new Baustein of `kind` holding `group`, titled by `generatedTitle(group)`. */\nfunction newPart(schema: Schema, kind: string, group: readonly PMNode[], hideTitle: boolean): PMNode {\n const { nodes } = schema\n const title = generatedTitle(group)\n return nodes.composite!.create({ kind, meta: hideTitle ? { [TITLE_HIDDEN_KEY]: true } : {} }, [\n nodes.compositeTitle!.create(null, title ? schema.text(title) : null),\n nodes.compositeDescription!.create(),\n nodes.compositeTags!.create(),\n ...(group.length ? group : [nodes.paragraph!.create()]),\n ])\n}\n\n/**\n * The nodes that replace `composite` once its body is cut into `groups` (in order, each non-empty).\n * - `children`: one node, `composite` with its own title, description, tags and meta whose body is one new Baustein of\n * `childKind` per group.\n * - `siblings`: `composite` itself keeping the first group, then one new Baustein of the same kind per further group.\n * New parts carry a generated title, hidden when `hideTitles`, and no block id.\n */\nexport function splitComposite(schema: Schema, composite: PMNode, groups: readonly (readonly PMNode[])[], mode: SplitMode, childKind: string, hideTitles: boolean): PMNode[] {\n const chrome = chromeOf(composite)\n if (mode === 'children') return [composite.copy(Fragment.from([...chrome, ...groups.map(group => newPart(schema, childKind, group, hideTitles))]))]\n const [first = [], ...rest] = groups\n return [\n composite.copy(Fragment.from([...chrome, ...(first.length ? first : [schema.nodes.paragraph!.create()])])),\n ...rest.map(group => newPart(schema, composite.attrs.kind as string, group, hideTitles)),\n ]\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n compositeSplit: {\n /**\n * Split the caret's innermost Baustein in front of the body block holding the caret (refused when that is the first body block or the caret is not in a body): the part before and the part from the caret on become sibling Bausteine or sub-Bausteine (`children`, of the default kind or the parent's own; refused when neither is accepted). The caret moves into the new part.\n */\n splitCompositeAtCaret: (options: { mode: SplitMode }) => ReturnType\n /**\n * Split the Baustein at `pos` (default: the caret's innermost one) into parts of about `targetChars` characters (default: setting `blocks.splitTargetChars`) without cutting inside a block. Refused with fewer than two parts, or in `children` mode when no child kind is accepted. The generated titles are hidden.\n */\n splitCompositeByLength: (options: { mode: SplitMode, targetChars?: number, pos?: number }) => ReturnType\n }\n }\n}\n\n/** Options of `CompositeSplit`: read on every use, so a changed setting applies without recreating the editor. */\nexport interface CompositeSplitOptions {\n /** Kind of the sub-Bausteine a `children` split creates when the parent accepts it (setting `blocks.defaultKind`). */\n defaultKind: () => string\n /** Target size in characters of a length split (setting `blocks.splitTargetChars`). */\n targetChars: () => number\n}\n\n/** The composite whose content starts at `pos` in `state`, or the caret's innermost one when `pos` is undefined. */\nfunction targetComposite(state: EditorState, pos: number | undefined): { pos: number, node: PMNode } | null {\n if (pos === undefined) return compositeAround(state.selection.$head)\n const node = state.doc.nodeAt(pos)\n return node?.type.name === 'composite' ? { pos, node } : null\n}\n\n/** Position right in front of the body of `composite`, which starts at `pos`. */\nfunction bodyStartOf(pos: number, composite: PMNode): number {\n return pos + 1 + chromeOf(composite).reduce((size, child) => size + child.nodeSize, 0)\n}\n\n/** Position in front of the body of the last part among `nodes`, which replaced a composite starting at `pos`. */\nfunction lastPartBodyStart(pos: number, nodes: readonly PMNode[], mode: SplitMode): number {\n if (mode === 'children') {\n const parent = nodes[0]!\n const last = parent.lastChild!\n return bodyStartOf(pos + parent.nodeSize - 1 - last.nodeSize, last)\n }\n const last = nodes[nodes.length - 1]!\n return bodyStartOf(pos + nodes.reduce((size, node) => size + node.nodeSize, 0) - last.nodeSize, last)\n}\n\n/** Client-only, no schema: the split commands of Bausteine. */\nexport const CompositeSplit = Extension.create<CompositeSplitOptions>({\n name: 'compositeSplit',\n addOptions: () => ({\n defaultKind: () => EDITOR_SETTINGS_DEFAULTS.blocks.defaultKind,\n targetChars: () => EDITOR_SETTINGS_DEFAULTS.blocks.splitTargetChars,\n }),\n addCommands() {\n /** The kind of the sub-Bausteine (`children`) or of the composite itself (`siblings`); null when a `children` split has no accepted kind. */\n const partKind = (composite: PMNode, mode: SplitMode): string | null =>\n mode === 'siblings' ? composite.attrs.kind as string : childKindFor(compositeKindsOf(this.editor), composite.attrs.kind as string, this.options.defaultKind())\n\n return {\n splitCompositeAtCaret: ({ mode }) => ({ state, tr, dispatch }) => {\n const { $head } = state.selection\n const found = compositeAround($head)\n if (!found) return false\n const index = $head.index(found.depth) - COMPOSITE_CHROME_COUNT\n if (index < 1) return false\n const kind = partKind(found.node, mode)\n if (kind === null) return false\n if (dispatch) {\n const body = bodyOf(found.node)\n const nodes = splitComposite(state.schema, found.node, [body.slice(0, index), body.slice(index)], mode, kind, true)\n tr.replaceWith(found.pos, found.pos + found.node.nodeSize, nodes)\n tr.setSelection(TextSelection.near(tr.doc.resolve(lastPartBodyStart(found.pos, nodes, mode))))\n }\n return true\n },\n\n splitCompositeByLength: ({ mode, targetChars, pos }) => ({ state, tr, dispatch }) => {\n const found = targetComposite(state, pos)\n if (!found) return false\n const groups = splitBodyByLength(bodyOf(found.node), targetChars ?? this.options.targetChars())\n if (groups.length < 2) return false\n const kind = partKind(found.node, mode)\n if (kind === null) return false\n if (dispatch) tr.replaceWith(found.pos, found.pos + found.node.nodeSize, splitComposite(state.schema, found.node, groups, mode, kind, true))\n return true\n },\n }\n },\n})\n"],"mappings":"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"}
@@ -9,8 +9,9 @@ import _default$5 from "./components/CompositeSummaryEditor.vue.js";
9
9
  import _default$6 from "./components/CompositeTable.vue.js";
10
10
  import _default$7 from "./components/CompositeTagsEditor.vue.js";
11
11
  import _default$8 from "./components/CompositeView.vue.js";
12
- import _default$9 from "./components/ReferenceView.vue.js";
13
- import _default$10 from "./components/SettingsEditingTab.vue.js";
12
+ import _default$9 from "./components/FocusChip.vue.js";
13
+ import _default$10 from "./components/ReferenceView.vue.js";
14
+ import _default$11 from "./components/SettingsEditingTab.vue.js";
14
15
  import { useCompositeAnchors } from "./use-composite-anchors.js";
15
16
  import { CompositeRowsModel, useCompositeRows } from "./use-composite-rows.js";
16
17
  //#region src/features/composites/vue.d.ts
@@ -20,5 +21,5 @@ import { CompositeRowsModel, useCompositeRows } from "./use-composite-rows.js";
20
21
  */
21
22
  export declare function compositeFeature(options?: CompositeFeatureOptions): VueFeature;
22
23
  //#endregion
23
- export { _default as CompositeChrome, _default$1 as CompositeDetails, _default$2 as CompositeFieldEditor, _default$3 as CompositeInspector, type CompositeRowsModel, _default$4 as CompositeStatusMenu, _default$5 as CompositeSummaryEditor, _default$6 as CompositeTable, _default$7 as CompositeTagsEditor, _default$8 as CompositeView, _default$9 as ReferenceView, _default$10 as SettingsEditingTab, useCompositeAnchors, useCompositeRows };
24
+ export { _default as CompositeChrome, _default$1 as CompositeDetails, _default$2 as CompositeFieldEditor, _default$3 as CompositeInspector, type CompositeRowsModel, _default$4 as CompositeStatusMenu, _default$5 as CompositeSummaryEditor, _default$6 as CompositeTable, _default$7 as CompositeTagsEditor, _default$8 as CompositeView, _default$9 as FocusChip, _default$10 as ReferenceView, _default$11 as SettingsEditingTab, useCompositeAnchors, useCompositeRows };
24
25
  //# sourceMappingURL=vue.d.ts.map
@@ -8,8 +8,9 @@ import _sfc_main$4 from "./components/CompositeStatusMenu.js";
8
8
  import _sfc_main$7 from "./components/CompositeTagsEditor.js";
9
9
  import _sfc_main from "./components/CompositeChrome.js";
10
10
  import _sfc_main$8 from "./components/CompositeView.js";
11
- import _sfc_main$9 from "./components/ReferenceView.js";
12
- import _sfc_main$10 from "./components/SettingsEditingTab.js";
11
+ import _sfc_main$9 from "./components/FocusChip.js";
12
+ import _sfc_main$10 from "./components/ReferenceView.js";
13
+ import _sfc_main$11 from "./components/SettingsEditingTab.js";
13
14
  import _sfc_main$3 from "./components/CompositeInspector.js";
14
15
  import _sfc_main$6 from "./components/CompositeTable.js";
15
16
  import { VueNodeViewRenderer } from "@tiptap/vue-3";
@@ -21,14 +22,15 @@ import { VueNodeViewRenderer } from "@tiptap/vue-3";
21
22
  function compositeFeature(options = {}) {
22
23
  return compositeFeatureCore({
23
24
  chrome: _sfc_main,
24
- settingsTab: _sfc_main$10,
25
+ focusChip: _sfc_main$9,
26
+ settingsTab: _sfc_main$11,
25
27
  nodeViews: {
26
28
  composite: VueNodeViewRenderer(_sfc_main$8),
27
- reference: VueNodeViewRenderer(_sfc_main$9)
29
+ reference: VueNodeViewRenderer(_sfc_main$10)
28
30
  }
29
31
  }, options);
30
32
  }
31
33
  //#endregion
32
- export { _sfc_main as CompositeChrome, _sfc_main$1 as CompositeDetails, _sfc_main$2 as CompositeFieldEditor, _sfc_main$3 as CompositeInspector, _sfc_main$4 as CompositeStatusMenu, _sfc_main$5 as CompositeSummaryEditor, _sfc_main$6 as CompositeTable, _sfc_main$7 as CompositeTagsEditor, _sfc_main$8 as CompositeView, _sfc_main$9 as ReferenceView, _sfc_main$10 as SettingsEditingTab, compositeFeature, useCompositeAnchors, useCompositeRows };
34
+ export { _sfc_main as CompositeChrome, _sfc_main$1 as CompositeDetails, _sfc_main$2 as CompositeFieldEditor, _sfc_main$3 as CompositeInspector, _sfc_main$4 as CompositeStatusMenu, _sfc_main$5 as CompositeSummaryEditor, _sfc_main$6 as CompositeTable, _sfc_main$7 as CompositeTagsEditor, _sfc_main$8 as CompositeView, _sfc_main$9 as FocusChip, _sfc_main$10 as ReferenceView, _sfc_main$11 as SettingsEditingTab, compositeFeature, useCompositeAnchors, useCompositeRows };
33
35
 
34
36
  //# sourceMappingURL=vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vue.js","names":["CompositeChrome","SettingsEditingTab","CompositeView","ReferenceView"],"sources":["../../../src/features/composites/vue.ts"],"sourcesContent":["import { VueNodeViewRenderer } from '@tiptap/vue-3'\nimport type { Component } from 'vue'\nimport type { VueFeature } from '../../vue'\nimport CompositeChrome from './components/CompositeChrome.vue'\nimport CompositeView from './components/CompositeView.vue'\nimport ReferenceView from './components/ReferenceView.vue'\nimport SettingsEditingTab from './components/SettingsEditingTab.vue'\nimport { type CompositeFeatureOptions, compositeFeatureCore } from './feature'\n\nexport { default as CompositeChrome } from './components/CompositeChrome.vue'\nexport { default as CompositeDetails } from './components/CompositeDetails.vue'\nexport { default as CompositeFieldEditor } from './components/CompositeFieldEditor.vue'\nexport { default as CompositeInspector } from './components/CompositeInspector.vue'\nexport { default as CompositeStatusMenu } from './components/CompositeStatusMenu.vue'\nexport { default as CompositeSummaryEditor } from './components/CompositeSummaryEditor.vue'\nexport { default as CompositeTable } from './components/CompositeTable.vue'\nexport { default as CompositeTagsEditor } from './components/CompositeTagsEditor.vue'\nexport { default as CompositeView } from './components/CompositeView.vue'\nexport { default as ReferenceView } from './components/ReferenceView.vue'\nexport { default as SettingsEditingTab } from './components/SettingsEditingTab.vue'\nexport { useCompositeAnchors } from './use-composite-anchors'\nexport { type CompositeRowsModel, useCompositeRows } from './use-composite-rows'\n\n/**\n * The Vue composites feature (Nuxt UI): see `compositeFeatureCore`. The host places `CompositeTable` and\n * `CompositeInspector` beside the editor.\n */\nexport function compositeFeature(options: CompositeFeatureOptions = {}): VueFeature {\n return compositeFeatureCore<Component>({\n chrome: CompositeChrome,\n settingsTab: SettingsEditingTab,\n nodeViews: { composite: VueNodeViewRenderer(CompositeView), reference: VueNodeViewRenderer(ReferenceView) },\n }, options)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,iBAAiB,UAAmC,CAAC,GAAe;CAClF,OAAO,qBAAgC;EACrC,QAAQA;EACR,aAAaC;EACb,WAAW;GAAE,WAAW,oBAAoBC,WAAa;GAAG,WAAW,oBAAoBC,WAAa;EAAE;CAC5G,GAAG,OAAO;AACZ"}
1
+ {"version":3,"file":"vue.js","names":["CompositeChrome","FocusChip","SettingsEditingTab","CompositeView","ReferenceView"],"sources":["../../../src/features/composites/vue.ts"],"sourcesContent":["import { VueNodeViewRenderer } from '@tiptap/vue-3'\nimport type { Component } from 'vue'\nimport type { VueFeature } from '../../vue'\nimport CompositeChrome from './components/CompositeChrome.vue'\nimport CompositeView from './components/CompositeView.vue'\nimport FocusChip from './components/FocusChip.vue'\nimport ReferenceView from './components/ReferenceView.vue'\nimport SettingsEditingTab from './components/SettingsEditingTab.vue'\nimport { type CompositeFeatureOptions, compositeFeatureCore } from './feature'\n\nexport { default as CompositeChrome } from './components/CompositeChrome.vue'\nexport { default as CompositeDetails } from './components/CompositeDetails.vue'\nexport { default as CompositeFieldEditor } from './components/CompositeFieldEditor.vue'\nexport { default as CompositeInspector } from './components/CompositeInspector.vue'\nexport { default as CompositeStatusMenu } from './components/CompositeStatusMenu.vue'\nexport { default as CompositeSummaryEditor } from './components/CompositeSummaryEditor.vue'\nexport { default as CompositeTable } from './components/CompositeTable.vue'\nexport { default as CompositeTagsEditor } from './components/CompositeTagsEditor.vue'\nexport { default as CompositeView } from './components/CompositeView.vue'\nexport { default as FocusChip } from './components/FocusChip.vue'\nexport { default as ReferenceView } from './components/ReferenceView.vue'\nexport { default as SettingsEditingTab } from './components/SettingsEditingTab.vue'\nexport { useCompositeAnchors } from './use-composite-anchors'\nexport { type CompositeRowsModel, useCompositeRows } from './use-composite-rows'\n\n/**\n * The Vue composites feature (Nuxt UI): see `compositeFeatureCore`. The host places `CompositeTable` and\n * `CompositeInspector` beside the editor.\n */\nexport function compositeFeature(options: CompositeFeatureOptions = {}): VueFeature {\n return compositeFeatureCore<Component>({\n chrome: CompositeChrome,\n focusChip: FocusChip,\n settingsTab: SettingsEditingTab,\n nodeViews: { composite: VueNodeViewRenderer(CompositeView), reference: VueNodeViewRenderer(ReferenceView) },\n }, options)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,iBAAiB,UAAmC,CAAC,GAAe;CAClF,OAAO,qBAAgC;EACrC,QAAQA;EACR,WAAWC;EACX,aAAaC;EACb,WAAW;GAAE,WAAW,oBAAoBC,WAAa;GAAG,WAAW,oBAAoBC,YAAa;EAAE;CAC5G,GAAG,OAAO;AACZ"}
@@ -0,0 +1,93 @@
1
+ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
2
+ import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
3
+ import { useStoreRef } from "../../../vue/store-ref.js";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeStyle, onBeforeUnmount, openBlock, ref, renderList, toDisplayString, unref, watch } from "vue";
5
+ //#region src/features/transfer/components/TransferMenu.vue
6
+ const _hoisted_1 = ["aria-label"];
7
+ const _hoisted_2 = { class: "px-2 py-1 text-xs text-[var(--sk-muted)]" };
8
+ /**
9
+ * The transform menu of a Shift-drop (mounted by the transfer feature): opens at the drop point while a drop waits in
10
+ * `editor.storage.blockDropTarget.pending`, lists the transforms available for it and runs the one picked. Escape or a
11
+ * click elsewhere cancels the drop.
12
+ */
13
+ const ITEM_HEIGHT = 32;
14
+ const MENU_WIDTH = 240;
15
+ const EDGE = 8;
16
+ const _sfc_main = /*@__PURE__*/ defineComponent({
17
+ __name: "TransferMenu",
18
+ props: { editor: {} },
19
+ setup(__props) {
20
+ const props = __props;
21
+ const { Button: UButton } = useUiKit();
22
+ const { t } = useEditorI18n();
23
+ const storage = props.editor.storage.blockDropTarget;
24
+ const pending = useStoreRef(storage.pending);
25
+ const transforms = computed(() => pending.value ? storage.available(pending.value) : []);
26
+ const label = (transform) => transform.labelKey ? t(transform.labelKey) : transform.label;
27
+ /** The menu's top-left corner: at the drop point, kept inside the viewport. */
28
+ const style = computed(() => {
29
+ const drop = pending.value;
30
+ if (!drop) return {};
31
+ const height = transforms.value.length * ITEM_HEIGHT + ITEM_HEIGHT + 16;
32
+ const left = Math.max(EDGE, Math.min(drop.x, window.innerWidth - MENU_WIDTH - EDGE));
33
+ const top = Math.max(EDGE, Math.min(drop.y, window.innerHeight - height - EDGE));
34
+ return {
35
+ left: `${left}px`,
36
+ top: `${top}px`,
37
+ width: `${MENU_WIDTH}px`
38
+ };
39
+ });
40
+ const root = ref(null);
41
+ const cancel = () => props.editor.commands.cancelTransfer();
42
+ const onKey = (event) => {
43
+ if (event.key !== "Escape") return;
44
+ event.preventDefault();
45
+ cancel();
46
+ };
47
+ const onPointerDown = (event) => {
48
+ if (!root.value?.contains(event.target)) cancel();
49
+ };
50
+ const listen = (on) => {
51
+ if (on) {
52
+ document.addEventListener("keydown", onKey, true);
53
+ document.addEventListener("pointerdown", onPointerDown, true);
54
+ } else {
55
+ document.removeEventListener("keydown", onKey, true);
56
+ document.removeEventListener("pointerdown", onPointerDown, true);
57
+ }
58
+ };
59
+ watch(() => pending.value !== null, listen, { immediate: true });
60
+ onBeforeUnmount(() => listen(false));
61
+ return (_ctx, _cache) => {
62
+ return unref(pending) ? (openBlock(), createElementBlock("div", {
63
+ key: 0,
64
+ ref_key: "root",
65
+ ref: root,
66
+ class: "fixed z-50 rounded-lg border border-[var(--sk-border)] bg-[var(--sk-bg)] p-1 shadow-lg",
67
+ style: normalizeStyle(style.value),
68
+ role: "menu",
69
+ "aria-label": unref(t)("transfer.menu.title")
70
+ }, [createElementVNode("p", _hoisted_2, toDisplayString(unref(t)("transfer.menu.title")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(transforms.value, (transform) => {
71
+ return openBlock(), createBlock(unref(UButton), {
72
+ key: transform.id,
73
+ class: "w-full justify-start",
74
+ size: "sm",
75
+ color: "neutral",
76
+ variant: "ghost",
77
+ role: "menuitem",
78
+ icon: transform.icon,
79
+ label: label(transform),
80
+ onClick: ($event) => unref(storage).run(transform.id, unref(pending))
81
+ }, null, 8, [
82
+ "icon",
83
+ "label",
84
+ "onClick"
85
+ ]);
86
+ }), 128))], 12, _hoisted_1)) : createCommentVNode("v-if", true);
87
+ };
88
+ }
89
+ });
90
+ //#endregion
91
+ export { _sfc_main as default };
92
+
93
+ //# sourceMappingURL=TransferMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferMenu.js","names":["_unref","_openBlock","_createElementBlock","_normalizeStyle","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createBlock","_createCommentVNode"],"sources":["../../../../src/features/transfer/components/TransferMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { BlockTransform } from '../transforms'\n\n/**\n * The transform menu of a Shift-drop (mounted by the transfer feature): opens at the drop point while a drop waits in\n * `editor.storage.blockDropTarget.pending`, lists the transforms available for it and runs the one picked. Escape or a\n * click elsewhere cancels the drop.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Button: UButton } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst storage = props.editor.storage.blockDropTarget!\nconst pending = useStoreRef(storage.pending)\n\nconst ITEM_HEIGHT = 32\nconst MENU_WIDTH = 240\nconst EDGE = 8\n\nconst transforms = computed<BlockTransform[]>(() => (pending.value ? storage.available(pending.value) : []))\nconst label = (transform: BlockTransform) => (transform.labelKey ? t(transform.labelKey) : transform.label)\n\n/** The menu's top-left corner: at the drop point, kept inside the viewport. */\nconst style = computed(() => {\n const drop = pending.value\n if (!drop) return {}\n const height = transforms.value.length * ITEM_HEIGHT + ITEM_HEIGHT + 2 * EDGE\n const left = Math.max(EDGE, Math.min(drop.x, window.innerWidth - MENU_WIDTH - EDGE))\n const top = Math.max(EDGE, Math.min(drop.y, window.innerHeight - height - EDGE))\n return { left: `${left}px`, top: `${top}px`, width: `${MENU_WIDTH}px` }\n})\n\nconst root = ref<HTMLElement | null>(null)\nconst cancel = () => props.editor.commands.cancelTransfer()\nconst onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n event.preventDefault()\n cancel()\n}\nconst onPointerDown = (event: PointerEvent) => {\n if (!root.value?.contains(event.target as Node)) cancel()\n}\nconst listen = (on: boolean) => {\n if (on) {\n document.addEventListener('keydown', onKey, true)\n document.addEventListener('pointerdown', onPointerDown, true)\n }\n else {\n document.removeEventListener('keydown', onKey, true)\n document.removeEventListener('pointerdown', onPointerDown, true)\n }\n}\nwatch(() => pending.value !== null, listen, { immediate: true })\nonBeforeUnmount(() => listen(false))\n</script>\n\n<template>\n <div\n v-if=\"pending\"\n ref=\"root\"\n class=\"fixed z-50 rounded-lg border border-[var(--sk-border)] bg-[var(--sk-bg)] p-1 shadow-lg\"\n :style=\"style\"\n role=\"menu\"\n :aria-label=\"t('transfer.menu.title')\"\n >\n <p class=\"px-2 py-1 text-xs text-[var(--sk-muted)]\">{{ t('transfer.menu.title') }}</p>\n <UButton\n v-for=\"transform in transforms\"\n :key=\"transform.id\"\n class=\"w-full justify-start\"\n size=\"sm\"\n color=\"neutral\"\n variant=\"ghost\"\n role=\"menuitem\"\n :icon=\"transform.icon\"\n :label=\"label(transform)\"\n @click=\"storage.run(transform.id, pending)\"\n />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,OAAO;;;;;EAVb,MAAM,QAAQ;EAEd,MAAM,EAAE,QAAQ,YAAY,SAAS;EACrC,MAAM,EAAE,MAAM,cAAc;EAE5B,MAAM,UAAU,MAAM,OAAO,QAAQ;EACrC,MAAM,UAAU,YAAY,QAAQ,OAAO;EAM3C,MAAM,aAAa,eAAkC,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,KAAK,IAAI,CAAC,CAAE;EAC3G,MAAM,SAAS,cAA+B,UAAU,WAAW,EAAE,UAAU,QAAQ,IAAI,UAAU;;EAGrG,MAAM,QAAQ,eAAe;GAC3B,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,MAAM,OAAO,CAAC;GACnB,MAAM,SAAS,WAAW,MAAM,SAAS,cAAc,cAAc;GACrE,MAAM,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,GAAG,OAAO,aAAa,aAAa,IAAI,CAAC;GACnF,MAAM,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,GAAG,OAAO,cAAc,SAAS,IAAI,CAAC;GAC/E,OAAO;IAAE,MAAM,GAAG,KAAK;IAAK,KAAK,GAAG,IAAI;IAAK,OAAO,GAAG,WAAW;GAAI;EACxE,CAAC;EAED,MAAM,OAAO,IAAwB,IAAI;EACzC,MAAM,eAAe,MAAM,OAAO,SAAS,eAAe;EAC1D,MAAM,SAAS,UAAyB;GACtC,IAAI,MAAM,QAAQ,UAAU;GAC5B,MAAM,eAAe;GACrB,OAAO;EACT;EACA,MAAM,iBAAiB,UAAwB;GAC7C,IAAI,CAAC,KAAK,OAAO,SAAS,MAAM,MAAc,GAAG,OAAO;EAC1D;EACA,MAAM,UAAU,OAAgB;GAC9B,IAAI,IAAI;IACN,SAAS,iBAAiB,WAAW,OAAO,IAAI;IAChD,SAAS,iBAAiB,eAAe,eAAe,IAAI;GAC9D,OACK;IACH,SAAS,oBAAoB,WAAW,OAAO,IAAI;IACnD,SAAS,oBAAoB,eAAe,eAAe,IAAI;GACjE;EACF;EACA,YAAY,QAAQ,UAAU,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;EAC/D,sBAAsB,OAAO,KAAK,CAAC;;GAKzB,OAAAA,MAAA,OAAA,KADRC,UAAA,GAAAC,mBAqBM,OAAA;;IAnBA,SAAA;IAAJ,KAAI;IACJ,OAAM;IACL,OAAKC,eAAE,MAAA,KAAK;IACb,MAAK;IACJ,cAAYH,MAAA,CAAA,CAAC,CAAA,qBAAA;GAEd,GAAA,CAAAI,mBAAsF,KAAtF,YAAsFC,gBAA/BL,MAAA,CAAA,CAAC,CAAA,qBAAA,CAAA,GAAA,CAAA,IACxDC,UAAA,IAAA,GAAAC,mBAWEI,UAAA,MAAAC,WAVoB,WAAA,QAAb,cAAS;IADlB,OAAAN,UAAA,GAAAO,YAWER,MAAA,OAAA,GAAA;KATC,KAAK,UAAU;KAChB,OAAM;KACN,MAAK;KACL,OAAM;KACN,SAAQ;KACR,MAAK;KACJ,MAAM,UAAU;KAChB,OAAO,MAAM,SAAS;KACtB,UAAK,WAAEA,MAAA,OAAA,CAAO,CAAC,IAAI,UAAU,IAAIA,MAAA,OAAA,CAAO"}
@@ -0,0 +1,15 @@
1
+ import { Editor } from "@tiptap/vue-3";
2
+ //#region src/features/transfer/components/TransferMenu.vue.d.ts
3
+ /**
4
+ * The transform menu of a Shift-drop (mounted by the transfer feature): opens at the drop point while a drop waits in
5
+ * `editor.storage.blockDropTarget.pending`, lists the transforms available for it and runs the one picked. Escape or a
6
+ * click elsewhere cancels the drop.
7
+ */
8
+ type __VLS_Props = {
9
+ editor: Editor;
10
+ };
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ //#endregion
14
+ export { _default as default };
15
+ //# sourceMappingURL=TransferMenu.vue.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { Store } from "../../core/store.js";
2
+ import { EditorFeature } from "../../core/feature.js";
3
+ import { BlockTransfer } from "../../core/block-transfer.js";
4
+ import "../../core/index.js";
5
+ import { BlockTransform } from "./transforms.js";
6
+ import { Node } from "@tiptap/pm/model";
7
+ //#region src/features/transfer/feature.d.ts
8
+ /** One drop of a block: the dragged block, where it lands (document position), whether the transform menu opens first and where it opens (viewport pixels). */
9
+ export interface TransferDrop {
10
+ payload: BlockTransfer;
11
+ pos: number;
12
+ /** `true`: the menu opens at (`x`, `y`) and the user picks the transform; `false`: the default transform runs. */
13
+ menu: boolean;
14
+ x: number;
15
+ y: number;
16
+ }
17
+ /** A drop waiting for the user to pick a transform from the menu (`TransferDrop` with `menu` on). */
18
+ export type PendingDrop = TransferDrop;
19
+ export interface BlockDropTargetStorage {
20
+ /** The drop the transform menu is open for; `null` while no menu is open. */
21
+ pending: Store<PendingDrop | null>;
22
+ /** The transforms the menu offers for `drop` (those whose `available` says yes). */
23
+ available: (drop: PendingDrop) => BlockTransform[];
24
+ /** Closes the menu and runs the transform `id` for `drop` (a refused insert shows its notice). */
25
+ run: (id: string, drop: PendingDrop) => Promise<void>;
26
+ }
27
+ declare module '@tiptap/core' {
28
+ interface Storage {
29
+ /** Absent in an editor without the transfer feature. */
30
+ blockDropTarget?: BlockDropTargetStorage;
31
+ }
32
+ interface Commands<ReturnType> {
33
+ blockTransfer: {
34
+ /**
35
+ * Puts the block of `payload` into the document at `pos` (a position where a block may be inserted): with `menu`
36
+ * the transform menu opens at (`x`, `y`) and the transform the user picks runs, else the default transform runs.
37
+ * Refuses a drop of a block onto its own position in the same document (notice). Used by the document tree's drop executor.
38
+ */
39
+ transferBlock: (options: {
40
+ payload: BlockTransfer;
41
+ pos: number;
42
+ menu: boolean;
43
+ x?: number;
44
+ y?: number;
45
+ }) => ReturnType;
46
+ /** Closes the transform menu without inserting anything. */
47
+ cancelTransfer: () => ReturnType;
48
+ };
49
+ }
50
+ }
51
+ /**
52
+ * Whether dropping `payload` at `pos` of `doc` lands inside the very block it was dragged from: the payload names a
53
+ * block of this document (same `documentId`, a block with that id exists) and `pos` is strictly inside it.
54
+ */
55
+ export declare function isSelfDrop(doc: Node, documentId: string | null | undefined, payload: BlockTransfer, pos: number): boolean;
56
+ /** Options of `transferFeature`. */
57
+ export interface TransferFeatureOptions {
58
+ /** The transforms the menu offers and a drop can run; default `[copyTransform]` (`composeEditor` adds the AI transforms of `@ecsplico/editor/integrations/composite-ai` when `ai` is on). A host list replaces the default (add built-ins explicitly). */
59
+ transforms?: readonly BlockTransform[];
60
+ /** Id of the transform a plain drop (no Shift) runs; default `copy`. */
61
+ defaultTransform?: string;
62
+ }
63
+ /** What a framework binding supplies to `transferFeatureCore`. */
64
+ export interface TransferUi<C> {
65
+ /** The transform menu (receives `{ editor }`), mounted as an overlay. */
66
+ menu: C;
67
+ }
68
+ /**
69
+ * Block transfer between editor surfaces and document trees (the framework-free part of `transferFeature`): a block
70
+ * dragged out of another surface lands as a copy with fresh ids and a `meta.derivation` pointing at its source, or,
71
+ * with Shift held at the drop, as the result of a transform picked from a menu (the copy, and the AI transforms a host or `composeEditor` adds).
72
+ * Insert guards apply; the commands `transferBlock` and `cancelTransfer` serve the document tree's drops.
73
+ */
74
+ export declare function transferFeatureCore<C>(ui: TransferUi<C>, options?: TransferFeatureOptions): EditorFeature<C>;
75
+ //#endregion
76
+ //# sourceMappingURL=feature.d.ts.map
@@ -0,0 +1,169 @@
1
+ import { findBlockById } from "../../core/schema/block-ids.js";
2
+ import { readBlockTransfer } from "../../core/block-transfer.js";
3
+ import { createStore } from "../../core/store.js";
4
+ import { DEFAULT_TRANSFORMS, availableTransforms, runTransform } from "./transforms.js";
5
+ import { Extension } from "@tiptap/core";
6
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
7
+ import { Fragment, Slice } from "@tiptap/pm/model";
8
+ import { dropPoint } from "@tiptap/pm/transform";
9
+ //#region src/features/transfer/feature.ts
10
+ /**
11
+ * Whether dropping `payload` at `pos` of `doc` lands inside the very block it was dragged from: the payload names a
12
+ * block of this document (same `documentId`, a block with that id exists) and `pos` is strictly inside it.
13
+ */
14
+ function isSelfDrop(doc, documentId, payload, pos) {
15
+ if (!payload.blockId || (payload.documentId ?? null) !== (documentId ?? null)) return false;
16
+ const found = findBlockById(doc, payload.blockId);
17
+ return !!found && pos > found.pos && pos < found.pos + found.node.nodeSize;
18
+ }
19
+ /**
20
+ * The shared tail of a drop (DOM drop and `transferBlock`): refuses a self-drop, else opens the menu (`menu`) or runs
21
+ * the default transform.
22
+ */
23
+ function dropBlock(env, documentId, defaultId, pending, drop) {
24
+ const { payload, pos } = drop;
25
+ pending.set(null);
26
+ if (isSelfDrop(env.editor.state.doc, documentId, payload, pos)) {
27
+ env.notify({
28
+ kind: "warning",
29
+ title: env.t("transfer.selfDrop")
30
+ });
31
+ return;
32
+ }
33
+ if (drop.menu) pending.set(drop);
34
+ else queueMicrotask(() => {
35
+ runTransform(env, defaultId, payload, pos);
36
+ });
37
+ }
38
+ const blockDropTargetKey = new PluginKey("blockDropTarget");
39
+ /**
40
+ * The slice dropping `payload` would insert, for `dropPoint`; `null` when this schema cannot hold the block. It is closed
41
+ * (`openStart` and `openEnd` 0): an open slice would let `dropPoint` return a position inside a paragraph, where the
42
+ * Baustein cannot be inserted.
43
+ */
44
+ function sliceOf(editor, payload) {
45
+ try {
46
+ return new Slice(Fragment.from(editor.schema.nodeFromJSON(payload.json)), 0, 0);
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Takes blocks dragged out of another editor surface or document tree (`BLOCK_MIME`): a plain drop runs the default
53
+ * transform, Shift opens the transform menu at the drop point. A drag that started in this editor stays ProseMirror's
54
+ * native move. Also the commands `transferBlock` and `cancelTransfer`.
55
+ */
56
+ function blockDropTarget(ctx, transforms, defaultId) {
57
+ const envOf = (editor) => ({
58
+ editor,
59
+ transforms,
60
+ ai: ctx.backend.ai,
61
+ app: ctx.app,
62
+ notify: ctx.notify,
63
+ t: ctx.t
64
+ });
65
+ let created;
66
+ const editorOf = () => {
67
+ if (!created) throw new Error("blockDropTarget: the editor is not created yet");
68
+ return created;
69
+ };
70
+ return Extension.create({
71
+ name: "blockDropTarget",
72
+ onBeforeCreate() {
73
+ created = this.editor;
74
+ },
75
+ addStorage() {
76
+ const pending = createStore(null);
77
+ return {
78
+ pending,
79
+ available: ({ payload, pos }) => availableTransforms(envOf(editorOf()), payload, pos),
80
+ run: async (id, { payload, pos }) => {
81
+ pending.set(null);
82
+ await runTransform(envOf(editorOf()), id, payload, pos);
83
+ }
84
+ };
85
+ },
86
+ addCommands() {
87
+ return {
88
+ transferBlock: ({ payload, pos, menu, x = 0, y = 0 }) => ({ editor, dispatch }) => {
89
+ if (!dispatch) return true;
90
+ dropBlock(envOf(editor), ctx.documentId ?? null, defaultId, this.storage.pending, {
91
+ payload,
92
+ pos: Math.min(Math.max(pos, 0), editor.state.doc.content.size),
93
+ menu,
94
+ x,
95
+ y
96
+ });
97
+ return true;
98
+ },
99
+ cancelTransfer: () => () => {
100
+ this.storage.pending.set(null);
101
+ return true;
102
+ }
103
+ };
104
+ },
105
+ addProseMirrorPlugins() {
106
+ const editor = this.editor;
107
+ return [new Plugin({
108
+ key: blockDropTargetKey,
109
+ props: {
110
+ handleDOMEvents: { dragover: (view, event) => {
111
+ if (view.dragging || !view.editable || !event.dataTransfer?.types.includes("application/x-tip-block")) return false;
112
+ event.preventDefault();
113
+ event.dataTransfer.dropEffect = "copy";
114
+ return false;
115
+ } },
116
+ handleDrop: (view, event) => {
117
+ if (view.dragging || !view.editable || !event.dataTransfer) return false;
118
+ const payload = readBlockTransfer(event.dataTransfer);
119
+ if (!payload) return false;
120
+ event.preventDefault();
121
+ const env = envOf(editor);
122
+ const slice = sliceOf(editor, payload);
123
+ if (!slice) {
124
+ env.notify({
125
+ kind: "warning",
126
+ title: env.t("transfer.refused")
127
+ });
128
+ return true;
129
+ }
130
+ const at = view.posAtCoords({
131
+ left: event.clientX,
132
+ top: event.clientY
133
+ })?.pos ?? view.state.doc.content.size;
134
+ const pos = dropPoint(view.state.doc, at, slice) ?? view.state.doc.content.size;
135
+ dropBlock(env, ctx.documentId ?? null, defaultId, this.storage.pending, {
136
+ payload,
137
+ pos,
138
+ menu: event.shiftKey,
139
+ x: event.clientX,
140
+ y: event.clientY
141
+ });
142
+ return true;
143
+ }
144
+ }
145
+ })];
146
+ }
147
+ });
148
+ }
149
+ /**
150
+ * Block transfer between editor surfaces and document trees (the framework-free part of `transferFeature`): a block
151
+ * dragged out of another surface lands as a copy with fresh ids and a `meta.derivation` pointing at its source, or,
152
+ * with Shift held at the drop, as the result of a transform picked from a menu (the copy, and the AI transforms a host or `composeEditor` adds).
153
+ * Insert guards apply; the commands `transferBlock` and `cancelTransfer` serve the document tree's drops.
154
+ */
155
+ function transferFeatureCore(ui, options = {}) {
156
+ const transforms = options.transforms ?? DEFAULT_TRANSFORMS;
157
+ const defaultId = options.defaultTransform ?? "copy";
158
+ return {
159
+ id: "transfer",
160
+ dimension: "interaction",
161
+ requires: ["composites"],
162
+ extensions: (ctx) => [blockDropTarget(ctx, transforms, defaultId)],
163
+ overlays: [ui.menu]
164
+ };
165
+ }
166
+ //#endregion
167
+ export { isSelfDrop, transferFeatureCore };
168
+
169
+ //# sourceMappingURL=feature.js.map