@pixerate/editor-react 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +55 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +61 -42
- package/dist/index.js.map +1 -1
- package/package.json +11 -8
package/dist/index.d.cts
CHANGED
|
@@ -11,7 +11,11 @@ export { ImageEditor, ImageEditorProps, UseImageEditorReturn, useImageEditor } f
|
|
|
11
11
|
import '@pixerate/editor/image-editor';
|
|
12
12
|
|
|
13
13
|
interface UsePromptEditorProps {
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The editor content as plain text. Changes from the parent (e.g. clearing
|
|
16
|
+
* after submit) are applied to the editor; echoes of the editor's own
|
|
17
|
+
* updates via `onContentChange` are ignored.
|
|
18
|
+
*/
|
|
15
19
|
content: string;
|
|
16
20
|
/** Callback fired when the editor content changes (returns plain text) */
|
|
17
21
|
onContentChange: (text: string) => void;
|
|
@@ -43,7 +47,10 @@ interface UsePromptEditorProps {
|
|
|
43
47
|
clearSlashCommand?: () => void;
|
|
44
48
|
/** Whether a slash command menu is currently active */
|
|
45
49
|
slashCommandActive?: boolean;
|
|
46
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Callback fired the first time each milestone is reached while the editor
|
|
52
|
+
* is mounted: `used_variable` ({variable}) and `used_magic` (__instruction__).
|
|
53
|
+
*/
|
|
47
54
|
onMilestone?: (milestone: string) => void;
|
|
48
55
|
/** DOM element ID of the container, used for positioning popovers */
|
|
49
56
|
containerId?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,11 @@ export { ImageEditor, ImageEditorProps, UseImageEditorReturn, useImageEditor } f
|
|
|
11
11
|
import '@pixerate/editor/image-editor';
|
|
12
12
|
|
|
13
13
|
interface UsePromptEditorProps {
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The editor content as plain text. Changes from the parent (e.g. clearing
|
|
16
|
+
* after submit) are applied to the editor; echoes of the editor's own
|
|
17
|
+
* updates via `onContentChange` are ignored.
|
|
18
|
+
*/
|
|
15
19
|
content: string;
|
|
16
20
|
/** Callback fired when the editor content changes (returns plain text) */
|
|
17
21
|
onContentChange: (text: string) => void;
|
|
@@ -43,7 +47,10 @@ interface UsePromptEditorProps {
|
|
|
43
47
|
clearSlashCommand?: () => void;
|
|
44
48
|
/** Whether a slash command menu is currently active */
|
|
45
49
|
slashCommandActive?: boolean;
|
|
46
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Callback fired the first time each milestone is reached while the editor
|
|
52
|
+
* is mounted: `used_variable` ({variable}) and `used_magic` (__instruction__).
|
|
53
|
+
*/
|
|
47
54
|
onMilestone?: (milestone: string) => void;
|
|
48
55
|
/** DOM element ID of the container, used for positioning popovers */
|
|
49
56
|
containerId?: string;
|
package/dist/index.js
CHANGED
|
@@ -8,18 +8,22 @@ import { useMemo, useEffect, useRef } from "react";
|
|
|
8
8
|
import { useEditor } from "@tiptap/react";
|
|
9
9
|
import StarterKit from "@tiptap/starter-kit";
|
|
10
10
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
11
|
-
import { Slice
|
|
11
|
+
import { Slice } from "@tiptap/pm/model";
|
|
12
12
|
import {
|
|
13
13
|
GradientText,
|
|
14
14
|
TemplateSuggestions,
|
|
15
15
|
LoadingNode,
|
|
16
16
|
plainTextToTipTapHtml,
|
|
17
|
-
|
|
17
|
+
plainTextToSlice,
|
|
18
|
+
getEditorText,
|
|
19
|
+
tokenizePrompt
|
|
18
20
|
} from "@pixerate/editor";
|
|
19
21
|
var DEFAULT_TEMPLATES = [];
|
|
20
22
|
var DEFAULT_COLOR_MAP = /* @__PURE__ */ new Map([
|
|
21
23
|
["default", { from: "#f43f5e", to: "#8b5cf6" }]
|
|
22
24
|
]);
|
|
25
|
+
var DEFAULT_CLASS_NAME = "w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words";
|
|
26
|
+
var normalizeNewlines = (text) => text.replace(/\r\n?/g, "\n");
|
|
23
27
|
function usePromptEditor({
|
|
24
28
|
content,
|
|
25
29
|
onContentChange,
|
|
@@ -60,6 +64,7 @@ function usePromptEditor({
|
|
|
60
64
|
const templateKey = useMemo(() => {
|
|
61
65
|
return (templates || []).map((t) => `${t.id || t.name}:${t.latestVersion || ""}`).sort().join(",");
|
|
62
66
|
}, [templates]);
|
|
67
|
+
const colorMapKey = userColorMap ? JSON.stringify(Array.from(userColorMap.entries())) : "";
|
|
63
68
|
const resolvedColorMap = useMemo(() => {
|
|
64
69
|
if (userColorMap) return userColorMap;
|
|
65
70
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -67,7 +72,9 @@ function usePromptEditor({
|
|
|
67
72
|
map.set(t.name, { from: "#f43f5e", to: "#8b5cf6" });
|
|
68
73
|
});
|
|
69
74
|
return map.size > 0 ? map : DEFAULT_COLOR_MAP;
|
|
70
|
-
}, [
|
|
75
|
+
}, [colorMapKey, templateKey]);
|
|
76
|
+
const jsonVariablesKey = (jsonVariables || []).join("\0");
|
|
77
|
+
const reachedMilestonesRef = useRef(/* @__PURE__ */ new Set());
|
|
71
78
|
const StarterKitExt = StarterKit?.configure ? StarterKit : StarterKit?.default?.configure ? StarterKit?.default : StarterKit;
|
|
72
79
|
const PlaceholderExt = Placeholder?.configure ? Placeholder : Placeholder?.default?.configure ? Placeholder?.default : Placeholder;
|
|
73
80
|
const editor = useEditor(
|
|
@@ -97,11 +104,13 @@ function usePromptEditor({
|
|
|
97
104
|
const plainText = getEditorText(ed);
|
|
98
105
|
onContentChangeRef.current?.(plainText);
|
|
99
106
|
if (onMilestoneRef.current) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
const reached = reachedMilestonesRef.current;
|
|
108
|
+
for (const token of tokenizePrompt(plainText)) {
|
|
109
|
+
const milestone = token.type === "variable" ? "used_variable" : token.type === "instruction" ? "used_magic" : null;
|
|
110
|
+
if (milestone && !reached.has(milestone)) {
|
|
111
|
+
reached.add(milestone);
|
|
112
|
+
onMilestoneRef.current(milestone);
|
|
113
|
+
}
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
const { state, view } = ed;
|
|
@@ -139,7 +148,7 @@ function usePromptEditor({
|
|
|
139
148
|
},
|
|
140
149
|
editorProps: {
|
|
141
150
|
attributes: {
|
|
142
|
-
class: className ||
|
|
151
|
+
class: className || DEFAULT_CLASS_NAME,
|
|
143
152
|
role: "textbox",
|
|
144
153
|
"aria-label": "Prompt editor"
|
|
145
154
|
},
|
|
@@ -171,51 +180,61 @@ function usePromptEditor({
|
|
|
171
180
|
event.preventDefault();
|
|
172
181
|
const schema = view.state.schema;
|
|
173
182
|
if (!schema) return false;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const nodeJson = { type: "paragraph" };
|
|
178
|
-
if (line.length > 0) {
|
|
179
|
-
nodeJson.content = [{ type: "text", text: line }];
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
const node = ProsemirrorNode.fromJSON(schema, nodeJson);
|
|
183
|
-
nodes.push(node);
|
|
184
|
-
} catch {
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
const parsedSlice = Slice.maxOpen(Fragment.fromArray(nodes));
|
|
188
|
-
const tr = view.state.tr.replaceSelection(parsedSlice);
|
|
189
|
-
view.dispatch(tr);
|
|
183
|
+
view.dispatch(
|
|
184
|
+
view.state.tr.replaceSelection(plainTextToSlice(text, schema))
|
|
185
|
+
);
|
|
190
186
|
return true;
|
|
191
187
|
},
|
|
192
188
|
clipboardTextParser: (text, context, _plain, view) => {
|
|
193
189
|
const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema;
|
|
194
190
|
if (!schema) return Slice.empty;
|
|
195
|
-
|
|
196
|
-
const nodes = [];
|
|
197
|
-
blocks.forEach((line) => {
|
|
198
|
-
const nodeJson = { type: "paragraph" };
|
|
199
|
-
if (line.length > 0) {
|
|
200
|
-
nodeJson.content = [{ type: "text", text: line }];
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const node = ProsemirrorNode.fromJSON(schema, nodeJson);
|
|
204
|
-
nodes.push(node);
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
return Slice.maxOpen(Fragment.fromArray(nodes));
|
|
191
|
+
return plainTextToSlice(text, schema);
|
|
209
192
|
}
|
|
210
193
|
}
|
|
211
194
|
},
|
|
212
|
-
|
|
195
|
+
// Only options baked into extensions recreate the editor; editable state,
|
|
196
|
+
// placeholder, class names and content are applied to the live instance.
|
|
197
|
+
[resolvedColorMap, templateKey, speed, jsonVariablesKey]
|
|
213
198
|
);
|
|
214
199
|
useEffect(() => {
|
|
215
200
|
if (editor && !editor.isDestroyed) {
|
|
216
201
|
editor.setEditable(isEditing);
|
|
217
202
|
}
|
|
218
203
|
}, [editor, isEditing]);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!editor || editor.isDestroyed) return;
|
|
206
|
+
if (normalizeNewlines(content) === getEditorText(editor)) return;
|
|
207
|
+
const { from, to } = editor.state.selection;
|
|
208
|
+
editor.commands.setContent(plainTextToTipTapHtml(content), false);
|
|
209
|
+
const max = editor.state.doc.content.size;
|
|
210
|
+
editor.commands.setTextSelection({
|
|
211
|
+
from: Math.min(from, max),
|
|
212
|
+
to: Math.min(to, max)
|
|
213
|
+
});
|
|
214
|
+
}, [editor, content]);
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (!editor || editor.isDestroyed) return;
|
|
217
|
+
editor.setOptions({
|
|
218
|
+
editorProps: {
|
|
219
|
+
...editor.options.editorProps,
|
|
220
|
+
attributes: {
|
|
221
|
+
...editor.options.editorProps.attributes,
|
|
222
|
+
class: className || DEFAULT_CLASS_NAME
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}, [editor, className]);
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!editor || editor.isDestroyed) return;
|
|
229
|
+
const placeholderExt = editor.extensionManager.extensions.find(
|
|
230
|
+
(ext) => ext.name === "placeholder"
|
|
231
|
+
);
|
|
232
|
+
if (!placeholderExt || placeholderExt.options.placeholder === placeholder) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
placeholderExt.options.placeholder = placeholder;
|
|
236
|
+
editor.view.dispatch(editor.state.tr);
|
|
237
|
+
}, [editor, placeholder]);
|
|
219
238
|
return editor;
|
|
220
239
|
}
|
|
221
240
|
|
|
@@ -518,7 +537,7 @@ var BubbleMenu = ({
|
|
|
518
537
|
// src/components/TemplateRenderer.tsx
|
|
519
538
|
import { useMemo as useMemo3 } from "react";
|
|
520
539
|
import {
|
|
521
|
-
tokenizePrompt,
|
|
540
|
+
tokenizePrompt as tokenizePrompt2,
|
|
522
541
|
resolveTemplates
|
|
523
542
|
} from "@pixerate/editor";
|
|
524
543
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -536,7 +555,7 @@ var TemplateRenderer = ({
|
|
|
536
555
|
return resolveTemplates(content, templates);
|
|
537
556
|
}, [content, templates, resolve]);
|
|
538
557
|
const tokens = useMemo3(() => {
|
|
539
|
-
return
|
|
558
|
+
return tokenizePrompt2(displayText);
|
|
540
559
|
}, [displayText]);
|
|
541
560
|
return /* @__PURE__ */ jsx2("div", { className, children: tokens.map((token, index) => {
|
|
542
561
|
if (token.type === "template") {
|