@ecsplico/editor 3.5.0 → 3.6.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 +13 -2
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/composites/kinds.d.ts +6 -0
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/merge.d.ts +4 -3
- package/dist/features/composites/merge.js +29 -3
- package/dist/features/composites/merge.js.map +1 -1
- package/dist/features/image-studio/node-core.d.ts +46 -0
- package/dist/features/image-studio/node-core.js +79 -0
- package/dist/features/image-studio/node-core.js.map +1 -0
- package/dist/features/image-studio/node-store.d.ts +3 -11
- package/dist/features/image-studio/node-store.js +15 -44
- package/dist/features/image-studio/node-store.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioChat.js +101 -0
- package/dist/features/image-studio/react/ImageStudioChat.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioDialog.d.ts +19 -0
- package/dist/features/image-studio/react/ImageStudioDialog.js +357 -0
- package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioTile.js +51 -0
- package/dist/features/image-studio/react/ImageStudioTile.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioView.js +126 -0
- package/dist/features/image-studio/react/ImageStudioView.js.map +1 -0
- package/dist/features/image-studio/react/store.d.ts +37 -0
- package/dist/features/image-studio/react/store.js +46 -0
- package/dist/features/image-studio/react/store.js.map +1 -0
- package/dist/features/image-studio/react.d.ts +18 -0
- package/dist/features/image-studio/react.js +21 -0
- package/dist/features/image-studio/react.js.map +1 -0
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
- package/dist/server/handlers/ai.d.ts +2 -2
- package/dist/vue/AdvancedEditor.vue.d.ts +5 -5
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/package.json +6 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { editorEntryOf } from "../../../core/editor-ui.js";
|
|
2
|
+
import "../../media/assets.js";
|
|
3
|
+
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
4
|
+
import { cn } from "../../../react/ui/utils.js";
|
|
5
|
+
import { Icon } from "../../../react/Icon.js";
|
|
6
|
+
import { Button } from "../../../react/ui/button.js";
|
|
7
|
+
import { useNotifier } from "../../../react/use-notifier.js";
|
|
8
|
+
import { normalizeImageStudioState } from "../state.js";
|
|
9
|
+
import { IMAGE_STUDIO_OPEN } from "../feature.js";
|
|
10
|
+
import { ImageStudioDialog } from "./ImageStudioDialog.js";
|
|
11
|
+
import { useImageStudioNodeStore } from "./store.js";
|
|
12
|
+
import { useEffect, useRef, useState } from "react";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
import { NodeViewWrapper } from "@tiptap/react";
|
|
15
|
+
//#region src/features/image-studio/react/ImageStudioView.tsx
|
|
16
|
+
/** The studio of one block: its store (and with it the Prompt-Bausteine list) exists from the first opening on. */
|
|
17
|
+
function NodeStudio({ editor, getPos, backend, documentId, open, onOpenChange }) {
|
|
18
|
+
const { t } = useEditorI18n();
|
|
19
|
+
const store = useImageStudioNodeStore({
|
|
20
|
+
editor,
|
|
21
|
+
getPos,
|
|
22
|
+
backend,
|
|
23
|
+
documentId
|
|
24
|
+
});
|
|
25
|
+
return store ? /* @__PURE__ */ jsx(ImageStudioDialog, {
|
|
26
|
+
open,
|
|
27
|
+
onOpenChange,
|
|
28
|
+
store,
|
|
29
|
+
title: t("imageStudio.node.dialogTitle")
|
|
30
|
+
}) : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The `imageStudio` block: the selected picture (or the description as a placeholder), the description as caption and the
|
|
34
|
+
* „Bild-Studio öffnen“ button. The studio dialog opens from that button, on double-click and through the block action
|
|
35
|
+
* `image-studio.open` (the `IMAGE_STUDIO_OPEN` event on the block's node view element); a read-only editor opens none,
|
|
36
|
+
* a backend without image generation explains itself.
|
|
37
|
+
*/
|
|
38
|
+
function ImageStudioView({ node, selected, editor, getPos }) {
|
|
39
|
+
const { t } = useEditorI18n();
|
|
40
|
+
const notify = useNotifier(editor);
|
|
41
|
+
const description = normalizeImageStudioState(node.attrs.studio).description.trim();
|
|
42
|
+
const [target, setTarget] = useState();
|
|
43
|
+
const [open, setOpen] = useState(false);
|
|
44
|
+
function openStudio() {
|
|
45
|
+
if (!editor.isEditable) return;
|
|
46
|
+
const ctx = editorEntryOf(editor)?.ctx;
|
|
47
|
+
const backend = ctx?.backend.images;
|
|
48
|
+
if (!ctx || !backend) {
|
|
49
|
+
notify({
|
|
50
|
+
id: "image-studio.unavailable",
|
|
51
|
+
title: t("imageStudio.action.notConfigured"),
|
|
52
|
+
kind: "warning"
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setTarget((previous) => previous?.backend === backend ? previous : {
|
|
57
|
+
backend,
|
|
58
|
+
documentId: ctx.documentId ?? "scratch"
|
|
59
|
+
});
|
|
60
|
+
setOpen(true);
|
|
61
|
+
}
|
|
62
|
+
const figure = useRef(null);
|
|
63
|
+
const openRef = useRef(openStudio);
|
|
64
|
+
openRef.current = openStudio;
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const dom = figure.current?.parentElement;
|
|
67
|
+
if (!dom) return;
|
|
68
|
+
const onOpen = () => openRef.current();
|
|
69
|
+
dom.addEventListener(IMAGE_STUDIO_OPEN, onOpen);
|
|
70
|
+
return () => dom.removeEventListener(IMAGE_STUDIO_OPEN, onOpen);
|
|
71
|
+
}, []);
|
|
72
|
+
return /* @__PURE__ */ jsxs(NodeViewWrapper, {
|
|
73
|
+
ref: figure,
|
|
74
|
+
as: "figure",
|
|
75
|
+
className: cn("my-3 flex flex-col gap-2 rounded-md", selected && "outline-2 outline-offset-2 outline-primary"),
|
|
76
|
+
"data-image-studio-block": true,
|
|
77
|
+
onDoubleClick: openStudio,
|
|
78
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
79
|
+
className: "flex flex-col gap-2",
|
|
80
|
+
contentEditable: false,
|
|
81
|
+
children: [
|
|
82
|
+
node.attrs.src ? /* @__PURE__ */ jsx("img", {
|
|
83
|
+
src: node.attrs.src,
|
|
84
|
+
alt: node.attrs.alt || description,
|
|
85
|
+
className: "m-0 max-w-full rounded-md"
|
|
86
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
87
|
+
className: "flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed border-border p-4 text-center text-sm",
|
|
88
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
89
|
+
name: "i-lucide-image-plus",
|
|
90
|
+
className: "size-6 text-muted-foreground/60"
|
|
91
|
+
}), description ? /* @__PURE__ */ jsx("span", {
|
|
92
|
+
className: "whitespace-pre-wrap text-muted-foreground",
|
|
93
|
+
children: description
|
|
94
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
95
|
+
className: "text-muted-foreground/60",
|
|
96
|
+
children: t("imageStudio.node.noDescription")
|
|
97
|
+
})]
|
|
98
|
+
}),
|
|
99
|
+
node.attrs.src && description && /* @__PURE__ */ jsx("figcaption", {
|
|
100
|
+
className: "whitespace-pre-wrap px-1 text-sm text-muted-foreground",
|
|
101
|
+
children: description
|
|
102
|
+
}),
|
|
103
|
+
editor.isEditable && /* @__PURE__ */ jsx("div", {
|
|
104
|
+
className: "flex",
|
|
105
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
106
|
+
size: "xs",
|
|
107
|
+
variant: "secondary",
|
|
108
|
+
onClick: openStudio,
|
|
109
|
+
children: [/* @__PURE__ */ jsx(Icon, { name: "i-lucide-wand-sparkles" }), t("imageStudio.node.open")]
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
}), target && /* @__PURE__ */ jsx(NodeStudio, {
|
|
114
|
+
editor,
|
|
115
|
+
getPos,
|
|
116
|
+
backend: target.backend,
|
|
117
|
+
documentId: target.documentId,
|
|
118
|
+
open,
|
|
119
|
+
onOpenChange: setOpen
|
|
120
|
+
})]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { ImageStudioView };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=ImageStudioView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioView.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioView.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { editorEntryOf } from '../../../core'\nimport type { EditorBackend } from '../../../core/backend'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { useNotifier } from '../../../react/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { IMAGE_STUDIO_OPEN } from '../feature'\nimport { normalizeImageStudioState } from '../state'\nimport { ImageStudioDialog } from './ImageStudioDialog'\nimport { useImageStudioNodeStore } from './store'\n\n/** The studio of one block: its store (and with it the Prompt-Bausteine list) exists from the first opening on. */\nfunction NodeStudio({ editor, getPos, backend, documentId, open, onOpenChange }: {\n editor: Editor\n getPos: NodeViewProps['getPos']\n backend: NonNullable<EditorBackend['images']>\n documentId: string\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const { t } = useEditorI18n()\n const store = useImageStudioNodeStore({ editor, getPos, backend, documentId })\n return store\n ? <ImageStudioDialog open={open} onOpenChange={onOpenChange} store={store} title={t('imageStudio.node.dialogTitle')} />\n : null\n}\n\n/**\n * The `imageStudio` block: the selected picture (or the description as a placeholder), the description as caption and the\n * „Bild-Studio öffnen“ button. The studio dialog opens from that button, on double-click and through the block action\n * `image-studio.open` (the `IMAGE_STUDIO_OPEN` event on the block's node view element); a read-only editor opens none,\n * a backend without image generation explains itself.\n */\nexport function ImageStudioView({ node, selected, editor, getPos }: NodeViewProps) {\n const { t } = useEditorI18n()\n const notify = useNotifier(editor)\n const studio = normalizeImageStudioState(node.attrs.studio)\n const description = studio.description.trim()\n\n const [target, setTarget] = useState<{ backend: NonNullable<EditorBackend['images']>, documentId: string }>()\n const [open, setOpen] = useState(false)\n\n function openStudio() {\n if (!editor.isEditable) return\n const ctx = editorEntryOf(editor)?.ctx\n const backend = ctx?.backend.images\n if (!ctx || !backend) {\n notify({ id: 'image-studio.unavailable', title: t('imageStudio.action.notConfigured'), kind: 'warning' })\n return\n }\n setTarget(previous => previous?.backend === backend ? previous : { backend, documentId: ctx.documentId ?? SCRATCH_DOCUMENT_ID })\n setOpen(true)\n }\n\n // The block action dispatches `IMAGE_STUDIO_OPEN` on the node view's element, the parent of the wrapper rendered here.\n const figure = useRef<HTMLElement>(null)\n const openRef = useRef(openStudio)\n openRef.current = openStudio\n useEffect(() => {\n const dom = figure.current?.parentElement\n if (!dom) return\n const onOpen = () => openRef.current()\n dom.addEventListener(IMAGE_STUDIO_OPEN, onOpen)\n return () => dom.removeEventListener(IMAGE_STUDIO_OPEN, onOpen)\n }, [])\n\n return (\n <NodeViewWrapper\n ref={figure}\n as=\"figure\"\n className={cn('my-3 flex flex-col gap-2 rounded-md', selected && 'outline-2 outline-offset-2 outline-primary')}\n data-image-studio-block\n onDoubleClick={openStudio}\n >\n <div className=\"flex flex-col gap-2\" contentEditable={false}>\n {node.attrs.src\n ? <img src={node.attrs.src} alt={node.attrs.alt || description} className=\"m-0 max-w-full rounded-md\" />\n : (\n <div className=\"flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed border-border p-4 text-center text-sm\">\n <Icon name=\"i-lucide-image-plus\" className=\"size-6 text-muted-foreground/60\" />\n {description\n ? <span className=\"whitespace-pre-wrap text-muted-foreground\">{description}</span>\n : <span className=\"text-muted-foreground/60\">{t('imageStudio.node.noDescription')}</span>}\n </div>\n )}\n {node.attrs.src && description && (\n <figcaption className=\"whitespace-pre-wrap px-1 text-sm text-muted-foreground\">{description}</figcaption>\n )}\n {editor.isEditable && (\n <div className=\"flex\">\n <Button size=\"xs\" variant=\"secondary\" onClick={openStudio}>\n <Icon name=\"i-lucide-wand-sparkles\" />\n {t('imageStudio.node.open')}\n </Button>\n </div>\n )}\n </div>\n {target && (\n <NodeStudio\n editor={editor}\n getPos={getPos}\n backend={target.backend}\n documentId={target.documentId}\n open={open}\n onOpenChange={setOpen}\n />\n )}\n </NodeViewWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAS,WAAW,EAAE,QAAQ,QAAQ,SAAS,YAAY,MAAM,gBAO9D;CACD,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,QAAQ,wBAAwB;EAAE;EAAQ;EAAQ;EAAS;CAAW,CAAC;CAC7E,OAAO,QACH,oBAAC,mBAAD;EAAyB;EAAoB;EAAqB;EAAO,OAAO,EAAE,8BAA8B;CAAI,CAAA,IACpH;AACN;;;;;;;AAQA,SAAgB,gBAAgB,EAAE,MAAM,UAAU,QAAQ,UAAyB;CACjF,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,SAAS,YAAY,MAAM;CAEjC,MAAM,cADS,0BAA0B,KAAK,MAAM,MAC3B,CAAC,CAAC,YAAY,KAAK;CAE5C,MAAM,CAAC,QAAQ,aAAa,SAAgF;CAC5G,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,SAAS,aAAa;EACpB,IAAI,CAAC,OAAO,YAAY;EACxB,MAAM,MAAM,cAAc,MAAM,CAAC,EAAE;EACnC,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;GACpB,OAAO;IAAE,IAAI;IAA4B,OAAO,EAAE,kCAAkC;IAAG,MAAM;GAAU,CAAC;GACxG;EACF;EACA,WAAU,aAAY,UAAU,YAAY,UAAU,WAAW;GAAE;GAAS,YAAY,IAAI,cAAA;EAAkC,CAAC;EAC/H,QAAQ,IAAI;CACd;CAGA,MAAM,SAAS,OAAoB,IAAI;CACvC,MAAM,UAAU,OAAO,UAAU;CACjC,QAAQ,UAAU;CAClB,gBAAgB;EACd,MAAM,MAAM,OAAO,SAAS;EAC5B,IAAI,CAAC,KAAK;EACV,MAAM,eAAe,QAAQ,QAAQ;EACrC,IAAI,iBAAiB,mBAAmB,MAAM;EAC9C,aAAa,IAAI,oBAAoB,mBAAmB,MAAM;CAChE,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,iBAAD;EACE,KAAK;EACL,IAAG;EACH,WAAW,GAAG,uCAAuC,YAAY,4CAA4C;EAC7G,2BAAA;EACA,eAAe;EALjB,UAAA,CAOE,qBAAC,OAAD;GAAK,WAAU;GAAsB,iBAAiB;GAAtD,UAAA;IACG,KAAK,MAAM,MACR,oBAAC,OAAD;KAAK,KAAK,KAAK,MAAM;KAAK,KAAK,KAAK,MAAM,OAAO;KAAa,WAAU;IAA6B,CAAA,IAEnG,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,MAAD;MAAM,MAAK;MAAsB,WAAU;KAAmC,CAAA,GAC7E,cACG,oBAAC,QAAD;MAAM,WAAU;MAA6C,UAAA;KAAkB,CAAA,IAC/E,oBAAC,QAAD;MAAM,WAAU;MAA4B,UAAA,EAAE,gCAAgC;KAAQ,CAAA,CACvF;;IAEV,KAAK,MAAM,OAAO,eACjB,oBAAC,cAAD;KAAY,WAAU;KAA0D,UAAA;IAAwB,CAAA;IAEzG,OAAO,cACN,oBAAC,OAAD;KAAK,WAAU;KACb,UAAA,qBAAC,QAAD;MAAQ,MAAK;MAAK,SAAQ;MAAY,SAAS;MAA/C,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,yBAA0B,CAAA,GACpC,EAAE,uBAAuB,CACpB;;IACL,CAAA;GAEJ;EACJ,CAAA,GAAA,UACC,oBAAC,YAAD;GACU;GACA;GACR,SAAS,OAAO;GAChB,YAAY,OAAO;GACb;GACN,cAAc;EACf,CAAA,CAEY;;AAErB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ImageStudioImage, ImageStudioPromptBlock } from "../../../core/backend-types.js";
|
|
2
|
+
import { ImageStudioState } from "../state.js";
|
|
3
|
+
import { ImageStudioNodeCoreOptions } from "../node-core.js";
|
|
4
|
+
//#region src/features/image-studio/react/store.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* What the React Bild-Studio dialog drives; a host supplies it. The same contract as the Vue `ImageStudioStore`, with
|
|
7
|
+
* plain values where Vue has refs: the host re-renders the dialog with a new `state` after `update`.
|
|
8
|
+
*
|
|
9
|
+
* The dialog applies the reducers of `../state` and hands the result to `update`; the store only persists it. The
|
|
10
|
+
* store's methods never touch `state` themselves:
|
|
11
|
+
* - `update` must lead to a new `state` (the dialog keeps working on its own latest value until `state` changes).
|
|
12
|
+
* - `generate` produces one image from the block's `state.description`, the active Prompt-Bausteine and the active
|
|
13
|
+
* images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;
|
|
14
|
+
* when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as
|
|
15
|
+
* `problem`. An aborted generation adds nothing.
|
|
16
|
+
* - `upload` stores the file and returns the image; the dialog then calls `update(addReference(state, image))`.
|
|
17
|
+
* - `problem` is a problem of the store itself (the Prompt-Bausteine could not be loaded, say); the dialog shows it as a notice.
|
|
18
|
+
*/
|
|
19
|
+
export interface ImageStudioReactStore {
|
|
20
|
+
readonly state: ImageStudioState;
|
|
21
|
+
/** The managed list of Prompt-Bausteine, loaded by the host. */
|
|
22
|
+
readonly promptBlocks: readonly ImageStudioPromptBlock[];
|
|
23
|
+
/** Called with the state after a reducer; the host persists it. */
|
|
24
|
+
update: (next: ImageStudioState) => void;
|
|
25
|
+
generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>;
|
|
26
|
+
upload: (file: File) => Promise<ImageStudioImage>;
|
|
27
|
+
readonly problem: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The `ImageStudioReactStore` of one `imageStudio` node (the React wrapper of `createImageStudioNodeCore`): `state`
|
|
31
|
+
* follows the node's `studio` attribute, `update` writes it back as one transaction, the rest goes to
|
|
32
|
+
* `backend.images`. The core lives for as long as the component and the `editor`, `backend` and `documentId` stay the
|
|
33
|
+
* same; the first render returns `undefined` (the core is built in an effect), so render the dialog once there is a store.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useImageStudioNodeStore(options: ImageStudioNodeCoreOptions): ImageStudioReactStore | undefined;
|
|
36
|
+
//#endregion
|
|
37
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createImageStudioNodeCore } from "../node-core.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/features/image-studio/react/store.ts
|
|
4
|
+
const noUnsubscribe = () => {};
|
|
5
|
+
/**
|
|
6
|
+
* The `ImageStudioReactStore` of one `imageStudio` node (the React wrapper of `createImageStudioNodeCore`): `state`
|
|
7
|
+
* follows the node's `studio` attribute, `update` writes it back as one transaction, the rest goes to
|
|
8
|
+
* `backend.images`. The core lives for as long as the component and the `editor`, `backend` and `documentId` stay the
|
|
9
|
+
* same; the first render returns `undefined` (the core is built in an effect), so render the dialog once there is a store.
|
|
10
|
+
*/
|
|
11
|
+
function useImageStudioNodeStore(options) {
|
|
12
|
+
const { editor, backend, documentId } = options;
|
|
13
|
+
const getPos = useRef(options.getPos);
|
|
14
|
+
getPos.current = options.getPos;
|
|
15
|
+
const [core, setCore] = useState();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const own = createImageStudioNodeCore({
|
|
18
|
+
editor,
|
|
19
|
+
backend,
|
|
20
|
+
documentId,
|
|
21
|
+
getPos: () => getPos.current()
|
|
22
|
+
});
|
|
23
|
+
setCore(own);
|
|
24
|
+
return () => {
|
|
25
|
+
own.dispose();
|
|
26
|
+
setCore(void 0);
|
|
27
|
+
};
|
|
28
|
+
}, [
|
|
29
|
+
editor,
|
|
30
|
+
backend,
|
|
31
|
+
documentId
|
|
32
|
+
]);
|
|
33
|
+
const subscribe = useCallback((listener) => core ? core.subscribe(listener) : noUnsubscribe, [core]);
|
|
34
|
+
const getSnapshot = useCallback(() => core?.get(), [core]);
|
|
35
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
36
|
+
return useMemo(() => core && snapshot ? {
|
|
37
|
+
...snapshot,
|
|
38
|
+
update: core.update,
|
|
39
|
+
generate: core.generate,
|
|
40
|
+
upload: core.upload
|
|
41
|
+
} : void 0, [core, snapshot]);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { useImageStudioNodeStore };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/features/image-studio/react/store.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'\nimport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions } from '../node-core'\nimport type { ImageStudioImage, ImageStudioPromptBlock, ImageStudioState } from '../state'\n\n/**\n * What the React Bild-Studio dialog drives; a host supplies it. The same contract as the Vue `ImageStudioStore`, with\n * plain values where Vue has refs: the host re-renders the dialog with a new `state` after `update`.\n *\n * The dialog applies the reducers of `../state` and hands the result to `update`; the store only persists it. The\n * store's methods never touch `state` themselves:\n * - `update` must lead to a new `state` (the dialog keeps working on its own latest value until `state` changes).\n * - `generate` produces one image from the block's `state.description`, the active Prompt-Bausteine and the active\n * images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;\n * when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as\n * `problem`. An aborted generation adds nothing.\n * - `upload` stores the file and returns the image; the dialog then calls `update(addReference(state, image))`.\n * - `problem` is a problem of the store itself (the Prompt-Bausteine could not be loaded, say); the dialog shows it as a notice.\n */\nexport interface ImageStudioReactStore {\n readonly state: ImageStudioState\n /** The managed list of Prompt-Bausteine, loaded by the host. */\n readonly promptBlocks: readonly ImageStudioPromptBlock[]\n /** Called with the state after a reducer; the host persists it. */\n update: (next: ImageStudioState) => void\n generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>\n upload: (file: File) => Promise<ImageStudioImage>\n readonly problem: string | undefined\n}\n\nconst noUnsubscribe = () => {}\n\n/**\n * The `ImageStudioReactStore` of one `imageStudio` node (the React wrapper of `createImageStudioNodeCore`): `state`\n * follows the node's `studio` attribute, `update` writes it back as one transaction, the rest goes to\n * `backend.images`. The core lives for as long as the component and the `editor`, `backend` and `documentId` stay the\n * same; the first render returns `undefined` (the core is built in an effect), so render the dialog once there is a store.\n */\nexport function useImageStudioNodeStore(options: ImageStudioNodeCoreOptions): ImageStudioReactStore | undefined {\n const { editor, backend, documentId } = options\n const getPos = useRef(options.getPos)\n getPos.current = options.getPos\n const [core, setCore] = useState<ImageStudioNodeCore>()\n useEffect(() => {\n const own = createImageStudioNodeCore({ editor, backend, documentId, getPos: () => getPos.current() })\n setCore(own)\n return () => {\n own.dispose()\n setCore(undefined)\n }\n }, [editor, backend, documentId])\n const subscribe = useCallback((listener: () => void) => core ? core.subscribe(listener) : noUnsubscribe, [core])\n const getSnapshot = useCallback(() => core?.get(), [core])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n return useMemo(\n () => core && snapshot ? { ...snapshot, update: core.update, generate: core.generate, upload: core.upload } : undefined,\n [core, snapshot],\n )\n}\n"],"mappings":";;;AA6BA,MAAM,sBAAsB,CAAC;;;;;;;AAQ7B,SAAgB,wBAAwB,SAAwE;CAC9G,MAAM,EAAE,QAAQ,SAAS,eAAe;CACxC,MAAM,SAAS,OAAO,QAAQ,MAAM;CACpC,OAAO,UAAU,QAAQ;CACzB,MAAM,CAAC,MAAM,WAAW,SAA8B;CACtD,gBAAgB;EACd,MAAM,MAAM,0BAA0B;GAAE;GAAQ;GAAS;GAAY,cAAc,OAAO,QAAQ;EAAE,CAAC;EACrG,QAAQ,GAAG;EACX,aAAa;GACX,IAAI,QAAQ;GACZ,QAAQ,KAAA,CAAS;EACnB;CACF,GAAG;EAAC;EAAQ;EAAS;CAAU,CAAC;CAChC,MAAM,YAAY,aAAa,aAAyB,OAAO,KAAK,UAAU,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC;CAC/G,MAAM,cAAc,kBAAkB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC;CACzD,MAAM,WAAW,qBAAqB,WAAW,aAAa,WAAW;CACzE,OAAO,cACC,QAAQ,WAAW;EAAE,GAAG;EAAU,QAAQ,KAAK;EAAQ,UAAU,KAAK;EAAU,QAAQ,KAAK;CAAO,IAAI,KAAA,GAC9G,CAAC,MAAM,QAAQ,CACjB;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
|
+
import { ReactFeature } from "../../react/EditorSurface.js";
|
|
3
|
+
import "../../react/index.js";
|
|
4
|
+
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
5
|
+
import { ImageStudioNodeCore, ImageStudioNodeCoreOptions, ImageStudioNodeSnapshot, createImageStudioNodeCore } from "./node-core.js";
|
|
6
|
+
import { ImageStudioReactStore, useImageStudioNodeStore } from "./react/store.js";
|
|
7
|
+
import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
8
|
+
//#region src/features/image-studio/react.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description
|
|
11
|
+
* placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry
|
|
12
|
+
* `bild-studio` and the action. Generation, upload and the Prompt-Bausteine need a backend with the `images`
|
|
13
|
+
* capability; without it the view says that generation is not set up.
|
|
14
|
+
*/
|
|
15
|
+
export declare function imageStudioFeature(): ReactFeature;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, type ImageStudioImage, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot, type ImageStudioPromptBlock, type ImageStudioReactStore, type ImageStudioState, type ImageStudioTurn, addGeneration, addReference, createImageStudioNodeCore, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
18
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
2
|
+
import { imageStudioFeatureCore } from "./feature.js";
|
|
3
|
+
import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
4
|
+
import { createImageStudioNodeCore } from "./node-core.js";
|
|
5
|
+
import { useImageStudioNodeStore } from "./react/store.js";
|
|
6
|
+
import { ImageStudioView } from "./react/ImageStudioView.js";
|
|
7
|
+
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
8
|
+
//#region src/features/image-studio/react.ts
|
|
9
|
+
/**
|
|
10
|
+
* The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description
|
|
11
|
+
* placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry
|
|
12
|
+
* `bild-studio` and the action. Generation, upload and the Prompt-Bausteine need a backend with the `images`
|
|
13
|
+
* capability; without it the view says that generation is not set up.
|
|
14
|
+
*/
|
|
15
|
+
function imageStudioFeature() {
|
|
16
|
+
return imageStudioFeatureCore({ nodeView: ReactNodeViewRenderer(ImageStudioView) });
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, addGeneration, addReference, createImageStudioNodeCore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../../../src/features/image-studio/react.ts"],"sourcesContent":["import { ReactNodeViewRenderer } from '@tiptap/react'\nimport type { ReactFeature } from '../../react'\nimport { imageStudioFeatureCore } from './feature'\nimport { ImageStudioView } from './react/ImageStudioView'\n\nexport { ImageStudioDialog } from './react/ImageStudioDialog'\nexport { type ImageStudioReactStore, useImageStudioNodeStore } from './react/store'\nexport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot } from './node-core'\nexport {\n addGeneration,\n addReference,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\n\n/**\n * The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description\n * placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry\n * `bild-studio` and the action. Generation, upload and the Prompt-Bausteine need a backend with the `images`\n * capability; without it the view says that generation is not set up.\n */\nexport function imageStudioFeature(): ReactFeature {\n return imageStudioFeatureCore({ nodeView: ReactNodeViewRenderer(ImageStudioView) })\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,SAAgB,qBAAmC;CACjD,OAAO,uBAAuB,EAAE,UAAU,sBAAsB,eAAe,EAAE,CAAC;AACpF"}
|
|
@@ -41,24 +41,24 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
41
41
|
settingsOpen: import("vue").WritableComputedRef<boolean, boolean>;
|
|
42
42
|
settingsTab: import("vue").WritableComputedRef<string | undefined, string | undefined>;
|
|
43
43
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
+
settingsOpen: (tab?: string | undefined) => any;
|
|
44
45
|
"update:sidebarOpen": (value: boolean) => any;
|
|
45
46
|
"update:commentsOpen": (value: boolean) => any;
|
|
46
|
-
ready: (editor: Editor) => any;
|
|
47
47
|
"update:modelValue": (markdown: string) => any;
|
|
48
|
+
frontmatter: (frontmatter: string | null) => any;
|
|
49
|
+
ready: (editor: Editor) => any;
|
|
48
50
|
metadataCreate: (list: string, item: MetadataItem) => any;
|
|
49
51
|
contentError: (error: Error) => any;
|
|
50
|
-
settingsOpen: (tab?: string | undefined) => any;
|
|
51
|
-
frontmatter: (frontmatter: string | null) => any;
|
|
52
52
|
diagnostics: (diagnostics: MarkdownDiagnostic[]) => any;
|
|
53
53
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
54
|
+
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
54
55
|
"onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
|
|
55
56
|
"onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
|
|
56
|
-
onReady?: ((editor: Editor) => any) | undefined;
|
|
57
57
|
"onUpdate:modelValue"?: ((markdown: string) => any) | undefined;
|
|
58
|
+
onFrontmatter?: ((frontmatter: string | null) => any) | undefined;
|
|
59
|
+
onReady?: ((editor: Editor) => any) | undefined;
|
|
58
60
|
onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
|
|
59
61
|
onContentError?: ((error: Error) => any) | undefined;
|
|
60
|
-
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
61
|
-
onFrontmatter?: ((frontmatter: string | null) => any) | undefined;
|
|
62
62
|
onDiagnostics?: ((diagnostics: MarkdownDiagnostic[]) => any) | undefined;
|
|
63
63
|
}>, {
|
|
64
64
|
modelValue: string;
|
|
@@ -11,9 +11,9 @@ export declare const chatBodySchema: z.ZodObject<{
|
|
|
11
11
|
feedback: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12
12
|
op: z.ZodString;
|
|
13
13
|
type: z.ZodEnum<{
|
|
14
|
-
add: "add";
|
|
15
|
-
delete: "delete";
|
|
16
14
|
update: "update";
|
|
15
|
+
delete: "delete";
|
|
16
|
+
add: "add";
|
|
17
17
|
convert: "convert";
|
|
18
18
|
}>;
|
|
19
19
|
target: z.ZodNullable<z.ZodString>;
|
|
@@ -71,24 +71,24 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
71
71
|
editor: import("vue").ComputedRef<Editor | undefined>;
|
|
72
72
|
preset: ComposedEditor<Component>;
|
|
73
73
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
74
|
+
settingsOpen: (tab?: string | undefined) => any;
|
|
74
75
|
"update:sidebarOpen": (value: boolean) => any;
|
|
75
76
|
"update:commentsOpen": (value: boolean) => any;
|
|
76
|
-
ready: (editor: Editor) => any;
|
|
77
77
|
"update:modelValue": (content: JSONContent) => any;
|
|
78
|
+
ready: (editor: Editor) => any;
|
|
78
79
|
metadataCreate: (list: string, item: MetadataItem) => any;
|
|
79
80
|
contentError: (error: Error) => any;
|
|
80
|
-
settingsOpen: (tab?: string | undefined) => any;
|
|
81
81
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
82
|
+
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
82
83
|
"onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
|
|
83
84
|
"onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
|
|
84
|
-
onReady?: ((editor: Editor) => any) | undefined;
|
|
85
85
|
"onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
|
|
86
|
+
onReady?: ((editor: Editor) => any) | undefined;
|
|
86
87
|
onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
|
|
87
88
|
onContentError?: ((error: Error) => any) | undefined;
|
|
88
|
-
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
89
89
|
}>, {
|
|
90
|
-
ai: boolean;
|
|
91
90
|
content: ContentLevel | readonly FeatureId[];
|
|
91
|
+
ai: boolean;
|
|
92
92
|
editable: boolean;
|
|
93
93
|
settingsUi: "internal" | "external";
|
|
94
94
|
interaction: InteractionLevel | readonly FeatureId[];
|
|
@@ -41,21 +41,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
41
41
|
editor: import("vue").ComputedRef<Editor | undefined>;
|
|
42
42
|
preset: EditorPreset<Component>;
|
|
43
43
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
+
settingsOpen: (tab?: string | undefined) => any;
|
|
44
45
|
"update:sidebarOpen": (value: boolean) => any;
|
|
45
46
|
"update:commentsOpen": (value: boolean) => any;
|
|
46
|
-
ready: (editor: Editor) => any;
|
|
47
47
|
"update:modelValue": (content: JSONContent) => any;
|
|
48
|
+
ready: (editor: Editor) => any;
|
|
48
49
|
metadataCreate: (list: string, item: MetadataItem) => any;
|
|
49
50
|
contentError: (error: Error) => any;
|
|
50
|
-
settingsOpen: (tab?: string | undefined) => any;
|
|
51
51
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
|
+
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
52
53
|
"onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
|
|
53
54
|
"onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
|
|
54
|
-
onReady?: ((editor: Editor) => any) | undefined;
|
|
55
55
|
"onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
|
|
56
|
+
onReady?: ((editor: Editor) => any) | undefined;
|
|
56
57
|
onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
|
|
57
58
|
onContentError?: ((error: Error) => any) | undefined;
|
|
58
|
-
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
59
59
|
}>, {
|
|
60
60
|
editable: boolean;
|
|
61
61
|
settingsUi: "internal" | "external";
|
|
@@ -43,21 +43,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
43
43
|
settingsOpen: import("vue").Ref<boolean, boolean>;
|
|
44
44
|
settingsTab: import("vue").Ref<string | undefined, string | undefined>;
|
|
45
45
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
46
|
+
settingsOpen: (tab?: string | undefined) => any;
|
|
46
47
|
"update:sidebarOpen": (value: boolean) => any;
|
|
47
48
|
"update:commentsOpen": (value: boolean) => any;
|
|
48
|
-
ready: (editor: Editor) => any;
|
|
49
49
|
"update:modelValue": (content: JSONContent) => any;
|
|
50
|
+
ready: (editor: Editor) => any;
|
|
50
51
|
metadataCreate: (list: string, item: MetadataItem) => any;
|
|
51
52
|
contentError: (error: Error) => any;
|
|
52
|
-
settingsOpen: (tab?: string | undefined) => any;
|
|
53
53
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
54
|
+
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
54
55
|
"onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
|
|
55
56
|
"onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
|
|
56
|
-
onReady?: ((editor: Editor) => any) | undefined;
|
|
57
57
|
"onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
|
|
58
|
+
onReady?: ((editor: Editor) => any) | undefined;
|
|
58
59
|
onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
|
|
59
60
|
onContentError?: ((error: Error) => any) | undefined;
|
|
60
|
-
onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
|
|
61
61
|
}>, {
|
|
62
62
|
editable: boolean;
|
|
63
63
|
settingsUi: "internal" | "external";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecsplico/editor",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vue and React editor library on TipTap v3 (Vue: Nuxt UI; React: shadcn/ui): framework-free features with Vue and React bindings, simple and advanced presets, tree-shakeable entries. Built ESM with type declarations.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -184,6 +184,11 @@
|
|
|
184
184
|
"types": "./dist/features/image-studio/vue.d.ts",
|
|
185
185
|
"import": "./dist/features/image-studio/vue.js"
|
|
186
186
|
},
|
|
187
|
+
"./image-studio/react": {
|
|
188
|
+
"skaile-source": "./src/features/image-studio/react.ts",
|
|
189
|
+
"types": "./dist/features/image-studio/react.d.ts",
|
|
190
|
+
"import": "./dist/features/image-studio/react.js"
|
|
191
|
+
},
|
|
187
192
|
"./image-studio/schema": {
|
|
188
193
|
"skaile-source": "./src/features/image-studio/schema.ts",
|
|
189
194
|
"types": "./dist/features/image-studio/schema.d.ts",
|