@ecsplico/editor 3.0.0 → 3.1.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 +10 -0
- package/dist/adapters/comments/record.d.ts +15 -0
- package/dist/adapters/comments/server.d.ts +2 -1
- package/dist/adapters/comments/server.js +2 -1
- package/dist/adapters/comments/server.js.map +1 -1
- package/dist/adapters/nitro.d.ts +4 -0
- package/dist/adapters/nitro.js +2 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/core/command-types.d.ts +7 -0
- package/dist/core/commands.d.ts +2 -2
- package/dist/core/commands.js.map +1 -1
- package/dist/core/feature.d.ts +8 -2
- package/dist/core/feature.js.map +1 -1
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +2 -1
- package/dist/core/surface-session.js +2 -0
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/transaction-guards.d.ts +21 -0
- package/dist/core/transaction-guards.js +49 -0
- package/dist/core/transaction-guards.js.map +1 -0
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/protocol/chat.js +2 -1
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/composites/ai-brief.js +8 -0
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.js +4 -4
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +32 -6
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.js +3 -2
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/inspector-model.js +2 -2
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +10 -3
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/meta-display.js +5 -1
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/react/CompositeChrome.js +3 -3
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.js +19 -0
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.js +1 -1
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/rows.d.ts +4 -1
- package/dist/features/composites/rows.js +9 -1
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/table-model.js +2 -2
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/media/index.js +1 -1
- package/dist/features/media/react/AudioRecorderModal.js +1 -1
- package/dist/features/media/react.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
- package/dist/react/compose.js +1 -1
- package/dist/server/h3.d.ts +8 -0
- package/dist/server/handlers/ai.d.ts +2 -1
- package/dist/server/handlers/ai.js +2 -1
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/routes.d.ts +22 -0
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { contributions } from "./define-editor.js";
|
|
2
|
+
import { Extension } from "@tiptap/core";
|
|
3
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
4
|
+
//#region src/core/transaction-guards.ts
|
|
5
|
+
/**
|
|
6
|
+
* Refuses a transaction that changes the document when a `TransactionGuard` answers a string: the guards run in order,
|
|
7
|
+
* the first refusal wins, its string is shown through `notify` and the whole transaction is dropped. Transactions that
|
|
8
|
+
* leave the document alone (selection, meta, focus) are never judged.
|
|
9
|
+
*/
|
|
10
|
+
const TransactionGuards = Extension.create({
|
|
11
|
+
name: "transactionGuards",
|
|
12
|
+
addOptions: () => ({
|
|
13
|
+
guards: [],
|
|
14
|
+
notify: () => {}
|
|
15
|
+
}),
|
|
16
|
+
addProseMirrorPlugins() {
|
|
17
|
+
const { guards, notify } = this.options;
|
|
18
|
+
return [new Plugin({
|
|
19
|
+
key: new PluginKey("transactionGuards"),
|
|
20
|
+
filterTransaction(tr, state) {
|
|
21
|
+
if (!tr.docChanged) return true;
|
|
22
|
+
for (const guard of guards) {
|
|
23
|
+
const verdict = guard(tr, state);
|
|
24
|
+
if (verdict === true) continue;
|
|
25
|
+
notify({
|
|
26
|
+
id: "transaction-guard",
|
|
27
|
+
title: verdict,
|
|
28
|
+
kind: "warning",
|
|
29
|
+
icon: "i-lucide-ban"
|
|
30
|
+
});
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
})];
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
/** The extension that enforces the `transactionGuards` of `preset`'s features: none when no feature contributes a guard. */
|
|
39
|
+
function transactionGuardExtensions(preset, ctx) {
|
|
40
|
+
const guards = contributions(preset, "transactionGuards").flatMap((factory) => factory(ctx));
|
|
41
|
+
return guards.length ? [TransactionGuards.configure({
|
|
42
|
+
guards,
|
|
43
|
+
notify: ctx.notify
|
|
44
|
+
})] : [];
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { TransactionGuards, transactionGuardExtensions };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=transaction-guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-guards.js","names":[],"sources":["../../src/core/transaction-guards.ts"],"sourcesContent":["import { type AnyExtension, Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport type { TransactionGuard } from './command-types'\nimport { contributions } from './define-editor'\nimport type { EditorPreset, FeatureContext } from './feature'\nimport type { Notifier } from './notify'\n\nexport interface TransactionGuardsOptions {\n /** The guards, in the order they run (the order of the features that contributed them). */\n guards: readonly TransactionGuard[]\n /** Shows the notice of a refused transaction (default: dropped). `transactionGuardExtensions` passes the surface's `ctx.notify`. */\n notify: Notifier\n}\n\n/**\n * Refuses a transaction that changes the document when a `TransactionGuard` answers a string: the guards run in order,\n * the first refusal wins, its string is shown through `notify` and the whole transaction is dropped. Transactions that\n * leave the document alone (selection, meta, focus) are never judged.\n */\nexport const TransactionGuards = Extension.create<TransactionGuardsOptions>({\n name: 'transactionGuards',\n addOptions: () => ({ guards: [], notify: () => {} }),\n addProseMirrorPlugins() {\n const { guards, notify } = this.options\n return [new Plugin({\n key: new PluginKey('transactionGuards'),\n filterTransaction(tr, state) {\n if (!tr.docChanged) return true\n for (const guard of guards) {\n const verdict = guard(tr, state)\n if (verdict === true) continue\n notify({ id: 'transaction-guard', title: verdict, kind: 'warning', icon: 'i-lucide-ban' })\n return false\n }\n return true\n },\n })]\n },\n})\n\n/** The extension that enforces the `transactionGuards` of `preset`'s features: none when no feature contributes a guard. */\nexport function transactionGuardExtensions<C>(preset: EditorPreset<C>, ctx: FeatureContext): AnyExtension[] {\n const guards = contributions(preset, 'transactionGuards').flatMap(factory => factory(ctx))\n return guards.length ? [TransactionGuards.configure({ guards, notify: ctx.notify })] : []\n}\n"],"mappings":";;;;;;;;;AAmBA,MAAa,oBAAoB,UAAU,OAAiC;CAC1E,MAAM;CACN,mBAAmB;EAAE,QAAQ,CAAC;EAAG,cAAc,CAAC;CAAE;CAClD,wBAAwB;EACtB,MAAM,EAAE,QAAQ,WAAW,KAAK;EAChC,OAAO,CAAC,IAAI,OAAO;GACjB,KAAK,IAAI,UAAU,mBAAmB;GACtC,kBAAkB,IAAI,OAAO;IAC3B,IAAI,CAAC,GAAG,YAAY,OAAO;IAC3B,KAAK,MAAM,SAAS,QAAQ;KAC1B,MAAM,UAAU,MAAM,IAAI,KAAK;KAC/B,IAAI,YAAY,MAAM;KACtB,OAAO;MAAE,IAAI;MAAqB,OAAO;MAAS,MAAM;MAAW,MAAM;KAAe,CAAC;KACzF,OAAO;IACT;IACA,OAAO;GACT;EACF,CAAC,CAAC;CACJ;AACF,CAAC;;AAGD,SAAgB,2BAA8B,QAAyB,KAAqC;CAC1G,MAAM,SAAS,cAAc,QAAQ,mBAAmB,CAAC,CAAC,SAAQ,YAAW,QAAQ,GAAG,CAAC;CACzF,OAAO,OAAO,SAAS,CAAC,kBAAkB,UAAU;EAAE;EAAQ,QAAQ,IAAI;CAAO,CAAC,CAAC,IAAI,CAAC;AAC1F"}
|
|
@@ -32,12 +32,12 @@ type __VLS_Slots = {} & { [K in NonNullable<typeof __VLS_13>]?: (props: typeof _
|
|
|
32
32
|
binary?: (props: typeof __VLS_26) => any;
|
|
33
33
|
};
|
|
34
34
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
35
|
-
"update:modelValue": (text: string) => any;
|
|
36
35
|
ready: (target: DocumentSurfaceTarget) => any;
|
|
36
|
+
"update:modelValue": (text: string) => any;
|
|
37
37
|
save: (text: string) => any;
|
|
38
38
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
|
-
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
40
39
|
onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
|
|
40
|
+
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
41
41
|
onSave?: ((text: string) => any) | undefined;
|
|
42
42
|
}>, {
|
|
43
43
|
editable: boolean;
|
|
@@ -23,13 +23,14 @@ const FIELD_TYPES = {
|
|
|
23
23
|
date: "Datum JJJJ-MM-TT",
|
|
24
24
|
number: "Zahl",
|
|
25
25
|
select: "Auswahl",
|
|
26
|
+
multiselect: "Mehrfachauswahl",
|
|
26
27
|
reference: "Block-ID eines Bausteins",
|
|
27
28
|
bibliography: "Literaturangabe als CSL-JSON-Objekt",
|
|
28
29
|
derivation: "Herkunft, nur lesen"
|
|
29
30
|
};
|
|
30
31
|
/** `status (Status: open = Offen, doing = In Arbeit, done = Erledigt)`, `due (Fällig, Datum JJJJ-MM-TT)`. */
|
|
31
32
|
function fieldText(field) {
|
|
32
|
-
const values = field.options?.length ? `: ${field.options.map((option) => `${option.value} = ${option.label}`).join(", ")}` : `, ${FIELD_TYPES[field.type ?? "text"]}`;
|
|
33
|
+
const values = field.options?.length ? `: ${field.options.map((option) => `${option.value} = ${option.label}`).join(", ")}${field.type === "multiselect" ? `; ${FIELD_TYPES.multiselect}, mehrere Werte als JSON-Liste` : ""}` : `, ${FIELD_TYPES[field.type ?? "text"]}`;
|
|
33
34
|
return `${field.key} (${field.label}${values})`;
|
|
34
35
|
}
|
|
35
36
|
/** „Blockmodell“: the document's line format for composites and host blocks holding blocks, the fence the model writes composites in, and `convert`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","names":[],"sources":["../../../../src/features/ai/protocol/chat.ts"],"sourcesContent":["// i18n-ignore-file: model-facing chat system prompt and messages (German prompts stay German whatever the editor's language)\nimport type { ModelMessage } from 'ai'\nimport type { AiBlock, AiContextKind } from '../../../core/feature'\nimport type { StructureMode } from '../../../settings'\nimport { AGENT_TOOL, type AgentOp, type AgentOpFeedback, agentFeedbackText, agentShownMessage } from './agent-ops'\nimport { BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, BLOCK_LINES_LABEL } from './block-lines'\n\n/**\n * One turn of the document chat; the conversation lives on the client and is sent in full with every message.\n * A user turn carries the decisions on earlier proposals made since the previous turn (`feedback`); an assistant turn\n * the edit operations it proposed (`ops`, possibly without any text) and how many invalid ones were dropped.\n */\nexport type DocumentChatMessage =\n | { role: 'user', content: string, feedback?: AgentOpFeedback[] }\n | { role: 'assistant', content: string, ops?: AgentOp[], dropped?: number }\n\n/** Input limits (characters / messages) of `POST /api/ai/chat`; the client trims to fit before sending. */\nexport const CHAT_LIMITS = { document: 120000, message: 8000, messages: 40 } as const\n\n/** Link target prefix of a quote citation: `[„Zitat“](#block-<id>)`. */\nexport const CITATION_HREF_PREFIX = '#block-'\n\n/**\n * The block ID a citation link points to, or `null` for any other href. Also accepts a bare `#<id>`, which models\n * write now and then despite the instructions; whether the ID exists is up to the caller.\n */\nexport function citationBlockId(href: unknown): string | null {\n if (typeof href !== 'string' || !href.startsWith('#')) return null\n return (href.startsWith(CITATION_HREF_PREFIX) ? href.slice(CITATION_HREF_PREFIX.length) : href.slice(1)) || null\n}\n\n/** A composite kind as the chat model learns it: the client sends the kinds of the editor's `aiContext` with every message. */\nexport type ChatCompositeKind = Pick<AiContextKind, 'id' | 'label' | 'fields' | 'purpose'>\ntype ChatField = ChatCompositeKind['fields'][number]\n\n/** A host-defined block as the chat model learns it: the editor's `aiContext.blocks`, sent with every message. */\nexport type ChatBlock = AiBlock\n\n/**\n * What the model may build and convert into: the editor's composite kinds and host blocks, the `convert` targets it\n * offers, and the user's preferred structure of new content (settings `blocks.structure`).\n */\nexport interface ChatBlockModel { kinds: readonly ChatCompositeKind[], blocks: readonly ChatBlock[], targets: readonly string[], structure: StructureMode }\n\nconst FIELD_TYPES: Record<NonNullable<ChatField['type']>, string> = { text: 'Text', textarea: 'Langtext', date: 'Datum JJJJ-MM-TT', number: 'Zahl', select: 'Auswahl', reference: 'Block-ID eines Bausteins', bibliography: 'Literaturangabe als CSL-JSON-Objekt', derivation: 'Herkunft, nur lesen' }\n\n/** `status (Status: open = Offen, doing = In Arbeit, done = Erledigt)`, `due (Fällig, Datum JJJJ-MM-TT)`. */\nfunction fieldText(field: ChatField): string {\n const values = field.options?.length ? `: ${field.options.map(option => `${option.value} = ${option.label}`).join(', ')}` : `, ${FIELD_TYPES[field.type ?? 'text']}`\n return `${field.key} (${field.label}${values})`\n}\n\n/** „Blockmodell“: the document's line format for composites and host blocks holding blocks, the fence the model writes composites in, and `convert`. */\nfunction blockModelText({ kinds, blocks, targets, structure }: ChatBlockModel): string[] {\n const composites = structure === 'composite' && kinds.length > 0\n const example = kinds.find(kind => kind.fields.some(field => field.options?.length)) ?? kinds[0]\n const exampleField = example?.fields.find(field => field.options?.length)\n const exampleMeta = exampleField ? ` {\"${exampleField.key}\":\"${exampleField.options!.at(-1)!.value}\"}` : ''\n return [\n 'Blockmodell:',\n `Blocktypen: Absatz, Überschrift (#, ##, ###), ${BLOCK_LINE_MARKERS}, Codeblock, Redebeitrag („**Name:** Text“) und Bausteine. ${BLOCK_LINE_LIST_TEXT}`,\n ...(blocks.length\n ? [\n `Eigene Blöcke: ${blocks.map(block => `${block.id} (${block.label}): ${block.description} Markdown-Form: „${block.syntax.replace(/\\n/g, '\\\\n')}“. Beispiel: „${block.example.replace(/\\n/g, '\\\\n')}“`).join(' ')}`,\n `${BLOCK_LINE_HOST_TEXT} „update“ auf seine ID mit vollständigem Markdown ersetzt den ganzen Block; „add“ nach einem seiner Inhaltsblöcke fügt innerhalb des Blocks ein, ein Block derselben Art danach.`,\n ]\n : []),\n composites\n ? 'Bevorzugte Gliederung: Bausteine. Neue Inhalte gliederst du mit Bausteinen statt mit Überschriften; Unterabschnitte sind verschachtelte Bausteine (Zaun im Zaun, jeder Zaun endet mit einer eigenen Zeile „:::“). Markdown-Überschriften schreibst du nur als Titelzeile eines Bausteins.'\n : 'Bevorzugte Gliederung: Überschriften. Neue Inhalte gliederst du mit Überschriften (##, ###) statt mit Bausteinen; einen Baustein schreibst du nur, wenn ausdrücklich ein Baustein (Abschnitt, Aufgabe, Notiz …) verlangt wird oder du einen bestehenden Baustein änderst.',\n ...(kinds.length\n ? [\n `Bausteine sind Blöcke mit Titel, Beschreibung, Tags, Feldern und eigenen Inhaltsblöcken. ${BLOCK_LINE_COMPOSITE_TEXT}`,\n `Arten von Bausteinen: ${kinds.map(kind => `${kind.id} (${kind.label}${kind.purpose ? `: ${kind.purpose}` : ''}; ${kind.fields.length ? `Felder: ${kind.fields.map(fieldText).join(', ')}` : 'keine Felder'})`).join('; ')}.`,\n `In „markdown“ schreibst du einen Baustein ohne IDs und ohne Einrückung als Zaun: die Zeile „::: Art {\"feld\":\"wert\"}“ (Felder als JSON, nur gesetzte), dann „## Titel“, „> Beschreibung“ und „Tags: #a #b“ (jeweils nur, wenn vorhanden), dann die Inhaltsblöcke als Markdown, zuletzt die Zeile „:::“. Beispiel: „::: ${example?.id ?? 'note'}${exampleMeta}\\\\n## Angebot prüfen\\\\n- Preise vergleichen\\\\n- Rückmeldung an Anna\\\\n:::“.`,\n '„update“ auf einen Baustein mit Markdown ohne :::-Zaun ersetzt nur seinen Inhalt (Kinder); mit Zaun den ganzen Baustein (Art, Felder, Titel, Beschreibung, Tags und Inhalt).',\n ]\n : []),\n `„convert“ wandelt den Block „id“ in den Blocktyp „to“ um und behält seinen Inhalt. Erlaubte Werte für „to“: ${targets.join(', ')}. Eine Überschrift, die zum Baustein wird, nimmt die folgenden Blöcke bis zur nächsten Überschrift gleicher oder höherer Ebene als Inhalt mit; ein Baustein, der zu einem anderen Blocktyp wird, behält Beschreibung, Tags und Felder als Textzeilen; wird er zur Überschrift, werden seine inneren Bausteine zu Unterüberschriften.`,\n 'Sollen sich Blocktyp und Inhalt oder Felder zugleich ändern (z. B. ein Absatz wird zu einer Aufgabe mit Status), nimm statt „convert“ ein „update“ mit dem vollständigen neuen Markdown samt :::-Zaun; jeder Block bekommt höchstens eine Operation.',\n ]\n}\n\n/** The system prompt: answering with citations, editing through `edit_document`, and the editor's block model. */\nfunction chatSystem(blockModel: ChatBlockModel): string {\n return [\n 'Du beantwortest Fragen zu einem Dokument, meist dem Transkript eines Gesprächs, und bearbeitest es auf Wunsch. Das Dokument steht in der letzten Nachricht der Nutzerin bzw. des Nutzers und ist der aktuelle Stand; frühere Nachrichten bezogen sich womöglich auf einen älteren Stand.',\n BLOCK_LINE_INTRO,\n '',\n 'Fragen:',\n `Belege deine Antworten mit wörtlichen Zitaten: ein zusammenhängender, unveränderter Ausschnitt aus einem einzigen Block. Jedes Zitat ist ein Markdown-Link, dessen Ziel „${CITATION_HREF_PREFIX}“ gefolgt von der vollständigen ID dieses Blocks ist: [„Zitat“](${CITATION_HREF_PREFIX}ID).`,\n `Beispiel zur Zeile oben: Anna kündigt den Start an: [„Wir starten im Mai.“](${CITATION_HREF_PREFIX}${BLOCK_LINE_EXAMPLE_ID})`,\n 'Verwende nur IDs, die im Dokument vorkommen, und nenne IDs nie außerhalb solcher Links.',\n 'Steht die Antwort nicht im Dokument, sag das ehrlich, statt etwas zu erfinden.',\n 'Antworte knapp in Markdown (Absätze, Aufzählungen, **fett**) und in der Sprache der Frage.',\n '',\n 'Änderungen:',\n `Bittet die Nutzerin bzw. der Nutzer darum, das Dokument zu ändern (kürzen, umformulieren, ergänzen, löschen, umwandeln …), rufe das Werkzeug ${AGENT_TOOL} auf, statt den neuen Text im Chat zu schreiben. Schreib davor höchstens ein bis zwei Sätze dazu, was du änderst. Bei Fragen rufst du das Werkzeug nicht auf.`,\n 'Operationen: „update“ ersetzt den Inhalt des Blocks „id“ durch „markdown“; „delete“ löscht den Block „id“; „add“ fügt „markdown“ (ein oder mehrere Blöcke, durch Leerzeilen getrennt) nach dem Block „afterId“ ein („afterId“ gehört bei „add“ immer dazu, für den Dokumentanfang schreibst du ausdrücklich null; mehrere „add“ nach demselben Block werden in der Reihenfolge eingefügt, in der du sie schreibst); „convert“ wandelt den Block „id“ in den Blocktyp „to“ um.',\n `${BLOCK_LINE_ECHO_TEXT} Redebeiträge behalten den fett gesetzten Namen mit Doppelpunkt am Anfang; ein anderer Name wechselt die Sprecherin bzw. den Sprecher. Neue Redebeiträge schreibst du ebenso als „**Name:** Text“.`,\n 'Ändere nur die Blöcke, die die Bitte betrifft; lass unveränderte Blöcke weg. Jeder Block höchstens einmal; eine Operation auf einen Block innerhalb eines Blocks, den eine andere Operation ersetzt oder löscht, ist ungültig (schreibe die Änderung dann in das Markdown der äußeren Operation). Verwende nur IDs aus dem Dokument. Nennt die Nachricht einen Bearbeitungsbereich, beziehen sich alle IDs und afterIds ausschließlich auf Blöcke daraus.',\n 'Jede Änderung wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt und einzeln übernommen oder abgelehnt. Die Entscheidungen erhältst du mit einer späteren Nachricht („Entscheidungen zu deinen letzten Vorschlägen: …“). Geh auf abgelehnte Vorschläge ein und wiederhole sie nicht unverändert.',\n '',\n ...blockModelText(blockModel),\n ].join('\\n')\n}\n\n/** A user turn as the model reads it: decisions on earlier proposals first, then the message. */\nfunction userText(content: string, feedback: readonly AgentOpFeedback[] | undefined): string {\n const decisions = agentFeedbackText(feedback ?? [])\n return decisions ? `${decisions}\\n\\nNachricht: ${content}` : content\n}\n\n/**\n * Model messages for a chat turn: the history as sent (proposed operations as `edit_document` tool calls), with the\n * current document placed in the last (user) message next to the scope, the latest decisions and the message —\n * document content never goes into the system prompt (the editor's block model does). `scope`: IDs the edits are\n * limited to (null: whole document).\n */\nexport function documentChatMessages(messages: readonly DocumentChatMessage[], document: string, scope: readonly string[] | null, blockModel: ChatBlockModel): ModelMessage[] {\n const history = messages.slice(0, -1).flatMap((message, index): ModelMessage[] => {\n if (message.role === 'user') return [{ role: 'user', content: userText(message.content, message.feedback) }]\n if (!message.ops?.length && !message.dropped) return [{ role: 'assistant', content: message.content }]\n const ops = message.ops ?? []\n const toolCallId = `edit_${index}`\n return [\n {\n role: 'assistant',\n content: [\n ...(message.content ? [{ type: 'text' as const, text: message.content }] : []),\n { type: 'tool-call', toolCallId, toolName: AGENT_TOOL, input: { ops: ops.map(({ op: _op, ...rest }) => rest) } },\n ],\n },\n {\n role: 'tool',\n content: [{ type: 'tool-result', toolCallId, toolName: AGENT_TOOL, output: { type: 'text', value: agentShownMessage(ops, message.dropped ?? 0) } }],\n },\n ]\n })\n const last = messages.at(-1)! as Extract<DocumentChatMessage, { role: 'user' }>\n const range = scope\n ? `Bearbeitungsbereich: die Auswahl der Nutzerin bzw. des Nutzers. Änderungen (id und afterId) nur an diesen Blöcken: ${scope.join(', ')}`\n : 'Bearbeitungsbereich: das ganze Dokument.'\n const decisions = agentFeedbackText(last.feedback ?? [])\n return [\n { role: 'system', content: chatSystem(blockModel) },\n ...history,\n {\n role: 'user',\n content: [\n `${BLOCK_LINES_LABEL}:\\n\"\"\"\\n${document}\\n\"\"\"`,\n range,\n ...(decisions ? [decisions] : []),\n `Nachricht: ${last.content}`,\n ].join('\\n\\n'),\n },\n ]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat.js","names":[],"sources":["../../../../src/features/ai/protocol/chat.ts"],"sourcesContent":["// i18n-ignore-file: model-facing chat system prompt and messages (German prompts stay German whatever the editor's language)\nimport type { ModelMessage } from 'ai'\nimport type { AiBlock, AiContextKind } from '../../../core/feature'\nimport type { StructureMode } from '../../../settings'\nimport { AGENT_TOOL, type AgentOp, type AgentOpFeedback, agentFeedbackText, agentShownMessage } from './agent-ops'\nimport { BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, BLOCK_LINES_LABEL } from './block-lines'\n\n/**\n * One turn of the document chat; the conversation lives on the client and is sent in full with every message.\n * A user turn carries the decisions on earlier proposals made since the previous turn (`feedback`); an assistant turn\n * the edit operations it proposed (`ops`, possibly without any text) and how many invalid ones were dropped.\n */\nexport type DocumentChatMessage =\n | { role: 'user', content: string, feedback?: AgentOpFeedback[] }\n | { role: 'assistant', content: string, ops?: AgentOp[], dropped?: number }\n\n/** Input limits (characters / messages) of `POST /api/ai/chat`; the client trims to fit before sending. */\nexport const CHAT_LIMITS = { document: 120000, message: 8000, messages: 40 } as const\n\n/** Link target prefix of a quote citation: `[„Zitat“](#block-<id>)`. */\nexport const CITATION_HREF_PREFIX = '#block-'\n\n/**\n * The block ID a citation link points to, or `null` for any other href. Also accepts a bare `#<id>`, which models\n * write now and then despite the instructions; whether the ID exists is up to the caller.\n */\nexport function citationBlockId(href: unknown): string | null {\n if (typeof href !== 'string' || !href.startsWith('#')) return null\n return (href.startsWith(CITATION_HREF_PREFIX) ? href.slice(CITATION_HREF_PREFIX.length) : href.slice(1)) || null\n}\n\n/** A composite kind as the chat model learns it: the client sends the kinds of the editor's `aiContext` with every message. */\nexport type ChatCompositeKind = Pick<AiContextKind, 'id' | 'label' | 'fields' | 'purpose'>\ntype ChatField = ChatCompositeKind['fields'][number]\n\n/** A host-defined block as the chat model learns it: the editor's `aiContext.blocks`, sent with every message. */\nexport type ChatBlock = AiBlock\n\n/**\n * What the model may build and convert into: the editor's composite kinds and host blocks, the `convert` targets it\n * offers, and the user's preferred structure of new content (settings `blocks.structure`).\n */\nexport interface ChatBlockModel { kinds: readonly ChatCompositeKind[], blocks: readonly ChatBlock[], targets: readonly string[], structure: StructureMode }\n\nconst FIELD_TYPES: Record<NonNullable<ChatField['type']>, string> = { text: 'Text', textarea: 'Langtext', date: 'Datum JJJJ-MM-TT', number: 'Zahl', select: 'Auswahl', multiselect: 'Mehrfachauswahl', reference: 'Block-ID eines Bausteins', bibliography: 'Literaturangabe als CSL-JSON-Objekt', derivation: 'Herkunft, nur lesen' }\n\n/** `status (Status: open = Offen, doing = In Arbeit, done = Erledigt)`, `due (Fällig, Datum JJJJ-MM-TT)`. */\nfunction fieldText(field: ChatField): string {\n const values = field.options?.length ? `: ${field.options.map(option => `${option.value} = ${option.label}`).join(', ')}${field.type === 'multiselect' ? `; ${FIELD_TYPES.multiselect}, mehrere Werte als JSON-Liste` : ''}` : `, ${FIELD_TYPES[field.type ?? 'text']}`\n return `${field.key} (${field.label}${values})`\n}\n\n/** „Blockmodell“: the document's line format for composites and host blocks holding blocks, the fence the model writes composites in, and `convert`. */\nfunction blockModelText({ kinds, blocks, targets, structure }: ChatBlockModel): string[] {\n const composites = structure === 'composite' && kinds.length > 0\n const example = kinds.find(kind => kind.fields.some(field => field.options?.length)) ?? kinds[0]\n const exampleField = example?.fields.find(field => field.options?.length)\n const exampleMeta = exampleField ? ` {\"${exampleField.key}\":\"${exampleField.options!.at(-1)!.value}\"}` : ''\n return [\n 'Blockmodell:',\n `Blocktypen: Absatz, Überschrift (#, ##, ###), ${BLOCK_LINE_MARKERS}, Codeblock, Redebeitrag („**Name:** Text“) und Bausteine. ${BLOCK_LINE_LIST_TEXT}`,\n ...(blocks.length\n ? [\n `Eigene Blöcke: ${blocks.map(block => `${block.id} (${block.label}): ${block.description} Markdown-Form: „${block.syntax.replace(/\\n/g, '\\\\n')}“. Beispiel: „${block.example.replace(/\\n/g, '\\\\n')}“`).join(' ')}`,\n `${BLOCK_LINE_HOST_TEXT} „update“ auf seine ID mit vollständigem Markdown ersetzt den ganzen Block; „add“ nach einem seiner Inhaltsblöcke fügt innerhalb des Blocks ein, ein Block derselben Art danach.`,\n ]\n : []),\n composites\n ? 'Bevorzugte Gliederung: Bausteine. Neue Inhalte gliederst du mit Bausteinen statt mit Überschriften; Unterabschnitte sind verschachtelte Bausteine (Zaun im Zaun, jeder Zaun endet mit einer eigenen Zeile „:::“). Markdown-Überschriften schreibst du nur als Titelzeile eines Bausteins.'\n : 'Bevorzugte Gliederung: Überschriften. Neue Inhalte gliederst du mit Überschriften (##, ###) statt mit Bausteinen; einen Baustein schreibst du nur, wenn ausdrücklich ein Baustein (Abschnitt, Aufgabe, Notiz …) verlangt wird oder du einen bestehenden Baustein änderst.',\n ...(kinds.length\n ? [\n `Bausteine sind Blöcke mit Titel, Beschreibung, Tags, Feldern und eigenen Inhaltsblöcken. ${BLOCK_LINE_COMPOSITE_TEXT}`,\n `Arten von Bausteinen: ${kinds.map(kind => `${kind.id} (${kind.label}${kind.purpose ? `: ${kind.purpose}` : ''}; ${kind.fields.length ? `Felder: ${kind.fields.map(fieldText).join(', ')}` : 'keine Felder'})`).join('; ')}.`,\n `In „markdown“ schreibst du einen Baustein ohne IDs und ohne Einrückung als Zaun: die Zeile „::: Art {\"feld\":\"wert\"}“ (Felder als JSON, nur gesetzte), dann „## Titel“, „> Beschreibung“ und „Tags: #a #b“ (jeweils nur, wenn vorhanden), dann die Inhaltsblöcke als Markdown, zuletzt die Zeile „:::“. Beispiel: „::: ${example?.id ?? 'note'}${exampleMeta}\\\\n## Angebot prüfen\\\\n- Preise vergleichen\\\\n- Rückmeldung an Anna\\\\n:::“.`,\n '„update“ auf einen Baustein mit Markdown ohne :::-Zaun ersetzt nur seinen Inhalt (Kinder); mit Zaun den ganzen Baustein (Art, Felder, Titel, Beschreibung, Tags und Inhalt).',\n ]\n : []),\n `„convert“ wandelt den Block „id“ in den Blocktyp „to“ um und behält seinen Inhalt. Erlaubte Werte für „to“: ${targets.join(', ')}. Eine Überschrift, die zum Baustein wird, nimmt die folgenden Blöcke bis zur nächsten Überschrift gleicher oder höherer Ebene als Inhalt mit; ein Baustein, der zu einem anderen Blocktyp wird, behält Beschreibung, Tags und Felder als Textzeilen; wird er zur Überschrift, werden seine inneren Bausteine zu Unterüberschriften.`,\n 'Sollen sich Blocktyp und Inhalt oder Felder zugleich ändern (z. B. ein Absatz wird zu einer Aufgabe mit Status), nimm statt „convert“ ein „update“ mit dem vollständigen neuen Markdown samt :::-Zaun; jeder Block bekommt höchstens eine Operation.',\n ]\n}\n\n/** The system prompt: answering with citations, editing through `edit_document`, and the editor's block model. */\nfunction chatSystem(blockModel: ChatBlockModel): string {\n return [\n 'Du beantwortest Fragen zu einem Dokument, meist dem Transkript eines Gesprächs, und bearbeitest es auf Wunsch. Das Dokument steht in der letzten Nachricht der Nutzerin bzw. des Nutzers und ist der aktuelle Stand; frühere Nachrichten bezogen sich womöglich auf einen älteren Stand.',\n BLOCK_LINE_INTRO,\n '',\n 'Fragen:',\n `Belege deine Antworten mit wörtlichen Zitaten: ein zusammenhängender, unveränderter Ausschnitt aus einem einzigen Block. Jedes Zitat ist ein Markdown-Link, dessen Ziel „${CITATION_HREF_PREFIX}“ gefolgt von der vollständigen ID dieses Blocks ist: [„Zitat“](${CITATION_HREF_PREFIX}ID).`,\n `Beispiel zur Zeile oben: Anna kündigt den Start an: [„Wir starten im Mai.“](${CITATION_HREF_PREFIX}${BLOCK_LINE_EXAMPLE_ID})`,\n 'Verwende nur IDs, die im Dokument vorkommen, und nenne IDs nie außerhalb solcher Links.',\n 'Steht die Antwort nicht im Dokument, sag das ehrlich, statt etwas zu erfinden.',\n 'Antworte knapp in Markdown (Absätze, Aufzählungen, **fett**) und in der Sprache der Frage.',\n '',\n 'Änderungen:',\n `Bittet die Nutzerin bzw. der Nutzer darum, das Dokument zu ändern (kürzen, umformulieren, ergänzen, löschen, umwandeln …), rufe das Werkzeug ${AGENT_TOOL} auf, statt den neuen Text im Chat zu schreiben. Schreib davor höchstens ein bis zwei Sätze dazu, was du änderst. Bei Fragen rufst du das Werkzeug nicht auf.`,\n 'Operationen: „update“ ersetzt den Inhalt des Blocks „id“ durch „markdown“; „delete“ löscht den Block „id“; „add“ fügt „markdown“ (ein oder mehrere Blöcke, durch Leerzeilen getrennt) nach dem Block „afterId“ ein („afterId“ gehört bei „add“ immer dazu, für den Dokumentanfang schreibst du ausdrücklich null; mehrere „add“ nach demselben Block werden in der Reihenfolge eingefügt, in der du sie schreibst); „convert“ wandelt den Block „id“ in den Blocktyp „to“ um.',\n `${BLOCK_LINE_ECHO_TEXT} Redebeiträge behalten den fett gesetzten Namen mit Doppelpunkt am Anfang; ein anderer Name wechselt die Sprecherin bzw. den Sprecher. Neue Redebeiträge schreibst du ebenso als „**Name:** Text“.`,\n 'Ändere nur die Blöcke, die die Bitte betrifft; lass unveränderte Blöcke weg. Jeder Block höchstens einmal; eine Operation auf einen Block innerhalb eines Blocks, den eine andere Operation ersetzt oder löscht, ist ungültig (schreibe die Änderung dann in das Markdown der äußeren Operation). Verwende nur IDs aus dem Dokument. Nennt die Nachricht einen Bearbeitungsbereich, beziehen sich alle IDs und afterIds ausschließlich auf Blöcke daraus.',\n 'Jede Änderung wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt und einzeln übernommen oder abgelehnt. Die Entscheidungen erhältst du mit einer späteren Nachricht („Entscheidungen zu deinen letzten Vorschlägen: …“). Geh auf abgelehnte Vorschläge ein und wiederhole sie nicht unverändert.',\n '',\n ...blockModelText(blockModel),\n ].join('\\n')\n}\n\n/** A user turn as the model reads it: decisions on earlier proposals first, then the message. */\nfunction userText(content: string, feedback: readonly AgentOpFeedback[] | undefined): string {\n const decisions = agentFeedbackText(feedback ?? [])\n return decisions ? `${decisions}\\n\\nNachricht: ${content}` : content\n}\n\n/**\n * Model messages for a chat turn: the history as sent (proposed operations as `edit_document` tool calls), with the\n * current document placed in the last (user) message next to the scope, the latest decisions and the message —\n * document content never goes into the system prompt (the editor's block model does). `scope`: IDs the edits are\n * limited to (null: whole document).\n */\nexport function documentChatMessages(messages: readonly DocumentChatMessage[], document: string, scope: readonly string[] | null, blockModel: ChatBlockModel): ModelMessage[] {\n const history = messages.slice(0, -1).flatMap((message, index): ModelMessage[] => {\n if (message.role === 'user') return [{ role: 'user', content: userText(message.content, message.feedback) }]\n if (!message.ops?.length && !message.dropped) return [{ role: 'assistant', content: message.content }]\n const ops = message.ops ?? []\n const toolCallId = `edit_${index}`\n return [\n {\n role: 'assistant',\n content: [\n ...(message.content ? [{ type: 'text' as const, text: message.content }] : []),\n { type: 'tool-call', toolCallId, toolName: AGENT_TOOL, input: { ops: ops.map(({ op: _op, ...rest }) => rest) } },\n ],\n },\n {\n role: 'tool',\n content: [{ type: 'tool-result', toolCallId, toolName: AGENT_TOOL, output: { type: 'text', value: agentShownMessage(ops, message.dropped ?? 0) } }],\n },\n ]\n })\n const last = messages.at(-1)! as Extract<DocumentChatMessage, { role: 'user' }>\n const range = scope\n ? `Bearbeitungsbereich: die Auswahl der Nutzerin bzw. des Nutzers. Änderungen (id und afterId) nur an diesen Blöcken: ${scope.join(', ')}`\n : 'Bearbeitungsbereich: das ganze Dokument.'\n const decisions = agentFeedbackText(last.feedback ?? [])\n return [\n { role: 'system', content: chatSystem(blockModel) },\n ...history,\n {\n role: 'user',\n content: [\n `${BLOCK_LINES_LABEL}:\\n\"\"\"\\n${document}\\n\"\"\"`,\n range,\n ...(decisions ? [decisions] : []),\n `Nachricht: ${last.content}`,\n ].join('\\n\\n'),\n },\n ]\n}\n"],"mappings":"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"}
|
|
@@ -52,6 +52,14 @@ function compositeAiBrief(editor, pos) {
|
|
|
52
52
|
value: `${derivation.method} from ${"asset" in derivation.from ? derivation.from.asset : derivation.from.block}`
|
|
53
53
|
}] : [];
|
|
54
54
|
}
|
|
55
|
+
if (field.type === "multiselect") {
|
|
56
|
+
const value = (Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : []).map((item) => field.options?.find((option) => option.value === item)?.label ?? item).join(", ");
|
|
57
|
+
return value ? [{
|
|
58
|
+
key: field.key,
|
|
59
|
+
label: field.label,
|
|
60
|
+
value
|
|
61
|
+
}] : [];
|
|
62
|
+
}
|
|
55
63
|
if (!isFilled(raw)) return [];
|
|
56
64
|
const value = String(raw);
|
|
57
65
|
return [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-brief.js","names":[],"sources":["../../../src/features/composites/ai-brief.ts"],"sourcesContent":["// Vue-free: what one Baustein tells the model (read by „Baustein füllen (KI)“, see `integrations/composite-ai`).\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport { type CslItem, citeFull } from './bibliography'\nimport { derivationOf } from './derivation'\nimport { compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT, compositeKindsOf } from './nodes'\n\n/** What one Baustein tells the model: kind brief, place in the hierarchy, chrome, field values, resolved instruction and skeleton, body. */\nexport interface CompositeAiBrief {\n kind: { id: string, label: string, purpose?: string }\n /** Titles of the enclosing composites, outermost first (empty at document level). */\n path: string[]\n title: string\n description: string\n tags: string[]\n /** Declared fields with a non-empty value, `select` values as their option label. */\n fields: { key: string, label: string, value: string }[]\n /** `meta[ai.instructionsField]` when non-empty, else `ai.instructions`, else ''. */\n instructions: string\n /** `ai.skeleton` with `{key}` replaced by the string value of `meta[key]` (declared fields only); '' without a skeleton. */\n skeleton: string\n /** Body blocks as Markdown ('' for a body without text). */\n body: string\n}\n\nconst PLACEHOLDER = /\\{([\\p{L}\\p{N}_-]+)\\}/gu\n\nconst isFilled = (value: unknown): value is string | number => (typeof value === 'string' && value !== '') || typeof value === 'number'\n\n/** The brief of the composite at `pos`; null unless the node there is a composite whose kind has `ai`. */\nexport function compositeAiBrief(editor: Editor, pos: number): CompositeAiBrief | null {\n const node = editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'composite' || node.childCount < COMPOSITE_CHROME_COUNT) return null\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n const ai = kind.ai\n if (!ai) return null\n\n const $pos = editor.state.doc.resolve(pos)\n const path: string[] = []\n for (let depth = 1; depth <= $pos.depth; depth++) {\n const ancestor = $pos.node(depth)\n if (ancestor.type.name === 'composite') path.push(ancestor.child(0).textContent.trim())\n }\n\n const tags: unknown = node.child(2).attrs.tags\n const body = Array.from({ length: node.childCount - COMPOSITE_CHROME_COUNT }, (_, i) => node.child(COMPOSITE_CHROME_COUNT + i))\n const meta: Record<string, unknown> = node.attrs.meta ?? {}\n\n const fields = kind.fields.flatMap((field) => {\n const raw = meta[field.key]\n if (field.type === 'bibliography') {\n const bib = raw as Partial<CslItem> | null | undefined\n return bib && typeof bib === 'object' && typeof bib.type === 'string' ? [{ key: field.key, label: field.label, value: citeFull({ ...(bib as CslItem), title: node.child(0).textContent }) }] : []\n }\n if (field.type === 'reference') {\n const target = typeof raw === 'string' && raw ? findBlockById(editor.state.doc, raw)?.node : undefined\n const title = target?.type.name === 'composite' ? target.child(0).textContent.trim() : ''\n return title ? [{ key: field.key, label: field.label, value: title }] : []\n }\n if (field.type === 'derivation') {\n const derivation = derivationOf(node)\n return derivation ? [{ key: field.key, label: field.label, value: `${derivation.method} from ${'asset' in derivation.from ? derivation.from.asset : derivation.from.block}` }] : []\n }\n if (!isFilled(raw)) return []\n const value = String(raw)\n return [{ key: field.key, label: field.label, value: field.type === 'select' ? (field.options?.find(option => option.value === value)?.label ?? value) : value }]\n })\n const keys = new Set(kind.fields.map(field => field.key))\n const own = ai.instructionsField ? String(meta[ai.instructionsField] ?? '').trim() : ''\n\n return {\n kind: { id: kind.id, label: kind.label, ...(ai.purpose && { purpose: ai.purpose }) },\n path,\n title: node.child(0).textContent,\n description: node.child(1).textContent,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n fields,\n instructions: own || ai.instructions?.trim() || '',\n skeleton: ai.skeleton?.replace(PLACEHOLDER, (match, key: string) => keys.has(key) && isFilled(meta[key]) ? String(meta[key]) : match) ?? '',\n body: body.some(child => child.textContent.trim())\n ? editor.markdown!.serialize({ type: 'doc', content: body.map(child => child.toJSON() as JSONContent) }).trim()\n : '',\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-brief.js","names":[],"sources":["../../../src/features/composites/ai-brief.ts"],"sourcesContent":["// Vue-free: what one Baustein tells the model (read by „Baustein füllen (KI)“, see `integrations/composite-ai`).\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport { type CslItem, citeFull } from './bibliography'\nimport { derivationOf } from './derivation'\nimport { compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT, compositeKindsOf } from './nodes'\n\n/** What one Baustein tells the model: kind brief, place in the hierarchy, chrome, field values, resolved instruction and skeleton, body. */\nexport interface CompositeAiBrief {\n kind: { id: string, label: string, purpose?: string }\n /** Titles of the enclosing composites, outermost first (empty at document level). */\n path: string[]\n title: string\n description: string\n tags: string[]\n /** Declared fields with a non-empty value, `select` values as their option label. */\n fields: { key: string, label: string, value: string }[]\n /** `meta[ai.instructionsField]` when non-empty, else `ai.instructions`, else ''. */\n instructions: string\n /** `ai.skeleton` with `{key}` replaced by the string value of `meta[key]` (declared fields only); '' without a skeleton. */\n skeleton: string\n /** Body blocks as Markdown ('' for a body without text). */\n body: string\n}\n\nconst PLACEHOLDER = /\\{([\\p{L}\\p{N}_-]+)\\}/gu\n\nconst isFilled = (value: unknown): value is string | number => (typeof value === 'string' && value !== '') || typeof value === 'number'\n\n/** The brief of the composite at `pos`; null unless the node there is a composite whose kind has `ai`. */\nexport function compositeAiBrief(editor: Editor, pos: number): CompositeAiBrief | null {\n const node = editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'composite' || node.childCount < COMPOSITE_CHROME_COUNT) return null\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n const ai = kind.ai\n if (!ai) return null\n\n const $pos = editor.state.doc.resolve(pos)\n const path: string[] = []\n for (let depth = 1; depth <= $pos.depth; depth++) {\n const ancestor = $pos.node(depth)\n if (ancestor.type.name === 'composite') path.push(ancestor.child(0).textContent.trim())\n }\n\n const tags: unknown = node.child(2).attrs.tags\n const body = Array.from({ length: node.childCount - COMPOSITE_CHROME_COUNT }, (_, i) => node.child(COMPOSITE_CHROME_COUNT + i))\n const meta: Record<string, unknown> = node.attrs.meta ?? {}\n\n const fields = kind.fields.flatMap((field) => {\n const raw = meta[field.key]\n if (field.type === 'bibliography') {\n const bib = raw as Partial<CslItem> | null | undefined\n return bib && typeof bib === 'object' && typeof bib.type === 'string' ? [{ key: field.key, label: field.label, value: citeFull({ ...(bib as CslItem), title: node.child(0).textContent }) }] : []\n }\n if (field.type === 'reference') {\n const target = typeof raw === 'string' && raw ? findBlockById(editor.state.doc, raw)?.node : undefined\n const title = target?.type.name === 'composite' ? target.child(0).textContent.trim() : ''\n return title ? [{ key: field.key, label: field.label, value: title }] : []\n }\n if (field.type === 'derivation') {\n const derivation = derivationOf(node)\n return derivation ? [{ key: field.key, label: field.label, value: `${derivation.method} from ${'asset' in derivation.from ? derivation.from.asset : derivation.from.block}` }] : []\n }\n if (field.type === 'multiselect') {\n const chosen = Array.isArray(raw) ? raw.filter((item): item is string => typeof item === 'string') : []\n const value = chosen.map(item => field.options?.find(option => option.value === item)?.label ?? item).join(', ')\n return value ? [{ key: field.key, label: field.label, value }] : []\n }\n if (!isFilled(raw)) return []\n const value = String(raw)\n return [{ key: field.key, label: field.label, value: field.type === 'select' ? (field.options?.find(option => option.value === value)?.label ?? value) : value }]\n })\n const keys = new Set(kind.fields.map(field => field.key))\n const own = ai.instructionsField ? String(meta[ai.instructionsField] ?? '').trim() : ''\n\n return {\n kind: { id: kind.id, label: kind.label, ...(ai.purpose && { purpose: ai.purpose }) },\n path,\n title: node.child(0).textContent,\n description: node.child(1).textContent,\n tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : [],\n fields,\n instructions: own || ai.instructions?.trim() || '',\n skeleton: ai.skeleton?.replace(PLACEHOLDER, (match, key: string) => keys.has(key) && isFilled(meta[key]) ? String(meta[key]) : match) ?? '',\n body: body.some(child => child.textContent.trim())\n ? editor.markdown!.serialize({ type: 'doc', content: body.map(child => child.toJSON() as JSONContent) }).trim()\n : '',\n }\n}\n"],"mappings":"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"}
|
|
@@ -38,7 +38,7 @@ const _hoisted_9 = { class: "w-64 p-3" };
|
|
|
38
38
|
* 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
|
|
39
39
|
* opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;
|
|
40
40
|
* `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite
|
|
41
|
-
* (`startBlockDrag`). With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is
|
|
41
|
+
* (`startBlockDrag`). A kind with `chrome.tags: false` shows no tag chips and no `+` chip. With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is
|
|
42
42
|
* open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.
|
|
43
43
|
* The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.
|
|
44
44
|
*/
|
|
@@ -184,7 +184,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
184
184
|
class: "size-3.5"
|
|
185
185
|
}, null, 8, ["name"]))], 46, _hoisted_2)]),
|
|
186
186
|
_: 2
|
|
187
|
-
}, 1024)) : createCommentVNode("v-if", true), item.status || item.row.tags.length || editable.value
|
|
187
|
+
}, 1024)) : createCommentVNode("v-if", true), item.status || item.derived || item.row.tagsVisible && (item.row.tags.length || editable.value) ? (openBlock(), createElementBlock("div", {
|
|
188
188
|
key: 1,
|
|
189
189
|
class: "absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]",
|
|
190
190
|
style: normalizeStyle({
|
|
@@ -242,7 +242,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
242
242
|
name: item.status.icon,
|
|
243
243
|
class: normalizeClass(["size-3.5", item.status.color])
|
|
244
244
|
}, null, 8, ["name", "class"]), createTextVNode(toDisplayString(item.status.label), 1)]))], 64)) : createCommentVNode("v-if", true),
|
|
245
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(item.row.tags, (tag) => {
|
|
245
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(item.row.tagsVisible ? item.row.tags : [], (tag) => {
|
|
246
246
|
return openBlock(), createElementBlock("span", {
|
|
247
247
|
key: tag,
|
|
248
248
|
class: normalizeClass([chip, "group max-w-40 gap-0"])
|
|
@@ -257,7 +257,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
257
257
|
class: "size-3"
|
|
258
258
|
})], 8, _hoisted_7)) : createCommentVNode("v-if", true)], 2);
|
|
259
259
|
}), 128)),
|
|
260
|
-
editable.value ? (openBlock(), createBlock(unref(UPopover), {
|
|
260
|
+
editable.value && item.row.tagsVisible ? (openBlock(), createBlock(unref(UPopover), {
|
|
261
261
|
key: 2,
|
|
262
262
|
content: {
|
|
263
263
|
side: "bottom",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositeChrome.js","names":["_unref","_openBlock","_createBlock","_Teleport","_createElementVNode","_createElementBlock","_Fragment","_renderList","_withCtx","_createVNode","CompositeDetails","_normalizeClass","_normalizeStyle","_withKeys","_withModifiers","_createCommentVNode","_createTextVNode","_toDisplayString","CompositeStatusMenu","CompositeTagsEditor"],"sources":["../../../../src/features/composites/components/CompositeChrome.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/vue-3'\nimport { computed, shallowRef, watch } from 'vue'\nimport { startBlockDrag } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { useCompositeAnchors } from '../use-composite-anchors'\nimport { useCompositeRows } from '../use-composite-rows'\nimport CompositeDetails from './CompositeDetails.vue'\nimport CompositeStatusMenu from './CompositeStatusMenu.vue'\nimport CompositeTagsEditor from './CompositeTagsEditor.vue'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon, Popover: UPopover } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst { rows } = useCompositeRows(() => props.editor)\nconst { host, anchors, frame } = useCompositeAnchors(() => props.editor, rows)\n\n// `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\nconst editable = shallowRef(false)\nwatch(() => props.editor, (instance, _previous, onCleanup) => {\n editable.value = !instance.isDestroyed && instance.isEditable\n const sync = () => {\n editable.value = !instance.isDestroyed && instance.isEditable\n }\n instance.on('transaction', sync)\n onCleanup(() => instance.off('transaction', sync))\n}, { immediate: true })\n\nconst { settings } = useEditorSettings()\nconst iconOnHover = computed(() => settings.value.blocks.kindIcon === 'hover')\n\n// Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n// composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\nconst hoveredKeys = shallowRef<ReadonlySet<string>>(new Set())\nconst chipsHoveredKey = shallowRef<string | null>(null)\nlet hoveredSection: Element | null = null\n\nfunction onEditorMouseover(event: MouseEvent) {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = props.editor\n hoveredKeys.value = new Set(rows.value.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key))\n}\n\nfunction onEditorMouseleave() {\n hoveredSection = null\n hoveredKeys.value = new Set()\n}\n\nwatch([() => props.editor, iconOnHover], ([instance, hover], _previous, onCleanup) => {\n onEditorMouseleave()\n if (!hover || instance.isDestroyed) return\n const { dom } = instance.view\n dom.addEventListener('mouseover', onEditorMouseover)\n dom.addEventListener('mouseleave', onEditorMouseleave)\n onCleanup(() => {\n dom.removeEventListener('mouseover', onEditorMouseover)\n dom.removeEventListener('mouseleave', onEditorMouseleave)\n })\n}, { immediate: true })\n\nfunction onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (!found || !startBlockDrag(props.editor, event, found.pos)) event.preventDefault()\n}\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\n\nconst placed = computed(() => {\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.value.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(props.editor, row)\n const open = !derived || !row.id || expanded.value.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.value.get(row.key)\n if (!anchor) return []\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: anchor.lastCenter - ROW_HEIGHT / 2,\n width: Math.max((frame.value?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n }]\n })\n})\n\nfunction toggleExpanded(row: CompositeRow) {\n if (row.id) props.editor.commands.toggleCompositeExpanded(row.id)\n}\n\nfunction removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(props.editor, row, row.tags.filter(candidate => candidate !== tag))\n}\n\nconst chip = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-[var(--sk-muted)]'\nconst chipButton = `${chip} transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]`\n\n/** Scale and fade in from the icon (`transform-origin` is Reka's), a bit longer than Nuxt UI's default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst detailsPopoverUi = {\n content: 'composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]',\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <div class=\"absolute left-0 top-0 h-0 w-0\">\n <template v-for=\"item in placed\" :key=\"item.row.key\">\n <UPopover\n v-if=\"item.anchor.toggleSize\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 4 }\"\n :ui=\"detailsPopoverUi\"\n >\n <!-- A span, not a `button`: Firefox does not start drags from buttons. -->\n <span\n role=\"button\"\n tabindex=\"0\"\n :draggable=\"editable\"\n :class=\"[\n 'absolute z-10 flex items-center justify-center rounded-sm bg-[var(--sk-bg)] text-[var(--sk-muted)] transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]',\n editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n ]\"\n :style=\"{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }\"\n :aria-label=\"t('composites.chrome.details', { kind: item.row.kindLabel })\"\n :title=\"editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })\"\n @keydown.enter.space.prevent=\"($event.currentTarget as HTMLElement).click()\"\n @dragstart=\"onIconDragStart($event, item.row)\"\n >\n <UIcon :key=\"item.row.icon\" :name=\"item.row.icon\" class=\"size-3.5\" />\n </span>\n <template #content>\n <CompositeDetails :editor=\"editor\" :row=\"item.row\" :editable=\"editable\" />\n </template>\n </UPopover>\n\n <div\n v-if=\"item.status || item.row.tags.length || editable || item.derived\"\n class=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]\"\n :style=\"{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }\"\n @mouseenter=\"chipsHoveredKey = item.row.key\"\n @mouseleave=\"chipsHoveredKey = null\"\n >\n <template v-if=\"item.derived\">\n <button\n v-if=\"item.derived.toggleable\"\n type=\"button\"\n :class=\"chipButton\"\n :aria-expanded=\"item.derived.open\"\n :aria-label=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n :title=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n @click=\"toggleExpanded(item.row)\"\n >\n <UIcon :name=\"item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'\" class=\"size-3.5\" />\n </button>\n <span :class=\"chip\">\n <UIcon name=\"i-lucide-git-branch\" class=\"size-3.5\" />{{ t('composites.derivation.badge') }}\n </span>\n <span v-if=\"item.derived.stale\" :class=\"[chip, 'text-[var(--sk-warning)]']\">\n <UIcon name=\"i-lucide-triangle-alert\" class=\"size-3.5\" />{{ t('composites.derivation.stale') }}\n </span>\n </template>\n\n <template v-if=\"item.status\">\n <CompositeStatusMenu v-if=\"editable\" :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.status.label', { status: item.status.label })\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </button>\n </CompositeStatusMenu>\n <span v-else :class=\"chip\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </span>\n </template>\n\n <span v-for=\"tag in item.row.tags\" :key=\"tag\" :class=\"[chip, 'group max-w-40 gap-0']\">\n <span class=\"truncate\">#{{ tag }}</span>\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]\"\n :aria-label=\"t('composites.tag.remove', { tag })\"\n @click=\"removeTag(item.row, tag)\"\n >\n <UIcon name=\"i-lucide-x\" class=\"size-3\" />\n </button>\n </span>\n\n <UPopover\n v-if=\"editable\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 6, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n >\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.tag.add')\" :title=\"t('composites.tag.add')\">\n <UIcon name=\"i-lucide-plus\" class=\"size-3.5\" />\n </button>\n <template #content>\n <div class=\"w-64 p-3\">\n <CompositeTagsEditor :editor=\"editor\" :row=\"item.row\" />\n </div>\n </template>\n </UPopover>\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompositeChrome.js","names":["_unref","_openBlock","_createBlock","_Teleport","_createElementVNode","_createElementBlock","_Fragment","_renderList","_withCtx","_createVNode","CompositeDetails","_normalizeClass","_normalizeStyle","_withKeys","_withModifiers","_createCommentVNode","_createTextVNode","_toDisplayString","CompositeStatusMenu","CompositeTagsEditor"],"sources":["../../../../src/features/composites/components/CompositeChrome.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/vue-3'\nimport { computed, shallowRef, watch } from 'vue'\nimport { startBlockDrag } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { useCompositeAnchors } from '../use-composite-anchors'\nimport { useCompositeRows } from '../use-composite-rows'\nimport CompositeDetails from './CompositeDetails.vue'\nimport CompositeStatusMenu from './CompositeStatusMenu.vue'\nimport CompositeTagsEditor from './CompositeTagsEditor.vue'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). A kind with `chrome.tags: false` shows no tag chips and no `+` chip. With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon, Popover: UPopover } = useUiKit()\nconst { t } = useEditorI18n()\n\nconst { rows } = useCompositeRows(() => props.editor)\nconst { host, anchors, frame } = useCompositeAnchors(() => props.editor, rows)\n\n// `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\nconst editable = shallowRef(false)\nwatch(() => props.editor, (instance, _previous, onCleanup) => {\n editable.value = !instance.isDestroyed && instance.isEditable\n const sync = () => {\n editable.value = !instance.isDestroyed && instance.isEditable\n }\n instance.on('transaction', sync)\n onCleanup(() => instance.off('transaction', sync))\n}, { immediate: true })\n\nconst { settings } = useEditorSettings()\nconst iconOnHover = computed(() => settings.value.blocks.kindIcon === 'hover')\n\n// Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n// composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\nconst hoveredKeys = shallowRef<ReadonlySet<string>>(new Set())\nconst chipsHoveredKey = shallowRef<string | null>(null)\nlet hoveredSection: Element | null = null\n\nfunction onEditorMouseover(event: MouseEvent) {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = props.editor\n hoveredKeys.value = new Set(rows.value.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key))\n}\n\nfunction onEditorMouseleave() {\n hoveredSection = null\n hoveredKeys.value = new Set()\n}\n\nwatch([() => props.editor, iconOnHover], ([instance, hover], _previous, onCleanup) => {\n onEditorMouseleave()\n if (!hover || instance.isDestroyed) return\n const { dom } = instance.view\n dom.addEventListener('mouseover', onEditorMouseover)\n dom.addEventListener('mouseleave', onEditorMouseleave)\n onCleanup(() => {\n dom.removeEventListener('mouseover', onEditorMouseover)\n dom.removeEventListener('mouseleave', onEditorMouseleave)\n })\n}, { immediate: true })\n\nfunction onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(props.editor.state.doc, row)\n if (!found || !startBlockDrag(props.editor, event, found.pos)) event.preventDefault()\n}\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst expanded = useStoreRef(() => props.editor.storage.composite.expanded)\n\nconst placed = computed(() => {\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.value.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(props.editor, row)\n const open = !derived || !row.id || expanded.value.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.value.get(row.key)\n if (!anchor) return []\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: anchor.lastCenter - ROW_HEIGHT / 2,\n width: Math.max((frame.value?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n }]\n })\n})\n\nfunction toggleExpanded(row: CompositeRow) {\n if (row.id) props.editor.commands.toggleCompositeExpanded(row.id)\n}\n\nfunction removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(props.editor, row, row.tags.filter(candidate => candidate !== tag))\n}\n\nconst chip = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-[var(--sk-muted)]'\nconst chipButton = `${chip} transition-colors duration-150 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]`\n\n/** Scale and fade in from the icon (`transform-origin` is Reka's), a bit longer than Nuxt UI's default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst detailsPopoverUi = {\n content: 'composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]',\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <div class=\"absolute left-0 top-0 h-0 w-0\">\n <template v-for=\"item in placed\" :key=\"item.row.key\">\n <UPopover\n v-if=\"item.anchor.toggleSize\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 4 }\"\n :ui=\"detailsPopoverUi\"\n >\n <!-- A span, not a `button`: Firefox does not start drags from buttons. -->\n <span\n role=\"button\"\n tabindex=\"0\"\n :draggable=\"editable\"\n :class=\"[\n 'absolute z-10 flex items-center justify-center rounded-sm bg-[var(--sk-bg)] text-[var(--sk-muted)] transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--sk-primary)]',\n editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n ]\"\n :style=\"{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }\"\n :aria-label=\"t('composites.chrome.details', { kind: item.row.kindLabel })\"\n :title=\"editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })\"\n @keydown.enter.space.prevent=\"($event.currentTarget as HTMLElement).click()\"\n @dragstart=\"onIconDragStart($event, item.row)\"\n >\n <UIcon :key=\"item.row.icon\" :name=\"item.row.icon\" class=\"size-3.5\" />\n </span>\n <template #content>\n <CompositeDetails :editor=\"editor\" :row=\"item.row\" :editable=\"editable\" />\n </template>\n </UPopover>\n\n <div\n v-if=\"item.status || item.derived || (item.row.tagsVisible && (item.row.tags.length || editable))\"\n class=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-[var(--sk-bg)]\"\n :style=\"{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }\"\n @mouseenter=\"chipsHoveredKey = item.row.key\"\n @mouseleave=\"chipsHoveredKey = null\"\n >\n <template v-if=\"item.derived\">\n <button\n v-if=\"item.derived.toggleable\"\n type=\"button\"\n :class=\"chipButton\"\n :aria-expanded=\"item.derived.open\"\n :aria-label=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n :title=\"t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')\"\n @click=\"toggleExpanded(item.row)\"\n >\n <UIcon :name=\"item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'\" class=\"size-3.5\" />\n </button>\n <span :class=\"chip\">\n <UIcon name=\"i-lucide-git-branch\" class=\"size-3.5\" />{{ t('composites.derivation.badge') }}\n </span>\n <span v-if=\"item.derived.stale\" :class=\"[chip, 'text-[var(--sk-warning)]']\">\n <UIcon name=\"i-lucide-triangle-alert\" class=\"size-3.5\" />{{ t('composites.derivation.stale') }}\n </span>\n </template>\n\n <template v-if=\"item.status\">\n <CompositeStatusMenu v-if=\"editable\" :editor=\"editor\" :row=\"item.row\">\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.status.label', { status: item.status.label })\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </button>\n </CompositeStatusMenu>\n <span v-else :class=\"chip\">\n <UIcon :name=\"item.status.icon\" :class=\"['size-3.5', item.status.color]\" />{{ item.status.label }}\n </span>\n </template>\n\n <span v-for=\"tag in item.row.tagsVisible ? item.row.tags : []\" :key=\"tag\" :class=\"[chip, 'group max-w-40 gap-0']\">\n <span class=\"truncate\">#{{ tag }}</span>\n <button\n v-if=\"editable\"\n type=\"button\"\n class=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[var(--sk-primary)]\"\n :aria-label=\"t('composites.tag.remove', { tag })\"\n @click=\"removeTag(item.row, tag)\"\n >\n <UIcon name=\"i-lucide-x\" class=\"size-3\" />\n </button>\n </span>\n\n <UPopover\n v-if=\"editable && item.row.tagsVisible\"\n :content=\"{ side: 'bottom', align: 'start', sideOffset: 6, onCloseAutoFocus: (event: Event) => event.preventDefault() }\"\n >\n <button type=\"button\" :class=\"chipButton\" :aria-label=\"t('composites.tag.add')\" :title=\"t('composites.tag.add')\">\n <UIcon name=\"i-lucide-plus\" class=\"size-3.5\" />\n </button>\n <template #content>\n <div class=\"w-64 p-3\">\n <CompositeTagsEditor :editor=\"editor\" :row=\"item.row\" />\n </div>\n </template>\n </UPopover>\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { Editor } from "@tiptap/vue-3";
|
|
|
5
5
|
* 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
|
|
6
6
|
* opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;
|
|
7
7
|
* `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite
|
|
8
|
-
* (`startBlockDrag`). With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is
|
|
8
|
+
* (`startBlockDrag`). A kind with `chrome.tags: false` shows no tag chips and no `+` chip. With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is
|
|
9
9
|
* open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.
|
|
10
10
|
* The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.
|
|
11
11
|
*/
|
|
@@ -23,11 +23,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
23
23
|
},
|
|
24
24
|
setup(__props) {
|
|
25
25
|
const props = __props;
|
|
26
|
-
const { Input: UInput, Select: USelect, Textarea: UTextarea } = useUiKit();
|
|
26
|
+
const { Input: UInput, InputMenu: UInputMenu, Select: USelect, Textarea: UTextarea } = useUiKit();
|
|
27
27
|
const { t } = useEditorI18n();
|
|
28
28
|
/** The Baustein a `derivation` field belongs to; the read-only provenance display needs the node, not the field text. */
|
|
29
29
|
const derived = computed(() => props.field.type === "derivation" ? compositeOfRow(props.editor.state.doc, props.row)?.node ?? null : null);
|
|
30
30
|
const stored = computed(() => metaText(props.row, props.field));
|
|
31
|
+
/** The chosen values of a `multiselect` field (`meta[key]` is a `string[]`). */
|
|
32
|
+
const chosen = computed(() => {
|
|
33
|
+
const value = props.row.meta[props.field.key];
|
|
34
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
35
|
+
});
|
|
31
36
|
const draft = ref(stored.value);
|
|
32
37
|
const typing = ref(false);
|
|
33
38
|
let dirty = false;
|
|
@@ -55,6 +60,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
55
60
|
function onChoice(value) {
|
|
56
61
|
setCompositeMetaField(props.editor, props.row, props.field.key, typeof value === "string" ? value : "");
|
|
57
62
|
}
|
|
63
|
+
function onChoices(value) {
|
|
64
|
+
setCompositeMetaField(props.editor, props.row, props.field.key, Array.isArray(value) ? value : []);
|
|
65
|
+
}
|
|
58
66
|
return (_ctx, _cache) => {
|
|
59
67
|
return __props.field.type === "select" ? (openBlock(), createBlock(unref(USelect), {
|
|
60
68
|
key: 0,
|
|
@@ -73,8 +81,26 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
73
81
|
"aria-label",
|
|
74
82
|
"disabled",
|
|
75
83
|
"size"
|
|
76
|
-
])) : __props.field.type === "
|
|
84
|
+
])) : __props.field.type === "multiselect" ? (openBlock(), createBlock(unref(UInputMenu), {
|
|
77
85
|
key: 1,
|
|
86
|
+
multiple: "",
|
|
87
|
+
"model-value": chosen.value,
|
|
88
|
+
items: __props.field.options ?? [],
|
|
89
|
+
placeholder: unref(t)("composites.field.choose", { field: __props.field.label }),
|
|
90
|
+
"aria-label": __props.field.label,
|
|
91
|
+
disabled: __props.disabled,
|
|
92
|
+
size: __props.size,
|
|
93
|
+
class: "w-full",
|
|
94
|
+
"onUpdate:modelValue": onChoices
|
|
95
|
+
}, null, 8, [
|
|
96
|
+
"model-value",
|
|
97
|
+
"items",
|
|
98
|
+
"placeholder",
|
|
99
|
+
"aria-label",
|
|
100
|
+
"disabled",
|
|
101
|
+
"size"
|
|
102
|
+
])) : __props.field.type === "reference" ? (openBlock(), createBlock(_sfc_main$3, {
|
|
103
|
+
key: 2,
|
|
78
104
|
editor: __props.editor,
|
|
79
105
|
row: __props.row,
|
|
80
106
|
field: __props.field,
|
|
@@ -87,7 +113,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
87
113
|
"disabled",
|
|
88
114
|
"size"
|
|
89
115
|
])) : __props.field.type === "bibliography" ? (openBlock(), createBlock(_sfc_main$1, {
|
|
90
|
-
key:
|
|
116
|
+
key: 3,
|
|
91
117
|
editor: __props.editor,
|
|
92
118
|
row: __props.row,
|
|
93
119
|
field: __props.field,
|
|
@@ -97,12 +123,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
97
123
|
"row",
|
|
98
124
|
"field",
|
|
99
125
|
"disabled"
|
|
100
|
-
])) : __props.field.type === "derivation" ? (openBlock(), createElementBlock(Fragment, { key:
|
|
126
|
+
])) : __props.field.type === "derivation" ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [derived.value ? (openBlock(), createBlock(_sfc_main$2, {
|
|
101
127
|
key: 0,
|
|
102
128
|
editor: __props.editor,
|
|
103
129
|
node: derived.value
|
|
104
130
|
}, null, 8, ["editor", "node"])) : createCommentVNode("v-if", true)], 64)) : __props.field.type === "textarea" ? (openBlock(), createBlock(unref(UTextarea), {
|
|
105
|
-
key:
|
|
131
|
+
key: 5,
|
|
106
132
|
"model-value": draft.value,
|
|
107
133
|
"aria-label": __props.field.label,
|
|
108
134
|
disabled: __props.disabled,
|
|
@@ -120,7 +146,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
120
146
|
"disabled",
|
|
121
147
|
"size"
|
|
122
148
|
])) : (openBlock(), createBlock(unref(UInput), {
|
|
123
|
-
key:
|
|
149
|
+
key: 6,
|
|
124
150
|
"model-value": draft.value,
|
|
125
151
|
type: __props.field.type,
|
|
126
152
|
"aria-label": __props.field.label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositeFieldEditor.js","names":["_openBlock","_createBlock","_unref","ReferenceFieldSelect","BibliographyEditor","_createElementBlock","_Fragment","DerivationBadge","_createCommentVNode","_withKeys"],"sources":["../../../../src/features/composites/components/CompositeFieldEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { metaText } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeMetaField } from '../rows'\nimport BibliographyEditor from './BibliographyEditor.vue'\nimport DerivationBadge from './DerivationBadge.vue'\nimport ReferenceFieldSelect from './ReferenceFieldSelect.vue'\n\n/**\n * One meta field of a Baustein as an input that writes through `setCompositeMetaField`. Text-like inputs keep a local\n * draft and commit after typing pauses, on blur and on unmount, so the one-frame-late re-read of the rows never\n * overwrites what is being typed.\n */\nconst props = withDefaults(defineProps<{\n editor: Editor\n row: CompositeRow\n field: CompositeMetaField\n disabled?: boolean\n size?: 'xs' | 'sm' | 'md'\n}>(), { size: 'sm' })\n\nconst { Input: UInput, Select: USelect, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\n/** The Baustein a `derivation` field belongs to; the read-only provenance display needs the node, not the field text. */\nconst derived = computed(() => props.field.type === 'derivation' ? compositeOfRow(props.editor.state.doc, props.row)?.node ?? null : null)\n\nconst stored = computed(() => metaText(props.row, props.field))\nconst draft = ref(stored.value)\nconst typing = ref(false)\nlet dirty = false\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(stored, (value) => {\n if (!typing.value && !dirty) draft.value = value\n})\n\nfunction commit() {\n clearTimeout(timer)\n if (!dirty) return\n dirty = false\n if (!props.editor.isDestroyed) setCompositeMetaField(props.editor, props.row, props.field.key, draft.value)\n}\n\nfunction onInput(value: unknown) {\n draft.value = typeof value === 'string' ? value : ''\n dirty = true\n clearTimeout(timer)\n timer = setTimeout(commit, 400)\n}\n\nfunction onBlur() {\n typing.value = false\n commit()\n}\n\nonBeforeUnmount(commit)\n\nfunction onChoice(value: unknown) {\n setCompositeMetaField(props.editor, props.row, props.field.key, typeof value === 'string' ? value : '')\n}\n</script>\n\n<template>\n <USelect\n v-if=\"field.type === 'select'\"\n :model-value=\"stored || undefined\"\n :items=\"field.options ?? []\"\n :placeholder=\"t('composites.field.choose', { field: field.label })\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n class=\"w-full\"\n @update:model-value=\"onChoice\"\n />\n <ReferenceFieldSelect\n v-else-if=\"field.type === 'reference'\"\n :editor=\"editor\"\n :row=\"row\"\n :field=\"field\"\n :disabled=\"disabled\"\n :size=\"size\"\n />\n <BibliographyEditor\n v-else-if=\"field.type === 'bibliography'\"\n :editor=\"editor\"\n :row=\"row\"\n :field=\"field\"\n :disabled=\"disabled\"\n />\n <template v-else-if=\"field.type === 'derivation'\">\n <DerivationBadge v-if=\"derived\" :editor=\"editor\" :node=\"derived\" />\n </template>\n <UTextarea\n v-else-if=\"field.type === 'textarea'\"\n :model-value=\"draft\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n autoresize\n :rows=\"2\"\n :maxrows=\"6\"\n class=\"w-full\"\n @focus=\"typing = true\"\n @blur=\"onBlur\"\n @update:model-value=\"onInput\"\n />\n <UInput\n v-else\n :model-value=\"draft\"\n :type=\"field.type\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n class=\"w-full\"\n @focus=\"typing = true\"\n @blur=\"onBlur\"\n @keydown.enter=\"commit\"\n @update:model-value=\"onInput\"\n />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EAiBA,MAAM,QAAQ;EAQd,MAAM,EAAE,OAAO,QAAQ,QAAQ,SAAS,UAAU,cAAc,SAAS;
|
|
1
|
+
{"version":3,"file":"CompositeFieldEditor.js","names":["_openBlock","_createBlock","_unref","ReferenceFieldSelect","BibliographyEditor","_createElementBlock","_Fragment","DerivationBadge","_createCommentVNode","_withKeys"],"sources":["../../../../src/features/composites/components/CompositeFieldEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Editor } from '@tiptap/core'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport type { CompositeMetaField } from '../kinds'\nimport { metaText } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeMetaField } from '../rows'\nimport BibliographyEditor from './BibliographyEditor.vue'\nimport DerivationBadge from './DerivationBadge.vue'\nimport ReferenceFieldSelect from './ReferenceFieldSelect.vue'\n\n/**\n * One meta field of a Baustein as an input that writes through `setCompositeMetaField`. Text-like inputs keep a local\n * draft and commit after typing pauses, on blur and on unmount, so the one-frame-late re-read of the rows never\n * overwrites what is being typed.\n */\nconst props = withDefaults(defineProps<{\n editor: Editor\n row: CompositeRow\n field: CompositeMetaField\n disabled?: boolean\n size?: 'xs' | 'sm' | 'md'\n}>(), { size: 'sm' })\n\nconst { Input: UInput, InputMenu: UInputMenu, Select: USelect, Textarea: UTextarea } = useUiKit()\nconst { t } = useEditorI18n()\n\n/** The Baustein a `derivation` field belongs to; the read-only provenance display needs the node, not the field text. */\nconst derived = computed(() => props.field.type === 'derivation' ? compositeOfRow(props.editor.state.doc, props.row)?.node ?? null : null)\n\nconst stored = computed(() => metaText(props.row, props.field))\n/** The chosen values of a `multiselect` field (`meta[key]` is a `string[]`). */\nconst chosen = computed(() => {\n const value = props.row.meta[props.field.key]\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []\n})\nconst draft = ref(stored.value)\nconst typing = ref(false)\nlet dirty = false\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(stored, (value) => {\n if (!typing.value && !dirty) draft.value = value\n})\n\nfunction commit() {\n clearTimeout(timer)\n if (!dirty) return\n dirty = false\n if (!props.editor.isDestroyed) setCompositeMetaField(props.editor, props.row, props.field.key, draft.value)\n}\n\nfunction onInput(value: unknown) {\n draft.value = typeof value === 'string' ? value : ''\n dirty = true\n clearTimeout(timer)\n timer = setTimeout(commit, 400)\n}\n\nfunction onBlur() {\n typing.value = false\n commit()\n}\n\nonBeforeUnmount(commit)\n\nfunction onChoice(value: unknown) {\n setCompositeMetaField(props.editor, props.row, props.field.key, typeof value === 'string' ? value : '')\n}\n\nfunction onChoices(value: unknown) {\n setCompositeMetaField(props.editor, props.row, props.field.key, Array.isArray(value) ? value : [])\n}\n</script>\n\n<template>\n <USelect\n v-if=\"field.type === 'select'\"\n :model-value=\"stored || undefined\"\n :items=\"field.options ?? []\"\n :placeholder=\"t('composites.field.choose', { field: field.label })\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n class=\"w-full\"\n @update:model-value=\"onChoice\"\n />\n <UInputMenu\n v-else-if=\"field.type === 'multiselect'\"\n multiple\n :model-value=\"chosen\"\n :items=\"field.options ?? []\"\n :placeholder=\"t('composites.field.choose', { field: field.label })\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n class=\"w-full\"\n @update:model-value=\"onChoices\"\n />\n <ReferenceFieldSelect\n v-else-if=\"field.type === 'reference'\"\n :editor=\"editor\"\n :row=\"row\"\n :field=\"field\"\n :disabled=\"disabled\"\n :size=\"size\"\n />\n <BibliographyEditor\n v-else-if=\"field.type === 'bibliography'\"\n :editor=\"editor\"\n :row=\"row\"\n :field=\"field\"\n :disabled=\"disabled\"\n />\n <template v-else-if=\"field.type === 'derivation'\">\n <DerivationBadge v-if=\"derived\" :editor=\"editor\" :node=\"derived\" />\n </template>\n <UTextarea\n v-else-if=\"field.type === 'textarea'\"\n :model-value=\"draft\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n autoresize\n :rows=\"2\"\n :maxrows=\"6\"\n class=\"w-full\"\n @focus=\"typing = true\"\n @blur=\"onBlur\"\n @update:model-value=\"onInput\"\n />\n <UInput\n v-else\n :model-value=\"draft\"\n :type=\"field.type\"\n :aria-label=\"field.label\"\n :disabled=\"disabled\"\n :size=\"size\"\n class=\"w-full\"\n @focus=\"typing = true\"\n @blur=\"onBlur\"\n @keydown.enter=\"commit\"\n @update:model-value=\"onInput\"\n />\n</template>\n"],"mappings":"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"}
|
|
@@ -200,7 +200,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
200
200
|
])]),
|
|
201
201
|
_: 1
|
|
202
202
|
}, 8, ["label", "help"]),
|
|
203
|
-
|
|
203
|
+
unref(focused).tagsVisible ? (openBlock(), createBlock(unref(UFormField), {
|
|
204
|
+
key: 0,
|
|
204
205
|
label: unref(t)("composites.tags.label"),
|
|
205
206
|
size: "sm"
|
|
206
207
|
}, {
|
|
@@ -238,7 +239,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
238
239
|
"onCreate"
|
|
239
240
|
])]),
|
|
240
241
|
_: 1
|
|
241
|
-
}, 8, ["label"]),
|
|
242
|
+
}, 8, ["label"])) : createCommentVNode("v-if", true),
|
|
242
243
|
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(focused).fields, (field) => {
|
|
243
244
|
return openBlock(), createBlock(unref(UFormField), {
|
|
244
245
|
key: field.key,
|