@ecsplico/editor 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/core/backend-capability.d.ts +1 -1
- package/dist/core/backend-capability.js +2 -1
- package/dist/core/backend-capability.js.map +1 -1
- package/dist/core/backend-types.d.ts +25 -0
- package/dist/core/backend-types.js.map +1 -1
- package/dist/core/backend.d.ts +9 -1
- package/dist/core/backend.js.map +1 -1
- package/dist/core/http-backend.js +17 -0
- package/dist/core/http-backend.js.map +1 -1
- package/dist/core/schema/abilities.d.ts +1 -0
- package/dist/core/schema/abilities.js +4 -0
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/composites/components/CompositeChrome.js +10 -9
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +4 -0
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/merge.d.ts +1 -1
- package/dist/features/composites/merge.js +5 -2
- package/dist/features/composites/merge.js.map +1 -1
- package/dist/features/composites/nodes.js +1 -0
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +9 -2
- package/dist/features/composites/outline.js +41 -3
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeChrome.js +11 -9
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/rows.d.ts +2 -0
- package/dist/features/composites/rows.js +1 -0
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
- package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
- package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
- package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioView.js +117 -0
- package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
- package/dist/features/image-studio/feature.d.ts +24 -0
- package/dist/features/image-studio/feature.js +53 -0
- package/dist/features/image-studio/feature.js.map +1 -0
- package/dist/features/image-studio/index.d.ts +5 -0
- package/dist/features/image-studio/index.js +4 -0
- package/dist/features/image-studio/node-store.d.ts +27 -0
- package/dist/features/image-studio/node-store.js +64 -0
- package/dist/features/image-studio/node-store.js.map +1 -0
- package/dist/features/image-studio/schema.d.ts +29 -0
- package/dist/features/image-studio/schema.js +86 -0
- package/dist/features/image-studio/schema.js.map +1 -0
- package/dist/features/image-studio/state.d.ts +52 -0
- package/dist/features/image-studio/state.js +147 -0
- package/dist/features/image-studio/state.js.map +1 -0
- package/dist/features/image-studio/store.d.ts +29 -0
- package/dist/features/image-studio/vue.d.ts +17 -0
- package/dist/features/image-studio/vue.js +20 -0
- package/dist/features/image-studio/vue.js.map +1 -0
- package/dist/features/media/schema.d.ts +5 -1
- package/dist/features/media/schema.js +3 -3
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/transcript/segment-node.js +1 -0
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/image-studio.js +39 -0
- package/dist/i18n/de/image-studio.js.map +1 -0
- package/dist/i18n/de.d.ts +32 -0
- package/dist/i18n/de.js +13 -11
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/image-studio.js +39 -0
- package/dist/i18n/en/image-studio.js.map +1 -0
- package/dist/i18n/en.js +13 -11
- package/dist/i18n/en.js.map +1 -1
- package/dist/nuxt/module.d.ts +64 -0
- package/dist/server/handlers/images.d.ts +22 -0
- package/dist/server/handlers/images.js +46 -0
- package/dist/server/handlers/images.js.map +1 -0
- package/dist/server/handlers/index.d.ts +2 -1
- package/dist/server/handlers/index.js +2 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/ports.d.ts +11 -1
- package/dist/server/ports.js +2 -1
- package/dist/server/ports.js.map +1 -1
- package/dist/server/routes.js +19 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/tree/DocumentTree.js +5 -4
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +2 -1
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-items.js +1 -0
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +1 -1
- package/package.json +16 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { nestingReason, outermostComposites } from './accepted-children'\nimport { basketOf, titleHiddenOf } from './baskets'\nimport { kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n title: string\n /** The composite has a description (the summary). */\n hasSummary: boolean\n /** Basket id (`meta.basket`), `null` without one. */\n basket: string | null\n /** The title is hidden in the editor (`meta.titleHidden`). */\n titleHidden: boolean\n children: OutlineNode[]\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title gets `untitled` (the\n * caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: node.firstChild?.textContent.trim() || untitled,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n basket: basketOf(node),\n titleHidden: titleHiddenOf(node),\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */\nexport type MoveVerdict = { ok: true } | { ok: false, reason: string }\n\nconst refused = (reason: string): MoveVerdict => ({ ok: false, reason })\n\n/**\n * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when\n * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind\n * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,\n * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not\n * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.\n */\nexport function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return refused(t('composites.move.blockGone'))\n if (targetId === null) return { ok: true }\n const target = findBlockById(doc, targetId)\n if (!target) return refused(t('composites.move.targetGone'))\n if (target.node.type.name !== 'composite') return refused(t('composites.move.targetNotBlock'))\n if (target.pos === block.pos) return refused(t('composites.move.intoSelf'))\n if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t('composites.move.intoOwn'))\n\n // The nearest Baustein around the block: when it is the target, the block only moves within it.\n const $block = doc.resolve(block.pos)\n let parentPos = -1\n for (let depth = $block.depth; depth > 0; depth--) {\n if ($block.node(depth).type.name === 'composite') {\n parentPos = $block.before(depth)\n break\n }\n }\n if (parentPos === target.pos) return { ok: true }\n\n const kinds = compositeKindsOf(editor)\n const targetKind = String(target.node.attrs.kind)\n for (const child of outermostComposites(block.node)) {\n const childKind = String(child.attrs.kind)\n if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t))\n }\n return { ok: true }\n}\n\n/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */\nconst untranslated: Translate = key => key\n\n/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n return moveIntoVerdict(editor, id, targetId, untranslated).ok\n}\n\n/**\n * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor\n * refused the transaction).\n */\nexport function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n if (!canMoveBlockInto(editor, id, targetId)) return false\n const { doc, tr } = editor.state\n const block = findBlockById(doc, id)!\n const target = targetId === null ? null : findBlockById(doc, targetId)!\n // A composite's body is its last children, so its end is just before the composite's closing token.\n const insertPos = target ? target.pos + target.node.nodeSize - 1 : doc.content.size\n tr.delete(block.pos, block.pos + block.node.nodeSize)\n tr.insert(tr.mapping.map(insertPos), block.node)\n editor.view.dispatch(tr.scrollIntoView())\n return editor.state.doc !== doc\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"outline.js","names":[],"sources":["../../../src/features/composites/outline.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport type { Translate } from '../../i18n'\nimport { nestingReason, outermostComposites } from './accepted-children'\nimport { basketOf, titleHiddenOf } from './baskets'\nimport { kindAccepts } from './kinds'\nimport { compositeKindsOf } from './nodes'\n\n/** One Baustein of a document's outline. */\nexport interface OutlineNode {\n /** Block ID, or `pos:<pos>` for a block without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite in the document it was read from. */\n pos: number\n kind: string\n /**\n * The title; for a composite without one, the text of the first heading of its own body, else the start of the first\n * text of its own body (`labelFromBody`), else the caller's `untitled`.\n */\n title: string\n /** `title` is derived from the body, the Baustein itself has no title. */\n labelFromBody: boolean\n /** The composite has a description (the summary). */\n hasSummary: boolean\n /** Basket id (`meta.basket`), `null` without one. */\n basket: string | null\n /** The title is hidden in the editor (`meta.titleHidden`). */\n titleHidden: boolean\n children: OutlineNode[]\n}\n\n/** Longest label taken from a paragraph; longer text is cut and ends in an ellipsis. */\nconst BODY_LABEL_CHARS = 60\n\nconst clipLabel = (text: string) => {\n if (text.length <= BODY_LABEL_CHARS) return text\n let end = BODY_LABEL_CHARS\n const last = text.charCodeAt(end - 1)\n if (last >= 0xD800 && last <= 0xDBFF) end-- // do not cut a surrogate pair\n return `${text.slice(0, end).trimEnd()}…`\n}\n\n/**\n * Walks the blocks of a composite's own body (not the ones inside a nested composite) in order. The text of the first\n * heading ends the walk; `found.text` collects the first non-empty text block as the fallback.\n */\nfunction scanBody(parent: PMNode, from: number, found: { heading: string | null, text: string | null }): void {\n for (let i = from; i < parent.childCount && found.heading === null; i++) {\n const node = parent.child(i)\n if (node.type.name === 'composite') continue\n if (node.isTextblock) {\n const text = node.textContent.trim()\n if (!text) continue\n if (node.type.name === 'heading') found.heading = text\n else found.text ??= text.replace(/\\s+/g, ' ')\n }\n else if (!node.isLeaf) scanBody(node, 0, found)\n }\n}\n\n/** The label of a composite without a title: its first heading, else the start of its first text; `null` for an empty body. */\nfunction bodyLabel(composite: PMNode): string | null {\n const found = { heading: null as string | null, text: null as string | null }\n scanBody(composite, 3, found) // title, description and tags come first\n return found.heading ?? (found.text === null ? null : clipLabel(found.text))\n}\n\n/**\n * The composites of `doc` as a tree (title, kind, summary flag). A composite without a title is labelled by its body\n * (`labelFromBody`: first heading, else the start of the first text; nested composites do not count) and only an empty\n * one gets `untitled` (the caller's translated „(ohne Titel)“). Composites of a kind in `hiddenKinds` are omitted and their visible descendants\n * take their place in the parent's list.\n */\nexport function compositeOutline(doc: PMNode, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[] {\n function collect(parent: PMNode, start: number, into: OutlineNode[]) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === 'composite') {\n const kind = String(node.attrs.kind)\n const children: OutlineNode[] = []\n collect(node, pos + 1, children)\n if (hiddenKinds.has(kind)) {\n into.push(...children)\n return\n }\n const id = blockIdOf(node)\n const ownTitle = node.firstChild?.textContent.trim() ?? ''\n const derived = ownTitle ? null : bodyLabel(node)\n into.push({\n key: id ?? `pos:${pos}`,\n id,\n pos,\n kind,\n title: ownTitle || derived || untitled,\n labelFromBody: derived !== null,\n hasSummary: (node.maybeChild(1)?.content.size ?? 0) > 0,\n basket: basketOf(node),\n titleHidden: titleHiddenOf(node),\n children,\n })\n }\n else if (!node.isTextblock && !node.isLeaf) {\n // Composites can sit in quotes or lists as well.\n collect(node, pos + 1, into)\n }\n })\n }\n const roots: OutlineNode[] = []\n collect(doc, 0, roots)\n return roots\n}\n\n/** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */\nexport type MoveVerdict = { ok: true } | { ok: false, reason: string }\n\nconst refused = (reason: string): MoveVerdict => ({ ok: false, reason })\n\n/**\n * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when\n * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind\n * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,\n * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not\n * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.\n */\nexport function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict {\n const { doc } = editor.state\n const block = findBlockById(doc, id)\n if (!block) return refused(t('composites.move.blockGone'))\n if (targetId === null) return { ok: true }\n const target = findBlockById(doc, targetId)\n if (!target) return refused(t('composites.move.targetGone'))\n if (target.node.type.name !== 'composite') return refused(t('composites.move.targetNotBlock'))\n if (target.pos === block.pos) return refused(t('composites.move.intoSelf'))\n if (target.pos > block.pos && target.pos < block.pos + block.node.nodeSize) return refused(t('composites.move.intoOwn'))\n\n // The nearest Baustein around the block: when it is the target, the block only moves within it.\n const $block = doc.resolve(block.pos)\n let parentPos = -1\n for (let depth = $block.depth; depth > 0; depth--) {\n if ($block.node(depth).type.name === 'composite') {\n parentPos = $block.before(depth)\n break\n }\n }\n if (parentPos === target.pos) return { ok: true }\n\n const kinds = compositeKindsOf(editor)\n const targetKind = String(target.node.attrs.kind)\n for (const child of outermostComposites(block.node)) {\n const childKind = String(child.attrs.kind)\n if (!kindAccepts(kinds, targetKind, childKind)) return refused(nestingReason(kinds, targetKind, childKind, t))\n }\n return { ok: true }\n}\n\n/** `canMoveBlockInto` reads only `ok`, so its verdict needs no text: the message keys stand in for the sentences. */\nconst untranslated: Translate = key => key\n\n/** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */\nexport function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n return moveIntoVerdict(editor, id, targetId, untranslated).ok\n}\n\n/**\n * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single\n * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor\n * refused the transaction).\n */\nexport function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean {\n if (!canMoveBlockInto(editor, id, targetId)) return false\n const { doc, tr } = editor.state\n const block = findBlockById(doc, id)!\n const target = targetId === null ? null : findBlockById(doc, targetId)!\n // A composite's body is its last children, so its end is just before the composite's closing token.\n const insertPos = target ? target.pos + target.node.nodeSize - 1 : doc.content.size\n tr.delete(block.pos, block.pos + block.node.nodeSize)\n tr.insert(tr.mapping.map(insertPos), block.node)\n editor.view.dispatch(tr.scrollIntoView())\n return editor.state.doc !== doc\n}\n"],"mappings":"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"}
|
|
@@ -101,6 +101,7 @@ function CompositeChrome({ editor }) {
|
|
|
101
101
|
return [{
|
|
102
102
|
row,
|
|
103
103
|
anchor,
|
|
104
|
+
editable: editable && row.controls,
|
|
104
105
|
left,
|
|
105
106
|
top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,
|
|
106
107
|
width: Math.max((frame?.right ?? anchor.right) - left, 120),
|
|
@@ -121,6 +122,7 @@ function CompositeChrome({ editor }) {
|
|
|
121
122
|
frame,
|
|
122
123
|
expanded,
|
|
123
124
|
revealedTitles,
|
|
125
|
+
editable,
|
|
124
126
|
t
|
|
125
127
|
]);
|
|
126
128
|
function toggleExpanded(row) {
|
|
@@ -141,8 +143,8 @@ function CompositeChrome({ editor }) {
|
|
|
141
143
|
children: /* @__PURE__ */ jsx("span", {
|
|
142
144
|
role: "button",
|
|
143
145
|
tabIndex: 0,
|
|
144
|
-
draggable: editable,
|
|
145
|
-
className: cn("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", editable ? "cursor-grab active:cursor-grabbing" : "cursor-pointer", iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && "opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"),
|
|
146
|
+
draggable: item.editable,
|
|
147
|
+
className: cn("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", item.editable ? "cursor-grab active:cursor-grabbing" : "cursor-pointer", iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && "opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"),
|
|
146
148
|
style: {
|
|
147
149
|
top: `${item.anchor.toggleTop}px`,
|
|
148
150
|
left: `${item.anchor.toggleLeft}px`,
|
|
@@ -150,7 +152,7 @@ function CompositeChrome({ editor }) {
|
|
|
150
152
|
height: `${item.anchor.toggleSize}px`
|
|
151
153
|
},
|
|
152
154
|
"aria-label": t("composites.chrome.details", { kind: item.row.kindLabel }),
|
|
153
|
-
title: editable ? t("composites.chrome.detailsDrag", { kind: item.row.kindLabel }) : t("composites.chrome.details", { kind: item.row.kindLabel }),
|
|
155
|
+
title: item.editable ? t("composites.chrome.detailsDrag", { kind: item.row.kindLabel }) : t("composites.chrome.details", { kind: item.row.kindLabel }),
|
|
154
156
|
onKeyDown: (event) => {
|
|
155
157
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
156
158
|
event.preventDefault();
|
|
@@ -170,9 +172,9 @@ function CompositeChrome({ editor }) {
|
|
|
170
172
|
children: /* @__PURE__ */ jsx(CompositeDetails, {
|
|
171
173
|
editor,
|
|
172
174
|
row: item.row,
|
|
173
|
-
editable
|
|
175
|
+
editable: item.editable
|
|
174
176
|
})
|
|
175
|
-
})] }), (editable || item.basket || item.status || item.derived || item.row.tagsVisible && item.row.tags.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
177
|
+
})] }), (item.editable || item.basket || item.status || item.derived || item.row.tagsVisible && item.row.tags.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
176
178
|
className: "absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background",
|
|
177
179
|
style: {
|
|
178
180
|
top: `${item.top}px`,
|
|
@@ -183,7 +185,7 @@ function CompositeChrome({ editor }) {
|
|
|
183
185
|
onMouseEnter: () => setChipsHoveredKey(item.row.key),
|
|
184
186
|
onMouseLeave: () => setChipsHoveredKey(null),
|
|
185
187
|
children: [
|
|
186
|
-
editable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CompositeBasketMenu, {
|
|
188
|
+
item.editable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CompositeBasketMenu, {
|
|
187
189
|
editor,
|
|
188
190
|
row: item.row,
|
|
189
191
|
children: /* @__PURE__ */ jsx("button", {
|
|
@@ -245,7 +247,7 @@ function CompositeChrome({ editor }) {
|
|
|
245
247
|
}), t("composites.derivation.stale")]
|
|
246
248
|
})
|
|
247
249
|
] }),
|
|
248
|
-
item.status && (editable ? /* @__PURE__ */ jsx(CompositeStatusMenu, {
|
|
250
|
+
item.status && (item.editable ? /* @__PURE__ */ jsx(CompositeStatusMenu, {
|
|
249
251
|
editor,
|
|
250
252
|
row: item.row,
|
|
251
253
|
children: /* @__PURE__ */ jsxs("button", {
|
|
@@ -269,7 +271,7 @@ function CompositeChrome({ editor }) {
|
|
|
269
271
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
270
272
|
className: "truncate",
|
|
271
273
|
children: ["#", tag]
|
|
272
|
-
}), editable && /* @__PURE__ */ jsx("button", {
|
|
274
|
+
}), item.editable && /* @__PURE__ */ jsx("button", {
|
|
273
275
|
type: "button",
|
|
274
276
|
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",
|
|
275
277
|
"aria-label": t("composites.tag.remove", { tag }),
|
|
@@ -280,7 +282,7 @@ function CompositeChrome({ editor }) {
|
|
|
280
282
|
})
|
|
281
283
|
})]
|
|
282
284
|
}, tag)),
|
|
283
|
-
editable && item.row.tagsVisible && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
285
|
+
item.editable && item.row.tagsVisible && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
284
286
|
asChild: true,
|
|
285
287
|
children: /* @__PURE__ */ jsx("button", {
|
|
286
288
|
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 { basketById, basketOf, titleHiddenOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeBasketMenu } from './CompositeBasketMenu'\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 const revealedTitles = useStore(editor.storage.composite.revealedTitles)\n\n const placed = useMemo(() => {\n const baskets = compositeBasketsOf(editor)\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.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 const titleHidden = titleHiddenOf(row)\n // A hidden title leaves nothing to measure: the chips sit in the strip beside the kind icon (`composites.css`).\n const inStrip = titleHidden && !(row.id && revealedTitles.has(row.id)) && anchor.toggleSize > 0\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,\n width: Math.max((frame?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n basket: basketById(baskets, basketOf(row)) ?? null,\n titleHidden,\n }]\n })\n }, [editor, rows, anchors, frame, expanded, revealedTitles, 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 function toggleTitleHidden(row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (found) editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos)\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 {(editable || item.basket || item.status || item.derived || (item.row.tagsVisible && item.row.tags.length > 0)) && (\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 {editable\n ? (\n <>\n <CompositeBasketMenu editor={editor} row={item.row}>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t('composites.basket.label')}\n title={item.basket ? `${t('composites.basket.label')}: ${item.basket.label}` : t('composites.basket.label')}\n >\n {item.basket\n ? <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n : <Icon name=\"i-lucide-circle-dashed\" className=\"size-3.5\" />}\n </button>\n </CompositeBasketMenu>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n title={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n onClick={() => toggleTitleHidden(item.row)}\n >\n <Icon name={item.titleHidden ? 'i-lucide-eye' : 'i-lucide-eye-off'} className=\"size-3.5\" />\n </button>\n </>\n )\n : item.basket && (\n <span className={CHIP} title={item.basket.label}>\n <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n </span>\n )}\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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"}
|
|
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 { basketById, basketOf, titleHiddenOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeBasketMenu } from './CompositeBasketMenu'\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 const revealedTitles = useStore(editor.storage.composite.revealedTitles)\n\n const placed = useMemo(() => {\n const baskets = compositeBasketsOf(editor)\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.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 const titleHidden = titleHiddenOf(row)\n // A hidden title leaves nothing to measure: the chips sit in the strip beside the kind icon (`composites.css`).\n const inStrip = titleHidden && !(row.id && revealedTitles.has(row.id)) && anchor.toggleSize > 0\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n // A kind without `chrome.controls` renders as in a read-only editor.\n editable: editable && row.controls,\n left,\n top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : 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 basket: basketById(baskets, basketOf(row)) ?? null,\n titleHidden,\n }]\n })\n }, [editor, rows, anchors, frame, expanded, revealedTitles, editable, 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 function toggleTitleHidden(row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (found) editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos)\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={item.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 item.editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n )}\n style={{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }}\n aria-label={t('composites.chrome.details', { kind: item.row.kindLabel })}\n title={item.editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })}\n 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={item.editable} />\n </PopoverContent>\n </Popover>\n )}\n\n {(item.editable || item.basket || item.status || item.derived || (item.row.tagsVisible && item.row.tags.length > 0)) && (\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.editable\n ? (\n <>\n <CompositeBasketMenu editor={editor} row={item.row}>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t('composites.basket.label')}\n title={item.basket ? `${t('composites.basket.label')}: ${item.basket.label}` : t('composites.basket.label')}\n >\n {item.basket\n ? <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n : <Icon name=\"i-lucide-circle-dashed\" className=\"size-3.5\" />}\n </button>\n </CompositeBasketMenu>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n title={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n onClick={() => toggleTitleHidden(item.row)}\n >\n <Icon name={item.titleHidden ? 'i-lucide-eye' : 'i-lucide-eye-off'} className=\"size-3.5\" />\n </button>\n </>\n )\n : item.basket && (\n <span className={CHIP} title={item.basket.label}>\n <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n </span>\n )}\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 && (item.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 {item.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 {item.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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"}
|
|
@@ -29,6 +29,8 @@ export interface CompositeRow {
|
|
|
29
29
|
fields: readonly CompositeMetaField[];
|
|
30
30
|
/** `false` when the composite's kind hides its tag row (`CompositeKind.chrome.tags === false`): chips, the add-tag control and the tags field stay away. */
|
|
31
31
|
tagsVisible: boolean;
|
|
32
|
+
/** `false` when the composite's kind has `chrome.controls === false`: its chrome (basket, status, title visibility, tags, drag handle) is read-only even in an editable editor. */
|
|
33
|
+
controls: boolean;
|
|
32
34
|
/** Nesting level among composites, 0 = outermost. */
|
|
33
35
|
depth: number;
|
|
34
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rows.js","names":[],"sources":["../../../src/features/composites/rows.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport { type CompositeKind, type CompositeMetaField, compositeKind } from './kinds'\nimport { referenceLabel, referenceTarget } from './reference-label'\n\n/** One composite („Baustein“) of a document with its metadata, flattened in document order. */\nexport interface CompositeRow {\n /** Block ID, or `pos:<pos>` for a composite without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite when the row was read; the write helpers re-resolve it, so a stale value is safe. */\n pos: number\n /** Position right after the composite, as of when the row was read. */\n end: number\n kind: string\n kindLabel: string\n icon: string\n /** Title text, empty for an untitled composite. */\n title: string\n /** Text of the description (summary), empty when there is none. */\n description: string\n /**\n * The description holds text only (marks such as bold, author or comment allowed, no mentions or tags); only then\n * can `setCompositeDescription` write it without losing inline content.\n */\n descriptionTextOnly: boolean\n tags: string[]\n meta: Record<string, unknown>\n /** The meta fields of the composite's kind. */\n fields: readonly CompositeMetaField[]\n /** `false` when the composite's kind hides its tag row (`CompositeKind.chrome.tags === false`): chips, the add-tag control and the tags field stay away. */\n tagsVisible: boolean\n /** Nesting level among composites, 0 = outermost. */\n depth: number\n}\n\n/** A distinct `@mention` chip of a document. */\nexport interface CompositeMention {\n id: string\n label: string\n color: string | null\n}\n\n/** A reference to a Baustein: a `reference` chip in the text of block `from`, or a `reference` field of the Baustein `from`. */\nexport interface CompositeReference {\n /** Block ID of the referenced Baustein. */\n target: string\n /** Block ID of the nearest block around the chip (the Baustein itself for a field); `null` when that block has no ID yet. */\n from: string | null\n /** The chip's locator (`S. 12`, `03:14`); `null` for a field or a chip without one. */\n locator: string | null\n}\n\nconst COMPOSITE = 'composite'\n\n/** True when `node`'s content is empty or text only (with any marks, but no inline nodes such as mentions or tags). */\nfunction isTextOnly(node: PMNode): boolean {\n let textOnly = true\n node.forEach((child) => {\n if (!child.isText) textOnly = false\n })\n return textOnly\n}\n\n/**\n * Reads every composite of `doc` (also inside quotes and lists) as a row, in document order, the distinct\n * `@mention` chips anywhere in the document and every reference (`reference` chips and `reference` fields) in document\n * order. Unknown kinds get the `section` kind's presentation under their own ID.\n */\nexport function compositeRows(doc: PMNode, kinds: readonly CompositeKind[]): { rows: CompositeRow[], mentions: CompositeMention[], references: CompositeReference[] } {\n const rows: CompositeRow[] = []\n const mentions = new Map<string, CompositeMention>()\n const references: CompositeReference[] = []\n\n function walk(parent: PMNode, start: number, depth: number, block: string | null) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === COMPOSITE) {\n const kind = compositeKind(kinds, String(node.attrs.kind))\n const id = blockIdOf(node)\n const tags: unknown = node.maybeChild(2)?.attrs.tags\n const description = node.maybeChild(1)\n const row: CompositeRow = {\n key: id ?? `pos:${pos}`,\n id,\n pos,\n end: pos + node.nodeSize,\n kind: kind.id,\n kindLabel: kind.label,\n icon: kind.icon,\n title: node.firstChild?.textContent.trim() ?? '',\n description: description?.textContent ?? '',\n descriptionTextOnly: description ? isTextOnly(description) : true,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n meta: (node.attrs.meta ?? {}) as Record<string, unknown>,\n fields: kind.fields,\n tagsVisible: kind.chrome?.tags !== false,\n depth,\n }\n rows.push(row)\n for (const field of kind.fields) {\n const value = field.type === 'reference' ? row.meta[field.key] : undefined\n if (typeof value === 'string' && value) references.push({ target: value, from: id, locator: null })\n }\n walk(node, pos + 1, depth + 1, id ?? block)\n }\n else if (node.type.name === 'reference') {\n const target = String(node.attrs.target ?? '')\n if (target) references.push({ target, from: block, locator: typeof node.attrs.locator === 'string' && node.attrs.locator ? node.attrs.locator : null })\n }\n else if (node.type.name === 'mention') {\n const mentionId = String(node.attrs.id ?? '')\n if (mentionId && !mentions.has(mentionId)) {\n mentions.set(mentionId, {\n id: mentionId,\n label: String(node.attrs.label ?? mentionId),\n color: typeof node.attrs.color === 'string' ? node.attrs.color : null,\n })\n }\n }\n else if (!node.isLeaf) {\n walk(node, pos + 1, depth, blockIdOf(node) ?? block)\n }\n })\n }\n walk(doc, 0, 0, null)\n return { rows, mentions: [...mentions.values()], references }\n}\n\n/** A place that refers to a Baustein: the block holding the chip (or the Baustein with the field) and a short text of it. */\nexport interface CompositeBacklink {\n from: string\n locator: string | null\n excerpt: string\n}\n\nconst EXCERPT_LENGTH = 40\n\n/** The text of `node` as read on screen: inline atoms count as their label (`@name`, `#tag`, a reference chip's short citation + locator), not as nothing. */\nfunction visibleText(doc: PMNode, kinds: readonly CompositeKind[], node: PMNode): string {\n return node.textBetween(0, node.content.size, ' ', (leaf) => {\n const attrs = leaf.attrs\n switch (leaf.type.name) {\n case 'mention': return `@${String(attrs.label ?? attrs.id ?? '')}`\n case 'tag': return `#${String(attrs.name ?? '')}`\n case 'reference': {\n const target = findBlockById(doc, String(attrs.target ?? ''))\n const label = target?.node.type.name === COMPOSITE ? referenceLabel(referenceTarget(target.node, kinds)) : String(attrs.label ?? '')\n return typeof attrs.locator === 'string' && attrs.locator ? `${label}, ${attrs.locator}` : label\n }\n default: return ''\n }\n })\n}\n\n/** Every reference to the Baustein `target` in document order; references whose enclosing block has no ID (or is gone) are left out, as nothing could reveal them. */\nexport function compositeBacklinks(doc: PMNode, kinds: readonly CompositeKind[], target: string): CompositeBacklink[] {\n const links: CompositeBacklink[] = []\n for (const reference of compositeRows(doc, kinds).references) {\n if (reference.target !== target || !reference.from) continue\n const found = findBlockById(doc, reference.from)\n if (!found) continue\n const source = found.node.type.name === COMPOSITE ? found.node.firstChild : found.node\n const text = source ? visibleText(doc, kinds, source).trim() : ''\n links.push({ from: reference.from, locator: reference.locator, excerpt: text.length > EXCERPT_LENGTH ? `${text.slice(0, EXCERPT_LENGTH)}…` : text })\n }\n return links\n}\n\n/** The innermost row around document position `pos`, `null` outside of any composite. */\nexport function compositeRowAt(rows: readonly CompositeRow[], pos: number): CompositeRow | null {\n let found: CompositeRow | null = null\n // Pre-order: the last match is the innermost composite.\n for (const row of rows) {\n if (row.pos <= pos && pos < row.end) found = row\n }\n return found\n}\n\n/**\n * The composite `row` stands for in `doc`: by block ID, else (the row has no ID yet) by its position.\n * Positions are re-resolved on every call, so a stale `row.pos` is safe for rows with an ID.\n */\nexport function compositeOfRow(doc: PMNode, row: Pick<CompositeRow, 'id' | 'pos'>): { pos: number, node: PMNode } | null {\n if (row.id) {\n const found = findBlockById(doc, row.id)\n return found?.node.type.name === COMPOSITE ? found : null\n }\n const node = doc.nodeAt(row.pos)\n return node?.type.name === COMPOSITE ? { pos: row.pos, node } : null\n}\n\n/**\n * `meta` with `value` stored under `field.key`: a number field converts a numeric string, an empty value (`''`,\n * `null`, `undefined`, `NaN`) removes the key. A `multiselect` field keeps the string entries of an array value and\n * removes the key when none is left. Returns a new object; `meta` is not changed.\n */\nexport function compositeMetaWith(meta: Record<string, unknown>, field: Pick<CompositeMetaField, 'key' | 'type'>, value: unknown): Record<string, unknown> {\n const next = { ...meta }\n if (field.type === 'multiselect') {\n const chosen = Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []\n if (chosen.length) next[field.key] = chosen\n else delete next[field.key]\n return next\n }\n const stored = field.type === 'number' && typeof value === 'string' && value !== '' ? Number(value) : value\n if (stored === '' || stored === null || stored === undefined || Number.isNaN(stored)) delete next[field.key]\n else next[field.key] = stored\n return next\n}\n\n/** Stores `value` under the meta field `key` of the row's composite (see `compositeMetaWith`), in one transaction. */\nexport function setCompositeMetaField(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos' | 'fields'>, key: string, value: unknown): boolean {\n const field = row.fields.find(candidate => candidate.key === key) ?? { key, type: 'text' as const }\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n if (!found) return false\n tr.setNodeMarkup(found.pos, undefined, { ...found.node.attrs, meta: compositeMetaWith(found.node.attrs.meta ?? {}, field, value) })\n return true\n })\n}\n\n/** Replaces the tags row of the row's composite (empty names are dropped), in one transaction. */\nexport function setCompositeTags(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>, tags: readonly string[]): boolean {\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n const title = found?.node.maybeChild(0)\n const description = found?.node.maybeChild(1)\n const tagsNode = found?.node.maybeChild(2)\n if (!found || !title || !description || tagsNode?.type.name !== 'compositeTags') return false\n const tagsPos = found.pos + 1 + title.nodeSize + description.nodeSize\n tr.setNodeMarkup(tagsPos, undefined, { ...tagsNode.attrs, tags: tags.filter(tag => tag) })\n return true\n })\n}\n\n/** The text `setCompositeDescription` stores for `text`: the description is one line, so line breaks collapse to spaces. */\nexport function compositeDescriptionText(text: string): string {\n return text.replace(/\\s*\\n\\s*/g, ' ').trim()\n}\n\n/**\n * Sets the text of the row's composite description to `compositeDescriptionText(text)`; an empty text empties it.\n * Only the changed span is replaced, so marks on the unchanged text (bold, author, comment) stay. Refuses (`false`)\n * when the description holds a mention or tag (it would be lost), and, when `expected` is given, when the\n * description no longer holds that text (a peer changed it since the caller read it; writing would drop that change).\n */\nexport function setCompositeDescription(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>, text: string, expected?: string): boolean {\n const value = compositeDescriptionText(text)\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n const title = found?.node.maybeChild(0)\n const description = found?.node.maybeChild(1)\n if (!found || !title || !description || description.type.name !== 'compositeDescription') return false\n if (!isTextOnly(description)) return false\n const current = description.textContent\n if (expected !== undefined && current !== expected) return false\n if (current === value) return true\n // Text only: every character is one position. Keep the common prefix and suffix, replace what lies between.\n let prefix = 0\n const max = Math.min(current.length, value.length)\n while (prefix < max && current[prefix] === value[prefix]) prefix++\n let suffix = 0\n while (suffix < max - prefix && current[current.length - 1 - suffix] === value[value.length - 1 - suffix]) suffix++\n const start = found.pos + 1 + title.nodeSize + 1\n // `insertText` with an empty text deletes the range; inserted text takes the marks at its start, as typing does.\n tr.insertText(value.slice(prefix, value.length - suffix), start + prefix, start + current.length - suffix)\n return true\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rows.js","names":[],"sources":["../../../src/features/composites/rows.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { blockIdOf, findBlockById } from '../../core/schema'\nimport { type CompositeKind, type CompositeMetaField, compositeKind } from './kinds'\nimport { referenceLabel, referenceTarget } from './reference-label'\n\n/** One composite („Baustein“) of a document with its metadata, flattened in document order. */\nexport interface CompositeRow {\n /** Block ID, or `pos:<pos>` for a composite without one yet; unique within the document. */\n key: string\n id: string | null\n /** Position of the composite when the row was read; the write helpers re-resolve it, so a stale value is safe. */\n pos: number\n /** Position right after the composite, as of when the row was read. */\n end: number\n kind: string\n kindLabel: string\n icon: string\n /** Title text, empty for an untitled composite. */\n title: string\n /** Text of the description (summary), empty when there is none. */\n description: string\n /**\n * The description holds text only (marks such as bold, author or comment allowed, no mentions or tags); only then\n * can `setCompositeDescription` write it without losing inline content.\n */\n descriptionTextOnly: boolean\n tags: string[]\n meta: Record<string, unknown>\n /** The meta fields of the composite's kind. */\n fields: readonly CompositeMetaField[]\n /** `false` when the composite's kind hides its tag row (`CompositeKind.chrome.tags === false`): chips, the add-tag control and the tags field stay away. */\n tagsVisible: boolean\n /** `false` when the composite's kind has `chrome.controls === false`: its chrome (basket, status, title visibility, tags, drag handle) is read-only even in an editable editor. */\n controls: boolean\n /** Nesting level among composites, 0 = outermost. */\n depth: number\n}\n\n/** A distinct `@mention` chip of a document. */\nexport interface CompositeMention {\n id: string\n label: string\n color: string | null\n}\n\n/** A reference to a Baustein: a `reference` chip in the text of block `from`, or a `reference` field of the Baustein `from`. */\nexport interface CompositeReference {\n /** Block ID of the referenced Baustein. */\n target: string\n /** Block ID of the nearest block around the chip (the Baustein itself for a field); `null` when that block has no ID yet. */\n from: string | null\n /** The chip's locator (`S. 12`, `03:14`); `null` for a field or a chip without one. */\n locator: string | null\n}\n\nconst COMPOSITE = 'composite'\n\n/** True when `node`'s content is empty or text only (with any marks, but no inline nodes such as mentions or tags). */\nfunction isTextOnly(node: PMNode): boolean {\n let textOnly = true\n node.forEach((child) => {\n if (!child.isText) textOnly = false\n })\n return textOnly\n}\n\n/**\n * Reads every composite of `doc` (also inside quotes and lists) as a row, in document order, the distinct\n * `@mention` chips anywhere in the document and every reference (`reference` chips and `reference` fields) in document\n * order. Unknown kinds get the `section` kind's presentation under their own ID.\n */\nexport function compositeRows(doc: PMNode, kinds: readonly CompositeKind[]): { rows: CompositeRow[], mentions: CompositeMention[], references: CompositeReference[] } {\n const rows: CompositeRow[] = []\n const mentions = new Map<string, CompositeMention>()\n const references: CompositeReference[] = []\n\n function walk(parent: PMNode, start: number, depth: number, block: string | null) {\n parent.forEach((node, offset) => {\n const pos = start + offset\n if (node.type.name === COMPOSITE) {\n const kind = compositeKind(kinds, String(node.attrs.kind))\n const id = blockIdOf(node)\n const tags: unknown = node.maybeChild(2)?.attrs.tags\n const description = node.maybeChild(1)\n const row: CompositeRow = {\n key: id ?? `pos:${pos}`,\n id,\n pos,\n end: pos + node.nodeSize,\n kind: kind.id,\n kindLabel: kind.label,\n icon: kind.icon,\n title: node.firstChild?.textContent.trim() ?? '',\n description: description?.textContent ?? '',\n descriptionTextOnly: description ? isTextOnly(description) : true,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n meta: (node.attrs.meta ?? {}) as Record<string, unknown>,\n fields: kind.fields,\n tagsVisible: kind.chrome?.tags !== false,\n controls: kind.chrome?.controls !== false,\n depth,\n }\n rows.push(row)\n for (const field of kind.fields) {\n const value = field.type === 'reference' ? row.meta[field.key] : undefined\n if (typeof value === 'string' && value) references.push({ target: value, from: id, locator: null })\n }\n walk(node, pos + 1, depth + 1, id ?? block)\n }\n else if (node.type.name === 'reference') {\n const target = String(node.attrs.target ?? '')\n if (target) references.push({ target, from: block, locator: typeof node.attrs.locator === 'string' && node.attrs.locator ? node.attrs.locator : null })\n }\n else if (node.type.name === 'mention') {\n const mentionId = String(node.attrs.id ?? '')\n if (mentionId && !mentions.has(mentionId)) {\n mentions.set(mentionId, {\n id: mentionId,\n label: String(node.attrs.label ?? mentionId),\n color: typeof node.attrs.color === 'string' ? node.attrs.color : null,\n })\n }\n }\n else if (!node.isLeaf) {\n walk(node, pos + 1, depth, blockIdOf(node) ?? block)\n }\n })\n }\n walk(doc, 0, 0, null)\n return { rows, mentions: [...mentions.values()], references }\n}\n\n/** A place that refers to a Baustein: the block holding the chip (or the Baustein with the field) and a short text of it. */\nexport interface CompositeBacklink {\n from: string\n locator: string | null\n excerpt: string\n}\n\nconst EXCERPT_LENGTH = 40\n\n/** The text of `node` as read on screen: inline atoms count as their label (`@name`, `#tag`, a reference chip's short citation + locator), not as nothing. */\nfunction visibleText(doc: PMNode, kinds: readonly CompositeKind[], node: PMNode): string {\n return node.textBetween(0, node.content.size, ' ', (leaf) => {\n const attrs = leaf.attrs\n switch (leaf.type.name) {\n case 'mention': return `@${String(attrs.label ?? attrs.id ?? '')}`\n case 'tag': return `#${String(attrs.name ?? '')}`\n case 'reference': {\n const target = findBlockById(doc, String(attrs.target ?? ''))\n const label = target?.node.type.name === COMPOSITE ? referenceLabel(referenceTarget(target.node, kinds)) : String(attrs.label ?? '')\n return typeof attrs.locator === 'string' && attrs.locator ? `${label}, ${attrs.locator}` : label\n }\n default: return ''\n }\n })\n}\n\n/** Every reference to the Baustein `target` in document order; references whose enclosing block has no ID (or is gone) are left out, as nothing could reveal them. */\nexport function compositeBacklinks(doc: PMNode, kinds: readonly CompositeKind[], target: string): CompositeBacklink[] {\n const links: CompositeBacklink[] = []\n for (const reference of compositeRows(doc, kinds).references) {\n if (reference.target !== target || !reference.from) continue\n const found = findBlockById(doc, reference.from)\n if (!found) continue\n const source = found.node.type.name === COMPOSITE ? found.node.firstChild : found.node\n const text = source ? visibleText(doc, kinds, source).trim() : ''\n links.push({ from: reference.from, locator: reference.locator, excerpt: text.length > EXCERPT_LENGTH ? `${text.slice(0, EXCERPT_LENGTH)}…` : text })\n }\n return links\n}\n\n/** The innermost row around document position `pos`, `null` outside of any composite. */\nexport function compositeRowAt(rows: readonly CompositeRow[], pos: number): CompositeRow | null {\n let found: CompositeRow | null = null\n // Pre-order: the last match is the innermost composite.\n for (const row of rows) {\n if (row.pos <= pos && pos < row.end) found = row\n }\n return found\n}\n\n/**\n * The composite `row` stands for in `doc`: by block ID, else (the row has no ID yet) by its position.\n * Positions are re-resolved on every call, so a stale `row.pos` is safe for rows with an ID.\n */\nexport function compositeOfRow(doc: PMNode, row: Pick<CompositeRow, 'id' | 'pos'>): { pos: number, node: PMNode } | null {\n if (row.id) {\n const found = findBlockById(doc, row.id)\n return found?.node.type.name === COMPOSITE ? found : null\n }\n const node = doc.nodeAt(row.pos)\n return node?.type.name === COMPOSITE ? { pos: row.pos, node } : null\n}\n\n/**\n * `meta` with `value` stored under `field.key`: a number field converts a numeric string, an empty value (`''`,\n * `null`, `undefined`, `NaN`) removes the key. A `multiselect` field keeps the string entries of an array value and\n * removes the key when none is left. Returns a new object; `meta` is not changed.\n */\nexport function compositeMetaWith(meta: Record<string, unknown>, field: Pick<CompositeMetaField, 'key' | 'type'>, value: unknown): Record<string, unknown> {\n const next = { ...meta }\n if (field.type === 'multiselect') {\n const chosen = Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []\n if (chosen.length) next[field.key] = chosen\n else delete next[field.key]\n return next\n }\n const stored = field.type === 'number' && typeof value === 'string' && value !== '' ? Number(value) : value\n if (stored === '' || stored === null || stored === undefined || Number.isNaN(stored)) delete next[field.key]\n else next[field.key] = stored\n return next\n}\n\n/** Stores `value` under the meta field `key` of the row's composite (see `compositeMetaWith`), in one transaction. */\nexport function setCompositeMetaField(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos' | 'fields'>, key: string, value: unknown): boolean {\n const field = row.fields.find(candidate => candidate.key === key) ?? { key, type: 'text' as const }\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n if (!found) return false\n tr.setNodeMarkup(found.pos, undefined, { ...found.node.attrs, meta: compositeMetaWith(found.node.attrs.meta ?? {}, field, value) })\n return true\n })\n}\n\n/** Replaces the tags row of the row's composite (empty names are dropped), in one transaction. */\nexport function setCompositeTags(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>, tags: readonly string[]): boolean {\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n const title = found?.node.maybeChild(0)\n const description = found?.node.maybeChild(1)\n const tagsNode = found?.node.maybeChild(2)\n if (!found || !title || !description || tagsNode?.type.name !== 'compositeTags') return false\n const tagsPos = found.pos + 1 + title.nodeSize + description.nodeSize\n tr.setNodeMarkup(tagsPos, undefined, { ...tagsNode.attrs, tags: tags.filter(tag => tag) })\n return true\n })\n}\n\n/** The text `setCompositeDescription` stores for `text`: the description is one line, so line breaks collapse to spaces. */\nexport function compositeDescriptionText(text: string): string {\n return text.replace(/\\s*\\n\\s*/g, ' ').trim()\n}\n\n/**\n * Sets the text of the row's composite description to `compositeDescriptionText(text)`; an empty text empties it.\n * Only the changed span is replaced, so marks on the unchanged text (bold, author, comment) stay. Refuses (`false`)\n * when the description holds a mention or tag (it would be lost), and, when `expected` is given, when the\n * description no longer holds that text (a peer changed it since the caller read it; writing would drop that change).\n */\nexport function setCompositeDescription(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>, text: string, expected?: string): boolean {\n const value = compositeDescriptionText(text)\n return editor.commands.command(({ tr }) => {\n const found = compositeOfRow(tr.doc, row)\n const title = found?.node.maybeChild(0)\n const description = found?.node.maybeChild(1)\n if (!found || !title || !description || description.type.name !== 'compositeDescription') return false\n if (!isTextOnly(description)) return false\n const current = description.textContent\n if (expected !== undefined && current !== expected) return false\n if (current === value) return true\n // Text only: every character is one position. Keep the common prefix and suffix, replace what lies between.\n let prefix = 0\n const max = Math.min(current.length, value.length)\n while (prefix < max && current[prefix] === value[prefix]) prefix++\n let suffix = 0\n while (suffix < max - prefix && current[current.length - 1 - suffix] === value[value.length - 1 - suffix]) suffix++\n const start = found.pos + 1 + title.nodeSize + 1\n // `insertText` with an empty text deletes the range; inserted text takes the marks at its start, as typing does.\n tr.insertText(value.slice(prefix, value.length - suffix), start + prefix, start + current.length - suffix)\n return true\n })\n}\n"],"mappings":";;;;AAwDA,MAAM,YAAY;;AAGlB,SAAS,WAAW,MAAuB;CACzC,IAAI,WAAW;CACf,KAAK,SAAS,UAAU;EACtB,IAAI,CAAC,MAAM,QAAQ,WAAW;CAChC,CAAC;CACD,OAAO;AACT;;;;;;AAOA,SAAgB,cAAc,KAAa,OAA2H;CACpK,MAAM,OAAuB,CAAC;CAC9B,MAAM,2BAAW,IAAI,IAA8B;CACnD,MAAM,aAAmC,CAAC;CAE1C,SAAS,KAAK,QAAgB,OAAe,OAAe,OAAsB;EAChF,OAAO,SAAS,MAAM,WAAW;GAC/B,MAAM,MAAM,QAAQ;GACpB,IAAI,KAAK,KAAK,SAAS,WAAW;IAChC,MAAM,OAAO,cAAc,OAAO,OAAO,KAAK,MAAM,IAAI,CAAC;IACzD,MAAM,KAAK,UAAU,IAAI;IACzB,MAAM,OAAgB,KAAK,WAAW,CAAC,CAAC,EAAE,MAAM;IAChD,MAAM,cAAc,KAAK,WAAW,CAAC;IACrC,MAAM,MAAoB;KACxB,KAAK,MAAM,OAAO;KAClB;KACA;KACA,KAAK,MAAM,KAAK;KAChB,MAAM,KAAK;KACX,WAAW,KAAK;KAChB,MAAM,KAAK;KACX,OAAO,KAAK,YAAY,YAAY,KAAK,KAAK;KAC9C,aAAa,aAAa,eAAe;KACzC,qBAAqB,cAAc,WAAW,WAAW,IAAI;KAC7D,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ,QAAuB,OAAO,QAAQ,QAAQ,IAAI,CAAC;KAC5F,MAAO,KAAK,MAAM,QAAQ,CAAC;KAC3B,QAAQ,KAAK;KACb,aAAa,KAAK,QAAQ,SAAS;KACnC,UAAU,KAAK,QAAQ,aAAa;KACpC;IACF;IACA,KAAK,KAAK,GAAG;IACb,KAAK,MAAM,SAAS,KAAK,QAAQ;KAC/B,MAAM,QAAQ,MAAM,SAAS,cAAc,IAAI,KAAK,MAAM,OAAO,KAAA;KACjE,IAAI,OAAO,UAAU,YAAY,OAAO,WAAW,KAAK;MAAE,QAAQ;MAAO,MAAM;MAAI,SAAS;KAAK,CAAC;IACpG;IACA,KAAK,MAAM,MAAM,GAAG,QAAQ,GAAG,MAAM,KAAK;GAC5C,OACK,IAAI,KAAK,KAAK,SAAS,aAAa;IACvC,MAAM,SAAS,OAAO,KAAK,MAAM,UAAU,EAAE;IAC7C,IAAI,QAAQ,WAAW,KAAK;KAAE;KAAQ,MAAM;KAAO,SAAS,OAAO,KAAK,MAAM,YAAY,YAAY,KAAK,MAAM,UAAU,KAAK,MAAM,UAAU;IAAK,CAAC;GACxJ,OACK,IAAI,KAAK,KAAK,SAAS,WAAW;IACrC,MAAM,YAAY,OAAO,KAAK,MAAM,MAAM,EAAE;IAC5C,IAAI,aAAa,CAAC,SAAS,IAAI,SAAS,GACtC,SAAS,IAAI,WAAW;KACtB,IAAI;KACJ,OAAO,OAAO,KAAK,MAAM,SAAS,SAAS;KAC3C,OAAO,OAAO,KAAK,MAAM,UAAU,WAAW,KAAK,MAAM,QAAQ;IACnE,CAAC;GAEL,OACK,IAAI,CAAC,KAAK,QACb,KAAK,MAAM,MAAM,GAAG,OAAO,UAAU,IAAI,KAAK,KAAK;EAEvD,CAAC;CACH;CACA,KAAK,KAAK,GAAG,GAAG,IAAI;CACpB,OAAO;EAAE;EAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;EAAG;CAAW;AAC9D;AASA,MAAM,iBAAiB;;AAGvB,SAAS,YAAY,KAAa,OAAiC,MAAsB;CACvF,OAAO,KAAK,YAAY,GAAG,KAAK,QAAQ,MAAM,MAAM,SAAS;EAC3D,MAAM,QAAQ,KAAK;EACnB,QAAQ,KAAK,KAAK,MAAlB;GACE,KAAK,WAAW,OAAO,IAAI,OAAO,MAAM,SAAS,MAAM,MAAM,EAAE;GAC/D,KAAK,OAAO,OAAO,IAAI,OAAO,MAAM,QAAQ,EAAE;GAC9C,KAAK,aAAa;IAChB,MAAM,SAAS,cAAc,KAAK,OAAO,MAAM,UAAU,EAAE,CAAC;IAC5D,MAAM,QAAQ,QAAQ,KAAK,KAAK,SAAS,YAAY,eAAe,gBAAgB,OAAO,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,SAAS,EAAE;IACnI,OAAO,OAAO,MAAM,YAAY,YAAY,MAAM,UAAU,GAAG,MAAM,IAAI,MAAM,YAAY;GAC7F;GACA,SAAS,OAAO;EAClB;CACF,CAAC;AACH;;AAGA,SAAgB,mBAAmB,KAAa,OAAiC,QAAqC;CACpH,MAAM,QAA6B,CAAC;CACpC,KAAK,MAAM,aAAa,cAAc,KAAK,KAAK,CAAC,CAAC,YAAY;EAC5D,IAAI,UAAU,WAAW,UAAU,CAAC,UAAU,MAAM;EACpD,MAAM,QAAQ,cAAc,KAAK,UAAU,IAAI;EAC/C,IAAI,CAAC,OAAO;EACZ,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS,YAAY,MAAM,KAAK,aAAa,MAAM;EAClF,MAAM,OAAO,SAAS,YAAY,KAAK,OAAO,MAAM,CAAC,CAAC,KAAK,IAAI;EAC/D,MAAM,KAAK;GAAE,MAAM,UAAU;GAAM,SAAS,UAAU;GAAS,SAAS,KAAK,SAAS,iBAAiB,GAAG,KAAK,MAAM,GAAG,cAAc,EAAE,KAAK;EAAK,CAAC;CACrJ;CACA,OAAO;AACT;;AAGA,SAAgB,eAAe,MAA+B,KAAkC;CAC9F,IAAI,QAA6B;CAEjC,KAAK,MAAM,OAAO,MAChB,IAAI,IAAI,OAAO,OAAO,MAAM,IAAI,KAAK,QAAQ;CAE/C,OAAO;AACT;;;;;AAMA,SAAgB,eAAe,KAAa,KAA6E;CACvH,IAAI,IAAI,IAAI;EACV,MAAM,QAAQ,cAAc,KAAK,IAAI,EAAE;EACvC,OAAO,OAAO,KAAK,KAAK,SAAS,YAAY,QAAQ;CACvD;CACA,MAAM,OAAO,IAAI,OAAO,IAAI,GAAG;CAC/B,OAAO,MAAM,KAAK,SAAS,YAAY;EAAE,KAAK,IAAI;EAAK;CAAK,IAAI;AAClE;;;;;;AAOA,SAAgB,kBAAkB,MAA+B,OAAiD,OAAyC;CACzJ,MAAM,OAAO,EAAE,GAAG,KAAK;CACvB,IAAI,MAAM,SAAS,eAAe;EAChC,MAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,SAAyB,OAAO,SAAS,QAAQ,IAAI,CAAC;EAC1G,IAAI,OAAO,QAAQ,KAAK,MAAM,OAAO;OAChC,OAAO,KAAK,MAAM;EACvB,OAAO;CACT;CACA,MAAM,SAAS,MAAM,SAAS,YAAY,OAAO,UAAU,YAAY,UAAU,KAAK,OAAO,KAAK,IAAI;CACtG,IAAI,WAAW,MAAM,WAAW,QAAQ,WAAW,KAAA,KAAa,OAAO,MAAM,MAAM,GAAG,OAAO,KAAK,MAAM;MACnG,KAAK,MAAM,OAAO;CACvB,OAAO;AACT;;AAGA,SAAgB,sBAAsB,QAAgB,KAAkD,KAAa,OAAyB;CAC5I,MAAM,QAAQ,IAAI,OAAO,MAAK,cAAa,UAAU,QAAQ,GAAG,KAAK;EAAE;EAAK,MAAM;CAAgB;CAClG,OAAO,OAAO,SAAS,SAAS,EAAE,SAAS;EACzC,MAAM,QAAQ,eAAe,GAAG,KAAK,GAAG;EACxC,IAAI,CAAC,OAAO,OAAO;EACnB,GAAG,cAAc,MAAM,KAAK,KAAA,GAAW;GAAE,GAAG,MAAM,KAAK;GAAO,MAAM,kBAAkB,MAAM,KAAK,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK;EAAE,CAAC;EAClI,OAAO;CACT,CAAC;AACH;;AAGA,SAAgB,iBAAiB,QAAgB,KAAuC,MAAkC;CACxH,OAAO,OAAO,SAAS,SAAS,EAAE,SAAS;EACzC,MAAM,QAAQ,eAAe,GAAG,KAAK,GAAG;EACxC,MAAM,QAAQ,OAAO,KAAK,WAAW,CAAC;EACtC,MAAM,cAAc,OAAO,KAAK,WAAW,CAAC;EAC5C,MAAM,WAAW,OAAO,KAAK,WAAW,CAAC;EACzC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAAe,UAAU,KAAK,SAAS,iBAAiB,OAAO;EACxF,MAAM,UAAU,MAAM,MAAM,IAAI,MAAM,WAAW,YAAY;EAC7D,GAAG,cAAc,SAAS,KAAA,GAAW;GAAE,GAAG,SAAS;GAAO,MAAM,KAAK,QAAO,QAAO,GAAG;EAAE,CAAC;EACzF,OAAO;CACT,CAAC;AACH;;AAGA,SAAgB,yBAAyB,MAAsB;CAC7D,OAAO,KAAK,QAAQ,aAAa,GAAG,CAAC,CAAC,KAAK;AAC7C;;;;;;;AAQA,SAAgB,wBAAwB,QAAgB,KAAuC,MAAc,UAA4B;CACvI,MAAM,QAAQ,yBAAyB,IAAI;CAC3C,OAAO,OAAO,SAAS,SAAS,EAAE,SAAS;EACzC,MAAM,QAAQ,eAAe,GAAG,KAAK,GAAG;EACxC,MAAM,QAAQ,OAAO,KAAK,WAAW,CAAC;EACtC,MAAM,cAAc,OAAO,KAAK,WAAW,CAAC;EAC5C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAAe,YAAY,KAAK,SAAS,wBAAwB,OAAO;EACjG,IAAI,CAAC,WAAW,WAAW,GAAG,OAAO;EACrC,MAAM,UAAU,YAAY;EAC5B,IAAI,aAAa,KAAA,KAAa,YAAY,UAAU,OAAO;EAC3D,IAAI,YAAY,OAAO,OAAO;EAE9B,IAAI,SAAS;EACb,MAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ,MAAM,MAAM;EACjD,OAAO,SAAS,OAAO,QAAQ,YAAY,MAAM,SAAS;EAC1D,IAAI,SAAS;EACb,OAAO,SAAS,MAAM,UAAU,QAAQ,QAAQ,SAAS,IAAI,YAAY,MAAM,MAAM,SAAS,IAAI,SAAS;EAC3G,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM,WAAW;EAE/C,GAAG,WAAW,MAAM,MAAM,QAAQ,MAAM,SAAS,MAAM,GAAG,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,MAAM;EACzG,OAAO;CACT,CAAC;AACH"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
2
|
+
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
3
|
+
import { Fragment, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useId, useTemplateRef, watch } from "vue";
|
|
4
|
+
//#region src/features/image-studio/components/ImageStudioChat.vue
|
|
5
|
+
const _hoisted_1 = ["aria-labelledby"];
|
|
6
|
+
const _hoisted_2 = ["id"];
|
|
7
|
+
const _hoisted_3 = {
|
|
8
|
+
ref: "log",
|
|
9
|
+
class: "flex min-h-24 flex-1 flex-col gap-2 overflow-y-auto",
|
|
10
|
+
"data-image-studio-log": ""
|
|
11
|
+
};
|
|
12
|
+
const _hoisted_4 = {
|
|
13
|
+
key: 0,
|
|
14
|
+
class: "text-sm text-[var(--sk-muted)]"
|
|
15
|
+
};
|
|
16
|
+
const _hoisted_5 = ["data-problem"];
|
|
17
|
+
const _hoisted_6 = ["src", "alt"];
|
|
18
|
+
const _hoisted_7 = { class: "min-w-0 flex-1" };
|
|
19
|
+
const _hoisted_8 = {
|
|
20
|
+
key: 0,
|
|
21
|
+
class: "mt-1 whitespace-pre-wrap break-words text-sm text-[var(--sk-danger)]",
|
|
22
|
+
role: "alert"
|
|
23
|
+
};
|
|
24
|
+
const _hoisted_9 = { class: "flex items-end gap-2" };
|
|
25
|
+
/**
|
|
26
|
+
* The chat of the Bild-Studio: the turn log (instruction, the picture it produced if the block still has it, the
|
|
27
|
+
* problem when it failed) and the instruction input. `Enter` sends, `Shift+Enter` breaks the line; while `running` the
|
|
28
|
+
* input and the button are disabled. An empty instruction is sent when `emptyAllowed` (the block has a description to generate from).
|
|
29
|
+
*/
|
|
30
|
+
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
31
|
+
__name: "ImageStudioChat",
|
|
32
|
+
props: {
|
|
33
|
+
turns: {},
|
|
34
|
+
images: {},
|
|
35
|
+
running: { type: Boolean },
|
|
36
|
+
emptyAllowed: { type: Boolean }
|
|
37
|
+
},
|
|
38
|
+
emits: ["send"],
|
|
39
|
+
setup(__props, { emit: __emit }) {
|
|
40
|
+
const props = __props;
|
|
41
|
+
const emit = __emit;
|
|
42
|
+
const { Button: UButton, Textarea: UTextarea } = useUiKit();
|
|
43
|
+
const { t } = useEditorI18n();
|
|
44
|
+
const headingId = useId();
|
|
45
|
+
const instruction = ref("");
|
|
46
|
+
const logRef = useTemplateRef("log");
|
|
47
|
+
const thumbnail = (turn) => turn.imageId === null ? void 0 : props.images.find((image) => image.id === turn.imageId);
|
|
48
|
+
const instructionOf = (turn) => turn.instruction || t("imageStudio.dialog.chat.fromDescription");
|
|
49
|
+
const canSend = () => !props.running && (props.emptyAllowed || instruction.value.trim() !== "");
|
|
50
|
+
function send() {
|
|
51
|
+
if (!canSend()) return;
|
|
52
|
+
const text = instruction.value.trim();
|
|
53
|
+
instruction.value = "";
|
|
54
|
+
emit("send", text);
|
|
55
|
+
}
|
|
56
|
+
function onKeydown(event) {
|
|
57
|
+
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
send();
|
|
60
|
+
}
|
|
61
|
+
watch(() => props.turns.length, async () => {
|
|
62
|
+
await nextTick();
|
|
63
|
+
if (logRef.value) logRef.value.scrollTop = logRef.value.scrollHeight;
|
|
64
|
+
}, { flush: "post" });
|
|
65
|
+
return (_ctx, _cache) => {
|
|
66
|
+
return openBlock(), createElementBlock("section", {
|
|
67
|
+
class: "flex min-h-0 flex-1 flex-col gap-2",
|
|
68
|
+
"aria-labelledby": unref(headingId)
|
|
69
|
+
}, [
|
|
70
|
+
createElementVNode("h3", {
|
|
71
|
+
id: unref(headingId),
|
|
72
|
+
class: "text-sm font-medium text-[var(--sk-fg-highlighted)]"
|
|
73
|
+
}, toDisplayString(unref(t)("imageStudio.dialog.chat.heading")), 9, _hoisted_2),
|
|
74
|
+
createElementVNode("ol", _hoisted_3, [!__props.turns.length ? (openBlock(), createElementBlock("li", _hoisted_4, toDisplayString(unref(t)("imageStudio.dialog.chat.empty")), 1)) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.turns, (turn) => {
|
|
75
|
+
return openBlock(), createElementBlock("li", {
|
|
76
|
+
key: turn.id,
|
|
77
|
+
class: "flex items-start gap-2 rounded-md border border-[var(--sk-border)] p-2",
|
|
78
|
+
"data-problem": turn.problem !== void 0,
|
|
79
|
+
"data-image-studio-turn": ""
|
|
80
|
+
}, [thumbnail(turn) ? (openBlock(), createElementBlock("img", {
|
|
81
|
+
key: 0,
|
|
82
|
+
src: thumbnail(turn)?.url,
|
|
83
|
+
alt: unref(t)("imageStudio.dialog.chat.turnImage", { instruction: instructionOf(turn) }),
|
|
84
|
+
class: "size-12 shrink-0 rounded object-cover"
|
|
85
|
+
}, null, 8, _hoisted_6)) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_7, [createElementVNode("p", { class: normalizeClass(["whitespace-pre-wrap break-words text-sm text-[var(--sk-fg)]", { "italic text-[var(--sk-muted)]": !turn.instruction }]) }, toDisplayString(instructionOf(turn)), 3), turn.problem ? (openBlock(), createElementBlock("p", _hoisted_8, toDisplayString(turn.problem), 1)) : createCommentVNode("v-if", true)])], 8, _hoisted_5);
|
|
86
|
+
}), 128))], 512),
|
|
87
|
+
createElementVNode("div", _hoisted_9, [createVNode(unref(UTextarea), {
|
|
88
|
+
modelValue: instruction.value,
|
|
89
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => instruction.value = $event),
|
|
90
|
+
class: "min-w-0 flex-1",
|
|
91
|
+
rows: 2,
|
|
92
|
+
maxrows: 6,
|
|
93
|
+
autoresize: "",
|
|
94
|
+
placeholder: unref(t)("imageStudio.dialog.chat.placeholder"),
|
|
95
|
+
"aria-label": unref(t)("imageStudio.dialog.chat.label"),
|
|
96
|
+
disabled: __props.running,
|
|
97
|
+
onKeydown
|
|
98
|
+
}, null, 8, [
|
|
99
|
+
"modelValue",
|
|
100
|
+
"placeholder",
|
|
101
|
+
"aria-label",
|
|
102
|
+
"disabled"
|
|
103
|
+
]), createVNode(unref(UButton), {
|
|
104
|
+
label: __props.running ? unref(t)("imageStudio.dialog.chat.running") : unref(t)("imageStudio.dialog.chat.send"),
|
|
105
|
+
icon: "i-lucide-sparkles",
|
|
106
|
+
loading: __props.running,
|
|
107
|
+
disabled: __props.running || !__props.emptyAllowed && instruction.value.trim() === "",
|
|
108
|
+
onClick: send
|
|
109
|
+
}, null, 8, [
|
|
110
|
+
"label",
|
|
111
|
+
"loading",
|
|
112
|
+
"disabled"
|
|
113
|
+
])])
|
|
114
|
+
], 8, _hoisted_1);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
//#endregion
|
|
119
|
+
export { _sfc_main as default };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=ImageStudioChat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioChat.js","names":["_openBlock","_createElementBlock","_unref","_createElementVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeClass","_createVNode"],"sources":["../../../../src/features/image-studio/components/ImageStudioChat.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { nextTick, ref, useId, useTemplateRef, watch } from 'vue'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { ImageStudioImage, ImageStudioTurn } from '../state'\n\n/**\n * The chat of the Bild-Studio: the turn log (instruction, the picture it produced if the block still has it, the\n * problem when it failed) and the instruction input. `Enter` sends, `Shift+Enter` breaks the line; while `running` the\n * input and the button are disabled. An empty instruction is sent when `emptyAllowed` (the block has a description to generate from).\n */\nconst props = defineProps<{\n turns: readonly ImageStudioTurn[]\n /** The block's images, to show the thumbnail of a turn. */\n images: readonly ImageStudioImage[]\n running: boolean\n emptyAllowed: boolean\n}>()\nconst emit = defineEmits<{ send: [instruction: string] }>()\n\nconst { Button: UButton, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst headingId = useId()\nconst instruction = ref('')\nconst logRef = useTemplateRef<HTMLElement>('log')\n\nconst thumbnail = (turn: ImageStudioTurn): ImageStudioImage | undefined =>\n turn.imageId === null ? undefined : props.images.find(image => image.id === turn.imageId)\nconst instructionOf = (turn: ImageStudioTurn): string => turn.instruction || t('imageStudio.dialog.chat.fromDescription')\n\nconst canSend = () => !props.running && (props.emptyAllowed || instruction.value.trim() !== '')\n\nfunction send() {\n if (!canSend()) return\n const text = instruction.value.trim()\n instruction.value = ''\n emit('send', text)\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key !== 'Enter' || event.shiftKey || event.isComposing) return\n event.preventDefault()\n send()\n}\n\n// The newest turn stays in view.\nwatch(() => props.turns.length, async () => {\n await nextTick()\n if (logRef.value) logRef.value.scrollTop = logRef.value.scrollHeight\n}, { flush: 'post' })\n</script>\n\n<template>\n <section class=\"flex min-h-0 flex-1 flex-col gap-2\" :aria-labelledby=\"headingId\">\n <h3 :id=\"headingId\" class=\"text-sm font-medium text-[var(--sk-fg-highlighted)]\">\n {{ t('imageStudio.dialog.chat.heading') }}\n </h3>\n <ol ref=\"log\" class=\"flex min-h-24 flex-1 flex-col gap-2 overflow-y-auto\" data-image-studio-log>\n <li v-if=\"!turns.length\" class=\"text-sm text-[var(--sk-muted)]\">\n {{ t('imageStudio.dialog.chat.empty') }}\n </li>\n <li\n v-for=\"turn in turns\"\n :key=\"turn.id\"\n class=\"flex items-start gap-2 rounded-md border border-[var(--sk-border)] p-2\"\n :data-problem=\"turn.problem !== undefined\"\n data-image-studio-turn\n >\n <img\n v-if=\"thumbnail(turn)\"\n :src=\"thumbnail(turn)?.url\"\n :alt=\"t('imageStudio.dialog.chat.turnImage', { instruction: instructionOf(turn) })\"\n class=\"size-12 shrink-0 rounded object-cover\"\n >\n <div class=\"min-w-0 flex-1\">\n <p class=\"whitespace-pre-wrap break-words text-sm text-[var(--sk-fg)]\" :class=\"{ 'italic text-[var(--sk-muted)]': !turn.instruction }\">\n {{ instructionOf(turn) }}\n </p>\n <p v-if=\"turn.problem\" class=\"mt-1 whitespace-pre-wrap break-words text-sm text-[var(--sk-danger)]\" role=\"alert\">\n {{ turn.problem }}\n </p>\n </div>\n </li>\n </ol>\n <div class=\"flex items-end gap-2\">\n <UTextarea\n v-model=\"instruction\"\n class=\"min-w-0 flex-1\"\n :rows=\"2\"\n :maxrows=\"6\"\n autoresize\n :placeholder=\"t('imageStudio.dialog.chat.placeholder')\"\n :aria-label=\"t('imageStudio.dialog.chat.label')\"\n :disabled=\"running\"\n @keydown=\"onKeydown\"\n />\n <UButton\n :label=\"running ? t('imageStudio.dialog.chat.running') : t('imageStudio.dialog.chat.send')\"\n icon=\"i-lucide-sparkles\"\n :loading=\"running\"\n :disabled=\"running || (!emptyAllowed && instruction.trim() === '')\"\n @click=\"send\"\n />\n </div>\n </section>\n</template>\n"],"mappings":"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"}
|