@ecsplico/editor 3.3.0 → 3.4.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 (31) hide show
  1. package/CHANGELOG.md +10 -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/composites/components/CompositeChrome.js +10 -9
  5. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  6. package/dist/features/composites/kinds.d.ts +4 -0
  7. package/dist/features/composites/kinds.js.map +1 -1
  8. package/dist/features/composites/merge.d.ts +1 -1
  9. package/dist/features/composites/merge.js +5 -2
  10. package/dist/features/composites/merge.js.map +1 -1
  11. package/dist/features/composites/outline.d.ts +9 -2
  12. package/dist/features/composites/outline.js +41 -3
  13. package/dist/features/composites/outline.js.map +1 -1
  14. package/dist/features/composites/react/CompositeChrome.js +11 -9
  15. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  16. package/dist/features/composites/rows.d.ts +2 -0
  17. package/dist/features/composites/rows.js +1 -0
  18. package/dist/features/composites/rows.js.map +1 -1
  19. package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
  20. package/dist/server/handlers/ai.d.ts +2 -2
  21. package/dist/tree/DocumentTree.js +5 -4
  22. package/dist/tree/DocumentTree.js.map +1 -1
  23. package/dist/tree/react/DocumentTree.js +2 -1
  24. package/dist/tree/react/DocumentTree.js.map +1 -1
  25. package/dist/tree/tree-host.js +1 -1
  26. package/dist/tree/tree-items.js +2 -1
  27. package/dist/tree/tree-items.js.map +1 -1
  28. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  29. package/dist/vue/SimpleEditor.vue.d.ts +6 -6
  30. package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
  31. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.4.0 - 2026-10-21
4
+
5
+ ### Added
6
+
7
+ - `CompositeKind.chrome.controls?: boolean` (default `true`). With `controls: false` the chrome of a Baustein of that kind is read-only although the editor is editable (Vue and React `CompositeChrome`; `CompositeRow.controls`): no basket menu, status menu, title-visibility button, tag removal or `+` chip and no drag handle on the kind icon, and a read-only details popover; existing basket, status and tags stay visible as chips. `mergeCompositeWithNext` also refuses (so `TreeModel.canMerge`, the row button and the block action disappear) when the Baustein or the one it would absorb has such a kind. For kinds whose attributes a host guard refuses to change.
8
+
9
+ ### Changed
10
+
11
+ - Outline label of a Baustein without a title: instead of „(ohne Titel)“ the document tree (Vue and React rows) shows the text of the first heading of the Baustein's own body, else the start of the first non-empty text of its own body (cut at 60 characters with „…“); text inside a nested Baustein is not used, and only a Baustein with an empty body keeps „(ohne Titel)“. `compositeOutline` marks such a label with `OutlineNode.labelFromBody` and `TreeItem.labelFromBody` (rows render it muted and italic, `data-label-from-body`), and the title filter matches the derived label too. A Baustein with a title is unchanged.
12
+
3
13
  ## 3.3.0 - 2026-10-21
4
14
 
5
15
  ### Added
@@ -11,17 +11,17 @@ 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
- ready: (view: EditorView) => any;
15
14
  "update:modelValue": (text: string) => any;
16
15
  save: (text: string) => any;
16
+ ready: (view: EditorView) => any;
17
17
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
- onReady?: ((view: EditorView) => any) | undefined;
19
18
  "onUpdate:modelValue"?: ((text: string) => any) | undefined;
20
19
  onSave?: ((text: string) => any) | undefined;
