@ecsplico/editor 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/adapters/comments/record.d.ts +15 -0
  3. package/dist/adapters/comments/server.d.ts +2 -1
  4. package/dist/adapters/comments/server.js +2 -1
  5. package/dist/adapters/comments/server.js.map +1 -1
  6. package/dist/adapters/nitro.d.ts +4 -0
  7. package/dist/adapters/nitro.js +2 -1
  8. package/dist/adapters/nitro.js.map +1 -1
  9. package/dist/code/CodeSurface.vue.d.ts +2 -2
  10. package/dist/core/command-types.d.ts +7 -0
  11. package/dist/core/commands.d.ts +2 -2
  12. package/dist/core/commands.js.map +1 -1
  13. package/dist/core/feature.d.ts +8 -2
  14. package/dist/core/feature.js.map +1 -1
  15. package/dist/core/index.d.ts +3 -2
  16. package/dist/core/index.js +2 -1
  17. package/dist/core/surface-session.js +2 -0
  18. package/dist/core/surface-session.js.map +1 -1
  19. package/dist/core/transaction-guards.d.ts +21 -0
  20. package/dist/core/transaction-guards.js +49 -0
  21. package/dist/core/transaction-guards.js.map +1 -0
  22. package/dist/document/DocumentSurface.vue.d.ts +2 -2
  23. package/dist/features/ai/protocol/chat.js +2 -1
  24. package/dist/features/ai/protocol/chat.js.map +1 -1
  25. package/dist/features/composites/ai-brief.js +8 -0
  26. package/dist/features/composites/ai-brief.js.map +1 -1
  27. package/dist/features/composites/components/CompositeChrome.js +4 -4
  28. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  29. package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
  30. package/dist/features/composites/components/CompositeFieldEditor.js +33 -6
  31. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  32. package/dist/features/composites/components/CompositeInspector.js +3 -2
  33. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  34. package/dist/features/composites/inspector-model.js +2 -2
  35. package/dist/features/composites/inspector-model.js.map +1 -1
  36. package/dist/features/composites/kinds.d.ts +10 -3
  37. package/dist/features/composites/kinds.js.map +1 -1
  38. package/dist/features/composites/meta-display.js +5 -1
  39. package/dist/features/composites/meta-display.js.map +1 -1
  40. package/dist/features/composites/react/CompositeChrome.js +3 -3
  41. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  42. package/dist/features/composites/react/CompositeFieldEditor.js +19 -0
  43. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  44. package/dist/features/composites/react/CompositeInspector.js +1 -1
  45. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  46. package/dist/features/composites/rows.d.ts +4 -1
  47. package/dist/features/composites/rows.js +9 -1
  48. package/dist/features/composites/rows.js.map +1 -1
  49. package/dist/features/composites/table-model.js +2 -2
  50. package/dist/features/composites/table-model.js.map +1 -1
  51. package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
  52. package/dist/server/h3.d.ts +8 -0
  53. package/dist/server/handlers/ai.d.ts +2 -1
  54. package/dist/server/handlers/ai.js +2 -1
  55. package/dist/server/handlers/ai.js.map +1 -1
  56. package/dist/server/routes.d.ts +22 -0
  57. package/dist/tree/tree-host.js +1 -1
  58. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  59. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  60. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  61. package/dist/vue/kit/types.d.ts +2 -0
  62. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeInspector.js","names":["_openBlock","_createElementBlock","_unref","_createElementVNode","_toDisplayString","_renderSlot","_Fragment","_renderList","_createVNode","_createBlock","_normalizeStyle","_withCtx","_normalizeClass","_createCommentVNode","_createTextVNode","CompositeFieldEditor"],"sources":["../../../../src/features/composites/components/CompositeInspector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { useId } from 'vue'\nimport { normalizeTagName } from '../../../core/tag-name'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { compositeFieldText, isStructuredField } from '../inspector-model'\nimport { useCompositeInspector } from '../use-composite-inspector'\nimport CompositeFieldEditor from './CompositeFieldEditor.vue'\n\n/**\n * Side panel with the metadata of the document *or* of the Baustein under the caret — one at a time — connected to\n * `editor`. Fills the height of its container and scrolls on its own; the width comes from the host. Open/close is\n * the host's business. The `document` slot carries host-provided document facts (title, folder, dates, presence) at\n * the top of the „Dokument“ section: render them as a `<dl>` with the same grid as below to look native.\n *\n * The „Baustein“ section shows while the caret is in a Baustein of the editor; the „Dokument“ section shows before\n * the editor was ever focused, while the caret is in editor text outside any Baustein, and while `documentFocused`.\n * `documentFocused`: the host's document-level input (its title field) has the focus. While it is true the panel\n * shows the „Dokument“ section and keeps showing it after the input blurs, until the caret returns to the editor.\n */\nconst props = defineProps<{ editor?: Editor, documentFocused?: boolean }>()\n\nconst { Badge: UBadge, FormField: UFormField, Icon: UIcon, Input: UInput, InputMenu: UInputMenu, Select: USelect, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\ndefineSlots<{\n /** Document facts known to the host only, rendered above the facts derived from the editor. */\n document?: () => unknown\n}>()\n\nconst { inspector, section, editable, draft, searchTerm, tagItems, kindOptions, summarized, hasSummary, rows, tags, kinds, mentions, focused } = useCompositeInspector(() => props.editor, () => props.documentFocused)\n\nconst uid = useId()\n\nconst HEX_COLOR = /^#[0-9a-f]{3,8}$/i\n// Same tint as the chip in the document (`--mention-color`), only invalid colors fall back to the primary color.\nconst mentionStyle = (color: string | null) => ({ '--mention-color': color && HEX_COLOR.test(color) ? color : 'var(--sk-primary)' })\n\n// The Nuxt UI inputs emit loosely typed values; the model takes strings.\nfunction onKind(kind: unknown) {\n if (typeof kind === 'string') inspector.value.setKind(kind)\n}\n\nfunction onDescriptionInput(value: unknown) {\n inspector.value.inputDescription(typeof value === 'string' ? value : '')\n}\n\nfunction onTags(value: unknown) {\n inspector.value.setTags(Array.isArray(value) ? value.filter((tag): tag is string => typeof tag === 'string') : [])\n}\n</script>\n\n<template>\n <aside class=\"flex h-full min-h-0 flex-col overflow-y-auto text-sm\" :aria-label=\"t('composites.inspector.label')\">\n <section v-if=\"section === 'document'\" class=\"flex-1 p-4\" :aria-labelledby=\"`${uid}-document`\">\n <h2 :id=\"`${uid}-document`\" class=\"mb-3 text-sm font-semibold text-[var(--sk-fg-highlighted)]\">\n {{ t('composites.inspector.document') }}\n </h2>\n <div class=\"flex flex-col gap-y-2\">\n <slot name=\"document\" />\n <dl class=\"grid grid-cols-[6.5rem_minmax(0,1fr)] items-start gap-x-3 gap-y-2\">\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.inspector.composites') }}\n </dt>\n <dd v-if=\"kinds.length\" class=\"m-0 flex min-w-0 flex-col gap-y-1\">\n <span v-for=\"kind in kinds\" :key=\"kind.id\" class=\"flex min-h-6 min-w-0 items-center gap-2\">\n <UIcon :name=\"kind.icon\" class=\"size-4 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"min-w-0 flex-1 truncate text-[var(--sk-fg-highlighted)]\">{{ kind.label }}</span>\n <span class=\"tabular-nums text-[var(--sk-muted)]\">{{ kind.count }}</span>\n </span>\n <span class=\"min-h-6 leading-6 tabular-nums text-[var(--sk-muted)]\">\n {{ t('composites.inspector.summarized', { n: summarized, total: rows.length }) }}\n </span>\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.tags.label') }}\n </dt>\n <dd v-if=\"tags.length\" class=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n <UBadge v-for=\"tag in tags\" :key=\"tag\" color=\"neutral\" variant=\"subtle\" size=\"sm\" :label=\"`#${tag}`\" class=\"max-w-full\" />\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.inspector.mentions') }}\n </dt>\n <dd v-if=\"mentions.length\" class=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n <span\n v-for=\"mention in mentions\"\n :key=\"mention.id\"\n class=\"inline-flex max-w-full items-center gap-1.5 truncate rounded-sm px-1.5 py-0.5 text-xs font-medium text-[var(--sk-fg-highlighted)]\"\n :style=\"[mentionStyle(mention.color), { background: 'color-mix(in oklab, var(--mention-color) 15%, transparent)' }]\"\n >\n <span class=\"size-1.5 shrink-0 rounded-full\" :style=\"{ background: 'var(--mention-color)' }\" />\n <span class=\"truncate\">@{{ mention.label }}</span>\n </span>\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n </dl>\n </div>\n </section>\n <section v-else-if=\"focused\" class=\"flex-1 p-4\" :aria-labelledby=\"`${uid}-composite`\">\n <h2 :id=\"`${uid}-composite`\" class=\"mb-3 text-sm font-semibold text-[var(--sk-fg-highlighted)]\">\n {{ t('composites.inspector.composite') }}\n </h2>\n\n <div :key=\"focused.key\" class=\"flex flex-col gap-3\">\n <UFormField :label=\"t('composites.inspector.kind')\" size=\"sm\">\n <USelect\n :model-value=\"focused.kind\"\n :items=\"kindOptions\"\n size=\"sm\"\n class=\"w-full\"\n :aria-label=\"t('composites.kind.select')\"\n :disabled=\"!editable\"\n @update:model-value=\"onKind\"\n />\n </UFormField>\n\n <UFormField :label=\"t('composites.inspector.title')\" size=\"sm\" :help=\"editable ? t('composites.inspector.titleHelp') : undefined\">\n <p class=\"min-h-6 truncate leading-6\" :class=\"focused.title ? 'text-[var(--sk-fg-highlighted)]' : 'text-[var(--sk-fg-dimmed)]'\" :title=\"focused.title\">\n {{ focused.title || t('composites.inspector.untitled') }}\n </p>\n </UFormField>\n\n <UFormField\n :label=\"t('composites.inspector.description')\"\n size=\"sm\"\n :help=\"focused.descriptionTextOnly ? undefined : t('composites.inspector.descriptionHelp')\"\n >\n <UTextarea\n :model-value=\"draft\"\n autoresize\n :rows=\"3\"\n :maxrows=\"10\"\n size=\"sm\"\n class=\"w-full\"\n :placeholder=\"t('composites.inspector.description')\"\n :aria-label=\"t('composites.inspector.description')\"\n :readonly=\"!editable || !focused.descriptionTextOnly\"\n @update:model-value=\"onDescriptionInput\"\n @blur=\"inspector.blurDescription\"\n />\n </UFormField>\n\n <UFormField :label=\"t('composites.tags.label')\" size=\"sm\">\n <!-- `position` is required in the object form: InputMenu renders the create row only for 'top' or 'bottom'. -->\n <UInputMenu\n :search-term=\"searchTerm\"\n multiple\n :create-item=\"{ position: 'bottom', when: 'always' }\"\n :model-value=\"focused.tags\"\n :items=\"tagItems\"\n ignore-filter\n :placeholder=\"t('composites.tags.label')\"\n :aria-label=\"t('composites.tags.label')\"\n size=\"sm\"\n icon=\"i-lucide-hash\"\n class=\"w-full\"\n :disabled=\"!editable\"\n @update:model-value=\"onTags\"\n @update:search-term=\"inspector.setSearchTerm\"\n @update:open=\"(isOpen: boolean) => isOpen && inspector.searchTags()\"\n @create=\"inspector.createTag\"\n >\n <template #create-item-label=\"{ item }: { item: string }\">\n {{ t('composites.tags.create', { tag: normalizeTagName(item) || item }) }}\n </template>\n </UInputMenu>\n </UFormField>\n\n <UFormField v-for=\"field in focused.fields\" :key=\"field.key\" :label=\"field.label\" size=\"sm\">\n <!-- Fields whose value is not text (reference, bibliography, derivation) have their own editors, as in the details card; a text input would overwrite the stored object with a string. -->\n <template v-if=\"isStructuredField(field)\">\n <CompositeFieldEditor\n v-if=\"editor\"\n :editor=\"editor\"\n :row=\"focused\"\n :field=\"field\"\n :disabled=\"!editable\"\n />\n </template>\n <USelect\n v-else-if=\"field.type === 'select'\"\n :model-value=\"compositeFieldText(focused, field) || undefined\"\n :items=\"field.options ?? []\"\n :aria-label=\"field.label\"\n placeholder=\"–\"\n size=\"sm\"\n class=\"w-full\"\n :disabled=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n <UTextarea\n v-else-if=\"field.type === 'textarea'\"\n :model-value=\"compositeFieldText(focused, field)\"\n :aria-label=\"field.label\"\n autoresize\n :rows=\"2\"\n :maxrows=\"8\"\n size=\"sm\"\n class=\"w-full\"\n :readonly=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n <UInput\n v-else\n :model-value=\"compositeFieldText(focused, field)\"\n :type=\"field.type\"\n :aria-label=\"field.label\"\n size=\"sm\"\n class=\"w-full\"\n :readonly=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n </UFormField>\n\n <div class=\"flex items-center gap-2 border-t border-[var(--sk-border)] pt-3\">\n <UIcon name=\"i-lucide-sparkles\" class=\"size-4 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"text-[var(--sk-muted)]\">{{ t('composites.inspector.aiStatus') }}</span>\n <span class=\"ml-auto inline-flex items-center gap-1.5\" :class=\"hasSummary ? 'text-[var(--sk-fg-highlighted)]' : 'text-[var(--sk-fg-dimmed)]'\">\n <span class=\"size-2 rounded-full\" :class=\"hasSummary ? 'bg-[var(--sk-success)]' : 'bg-[var(--sk-bg-accented)]'\" />\n {{ hasSummary ? t('composites.inspector.summaryPresent') : t('composites.inspector.summaryMissing') }}\n </span>\n </div>\n </div>\n </section>\n </aside>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompositeInspector.js","names":["_openBlock","_createElementBlock","_unref","_createElementVNode","_toDisplayString","_renderSlot","_Fragment","_renderList","_createVNode","_createBlock","_normalizeStyle","_withCtx","_normalizeClass","_createCommentVNode","_createTextVNode","CompositeFieldEditor"],"sources":["../../../../src/features/composites/components/CompositeInspector.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { useId } from 'vue'\nimport { normalizeTagName } from '../../../core/tag-name'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { compositeFieldText, isStructuredField } from '../inspector-model'\nimport { useCompositeInspector } from '../use-composite-inspector'\nimport CompositeFieldEditor from './CompositeFieldEditor.vue'\n\n/**\n * Side panel with the metadata of the document *or* of the Baustein under the caret — one at a time — connected to\n * `editor`. Fills the height of its container and scrolls on its own; the width comes from the host. Open/close is\n * the host's business. The `document` slot carries host-provided document facts (title, folder, dates, presence) at\n * the top of the „Dokument“ section: render them as a `<dl>` with the same grid as below to look native.\n *\n * The „Baustein“ section shows while the caret is in a Baustein of the editor; the „Dokument“ section shows before\n * the editor was ever focused, while the caret is in editor text outside any Baustein, and while `documentFocused`.\n * `documentFocused`: the host's document-level input (its title field) has the focus. While it is true the panel\n * shows the „Dokument“ section and keeps showing it after the input blurs, until the caret returns to the editor.\n */\nconst props = defineProps<{ editor?: Editor, documentFocused?: boolean }>()\n\nconst { Badge: UBadge, FormField: UFormField, Icon: UIcon, Input: UInput, InputMenu: UInputMenu, Select: USelect, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\ndefineSlots<{\n /** Document facts known to the host only, rendered above the facts derived from the editor. */\n document?: () => unknown\n}>()\n\nconst { inspector, section, editable, draft, searchTerm, tagItems, kindOptions, summarized, hasSummary, rows, tags, kinds, mentions, focused } = useCompositeInspector(() => props.editor, () => props.documentFocused)\n\nconst uid = useId()\n\nconst HEX_COLOR = /^#[0-9a-f]{3,8}$/i\n// Same tint as the chip in the document (`--mention-color`), only invalid colors fall back to the primary color.\nconst mentionStyle = (color: string | null) => ({ '--mention-color': color && HEX_COLOR.test(color) ? color : 'var(--sk-primary)' })\n\n// The Nuxt UI inputs emit loosely typed values; the model takes strings.\nfunction onKind(kind: unknown) {\n if (typeof kind === 'string') inspector.value.setKind(kind)\n}\n\nfunction onDescriptionInput(value: unknown) {\n inspector.value.inputDescription(typeof value === 'string' ? value : '')\n}\n\nfunction onTags(value: unknown) {\n inspector.value.setTags(Array.isArray(value) ? value.filter((tag): tag is string => typeof tag === 'string') : [])\n}\n</script>\n\n<template>\n <aside class=\"flex h-full min-h-0 flex-col overflow-y-auto text-sm\" :aria-label=\"t('composites.inspector.label')\">\n <section v-if=\"section === 'document'\" class=\"flex-1 p-4\" :aria-labelledby=\"`${uid}-document`\">\n <h2 :id=\"`${uid}-document`\" class=\"mb-3 text-sm font-semibold text-[var(--sk-fg-highlighted)]\">\n {{ t('composites.inspector.document') }}\n </h2>\n <div class=\"flex flex-col gap-y-2\">\n <slot name=\"document\" />\n <dl class=\"grid grid-cols-[6.5rem_minmax(0,1fr)] items-start gap-x-3 gap-y-2\">\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.inspector.composites') }}\n </dt>\n <dd v-if=\"kinds.length\" class=\"m-0 flex min-w-0 flex-col gap-y-1\">\n <span v-for=\"kind in kinds\" :key=\"kind.id\" class=\"flex min-h-6 min-w-0 items-center gap-2\">\n <UIcon :name=\"kind.icon\" class=\"size-4 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"min-w-0 flex-1 truncate text-[var(--sk-fg-highlighted)]\">{{ kind.label }}</span>\n <span class=\"tabular-nums text-[var(--sk-muted)]\">{{ kind.count }}</span>\n </span>\n <span class=\"min-h-6 leading-6 tabular-nums text-[var(--sk-muted)]\">\n {{ t('composites.inspector.summarized', { n: summarized, total: rows.length }) }}\n </span>\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.tags.label') }}\n </dt>\n <dd v-if=\"tags.length\" class=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n <UBadge v-for=\"tag in tags\" :key=\"tag\" color=\"neutral\" variant=\"subtle\" size=\"sm\" :label=\"`#${tag}`\" class=\"max-w-full\" />\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n\n <dt class=\"min-h-6 leading-6 text-[var(--sk-muted)]\">\n {{ t('composites.inspector.mentions') }}\n </dt>\n <dd v-if=\"mentions.length\" class=\"m-0 flex min-w-0 flex-wrap items-center gap-1\">\n <span\n v-for=\"mention in mentions\"\n :key=\"mention.id\"\n class=\"inline-flex max-w-full items-center gap-1.5 truncate rounded-sm px-1.5 py-0.5 text-xs font-medium text-[var(--sk-fg-highlighted)]\"\n :style=\"[mentionStyle(mention.color), { background: 'color-mix(in oklab, var(--mention-color) 15%, transparent)' }]\"\n >\n <span class=\"size-1.5 shrink-0 rounded-full\" :style=\"{ background: 'var(--mention-color)' }\" />\n <span class=\"truncate\">@{{ mention.label }}</span>\n </span>\n </dd>\n <dd v-else class=\"m-0 min-h-6 leading-6 text-[var(--sk-fg-dimmed)]\">\n {{ t('composites.inspector.none') }}\n </dd>\n </dl>\n </div>\n </section>\n <section v-else-if=\"focused\" class=\"flex-1 p-4\" :aria-labelledby=\"`${uid}-composite`\">\n <h2 :id=\"`${uid}-composite`\" class=\"mb-3 text-sm font-semibold text-[var(--sk-fg-highlighted)]\">\n {{ t('composites.inspector.composite') }}\n </h2>\n\n <div :key=\"focused.key\" class=\"flex flex-col gap-3\">\n <UFormField :label=\"t('composites.inspector.kind')\" size=\"sm\">\n <USelect\n :model-value=\"focused.kind\"\n :items=\"kindOptions\"\n size=\"sm\"\n class=\"w-full\"\n :aria-label=\"t('composites.kind.select')\"\n :disabled=\"!editable\"\n @update:model-value=\"onKind\"\n />\n </UFormField>\n\n <UFormField :label=\"t('composites.inspector.title')\" size=\"sm\" :help=\"editable ? t('composites.inspector.titleHelp') : undefined\">\n <p class=\"min-h-6 truncate leading-6\" :class=\"focused.title ? 'text-[var(--sk-fg-highlighted)]' : 'text-[var(--sk-fg-dimmed)]'\" :title=\"focused.title\">\n {{ focused.title || t('composites.inspector.untitled') }}\n </p>\n </UFormField>\n\n <UFormField\n :label=\"t('composites.inspector.description')\"\n size=\"sm\"\n :help=\"focused.descriptionTextOnly ? undefined : t('composites.inspector.descriptionHelp')\"\n >\n <UTextarea\n :model-value=\"draft\"\n autoresize\n :rows=\"3\"\n :maxrows=\"10\"\n size=\"sm\"\n class=\"w-full\"\n :placeholder=\"t('composites.inspector.description')\"\n :aria-label=\"t('composites.inspector.description')\"\n :readonly=\"!editable || !focused.descriptionTextOnly\"\n @update:model-value=\"onDescriptionInput\"\n @blur=\"inspector.blurDescription\"\n />\n </UFormField>\n\n <UFormField v-if=\"focused.tagsVisible\" :label=\"t('composites.tags.label')\" size=\"sm\">\n <!-- `position` is required in the object form: InputMenu renders the create row only for 'top' or 'bottom'. -->\n <UInputMenu\n :search-term=\"searchTerm\"\n multiple\n :create-item=\"{ position: 'bottom', when: 'always' }\"\n :model-value=\"focused.tags\"\n :items=\"tagItems\"\n ignore-filter\n :placeholder=\"t('composites.tags.label')\"\n :aria-label=\"t('composites.tags.label')\"\n size=\"sm\"\n icon=\"i-lucide-hash\"\n class=\"w-full\"\n :disabled=\"!editable\"\n @update:model-value=\"onTags\"\n @update:search-term=\"inspector.setSearchTerm\"\n @update:open=\"(isOpen: boolean) => isOpen && inspector.searchTags()\"\n @create=\"inspector.createTag\"\n >\n <template #create-item-label=\"{ item }: { item: string }\">\n {{ t('composites.tags.create', { tag: normalizeTagName(item) || item }) }}\n </template>\n </UInputMenu>\n </UFormField>\n\n <UFormField v-for=\"field in focused.fields\" :key=\"field.key\" :label=\"field.label\" size=\"sm\">\n <!-- Fields whose value is not text (reference, bibliography, derivation) have their own editors, as in the details card; a text input would overwrite the stored object with a string. -->\n <template v-if=\"isStructuredField(field)\">\n <CompositeFieldEditor\n v-if=\"editor\"\n :editor=\"editor\"\n :row=\"focused\"\n :field=\"field\"\n :disabled=\"!editable\"\n />\n </template>\n <USelect\n v-else-if=\"field.type === 'select'\"\n :model-value=\"compositeFieldText(focused, field) || undefined\"\n :items=\"field.options ?? []\"\n :aria-label=\"field.label\"\n placeholder=\"–\"\n size=\"sm\"\n class=\"w-full\"\n :disabled=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n <UTextarea\n v-else-if=\"field.type === 'textarea'\"\n :model-value=\"compositeFieldText(focused, field)\"\n :aria-label=\"field.label\"\n autoresize\n :rows=\"2\"\n :maxrows=\"8\"\n size=\"sm\"\n class=\"w-full\"\n :readonly=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n <UInput\n v-else\n :model-value=\"compositeFieldText(focused, field)\"\n :type=\"field.type\"\n :aria-label=\"field.label\"\n size=\"sm\"\n class=\"w-full\"\n :readonly=\"!editable\"\n @update:model-value=\"(value: unknown) => inspector.setField(field, value)\"\n />\n </UFormField>\n\n <div class=\"flex items-center gap-2 border-t border-[var(--sk-border)] pt-3\">\n <UIcon name=\"i-lucide-sparkles\" class=\"size-4 shrink-0 text-[var(--sk-muted)]\" />\n <span class=\"text-[var(--sk-muted)]\">{{ t('composites.inspector.aiStatus') }}</span>\n <span class=\"ml-auto inline-flex items-center gap-1.5\" :class=\"hasSummary ? 'text-[var(--sk-fg-highlighted)]' : 'text-[var(--sk-fg-dimmed)]'\">\n <span class=\"size-2 rounded-full\" :class=\"hasSummary ? 'bg-[var(--sk-success)]' : 'bg-[var(--sk-bg-accented)]'\" />\n {{ hasSummary ? t('composites.inspector.summaryPresent') : t('composites.inspector.summaryMissing') }}\n </span>\n </div>\n </div>\n </section>\n </aside>\n</template>\n"],"mappings":"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"}
@@ -10,9 +10,9 @@ import { createCompositeRows } from "./rows-model.js";
10
10
  const COMMIT_DELAY_MS = 600;
11
11
  /** Pause after the last change of the tag search term before the metadata source is asked. */
12
12
  const SEARCH_DELAY_MS = 150;
13
- /** Fields whose value is not text (a block ID, a CSL item, a provenance record): the inspector hands them to `CompositeFieldEditor` and never writes a string to them. */
13
+ /** Fields whose value is not text (a block ID, a CSL item, a provenance record, a list of choices): the inspector hands them to `CompositeFieldEditor` and never writes a string to them. */
14
14
  function isStructuredField(field) {
15
- return field.type === "reference" || field.type === "bibliography" || field.type === "derivation";
15
+ return field.type === "reference" || field.type === "bibliography" || field.type === "derivation" || field.type === "multiselect";
16
16
  }
17
17
  /** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */
18
18
  function compositeFieldText(row, field) {
@@ -1 +1 @@
1
- {"version":3,"file":"inspector-model.js","names":[],"sources":["../../../src/features/composites/inspector-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { metadataOf } from '../../core/metadata'\nimport { METADATA_LIST } from '../../core/metadata-types'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { normalizeTagName } from '../../core/tag-name'\nimport type { CompositeMetaField } from './kinds'\nimport { compositeKindsOf } from './nodes'\nimport { compositeDescriptionText, compositeOfRow, type CompositeRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from './rows'\nimport { type CompositeRowsState, createCompositeRows } from './rows-model'\n\n/** Pause after the last keystroke in the description before the draft is written to the document. */\nconst COMMIT_DELAY_MS = 600\n/** Pause after the last change of the tag search term before the metadata source is asked. */\nconst SEARCH_DELAY_MS = 150\n\n/** Which section of the inspector shows: the document's facts, or the properties of the Baustein under the caret. */\nexport type CompositeInspectorSection = 'document' | 'composite'\n\n/** A kind the Baustein can be switched to, as the kind select lists it. */\nexport interface CompositeKindOption {\n value: string\n label: string\n icon: string\n}\n\n/** Everything the inspector renders (see `createCompositeInspector`): the live rows plus the inspector's own state. */\nexport interface CompositeInspectorState extends CompositeRowsState {\n /** `'composite'` only while the caret last was in the editor and a Baustein is focused, else `'document'`. */\n section: CompositeInspectorSection\n /** Whether the editor accepts edits; follows `setEditable` (see `createCompositeInspector`). */\n editable: boolean\n /** The description text as the user sees it in the focused Baustein's textarea; may be ahead of the document until it is committed. */\n draft: string\n /** The text typed into the tag search. */\n searchTerm: string\n /** Tag names the metadata source suggested for the latest search that was not superseded. */\n tagItems: string[]\n /** The kinds the editor was configured with, in configured order; empty without an editor. */\n kindOptions: CompositeKindOption[]\n /** How many Bausteine have a description. */\n summarized: number\n /** The focused Baustein has a description. */\n hasSummary: boolean\n}\n\n/** The inspector model of an editor: observable `state`, the actions of the panel, and `dispose()`. */\nexport interface CompositeInspector {\n readonly state: ReadonlyStore<CompositeInspectorState>\n /**\n * Reports whether the host's document-level input (its title field) has the focus. Only the rising edge switches\n * the section to `'document'`; the section stays there after the input blurs, until the editor takes the focus.\n */\n setDocumentFocused: (focused: boolean) => void\n /** Switches the focused Baustein to `kind`; ignored when it already has it or no Baustein is focused. */\n setKind: (kind: string) => void\n /**\n * The user typed `text` into the description. The draft is written to the document 600 ms after the last call and\n * on `blurDescription`; the Baustein focused at the first call of a pending draft is the one that is written.\n */\n inputDescription: (text: string) => void\n /**\n * The description textarea lost focus: writes the pending draft, then shows what the document holds. The draft\n * shows the normalized text after a write; after a refused write (a peer changed the description since the draft\n * started) it shows the peer's text.\n */\n blurDescription: () => void\n /** Replaces the tags of the focused Baustein. */\n setTags: (tags: readonly string[]) => void\n /** Creates the tag `raw` (normalized) on the focused Baustein and registers it with the metadata source; clears the search term. */\n createTag: (raw: string) => void\n /** Sets the tag search term and schedules a search for it. */\n setSearchTerm: (term: string) => void\n /** Schedules a tag search for the current term (the picker opened). */\n searchTags: () => void\n /**\n * Writes the text `value` to the meta `field` of the focused Baustein; ignored while the editor is not editable and\n * for structured fields (`isStructuredField`), whose values are not text and have their own editors.\n */\n setField: (field: CompositeMetaField, value: unknown) => void\n /** Commits a pending description draft and detaches from the editor. Idempotent; the model is inert afterwards. */\n dispose: () => void\n}\n\n/** Fields whose value is not text (a block ID, a CSL item, a provenance record): the inspector hands them to `CompositeFieldEditor` and never writes a string to them. */\nexport function isStructuredField(field: Pick<CompositeMetaField, 'type'>): boolean {\n return field.type === 'reference' || field.type === 'bibliography' || field.type === 'derivation'\n}\n\n/** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */\nexport function compositeFieldText(row: Pick<CompositeRow, 'meta'> | null, field: Pick<CompositeMetaField, 'key'>): string {\n const value = row?.meta[field.key]\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nfunction sameState(a: CompositeInspectorState, b: CompositeInspectorState): boolean {\n return (Object.keys(a) as (keyof CompositeInspectorState)[]).every(key => Object.is(a[key], b[key]))\n}\n\n/**\n * The properties panel of `editor`: which section shows, the focused Baustein's description draft, the tag search\n * and the writes. Owns its own `createCompositeRows(editor)`, so a focus change and the rows it refers to arrive in\n * one notification and the draft is committed before it is replaced. Create one model per editor (a framework hook\n * follows an editor that changes); without an editor the state is empty and every action is ignored.\n *\n * Section: the caret is either in the host's input (`setDocumentFocused`) or in the editor. Only the editor's `focus`\n * event moves it to the editor (not `selectionUpdate`: collaboration sync moves the selection while the host's input\n * has the focus). A deleted Baustein falls back to the document section.\n *\n * `isEditable` is not reactive: `editable` follows it on every editor `transaction`, so whoever calls `setEditable`\n * must dispatch a transaction afterwards (`EditorSurface` does).\n *\n * Description: edits go to a local `draft` so the asynchronous re-read of the document never overwrites the text\n * (and moves the caret) while it is typed. `base` is the document text the draft started from; the write lands only\n * while the description still holds it, so a peer's change made meanwhile is never replaced. The draft is committed\n * 600 ms after typing pauses, on blur, when another Baustein takes the focus (before the draft is replaced), when\n * the panel leaves the Baustein section, and on `dispose()`. A commit is skipped when the editor is destroyed or not\n * editable, and refused when a peer changed the description; the draft then reloads the live text. A draft that is\n * not dirty follows every change of the focused description.\n *\n * Tag search: each term change restarts a 150 ms debounce; when it fires, the previous search is aborted and the\n * source is asked with a fresh `AbortSignal`. Results of aborted searches are dropped, errors of aborted searches\n * are silent, other errors are logged and keep the old suggestions.\n */\nexport function createCompositeInspector(editor: Editor | undefined): CompositeInspector {\n const rows = createCompositeRows(editor)\n const kindOptions: CompositeKindOption[] = editor && !editor.isDestroyed\n ? compositeKindsOf(editor).map(kind => ({ value: kind.id, label: kind.label, icon: kind.icon }))\n : []\n const isEditable = () => !!editor && !editor.isDestroyed && editor.isEditable\n const focusedRow = () => rows.state.get().focused\n\n let disposed = false\n\n // --- Section ---\n let caretIn: 'document' | 'editor' = 'document'\n let documentFocused = false\n let section: CompositeInspectorSection = 'document'\n let editable = isEditable()\n\n // --- Description draft ---\n let draft = ''\n let base = ''\n let dirty = false\n let pendingRow: CompositeRow | null = null\n let commitTimer: ReturnType<typeof setTimeout> | undefined\n // What the focused Baustein looked like at the last rows notification: a change of the key is a switch, a change of the description a peer's (or our own) write.\n let focusKey = focusedRow()?.key\n let focusDescription = focusedRow()?.description\n\n // --- Tag search ---\n let searchTerm = ''\n let tagItems: string[] = []\n let searchTimer: ReturnType<typeof setTimeout> | undefined\n let pendingSearch: AbortController | undefined\n\n let summarizedRows: CompositeRowsState['rows'] | undefined\n let summarized = 0\n const store = createStore<CompositeInspectorState>(snapshot())\n\n function snapshot(): CompositeInspectorState {\n const read = rows.state.get()\n if (read.rows !== summarizedRows) {\n summarizedRows = read.rows\n summarized = read.rows.filter(row => row.description.trim()).length\n }\n return {\n ...read,\n section,\n editable,\n draft,\n searchTerm,\n tagItems,\n kindOptions,\n summarized,\n hasSummary: !!read.focused?.description.trim(),\n }\n }\n\n function publish() {\n const next = snapshot()\n if (!sameState(store.get(), next)) store.set(next)\n }\n\n function load() {\n dirty = false\n pendingRow = null\n base = focusedRow()?.description ?? ''\n draft = base\n }\n\n /** Writes the pending draft; `false` when there was nothing to write or the write was refused. Does not touch `draft`. */\n function commit(): boolean {\n clearTimeout(commitTimer)\n if (!dirty || !pendingRow) return false\n dirty = false\n const row = pendingRow\n pendingRow = null\n if (!editor || editor.isDestroyed || !editor.isEditable) return false\n if (!setCompositeDescription(editor, row, draft, base)) return false\n base = compositeDescriptionText(draft)\n return true\n }\n\n function refreshSection() {\n const next = caretIn === 'editor' && focusedRow() ? 'composite' : 'document'\n if (section === 'composite' && next !== 'composite') commit()\n section = next\n }\n\n // A switch commits the draft of the previous Baustein first (`draft` still holds its text), then loads the new one.\n // A change of the description reaches an untouched draft, also while the textarea has focus. A draft with unsaved\n // typing keeps it; if a peer changed the description meanwhile, its commit is refused and the peer's text is\n // loaded instead.\n function followRows() {\n const focused = focusedRow()\n const key = focused?.key\n const description = focused?.description\n if (key !== focusKey) {\n focusKey = key\n focusDescription = description\n commit()\n load()\n }\n else if (description !== focusDescription) {\n focusDescription = description\n if (dirty) {\n if (description !== base && !commit()) load()\n }\n else if (description !== base) {\n load()\n }\n }\n refreshSection()\n publish()\n }\n\n function scheduleSearch() {\n clearTimeout(searchTimer)\n searchTimer = setTimeout(async () => {\n if (!editor || editor.isDestroyed) return\n pendingSearch?.abort()\n const controller = pendingSearch = new AbortController()\n try {\n const found = await metadataOf(editor).search(METADATA_LIST.hashtags, { query: searchTerm, editor, signal: controller.signal })\n if (controller.signal.aborted) return\n tagItems = found.map(item => item.label)\n publish()\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Tag-Vorschläge konnten nicht geladen werden', error)\n }\n }, SEARCH_DELAY_MS)\n }\n\n function setSearchTerm(term: string) {\n if (disposed || term === searchTerm) return\n searchTerm = term\n scheduleSearch()\n publish()\n }\n\n const syncEditable = () => {\n const next = isEditable()\n if (next === editable) return\n editable = next\n publish()\n }\n const toEditor = () => {\n caretIn = 'editor'\n refreshSection()\n publish()\n }\n\n load()\n refreshSection()\n const stopRows = rows.state.subscribe(followRows)\n if (editor && !editor.isDestroyed) {\n editor.on('transaction', syncEditable)\n // Only `focus`, not `selectionUpdate`: a collaboration sync can move the selection while the host's input has the focus.\n editor.on('focus', toEditor)\n }\n publish()\n\n return {\n state: store,\n\n setDocumentFocused(focused) {\n if (disposed) return\n const rising = focused && !documentFocused\n documentFocused = focused\n if (!rising) return\n caretIn = 'document'\n refreshSection()\n publish()\n },\n\n setKind(kind) {\n const row = focusedRow()\n const found = editor && row ? compositeOfRow(editor.state.doc, row) : null\n if (!editor || !found || kind === row?.kind) return\n editor.commands.setCompositeKind(kind, found.pos)\n },\n\n inputDescription(text) {\n if (disposed) return\n draft = text\n dirty = true\n pendingRow ??= focusedRow()\n clearTimeout(commitTimer)\n commitTimer = setTimeout(commit, COMMIT_DELAY_MS)\n publish()\n },\n\n blurDescription() {\n if (disposed) return\n if (commit()) draft = base\n else load()\n publish()\n },\n\n setTags(tags) {\n const row = focusedRow()\n if (editor && row) setCompositeTags(editor, row, tags)\n },\n\n // „Neu Tag“ → `neu-tag`. A name that normalizes to an already chosen tag is ignored; only unknown names are reported as created.\n createTag(raw) {\n const row = focusedRow()\n const name = normalizeTagName(raw)\n setSearchTerm('')\n if (!editor || !row || !name || row.tags.includes(name)) return\n setCompositeTags(editor, row, [...row.tags, name])\n if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, { id: name, label: name })\n },\n\n setSearchTerm,\n\n searchTags() {\n if (!disposed) scheduleSearch()\n },\n\n setField(field, value) {\n const row = focusedRow()\n if (editor && isEditable() && row && !isStructuredField(field)) setCompositeMetaField(editor, row, field.key, value)\n },\n\n dispose() {\n if (disposed) return\n commit()\n disposed = true\n clearTimeout(searchTimer)\n pendingSearch?.abort()\n stopRows()\n if (editor && !editor.isDestroyed) {\n editor.off('transaction', syncEditable)\n editor.off('focus', toEditor)\n }\n rows.dispose()\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"inspector-model.js","names":[],"sources":["../../../src/features/composites/inspector-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { metadataOf } from '../../core/metadata'\nimport { METADATA_LIST } from '../../core/metadata-types'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { normalizeTagName } from '../../core/tag-name'\nimport type { CompositeMetaField } from './kinds'\nimport { compositeKindsOf } from './nodes'\nimport { compositeDescriptionText, compositeOfRow, type CompositeRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from './rows'\nimport { type CompositeRowsState, createCompositeRows } from './rows-model'\n\n/** Pause after the last keystroke in the description before the draft is written to the document. */\nconst COMMIT_DELAY_MS = 600\n/** Pause after the last change of the tag search term before the metadata source is asked. */\nconst SEARCH_DELAY_MS = 150\n\n/** Which section of the inspector shows: the document's facts, or the properties of the Baustein under the caret. */\nexport type CompositeInspectorSection = 'document' | 'composite'\n\n/** A kind the Baustein can be switched to, as the kind select lists it. */\nexport interface CompositeKindOption {\n value: string\n label: string\n icon: string\n}\n\n/** Everything the inspector renders (see `createCompositeInspector`): the live rows plus the inspector's own state. */\nexport interface CompositeInspectorState extends CompositeRowsState {\n /** `'composite'` only while the caret last was in the editor and a Baustein is focused, else `'document'`. */\n section: CompositeInspectorSection\n /** Whether the editor accepts edits; follows `setEditable` (see `createCompositeInspector`). */\n editable: boolean\n /** The description text as the user sees it in the focused Baustein's textarea; may be ahead of the document until it is committed. */\n draft: string\n /** The text typed into the tag search. */\n searchTerm: string\n /** Tag names the metadata source suggested for the latest search that was not superseded. */\n tagItems: string[]\n /** The kinds the editor was configured with, in configured order; empty without an editor. */\n kindOptions: CompositeKindOption[]\n /** How many Bausteine have a description. */\n summarized: number\n /** The focused Baustein has a description. */\n hasSummary: boolean\n}\n\n/** The inspector model of an editor: observable `state`, the actions of the panel, and `dispose()`. */\nexport interface CompositeInspector {\n readonly state: ReadonlyStore<CompositeInspectorState>\n /**\n * Reports whether the host's document-level input (its title field) has the focus. Only the rising edge switches\n * the section to `'document'`; the section stays there after the input blurs, until the editor takes the focus.\n */\n setDocumentFocused: (focused: boolean) => void\n /** Switches the focused Baustein to `kind`; ignored when it already has it or no Baustein is focused. */\n setKind: (kind: string) => void\n /**\n * The user typed `text` into the description. The draft is written to the document 600 ms after the last call and\n * on `blurDescription`; the Baustein focused at the first call of a pending draft is the one that is written.\n */\n inputDescription: (text: string) => void\n /**\n * The description textarea lost focus: writes the pending draft, then shows what the document holds. The draft\n * shows the normalized text after a write; after a refused write (a peer changed the description since the draft\n * started) it shows the peer's text.\n */\n blurDescription: () => void\n /** Replaces the tags of the focused Baustein. */\n setTags: (tags: readonly string[]) => void\n /** Creates the tag `raw` (normalized) on the focused Baustein and registers it with the metadata source; clears the search term. */\n createTag: (raw: string) => void\n /** Sets the tag search term and schedules a search for it. */\n setSearchTerm: (term: string) => void\n /** Schedules a tag search for the current term (the picker opened). */\n searchTags: () => void\n /**\n * Writes the text `value` to the meta `field` of the focused Baustein; ignored while the editor is not editable and\n * for structured fields (`isStructuredField`), whose values are not text and have their own editors.\n */\n setField: (field: CompositeMetaField, value: unknown) => void\n /** Commits a pending description draft and detaches from the editor. Idempotent; the model is inert afterwards. */\n dispose: () => void\n}\n\n/** Fields whose value is not text (a block ID, a CSL item, a provenance record, a list of choices): the inspector hands them to `CompositeFieldEditor` and never writes a string to them. */\nexport function isStructuredField(field: Pick<CompositeMetaField, 'type'>): boolean {\n return field.type === 'reference' || field.type === 'bibliography' || field.type === 'derivation' || field.type === 'multiselect'\n}\n\n/** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */\nexport function compositeFieldText(row: Pick<CompositeRow, 'meta'> | null, field: Pick<CompositeMetaField, 'key'>): string {\n const value = row?.meta[field.key]\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nfunction sameState(a: CompositeInspectorState, b: CompositeInspectorState): boolean {\n return (Object.keys(a) as (keyof CompositeInspectorState)[]).every(key => Object.is(a[key], b[key]))\n}\n\n/**\n * The properties panel of `editor`: which section shows, the focused Baustein's description draft, the tag search\n * and the writes. Owns its own `createCompositeRows(editor)`, so a focus change and the rows it refers to arrive in\n * one notification and the draft is committed before it is replaced. Create one model per editor (a framework hook\n * follows an editor that changes); without an editor the state is empty and every action is ignored.\n *\n * Section: the caret is either in the host's input (`setDocumentFocused`) or in the editor. Only the editor's `focus`\n * event moves it to the editor (not `selectionUpdate`: collaboration sync moves the selection while the host's input\n * has the focus). A deleted Baustein falls back to the document section.\n *\n * `isEditable` is not reactive: `editable` follows it on every editor `transaction`, so whoever calls `setEditable`\n * must dispatch a transaction afterwards (`EditorSurface` does).\n *\n * Description: edits go to a local `draft` so the asynchronous re-read of the document never overwrites the text\n * (and moves the caret) while it is typed. `base` is the document text the draft started from; the write lands only\n * while the description still holds it, so a peer's change made meanwhile is never replaced. The draft is committed\n * 600 ms after typing pauses, on blur, when another Baustein takes the focus (before the draft is replaced), when\n * the panel leaves the Baustein section, and on `dispose()`. A commit is skipped when the editor is destroyed or not\n * editable, and refused when a peer changed the description; the draft then reloads the live text. A draft that is\n * not dirty follows every change of the focused description.\n *\n * Tag search: each term change restarts a 150 ms debounce; when it fires, the previous search is aborted and the\n * source is asked with a fresh `AbortSignal`. Results of aborted searches are dropped, errors of aborted searches\n * are silent, other errors are logged and keep the old suggestions.\n */\nexport function createCompositeInspector(editor: Editor | undefined): CompositeInspector {\n const rows = createCompositeRows(editor)\n const kindOptions: CompositeKindOption[] = editor && !editor.isDestroyed\n ? compositeKindsOf(editor).map(kind => ({ value: kind.id, label: kind.label, icon: kind.icon }))\n : []\n const isEditable = () => !!editor && !editor.isDestroyed && editor.isEditable\n const focusedRow = () => rows.state.get().focused\n\n let disposed = false\n\n // --- Section ---\n let caretIn: 'document' | 'editor' = 'document'\n let documentFocused = false\n let section: CompositeInspectorSection = 'document'\n let editable = isEditable()\n\n // --- Description draft ---\n let draft = ''\n let base = ''\n let dirty = false\n let pendingRow: CompositeRow | null = null\n let commitTimer: ReturnType<typeof setTimeout> | undefined\n // What the focused Baustein looked like at the last rows notification: a change of the key is a switch, a change of the description a peer's (or our own) write.\n let focusKey = focusedRow()?.key\n let focusDescription = focusedRow()?.description\n\n // --- Tag search ---\n let searchTerm = ''\n let tagItems: string[] = []\n let searchTimer: ReturnType<typeof setTimeout> | undefined\n let pendingSearch: AbortController | undefined\n\n let summarizedRows: CompositeRowsState['rows'] | undefined\n let summarized = 0\n const store = createStore<CompositeInspectorState>(snapshot())\n\n function snapshot(): CompositeInspectorState {\n const read = rows.state.get()\n if (read.rows !== summarizedRows) {\n summarizedRows = read.rows\n summarized = read.rows.filter(row => row.description.trim()).length\n }\n return {\n ...read,\n section,\n editable,\n draft,\n searchTerm,\n tagItems,\n kindOptions,\n summarized,\n hasSummary: !!read.focused?.description.trim(),\n }\n }\n\n function publish() {\n const next = snapshot()\n if (!sameState(store.get(), next)) store.set(next)\n }\n\n function load() {\n dirty = false\n pendingRow = null\n base = focusedRow()?.description ?? ''\n draft = base\n }\n\n /** Writes the pending draft; `false` when there was nothing to write or the write was refused. Does not touch `draft`. */\n function commit(): boolean {\n clearTimeout(commitTimer)\n if (!dirty || !pendingRow) return false\n dirty = false\n const row = pendingRow\n pendingRow = null\n if (!editor || editor.isDestroyed || !editor.isEditable) return false\n if (!setCompositeDescription(editor, row, draft, base)) return false\n base = compositeDescriptionText(draft)\n return true\n }\n\n function refreshSection() {\n const next = caretIn === 'editor' && focusedRow() ? 'composite' : 'document'\n if (section === 'composite' && next !== 'composite') commit()\n section = next\n }\n\n // A switch commits the draft of the previous Baustein first (`draft` still holds its text), then loads the new one.\n // A change of the description reaches an untouched draft, also while the textarea has focus. A draft with unsaved\n // typing keeps it; if a peer changed the description meanwhile, its commit is refused and the peer's text is\n // loaded instead.\n function followRows() {\n const focused = focusedRow()\n const key = focused?.key\n const description = focused?.description\n if (key !== focusKey) {\n focusKey = key\n focusDescription = description\n commit()\n load()\n }\n else if (description !== focusDescription) {\n focusDescription = description\n if (dirty) {\n if (description !== base && !commit()) load()\n }\n else if (description !== base) {\n load()\n }\n }\n refreshSection()\n publish()\n }\n\n function scheduleSearch() {\n clearTimeout(searchTimer)\n searchTimer = setTimeout(async () => {\n if (!editor || editor.isDestroyed) return\n pendingSearch?.abort()\n const controller = pendingSearch = new AbortController()\n try {\n const found = await metadataOf(editor).search(METADATA_LIST.hashtags, { query: searchTerm, editor, signal: controller.signal })\n if (controller.signal.aborted) return\n tagItems = found.map(item => item.label)\n publish()\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Tag-Vorschläge konnten nicht geladen werden', error)\n }\n }, SEARCH_DELAY_MS)\n }\n\n function setSearchTerm(term: string) {\n if (disposed || term === searchTerm) return\n searchTerm = term\n scheduleSearch()\n publish()\n }\n\n const syncEditable = () => {\n const next = isEditable()\n if (next === editable) return\n editable = next\n publish()\n }\n const toEditor = () => {\n caretIn = 'editor'\n refreshSection()\n publish()\n }\n\n load()\n refreshSection()\n const stopRows = rows.state.subscribe(followRows)\n if (editor && !editor.isDestroyed) {\n editor.on('transaction', syncEditable)\n // Only `focus`, not `selectionUpdate`: a collaboration sync can move the selection while the host's input has the focus.\n editor.on('focus', toEditor)\n }\n publish()\n\n return {\n state: store,\n\n setDocumentFocused(focused) {\n if (disposed) return\n const rising = focused && !documentFocused\n documentFocused = focused\n if (!rising) return\n caretIn = 'document'\n refreshSection()\n publish()\n },\n\n setKind(kind) {\n const row = focusedRow()\n const found = editor && row ? compositeOfRow(editor.state.doc, row) : null\n if (!editor || !found || kind === row?.kind) return\n editor.commands.setCompositeKind(kind, found.pos)\n },\n\n inputDescription(text) {\n if (disposed) return\n draft = text\n dirty = true\n pendingRow ??= focusedRow()\n clearTimeout(commitTimer)\n commitTimer = setTimeout(commit, COMMIT_DELAY_MS)\n publish()\n },\n\n blurDescription() {\n if (disposed) return\n if (commit()) draft = base\n else load()\n publish()\n },\n\n setTags(tags) {\n const row = focusedRow()\n if (editor && row) setCompositeTags(editor, row, tags)\n },\n\n // „Neu Tag“ → `neu-tag`. A name that normalizes to an already chosen tag is ignored; only unknown names are reported as created.\n createTag(raw) {\n const row = focusedRow()\n const name = normalizeTagName(raw)\n setSearchTerm('')\n if (!editor || !row || !name || row.tags.includes(name)) return\n setCompositeTags(editor, row, [...row.tags, name])\n if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, { id: name, label: name })\n },\n\n setSearchTerm,\n\n searchTags() {\n if (!disposed) scheduleSearch()\n },\n\n setField(field, value) {\n const row = focusedRow()\n if (editor && isEditable() && row && !isStructuredField(field)) setCompositeMetaField(editor, row, field.key, value)\n },\n\n dispose() {\n if (disposed) return\n commit()\n disposed = true\n clearTimeout(searchTimer)\n pendingSearch?.abort()\n stopRows()\n if (editor && !editor.isDestroyed) {\n editor.off('transaction', syncEditable)\n editor.off('focus', toEditor)\n }\n rows.dispose()\n },\n }\n}\n"],"mappings":"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"}
@@ -6,9 +6,12 @@ export declare const SOURCE_MEDIA_TYPES: readonly ["image", "audio", "video", "f
6
6
  export interface CompositeMetaField {
7
7
  key: string;
8
8
  label: string;
9
- /** `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein. */
10
- type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'reference' | 'bibliography' | 'derivation';
11
- /** Choices of a `select` field. */
9
+ /**
10
+ * `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein;
11
+ * `multiselect`: several of `options`, stored as `string[]` (the key is removed when none is chosen).
12
+ */
13
+ type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'multiselect' | 'reference' | 'bibliography' | 'derivation';
14
+ /** Choices of a `select` or `multiselect` field (required for `multiselect`). */
12
15
  options?: {
13
16
  value: string;
14
17
  label: string;
@@ -38,6 +41,10 @@ export interface CompositeKind {
38
41
  titlePlaceholder: string;
39
42
  descriptionPlaceholder: string;
40
43
  fields: CompositeMetaField[];
44
+ /** How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty). */
45
+ chrome?: {
46
+ tags?: boolean;
47
+ };
41
48
  /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */
42
49
  ai?: CompositeKindAi;
43
50
  /**
@@ -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 /** `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein. */\n type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'reference' | 'bibliography' | 'derivation'\n /** Choices of a `select` field. */\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 /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n /**\n * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`\n * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.\n * Only new moves are checked; documents that already nest against it stay as they are.\n */\n accepts?: readonly string[] | 'all'\n /** Present on source-holding kinds: the media block types the kind's source may be; `sourceOf` returns the first body child of one of these. */\n source?: readonly (typeof SOURCE_MEDIA_TYPES)[number][]\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [], accepts: 'all' },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n // i18n-ignore: model-facing kind purpose (German prompt text, not UI)\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [], ai: { purpose: 'Eigene Notiz zu einer Quelle oder einem Thema: Zusammenfassung, Gedanken, offene Fragen.' } },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), source: [...SOURCE_MEDIA_TYPES], fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n ] },\n // i18n-ignore-start: model-facing kind purposes and instructions (German prompt text, not UI)\n { id: 'recording', label: t('composites.kind.recording.label'), icon: 'i-lucide-mic', titlePlaceholder: t('composites.kind.recording.title'), descriptionPlaceholder: t('composites.kind.recording.description'), accepts: 'all', source: ['audio', 'video'], fields: [\n ...importFields(t, 'recording'),\n { key: 'recordedAt', label: t('composites.kind.recording.field.recordedAt'), type: 'date' },\n { key: 'participants', label: t('composites.kind.recording.field.participants'), type: 'text' },\n { key: 'durationSeconds', label: t('composites.kind.recording.field.durationSeconds'), type: 'number' },\n ], ai: { purpose: 'Aufnahme (Audio oder Video) als Quelle; der Inhalt hält Lesefassungen wie das Transkript und eigene Notizen.' } },\n { id: 'paper', label: t('composites.kind.paper.label'), icon: 'i-lucide-scroll-text', titlePlaceholder: t('composites.kind.paper.title'), descriptionPlaceholder: t('composites.kind.paper.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'paper'),\n { key: 'bib', label: t('composites.kind.paper.field.bib'), type: 'bibliography' },\n { key: 'doi', label: t('composites.kind.paper.field.doi'), type: 'text' },\n ], ai: { purpose: 'Wissenschaftliche Arbeit als Quelle (PDF) mit Literaturangabe; der Inhalt hält Volltext, Zusammenfassungen und Notizen.' } },\n { id: 'book', label: t('composites.kind.book.label'), icon: 'i-lucide-book', titlePlaceholder: t('composites.kind.book.title'), descriptionPlaceholder: t('composites.kind.book.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'book'),\n { key: 'bib', label: t('composites.kind.book.field.bib'), type: 'bibliography' },\n { key: 'isbn', label: t('composites.kind.book.field.isbn'), type: 'text' },\n ], ai: { purpose: 'Buch als Quelle (PDF) mit Literaturangabe; der Inhalt hält den Volltext, gegliedert in Kapitel, und Notizen.' } },\n { id: 'figure', label: t('composites.kind.figure.label'), icon: 'i-lucide-image', titlePlaceholder: t('composites.kind.figure.title'), descriptionPlaceholder: t('composites.kind.figure.description'), accepts: 'all', source: ['image'], fields: [\n ...importFields(t, 'figure'),\n { key: 'credit', label: t('composites.kind.figure.field.credit'), type: 'text' },\n ], ai: { purpose: 'Bild als Quelle; der Inhalt hält die Bildbeschreibung und eigene Notizen.' } },\n { id: 'rendition', label: t('composites.kind.rendition.label'), icon: 'i-lucide-captions', titlePlaceholder: t('composites.kind.rendition.title'), descriptionPlaceholder: t('composites.kind.rendition.description'), accepts: 'all', fields: [\n { key: 'derivation', label: t('composites.kind.rendition.field.derivation'), type: 'derivation' },\n { key: 'language', label: t('composites.kind.rendition.field.language'), type: 'text' },\n ], ai: {\n purpose: 'Lesbare Fassung einer Quelle (Transkript, Volltext, Beschreibung, Übersetzung)',\n instructions: 'Übertrage den Inhalt vollständig und gut lesbar; nichts weglassen, nichts hinzuerfinden.',\n } },\n // i18n-ignore-end\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The four fields the import of a file fills in (`wrapImport` of `composite-media`), shared by every source-holding kind. */\nfunction importFields(t: Translate, kind: 'recording' | 'paper' | 'book' | 'figure'): CompositeMetaField[] {\n return [\n { key: 'filename', label: t(`composites.kind.${kind}.field.filename`), type: 'text' },\n { key: 'mimeType', label: t(`composites.kind.${kind}.field.mimeType`), type: 'text' },\n { key: 'size', label: t(`composites.kind.${kind}.field.size`), type: 'number' },\n { key: 'importedAt', label: t(`composites.kind.${kind}.field.importedAt`), type: 'date' },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n\n/** Kinds that accept every Baustein kind when their definition sets no `accepts`. */\nconst ACCEPTS_ALL_BY_DEFAULT: Record<string, true> = { folder: true, document: true }\n\n/**\n * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind\n * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.\n */\nexport function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean {\n const accepts = kinds.find(kind => kind.id === parentKind)?.accepts\n if (accepts === undefined) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind)\n return accepts === 'all' || accepts.includes(childKind)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kinds.js","names":[],"sources":["../../../src/features/composites/kinds.ts"],"sourcesContent":["// Vue-free and server-importable: the kind registry of the composite block family („Bausteine“).\nimport { createTranslator, type Translate } from '../../i18n'\n\n/** Media block types a source-holding kind's source may be (`CompositeKind.source`). */\nexport const SOURCE_MEDIA_TYPES = ['image', 'audio', 'video', 'file'] as const\n\n/** One metadata field of a composite kind, stored under `key` in the composite's `meta` attribute. */\nexport interface CompositeMetaField {\n key: string\n label: string\n /**\n * `bibliography`: CSL-JSON item under `meta.bib`; `derivation`: read-only provenance of a derived Baustein; `reference`: block id of another Baustein;\n * `multiselect`: several of `options`, stored as `string[]` (the key is removed when none is chosen).\n */\n type: 'text' | 'textarea' | 'date' | 'number' | 'select' | 'multiselect' | 'reference' | 'bibliography' | 'derivation'\n /** Choices of a `select` or `multiselect` field (required for `multiselect`). */\n options?: { value: string, label: string }[]\n /** `reference`: kind ids the field may point to; absent = any Baustein. */\n targets?: readonly string[]\n}\n\n/**\n * What a kind tells the model beyond its fields. `purpose` goes into every AI request's block model (chat, generate\n * tasks); `instructions`, `instructionsField` and `skeleton` are read only by „Baustein füllen (KI)“ (`fillComposite`).\n */\nexport interface CompositeKindAi {\n /** One or two German sentences: what a Baustein of this kind is for and what its body holds. */\n purpose: string\n /** Default instruction for filling the body of a Baustein of this kind (what to write, structure, tone). */\n instructions?: string\n /** Key of a `textarea` field of this kind; a Baustein whose value is non-empty replaces `instructions` with it. */\n instructionsField?: string\n /** Target text (Vorlage) as Markdown; `{key}` placeholders are replaced by the Baustein's `meta[key]` before it goes to the model, unknown ones stay. */\n skeleton?: string\n}\n\n/** A composite kind: what its `kind` attribute selects (label, icon, chrome placeholders, meta fields). */\nexport interface CompositeKind {\n id: string\n label: string\n icon: string\n titlePlaceholder: string\n descriptionPlaceholder: string\n fields: CompositeMetaField[]\n /** How much of the chrome the kind shows. Absent: all of it. `tags: false` hides the tag row of this kind (the node stays in the schema, empty). */\n chrome?: { tags?: boolean }\n /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n /**\n * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`\n * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.\n * Only new moves are checked; documents that already nest against it stay as they are.\n */\n accepts?: readonly string[] | 'all'\n /** Present on source-holding kinds: the media block types the kind's source may be; `sourceOf` returns the first body child of one of these. */\n source?: readonly (typeof SOURCE_MEDIA_TYPES)[number][]\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [], accepts: 'all' },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n // i18n-ignore: model-facing kind purpose (German prompt text, not UI)\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [], ai: { purpose: 'Eigene Notiz zu einer Quelle oder einem Thema: Zusammenfassung, Gedanken, offene Fragen.' } },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), source: [...SOURCE_MEDIA_TYPES], fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n ] },\n // i18n-ignore-start: model-facing kind purposes and instructions (German prompt text, not UI)\n { id: 'recording', label: t('composites.kind.recording.label'), icon: 'i-lucide-mic', titlePlaceholder: t('composites.kind.recording.title'), descriptionPlaceholder: t('composites.kind.recording.description'), accepts: 'all', source: ['audio', 'video'], fields: [\n ...importFields(t, 'recording'),\n { key: 'recordedAt', label: t('composites.kind.recording.field.recordedAt'), type: 'date' },\n { key: 'participants', label: t('composites.kind.recording.field.participants'), type: 'text' },\n { key: 'durationSeconds', label: t('composites.kind.recording.field.durationSeconds'), type: 'number' },\n ], ai: { purpose: 'Aufnahme (Audio oder Video) als Quelle; der Inhalt hält Lesefassungen wie das Transkript und eigene Notizen.' } },\n { id: 'paper', label: t('composites.kind.paper.label'), icon: 'i-lucide-scroll-text', titlePlaceholder: t('composites.kind.paper.title'), descriptionPlaceholder: t('composites.kind.paper.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'paper'),\n { key: 'bib', label: t('composites.kind.paper.field.bib'), type: 'bibliography' },\n { key: 'doi', label: t('composites.kind.paper.field.doi'), type: 'text' },\n ], ai: { purpose: 'Wissenschaftliche Arbeit als Quelle (PDF) mit Literaturangabe; der Inhalt hält Volltext, Zusammenfassungen und Notizen.' } },\n { id: 'book', label: t('composites.kind.book.label'), icon: 'i-lucide-book', titlePlaceholder: t('composites.kind.book.title'), descriptionPlaceholder: t('composites.kind.book.description'), accepts: 'all', source: ['file'], fields: [\n ...importFields(t, 'book'),\n { key: 'bib', label: t('composites.kind.book.field.bib'), type: 'bibliography' },\n { key: 'isbn', label: t('composites.kind.book.field.isbn'), type: 'text' },\n ], ai: { purpose: 'Buch als Quelle (PDF) mit Literaturangabe; der Inhalt hält den Volltext, gegliedert in Kapitel, und Notizen.' } },\n { id: 'figure', label: t('composites.kind.figure.label'), icon: 'i-lucide-image', titlePlaceholder: t('composites.kind.figure.title'), descriptionPlaceholder: t('composites.kind.figure.description'), accepts: 'all', source: ['image'], fields: [\n ...importFields(t, 'figure'),\n { key: 'credit', label: t('composites.kind.figure.field.credit'), type: 'text' },\n ], ai: { purpose: 'Bild als Quelle; der Inhalt hält die Bildbeschreibung und eigene Notizen.' } },\n { id: 'rendition', label: t('composites.kind.rendition.label'), icon: 'i-lucide-captions', titlePlaceholder: t('composites.kind.rendition.title'), descriptionPlaceholder: t('composites.kind.rendition.description'), accepts: 'all', fields: [\n { key: 'derivation', label: t('composites.kind.rendition.field.derivation'), type: 'derivation' },\n { key: 'language', label: t('composites.kind.rendition.field.language'), type: 'text' },\n ], ai: {\n purpose: 'Lesbare Fassung einer Quelle (Transkript, Volltext, Beschreibung, Übersetzung)',\n instructions: 'Übertrage den Inhalt vollständig und gut lesbar; nichts weglassen, nichts hinzuerfinden.',\n } },\n // i18n-ignore-end\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The four fields the import of a file fills in (`wrapImport` of `composite-media`), shared by every source-holding kind. */\nfunction importFields(t: Translate, kind: 'recording' | 'paper' | 'book' | 'figure'): CompositeMetaField[] {\n return [\n { key: 'filename', label: t(`composites.kind.${kind}.field.filename`), type: 'text' },\n { key: 'mimeType', label: t(`composites.kind.${kind}.field.mimeType`), type: 'text' },\n { key: 'size', label: t(`composites.kind.${kind}.field.size`), type: 'number' },\n { key: 'importedAt', label: t(`composites.kind.${kind}.field.importedAt`), type: 'date' },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n\n/** Kinds that accept every Baustein kind when their definition sets no `accepts`. */\nconst ACCEPTS_ALL_BY_DEFAULT: Record<string, true> = { folder: true, document: true }\n\n/**\n * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind\n * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.\n */\nexport function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean {\n const accepts = kinds.find(kind => kind.id === parentKind)?.accepts\n if (accepts === undefined) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind)\n return accepts === 'all' || accepts.includes(childKind)\n}\n"],"mappings":"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"}
@@ -3,9 +3,13 @@ import { compositeKind } from "./kinds.js";
3
3
  import { citeShort } from "./bibliography.js";
4
4
  import { derivationMethodLabel } from "./derived-chrome.js";
5
5
  //#region src/features/composites/meta-display.ts
6
- /** The stored value of a meta field as editable text (`''` when unset). */
6
+ /**
7
+ * The stored value of a meta field as text (`''` when unset). An array (`multiselect`) reads as its entries joined by
8
+ * `, `: the labels of the matching `options`, else the stored values.
9
+ */
7
10
  function metaText(row, field) {
8
11
  const value = row.meta[field.key];
12
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string").map((item) => field.options?.find((option) => option.value === item)?.label ?? item).join(", ");
9
13
  return typeof value === "string" || typeof value === "number" ? String(value) : "";
10
14
  }
11
15
  const dateFormat = new Intl.DateTimeFormat("de-DE", {
@@ -1 +1 @@
1
- {"version":3,"file":"meta-display.js","names":[],"sources":["../../../src/features/composites/meta-display.ts"],"sourcesContent":["import type { Node as PMNode } from '@tiptap/pm/model'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport type { Translate } from '../../i18n'\nimport { type CslItem, citeShort } from './bibliography'\nimport type { Derivation } from './derivation'\nimport { derivationMethodLabel } from './derived-chrome'\nimport { type CompositeKind, type CompositeMetaField, compositeKind } from './kinds'\nimport type { CompositeRow } from './rows'\n\n/** The stored value of a meta field as editable text (`''` when unset). */\nexport function metaText(row: Pick<CompositeRow, 'meta'>, field: Pick<CompositeMetaField, 'key'>): string {\n const value = row.meta[field.key]\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nconst dateFormat = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })\n\n/** `2026-05-01` → `01.05.2026`; parsed by hand because `new Date('2026-05-01')` is UTC and can show the previous day. */\nexport function formatDate(value: string): string {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value)\n const date = match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value)\n return Number.isNaN(date.getTime()) ? value : dateFormat.format(date)\n}\n\n/**\n * The text a field's value stands for in a table cell: `reference` the title of the Baustein it points to (its kind label\n * when untitled, `''` when it is gone or `doc` is unknown), `bibliography` the short citation, `derivation`\n * `method · date` (the method as words when `t` is given, else its stored id); every other type its stored text.\n */\nexport function fieldDisplay(row: Pick<CompositeRow, 'meta' | 'title'>, field: Pick<CompositeMetaField, 'key' | 'type'>, doc: PMNode | null, kinds: readonly CompositeKind[], t?: Translate): string {\n const value = row.meta[field.key]\n if (field.type === 'reference') {\n if (typeof value !== 'string' || !value || !doc) return ''\n const target = findBlockById(doc, value)?.node\n if (target?.type.name !== 'composite') return ''\n return target.child(0).textContent.trim() || compositeKind(kinds, String(target.attrs.kind)).label\n }\n if (field.type === 'bibliography') {\n const bib = value as Partial<CslItem> | null | undefined\n return bib && typeof bib === 'object' && typeof bib.type === 'string' ? citeShort(bib, row.title) : ''\n }\n if (field.type === 'derivation') {\n const derivation = value as Partial<Derivation> | null | undefined\n if (!derivation || typeof derivation !== 'object' || typeof derivation.method !== 'string') return ''\n return [t ? derivationMethodLabel(t, derivation.method) : derivation.method, derivation.createdAt ? formatDate(derivation.createdAt) : ''].filter(Boolean).join(' · ')\n }\n return metaText(row, field)\n}\n\n/** Icon and color class of the known status values; unknown values read as neutral. */\nconst STATUS_STYLE: Record<string, { icon: string, color: string }> = {\n open: { icon: 'i-lucide-circle', color: 'text-[var(--sk-muted)]' },\n doing: { icon: 'i-lucide-circle-dot', color: 'text-[var(--sk-info)]' },\n done: { icon: 'i-lucide-circle-check', color: 'text-[var(--sk-success)]' },\n}\nconst STATUS_UNSET = { icon: 'i-lucide-circle-dashed', color: 'text-[var(--sk-fg-dimmed)]' }\nconst STATUS_OTHER = { icon: 'i-lucide-circle', color: 'text-[var(--sk-muted)]' }\n\nexport const statusStyle = (value: string) => STATUS_STYLE[value] ?? (value ? STATUS_OTHER : STATUS_UNSET)\n\n/** A row's `status` field with its current value. */\nexport interface RowStatus {\n field: CompositeMetaField\n value: string\n label: string\n icon: string\n color: string\n}\n\n/** The row's `status` select field with its current value, `null` for a kind without one; `t` words an unset status. */\nexport function rowStatus(row: CompositeRow, t: Translate): RowStatus | null {\n const field = row.fields.find(candidate => candidate.key === 'status' && candidate.type === 'select')\n if (!field) return null\n const value = metaText(row, field)\n return { field, value, label: field.options?.find(option => option.value === value)?.label ?? (value || t('composites.status.none')), ...statusStyle(value) }\n}\n"],"mappings":";;;;;;AAUA,SAAgB,SAAS,KAAiC,OAAgD;CACxG,MAAM,QAAQ,IAAI,KAAK,MAAM;CAC7B,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI;AAClF;AAEA,MAAM,aAAa,IAAI,KAAK,eAAe,SAAS;CAAE,KAAK;CAAW,OAAO;CAAW,MAAM;AAAU,CAAC;;AAGzG,SAAgB,WAAW,OAAuB;CAChD,MAAM,QAAQ,2BAA2B,KAAK,KAAK;CACnD,MAAM,OAAO,QAAQ,IAAI,KAAK,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,EAAE,CAAC,IAAI,IAAI,KAAK,KAAK;CACxG,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,QAAQ,WAAW,OAAO,IAAI;AACtE;;;;;;AAOA,SAAgB,aAAa,KAA2C,OAAiD,KAAoB,OAAiC,GAAuB;CACnM,MAAM,QAAQ,IAAI,KAAK,MAAM;CAC7B,IAAI,MAAM,SAAS,aAAa;EAC9B,IAAI,OAAO,UAAU,YAAY,CAAC,SAAS,CAAC,KAAK,OAAO;EACxD,MAAM,SAAS,cAAc,KAAK,KAAK,CAAC,EAAE;EAC1C,IAAI,QAAQ,KAAK,SAAS,aAAa,OAAO;EAC9C,OAAO,OAAO,MAAM,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,cAAc,OAAO,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC;CAC/F;CACA,IAAI,MAAM,SAAS,gBAAgB;EACjC,MAAM,MAAM;EACZ,OAAO,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,WAAW,UAAU,KAAK,IAAI,KAAK,IAAI;CACtG;CACA,IAAI,MAAM,SAAS,cAAc;EAC/B,MAAM,aAAa;EACnB,IAAI,CAAC,cAAc,OAAO,eAAe,YAAY,OAAO,WAAW,WAAW,UAAU,OAAO;EACnG,OAAO,CAAC,IAAI,sBAAsB,GAAG,WAAW,MAAM,IAAI,WAAW,QAAQ,WAAW,YAAY,WAAW,WAAW,SAAS,IAAI,EAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;CACvK;CACA,OAAO,SAAS,KAAK,KAAK;AAC5B;;AAGA,MAAM,eAAgE;CACpE,MAAM;EAAE,MAAM;EAAmB,OAAO;CAAyB;CACjE,OAAO;EAAE,MAAM;EAAuB,OAAO;CAAwB;CACrE,MAAM;EAAE,MAAM;EAAyB,OAAO;CAA2B;AAC3E;AACA,MAAM,eAAe;CAAE,MAAM;CAA0B,OAAO;AAA6B;AAC3F,MAAM,eAAe;CAAE,MAAM;CAAmB,OAAO;AAAyB;AAEhF,MAAa,eAAe,UAAkB,aAAa,WAAW,QAAQ,eAAe;;AAY7F,SAAgB,UAAU,KAAmB,GAAgC;CAC3E,MAAM,QAAQ,IAAI,OAAO,MAAK,cAAa,UAAU,QAAQ,YAAY,UAAU,SAAS,QAAQ;CACpG,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,QAAQ,SAAS,KAAK,KAAK;CACjC,OAAO;EAAE;EAAO;EAAO,OAAO,MAAM,SAAS,MAAK,WAAU,OAAO,UAAU,KAAK,CAAC,EAAE,UAAU,SAAS,EAAE,wBAAwB;EAAI,GAAG,YAAY,KAAK;CAAE;AAC9J"}
1
+ {"version":3,"file":"meta-display.js","names":[],"sources":["../../../src/features/composites/meta-display.ts"],"sourcesContent":["import type { Node as PMNode } from '@tiptap/pm/model'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport type { Translate } from '../../i18n'\nimport { type CslItem, citeShort } from './bibliography'\nimport type { Derivation } from './derivation'\nimport { derivationMethodLabel } from './derived-chrome'\nimport { type CompositeKind, type CompositeMetaField, compositeKind } from './kinds'\nimport type { CompositeRow } from './rows'\n\n/**\n * The stored value of a meta field as text (`''` when unset). An array (`multiselect`) reads as its entries joined by\n * `, `: the labels of the matching `options`, else the stored values.\n */\nexport function metaText(row: Pick<CompositeRow, 'meta'>, field: Pick<CompositeMetaField, 'key'> & { options?: CompositeMetaField['options'] }): string {\n const value = row.meta[field.key]\n if (Array.isArray(value)) return value.filter((item): item is string => typeof item === 'string').map(item => field.options?.find(option => option.value === item)?.label ?? item).join(', ')\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nconst dateFormat = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })\n\n/** `2026-05-01` → `01.05.2026`; parsed by hand because `new Date('2026-05-01')` is UTC and can show the previous day. */\nexport function formatDate(value: string): string {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(value)\n const date = match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value)\n return Number.isNaN(date.getTime()) ? value : dateFormat.format(date)\n}\n\n/**\n * The text a field's value stands for in a table cell: `reference` the title of the Baustein it points to (its kind label\n * when untitled, `''` when it is gone or `doc` is unknown), `bibliography` the short citation, `derivation`\n * `method · date` (the method as words when `t` is given, else its stored id); every other type its stored text.\n */\nexport function fieldDisplay(row: Pick<CompositeRow, 'meta' | 'title'>, field: Pick<CompositeMetaField, 'key' | 'type' | 'options'>, doc: PMNode | null, kinds: readonly CompositeKind[], t?: Translate): string {\n const value = row.meta[field.key]\n if (field.type === 'reference') {\n if (typeof value !== 'string' || !value || !doc) return ''\n const target = findBlockById(doc, value)?.node\n if (target?.type.name !== 'composite') return ''\n return target.child(0).textContent.trim() || compositeKind(kinds, String(target.attrs.kind)).label\n }\n if (field.type === 'bibliography') {\n const bib = value as Partial<CslItem> | null | undefined\n return bib && typeof bib === 'object' && typeof bib.type === 'string' ? citeShort(bib, row.title) : ''\n }\n if (field.type === 'derivation') {\n const derivation = value as Partial<Derivation> | null | undefined\n if (!derivation || typeof derivation !== 'object' || typeof derivation.method !== 'string') return ''\n return [t ? derivationMethodLabel(t, derivation.method) : derivation.method, derivation.createdAt ? formatDate(derivation.createdAt) : ''].filter(Boolean).join(' · ')\n }\n return metaText(row, field)\n}\n\n/** Icon and color class of the known status values; unknown values read as neutral. */\nconst STATUS_STYLE: Record<string, { icon: string, color: string }> = {\n open: { icon: 'i-lucide-circle', color: 'text-[var(--sk-muted)]' },\n doing: { icon: 'i-lucide-circle-dot', color: 'text-[var(--sk-info)]' },\n done: { icon: 'i-lucide-circle-check', color: 'text-[var(--sk-success)]' },\n}\nconst STATUS_UNSET = { icon: 'i-lucide-circle-dashed', color: 'text-[var(--sk-fg-dimmed)]' }\nconst STATUS_OTHER = { icon: 'i-lucide-circle', color: 'text-[var(--sk-muted)]' }\n\nexport const statusStyle = (value: string) => STATUS_STYLE[value] ?? (value ? STATUS_OTHER : STATUS_UNSET)\n\n/** A row's `status` field with its current value. */\nexport interface RowStatus {\n field: CompositeMetaField\n value: string\n label: string\n icon: string\n color: string\n}\n\n/** The row's `status` select field with its current value, `null` for a kind without one; `t` words an unset status. */\nexport function rowStatus(row: CompositeRow, t: Translate): RowStatus | null {\n const field = row.fields.find(candidate => candidate.key === 'status' && candidate.type === 'select')\n if (!field) return null\n const value = metaText(row, field)\n return { field, value, label: field.options?.find(option => option.value === value)?.label ?? (value || t('composites.status.none')), ...statusStyle(value) }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,SAAS,KAAiC,OAA8F;CACtJ,MAAM,QAAQ,IAAI,KAAK,MAAM;CAC7B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,QAAQ,CAAC,CAAC,KAAI,SAAQ,MAAM,SAAS,MAAK,WAAU,OAAO,UAAU,IAAI,CAAC,EAAE,SAAS,IAAI,CAAC,CAAC,KAAK,IAAI;CAC5L,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI;AAClF;AAEA,MAAM,aAAa,IAAI,KAAK,eAAe,SAAS;CAAE,KAAK;CAAW,OAAO;CAAW,MAAM;AAAU,CAAC;;AAGzG,SAAgB,WAAW,OAAuB;CAChD,MAAM,QAAQ,2BAA2B,KAAK,KAAK;CACnD,MAAM,OAAO,QAAQ,IAAI,KAAK,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,EAAE,CAAC,IAAI,IAAI,KAAK,KAAK;CACxG,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,QAAQ,WAAW,OAAO,IAAI;AACtE;;;;;;AAOA,SAAgB,aAAa,KAA2C,OAA6D,KAAoB,OAAiC,GAAuB;CAC/M,MAAM,QAAQ,IAAI,KAAK,MAAM;CAC7B,IAAI,MAAM,SAAS,aAAa;EAC9B,IAAI,OAAO,UAAU,YAAY,CAAC,SAAS,CAAC,KAAK,OAAO;EACxD,MAAM,SAAS,cAAc,KAAK,KAAK,CAAC,EAAE;EAC1C,IAAI,QAAQ,KAAK,SAAS,aAAa,OAAO;EAC9C,OAAO,OAAO,MAAM,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,cAAc,OAAO,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,CAAC;CAC/F;CACA,IAAI,MAAM,SAAS,gBAAgB;EACjC,MAAM,MAAM;EACZ,OAAO,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,WAAW,UAAU,KAAK,IAAI,KAAK,IAAI;CACtG;CACA,IAAI,MAAM,SAAS,cAAc;EAC/B,MAAM,aAAa;EACnB,IAAI,CAAC,cAAc,OAAO,eAAe,YAAY,OAAO,WAAW,WAAW,UAAU,OAAO;EACnG,OAAO,CAAC,IAAI,sBAAsB,GAAG,WAAW,MAAM,IAAI,WAAW,QAAQ,WAAW,YAAY,WAAW,WAAW,SAAS,IAAI,EAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;CACvK;CACA,OAAO,SAAS,KAAK,KAAK;AAC5B;;AAGA,MAAM,eAAgE;CACpE,MAAM;EAAE,MAAM;EAAmB,OAAO;CAAyB;CACjE,OAAO;EAAE,MAAM;EAAuB,OAAO;CAAwB;CACrE,MAAM;EAAE,MAAM;EAAyB,OAAO;CAA2B;AAC3E;AACA,MAAM,eAAe;CAAE,MAAM;CAA0B,OAAO;AAA6B;AAC3F,MAAM,eAAe;CAAE,MAAM;CAAmB,OAAO;AAAyB;AAEhF,MAAa,eAAe,UAAkB,aAAa,WAAW,QAAQ,eAAe;;AAY7F,SAAgB,UAAU,KAAmB,GAAgC;CAC3E,MAAM,QAAQ,IAAI,OAAO,MAAK,cAAa,UAAU,QAAQ,YAAY,UAAU,SAAS,QAAQ;CACpG,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,QAAQ,SAAS,KAAK,KAAK;CACjC,OAAO;EAAE;EAAO;EAAO,OAAO,MAAM,SAAS,MAAK,WAAU,OAAO,UAAU,KAAK,CAAC,EAAE,UAAU,SAAS,EAAE,wBAAwB;EAAI,GAAG,YAAY,KAAK;CAAE;AAC9J"}
@@ -158,7 +158,7 @@ function CompositeChrome({ editor }) {
158
158
  row: item.row,
159
159
  editable
160
160
  })
161
- })] }), (item.status || item.row.tags.length > 0 || editable || item.derived) && /* @__PURE__ */ jsxs("div", {
161
+ })] }), (item.status || item.derived || item.row.tagsVisible && (item.row.tags.length > 0 || editable)) && /* @__PURE__ */ jsxs("div", {
162
162
  className: "absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background",
163
163
  style: {
164
164
  top: `${item.top}px`,
@@ -216,7 +216,7 @@ function CompositeChrome({ editor }) {
216
216
  className: cn("size-3.5", STATUS_COLOR[item.status.color] ?? item.status.color)
217
217
  }), item.status.label]
218
218
  })),
219
- item.row.tags.map((tag) => /* @__PURE__ */ jsxs("span", {
219
+ (item.row.tagsVisible ? item.row.tags : []).map((tag) => /* @__PURE__ */ jsxs("span", {
220
220
  className: cn(CHIP, "group max-w-40 gap-0"),
221
221
  children: [/* @__PURE__ */ jsxs("span", {
222
222
  className: "truncate",
@@ -232,7 +232,7 @@ function CompositeChrome({ editor }) {
232
232
  })
233
233
  })]
234
234
  }, tag)),
235
- editable && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
235
+ editable && item.row.tagsVisible && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
236
236
  asChild: true,
237
237
  children: /* @__PURE__ */ jsx("button", {
238
238
  type: "button",
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeChrome.js","names":[],"sources":["../../../../src/features/composites/react/CompositeChrome.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { Fragment, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { startBlockDrag } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useStore } from '../../../react/use-store'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { cn } from '../../../react/ui/utils'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeDetails } from './CompositeDetails'\nimport { CompositeStatusMenu } from './CompositeStatusMenu'\nimport { CompositeTagsEditor } from './CompositeTagsEditor'\nimport { useCompositeAnchors } from './use-composite-anchors'\nimport { useCompositeRows } from './use-composite-rows'\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`). 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 */\nexport function CompositeChrome({ editor }: { editor: Editor }) {\n const { rows } = useCompositeRows(editor)\n const { host, anchors, frame } = useCompositeAnchors(editor, rows)\n\n // `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable)\n useEffect(() => {\n const sync = () => setEditable(!editor.isDestroyed && editor.isEditable)\n sync()\n editor.on('transaction', sync)\n return () => {\n editor.off('transaction', sync)\n }\n }, [editor])\n\n const { settings } = useEditorSettings()\n const { t } = useEditorI18n()\n const iconOnHover = settings.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).\n const [hoveredKeys, setHoveredKeys] = useState<ReadonlySet<string>>(NO_KEYS)\n const [chipsHoveredKey, setChipsHoveredKey] = useState<string | null>(null)\n const latestRows = useRef(rows)\n latestRows.current = rows\n\n useEffect(() => {\n let hoveredSection: Element | null = null\n const onMouseleave = () => {\n hoveredSection = null\n setHoveredKeys(NO_KEYS)\n }\n const onMouseover = (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 } = editor\n setHoveredKeys(new Set(latestRows.current.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\n onMouseleave()\n if (!iconOnHover || editor.isDestroyed) return\n const { dom } = editor.view\n dom.addEventListener('mouseover', onMouseover)\n dom.addEventListener('mouseleave', onMouseleave)\n return () => {\n dom.removeEventListener('mouseover', onMouseover)\n dom.removeEventListener('mouseleave', onMouseleave)\n }\n }, [editor, iconOnHover])\n\n function onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (!found || !startBlockDrag(editor, event, found.pos)) event.preventDefault()\n }\n\n const expanded = useStore(editor.storage.composite.expanded)\n\n const placed = useMemo(() => {\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.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(editor, row)\n const open = !derived || !row.id || expanded.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.get(row.key)\n if (!anchor) return []\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: anchor.lastCenter - ROW_HEIGHT / 2,\n width: Math.max((frame?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n }]\n })\n }, [editor, rows, anchors, frame, expanded, t])\n\n function toggleExpanded(row: CompositeRow) {\n if (row.id) editor.commands.toggleCompositeExpanded(row.id)\n }\n\n function removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(editor, row, row.tags.filter(candidate => candidate !== tag))\n }\n\n if (!host) return null\n\n return createPortal(\n <div className=\"absolute top-0 left-0 h-0 w-0\">\n {placed.map(item => (\n <Fragment key={item.row.key}>\n {item.anchor.toggleSize > 0 && (\n <Popover>\n <PopoverTrigger asChild>\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 className={cn(\n 'absolute z-10 flex items-center justify-center rounded-sm bg-background text-muted-foreground transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-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 onKeyDown={(event: KeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.currentTarget.click()\n }}\n onDragStart={event => onIconDragStart(event.nativeEvent, item.row)}\n >\n <Icon key={item.row.icon} name={item.row.icon} className=\"size-3.5\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n className={DETAILS_POPOVER}\n >\n <CompositeDetails editor={editor} row={item.row} editable={editable} />\n </PopoverContent>\n </Popover>\n )}\n\n {(item.status || item.row.tags.length > 0 || editable || item.derived) && (\n <div\n className=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background\"\n style={{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }}\n onMouseEnter={() => setChipsHoveredKey(item.row.key)}\n onMouseLeave={() => setChipsHoveredKey(null)}\n >\n {item.derived && (\n <>\n {item.derived.toggleable && (\n <button\n type=\"button\"\n className={CHIP_BUTTON}\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 onClick={() => toggleExpanded(item.row)}\n >\n <Icon name={item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'} className=\"size-3.5\" />\n </button>\n )}\n <span className={CHIP}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {item.derived.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n </>\n )}\n\n {item.status && (editable\n ? (\n <CompositeStatusMenu editor={editor} row={item.row}>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.status.label', { status: item.status.label })}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </button>\n </CompositeStatusMenu>\n )\n : (\n <span className={CHIP}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </span>\n ))}\n\n {item.row.tags.map(tag => (\n <span key={tag} className={cn(CHIP, 'group max-w-40 gap-0')}>\n <span className=\"truncate\">#{tag}</span>\n {editable && (\n <button\n type=\"button\"\n className=\"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-muted hover:text-foreground focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-primary\"\n aria-label={t('composites.tag.remove', { tag })}\n onClick={() => removeTag(item.row, tag)}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n )}\n </span>\n ))}\n\n {editable && (\n <Popover>\n <PopoverTrigger asChild>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.tag.add')} title={t('composites.tag.add')}>\n <Icon name=\"i-lucide-plus\" className=\"size-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={6}\n className=\"w-auto gap-0 p-0\"\n onCloseAutoFocus={event => event.preventDefault()}\n >\n <div className=\"w-64 p-3\">\n <CompositeTagsEditor editor={editor} row={item.row} />\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>,\n host,\n )\n}\n\nconst NO_KEYS: ReadonlySet<string> = new Set()\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 CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-muted-foreground'\nconst CHIP_BUTTON = `${CHIP} transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`\n\n/** `statusStyle` names Nuxt UI text colors: the shadcn counterparts (`--sk-info` and `--sk-success` come from the token stylesheets). */\nconst STATUS_COLOR: Record<string, string> = {\n 'text-muted': 'text-muted-foreground',\n 'text-info': 'text-(--sk-info)',\n 'text-success': 'text-(--sk-success)',\n 'text-dimmed': 'text-muted-foreground/70',\n}\n\n/** Scale and fade in from the icon (`transform-origin` is Radix's), a bit longer than the default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst DETAILS_POPOVER = 'w-auto gap-0 p-0 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"],"mappings":"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"}
1
+ {"version":3,"file":"CompositeChrome.js","names":[],"sources":["../../../../src/features/composites/react/CompositeChrome.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { Fragment, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { startBlockDrag } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useStore } from '../../../react/use-store'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { cn } from '../../../react/ui/utils'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeDetails } from './CompositeDetails'\nimport { CompositeStatusMenu } from './CompositeStatusMenu'\nimport { CompositeTagsEditor } from './CompositeTagsEditor'\nimport { useCompositeAnchors } from './use-composite-anchors'\nimport { useCompositeRows } from './use-composite-rows'\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`). 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 */\nexport function CompositeChrome({ editor }: { editor: Editor }) {\n const { rows } = useCompositeRows(editor)\n const { host, anchors, frame } = useCompositeAnchors(editor, rows)\n\n // `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable)\n useEffect(() => {\n const sync = () => setEditable(!editor.isDestroyed && editor.isEditable)\n sync()\n editor.on('transaction', sync)\n return () => {\n editor.off('transaction', sync)\n }\n }, [editor])\n\n const { settings } = useEditorSettings()\n const { t } = useEditorI18n()\n const iconOnHover = settings.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).\n const [hoveredKeys, setHoveredKeys] = useState<ReadonlySet<string>>(NO_KEYS)\n const [chipsHoveredKey, setChipsHoveredKey] = useState<string | null>(null)\n const latestRows = useRef(rows)\n latestRows.current = rows\n\n useEffect(() => {\n let hoveredSection: Element | null = null\n const onMouseleave = () => {\n hoveredSection = null\n setHoveredKeys(NO_KEYS)\n }\n const onMouseover = (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 } = editor\n setHoveredKeys(new Set(latestRows.current.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\n onMouseleave()\n if (!iconOnHover || editor.isDestroyed) return\n const { dom } = editor.view\n dom.addEventListener('mouseover', onMouseover)\n dom.addEventListener('mouseleave', onMouseleave)\n return () => {\n dom.removeEventListener('mouseover', onMouseover)\n dom.removeEventListener('mouseleave', onMouseleave)\n }\n }, [editor, iconOnHover])\n\n function onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (!found || !startBlockDrag(editor, event, found.pos)) event.preventDefault()\n }\n\n const expanded = useStore(editor.storage.composite.expanded)\n\n const placed = useMemo(() => {\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.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(editor, row)\n const open = !derived || !row.id || expanded.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.get(row.key)\n if (!anchor) return []\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: anchor.lastCenter - ROW_HEIGHT / 2,\n width: Math.max((frame?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n }]\n })\n }, [editor, rows, anchors, frame, expanded, t])\n\n function toggleExpanded(row: CompositeRow) {\n if (row.id) editor.commands.toggleCompositeExpanded(row.id)\n }\n\n function removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(editor, row, row.tags.filter(candidate => candidate !== tag))\n }\n\n if (!host) return null\n\n return createPortal(\n <div className=\"absolute top-0 left-0 h-0 w-0\">\n {placed.map(item => (\n <Fragment key={item.row.key}>\n {item.anchor.toggleSize > 0 && (\n <Popover>\n <PopoverTrigger asChild>\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 className={cn(\n 'absolute z-10 flex items-center justify-center rounded-sm bg-background text-muted-foreground transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-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 onKeyDown={(event: KeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.currentTarget.click()\n }}\n onDragStart={event => onIconDragStart(event.nativeEvent, item.row)}\n >\n <Icon key={item.row.icon} name={item.row.icon} className=\"size-3.5\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n className={DETAILS_POPOVER}\n >\n <CompositeDetails editor={editor} row={item.row} editable={editable} />\n </PopoverContent>\n </Popover>\n )}\n\n {(item.status || item.derived || (item.row.tagsVisible && (item.row.tags.length > 0 || editable))) && (\n <div\n className=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background\"\n style={{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }}\n onMouseEnter={() => setChipsHoveredKey(item.row.key)}\n onMouseLeave={() => setChipsHoveredKey(null)}\n >\n {item.derived && (\n <>\n {item.derived.toggleable && (\n <button\n type=\"button\"\n className={CHIP_BUTTON}\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 onClick={() => toggleExpanded(item.row)}\n >\n <Icon name={item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'} className=\"size-3.5\" />\n </button>\n )}\n <span className={CHIP}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {item.derived.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n </>\n )}\n\n {item.status && (editable\n ? (\n <CompositeStatusMenu editor={editor} row={item.row}>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.status.label', { status: item.status.label })}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </button>\n </CompositeStatusMenu>\n )\n : (\n <span className={CHIP}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </span>\n ))}\n\n {(item.row.tagsVisible ? item.row.tags : []).map(tag => (\n <span key={tag} className={cn(CHIP, 'group max-w-40 gap-0')}>\n <span className=\"truncate\">#{tag}</span>\n {editable && (\n <button\n type=\"button\"\n className=\"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-muted hover:text-foreground focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-primary\"\n aria-label={t('composites.tag.remove', { tag })}\n onClick={() => removeTag(item.row, tag)}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n )}\n </span>\n ))}\n\n {editable && item.row.tagsVisible && (\n <Popover>\n <PopoverTrigger asChild>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.tag.add')} title={t('composites.tag.add')}>\n <Icon name=\"i-lucide-plus\" className=\"size-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={6}\n className=\"w-auto gap-0 p-0\"\n onCloseAutoFocus={event => event.preventDefault()}\n >\n <div className=\"w-64 p-3\">\n <CompositeTagsEditor editor={editor} row={item.row} />\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>,\n host,\n )\n}\n\nconst NO_KEYS: ReadonlySet<string> = new Set()\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 CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-muted-foreground'\nconst CHIP_BUTTON = `${CHIP} transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`\n\n/** `statusStyle` names Nuxt UI text colors: the shadcn counterparts (`--sk-info` and `--sk-success` come from the token stylesheets). */\nconst STATUS_COLOR: Record<string, string> = {\n 'text-muted': 'text-muted-foreground',\n 'text-info': 'text-(--sk-info)',\n 'text-success': 'text-(--sk-success)',\n 'text-dimmed': 'text-muted-foreground/70',\n}\n\n/** Scale and fade in from the icon (`transform-origin` is Radix's), a bit longer than the default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst DETAILS_POPOVER = 'w-auto gap-0 p-0 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"],"mappings":"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"}
@@ -100,6 +100,25 @@ function CompositeFieldEditor({ editor, row, field, disabled, size = "sm" }) {
100
100
  children: option.label
101
101
  }, option.value)) })]
