@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 CHANGED
@@ -64,6 +64,8 @@ var DEFAULT_TEMPLATES = [];
64
64
  var DEFAULT_COLOR_MAP = /* @__PURE__ */ new Map([
65
65
  ["default", { from: "#f43f5e", to: "#8b5cf6" }]
66
66
  ]);
67
+ 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";
68
+ var normalizeNewlines = (text) => text.replace(/\r\n?/g, "\n");
67
69
  function usePromptEditor({
68
70
  content,
69
71
  onContentChange,
@@ -104,6 +106,7 @@ function usePromptEditor({
104
106
  const templateKey = (0, import_react.useMemo)(() => {
105
107
  return (templates || []).map((t) => `${t.id || t.name}:${t.latestVersion || ""}`).sort().join(",");
106
108
  }, [templates]);
109
+ const colorMapKey = userColorMap ? JSON.stringify(Array.from(userColorMap.entries())) : "";
107
110
  const resolvedColorMap = (0, import_react.useMemo)(() => {
108
111
  if (userColorMap) return userColorMap;
109
112
  const map = /* @__PURE__ */ new Map();
@@ -111,7 +114,9 @@ function usePromptEditor({
111
114
  map.set(t.name, { from: "#f43f5e", to: "#8b5cf6" });
112
115
  });
113
116
  return map.size > 0 ? map : DEFAULT_COLOR_MAP;
114
- }, [userColorMap, templateKey]);
117
+ }, [colorMapKey, templateKey]);
118
+ const jsonVariablesKey = (jsonVariables || []).join("\0");
119
+ const reachedMilestonesRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
115
120
  const StarterKitExt = import_starter_kit.default?.configure ? import_starter_kit.default : import_starter_kit.default?.default?.configure ? import_starter_kit.default?.default : import_starter_kit.default;
116
121
  const PlaceholderExt = import_extension_placeholder.default?.configure ? import_extension_placeholder.default : import_extension_placeholder.default?.default?.configure ? import_extension_placeholder.default?.default : import_extension_placeholder.default;
117
122
  const editor = (0, import_react2.useEditor)(
@@ -141,11 +146,13 @@ function usePromptEditor({
141
146
  const plainText = (0, import_editor.getEditorText)(ed);
142
147
  onContentChangeRef.current?.(plainText);
143
148
  if (onMilestoneRef.current) {
144
- if (plainText.match(/(?<!\{)\{([^{}]+)\}(?!\})/g)) {
145
- onMilestoneRef.current("used_variable");
146
- }
147
- if (plainText.match(/__([^_]+)__/g)) {
148
- onMilestoneRef.current("used_magic");
149
+ const reached = reachedMilestonesRef.current;
150
+ for (const token of (0, import_editor.tokenizePrompt)(plainText)) {
151
+ const milestone = token.type === "variable" ? "used_variable" : token.type === "instruction" ? "used_magic" : null;
152
+ if (milestone && !reached.has(milestone)) {
153
+ reached.add(milestone);
154
+ onMilestoneRef.current(milestone);
155
+ }
149
156
  }
150
157
  }
151
158
  const { state, view } = ed;
@@ -183,7 +190,7 @@ function usePromptEditor({
183
190
  },
184
191
  editorProps: {
185
192
  attributes: {
186
- class: className || "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",
193
+ class: className || DEFAULT_CLASS_NAME,
187
194
  role: "textbox",
188
195
  "aria-label": "Prompt editor"
189
196
  },
@@ -215,51 +222,61 @@ function usePromptEditor({
215
222
  event.preventDefault();
216
223
  const schema = view.state.schema;
217
224
  if (!schema) return false;
218
- const blocks = text.replace(/\r\n?/g, "\n").split("\n");
219
- const nodes = [];
220
- blocks.forEach((line) => {
221
- const nodeJson = { type: "paragraph" };
222
- if (line.length > 0) {
223
- nodeJson.content = [{ type: "text", text: line }];
224
- }
225
- try {
226
- const node = import_model.Node.fromJSON(schema, nodeJson);
227
- nodes.push(node);
228
- } catch {
229
- }
230
- });
231
- const parsedSlice = import_model.Slice.maxOpen(import_model.Fragment.fromArray(nodes));
232
- const tr = view.state.tr.replaceSelection(parsedSlice);
233
- view.dispatch(tr);
225
+ view.dispatch(
226
+ view.state.tr.replaceSelection((0, import_editor.plainTextToSlice)(text, schema))
227
+ );
234
228
  return true;
235
229
  },
236
230
  clipboardTextParser: (text, context, _plain, view) => {
237
231
  const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema;
238
232
  if (!schema) return import_model.Slice.empty;
239
- const blocks = text.replace(/\r\n?/g, "\n").split("\n");
240
- const nodes = [];
241
- blocks.forEach((line) => {
242
- const nodeJson = { type: "paragraph" };
243
- if (line.length > 0) {
244
- nodeJson.content = [{ type: "text", text: line }];
245
- }
246
- try {
247
- const node = import_model.Node.fromJSON(schema, nodeJson);
248
- nodes.push(node);
249
- } catch {
250
- }
251
- });
252
- return import_model.Slice.maxOpen(import_model.Fragment.fromArray(nodes));
233
+ return (0, import_editor.plainTextToSlice)(text, schema);
253
234
  }
254
235
  }
255
236
  },
256
- [resolvedColorMap, templateKey, isEditing, placeholder, speed]
237
+ // Only options baked into extensions recreate the editor; editable state,
238
+ // placeholder, class names and content are applied to the live instance.
239
+ [resolvedColorMap, templateKey, speed, jsonVariablesKey]
257
240
  );
258
241
  (0, import_react.useEffect)(() => {
259
242
  if (editor && !editor.isDestroyed) {
260
243
  editor.setEditable(isEditing);
261
244
  }
262
245
  }, [editor, isEditing]);
246
+ (0, import_react.useEffect)(() => {
247
+ if (!editor || editor.isDestroyed) return;
248
+ if (normalizeNewlines(content) === (0, import_editor.getEditorText)(editor)) return;
249
+ const { from, to } = editor.state.selection;
250
+ editor.commands.setContent((0, import_editor.plainTextToTipTapHtml)(content), false);
251
+ const max = editor.state.doc.content.size;
252
+ editor.commands.setTextSelection({
253
+ from: Math.min(from, max),
254
+ to: Math.min(to, max)
255
+ });
256
+ }, [editor, content]);
257
+ (0, import_react.useEffect)(() => {
258
+ if (!editor || editor.isDestroyed) return;
259
+ editor.setOptions({
260
+ editorProps: {
261
+ ...editor.options.editorProps,
262
+ attributes: {
263
+ ...editor.options.editorProps.attributes,
264
+ class: className || DEFAULT_CLASS_NAME
265
+ }
266
+ }
267
+ });
268
+ }, [editor, className]);
269
+ (0, import_react.useEffect)(() => {
270
+ if (!editor || editor.isDestroyed) return;
271
+ const placeholderExt = editor.extensionManager.extensions.find(
272
+ (ext) => ext.name === "placeholder"
273
+ );
274
+ if (!placeholderExt || placeholderExt.options.placeholder === placeholder) {
275
+ return;
276
+ }
277
+ placeholderExt.options.placeholder = placeholder;
278
+ editor.view.dispatch(editor.state.tr);
279
+ }, [editor, placeholder]);
263
280
  return editor;
264
281
  }
265
282