20
+ onReady?: ((view: EditorView) => any) | undefined;
21
21
  }>, {
22
- modelValue: string;
23
22
  editable: boolean;
24
23
  wordWrap: boolean;
24
+ modelValue: string;
25
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
26
  declare const _default: typeof __VLS_export;
27
27
  //#endregion
@@ -32,17 +32,17 @@ 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
- ready: (target: DocumentSurfaceTarget) => any;
36
35
  "update:modelValue": (text: string) => any;
37
36
  save: (text: string) => any;
37
+ ready: (target: DocumentSurfaceTarget) => any;
38
38
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
- onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
40
39
  "onUpdate:modelValue"?: ((text: string) => any) | undefined;
41
40
  onSave?: ((text: string) => any) | undefined;
41
+ onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
42
42
  }>, {
43
- modelValue: string;
44
43
  editable: boolean;
45
44
  wordWrap: boolean;
45
+ modelValue: string;
46
46
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
47
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
48
48
  declare const _default: typeof __VLS_export;
@@ -129,6 +129,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
129
129
  return [{
130
130
  row,
131
131
  anchor,
132
+ editable: editable.value && row.controls,
132
133
  left,
133
134
  top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,
134
135
  width: Math.max((frame.value?.right ?? anchor.right) - left, 120),
@@ -173,7 +174,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
173
174
  content: withCtx(() => [createVNode(_sfc_main$2, {
174
175
  editor: __props.editor,
175
176
  row: item.row,
176
- editable: editable.value
177
+ editable: item.editable
177
178
  }, null, 8, [
178
179
  "editor",
179
180
  "row",
@@ -182,10 +183,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
182
183
  default: withCtx(() => [createElementVNode("span", {
183
184
  role: "button",
184
185
  tabindex: "0",
185
- draggable: editable.value,
186
+ draggable: item.editable,
186
187
  class: normalizeClass([
187
188
  "absolute z-10 flex items-center justify-center rounded-sm bg-[var(--sk-bg)] text-[var(--sk-muted)] transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]",
188
- editable.value ? "cursor-grab active:cursor-grabbing" : "cursor-pointer",
189
+ item.editable ? "cursor-grab active:cursor-grabbing" : "cursor-pointer",
189
190
  iconOnHover.value && !hoveredKeys.value.has(item.row.key) && chipsHoveredKey.value !== item.row.key && "opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
190
191
  ]),
191
192
  style: normalizeStyle({
@@ -195,7 +196,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
195
196
  height: `${item.anchor.toggleSize}px`
196
197
  }),
197
198
  "aria-label": unref(t)("composites.chrome.details", { kind: item.row.kindLabel }),
198
- title: editable.value ? unref(t)("composites.chrome.detailsDrag", { kind: item.row.kindLabel }) : unref(t)("composites.chrome.details", { kind: item.row.kindLabel }),
199
+ title: item.editable ? unref(t)("composites.chrome.detailsDrag", { kind: item.row.kindLabel }) : unref(t)("composites.chrome.details", { kind: item.row.kindLabel }),
199
200
  onKeydown: _cache[0] || (_cache[0] = withKeys(withModifiers(($event) => $event.currentTarget.click(), ["prevent"]), ["enter", "space"])),
200
201
  onDragstart: ($event) => onIconDragStart($event, item.row)
201
202
  }, [(openBlock(), createBlock(unref(UIcon), {
@@ -204,7 +205,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
204
205
  class: "size-3.5"
205
206
  }, null, 8, ["name"]))], 46, _hoisted_2)]),
206
207
  _: 2
207
- }, 1024)) : createCommentVNode("v-if", true), editable.value || item.basket || item.status || item.derived || item.row.tagsVisible && item.row.tags.length ? (openBlock(), createElementBlock("div", {
208
+ }, 1024)) : createCommentVNode("v-if", true), item.editable || item.basket || item.status || item.derived || item.row.tagsVisible && item.row.tags.length ? (openBlock(), createElementBlock("div", {
208
209
  key: 1,
209
210
  class: "absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]",
210
211
  style: normalizeStyle({
@@ -216,7 +217,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
216
217
  onMouseenter: ($event) => chipsHoveredKey.value = item.row.key,
217
218
  onMouseleave: _cache[1] || (_cache[1] = ($event) => chipsHoveredKey.value = null)
218
219
  }, [
219
- editable.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_sfc_main$1, {
220
+ item.editable ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_sfc_main$1, {
220
221
  editor: __props.editor,
221
222
  row: item.row
222
223
  }, {
@@ -277,7 +278,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
277
278
  class: "size-3.5"
278
279
  }), createTextVNode(toDisplayString(unref(t)("composites.derivation.stale")), 1)], 2)) : createCommentVNode("v-if", true)
279
280
  ], 64)) : createCommentVNode("v-if", true),
280
- item.status ? (openBlock(), createElementBlock(Fragment, { key: 3 }, [editable.value ? (openBlock(), createBlock(_sfc_main$3, {
281
+ item.status ? (openBlock(), createElementBlock(Fragment, { key: 3 }, [item.editable ? (openBlock(), createBlock(_sfc_main$3, {
281
282
  key: 0,
282
283
  editor: __props.editor,
283
284
  row: item.row
@@ -302,7 +303,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
302
303
  return openBlock(), createElementBlock("span", {
303
304
  key: tag,
304
305
  class: normalizeClass([chip, "group max-w-40 gap-0"])
305
- }, [createElementVNode("span", _hoisted_9, "#" + toDisplayString(tag), 1), editable.value ? (openBlock(), createElementBlock("button", {
306
+ }, [createElementVNode("span", _hoisted_9, "#" + toDisplayString(tag), 1), item.editable ? (openBlock(), createElementBlock("button", {
306
307
  key: 0,
307
308
  type: "button",
308
309
  class: "flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]",
@@ -313,7 +314,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
313
314
  class: "size-3"
314
315
  })], 8, _hoisted_10)) : createCommentVNode("v-if", true)], 2);
315
316
  }), 128)),
316
- editable.value && item.row.tagsVisible ? (openBlock(), createBlock(unref(UPopover), {
317
+ item.editable && item.row.tagsVisible ? (openBlock(), createBlock(unref(UPopover), {
317
318
  key: 4,
318
319
  content: {
319
320
  side: "bottom",
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeChrome.js","names":["_unref","_openBlock","_createBlock","_Teleport","_createElementVNode","_createElementBlock","_Fragment","_renderList","_withCtx","_createVNode","CompositeDetails","_normalizeClass","_normalizeStyle","_withKeys","_withModifiers","_createCommentVNode","CompositeBasketMenu","_createTextVNode","_toDisplayString","CompositeStatusMenu","CompositeTagsEditor"],"sources":["../../../../src/features/composites/components/CompositeChrome.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/vue-3'\nimport { computed, shallowRef, watch } from 'vue'\nimport { startBlockDrag } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { basketById, basketOf, titleHiddenOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { useCompositeAnchors } from '../use-composite-anchors'\nimport { useCompositeRows } from '../use-composite-rows'\nimport CompositeBasketMenu from './CompositeBasketMenu.vue'\nimport CompositeDetails from './CompositeDetails.vue'\nimport CompositeStatusMenu from './CompositeStatusMenu.vue'\nimport CompositeTagsEditor from './CompositeTagsEditor.vue'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). A kind with `chrome.tags: false` shows no tag chips and no `+` chip. With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon, Popover: UPopover } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst { rows } = useCompositeRows(() => props.editor)\nconst { host, anchors, frame } = useCompositeAnchors(() => props.editor, rows)\n\n// `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\nconst editable = shallowRef(false)\nwatch(() => props.editor, (instance, _previous, onCleanup) => {\n editable.value = !instance.isDestroyed && instance.isEditable\n const sync = () => {\n editable.value = !instance.isDestroyed && instance.isEditable\n }\n instance.on('transaction', sync)\n onCleanup(() => instance.off('transaction', sync))\n}, { immediate: true })\n\nconst { settings } = useEditorSettings()\nconst iconOnHover = computed(() => settings.value.blocks.kindIcon === 'hover')\n\n// Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n// composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\nconst hoveredKeys = shallowRef<ReadonlySet<string>>(new Set())\nconst chipsHoveredKey = shallowRef<string | null>(null)\nlet hoveredSection: Element | null = null\n\nfunction onEditorMouseover(event: MouseEvent) {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = props.editor\n hoveredKeys.value = new Set(rows.value.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key))\n}\n\nfunction onEditorMouseleave() {\n hoveredSection = null\n hoveredKeys.value = new Set()\n}\n\nwatch([() => props.editor, iconOnHover], ([instance, hover], _previous, onCleanup) => {\n onEditorMouseleave()\n if (!hover || instance.isDestroyed) return\n const { dom } = instance.view\n dom.addEventListener('mouseover', onEditorMouseover)\n dom.addEventListener('mouseleave', onEditorMouseleave)\n onCleanup(() => {\n dom.removeEventListener('mouseover', onEditorMouseover)\n dom.removeEventListener('mouseleave', onEditorMouseleave)\n })\n}, { immediate: true })\n\nfunction onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (!found || !startBlockDrag(props.editor, event, found.pos)) event.preventDefault()\n}\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\nconst revealedTitles = useStoreRef(() => props.editor.storage.composite.revealedTitles)\n\nconst placed = computed(() => {\n const baskets = compositeBasketsOf(props.editor)\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.value.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(props.editor, row)\n const open = !derived || !row.id || expanded.value.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.value.get(row.key)\n if (!anchor) return []\n const titleHidden = titleHiddenOf(row)\n // A hidden title leaves nothing to measure: the chips sit in the strip beside the kind icon (`composites.css`).\n const inStrip = titleHidden && !(row.id && revealedTitles.value.has(row.id)) && anchor.toggleSize > 0\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,\n width: Math.max((frame.value?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n basket: basketById(baskets, basketOf(row)) ?? null,\n titleHidden,\n }]\n })\n})\n\nfunction toggleExpanded(row: CompositeRow) {\n if (row.id) props.editor.commands.toggleCompositeExpanded(row.id)\n}\n\nfunction removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(props.editor, row, row.tags.filter(candidate => candidate !== tag))\n}\n\nfunction toggleTitleHidden(row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (found) props.editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos)\n}\n\nconst chip = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-[var(--sk-muted)]'\nconst chipButton = `${chip} transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]`\n\n/** Scale and fade in from the icon (`transform-origin` is Reka's), a bit longer than Nuxt UI's default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst detailsPopoverUi = {\n content: 'composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]',\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <div class=\"absolute left-0 top-0 h-0 w-0\">\n <template v-for=\"item in placed\" :key=\"item.row.key\">\n <UPopover\n v-if=\"item.anchor.toggleSize\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 4 }\"\n :ui=\"detailsPopoverUi\"\n >\n <!-- A span, not a `button`: Firefox does not start drags from buttons. -->\n <span\n role=\"button\"\n tabindex=\"0\"\n :draggable=\"editable\"\n :class=\"[\n 'absolute z-10 flex items-center justify-center rounded-sm bg-[var(--sk-bg)] text-[var(--sk-muted)] transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]',\n editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n ]\"\n :style=\"{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }\"\n :aria-label=\"t('composites.chrome.details', { kind: item.row.kindLabel })\"\n :title=\"editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })\"\n @keydown.enter.space.prevent=\"($event.currentTarget as HTMLElement).click()\"\n @dragstart=\"onIconDragStart($event, item.row)\"\n >\n <UIcon :key=\"item.row.icon\" :name=\"item.row.icon\" class=\"size-3.5\" />\n </span>\n <template #content>\n <CompositeDetails :editor=\"editor\" :row=\"item.row\" :editable=\"editable\" />\n </template>\n </UPopover>\n\n <div\n v-if=\"editable || item.basket || item.status || item.derived || (item.row.tagsVisible && item.row.tags.length)\"\n class=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]\"\n :style=\"{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }\"\n @mouseenter=\"chipsHoveredKey = item.row.key\"\n @mouseleave=\"chipsHoveredKey = null\"\n >\n <template v-if=\"editable\">\n <CompositeBasketMenu :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.basket.label')\" :title=\"item.basket ? `${t('composites.basket.label')}: ${item.basket.label}` : t('composites.basket.label')\">\n <span v-if=\"item.basket\" class=\"size-2.5 rounded-full\" :style=\"{ backgroundColor: item.basket.color }\" />\n <UIcon v-else name=\"i-lucide-circle-dashed\" class=\"size-3.5\" />\n </button>\n </CompositeBasketMenu>\n <button\n type=\"button\"\n :class=\"chipButton\"\n :aria-label=\"t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')\"\n :title=\"t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')\"\n @click=\"toggleTitleHidden(item.row)\"\n >\n <UIcon :name=\"item.titleHidden ? 'i-lucide-eye' : 'i-lucide-eye-off'\" class=\"size-3.5\" />\n </button>\n </template>\n <span v-else-if=\"item.basket\" :class=\"chip\" :title=\"item.basket.label\">\n <span class=\"size-2.5 rounded-full\" :style=\"{ backgroundColor: item.basket.color }\" />\n </span>\n\n <template v-if=\"item.derived\">\n <button\n v-if=\"item.derived.toggleable\"\n type=\"button\"\n :class=\"chipButton\"\n :aria-expanded=\"item.derived.open\"\n :aria-label=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n :title=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n @click=\"toggleExpanded(item.row)\"\n >\n <UIcon :name=\"item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'\" class=\"size-3.5\" />\n </button>\n <span :class=\"chip\">\n <UIcon name=\"i-lucide-git-branch\" class=\"size-3.5\" />{{ t('composites.derivation.badge') }}\n </span>\n <span v-if=\"item.derived.stale\" :class=\"[chip, 'text-[var(--sk-warning)]']\">\n <UIcon name=\"i-lucide-triangle-alert\" class=\"size-3.5\" />{{ t('composites.derivation.stale') }}\n </span>\n </template>\n\n <template v-if=\"item.status\">\n <CompositeStatusMenu v-if=\"editable\" :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.status.label', { status: item.status.label })\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </button>\n </CompositeStatusMenu>\n <span v-else :class=\"chip\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </span>\n </template>\n\n <span v-for=\"tag in item.row.tagsVisible ? item.row.tags : []\" :key=\"tag\" :class=\"[chip, 'group max-w-40 gap-0']\">\n <span class=\"truncate\">#{{ tag }}</span>\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]\"\n :aria-label=\"t('composites.tag.remove', { tag })\"\n @click=\"removeTag(item.row, tag)\"\n >\n <UIcon name=\"i-lucide-x\" class=\"size-3\" />\n </button>\n </span>\n\n <UPopover\n v-if=\"editable && item.row.tagsVisible\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 6, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n >\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.tag.add')\" :title=\"t('composites.tag.add')\">\n <UIcon name=\"i-lucide-plus\" class=\"size-3.5\" />\n </button>\n <template #content>\n <div class=\"w-64 p-3\">\n <CompositeTagsEditor :editor=\"editor\" :row=\"item.row\" />\n </div>\n </template>\n </UPopover>\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompositeChrome.js","names":["_unref","_openBlock","_createBlock","_Teleport","_createElementVNode","_createElementBlock","_Fragment","_renderList","_withCtx","_createVNode","CompositeDetails","_normalizeClass","_normalizeStyle","_withKeys","_withModifiers","_createCommentVNode","CompositeBasketMenu","_createTextVNode","_toDisplayString","CompositeStatusMenu","CompositeTagsEditor"],"sources":["../../../../src/features/composites/components/CompositeChrome.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/vue-3'\nimport { computed, shallowRef, watch } from 'vue'\nimport { startBlockDrag } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { basketById, basketOf, titleHiddenOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { useCompositeAnchors } from '../use-composite-anchors'\nimport { useCompositeRows } from '../use-composite-rows'\nimport CompositeBasketMenu from './CompositeBasketMenu.vue'\nimport CompositeDetails from './CompositeDetails.vue'\nimport CompositeStatusMenu from './CompositeStatusMenu.vue'\nimport CompositeTagsEditor from './CompositeTagsEditor.vue'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). A kind with `chrome.tags: false` shows no tag chips and no `+` chip. With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon, Popover: UPopover } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst { rows } = useCompositeRows(() => props.editor)\nconst { host, anchors, frame } = useCompositeAnchors(() => props.editor, rows)\n\n// `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\nconst editable = shallowRef(false)\nwatch(() => props.editor, (instance, _previous, onCleanup) => {\n editable.value = !instance.isDestroyed && instance.isEditable\n const sync = () => {\n editable.value = !instance.isDestroyed && instance.isEditable\n }\n instance.on('transaction', sync)\n onCleanup(() => instance.off('transaction', sync))\n}, { immediate: true })\n\nconst { settings } = useEditorSettings()\nconst iconOnHover = computed(() => settings.value.blocks.kindIcon === 'hover')\n\n// Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n// composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\nconst hoveredKeys = shallowRef<ReadonlySet<string>>(new Set())\nconst chipsHoveredKey = shallowRef<string | null>(null)\nlet hoveredSection: Element | null = null\n\nfunction onEditorMouseover(event: MouseEvent) {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = props.editor\n hoveredKeys.value = new Set(rows.value.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key))\n}\n\nfunction onEditorMouseleave() {\n hoveredSection = null\n hoveredKeys.value = new Set()\n}\n\nwatch([() => props.editor, iconOnHover], ([instance, hover], _previous, onCleanup) => {\n onEditorMouseleave()\n if (!hover || instance.isDestroyed) return\n const { dom } = instance.view\n dom.addEventListener('mouseover', onEditorMouseover)\n dom.addEventListener('mouseleave', onEditorMouseleave)\n onCleanup(() => {\n dom.removeEventListener('mouseover', onEditorMouseover)\n dom.removeEventListener('mouseleave', onEditorMouseleave)\n })\n}, { immediate: true })\n\nfunction onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (!found || !startBlockDrag(props.editor, event, found.pos)) event.preventDefault()\n}\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\nconst revealedTitles = useStoreRef(() => props.editor.storage.composite.revealedTitles)\n\nconst placed = computed(() => {\n const baskets = compositeBasketsOf(props.editor)\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.value.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(props.editor, row)\n const open = !derived || !row.id || expanded.value.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.value.get(row.key)\n if (!anchor) return []\n const titleHidden = titleHiddenOf(row)\n // A hidden title leaves nothing to measure: the chips sit in the strip beside the kind icon (`composites.css`).\n const inStrip = titleHidden && !(row.id && revealedTitles.value.has(row.id)) && anchor.toggleSize > 0\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n // A kind without `chrome.controls` renders as in a read-only editor.\n editable: editable.value && row.controls,\n left,\n top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,\n width: Math.max((frame.value?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n basket: basketById(baskets, basketOf(row)) ?? null,\n titleHidden,\n }]\n })\n})\n\nfunction toggleExpanded(row: CompositeRow) {\n if (row.id) props.editor.commands.toggleCompositeExpanded(row.id)\n}\n\nfunction removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(props.editor, row, row.tags.filter(candidate => candidate !== tag))\n}\n\nfunction toggleTitleHidden(row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (found) props.editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos)\n}\n\nconst chip = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-[var(--sk-muted)]'\nconst chipButton = `${chip} transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]`\n\n/** Scale and fade in from the icon (`transform-origin` is Reka's), a bit longer than Nuxt UI's default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst detailsPopoverUi = {\n content: 'composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]',\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <div class=\"absolute left-0 top-0 h-0 w-0\">\n <template v-for=\"item in placed\" :key=\"item.row.key\">\n <UPopover\n v-if=\"item.anchor.toggleSize\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 4 }\"\n :ui=\"detailsPopoverUi\"\n >\n <!-- A span, not a `button`: Firefox does not start drags from buttons. -->\n <span\n role=\"button\"\n tabindex=\"0\"\n :draggable=\"item.editable\"\n :class=\"[\n 'absolute z-10 flex items-center justify-center rounded-sm bg-[var(--sk-bg)] text-[var(--sk-muted)] transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]',\n item.editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n ]\"\n :style=\"{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }\"\n :aria-label=\"t('composites.chrome.details', { kind: item.row.kindLabel })\"\n :title=\"item.editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })\"\n @keydown.enter.space.prevent=\"($event.currentTarget as HTMLElement).click()\"\n @dragstart=\"onIconDragStart($event, item.row)\"\n >\n <UIcon :key=\"item.row.icon\" :name=\"item.row.icon\" class=\"size-3.5\" />\n </span>\n <template #content>\n <CompositeDetails :editor=\"editor\" :row=\"item.row\" :editable=\"item.editable\" />\n </template>\n </UPopover>\n\n <div\n v-if=\"item.editable || item.basket || item.status || item.derived || (item.row.tagsVisible && item.row.tags.length)\"\n class=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]\"\n :style=\"{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }\"\n @mouseenter=\"chipsHoveredKey = item.row.key\"\n @mouseleave=\"chipsHoveredKey = null\"\n >\n <template v-if=\"item.editable\">\n <CompositeBasketMenu :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.basket.label')\" :title=\"item.basket ? `${t('composites.basket.label')}: ${item.basket.label}` : t('composites.basket.label')\">\n <span v-if=\"item.basket\" class=\"size-2.5 rounded-full\" :style=\"{ backgroundColor: item.basket.color }\" />\n <UIcon v-else name=\"i-lucide-circle-dashed\" class=\"size-3.5\" />\n </button>\n </CompositeBasketMenu>\n <button\n type=\"button\"\n :class=\"chipButton\"\n :aria-label=\"t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')\"\n :title=\"t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')\"\n @click=\"toggleTitleHidden(item.row)\"\n >\n <UIcon :name=\"item.titleHidden ? 'i-lucide-eye' : 'i-lucide-eye-off'\" class=\"size-3.5\" />\n </button>\n </template>\n <span v-else-if=\"item.basket\" :class=\"chip\" :title=\"item.basket.label\">\n <span class=\"size-2.5 rounded-full\" :style=\"{ backgroundColor: item.basket.color }\" />\n </span>\n\n <template v-if=\"item.derived\">\n <button\n v-if=\"item.derived.toggleable\"\n type=\"button\"\n :class=\"chipButton\"\n :aria-expanded=\"item.derived.open\"\n :aria-label=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n :title=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n @click=\"toggleExpanded(item.row)\"\n >\n <UIcon :name=\"item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'\" class=\"size-3.5\" />\n </button>\n <span :class=\"chip\">\n <UIcon name=\"i-lucide-git-branch\" class=\"size-3.5\" />{{ t('composites.derivation.badge') }}\n </span>\n <span v-if=\"item.derived.stale\" :class=\"[chip, 'text-[var(--sk-warning)]']\">\n <UIcon name=\"i-lucide-triangle-alert\" class=\"size-3.5\" />{{ t('composites.derivation.stale') }}\n </span>\n </template>\n\n <template v-if=\"item.status\">\n <CompositeStatusMenu v-if=\"item.editable\" :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.status.label', { status: item.status.label })\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </button>\n </CompositeStatusMenu>\n <span v-else :class=\"chip\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </span>\n </template>\n\n <span v-for=\"tag in item.row.tagsVisible ? item.row.tags : []\" :key=\"tag\" :class=\"[chip, 'group max-w-40 gap-0']\">\n <span class=\"truncate\">#{{ tag }}</span>\n <button\n v-if=\"item.editable\"\n type=\"button\"\n class=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]\"\n :aria-label=\"t('composites.tag.remove', { tag })\"\n @click=\"removeTag(item.row, tag)\"\n >\n <UIcon name=\"i-lucide-x\" class=\"size-3\" />\n </button>\n </span>\n\n <UPopover\n v-if=\"item.editable && item.row.tagsVisible\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 6, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n >\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.tag.add')\" :title=\"t('composites.tag.add')\">\n <UIcon name=\"i-lucide-plus\" class=\"size-3.5\" />\n </button>\n <template #content>\n <div class=\"w-64 p-3\">\n <CompositeTagsEditor :editor=\"editor\" :row=\"item.row\" />\n </div>\n </template>\n </UPopover>\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
@@ -45,10 +45,14 @@ export interface CompositeKind {
45
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
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
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
+ * `controls: false` makes the chrome of a Baustein of this kind read-only although the editor is editable (for a kind whose attributes a host
49
+ * guard refuses to change): no basket menu, status menu, title-visibility toggle, tag editing or drag handle on the kind icon, and a read-only
50
+ * details popover; an existing basket, status and tags still show as chips. Default `true`.
48
51
  */
49
52
  chrome?: {
50
53
  tags?: boolean;
51
54
  tone?: string;
55
+ controls?: boolean;
52
56
  };
53
57
  /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */
54
58
  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 /**\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":";;;AAIA,MAAa,qBAAqB;CAAC;CAAS;CAAS;CAAS;AAAM;;;;;;;AAgEpE,SAAgB,sBAAsB,GAAwC;CAC5E,IAAI,QAAQ,cAAc,IAAI,CAAC;CAC/B,IAAI,CAAC,OAAO,cAAc,IAAI,GAAG,QAAQ,kBAAkB,CAAC,CAAC;CAC7D,OAAO;AACT;AAEA,MAAM,gCAAgB,IAAI,QAA6C;AAEvE,SAAS,kBAAkB,GAAwC;CACjE,OAAO;EACL;GAAE,IAAI;GAAW,OAAO,EAAE,+BAA+B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,+BAA+B;GAAG,wBAAwB,EAAE,qCAAqC;GAAG,QAAQ,CAAC;GAAG,SAAS;EAAM;EACxO;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAyB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ;IAC7M;KAAE,KAAK;KAAU,OAAO,EAAE,mCAAmC;KAAG,MAAM;KAAU,SAAS;MACvF;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;MAC9D;OAAE,OAAO;OAAS,OAAO,EAAE,mCAAmC;MAAE;MAChE;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;KAChE;IAAE;IACF;KAAE,KAAK;KAAO,OAAO,EAAE,gCAAgC;KAAG,MAAM;IAAO;IACvE;KAAE,KAAK;KAAY,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAO;GACnF;EAAE;EAEF;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAwB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ,CAAC;GAAG,IAAI,EAAE,SAAS,2FAA2F;EAAE;EAC9T;GAAE,IAAI;GAAY,OAAO,EAAE,gCAAgC;GAAG,MAAM;GAAsB,kBAAkB,EAAE,gCAAgC;GAAG,wBAAwB,EAAE,sCAAsC;GAAG,QAAQ,CAAC,GAAG,kBAAkB;GAAG,QAAQ;IAC3P;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAQ,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAS;IAC/E;KAAE,KAAK;KAAc,OAAO,EAAE,2CAA2C;KAAG,MAAM;IAAO;GAC3F;EAAE;EAEF;GAAE,IAAI;GAAa,OAAO,EAAE,iCAAiC;GAAG,MAAM;GAAgB,kBAAkB,EAAE,iCAAiC;GAAG,wBAAwB,EAAE,uCAAuC;GAAG,SAAS;GAAO,QAAQ,CAAC,SAAS,OAAO;GAAG,QAAQ;IACpQ,GAAG,aAAa,GAAG,WAAW;IAC9B;KAAE,KAAK;KAAc,OAAO,EAAE,4CAA4C;KAAG,MAAM;IAAO;IAC1F;KAAE,KAAK;KAAgB,OAAO,EAAE,8CAA8C;KAAG,MAAM;IAAO;IAC9F;KAAE,KAAK;KAAmB,OAAO,EAAE,iDAAiD;KAAG,MAAM;IAAS;GACxG;GAAG,IAAI,EAAE,SAAS,+GAA+G;EAAE;EACnI;GAAE,IAAI;GAAS,OAAO,EAAE,6BAA6B;GAAG,MAAM;GAAwB,kBAAkB,EAAE,6BAA6B;GAAG,wBAAwB,EAAE,mCAAmC;GAAG,SAAS;GAAO,QAAQ,CAAC,MAAM;GAAG,QAAQ;IAClP,GAAG,aAAa,GAAG,OAAO;IAC1B;KAAE,KAAK;KAAO,OAAO,EAAE,iCAAiC;KAAG,MAAM;IAAe;IAChF;KAAE,KAAK;KAAO,OAAO,EAAE,iCAAiC;KAAG,MAAM;IAAO;GAC1E;GAAG,IAAI,EAAE,SAAS,0HAA0H;EAAE;EAC9I;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAiB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,SAAS;GAAO,QAAQ,CAAC,MAAM;GAAG,QAAQ;IACvO,GAAG,aAAa,GAAG,MAAM;IACzB;KAAE,KAAK;KAAO,OAAO,EAAE,gCAAgC;KAAG,MAAM;IAAe;IAC/E;KAAE,KAAK;KAAQ,OAAO,EAAE,iCAAiC;KAAG,MAAM;IAAO;GAC3E;GAAG,IAAI,EAAE,SAAS,+GAA+G;EAAE;EACnI;GAAE,IAAI;GAAU,OAAO,EAAE,8BAA8B;GAAG,MAAM;GAAkB,kBAAkB,EAAE,8BAA8B;GAAG,wBAAwB,EAAE,oCAAoC;GAAG,SAAS;GAAO,QAAQ,CAAC,OAAO;GAAG,QAAQ,CACjP,GAAG,aAAa,GAAG,QAAQ,GAC3B;IAAE,KAAK;IAAU,OAAO,EAAE,qCAAqC;IAAG,MAAM;GAAO,CACjF;GAAG,IAAI,EAAE,SAAS,4EAA4E;EAAE;EAChG;GAAE,IAAI;GAAa,OAAO,EAAE,iCAAiC;GAAG,MAAM;GAAqB,kBAAkB,EAAE,iCAAiC;GAAG,wBAAwB,EAAE,uCAAuC;GAAG,SAAS;GAAO,QAAQ,CAC7O;IAAE,KAAK;IAAc,OAAO,EAAE,4CAA4C;IAAG,MAAM;GAAa,GAChG;IAAE,KAAK;IAAY,OAAO,EAAE,0CAA0C;IAAG,MAAM;GAAO,CACxF;GAAG,IAAI;IACL,SAAS;IACT,cAAc;GAChB;EAAE;EAEF;GAAE,IAAI;GAAU,OAAO,EAAE,8BAA8B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,8BAA8B;GAAG,wBAAwB,EAAE,oCAAoC;GAAG,QAAQ,CAAC;EAAE;CACtN;AACF;;AAGA,SAAS,aAAa,GAAc,MAAuE;CACzG,OAAO;EACL;GAAE,KAAK;GAAY,OAAO,EAAE,mBAAmB,KAAK,gBAAgB;GAAG,MAAM;EAAO;EACpF;GAAE,KAAK;GAAY,OAAO,EAAE,mBAAmB,KAAK,gBAAgB;GAAG,MAAM;EAAO;EACpF;GAAE,KAAK;GAAQ,OAAO,EAAE,mBAAmB,KAAK,YAAY;GAAG,MAAM;EAAS;EAC9E;GAAE,KAAK;GAAc,OAAO,EAAE,mBAAmB,KAAK,kBAAkB;GAAG,MAAM;EAAO;CAC1F;AACF;;AAGA,MAAa,+BAAyD,sBAAsB,iBAAiB;AAE7G,MAAM,oBAAoB,iBAAiB;;AAG3C,SAAgB,cAAc,OAAiC,IAA2B;CACxF,MAAM,QAAQ,MAAM,MAAK,SAAQ,KAAK,OAAO,EAAE;CAC/C,IAAI,OAAO,OAAO;CAElB,OAAO;EAAE,GADI,MAAM,MAAK,SAAQ,KAAK,OAAO,SAAS,KAAK,MAAM,MAAM,uBAAuB,CAAC,CAAC;EAC7E;EAAI,OAAO;CAAG;AAClC;;AAGA,MAAM,yBAA+C;CAAE,QAAQ;CAAM,UAAU;AAAK;;;;;AAMpF,SAAgB,YAAY,OAAiC,YAAoB,WAA4B;CAC3G,MAAM,UAAU,MAAM,MAAK,SAAQ,KAAK,OAAO,UAAU,CAAC,EAAE;CAC5D,IAAI,YAAY,KAAA,GAAW,OAAO,OAAO,OAAO,wBAAwB,UAAU;CAClF,OAAO,YAAY,SAAS,QAAQ,SAAS,SAAS;AACxD"}
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 * `controls: false` makes the chrome of a Baustein of this kind read-only although the editor is editable (for a kind whose attributes a host\n * guard refuses to change): no basket menu, status menu, title-visibility toggle, tag editing or drag handle on the kind icon, and a read-only\n * details popover; an existing basket, status and tags still show as chips. Default `true`.\n */\n chrome?: { tags?: boolean, tone?: string, controls?: 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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"}
@@ -12,7 +12,7 @@ declare module '@tiptap/core' {
12
12
  interface Commands<ReturnType> {
13
13
  compositeMerge: {
14
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.
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, when either Baustein has a kind with `chrome.controls === false` (host-managed), or when the editor is not editable.
16
16
  */
17
17
  mergeCompositeWithNext: (pos?: number) => ReturnType;
18
18
  };
@@ -1,4 +1,4 @@
1
- import { kindAccepts } from "./kinds.js";
1
+ import { compositeKind, kindAccepts } from "./kinds.js";
2
2
  import { compositeAt, compositeKindsOf } from "./nodes.js";
3
3
  import { outermostComposites } from "./accepted-children.js";
4
4
  import { Extension } from "@tiptap/core";
@@ -29,7 +29,10 @@ const CompositeMerge = Extension.create({
29
29
  const $pos = state.doc.resolve(found.pos);
30
30
  const next = $pos.parent.maybeChild($pos.index() + 1);
31
31
  if (next?.type.name !== "composite") return false;
32
- const appended = mergeBody(compositeKindsOf(this.editor), found.node, next);
32
+ const kinds = compositeKindsOf(this.editor);
33
+ const hostManaged = (node) => compositeKind(kinds, String(node.attrs.kind)).chrome?.controls === false;
34
+ if (hostManaged(found.node) || hostManaged(next)) return false;
35
+ const appended = mergeBody(kinds, found.node, next);
33
36
  if (!appended) return false;
34
37
  if (dispatch) {
35
38
  const nextPos = found.pos + found.node.nodeSize;
@@ -1 +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"}
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, 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, when either Baustein has a kind with `chrome.controls === false` (host-managed), 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 kinds = compositeKindsOf(this.editor)\n // A kind with `chrome.controls === false` is host-managed: neither absorbing nor being absorbed is the editor's call.\n const hostManaged = (node: PMNode) => compositeKind(kinds, String(node.attrs.kind)).chrome?.controls === false\n if (hostManaged(found.node) || hostManaged(next)) return false\n const appended = mergeBody(kinds, 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,QAAQ,iBAAiB,KAAK,MAAM;GAE1C,MAAM,eAAe,SAAiB,cAAc,OAAO,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,aAAa;GACzG,IAAI,YAAY,MAAM,IAAI,KAAK,YAAY,IAAI,GAAG,OAAO;GACzD,MAAM,WAAW,UAAU,OAAO,MAAM,MAAM,IAAI;GAClD,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"}
@@ -10,7 +10,13 @@ export interface OutlineNode {
10
10
  /** Position of the composite in the document it was read from. */
11
11
  pos: number;
12
12
  kind: string;
13
+ /**
14
+ * The title; for a composite without one, the text of the first heading of its own body, else the start of the first
15
+ * text of its own body (`labelFromBody`), else the caller's `untitled`.
16
+ */
13
17
  title: string;
18
+ /** `title` is derived from the body, the Baustein itself has no title. */
19
+ labelFromBody: boolean;
14
20
  /** The composite has a description (the summary). */
15
21
  hasSummary: boolean;
16
22
  /** Basket id (`meta.basket`), `null` without one. */
@@ -20,8 +26,9 @@ export interface OutlineNode {
20
26
  children: OutlineNode[];
21
27
  }
22
28
  /**
23
- * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the
24
- * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants
29
+ * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title is labelled by its body
30
+ * (`labelFromBody`: first heading, else the start of the first text; nested composites do not count) and only an empty
31
+ * one gets `untitled` (the caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants
25
32
  * take their place in the parent's list.
26
33
  */
27
34
  export declare function compositeOutline(doc: Node$1, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[];
@@ -4,9 +4,44 @@ import { basketOf, titleHiddenOf } from "./baskets.js";
4
4
  import { compositeKindsOf } from "./nodes.js";
5
5
  import { nestingReason, outermostComposites } from "./accepted-children.js";
6
6
  //#region src/features/composites/outline.ts
7
+ /** Longest label taken from a paragraph; longer text is cut and ends in an ellipsis. */
8
+ const BODY_LABEL_CHARS = 60;
9
+ const clipLabel = (text) => {
10
+ if (text.length <= BODY_LABEL_CHARS) return text;
11
+ let end = BODY_LABEL_CHARS;
12
+ const last = text.charCodeAt(end - 1);
13
+ if (last >= 55296 && last <= 56319) end--;
14
+ return `${text.slice(0, end).trimEnd()}…`;
15
+ };
7
16
  /**
8
- * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the
9
- * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants
17
+ * Walks the blocks of a composite's own body (not the ones inside a nested composite) in order. The text of the first
18
+ * heading ends the walk; `found.text` collects the first non-empty text block as the fallback.
19
+ */
20
+ function scanBody(parent, from, found) {
21
+ for (let i = from; i < parent.childCount && found.heading === null; i++) {
22
+ const node = parent.child(i);
23
+ if (node.type.name === "composite") continue;
24
+ if (node.isTextblock) {
25
+ const text = node.textContent.trim();
26
+ if (!text) continue;
27
+ if (node.type.name === "heading") found.heading = text;
28
+ else found.text ??= text.replace(/\s+/g, " ");
29
+ } else if (!node.isLeaf) scanBody(node, 0, found);
30
+ }
31
+ }
32
+ /** The label of a composite without a title: its first heading, else the start of its first text; `null` for an empty body. */
33
+ function bodyLabel(composite) {
34
+ const found = {
35
+ heading: null,
36
+ text: null
37
+ };
38
+ scanBody(composite, 3, found);
39
+ return found.heading ?? (found.text === null ? null : clipLabel(found.text));
40
+ }
41
+ /**
42
+ * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title is labelled by its body
43
+ * (`labelFromBody`: first heading, else the start of the first text; nested composites do not count) and only an empty
44
+ * one gets `untitled` (the caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants
10
45
  * take their place in the parent's list.
11
46
  */
12
47
  function compositeOutline(doc, hiddenKinds, untitled) {
@@ -22,12 +57,15 @@ function compositeOutline(doc, hiddenKinds, untitled) {
22
57
  return;
23
58
  }
24
59
  const id = blockIdOf(node);
60
+ const ownTitle = node.firstChild?.textContent.trim() ?? "";
61
+ const derived = ownTitle ? null : bodyLabel(node);
25
62
  into.push({
26
63
  key: id ?? `pos:${pos}`,
27
64
  id,
28
65
  pos,
29
66
  kind,
30
- title: node.firstChild?.textContent.trim() || untitled,
67
+ title: ownTitle || derived || untitled,
68
+ labelFromBody: derived !== null,
31
69
  hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,
32
70
  basket: basketOf(node),
33
71
  titleHidden: titleHiddenOf(node),
@@ -1 +1 @@
1
- {"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { nestingReason, outermostComposites } from './accepted-children'\nimport { basketOf, titleHiddenOf } from './baskets'\nimport { kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n title: string\n /** The composite has a description (the summary). */\n hasSummary: boolean\n /** Basket id (`meta.basket`), `null` without one. */\n basket: string | null\n /** The title is hidden in the editor (`meta.titleHidden`). */\n titleHidden: boolean\n children: OutlineNode[]\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the\n * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: node.firstChild?.textContent.trim() || untitled,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n basket: basketOf(node),\n titleHidden: titleHiddenOf(node),\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */\nexport type MoveVerdict = { ok: true } | { ok: false, reason: string }\n\nconst refused = (reason: string): MoveVerdict => ({ ok: false, reason })\n\n/**\n * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when\n * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind\n * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,\n * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not\n * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.\n */\nexport function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return refused(t('composites.move.blockGone'))\n if (targetId === null) return { ok: true }\n const target = findBlockById(doc, targetId)\n if (!target) return refused(t('composites.move.targetGone'))\n if (target.node.type.name !== 'composite') return refused(t('composites.move.targetNotBlock'))\n if (target.pos === block.pos) return refused(t('composites.move.intoSelf'))\n if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t('composites.move.intoOwn'))\n\n // The nearest Baustein around the block: when it is the target, the block only moves within it.\n const $block = doc.resolve(block.pos)\n let parentPos = -1\n for (let depth = $block.depth; depth > 0; depth--) {\n if ($block.node(depth).type.name === 'composite') {\n parentPos = $block.before(depth)\n break\n }\n }\n if (parentPos === target.pos) return { ok: true }\n\n const kinds = compositeKindsOf(editor)\n const targetKind = String(target.node.attrs.kind)\n for (const child of outermostComposites(block.node)) {\n const childKind = String(child.attrs.kind)\n if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t))\n }\n return { ok: true }\n}\n\n/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */\nconst untranslated: Translate = key => key\n\n/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n return moveIntoVerdict(editor, id, targetId, untranslated).ok\n}\n\n/**\n * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor\n * refused the transaction).\n */\nexport function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n if (!canMoveBlockInto(editor, id, targetId)) return false\n const { doc, tr } = editor.state\n const block = findBlockById(doc, id)!\n const target = targetId === null ? null : findBlockById(doc, targetId)!\n // A composite's body is its last children, so its end is just before the composite's closing token.\n const insertPos = target ? target.pos + target.node.nodeSize - 1 : doc.content.size\n tr.delete(block.pos, block.pos + block.node.nodeSize)\n tr.insert(tr.mapping.map(insertPos), block.node)\n editor.view.dispatch(tr.scrollIntoView())\n return editor.state.doc !== doc\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { nestingReason, outermostComposites } from './accepted-children'\nimport { basketOf, titleHiddenOf } from './baskets'\nimport { kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n /**\n * The title; for a composite without one, the text of the first heading of its own body, else the start of the first\n * text of its own body (`labelFromBody`), else the caller's `untitled`.\n */\n title: string\n /** `title` is derived from the body, the Baustein itself has no title. */\n labelFromBody: boolean\n /** The composite has a description (the summary). */\n hasSummary: boolean\n /** Basket id (`meta.basket`), `null` without one. */\n basket: string | null\n /** The title is hidden in the editor (`meta.titleHidden`). */\n titleHidden: boolean\n children: OutlineNode[]\n}\n\n/** Longest label taken from a paragraph; longer text is cut and ends in an ellipsis. */\nconst BODY_LABEL_CHARS = 60\n\nconst clipLabel = (text: string) => {\n if (text.length <= BODY_LABEL_CHARS) return text\n let end = BODY_LABEL_CHARS\n const last = text.charCodeAt(end - 1)\n if (last >= 0xD800 && last <= 0xDBFF) end-- // do not cut a surrogate pair\n return `${text.slice(0, end).trimEnd()}…`\n}\n\n/**\n * Walks the blocks of a composite's own body (not the ones inside a nested composite) in order. The text of the first\n * heading ends the walk; `found.text` collects the first non-empty text block as the fallback.\n */\nfunction scanBody(parent: PMNode, from: number, found: { heading: string | null, text: string | null }): void {\n for (let i = from; i < parent.childCount && found.heading === null; i++) {\n const node = parent.child(i)\n if (node.type.name === 'composite') continue\n if (node.isTextblock) {\n const text = node.textContent.trim()\n if (!text) continue\n if (node.type.name === 'heading') found.heading = text\n else found.text ??= text.replace(/\\s+/g, ' ')\n }\n else if (!node.isLeaf) scanBody(node, 0, found)\n }\n}\n\n/** The label of a composite without a title: its first heading, else the start of its first text; `null` for an empty body. */\nfunction bodyLabel(composite: PMNode): string | null {\n const found = { heading: null as string | null, text: null as string | null }\n scanBody(composite, 3, found) // title, description and tags come first\n return found.heading ?? (found.text === null ? null : clipLabel(found.text))\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title is labelled by its body\n * (`labelFromBody`: first heading, else the start of the first text; nested composites do not count) and only an empty\n * one gets `untitled` (the caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n const ownTitle = node.firstChild?.textContent.trim() ?? ''\n const derived = ownTitle ? null : bodyLabel(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: ownTitle || derived || untitled,\n labelFromBody: derived !== null,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n basket: basketOf(node),\n titleHidden: titleHiddenOf(node),\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */\nexport type MoveVerdict = { ok: true } | { ok: false, reason: string }\n\nconst refused = (reason: string): MoveVerdict => ({ ok: false, reason })\n\n/**\n * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when\n * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind\n * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,\n * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not\n * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.\n */\nexport function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return refused(t('composites.move.blockGone'))\n if (targetId === null) return { ok: true }\n const target = findBlockById(doc, targetId)\n if (!target) return refused(t('composites.move.targetGone'))\n if (target.node.type.name !== 'composite') return refused(t('composites.move.targetNotBlock'))\n if (target.pos === block.pos) return refused(t('composites.move.intoSelf'))\n if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t('composites.move.intoOwn'))\n\n // The nearest Baustein around the block: when it is the target, the block only moves within it.\n const $block = doc.resolve(block.pos)\n let parentPos = -1\n for (let depth = $block.depth; depth > 0; depth--) {\n if ($block.node(depth).type.name === 'composite') {\n parentPos = $block.before(depth)\n break\n }\n }\n if (parentPos === target.pos) return { ok: true }\n\n const kinds = compositeKindsOf(editor)\n const targetKind = String(target.node.attrs.kind)\n for (const child of outermostComposites(block.node)) {\n const childKind = String(child.attrs.kind)\n if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t))\n }\n return { ok: true }\n}\n\n/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */\nconst untranslated: Translate = key => key\n\n/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n return moveIntoVerdict(editor, id, targetId, untranslated).ok\n}\n\n/**\n * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor\n * refused the transaction).\n */\nexport function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n if (!canMoveBlockInto(editor, id, targetId)) return false\n const { doc, tr } = editor.state\n const block = findBlockById(doc, id)!\n const target = targetId === null ? null : findBlockById(doc, targetId)!\n // A composite's body is its last children, so its end is just before the composite's closing token.\n const insertPos = target ? target.pos + target.node.nodeSize - 1 : doc.content.size\n tr.delete(block.pos, block.pos + block.node.nodeSize)\n tr.insert(tr.mapping.map(insertPos), block.node)\n editor.view.dispatch(tr.scrollIntoView())\n return editor.state.doc !== doc\n}\n"],"mappings":"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"}