102
102
  });
103
+ if (field.type === "multiselect") {
104
+ const value = row.meta[field.key];
105
+ const chosen = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
106
+ return /* @__PURE__ */ jsx("div", {
107
+ role: "group",
108
+ "aria-label": field.label,
109
+ className: "flex w-full flex-col gap-1",
110
+ children: (field.options ?? []).map((option) => /* @__PURE__ */ jsxs("label", {
111
+ className: cn("flex items-center gap-2", size === "md" ? "text-sm" : "text-xs"),
112
+ children: [/* @__PURE__ */ jsx("input", {
113
+ type: "checkbox",
114
+ className: "size-3.5 shrink-0 accent-(--sk-primary)",
115
+ checked: chosen.includes(option.value),
116
+ disabled,
117
+ onChange: (event) => setCompositeMetaField(editor, row, field.key, event.target.checked ? [...chosen, option.value] : chosen.filter((item) => item !== option.value))
118
+ }), option.label]
119
+ }, option.value))
120
+ });
121
+ }
103
122
  if (field.type === "reference") return /* @__PURE__ */ jsx(ReferenceFieldSelect, {
104
123
  editor,
105
124
  row,
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeFieldEditor.js","names":[],"sources":["../../../../src/features/composites/react/CompositeFieldEditor.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { Input } from '../../../react/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { metaText } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeMetaField } from '../rows'\nimport { BibliographyEditor } from './BibliographyEditor'\nimport { DerivationBadge } from './DerivationBadge'\nimport { ReferenceFieldSelect } from './ReferenceFieldSelect'\n\n/** Radix Select has no empty-string value: an option with an empty value travels as this sentinel. */\nconst EMPTY = '__empty__'\n\nconst INPUT_SIZE = {\n xs: 'h-6 px-2 text-xs md:text-xs',\n sm: 'h-7 px-2.5 text-sm md:text-sm',\n md: '',\n}\nconst TEXTAREA_SIZE = {\n xs: 'px-2 py-1 text-xs md:text-xs',\n sm: 'px-2.5 py-1.5 text-sm md:text-sm',\n md: '',\n}\nconst SELECT_SIZE = {\n xs: 'data-[size=sm]:h-6 text-xs',\n sm: 'data-[size=sm]:h-7',\n md: '',\n}\n\n/**\n * One meta field of a Baustein as an input that writes through `setCompositeMetaField`. Text-like inputs keep a local\n * draft and commit after typing pauses, on blur and on unmount, so the one-frame-late re-read of the rows never\n * overwrites what is being typed.\n */\nexport function CompositeFieldEditor({ editor, row, field, disabled, size = 'sm' }: {\n editor: Editor\n row: CompositeRow\n field: CompositeMetaField\n disabled?: boolean\n size?: 'xs' | 'sm' | 'md'\n}) {\n const { t } = useEditorI18n()\n const stored = metaText(row, field)\n const [draft, setDraft] = useState(stored)\n const draftRef = useRef(draft)\n const typing = useRef(false)\n const dirty = useRef(false)\n const timer = useRef<number | undefined>(undefined)\n // The latest props for the debounced commit and the flush on unmount; updated after every render, so the cleanup of a\n // changed `row.key` still sees the previous row.\n const target = useRef({ editor, row, field })\n useEffect(() => {\n target.current = { editor, row, field }\n })\n\n useEffect(() => {\n if (!typing.current && !dirty.current) {\n draftRef.current = stored\n setDraft(stored)\n }\n }, [stored, row.key])\n\n const commit = useCallback(() => {\n clearTimeout(timer.current)\n if (!dirty.current) return\n dirty.current = false\n const current = target.current\n if (!current.editor.isDestroyed) setCompositeMetaField(current.editor, current.row, current.field.key, draftRef.current)\n }, [])\n\n // Flush on unmount and when the field is bound to another row.\n useEffect(() => commit, [commit, editor, row.key, field.key])\n\n function onInput(value: string) {\n draftRef.current = value\n setDraft(value)\n dirty.current = true\n clearTimeout(timer.current)\n timer.current = window.setTimeout(commit, 400)\n }\n\n function onBlur() {\n typing.current = false\n commit()\n }\n\n function onChoice(value: string) {\n setCompositeMetaField(editor, row, field.key, value === EMPTY ? '' : value)\n }\n\n if (field.type === 'select') {\n return (\n <Select value={stored} onValueChange={onChoice} disabled={disabled}>\n <SelectTrigger size=\"sm\" className={cn('w-full', SELECT_SIZE[size])} aria-label={field.label}>\n <SelectValue placeholder={t('composites.field.choose', { field: field.label })} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map(option => (\n <SelectItem key={option.value} value={option.value || EMPTY}>{option.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n if (field.type === 'reference') {\n return <ReferenceFieldSelect editor={editor} row={row} field={field} disabled={disabled} size={size} />\n }\n\n if (field.type === 'bibliography') {\n return <BibliographyEditor editor={editor} row={row} field={field} disabled={disabled} />\n }\n\n if (field.type === 'derivation') {\n const derived = compositeOfRow(editor.state.doc, row)?.node\n return derived ? <DerivationBadge editor={editor} node={derived} /> : null\n }\n\n if (field.type === 'textarea') {\n return (\n <Textarea\n value={draft}\n aria-label={field.label}\n disabled={disabled}\n rows={2}\n className={cn('max-h-[8.5rem] min-h-0 w-full', TEXTAREA_SIZE[size])}\n onFocus={() => {\n typing.current = true\n }}\n onBlur={onBlur}\n onChange={event => onInput(event.target.value)}\n />\n )\n }\n\n return (\n <Input\n value={draft}\n type={field.type}\n aria-label={field.label}\n disabled={disabled}\n className={cn('w-full', INPUT_SIZE[size])}\n onFocus={() => {\n typing.current = true\n }}\n onBlur={onBlur}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commit()\n }}\n onChange={event => onInput(event.target.value)}\n />\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompositeFieldEditor.js","names":[],"sources":["../../../../src/features/composites/react/CompositeFieldEditor.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { Input } from '../../../react/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { metaText } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeMetaField } from '../rows'\nimport { BibliographyEditor } from './BibliographyEditor'\nimport { DerivationBadge } from './DerivationBadge'\nimport { ReferenceFieldSelect } from './ReferenceFieldSelect'\n\n/** Radix Select has no empty-string value: an option with an empty value travels as this sentinel. */\nconst EMPTY = '__empty__'\n\nconst INPUT_SIZE = {\n xs: 'h-6 px-2 text-xs md:text-xs',\n sm: 'h-7 px-2.5 text-sm md:text-sm',\n md: '',\n}\nconst TEXTAREA_SIZE = {\n xs: 'px-2 py-1 text-xs md:text-xs',\n sm: 'px-2.5 py-1.5 text-sm md:text-sm',\n md: '',\n}\nconst SELECT_SIZE = {\n xs: 'data-[size=sm]:h-6 text-xs',\n sm: 'data-[size=sm]:h-7',\n md: '',\n}\n\n/**\n * One meta field of a Baustein as an input that writes through `setCompositeMetaField`. Text-like inputs keep a local\n * draft and commit after typing pauses, on blur and on unmount, so the one-frame-late re-read of the rows never\n * overwrites what is being typed.\n */\nexport function CompositeFieldEditor({ editor, row, field, disabled, size = 'sm' }: {\n editor: Editor\n row: CompositeRow\n field: CompositeMetaField\n disabled?: boolean\n size?: 'xs' | 'sm' | 'md'\n}) {\n const { t } = useEditorI18n()\n const stored = metaText(row, field)\n const [draft, setDraft] = useState(stored)\n const draftRef = useRef(draft)\n const typing = useRef(false)\n const dirty = useRef(false)\n const timer = useRef<number | undefined>(undefined)\n // The latest props for the debounced commit and the flush on unmount; updated after every render, so the cleanup of a\n // changed `row.key` still sees the previous row.\n const target = useRef({ editor, row, field })\n useEffect(() => {\n target.current = { editor, row, field }\n })\n\n useEffect(() => {\n if (!typing.current && !dirty.current) {\n draftRef.current = stored\n setDraft(stored)\n }\n }, [stored, row.key])\n\n const commit = useCallback(() => {\n clearTimeout(timer.current)\n if (!dirty.current) return\n dirty.current = false\n const current = target.current\n if (!current.editor.isDestroyed) setCompositeMetaField(current.editor, current.row, current.field.key, draftRef.current)\n }, [])\n\n // Flush on unmount and when the field is bound to another row.\n useEffect(() => commit, [commit, editor, row.key, field.key])\n\n function onInput(value: string) {\n draftRef.current = value\n setDraft(value)\n dirty.current = true\n clearTimeout(timer.current)\n timer.current = window.setTimeout(commit, 400)\n }\n\n function onBlur() {\n typing.current = false\n commit()\n }\n\n function onChoice(value: string) {\n setCompositeMetaField(editor, row, field.key, value === EMPTY ? '' : value)\n }\n\n if (field.type === 'select') {\n return (\n <Select value={stored} onValueChange={onChoice} disabled={disabled}>\n <SelectTrigger size=\"sm\" className={cn('w-full', SELECT_SIZE[size])} aria-label={field.label}>\n <SelectValue placeholder={t('composites.field.choose', { field: field.label })} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map(option => (\n <SelectItem key={option.value} value={option.value || EMPTY}>{option.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n if (field.type === 'multiselect') {\n const value = row.meta[field.key]\n const chosen = Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []\n return (\n <div role=\"group\" aria-label={field.label} className=\"flex w-full flex-col gap-1\">\n {(field.options ?? []).map(option => (\n <label key={option.value} className={cn('flex items-center gap-2', size === 'md' ? 'text-sm' : 'text-xs')}>\n <input\n type=\"checkbox\"\n className=\"size-3.5 shrink-0 accent-(--sk-primary)\"\n checked={chosen.includes(option.value)}\n disabled={disabled}\n onChange={event => setCompositeMetaField(editor, row, field.key, event.target.checked ? [...chosen, option.value] : chosen.filter(item => item !== option.value))}\n />\n {option.label}\n </label>\n ))}\n </div>\n )\n }\n\n if (field.type === 'reference') {\n return <ReferenceFieldSelect editor={editor} row={row} field={field} disabled={disabled} size={size} />\n }\n\n if (field.type === 'bibliography') {\n return <BibliographyEditor editor={editor} row={row} field={field} disabled={disabled} />\n }\n\n if (field.type === 'derivation') {\n const derived = compositeOfRow(editor.state.doc, row)?.node\n return derived ? <DerivationBadge editor={editor} node={derived} /> : null\n }\n\n if (field.type === 'textarea') {\n return (\n <Textarea\n value={draft}\n aria-label={field.label}\n disabled={disabled}\n rows={2}\n className={cn('max-h-[8.5rem] min-h-0 w-full', TEXTAREA_SIZE[size])}\n onFocus={() => {\n typing.current = true\n }}\n onBlur={onBlur}\n onChange={event => onInput(event.target.value)}\n />\n )\n }\n\n return (\n <Input\n value={draft}\n type={field.type}\n aria-label={field.label}\n disabled={disabled}\n className={cn('w-full', INPUT_SIZE[size])}\n onFocus={() => {\n typing.current = true\n }}\n onBlur={onBlur}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commit()\n }}\n onChange={event => onInput(event.target.value)}\n />\n )\n}\n"],"mappings":"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"}
@@ -219,7 +219,7 @@ function CompositeInspector({ editor, documentFocused, document, className }) {
219
219
  onBlur: inspector.blurDescription
220
220
  })
221
221
  }),
222
- /* @__PURE__ */ jsx(FormField, {
222
+ focused.tagsVisible && /* @__PURE__ */ jsx(FormField, {
223
223
  label: t("composites.tags.label"),
224
224
  children: /* @__PURE__ */ jsxs(Popover, {
225
225
  open: tagsOpen,