@ecsplico/editor 3.2.0 → 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 (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/code/CodeSurface.vue.d.ts +3 -3
  3. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  4. package/dist/features/ai/protocol/generate.d.ts +1 -1
  5. package/dist/features/composites/components/CompositeView.js +5 -1
  6. package/dist/features/composites/components/CompositeView.js.map +1 -1
  7. package/dist/features/composites/feature.js +12 -1
  8. package/dist/features/composites/feature.js.map +1 -1
  9. package/dist/features/composites/index.d.ts +2 -1
  10. package/dist/features/composites/index.js +2 -1
  11. package/dist/features/composites/kinds.d.ts +6 -1
  12. package/dist/features/composites/kinds.js.map +1 -1
  13. package/dist/features/composites/merge.d.ts +24 -0
  14. package/dist/features/composites/merge.js +47 -0
  15. package/dist/features/composites/merge.js.map +1 -0
  16. package/dist/features/composites/nodes.d.ts +1 -0
  17. package/dist/features/composites/nodes.js +1 -1
  18. package/dist/features/composites/nodes.js.map +1 -1
  19. package/dist/features/composites/react/CompositeView.js +4 -1
  20. package/dist/features/composites/react/CompositeView.js.map +1 -1
  21. package/dist/i18n/de/composites.js +1 -0
  22. package/dist/i18n/de/composites.js.map +1 -1
  23. package/dist/i18n/de/tree.js +1 -0
  24. package/dist/i18n/de/tree.js.map +1 -1
  25. package/dist/i18n/de.d.ts +2 -0
  26. package/dist/i18n/en/composites.js +1 -0
  27. package/dist/i18n/en/composites.js.map +1 -1
  28. package/dist/i18n/en/tree.js +1 -0
  29. package/dist/i18n/en/tree.js.map +1 -1
  30. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  31. package/dist/nuxt/module.d.ts +4 -0
  32. package/dist/server/handlers/ai.d.ts +3 -3
  33. package/dist/styles/composites.css +10 -1
  34. package/dist/tree/DocumentTree.js +20 -5
  35. package/dist/tree/DocumentTree.js.map +1 -1
  36. package/dist/tree/react/DocumentTree.js +12 -1
  37. package/dist/tree/react/DocumentTree.js.map +1 -1
  38. package/dist/tree/tree-host.js +10 -0
  39. package/dist/tree/tree-host.js.map +1 -1
  40. package/dist/tree/tree-items.js +1 -0
  41. package/dist/tree/tree-items.js.map +1 -1
  42. package/dist/tree/tree-model.js +9 -0
  43. package/dist/tree/tree-model.js.map +1 -1
  44. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  45. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  46. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.3.0 - 2026-10-21
4
+
5
+ ### Added
6
+
7
+ - Kind tone: `CompositeKind.chrome.tone` (a CSS colour or `var(...)`). A Baustein of such a kind gets `data-kind-tone` and `--sk-kind-tone` on its wrapper (Vue and React `CompositeView`), a tinted surface and a left border in the tone (`composites.css`; strength `--sk-kind-tone-mix`, `8%` under `:where(:root)`, `14%` under `:where(.dark)`), and its document-tree rows tint the kind icon (`TreeItem.tone`). Kinds without a tone are unchanged.
8
+ - Merging a Baustein with the next one (`@ecsplico/editor/composites`): the command `mergeCompositeWithNext(pos?)` makes a Baustein absorb the Baustein that directly follows it (body and sub-Bausteine appended; the first keeps title, description, tags, id, kind and meta) in one undo step, refused without a following Baustein, when the first kind does not accept a nested kind, or when read-only. Block action „Mit nächstem zusammenführen“, a merge button on `DocumentTree` rows (Vue and React; `TreeCommand` `{ type: 'mergeBlock', id }`, `TreeModel.canMerge(item)` / `merge(item)`; a host-built `TreeEditor` must now provide `canMerge(id)`), exported `CompositeMerge` and `mergeBody`.
9
+
3
10
  ## 3.2.0 - 2026-10-20
4
11
 
5
12
  ### Added
@@ -11,16 +11,16 @@ type __VLS_Props = {
11
11
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
12
12
  view: import("vue").ShallowRef<EditorView | undefined, EditorView | undefined>;
13
13
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
- "update:modelValue": (text: string) => any;
15
14
  ready: (view: EditorView) => any;
15
+ "update:modelValue": (text: string) => any;
16
16
  save: (text: string) => any;
17
17
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
- "onUpdate:modelValue"?: ((text: string) => any) | undefined;
19
18
  onReady?: ((view: EditorView) => any) | undefined;
19
+ "onUpdate:modelValue"?: ((text: string) => any) | undefined;
20
20
  onSave?: ((text: string) => any) | undefined;
21
21
  }>, {
22
- editable: boolean;
23
22
  modelValue: string;
23
+ editable: boolean;
24
24
  wordWrap: boolean;
25
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
26
  declare const _default: typeof __VLS_export;
@@ -32,16 +32,16 @@ type __VLS_Slots = {} & { [K in NonNullable<typeof __VLS_13>]?: (props: typeof _
32
32
  binary?: (props: typeof __VLS_26) => any;
33
33
  };
34
34
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
35
- "update:modelValue": (text: string) => any;
36
35
  ready: (target: DocumentSurfaceTarget) => any;
36
+ "update:modelValue": (text: string) => any;
37
37
  save: (text: string) => any;
38
38
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
- "onUpdate:modelValue"?: ((text: string) => any) | undefined;
40
39
  onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
40
+ "onUpdate:modelValue"?: ((text: string) => any) | undefined;
41
41
  onSave?: ((text: string) => any) | undefined;
42
42
  }>, {
43
- editable: boolean;
44
43
  modelValue: string;
44
+ editable: boolean;
45
45
  wordWrap: boolean;
46
46
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
47
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -17,8 +17,8 @@ export type ChatStream = (messages: ChatMessage[], signal: AbortSignal) => Async
17
17
  export declare const generateBodySchema: z.ZodObject<{
18
18
  task: z.ZodEnum<{
19
19
  custom: "custom";
20
- continue: "continue";
21
20
  generate: "generate";
21
+ continue: "continue";
22
22
  reformulate: "reformulate";
23
23
  "transcript-summary": "transcript-summary";
24
24
  "transcript-minutes": "transcript-minutes";
@@ -19,6 +19,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
19
19
  });
20
20
  const basket = computed(() => basketOf(props.node));
21
21
  const basketColor = computed(() => compositeBasketsOf(props.editor).find((candidate) => candidate.id === basket.value)?.color);
22
+ const kindTone = computed(() => kind.value.chrome?.tone || void 0);
22
23
  const revealedTitles = useStoreRef(() => props.editor.storage.composite.revealedTitles);
23
24
  const titleHidden = computed(() => {
24
25
  const id = props.node.attrs.id;
@@ -28,7 +29,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
28
29
  const cssString = (text) => `"${text.replace(/["\\]/g, "\\$&").replace(/\n/g, "\\a ")}"`;
29
30
  const placeholderVars = computed(() => ({
30
31
  "--composite-title-placeholder": cssString(kind.value.titlePlaceholder),
31
- ...basketColor.value ? { "--sk-basket-color": basketColor.value } : {}
32
+ ...basketColor.value ? { "--sk-basket-color": basketColor.value } : {},
33
+ ...kindTone.value ? { "--sk-kind-tone": kindTone.value } : {}
32
34
  }));
33
35
  return (_ctx, _cache) => {
34
36
  return openBlock(), createBlock(unref(NodeViewWrapper), {
@@ -41,6 +43,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
41
43
  "data-kind": _ctx.node.attrs.kind,
42
44
  "data-id": _ctx.node.attrs.id || void 0,
43
45
  "data-basket": basket.value || void 0,
46
+ "data-kind-tone": kindTone.value ? "" : void 0,
44
47
  "data-title-hidden": titleHidden.value ? "" : void 0,
45
48
  style: normalizeStyle(placeholderVars.value)
46
49
  }, {
@@ -59,6 +62,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
59
62
  "data-kind",
60
63
  "data-id",
61
64
  "data-basket",
65
+ "data-kind-tone",
62
66
  "data-title-hidden",
63
67
  "style"
64
68
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeView.js","names":["selected","node","_openBlock","_createBlock","_unref","_normalizeClass","_normalizeStyle","_withCtx","_createCommentVNode","_createElementVNode","_createVNode"],"sources":["../../../../src/features/composites/components/CompositeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewContent, NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed } from 'vue'\nimport { basketOf, titleHiddenOf } from '../baskets'\nimport { compositeKind } from '../kinds'\nimport { compositeBasketsOf, compositeKindsOf } from '../nodes'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { derivationOf } from '../derivation'\n\nconst props = defineProps(nodeViewProps)\n\nconst kind = computed(() => compositeKind(compositeKindsOf(props.editor), String(props.node.attrs.kind)))\n\n// A derived Baustein (one with a `meta.derivation`) shows collapsed to a faded preview until it is opened; the content\n// stays laid out (CSS clips it), so editing positions are unaffected. The chrome's chevron and a caret inside open it.\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\nconst collapsed = computed(() => {\n const id: unknown = props.node.attrs.id\n return typeof id === 'string' && id !== '' && derivationOf(props.node) !== null && !expanded.value.has(id)\n})\n\n// Colour basket (`meta.basket`): `data-basket` carries the id, the stripe takes the basket's colour from `--sk-basket-color`.\nconst basket = computed(() => basketOf(props.node))\nconst basketColor = computed(() => compositeBasketsOf(props.editor).find(candidate => candidate.id === basket.value)?.color)\n\n// A hidden title (`meta.titleHidden`) stays hidden until a caret enters it (`revealedTitles`, see `CompositeNode`).\nconst revealedTitles = useStoreRef(() => props.editor.storage.composite.revealedTitles)\nconst titleHidden = computed(() => {\n const id: unknown = props.node.attrs.id\n return titleHiddenOf(props.node) && !(typeof id === 'string' && revealedTitles.value.has(id))\n})\n\n/** `text` as a CSS string literal (for `content:`). */\nconst cssString = (text: string) => `\"${text.replace(/[\"\\\\]/g, '\\\\$&').replace(/\\n/g, '\\\\a ')}\"`\n// Placeholder of the empty title (composites.css `content: var(…)`); a nested composite sets its own.\nconst placeholderVars = computed(() => ({\n '--composite-title-placeholder': cssString(kind.value.titlePlaceholder),\n ...(basketColor.value ? { '--sk-basket-color': basketColor.value } : {}),\n}))\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"section\"\n :class=\"['composite', { 'is-collapsed': collapsed, 'is-selected rounded-md ring-1 ring-[var(--sk-primary)]': selected }]\"\n data-type=\"composite\"\n :data-kind=\"node.attrs.kind\"\n :data-id=\"node.attrs.id || undefined\"\n :data-basket=\"basket || undefined\"\n :data-title-hidden=\"titleHidden ? '' : undefined\"\n :style=\"placeholderVars\"\n >\n <!-- Anchor of the kind icon on the left border: it only takes the icon's place and size. The composite chrome overlay\n measures it and draws the icon as the button that opens the details popover and drags the composite. -->\n <span class=\"composite__toggle\" contenteditable=\"false\" aria-hidden=\"true\" />\n <NodeViewContent class=\"composite__content\" />\n </NodeViewWrapper>\n</template>\n"],"mappings":";;;;;;;;;;;;EASA,MAAM,QAAQ;EAEd,MAAM,OAAO,eAAe,cAAc,iBAAiB,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;EAIxG,MAAM,WAAW,kBAAkB,MAAM,OAAO,QAAQ,UAAU,QAAQ;EAC1E,MAAM,YAAY,eAAe;GAC/B,MAAM,KAAc,MAAM,KAAK,MAAM;GACrC,OAAO,OAAO,OAAO,YAAY,OAAO,MAAM,aAAa,MAAM,IAAI,MAAM,QAAQ,CAAC,SAAS,MAAM,IAAI,EAAE;EAC3G,CAAC;EAGD,MAAM,SAAS,eAAe,SAAS,MAAM,IAAI,CAAC;EAClD,MAAM,cAAc,eAAe,mBAAmB,MAAM,MAAM,CAAC,CAAC,MAAK,cAAa,UAAU,OAAO,OAAO,KAAK,CAAC,EAAE,KAAK;EAG3H,MAAM,iBAAiB,kBAAkB,MAAM,OAAO,QAAQ,UAAU,cAAc;EACtF,MAAM,cAAc,eAAe;GACjC,MAAM,KAAc,MAAM,KAAK,MAAM;GACrC,OAAO,cAAc,MAAM,IAAI,KAAK,EAAE,OAAO,OAAO,YAAY,eAAe,MAAM,IAAI,EAAE;EAC7F,CAAC;;EAGD,MAAM,aAAa,SAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM,CAAC,CAAC,QAAQ,OAAO,MAAM,EAAE;EAE9F,MAAM,kBAAkB,gBAAgB;GACtC,iCAAiC,UAAU,KAAK,MAAM,gBAAgB;GACtE,GAAI,YAAY,QAAQ,EAAE,qBAAqB,YAAY,MAAM,IAAI,CAAC;EACxE,EAAE;;GAIA,OAAAE,UAAA,GAAAC,YAckBC,MAAA,eAAA,GAAA;IAbhB,IAAG;IACF,OAAKC,eAAA,CAAA,aAAA;KAAA,gBAAkC,UAAA;KAAS,0DAA4DL,KAAAA;IAAQ,CAAA,CAAA;IACrH,aAAU;IACT,aAAWC,KAAAA,KAAK,MAAM;IACtB,WAASA,KAAAA,KAAK,MAAM,MAAM,KAAA;IAC1B,eAAa,OAAA,SAAU,KAAA;IACvB,qBAAmB,YAAA,QAAW,KAAQ,KAAA;IACtC,OAAKK,eAAE,gBAAA,KAAe;;IAEvB,SAAAC,cAC6G;KAD7GC,mBAAA,oOAAA;KAEA,OAAA,OAAA,OAAA,KAAAC,mBAA6E,QAAA;MAAvE,OAAM;MAAoB,iBAAgB;MAAQ,eAAY;;KACpEC,YAA8CN,MAAA,eAAA,GAAA,EAA7B,OAAM,qBAAoB,CAAA"}
1
+ {"version":3,"file":"CompositeView.js","names":["selected","node","_openBlock","_createBlock","_unref","_normalizeClass","_normalizeStyle","_withCtx","_createCommentVNode","_createElementVNode","_createVNode"],"sources":["../../../../src/features/composites/components/CompositeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewContent, NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed } from 'vue'\nimport { basketOf, titleHiddenOf } from '../baskets'\nimport { compositeKind } from '../kinds'\nimport { compositeBasketsOf, compositeKindsOf } from '../nodes'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { derivationOf } from '../derivation'\n\nconst props = defineProps(nodeViewProps)\n\nconst kind = computed(() => compositeKind(compositeKindsOf(props.editor), String(props.node.attrs.kind)))\n\n// A derived Baustein (one with a `meta.derivation`) shows collapsed to a faded preview until it is opened; the content\n// stays laid out (CSS clips it), so editing positions are unaffected. The chrome's chevron and a caret inside open it.\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\nconst collapsed = computed(() => {\n const id: unknown = props.node.attrs.id\n return typeof id === 'string' && id !== '' && derivationOf(props.node) !== null && !expanded.value.has(id)\n})\n\n// Colour basket (`meta.basket`): `data-basket` carries the id, the stripe takes the basket's colour from `--sk-basket-color`.\nconst basket = computed(() => basketOf(props.node))\nconst basketColor = computed(() => compositeBasketsOf(props.editor).find(candidate => candidate.id === basket.value)?.color)\n\n// Kind tone (`CompositeKind.chrome.tone`): `data-kind-tone` marks the wrapper, the surface reads the colour from `--sk-kind-tone`.\nconst kindTone = computed(() => kind.value.chrome?.tone || undefined)\n\n// A hidden title (`meta.titleHidden`) stays hidden until a caret enters it (`revealedTitles`, see `CompositeNode`).\nconst revealedTitles = useStoreRef(() => props.editor.storage.composite.revealedTitles)\nconst titleHidden = computed(() => {\n const id: unknown = props.node.attrs.id\n return titleHiddenOf(props.node) && !(typeof id === 'string' && revealedTitles.value.has(id))\n})\n\n/** `text` as a CSS string literal (for `content:`). */\nconst cssString = (text: string) => `\"${text.replace(/[\"\\\\]/g, '\\\\$&').replace(/\\n/g, '\\\\a ')}\"`\n// Placeholder of the empty title (composites.css `content: var(…)`); a nested composite sets its own.\nconst placeholderVars = computed(() => ({\n '--composite-title-placeholder': cssString(kind.value.titlePlaceholder),\n ...(basketColor.value ? { '--sk-basket-color': basketColor.value } : {}),\n ...(kindTone.value ? { '--sk-kind-tone': kindTone.value } : {}),\n}))\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"section\"\n :class=\"['composite', { 'is-collapsed': collapsed, 'is-selected rounded-md ring-1 ring-[var(--sk-primary)]': selected }]\"\n data-type=\"composite\"\n :data-kind=\"node.attrs.kind\"\n :data-id=\"node.attrs.id || undefined\"\n :data-basket=\"basket || undefined\"\n :data-kind-tone=\"kindTone ? '' : undefined\"\n :data-title-hidden=\"titleHidden ? '' : undefined\"\n :style=\"placeholderVars\"\n >\n <!-- Anchor of the kind icon on the left border: it only takes the icon's place and size. The composite chrome overlay\n measures it and draws the icon as the button that opens the details popover and drags the composite. -->\n <span class=\"composite__toggle\" contenteditable=\"false\" aria-hidden=\"true\" />\n <NodeViewContent class=\"composite__content\" />\n </NodeViewWrapper>\n</template>\n"],"mappings":";;;;;;;;;;;;EASA,MAAM,QAAQ;EAEd,MAAM,OAAO,eAAe,cAAc,iBAAiB,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;EAIxG,MAAM,WAAW,kBAAkB,MAAM,OAAO,QAAQ,UAAU,QAAQ;EAC1E,MAAM,YAAY,eAAe;GAC/B,MAAM,KAAc,MAAM,KAAK,MAAM;GACrC,OAAO,OAAO,OAAO,YAAY,OAAO,MAAM,aAAa,MAAM,IAAI,MAAM,QAAQ,CAAC,SAAS,MAAM,IAAI,EAAE;EAC3G,CAAC;EAGD,MAAM,SAAS,eAAe,SAAS,MAAM,IAAI,CAAC;EAClD,MAAM,cAAc,eAAe,mBAAmB,MAAM,MAAM,CAAC,CAAC,MAAK,cAAa,UAAU,OAAO,OAAO,KAAK,CAAC,EAAE,KAAK;EAG3H,MAAM,WAAW,eAAe,KAAK,MAAM,QAAQ,QAAQ,KAAA,CAAS;EAGpE,MAAM,iBAAiB,kBAAkB,MAAM,OAAO,QAAQ,UAAU,cAAc;EACtF,MAAM,cAAc,eAAe;GACjC,MAAM,KAAc,MAAM,KAAK,MAAM;GACrC,OAAO,cAAc,MAAM,IAAI,KAAK,EAAE,OAAO,OAAO,YAAY,eAAe,MAAM,IAAI,EAAE;EAC7F,CAAC;;EAGD,MAAM,aAAa,SAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM,CAAC,CAAC,QAAQ,OAAO,MAAM,EAAE;EAE9F,MAAM,kBAAkB,gBAAgB;GACtC,iCAAiC,UAAU,KAAK,MAAM,gBAAgB;GACtE,GAAI,YAAY,QAAQ,EAAE,qBAAqB,YAAY,MAAM,IAAI,CAAC;GACtE,GAAI,SAAS,QAAQ,EAAE,kBAAkB,SAAS,MAAM,IAAI,CAAC;EAC/D,EAAE;;GAIA,OAAAE,UAAA,GAAAC,YAekBC,MAAA,eAAA,GAAA;IAdhB,IAAG;IACF,OAAKC,eAAA,CAAA,aAAA;KAAA,gBAAkC,UAAA;KAAS,0DAA4DL,KAAAA;IAAQ,CAAA,CAAA;IACrH,aAAU;IACT,aAAWC,KAAAA,KAAK,MAAM;IACtB,WAASA,KAAAA,KAAK,MAAM,MAAM,KAAA;IAC1B,eAAa,OAAA,SAAU,KAAA;IACvB,kBAAgB,SAAA,QAAQ,KAAQ,KAAA;IAChC,qBAAmB,YAAA,QAAW,KAAQ,KAAA;IACtC,OAAKK,eAAE,gBAAA,KAAe;;IAEvB,SAAAC,cAC6G;KAD7GC,mBAAA,oOAAA;KAEA,OAAA,OAAA,OAAA,KAAAC,mBAA6E,QAAA;MAAvE,OAAM;MAAoB,iBAAgB;MAAQ,eAAY;;KACpEC,YAA8CN,MAAA,eAAA,GAAA,EAA7B,OAAM,qBAAoB,CAAA"}
@@ -19,6 +19,7 @@ import { AcceptedChildren, compositeInsertGuard } from "./accepted-children.js";
19
19
  import { documentCompositeMarkdown, markdownFileName } from "./markdown-export.js";
20
20
  import { compositeNodes } from "./node-views.js";
21
21
  import { CompositeFocus } from "./focus.js";
22
+ import { CompositeMerge } from "./merge.js";
22
23
  import { REFERENCE_AI_BLOCK } from "./reference-ai.js";
23
24
  import { referenceSuggestion } from "./reference-suggestion.js";
24
25
  import { SourceReveal } from "./reveal-source.js";
@@ -64,6 +65,7 @@ function compositeFeatureCore(ui, options = {}) {
64
65
  }),
65
66
  SourceReveal,
66
67
  CompositeFocus,
68
+ CompositeMerge,
67
69
  CompositeSplit.configure({
68
70
  defaultKind: () => ctx.settings.get().blocks.defaultKind,
69
71
  targetChars: () => ctx.settings.get().blocks.splitTargetChars
@@ -103,7 +105,16 @@ function compositeFeatureCore(ui, options = {}) {
103
105
  if (id) editor.commands.focusComposite(id);
104
106
  }
105
107
  },
106
- ...splitBlockActions(ctx)
108
+ ...splitBlockActions(ctx),
109
+ {
110
+ id: "composite-merge-next",
111
+ label: ctx.t("composites.merge.next"),
112
+ icon: "i-lucide-merge",
113
+ ability: ABILITY.compositeMeta,
114
+ order: 470,
115
+ isVisible: ({ editor, pos }) => editor.can().mergeCompositeWithNext(pos),
116
+ run: ({ editor, pos }) => void editor.chain().focus().mergeCompositeWithNext(pos).run()
117
+ }
107
118
  ],
108
119
  overlays: [ui.chrome, ui.focusChip].filter((overlay) => overlay !== void 0),
109
120
  settingsTabs: (ctx) => [{
@@ -1 +1 @@
1
- {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/composites/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { BlockAction, CommandDef, EditorFeature, FeatureContext, MenuItem, ToolbarGroupDef } from '../../core'\nimport { abilitiesAtSelection, downloadText, editorEntryOf, settingsTabsOf } from '../../core'\nimport type { Translate } from '../../i18n'\nimport { ABILITY, blockIdOf, findBlockById } from '../../core/schema'\nimport { AcceptedChildren, compositeInsertGuard } from './accepted-children'\nimport { type Basket, defaultBaskets } from './baskets'\nimport { bibOf, bibtexOf, cslOf } from './bibliography'\nimport { compositeConversions } from './convert'\nimport { bodyMarkdown, derivationOf } from './derivation'\nimport { blocksFromMarkdown } from './fences'\nimport { structureBlocks } from './heading-composites'\nimport { type CompositeKind, compositeKind, defaultCompositeKinds } from './kinds'\nimport { migrateLegacyContent } from './legacy'\nimport { compositeSchemaExtensions } from './schema'\nimport { documentCompositeMarkdown, markdownFileName } from './markdown-export'\nimport { type CompositeNodeViews, compositeNodes } from './node-views'\nimport { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeTitleNode, compositeAround, compositeKindsOf } from './nodes'\nimport { CompositeFocus } from './focus'\nimport { REFERENCE_TRIGGER } from './reference'\nimport { REFERENCE_AI_BLOCK } from './reference-ai'\nimport { referenceSuggestion } from './reference-suggestion'\nimport { compositeIdsOf, normalizeReferences } from './reference-text'\nimport { SourceReveal } from './reveal-source'\nimport { CompositeSplit, type SplitMode } from './split'\nimport { Structure, structureOf } from './structure'\nimport { STRUCTURE_MODE_KEYS } from './structure-mode'\n\n/** What a framework binding supplies to the composites feature. */\nexport interface CompositesUi<C> {\n /** The chips on each title line and the details popover on the kind icon; mounted as an overlay. */\n chrome: C\n /** The focus chip („Fokus: <Titel> · Aufheben“) shown while a Baustein is focused; mounted as an overlay. Absent: no chip (the focus mode itself works). */\n focusChip?: C\n /** The „Bearbeiten“ settings tab; receives `{ kinds }` as props. */\n settingsTab: C\n /** The composite block view of the framework. */\n nodeViews: CompositeNodeViews\n}\n\n/** Options of `compositeFeature`. */\nexport interface CompositeFeatureOptions {\n /** The composite kinds („Bausteine“) offered in the editor, with their own labels (default: `defaultCompositeKinds(t)`, the built-in kinds in the editor's language); read once. */\n kinds?: readonly CompositeKind[]\n /** The colour baskets a Baustein can be put into (default: `defaultBaskets(t)`, red/orange/yellow/green/blue in the editor's language; colours are the `--sk-basket-<id>` CSS variables); read once. */\n baskets?: readonly Basket[]\n}\n\n/** Heading level the structure stage starts at when it turns composites into headings (below the document title). */\nconst STRUCTURE_STAGE_LEVEL = 2\n\n/**\n * Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,\n * „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),\n * Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,\n * the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), focus mode on one\n * Baustein (`CompositeFocus`, the „Fokussieren“ block action and the binding's `focusChip` overlay), and\n * the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein\n * into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,\n * `structure` and `migrateContent` stages. Imports no AI, media or transcript code.\n */\nexport function compositeFeatureCore<C>(ui: CompositesUi<C>, options: CompositeFeatureOptions = {}): EditorFeature<C> {\n // Host kinds keep their labels; the built-in ones are produced from the context's translator (memoized per `t`).\n const kindsOf = (ctx: { t: Translate }) => options.kinds ?? defaultCompositeKinds(ctx.t)\n const basketsOf = (ctx: { t: Translate }) => options.baskets ?? defaultBaskets(ctx.t)\n const { Composite, CompositeTags, Reference } = compositeNodes(ui.nodeViews)\n return {\n id: 'composites',\n dimension: 'content',\n schemaExtensions: () => compositeSchemaExtensions({ ...(options.kinds && { kinds: options.kinds }), ...(options.baskets && { baskets: options.baskets }) }),\n extensions: ctx => [\n Composite.configure({ kinds: kindsOf(ctx), baskets: basketsOf(ctx) }),\n CompositeTitleNode,\n CompositeDescriptionNode,\n CompositeTags,\n Structure.configure({ mode: () => ctx.settings.get().blocks.structure }),\n AcceptedChildren.configure({ notify: ctx.notify, t: ctx.t }),\n SourceReveal,\n CompositeFocus,\n CompositeSplit.configure({ defaultKind: () => ctx.settings.get().blocks.defaultKind, targetChars: () => ctx.settings.get().blocks.splitTargetChars }),\n Reference.configure({ suggestion: referenceSuggestion(ctx.t) }),\n ],\n toolbarGroups: COMPOSITE_TOOLBAR_GROUPS,\n commands: ctx => compositeCommands(ctx, kindsOf(ctx)),\n insertGuards: () => [compositeInsertGuard],\n blockActions: ctx => [{\n id: 'document-export',\n label: ctx.t('composites.blockAction.export'),\n icon: 'i-lucide-download',\n ability: ABILITY.compositeExport,\n order: 400,\n isVisible: ({ node }) => node.attrs.kind === 'document',\n run: ({ editor, node }) => downloadText(markdownFileName(node), documentCompositeMarkdown(editor, node)),\n }, {\n id: 'bibliography-export',\n label: ctx.t('composites.blockAction.bibtex'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeExport,\n order: 410,\n isVisible: ({ node }) => bibOf(node) !== null,\n run: ({ node }) => downloadText(`${markdownFileName(node).replace(/\\.md$/, '')}.bib`, bibtexOf(cslOf(node)!), 'text/x-bibtex;charset=utf-8'),\n }, {\n id: 'composite-focus',\n label: ctx.t('composites.focus.action'),\n icon: 'i-lucide-focus',\n order: 420,\n isVisible: ({ node }) => node.type.name === 'composite',\n run: ({ editor, node }) => {\n const id = blockIdOf(node)\n if (id) editor.commands.focusComposite(id)\n },\n }, ...splitBlockActions(ctx)],\n overlays: [ui.chrome, ui.focusChip].filter(overlay => overlay !== undefined),\n settingsTabs: ctx => [{ id: 'editing', label: ctx.t('composites.tab'), icon: 'i-lucide-pencil', component: ui.settingsTab, props: { kinds: kindsOf(ctx) } }],\n blocks: {\n parseMarkdown: (editor, markdown) => blocksFromMarkdown(md => editor.markdown!.parse(md), markdown, compositeKindsOf(editor)),\n structure: (editor, blocks) => {\n const { schema } = editor\n return structureBlocks(schema, blocks.map(block => schema.nodeFromJSON(block)), structureOf(editor), STRUCTURE_STAGE_LEVEL, compositeKindsOf(editor)).map(node => node.toJSON())\n },\n migrateContent: migrateLegacyContent,\n normalize: (editor, blocks) => normalizeReferences(blocks, compositeIdsOf(editor.state.doc)),\n },\n conversions: compositeConversions(options.kinds),\n aiContext: editor => ({\n kinds: compositeKindsOf(editor).map(({ id, label, fields, ai }) => ({ id, label, fields, ...(ai?.purpose && { purpose: ai.purpose }) })),\n structure: structureOf(editor),\n blocks: [REFERENCE_AI_BLOCK],\n }),\n // A Baustein derived from another block hands the model that source block's Markdown.\n aiBlockData: ({ editor, node }) => {\n const from = derivationOf(node)?.from\n const source = from && 'block' in from ? findBlockById(editor.state.doc, from.block)?.node : undefined\n // i18n-ignore: model-facing data label (German prompt text, not UI)\n return source?.type.name === 'composite' ? [{ label: `Quelle: ${source.child(0).textContent.trim()}`, text: bodyMarkdown(editor, source) }] : []\n },\n }\n}\n\n/**\n * The four split actions of a Baustein: at the caret (as sibling or sub-Bausteine; offered on the Baustein around the\n * caret while the caret is past the first body block) and by length (as sub-Bausteine or siblings; offered while the\n * body falls into two parts or more). Each is hidden when the command refuses, so a kind that accepts no child kind\n * never shows the `children` variants.\n */\nfunction splitBlockActions({ t }: FeatureContext): BlockAction[] {\n const atCaret = (id: string, labelKey: 'composites.split.here' | 'composites.split.children', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => {\n const { $head } = editor.state.selection\n const found = compositeAround($head)\n return found?.pos === pos && $head.index(found.depth) > COMPOSITE_CHROME_COUNT && editor.can().splitCompositeAtCaret({ mode })\n },\n run: ({ editor }) => void editor.chain().focus().splitCompositeAtCaret({ mode }).run(),\n })\n const byLength = (id: string, labelKey: 'composites.split.lengthChildren' | 'composites.split.lengthSiblings', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => editor.can().splitCompositeByLength({ mode, pos }),\n run: ({ editor, pos }) => void editor.chain().focus().splitCompositeByLength({ mode, pos }).run(),\n })\n return [\n atCaret('composite-split-here', 'composites.split.here', 'i-lucide-scissors', 430, 'siblings'),\n atCaret('composite-split-children', 'composites.split.children', 'i-lucide-split', 440, 'children'),\n byLength('composite-split-length-children', 'composites.split.lengthChildren', 'i-lucide-ruler', 450, 'children'),\n byLength('composite-split-length-siblings', 'composites.split.lengthSiblings', 'i-lucide-ruler', 460, 'siblings'),\n ]\n}\n\nconst COMPOSITE_TOOLBAR_GROUPS: ToolbarGroupDef[] = [{ id: 'composites', order: 60, labelKey: 'palette.group.composites' }]\n\n/**\n * Converting headings to Bausteine and back: the checks and actions the toolbar's „Gliederung umwandeln“ menu and the\n * `/gliedern*` and `/aufloesen*` commands share, so both placements gate and act identically. They read the scope and\n * default kind from the settings at call time.\n */\nfunction structureActions({ settings }: FeatureContext) {\n const groupOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope, kind: settings.get().blocks.defaultKind })\n const flattenOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope })\n return {\n canGroup: (editor: Editor, recursive: boolean) => editor.can().convertHeadingsToComposites(groupOptions(recursive)),\n canFlatten: (editor: Editor, recursive: boolean) => editor.can().convertCompositesToHeadings(flattenOptions(recursive)),\n group: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertHeadingsToComposites(groupOptions(recursive)).run(),\n flatten: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertCompositesToHeadings(flattenOptions(recursive)).run(),\n }\n}\n\n/**\n * The commands of the composites feature, each declared once. Toolbar group beside the inserts: the default kind's\n * insert button (it follows the setting, so label/icon/shortcut/`creates` are getters read at render time), „In\n * Baustein umwandeln“ (one entry per kind for the caret's block) and „Gliederung umwandeln“ (headings <-> composites in\n * both directions, the preferred structure's direction first). `/` menu: one command per kind (`creates` that kind, so\n * the insert guard hides kinds the surrounding Baustein does not accept) and the four heading conversions.\n */\nfunction compositeCommands<C>(ctx: FeatureContext, kinds: readonly CompositeKind[]): CommandDef<C>[] {\n const { settings, t } = ctx\n const defaultKind = () => compositeKind(kinds, settings.get().blocks.defaultKind)\n const { canGroup, canFlatten, group, flatten } = structureActions(ctx)\n\n function kindItems(editor: Editor): MenuItem[] {\n return [\n { type: 'label', label: t('composites.menu.kindHeading') },\n ...compositeKindsOf(editor).map((kind): MenuItem => {\n const target = { type: 'composite', kind: kind.id } as const\n return { label: kind.label, icon: kind.icon, disabled: !editor.can().convertBlockTo(target), onSelect: () => editor.chain().focus().convertBlockTo(target).run() }\n }),\n ]\n }\n\n function structureItems(editor: Editor): MenuItem[] {\n const structure = settings.get().blocks.structure\n const abilities = abilitiesAtSelection(editor)\n const composite = abilities.has(ABILITY.composite)\n const headings = abilities.has(ABILITY.headings)\n const sep: MenuItem = { type: 'separator' }\n const toComposites: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toComposites') },\n { label: t('composites.menu.toComposites.top'), description: t('composites.menu.toComposites.top.description'), icon: 'i-lucide-layers', disabled: !composite || !canGroup(editor, false), onSelect: () => group(editor, false) },\n { label: t('composites.menu.toComposites.all'), description: t('composites.menu.toComposites.all.description'), icon: 'i-lucide-list-tree', disabled: !composite || !canGroup(editor, true), onSelect: () => group(editor, true) },\n ]\n const toHeadings: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toHeadings') },\n { label: t('composites.menu.toHeadings.top'), description: t('composites.menu.toHeadings.top.description'), icon: 'i-lucide-heading', disabled: !headings || !canFlatten(editor, false), onSelect: () => flatten(editor, false) },\n { label: t('composites.menu.toHeadings.all'), description: t('composites.menu.toHeadings.all.description'), icon: 'i-lucide-align-left', disabled: !headings || !canFlatten(editor, true), onSelect: () => flatten(editor, true) },\n ]\n const entry = editorEntryOf(editor)\n // The settings entry exists only while some feature contributes a tab to open.\n const settingsEntry: MenuItem[] = entry && settingsTabsOf(entry.preset, entry.ctx.app).length\n ? [sep, { label: t('composites.menu.settings'), icon: 'i-lucide-settings-2', onSelect: () => entry.ui.openSettings('editing') }]\n : []\n return [\n { type: 'label', label: t('composites.menu.preferred', { mode: t(STRUCTURE_MODE_KEYS[structure].labelKey) }) },\n ...(structure === 'composite' ? [...toComposites, sep, ...toHeadings] : [...toHeadings, sep, ...toComposites]),\n ...settingsEntry,\n ]\n }\n\n const insertKind = (kind: string, slashId: string, label: string, icon: string): CommandDef<C> => ({\n id: `composite.${kind}`,\n slash: { id: slashId },\n creates: { type: 'composite', attrs: { kind } },\n icon,\n label,\n ability: ABILITY.composite,\n run: (editor) => { editor.chain().focus().insertComposite(kind).run() },\n })\n\n return [\n {\n id: 'insert',\n toolbar: { group: 'composites', order: 10 },\n // Getters: the default kind arrives with the server settings; the toolbar reads them at render time.\n get icon() { return defaultKind().icon },\n get label() { return defaultKind().label },\n // Mod-Shift-a (CompositeNode) always inserts a `section`, so only then is it shown as the shortcut.\n get kbds() { return defaultKind().id === 'section' ? ['meta', 'shift', 'a'] : undefined },\n get creates() { return { type: 'composite', attrs: { kind: defaultKind().id } } },\n ability: ABILITY.composite,\n can: editor => editor.can().insertComposite(defaultKind().id),\n run: (editor) => { editor.chain().focus().insertComposite(defaultKind().id).run() },\n },\n {\n id: 'to-kind',\n toolbar: { group: 'composites', order: 20 },\n icon: 'i-lucide-replace',\n label: t('composites.toolbar.toKind'),\n ability: ABILITY.composite,\n can: editor => kindItems(editor).some(item => item.type !== 'label' && !item.disabled),\n menu: kindItems,\n },\n {\n id: 'structure',\n toolbar: { group: 'composites', order: 30 },\n icon: 'i-lucide-list-tree',\n label: t('composites.toolbar.structure'),\n can: editor => canGroup(editor, true) || canFlatten(editor, true),\n menu: structureItems,\n },\n insertKind('section', 'abschnitt', t('composites.slash.section'), 'i-lucide-layers'),\n ...kinds.filter(kind => kind.id !== 'section').map(kind => insertKind(kind.id, kind.id, kind.label, kind.icon)),\n { id: 'gliedern', slash: {}, label: t('composites.slash.group'), icon: 'i-lucide-layers', ability: ABILITY.composite, can: editor => canGroup(editor, false), run: editor => group(editor, false) },\n { id: 'gliedern-alle', slash: {}, label: t('composites.slash.groupAll'), icon: 'i-lucide-list-tree', ability: ABILITY.composite, can: editor => canGroup(editor, true), run: editor => group(editor, true) },\n { id: 'aufloesen', slash: {}, label: t('composites.slash.flatten'), icon: 'i-lucide-heading', ability: ABILITY.headings, can: editor => canFlatten(editor, false), run: editor => flatten(editor, false) },\n { id: 'aufloesen-alle', slash: {}, label: t('composites.slash.flattenAll'), icon: 'i-lucide-align-left', ability: ABILITY.headings, can: editor => canFlatten(editor, true), run: editor => flatten(editor, true) },\n { id: 'verweis', slash: {}, label: t('composites.reference.insert'), icon: 'i-lucide-link-2', ability: ABILITY.reference, run: (editor) => { editor.chain().focus().insertContent(REFERENCE_TRIGGER).run() } },\n ]\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/composites/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { BlockAction, CommandDef, EditorFeature, FeatureContext, MenuItem, ToolbarGroupDef } from '../../core'\nimport { abilitiesAtSelection, downloadText, editorEntryOf, settingsTabsOf } from '../../core'\nimport type { Translate } from '../../i18n'\nimport { ABILITY, blockIdOf, findBlockById } from '../../core/schema'\nimport { AcceptedChildren, compositeInsertGuard } from './accepted-children'\nimport { type Basket, defaultBaskets } from './baskets'\nimport { bibOf, bibtexOf, cslOf } from './bibliography'\nimport { compositeConversions } from './convert'\nimport { bodyMarkdown, derivationOf } from './derivation'\nimport { blocksFromMarkdown } from './fences'\nimport { structureBlocks } from './heading-composites'\nimport { type CompositeKind, compositeKind, defaultCompositeKinds } from './kinds'\nimport { migrateLegacyContent } from './legacy'\nimport { compositeSchemaExtensions } from './schema'\nimport { documentCompositeMarkdown, markdownFileName } from './markdown-export'\nimport { type CompositeNodeViews, compositeNodes } from './node-views'\nimport { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeTitleNode, compositeAround, compositeKindsOf } from './nodes'\nimport { CompositeFocus } from './focus'\nimport { CompositeMerge } from './merge'\nimport { REFERENCE_TRIGGER } from './reference'\nimport { REFERENCE_AI_BLOCK } from './reference-ai'\nimport { referenceSuggestion } from './reference-suggestion'\nimport { compositeIdsOf, normalizeReferences } from './reference-text'\nimport { SourceReveal } from './reveal-source'\nimport { CompositeSplit, type SplitMode } from './split'\nimport { Structure, structureOf } from './structure'\nimport { STRUCTURE_MODE_KEYS } from './structure-mode'\n\n/** What a framework binding supplies to the composites feature. */\nexport interface CompositesUi<C> {\n /** The chips on each title line and the details popover on the kind icon; mounted as an overlay. */\n chrome: C\n /** The focus chip („Fokus: <Titel> · Aufheben“) shown while a Baustein is focused; mounted as an overlay. Absent: no chip (the focus mode itself works). */\n focusChip?: C\n /** The „Bearbeiten“ settings tab; receives `{ kinds }` as props. */\n settingsTab: C\n /** The composite block view of the framework. */\n nodeViews: CompositeNodeViews\n}\n\n/** Options of `compositeFeature`. */\nexport interface CompositeFeatureOptions {\n /** The composite kinds („Bausteine“) offered in the editor, with their own labels (default: `defaultCompositeKinds(t)`, the built-in kinds in the editor's language); read once. */\n kinds?: readonly CompositeKind[]\n /** The colour baskets a Baustein can be put into (default: `defaultBaskets(t)`, red/orange/yellow/green/blue in the editor's language; colours are the `--sk-basket-<id>` CSS variables); read once. */\n baskets?: readonly Basket[]\n}\n\n/** Heading level the structure stage starts at when it turns composites into headings (below the document title). */\nconst STRUCTURE_STAGE_LEVEL = 2\n\n/**\n * Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,\n * „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),\n * Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,\n * the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), focus mode on one\n * Baustein (`CompositeFocus`, the „Fokussieren“ block action and the binding's `focusChip` overlay), and\n * the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein\n * into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,\n * `structure` and `migrateContent` stages. Imports no AI, media or transcript code.\n */\nexport function compositeFeatureCore<C>(ui: CompositesUi<C>, options: CompositeFeatureOptions = {}): EditorFeature<C> {\n // Host kinds keep their labels; the built-in ones are produced from the context's translator (memoized per `t`).\n const kindsOf = (ctx: { t: Translate }) => options.kinds ?? defaultCompositeKinds(ctx.t)\n const basketsOf = (ctx: { t: Translate }) => options.baskets ?? defaultBaskets(ctx.t)\n const { Composite, CompositeTags, Reference } = compositeNodes(ui.nodeViews)\n return {\n id: 'composites',\n dimension: 'content',\n schemaExtensions: () => compositeSchemaExtensions({ ...(options.kinds && { kinds: options.kinds }), ...(options.baskets && { baskets: options.baskets }) }),\n extensions: ctx => [\n Composite.configure({ kinds: kindsOf(ctx), baskets: basketsOf(ctx) }),\n CompositeTitleNode,\n CompositeDescriptionNode,\n CompositeTags,\n Structure.configure({ mode: () => ctx.settings.get().blocks.structure }),\n AcceptedChildren.configure({ notify: ctx.notify, t: ctx.t }),\n SourceReveal,\n CompositeFocus,\n CompositeMerge,\n CompositeSplit.configure({ defaultKind: () => ctx.settings.get().blocks.defaultKind, targetChars: () => ctx.settings.get().blocks.splitTargetChars }),\n Reference.configure({ suggestion: referenceSuggestion(ctx.t) }),\n ],\n toolbarGroups: COMPOSITE_TOOLBAR_GROUPS,\n commands: ctx => compositeCommands(ctx, kindsOf(ctx)),\n insertGuards: () => [compositeInsertGuard],\n blockActions: ctx => [{\n id: 'document-export',\n label: ctx.t('composites.blockAction.export'),\n icon: 'i-lucide-download',\n ability: ABILITY.compositeExport,\n order: 400,\n isVisible: ({ node }) => node.attrs.kind === 'document',\n run: ({ editor, node }) => downloadText(markdownFileName(node), documentCompositeMarkdown(editor, node)),\n }, {\n id: 'bibliography-export',\n label: ctx.t('composites.blockAction.bibtex'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeExport,\n order: 410,\n isVisible: ({ node }) => bibOf(node) !== null,\n run: ({ node }) => downloadText(`${markdownFileName(node).replace(/\\.md$/, '')}.bib`, bibtexOf(cslOf(node)!), 'text/x-bibtex;charset=utf-8'),\n }, {\n id: 'composite-focus',\n label: ctx.t('composites.focus.action'),\n icon: 'i-lucide-focus',\n order: 420,\n isVisible: ({ node }) => node.type.name === 'composite',\n run: ({ editor, node }) => {\n const id = blockIdOf(node)\n if (id) editor.commands.focusComposite(id)\n },\n }, ...splitBlockActions(ctx), {\n id: 'composite-merge-next',\n label: ctx.t('composites.merge.next'),\n icon: 'i-lucide-merge',\n ability: ABILITY.compositeMeta,\n order: 470,\n isVisible: ({ editor, pos }) => editor.can().mergeCompositeWithNext(pos),\n run: ({ editor, pos }) => void editor.chain().focus().mergeCompositeWithNext(pos).run(),\n }],\n overlays: [ui.chrome, ui.focusChip].filter(overlay => overlay !== undefined),\n settingsTabs: ctx => [{ id: 'editing', label: ctx.t('composites.tab'), icon: 'i-lucide-pencil', component: ui.settingsTab, props: { kinds: kindsOf(ctx) } }],\n blocks: {\n parseMarkdown: (editor, markdown) => blocksFromMarkdown(md => editor.markdown!.parse(md), markdown, compositeKindsOf(editor)),\n structure: (editor, blocks) => {\n const { schema } = editor\n return structureBlocks(schema, blocks.map(block => schema.nodeFromJSON(block)), structureOf(editor), STRUCTURE_STAGE_LEVEL, compositeKindsOf(editor)).map(node => node.toJSON())\n },\n migrateContent: migrateLegacyContent,\n normalize: (editor, blocks) => normalizeReferences(blocks, compositeIdsOf(editor.state.doc)),\n },\n conversions: compositeConversions(options.kinds),\n aiContext: editor => ({\n kinds: compositeKindsOf(editor).map(({ id, label, fields, ai }) => ({ id, label, fields, ...(ai?.purpose && { purpose: ai.purpose }) })),\n structure: structureOf(editor),\n blocks: [REFERENCE_AI_BLOCK],\n }),\n // A Baustein derived from another block hands the model that source block's Markdown.\n aiBlockData: ({ editor, node }) => {\n const from = derivationOf(node)?.from\n const source = from && 'block' in from ? findBlockById(editor.state.doc, from.block)?.node : undefined\n // i18n-ignore: model-facing data label (German prompt text, not UI)\n return source?.type.name === 'composite' ? [{ label: `Quelle: ${source.child(0).textContent.trim()}`, text: bodyMarkdown(editor, source) }] : []\n },\n }\n}\n\n/**\n * The four split actions of a Baustein: at the caret (as sibling or sub-Bausteine; offered on the Baustein around the\n * caret while the caret is past the first body block) and by length (as sub-Bausteine or siblings; offered while the\n * body falls into two parts or more). Each is hidden when the command refuses, so a kind that accepts no child kind\n * never shows the `children` variants.\n */\nfunction splitBlockActions({ t }: FeatureContext): BlockAction[] {\n const atCaret = (id: string, labelKey: 'composites.split.here' | 'composites.split.children', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => {\n const { $head } = editor.state.selection\n const found = compositeAround($head)\n return found?.pos === pos && $head.index(found.depth) > COMPOSITE_CHROME_COUNT && editor.can().splitCompositeAtCaret({ mode })\n },\n run: ({ editor }) => void editor.chain().focus().splitCompositeAtCaret({ mode }).run(),\n })\n const byLength = (id: string, labelKey: 'composites.split.lengthChildren' | 'composites.split.lengthSiblings', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => editor.can().splitCompositeByLength({ mode, pos }),\n run: ({ editor, pos }) => void editor.chain().focus().splitCompositeByLength({ mode, pos }).run(),\n })\n return [\n atCaret('composite-split-here', 'composites.split.here', 'i-lucide-scissors', 430, 'siblings'),\n atCaret('composite-split-children', 'composites.split.children', 'i-lucide-split', 440, 'children'),\n byLength('composite-split-length-children', 'composites.split.lengthChildren', 'i-lucide-ruler', 450, 'children'),\n byLength('composite-split-length-siblings', 'composites.split.lengthSiblings', 'i-lucide-ruler', 460, 'siblings'),\n ]\n}\n\nconst COMPOSITE_TOOLBAR_GROUPS: ToolbarGroupDef[] = [{ id: 'composites', order: 60, labelKey: 'palette.group.composites' }]\n\n/**\n * Converting headings to Bausteine and back: the checks and actions the toolbar's „Gliederung umwandeln“ menu and the\n * `/gliedern*` and `/aufloesen*` commands share, so both placements gate and act identically. They read the scope and\n * default kind from the settings at call time.\n */\nfunction structureActions({ settings }: FeatureContext) {\n const groupOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope, kind: settings.get().blocks.defaultKind })\n const flattenOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope })\n return {\n canGroup: (editor: Editor, recursive: boolean) => editor.can().convertHeadingsToComposites(groupOptions(recursive)),\n canFlatten: (editor: Editor, recursive: boolean) => editor.can().convertCompositesToHeadings(flattenOptions(recursive)),\n group: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertHeadingsToComposites(groupOptions(recursive)).run(),\n flatten: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertCompositesToHeadings(flattenOptions(recursive)).run(),\n }\n}\n\n/**\n * The commands of the composites feature, each declared once. Toolbar group beside the inserts: the default kind's\n * insert button (it follows the setting, so label/icon/shortcut/`creates` are getters read at render time), „In\n * Baustein umwandeln“ (one entry per kind for the caret's block) and „Gliederung umwandeln“ (headings <-> composites in\n * both directions, the preferred structure's direction first). `/` menu: one command per kind (`creates` that kind, so\n * the insert guard hides kinds the surrounding Baustein does not accept) and the four heading conversions.\n */\nfunction compositeCommands<C>(ctx: FeatureContext, kinds: readonly CompositeKind[]): CommandDef<C>[] {\n const { settings, t } = ctx\n const defaultKind = () => compositeKind(kinds, settings.get().blocks.defaultKind)\n const { canGroup, canFlatten, group, flatten } = structureActions(ctx)\n\n function kindItems(editor: Editor): MenuItem[] {\n return [\n { type: 'label', label: t('composites.menu.kindHeading') },\n ...compositeKindsOf(editor).map((kind): MenuItem => {\n const target = { type: 'composite', kind: kind.id } as const\n return { label: kind.label, icon: kind.icon, disabled: !editor.can().convertBlockTo(target), onSelect: () => editor.chain().focus().convertBlockTo(target).run() }\n }),\n ]\n }\n\n function structureItems(editor: Editor): MenuItem[] {\n const structure = settings.get().blocks.structure\n const abilities = abilitiesAtSelection(editor)\n const composite = abilities.has(ABILITY.composite)\n const headings = abilities.has(ABILITY.headings)\n const sep: MenuItem = { type: 'separator' }\n const toComposites: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toComposites') },\n { label: t('composites.menu.toComposites.top'), description: t('composites.menu.toComposites.top.description'), icon: 'i-lucide-layers', disabled: !composite || !canGroup(editor, false), onSelect: () => group(editor, false) },\n { label: t('composites.menu.toComposites.all'), description: t('composites.menu.toComposites.all.description'), icon: 'i-lucide-list-tree', disabled: !composite || !canGroup(editor, true), onSelect: () => group(editor, true) },\n ]\n const toHeadings: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toHeadings') },\n { label: t('composites.menu.toHeadings.top'), description: t('composites.menu.toHeadings.top.description'), icon: 'i-lucide-heading', disabled: !headings || !canFlatten(editor, false), onSelect: () => flatten(editor, false) },\n { label: t('composites.menu.toHeadings.all'), description: t('composites.menu.toHeadings.all.description'), icon: 'i-lucide-align-left', disabled: !headings || !canFlatten(editor, true), onSelect: () => flatten(editor, true) },\n ]\n const entry = editorEntryOf(editor)\n // The settings entry exists only while some feature contributes a tab to open.\n const settingsEntry: MenuItem[] = entry && settingsTabsOf(entry.preset, entry.ctx.app).length\n ? [sep, { label: t('composites.menu.settings'), icon: 'i-lucide-settings-2', onSelect: () => entry.ui.openSettings('editing') }]\n : []\n return [\n { type: 'label', label: t('composites.menu.preferred', { mode: t(STRUCTURE_MODE_KEYS[structure].labelKey) }) },\n ...(structure === 'composite' ? [...toComposites, sep, ...toHeadings] : [...toHeadings, sep, ...toComposites]),\n ...settingsEntry,\n ]\n }\n\n const insertKind = (kind: string, slashId: string, label: string, icon: string): CommandDef<C> => ({\n id: `composite.${kind}`,\n slash: { id: slashId },\n creates: { type: 'composite', attrs: { kind } },\n icon,\n label,\n ability: ABILITY.composite,\n run: (editor) => { editor.chain().focus().insertComposite(kind).run() },\n })\n\n return [\n {\n id: 'insert',\n toolbar: { group: 'composites', order: 10 },\n // Getters: the default kind arrives with the server settings; the toolbar reads them at render time.\n get icon() { return defaultKind().icon },\n get label() { return defaultKind().label },\n // Mod-Shift-a (CompositeNode) always inserts a `section`, so only then is it shown as the shortcut.\n get kbds() { return defaultKind().id === 'section' ? ['meta', 'shift', 'a'] : undefined },\n get creates() { return { type: 'composite', attrs: { kind: defaultKind().id } } },\n ability: ABILITY.composite,\n can: editor => editor.can().insertComposite(defaultKind().id),\n run: (editor) => { editor.chain().focus().insertComposite(defaultKind().id).run() },\n },\n {\n id: 'to-kind',\n toolbar: { group: 'composites', order: 20 },\n icon: 'i-lucide-replace',\n label: t('composites.toolbar.toKind'),\n ability: ABILITY.composite,\n can: editor => kindItems(editor).some(item => item.type !== 'label' && !item.disabled),\n menu: kindItems,\n },\n {\n id: 'structure',\n toolbar: { group: 'composites', order: 30 },\n icon: 'i-lucide-list-tree',\n label: t('composites.toolbar.structure'),\n can: editor => canGroup(editor, true) || canFlatten(editor, true),\n menu: structureItems,\n },\n insertKind('section', 'abschnitt', t('composites.slash.section'), 'i-lucide-layers'),\n ...kinds.filter(kind => kind.id !== 'section').map(kind => insertKind(kind.id, kind.id, kind.label, kind.icon)),\n { id: 'gliedern', slash: {}, label: t('composites.slash.group'), icon: 'i-lucide-layers', ability: ABILITY.composite, can: editor => canGroup(editor, false), run: editor => group(editor, false) },\n { id: 'gliedern-alle', slash: {}, label: t('composites.slash.groupAll'), icon: 'i-lucide-list-tree', ability: ABILITY.composite, can: editor => canGroup(editor, true), run: editor => group(editor, true) },\n { id: 'aufloesen', slash: {}, label: t('composites.slash.flatten'), icon: 'i-lucide-heading', ability: ABILITY.headings, can: editor => canFlatten(editor, false), run: editor => flatten(editor, false) },\n { id: 'aufloesen-alle', slash: {}, label: t('composites.slash.flattenAll'), icon: 'i-lucide-align-left', ability: ABILITY.headings, can: editor => canFlatten(editor, true), run: editor => flatten(editor, true) },\n { id: 'verweis', slash: {}, label: t('composites.reference.insert'), icon: 'i-lucide-link-2', ability: ABILITY.reference, run: (editor) => { editor.chain().focus().insertContent(REFERENCE_TRIGGER).run() } },\n ]\n}\n"],"mappings":"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"}
@@ -17,6 +17,7 @@ import { CompositeKindCount, CompositeRowsHandle, CompositeRowsState, createComp
17
17
  import { CompositeInspector, CompositeInspectorSection, CompositeInspectorState, CompositeKindOption, compositeFieldText, createCompositeInspector } from "./inspector-model.js";
18
18
  import { ReferenceTarget, referenceLabel, referenceTarget, referenceTitle } from "./reference-label.js";
19
19
  import { referenceSuggestion } from "./reference-suggestion.js";
20
+ import { CompositeMerge, mergeBody } from "./merge.js";
20
21
  import { CompositeSplit, CompositeSplitOptions, SplitMode, childKindFor, generatedTitle, splitBodyByLength, splitComposite } from "./split.js";
21
22
  import { Structure, StructureStorage, structureOf } from "./structure.js";
22
23
  import { STRUCTURE_MODE_KEYS } from "./structure-mode.js";
@@ -31,4 +32,4 @@ import { Derivation, DerivationMethod, DerivationSource, bodyMarkdown, derivatio
31
32
  import { LEGACY_COMPOSITE_NODE, migrateLegacyContent } from "./legacy.js";
32
33
  import { normalizeReferences, referencesFromText } from "./reference-text.js";
33
34
  import { compositeSchemaExtensions } from "./schema.js";
34
- export { AcceptedChildren, type AcceptedChildrenOptions, BASKET_IDS, BASKET_KEY, BIB_KEY, type Basket, COMPOSITE_CHROME_COUNT, CSL_TYPES, type CompositeAiBrief, type CompositeAnchor, type CompositeAnchorsHandle, type CompositeBacklink, CompositeDescriptionNode, type CompositeFeatureOptions, type CompositeFence, CompositeFocus, type CompositeFocusState, type CompositeFrame, type CompositeInspector, type CompositeInspectorSection, type CompositeInspectorState, type CompositeKind, type CompositeKindAi, type CompositeKindCount, type CompositeKindOption, type CompositeMention, type CompositeMetaField, CompositeNode, type CompositeNodeViews, type CompositeOptions, type CompositeReference, type CompositeRow, type CompositeRowsHandle, type CompositeRowsState, CompositeSplit, type CompositeSplitOptions, type CompositeStorage, CompositeTagsNode, CompositeTitleNode, type CompositesUi, type CslDate, type CslItem, type CslName, type CslType, DERIVATION_KEY, type Derivation, type DerivationMethod, type DerivationSource, type FenceSegment, LEGACY_COMPOSITE_NODE, type MoveVerdict, type OutlineNode, REFERENCE_NODE, type ReferenceAttrs, ReferenceNode, type ReferenceOptions, ReferenceSuggestionPluginKey, type ReferenceTarget, SOURCE_MEDIA_TYPES, STRUCTURE_MODE_KEYS, type SourceLocator, SourceReveal, type SplitMode, Structure, type StructureStorage, TITLE_HIDDEN_KEY, TREE_ROOT_KEYS, basketById, basketOf, bibOf, bibtexOf, blocksFromMarkdown, bodyMarkdown, canMoveBlockInto, childKindFor, citeFull, citeShort, compositeAiBrief, compositeBacklinks, compositeBasketsOf, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFieldText, compositeFocusKey, compositeFocusOf, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeInspector, createCompositeRows, cslOf, cslTypeOptions, defaultBaskets, defaultCompositeKinds, derivationOf, derivationState, documentCompositeMarkdown, flattenCompositesToHeadings, focusDecorations, generatedTitle, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, kindAccepts, markdownFileName, migrateLegacyContent, moveBlockInto, moveIntoVerdict, normalizeReferences, observeCompositeAnchors, parseLocator, referenceLabel, referenceMarkdown, referenceSuggestion, referenceTarget, referenceTitle, referencesFromText, renditionsOf, revealSource, seekSource, setCompositeDescription, setCompositeMetaField, setCompositeTags, sourceOf, splitBodyByLength, splitComposite, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf, titleHiddenOf, yearOf };
35
+ export { AcceptedChildren, type AcceptedChildrenOptions, BASKET_IDS, BASKET_KEY, BIB_KEY, type Basket, COMPOSITE_CHROME_COUNT, CSL_TYPES, type CompositeAiBrief, type CompositeAnchor, type CompositeAnchorsHandle, type CompositeBacklink, CompositeDescriptionNode, type CompositeFeatureOptions, type CompositeFence, CompositeFocus, type CompositeFocusState, type CompositeFrame, type CompositeInspector, type CompositeInspectorSection, type CompositeInspectorState, type CompositeKind, type CompositeKindAi, type CompositeKindCount, type CompositeKindOption, type CompositeMention, CompositeMerge, type CompositeMetaField, CompositeNode, type CompositeNodeViews, type CompositeOptions, type CompositeReference, type CompositeRow, type CompositeRowsHandle, type CompositeRowsState, CompositeSplit, type CompositeSplitOptions, type CompositeStorage, CompositeTagsNode, CompositeTitleNode, type CompositesUi, type CslDate, type CslItem, type CslName, type CslType, DERIVATION_KEY, type Derivation, type DerivationMethod, type DerivationSource, type FenceSegment, LEGACY_COMPOSITE_NODE, type MoveVerdict, type OutlineNode, REFERENCE_NODE, type ReferenceAttrs, ReferenceNode, type ReferenceOptions, ReferenceSuggestionPluginKey, type ReferenceTarget, SOURCE_MEDIA_TYPES, STRUCTURE_MODE_KEYS, type SourceLocator, SourceReveal, type SplitMode, Structure, type StructureStorage, TITLE_HIDDEN_KEY, TREE_ROOT_KEYS, basketById, basketOf, bibOf, bibtexOf, blocksFromMarkdown, bodyMarkdown, canMoveBlockInto, childKindFor, citeFull, citeShort, compositeAiBrief, compositeBacklinks, compositeBasketsOf, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFieldText, compositeFocusKey, compositeFocusOf, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeInspector, createCompositeRows, cslOf, cslTypeOptions, defaultBaskets, defaultCompositeKinds, derivationOf, derivationState, documentCompositeMarkdown, flattenCompositesToHeadings, focusDecorations, generatedTitle, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, kindAccepts, markdownFileName, mergeBody, migrateLegacyContent, moveBlockInto, moveIntoVerdict, normalizeReferences, observeCompositeAnchors, parseLocator, referenceLabel, referenceMarkdown, referenceSuggestion, referenceTarget, referenceTitle, referencesFromText, renditionsOf, revealSource, seekSource, setCompositeDescription, setCompositeMetaField, setCompositeTags, sourceOf, splitBodyByLength, splitComposite, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf, titleHiddenOf, yearOf };
@@ -18,6 +18,7 @@ import { AcceptedChildren } from "./accepted-children.js";
18
18
  import { documentCompositeMarkdown, headingsMarkdown, markdownFileName } from "./markdown-export.js";
19
19
  import { compositeNodes } from "./node-views.js";
20
20
  import { CompositeFocus, compositeFocusKey, compositeFocusOf, focusDecorations } from "./focus.js";
21
+ import { CompositeMerge, mergeBody } from "./merge.js";
21
22
  import { referenceLabel, referenceTarget, referenceTitle } from "./reference-label.js";
22
23
  import { compositeBacklinks, compositeDescriptionText, compositeMetaWith, compositeOfRow, compositeRowAt, compositeRows, setCompositeDescription, setCompositeMetaField, setCompositeTags } from "./rows.js";
23
24
  import { referenceSuggestion } from "./reference-suggestion.js";
@@ -31,4 +32,4 @@ import { createCompositeRows } from "./rows-model.js";
31
32
  import { TREE_ROOT_KEYS } from "./tree-root.js";
32
33
  import { compositeFieldText, createCompositeInspector } from "./inspector-model.js";
33
34
  import { canMoveBlockInto, compositeOutline, moveBlockInto, moveIntoVerdict } from "./outline.js";
34
- export { AcceptedChildren, BASKET_IDS, BASKET_KEY, BIB_KEY, COMPOSITE_CHROME_COUNT, CSL_TYPES, CompositeDescriptionNode, CompositeFocus, CompositeNode, CompositeSplit, CompositeTagsNode, CompositeTitleNode, DERIVATION_KEY, LEGACY_COMPOSITE_NODE, REFERENCE_NODE, ReferenceNode, ReferenceSuggestionPluginKey, SOURCE_MEDIA_TYPES, STRUCTURE_MODE_KEYS, SourceReveal, Structure, TITLE_HIDDEN_KEY, TREE_ROOT_KEYS, basketById, basketOf, bibOf, bibtexOf, blocksFromMarkdown, bodyMarkdown, canMoveBlockInto, childKindFor, citeFull, citeShort, compositeAiBrief, compositeBacklinks, compositeBasketsOf, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFieldText, compositeFocusKey, compositeFocusOf, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeInspector, createCompositeRows, cslOf, cslTypeOptions, defaultBaskets, defaultCompositeKinds, derivationOf, derivationState, documentCompositeMarkdown, flattenCompositesToHeadings, focusDecorations, generatedTitle, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, kindAccepts, markdownFileName, migrateLegacyContent, moveBlockInto, moveIntoVerdict, normalizeReferences, observeCompositeAnchors, parseLocator, referenceLabel, referenceMarkdown, referenceSuggestion, referenceTarget, referenceTitle, referencesFromText, renditionsOf, revealSource, seekSource, setCompositeDescription, setCompositeMetaField, setCompositeTags, sourceOf, splitBodyByLength, splitComposite, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf, titleHiddenOf, yearOf };
35
+ export { AcceptedChildren, BASKET_IDS, BASKET_KEY, BIB_KEY, COMPOSITE_CHROME_COUNT, CSL_TYPES, CompositeDescriptionNode, CompositeFocus, CompositeMerge, CompositeNode, CompositeSplit, CompositeTagsNode, CompositeTitleNode, DERIVATION_KEY, LEGACY_COMPOSITE_NODE, REFERENCE_NODE, ReferenceNode, ReferenceSuggestionPluginKey, SOURCE_MEDIA_TYPES, STRUCTURE_MODE_KEYS, SourceReveal, Structure, TITLE_HIDDEN_KEY, TREE_ROOT_KEYS, basketById, basketOf, bibOf, bibtexOf, blocksFromMarkdown, bodyMarkdown, canMoveBlockInto, childKindFor, citeFull, citeShort, compositeAiBrief, compositeBacklinks, compositeBasketsOf, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFieldText, compositeFocusKey, compositeFocusOf, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeInspector, createCompositeRows, cslOf, cslTypeOptions, defaultBaskets, defaultCompositeKinds, derivationOf, derivationState, documentCompositeMarkdown, flattenCompositesToHeadings, focusDecorations, generatedTitle, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, kindAccepts, markdownFileName, mergeBody, migrateLegacyContent, moveBlockInto, moveIntoVerdict, normalizeReferences, observeCompositeAnchors, parseLocator, referenceLabel, referenceMarkdown, referenceSuggestion, referenceTarget, referenceTitle, referencesFromText, renditionsOf, revealSource, seekSource, setCompositeDescription, setCompositeMetaField, setCompositeTags, sourceOf, splitBodyByLength, splitComposite, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf, titleHiddenOf, yearOf };
@@ -41,9 +41,14 @@ export interface CompositeKind {
41
41
  titlePlaceholder: string;
42
42
  descriptionPlaceholder: string;
43
43
  fields: CompositeMetaField[];
44
- /** How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty). */
44
+ /**
45
+ * How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty).
46
+ * `tone`: a CSS colour or `var(...)` that sets the kind apart: a Baustein of this kind gets `data-kind-tone` and `--sk-kind-tone` on its wrapper
47
+ * (a tinted surface and a left border in the tone) and its tree rows tint the kind icon with it. Absent: the kind looks as before.
48
+ */
45
49
  chrome?: {
46
50
  tags?: boolean;
51
+ tone?: string;
47
52
  };
48
53
  /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */
49
54
  ai?: CompositeKindAi;
@@ -1 +1 @@
1
- {"version":3,"file":"kinds.js","names":[],"sources":["../../../src/features/composites/kinds.ts"],"sourcesContent":["// Vue-free and server-importable: the kind registry of the composite block family („Bausteine“).\nimport { createTranslator, type Translate } from '../../i18n'\n\n/** Media block types a source-holding kind's source may be (`CompositeKind.source`). */\nexport const SOURCE_MEDIA_TYPES = ['image', 'audio', 'video', 'file'] as const\n\n/** One metadata field of a composite kind, stored under `key` in the composite's `meta` attribute. */\nexport interface CompositeMetaField {\n key: string\n label: string\n /**\n * `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein;\n * `multiselect`: several of `options`, stored as `string[]` (the key is removed when none is chosen).\n */\n type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'multiselect' | 'reference' | 'bibliography' | 'derivation'\n /** Choices of a `select` or `multiselect` field (required for `multiselect`). */\n options?: { value: string, label: string }[]\n /** `reference`: kind ids the field may point to; absent = any Baustein. */\n targets?: readonly string[]\n}\n\n/**\n * What a kind tells the model beyond its fields. `purpose` goes into every AI request's block model (chat, generate\n * tasks); `instructions`, `instructionsField` and `skeleton` are read only by „Baustein füllen (KI)“ (`fillComposite`).\n */\nexport interface CompositeKindAi {\n /** One or two German sentences: what a Baustein of this kind is for and what its body holds. */\n purpose: string\n /** Default instruction for filling the body of a Baustein of this kind (what to write, structure, tone). */\n instructions?: string\n /** Key of a `textarea` field of this kind; a Baustein whose value is non-empty replaces `instructions` with it. */\n instructionsField?: string\n /** Target text (Vorlage) as Markdown; `{key}` placeholders are replaced by the Baustein's `meta[key]` before it goes to the model, unknown ones stay. */\n skeleton?: string\n}\n\n/** A composite kind: what its `kind` attribute selects (label, icon, chrome placeholders, meta fields). */\nexport interface CompositeKind {\n id: string\n label: string\n icon: string\n titlePlaceholder: string\n descriptionPlaceholder: string\n fields: CompositeMetaField[]\n /** How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty). */\n chrome?: { tags?: boolean }\n /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n /**\n * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`\n * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.\n * Only new moves are checked; documents that already nest against it stay as they are.\n */\n accepts?: readonly string[] | 'all'\n /** Present on source-holding kinds: the media block types the kind's source may be; `sourceOf` returns the first body child of one of these. */\n source?: readonly (typeof SOURCE_MEDIA_TYPES)[number][]\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [], accepts: 'all' },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n // i18n-ignore: model-facing kind purpose (German prompt text, not UI)\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [], ai: { purpose: 'Eigene Notiz zu einer Quelle oder einem Thema: Zusammenfassung, Gedanken, offene Fragen.' } },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), source: [...SOURCE_MEDIA_TYPES], fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n ] },\n // i18n-ignore-start: model-facing kind purposes and instructions (German prompt text, not UI)\n { id: 'recording', label: t('composites.kind.recording.label'), icon: 'i-lucide-mic', titlePlaceholder: t('composites.kind.recording.title'), descriptionPlaceholder: t('composites.kind.recording.description'), accepts: 'all', source: ['audio', 'video'], fields: [\n ...importFields(t, 'recording'),\n { key: 'recordedAt', label: t('composites.kind.recording.field.recordedAt'), type: 'date' },\n { key: 'participants', label: t('composites.kind.recording.field.participants'), type: 'text' },\n { key: 'durationSeconds', label: t('composites.kind.recording.field.durationSeconds'), type: 'number' },\n ], ai: { purpose: 'Aufnahme (Audio oder Video) als Quelle; der Inhalt hält Lesefassungen wie das Transkript und eigene Notizen.' } },\n { id: 'paper', label: t('composites.kind.paper.label'), icon: 'i-lucide-scroll-text', titlePlaceholder: t('composites.kind.paper.title'), descriptionPlaceholder: t('composites.kind.paper.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'paper'),\n { key: 'bib', label: t('composites.kind.paper.field.bib'), type: 'bibliography' },\n { key: 'doi', label: t('composites.kind.paper.field.doi'), type: 'text' },\n ], ai: { purpose: 'Wissenschaftliche Arbeit als Quelle (PDF) mit Literaturangabe; der Inhalt hält Volltext, Zusammenfassungen und Notizen.' } },\n { id: 'book', label: t('composites.kind.book.label'), icon: 'i-lucide-book', titlePlaceholder: t('composites.kind.book.title'), descriptionPlaceholder: t('composites.kind.book.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'book'),\n { key: 'bib', label: t('composites.kind.book.field.bib'), type: 'bibliography' },\n { key: 'isbn', label: t('composites.kind.book.field.isbn'), type: 'text' },\n ], ai: { purpose: 'Buch als Quelle (PDF) mit Literaturangabe; der Inhalt hält den Volltext, gegliedert in Kapitel, und Notizen.' } },\n { id: 'figure', label: t('composites.kind.figure.label'), icon: 'i-lucide-image', titlePlaceholder: t('composites.kind.figure.title'), descriptionPlaceholder: t('composites.kind.figure.description'), accepts: 'all', source: ['image'], fields: [\n ...importFields(t, 'figure'),\n { key: 'credit', label: t('composites.kind.figure.field.credit'), type: 'text' },\n ], ai: { purpose: 'Bild als Quelle; der Inhalt hält die Bildbeschreibung und eigene Notizen.' } },\n { id: 'rendition', label: t('composites.kind.rendition.label'), icon: 'i-lucide-captions', titlePlaceholder: t('composites.kind.rendition.title'), descriptionPlaceholder: t('composites.kind.rendition.description'), accepts: 'all', fields: [\n { key: 'derivation', label: t('composites.kind.rendition.field.derivation'), type: 'derivation' },\n { key: 'language', label: t('composites.kind.rendition.field.language'), type: 'text' },\n ], ai: {\n purpose: 'Lesbare Fassung einer Quelle (Transkript, Volltext, Beschreibung, Übersetzung)',\n instructions: 'Übertrage den Inhalt vollständig und gut lesbar; nichts weglassen, nichts hinzuerfinden.',\n } },\n // i18n-ignore-end\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The four fields the import of a file fills in (`wrapImport` of `composite-media`), shared by every source-holding kind. */\nfunction importFields(t: Translate, kind: 'recording' | 'paper' | 'book' | 'figure'): CompositeMetaField[] {\n return [\n { key: 'filename', label: t(`composites.kind.${kind}.field.filename`), type: 'text' },\n { key: 'mimeType', label: t(`composites.kind.${kind}.field.mimeType`), type: 'text' },\n { key: 'size', label: t(`composites.kind.${kind}.field.size`), type: 'number' },\n { key: 'importedAt', label: t(`composites.kind.${kind}.field.importedAt`), type: 'date' },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n\n/** Kinds that accept every Baustein kind when their definition sets no `accepts`. */\nconst ACCEPTS_ALL_BY_DEFAULT: Record<string, true> = { folder: true, document: true }\n\n/**\n * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind\n * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.\n */\nexport function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean {\n const accepts = kinds.find(kind => kind.id === parentKind)?.accepts\n if (accepts === undefined) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind)\n return accepts === 'all' || accepts.includes(childKind)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kinds.js","names":[],"sources":["../../../src/features/composites/kinds.ts"],"sourcesContent":["// Vue-free and server-importable: the kind registry of the composite block family („Bausteine“).\nimport { createTranslator, type Translate } from '../../i18n'\n\n/** Media block types a source-holding kind's source may be (`CompositeKind.source`). */\nexport const SOURCE_MEDIA_TYPES = ['image', 'audio', 'video', 'file'] as const\n\n/** One metadata field of a composite kind, stored under `key` in the composite's `meta` attribute. */\nexport interface CompositeMetaField {\n key: string\n label: string\n /**\n * `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein;\n * `multiselect`: several of `options`, stored as `string[]` (the key is removed when none is chosen).\n */\n type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'multiselect' | 'reference' | 'bibliography' | 'derivation'\n /** Choices of a `select` or `multiselect` field (required for `multiselect`). */\n options?: { value: string, label: string }[]\n /** `reference`: kind ids the field may point to; absent = any Baustein. */\n targets?: readonly string[]\n}\n\n/**\n * What a kind tells the model beyond its fields. `purpose` goes into every AI request's block model (chat, generate\n * tasks); `instructions`, `instructionsField` and `skeleton` are read only by „Baustein füllen (KI)“ (`fillComposite`).\n */\nexport interface CompositeKindAi {\n /** One or two German sentences: what a Baustein of this kind is for and what its body holds. */\n purpose: string\n /** Default instruction for filling the body of a Baustein of this kind (what to write, structure, tone). */\n instructions?: string\n /** Key of a `textarea` field of this kind; a Baustein whose value is non-empty replaces `instructions` with it. */\n instructionsField?: string\n /** Target text (Vorlage) as Markdown; `{key}` placeholders are replaced by the Baustein's `meta[key]` before it goes to the model, unknown ones stay. */\n skeleton?: string\n}\n\n/** A composite kind: what its `kind` attribute selects (label, icon, chrome placeholders, meta fields). */\nexport interface CompositeKind {\n id: string\n label: string\n icon: string\n titlePlaceholder: string\n descriptionPlaceholder: string\n fields: CompositeMetaField[]\n /**\n * How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty).\n * `tone`: a CSS colour or `var(...)` that sets the kind apart: a Baustein of this kind gets `data-kind-tone` and `--sk-kind-tone` on its wrapper\n * (a tinted surface and a left border in the tone) and its tree rows tint the kind icon with it. Absent: the kind looks as before.\n */\n chrome?: { tags?: boolean, tone?: string }\n /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n /**\n * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`\n * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.\n * Only new moves are checked; documents that already nest against it stay as they are.\n */\n accepts?: readonly string[] | 'all'\n /** Present on source-holding kinds: the media block types the kind's source may be; `sourceOf` returns the first body child of one of these. */\n source?: readonly (typeof SOURCE_MEDIA_TYPES)[number][]\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [], accepts: 'all' },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n // i18n-ignore: model-facing kind purpose (German prompt text, not UI)\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [], ai: { purpose: 'Eigene Notiz zu einer Quelle oder einem Thema: Zusammenfassung, Gedanken, offene Fragen.' } },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), source: [...SOURCE_MEDIA_TYPES], fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n ] },\n // i18n-ignore-start: model-facing kind purposes and instructions (German prompt text, not UI)\n { id: 'recording', label: t('composites.kind.recording.label'), icon: 'i-lucide-mic', titlePlaceholder: t('composites.kind.recording.title'), descriptionPlaceholder: t('composites.kind.recording.description'), accepts: 'all', source: ['audio', 'video'], fields: [\n ...importFields(t, 'recording'),\n { key: 'recordedAt', label: t('composites.kind.recording.field.recordedAt'), type: 'date' },\n { key: 'participants', label: t('composites.kind.recording.field.participants'), type: 'text' },\n { key: 'durationSeconds', label: t('composites.kind.recording.field.durationSeconds'), type: 'number' },\n ], ai: { purpose: 'Aufnahme (Audio oder Video) als Quelle; der Inhalt hält Lesefassungen wie das Transkript und eigene Notizen.' } },\n { id: 'paper', label: t('composites.kind.paper.label'), icon: 'i-lucide-scroll-text', titlePlaceholder: t('composites.kind.paper.title'), descriptionPlaceholder: t('composites.kind.paper.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'paper'),\n { key: 'bib', label: t('composites.kind.paper.field.bib'), type: 'bibliography' },\n { key: 'doi', label: t('composites.kind.paper.field.doi'), type: 'text' },\n ], ai: { purpose: 'Wissenschaftliche Arbeit als Quelle (PDF) mit Literaturangabe; der Inhalt hält Volltext, Zusammenfassungen und Notizen.' } },\n { id: 'book', label: t('composites.kind.book.label'), icon: 'i-lucide-book', titlePlaceholder: t('composites.kind.book.title'), descriptionPlaceholder: t('composites.kind.book.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'book'),\n { key: 'bib', label: t('composites.kind.book.field.bib'), type: 'bibliography' },\n { key: 'isbn', label: t('composites.kind.book.field.isbn'), type: 'text' },\n ], ai: { purpose: 'Buch als Quelle (PDF) mit Literaturangabe; der Inhalt hält den Volltext, gegliedert in Kapitel, und Notizen.' } },\n { id: 'figure', label: t('composites.kind.figure.label'), icon: 'i-lucide-image', titlePlaceholder: t('composites.kind.figure.title'), descriptionPlaceholder: t('composites.kind.figure.description'), accepts: 'all', source: ['image'], fields: [\n ...importFields(t, 'figure'),\n { key: 'credit', label: t('composites.kind.figure.field.credit'), type: 'text' },\n ], ai: { purpose: 'Bild als Quelle; der Inhalt hält die Bildbeschreibung und eigene Notizen.' } },\n { id: 'rendition', label: t('composites.kind.rendition.label'), icon: 'i-lucide-captions', titlePlaceholder: t('composites.kind.rendition.title'), descriptionPlaceholder: t('composites.kind.rendition.description'), accepts: 'all', fields: [\n { key: 'derivation', label: t('composites.kind.rendition.field.derivation'), type: 'derivation' },\n { key: 'language', label: t('composites.kind.rendition.field.language'), type: 'text' },\n ], ai: {\n purpose: 'Lesbare Fassung einer Quelle (Transkript, Volltext, Beschreibung, Übersetzung)',\n instructions: 'Übertrage den Inhalt vollständig und gut lesbar; nichts weglassen, nichts hinzuerfinden.',\n } },\n // i18n-ignore-end\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The four fields the import of a file fills in (`wrapImport` of `composite-media`), shared by every source-holding kind. */\nfunction importFields(t: Translate, kind: 'recording' | 'paper' | 'book' | 'figure'): CompositeMetaField[] {\n return [\n { key: 'filename', label: t(`composites.kind.${kind}.field.filename`), type: 'text' },\n { key: 'mimeType', label: t(`composites.kind.${kind}.field.mimeType`), type: 'text' },\n { key: 'size', label: t(`composites.kind.${kind}.field.size`), type: 'number' },\n { key: 'importedAt', label: t(`composites.kind.${kind}.field.importedAt`), type: 'date' },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n\n/** Kinds that accept every Baustein kind when their definition sets no `accepts`. */\nconst ACCEPTS_ALL_BY_DEFAULT: Record<string, true> = { folder: true, document: true }\n\n/**\n * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind\n * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.\n */\nexport function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean {\n const accepts = kinds.find(kind => kind.id === parentKind)?.accepts\n if (accepts === undefined) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind)\n return accepts === 'all' || accepts.includes(childKind)\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { CompositeKind } from "./kinds.js";
2
+ import { Extension } from "@tiptap/core";
3
+ import { Fragment, Node as Node$1 } from "@tiptap/pm/model";
4
+ //#region src/features/composites/merge.d.ts
5
+ /**
6
+ * What a merge appends to the end of the first Baustein's body: the whole body of `next`, its own sub-Bausteine
7
+ * included, in document order. A body that is one empty paragraph (a Baustein nothing was written in) adds nothing.
8
+ * Null when `first`'s kind does not accept the kind of a Baustein nested in that body (the merge is refused).
9
+ */
10
+ export declare function mergeBody(kinds: readonly CompositeKind[], first: Node$1, next: Node$1): Fragment | null;
11
+ declare module '@tiptap/core' {
12
+ interface Commands<ReturnType> {
13
+ compositeMerge: {
14
+ /**
15
+ * The Baustein at `pos` (default: the caret's innermost one) absorbs the Baustein that directly follows it in the same parent: that one's body, with its sub-Bausteine, is appended to the end of this one's body and the following Baustein is gone. Title, description, tags, id, kind and meta stay those of the first. One transaction. Refused when no Baustein follows, when the first kind does not accept a Baustein nested in the following one, or when the editor is not editable.
16
+ */
17
+ mergeCompositeWithNext: (pos?: number) => ReturnType;
18
+ };
19
+ }
20
+ }
21
+ /** Client-only, no schema: the merge command of Bausteine. */
22
+ export declare const CompositeMerge: Extension<any, any>;
23
+ //#endregion
24
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { kindAccepts } from "./kinds.js";
2
+ import { compositeAt, compositeKindsOf } from "./nodes.js";
3
+ import { outermostComposites } from "./accepted-children.js";
4
+ import { Extension } from "@tiptap/core";
5
+ import { Fragment } from "@tiptap/pm/model";
6
+ //#region src/features/composites/merge.ts
7
+ /**
8
+ * What a merge appends to the end of the first Baustein's body: the whole body of `next`, its own sub-Bausteine
9
+ * included, in document order. A body that is one empty paragraph (a Baustein nothing was written in) adds nothing.
10
+ * Null when `first`'s kind does not accept the kind of a Baustein nested in that body (the merge is refused).
11
+ */
12
+ function mergeBody(kinds, first, next) {
13
+ let chrome = 0;
14
+ for (let i = 0; i < 3; i++) chrome += next.child(i).nodeSize;
15
+ const body = next.content.cut(chrome);
16
+ const firstKind = String(first.attrs.kind);
17
+ for (let i = 0; i < body.childCount; i++) for (const child of outermostComposites(body.child(i))) if (!kindAccepts(kinds, firstKind, String(child.attrs.kind))) return null;
18
+ const only = body.childCount === 1 ? body.firstChild : null;
19
+ return only?.type.name === "paragraph" && only.content.size === 0 ? Fragment.empty : body;
20
+ }
21
+ /** Client-only, no schema: the merge command of Bausteine. */
22
+ const CompositeMerge = Extension.create({
23
+ name: "compositeMerge",
24
+ addCommands() {
25
+ return { mergeCompositeWithNext: (pos) => ({ state, tr, dispatch }) => {
26
+ if (!this.editor.isEditable) return false;
27
+ const found = compositeAt(state, pos);
28
+ if (!found) return false;
29
+ const $pos = state.doc.resolve(found.pos);
30
+ const next = $pos.parent.maybeChild($pos.index() + 1);
31
+ if (next?.type.name !== "composite") return false;
32
+ const appended = mergeBody(compositeKindsOf(this.editor), found.node, next);
33
+ if (!appended) return false;
34
+ if (dispatch) {
35
+ const nextPos = found.pos + found.node.nodeSize;
36
+ tr.insert(nextPos - 1, appended);
37
+ const shifted = nextPos + appended.size;
38
+ tr.delete(shifted, shifted + next.nodeSize);
39
+ }
40
+ return true;
41
+ } };
42
+ }
43
+ });
44
+ //#endregion
45
+ export { CompositeMerge, mergeBody };
46
+
47
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","names":[],"sources":["../../../src/features/composites/merge.ts"],"sourcesContent":["// Merging a Baustein with its next sibling: the counterpart of the split. The pure part (`mergeBody`) knows nothing about\n// the editor; the `CompositeMerge` extension wraps it into the command `mergeCompositeWithNext`.\nimport { Extension } from '@tiptap/core'\nimport { Fragment, type Node as PMNode } from '@tiptap/pm/model'\nimport { outermostComposites } from './accepted-children'\nimport { type CompositeKind, kindAccepts } from './kinds'\nimport { COMPOSITE_CHROME_COUNT, compositeAt, compositeKindsOf } from './nodes'\n\n/**\n * What a merge appends to the end of the first Baustein's body: the whole body of `next`, its own sub-Bausteine\n * included, in document order. A body that is one empty paragraph (a Baustein nothing was written in) adds nothing.\n * Null when `first`'s kind does not accept the kind of a Baustein nested in that body (the merge is refused).\n */\nexport function mergeBody(kinds: readonly CompositeKind[], first: PMNode, next: PMNode): Fragment | null {\n let chrome = 0\n for (let i = 0; i < COMPOSITE_CHROME_COUNT; i++) chrome += next.child(i).nodeSize\n const body = next.content.cut(chrome)\n const firstKind = String(first.attrs.kind)\n for (let i = 0; i < body.childCount; i++) {\n for (const child of outermostComposites(body.child(i))) {\n if (!kindAccepts(kinds, firstKind, String(child.attrs.kind))) return null\n }\n }\n const only = body.childCount === 1 ? body.firstChild! : null\n return only?.type.name === 'paragraph' && only.content.size === 0 ? Fragment.empty : body\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n compositeMerge: {\n /**\n * The Baustein at `pos` (default: the caret's innermost one) absorbs the Baustein that directly follows it in the same parent: that one's body, with its sub-Bausteine, is appended to the end of this one's body and the following Baustein is gone. Title, description, tags, id, kind and meta stay those of the first. One transaction. Refused when no Baustein follows, when the first kind does not accept a Baustein nested in the following one, or when the editor is not editable.\n */\n mergeCompositeWithNext: (pos?: number) => ReturnType\n }\n }\n}\n\n/** Client-only, no schema: the merge command of Bausteine. */\nexport const CompositeMerge = Extension.create({\n name: 'compositeMerge',\n addCommands() {\n return {\n mergeCompositeWithNext: pos => ({ state, tr, dispatch }) => {\n if (!this.editor.isEditable) return false\n const found = compositeAt(state, pos)\n if (!found) return false\n const $pos = state.doc.resolve(found.pos)\n const next = $pos.parent.maybeChild($pos.index() + 1)\n if (next?.type.name !== 'composite') return false\n const appended = mergeBody(compositeKindsOf(this.editor), found.node, next)\n if (!appended) return false\n if (dispatch) {\n const nextPos = found.pos + found.node.nodeSize\n // The body goes in at the end of the first Baustein, then the second one is cut out behind it.\n tr.insert(nextPos - 1, appended)\n const shifted = nextPos + appended.size\n tr.delete(shifted, shifted + next.nodeSize)\n }\n return true\n },\n }\n },\n})\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,UAAU,OAAiC,OAAe,MAA+B;CACvG,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAA,GAA4B,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC;CACzE,MAAM,OAAO,KAAK,QAAQ,IAAI,MAAM;CACpC,MAAM,YAAY,OAAO,MAAM,MAAM,IAAI;CACzC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,YAAY,KACnC,KAAK,MAAM,SAAS,oBAAoB,KAAK,MAAM,CAAC,CAAC,GACnD,IAAI,CAAC,YAAY,OAAO,WAAW,OAAO,MAAM,MAAM,IAAI,CAAC,GAAG,OAAO;CAGzE,MAAM,OAAO,KAAK,eAAe,IAAI,KAAK,aAAc;CACxD,OAAO,MAAM,KAAK,SAAS,eAAe,KAAK,QAAQ,SAAS,IAAI,SAAS,QAAQ;AACvF;;AAcA,MAAa,iBAAiB,UAAU,OAAO;CAC7C,MAAM;CACN,cAAc;EACZ,OAAO,EACL,yBAAwB,SAAQ,EAAE,OAAO,IAAI,eAAe;GAC1D,IAAI,CAAC,KAAK,OAAO,YAAY,OAAO;GACpC,MAAM,QAAQ,YAAY,OAAO,GAAG;GACpC,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,OAAO,MAAM,IAAI,QAAQ,MAAM,GAAG;GACxC,MAAM,OAAO,KAAK,OAAO,WAAW,KAAK,MAAM,IAAI,CAAC;GACpD,IAAI,MAAM,KAAK,SAAS,aAAa,OAAO;GAC5C,MAAM,WAAW,UAAU,iBAAiB,KAAK,MAAM,GAAG,MAAM,MAAM,IAAI;GAC1E,IAAI,CAAC,UAAU,OAAO;GACtB,IAAI,UAAU;IACZ,MAAM,UAAU,MAAM,MAAM,MAAM,KAAK;IAEvC,GAAG,OAAO,UAAU,GAAG,QAAQ;IAC/B,MAAM,UAAU,UAAU,SAAS;IACnC,GAAG,OAAO,SAAS,UAAU,KAAK,QAAQ;GAC5C;GACA,OAAO;EACT,EACF;CACF;AACF,CAAC"}
@@ -4,6 +4,7 @@ import "../../settings/index.js";
4
4
  import { CompositeKind } from "./kinds.js";
5
5
  import { Basket } from "./baskets.js";
6
6
  import { Editor, JSONContent, Node } from "@tiptap/core";
7
+ import { EditorState } from "@tiptap/pm/state";
7
8
  import { Node as Node$1, ResolvedPos } from "@tiptap/pm/model";
8
9
  //#region src/features/composites/nodes.d.ts
9
10
  /** Options of the `composite` node: the kinds its `kind` attribute selects from and the baskets its `meta.basket` selects from. */
@@ -557,6 +557,6 @@ const CompositeTagsNode = Node.create({
557
557
  }
558
558
  });
559
559
  //#endregion
560
- export { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeNode, CompositeTagsNode, CompositeTitleNode, compositeAround, compositeBasketsOf, compositeJSON, compositeKindsOf };
560
+ export { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeNode, CompositeTagsNode, CompositeTitleNode, compositeAround, compositeAt, compositeBasketsOf, compositeJSON, compositeKindsOf };
561
561
 
562
562
  //# sourceMappingURL=nodes.js.map