@alpaca-editor/core 1.0.3831 → 1.0.3833

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.
Files changed (71) hide show
  1. package/dist/config/config.js +17 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/ContentTree.js +0 -1
  4. package/dist/editor/ContentTree.js.map +1 -1
  5. package/dist/editor/ai/AiResponseMessage.d.ts +5 -6
  6. package/dist/editor/ai/AiResponseMessage.js +8 -6
  7. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  8. package/dist/editor/ai/AiTerminal.d.ts +13 -3
  9. package/dist/editor/ai/AiTerminal.js +107 -71
  10. package/dist/editor/ai/AiTerminal.js.map +1 -1
  11. package/dist/editor/ai/AiToolCall.d.ts +3 -7
  12. package/dist/editor/ai/AiToolCall.js +15 -3
  13. package/dist/editor/ai/AiToolCall.js.map +1 -1
  14. package/dist/editor/ai/GhostWriter.d.ts +1 -0
  15. package/dist/editor/ai/GhostWriter.js +307 -0
  16. package/dist/editor/ai/GhostWriter.js.map +1 -0
  17. package/dist/editor/client/EditorClient.js +3 -0
  18. package/dist/editor/client/EditorClient.js.map +1 -1
  19. package/dist/editor/client/editContext.d.ts +2 -0
  20. package/dist/editor/client/editContext.js.map +1 -1
  21. package/dist/editor/media-selector/AiImageSearch.js +2 -2
  22. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  23. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -2
  24. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  25. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  26. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  27. package/dist/editor/page-editor-chrome/InlineEditor.js +124 -11
  28. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  29. package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +7 -0
  30. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +600 -0
  31. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -0
  32. package/dist/editor/page-viewer/PageViewer.js +6 -1
  33. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  34. package/dist/editor/page-viewer/PageViewerFrame.js +0 -1
  35. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  36. package/dist/editor/services/aiService.d.ts +6 -1
  37. package/dist/editor/services/aiService.js +2 -3
  38. package/dist/editor/services/aiService.js.map +1 -1
  39. package/dist/page-wizard/steps/CreatePage.js +5 -2
  40. package/dist/page-wizard/steps/CreatePage.js.map +1 -1
  41. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -1
  42. package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
  43. package/dist/page-wizard/steps/ImagesStep.js +1 -3
  44. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  45. package/dist/page-wizard/steps/LayoutStep.js +5 -5
  46. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  47. package/dist/page-wizard/steps/SelectStep.js +5 -5
  48. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  49. package/dist/styles.css +9 -0
  50. package/package.json +1 -1
  51. package/src/config/config.tsx +20 -2
  52. package/src/editor/ContentTree.tsx +0 -2
  53. package/src/editor/ai/AiResponseMessage.tsx +43 -19
  54. package/src/editor/ai/AiTerminal.tsx +153 -105
  55. package/src/editor/ai/AiToolCall.tsx +37 -21
  56. package/src/editor/ai/GhostWriter.tsx +432 -0
  57. package/src/editor/client/EditorClient.tsx +4 -0
  58. package/src/editor/client/editContext.ts +3 -0
  59. package/src/editor/media-selector/AiImageSearch.tsx +8 -9
  60. package/src/editor/media-selector/AiImageSearchPrompt.tsx +4 -5
  61. package/src/editor/page-editor-chrome/FrameMenu.tsx +1 -0
  62. package/src/editor/page-editor-chrome/InlineEditor.tsx +177 -18
  63. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +734 -0
  64. package/src/editor/page-viewer/PageViewer.tsx +21 -6
  65. package/src/editor/page-viewer/PageViewerFrame.tsx +0 -1
  66. package/src/editor/services/aiService.ts +10 -6
  67. package/src/page-wizard/steps/CreatePage.tsx +21 -26
  68. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +2 -2
  69. package/src/page-wizard/steps/ImagesStep.tsx +2 -5
  70. package/src/page-wizard/steps/LayoutStep.tsx +12 -13
  71. package/src/page-wizard/steps/SelectStep.tsx +12 -13
@@ -9,6 +9,9 @@ import { getFieldDescriptorFromElement, hasFieldLock } from "../utils";
9
9
  import { PageViewContext } from "../page-viewer/pageViewContext";
10
10
  import { applyPatch, diffWords } from "diff";
11
11
  import { createPatch } from "diff";
12
+ // import { useDebouncedCallback } from "use-debounce";
13
+ // import { executePrompt } from "../services/aiService";
14
+ import { useInlineAiCompletion } from "./useInlineAICompletion";
12
15
 
13
16
  export function InlineEditor({
14
17
  pageViewContext,
@@ -20,10 +23,20 @@ export function InlineEditor({
20
23
  const context = useEditContext();
21
24
  const contextRef = useEditContextRef();
22
25
  const modifiedFieldsContext = useModifiedFieldsContext();
23
-
26
+ const [cursorSpanId] = useState(
27
+ () => `cursor-indicator-${Math.random().toString(36).substring(2, 9)}`,
28
+ );
29
+ const isUpdatingRef = useRef(false);
24
30
  if (!context) return;
25
31
 
26
32
  const mutationObserverRef = useRef<MutationObserver | null>(null);
33
+ const selectionChangeListenerRef = useRef<() => void | null>(null);
34
+
35
+ const refreshCompletion = useInlineAiCompletion({
36
+ pageViewContext,
37
+ cursorSpanId,
38
+ isUpdatingRef,
39
+ });
27
40
 
28
41
  const isTextFieldType = (fieldType: string) => {
29
42
  const fieldTypeLower = fieldType.toLowerCase();
@@ -36,6 +49,31 @@ export function InlineEditor({
36
49
 
37
50
  const debouncedSetFieldvalue = useThrottledCallback(
38
51
  (value, fieldId, fieldName, itemId, language, version) => {
52
+ // Don't include our cursor indicator in the saved value
53
+ const iframeDocument =
54
+ pageViewContext.editorIframeRef.current?.contentWindow?.document;
55
+ const element = context.inlineEditingFieldElement;
56
+ const isRichText = element?.getAttribute("data-is-richtext") === "true";
57
+
58
+ // Clone element to avoid modifying the original
59
+ if (element && iframeDocument) {
60
+ const clone = element.cloneNode(true) as HTMLElement;
61
+ const cursorElem = clone.querySelector(`#${cursorSpanId}`);
62
+ if (cursorElem) {
63
+ cursorElem.parentNode?.removeChild(cursorElem);
64
+ }
65
+
66
+ // Remove any zero-width spaces we might have added
67
+ const textNodes = Array.from(clone.childNodes).filter(
68
+ (node) =>
69
+ node.nodeType === Node.TEXT_NODE && node.textContent === "\u200B",
70
+ );
71
+ textNodes.forEach((node) => node.parentNode?.removeChild(node));
72
+
73
+ value = isRichText ? clone.innerHTML : clone.innerText;
74
+ console.log("value", value);
75
+ }
76
+
39
77
  var modifiedFieldValue = modifiedFieldsContext?.modifiedFields.find(
40
78
  (x) =>
41
79
  x.fieldId === fieldId &&
@@ -78,6 +116,14 @@ export function InlineEditor({
78
116
  });
79
117
 
80
118
  if (element) {
119
+ // Clean up any existing cursor indicators
120
+ const iframeDocument =
121
+ pageViewContext.editorIframeRef.current?.contentWindow?.document;
122
+ const existingCursors = iframeDocument?.querySelectorAll(
123
+ '[data-cursor-indicator="true"]',
124
+ );
125
+ existingCursors?.forEach((el) => el.parentNode?.removeChild(el));
126
+
81
127
  const fieldId = element.getAttribute("data-fieldid");
82
128
  const fieldName = element.getAttribute("data-fieldname");
83
129
  const itemId = element.getAttribute("data-itemid");
@@ -98,28 +144,128 @@ export function InlineEditor({
98
144
 
99
145
  element.setAttribute("contenteditable", "true");
100
146
 
101
- mutationObserverRef.current = new MutationObserver(() => {
102
- debouncedSetFieldvalue(
103
- isRichText ? element.innerHTML : element.innerText,
104
- fieldId,
105
- fieldName,
106
- itemId,
107
- language,
108
- version,
109
- );
110
- // getCompletionDebounced();
147
+ const iframeWindow =
148
+ pageViewContext.editorIframeRef.current?.contentWindow;
149
+
150
+ // Setup a more robust mutation observer
151
+ mutationObserverRef.current = new MutationObserver((mutations) => {
152
+ // If we're already processing an update, don't trigger another one
153
+
154
+ if (isUpdatingRef.current) return;
155
+
156
+ // Check if mutations only affect our cursor span
157
+ const hasRealChanges = mutations.some((mutation) => {
158
+ // Skip our cursor span
159
+ if (
160
+ mutation.target.nodeType === Node.ELEMENT_NODE &&
161
+ (mutation.target as Element).id === cursorSpanId
162
+ ) {
163
+ console.log("skipping cursor span");
164
+ return false;
165
+ }
166
+
167
+ // Skip mutations where target is our span
168
+ if (
169
+ mutation.target.nodeType === Node.ELEMENT_NODE &&
170
+ (mutation.target as Element).getAttribute &&
171
+ (mutation.target as Element).getAttribute(
172
+ "data-cursor-indicator",
173
+ ) === "true"
174
+ ) {
175
+ console.log("skipping cursor span 2");
176
+ return false;
177
+ }
178
+
179
+ // Skip if all added/removed nodes are just our cursor span
180
+ if (mutation.type === "childList") {
181
+ const nonCursorChanges = Array.from(mutation.addedNodes)
182
+ .concat(Array.from(mutation.removedNodes))
183
+ .filter((node) => {
184
+ return (
185
+ node.nodeType !== Node.ELEMENT_NODE ||
186
+ !(node as Element).id ||
187
+ (node as Element).id !== cursorSpanId
188
+ );
189
+ });
190
+ return nonCursorChanges.length > 0;
191
+ }
192
+
193
+ return true;
194
+ });
195
+
196
+ if (hasRealChanges) {
197
+ debouncedSetFieldvalue(
198
+ isRichText ? element.innerHTML : element.innerText,
199
+ fieldId,
200
+ fieldName,
201
+ itemId,
202
+ language,
203
+ version,
204
+ );
205
+ }
206
+
207
+ // Always update cursor position after any mutation,
208
+ // but delay to let the mutation processing complete
209
+ // if (!isUpdatingRef.current) {
210
+ // setTimeout(updateCursorSpan, 0);
211
+ // // updateCompletion();
212
+ // }
111
213
  });
112
214
 
113
215
  setTimeout(() => {
114
216
  element.focus();
115
217
  }, 50);
116
218
 
117
- mutationObserverRef.current.observe(element, {
118
- childList: true, // observe direct children changes
119
- subtree: true, // observe all descendants changes
120
- characterData: true, // observe text changes
121
- //attributes: true, // observe attribute changes (like style or class)
122
- });
219
+ // Initial cursor placement
220
+ // setTimeout(updateCursorSpan, 10);
221
+
222
+ // Add listener for selection changes
223
+ if (iframeWindow) {
224
+ const selectionChangeHandler = () => {
225
+ // Only update if we're not already updating
226
+ // if (!isUpdatingRef.current) {
227
+ // setTimeout(updateCursorSpan, 0);
228
+ // }
229
+ };
230
+
231
+ iframeWindow.document.addEventListener(
232
+ "selectionchange",
233
+ selectionChangeHandler,
234
+ );
235
+ selectionChangeListenerRef.current = () => {
236
+ iframeWindow.document.removeEventListener(
237
+ "selectionchange",
238
+ selectionChangeHandler,
239
+ );
240
+ };
241
+
242
+ mutationObserverRef.current.observe(element, {
243
+ childList: true, // observe direct children changes
244
+ subtree: true, // observe all descendants changes
245
+ characterData: true, // observe text changes
246
+ });
247
+
248
+ // Cleanup
249
+ return () => {
250
+ if (mutationObserverRef.current) {
251
+ mutationObserverRef.current.disconnect();
252
+ mutationObserverRef.current = null;
253
+ }
254
+
255
+ if (selectionChangeListenerRef.current) {
256
+ selectionChangeListenerRef.current();
257
+ selectionChangeListenerRef.current = null;
258
+ }
259
+
260
+ // iframeWindow.document.removeEventListener("keydown", keyHandler);
261
+
262
+ // Clean up any cursor indicators on unmount
263
+ const cursorElement = iframeDocument?.getElementById(cursorSpanId);
264
+ if (cursorElement) {
265
+ cursorElement.parentNode?.removeChild(cursorElement);
266
+ }
267
+ };
268
+ }
123
269
  }
124
270
 
125
271
  async function updateFocusedFieldContent() {
@@ -222,6 +368,19 @@ export function InlineEditor({
222
368
  mutationObserverRef.current.disconnect();
223
369
  mutationObserverRef.current = null;
224
370
  }
371
+
372
+ if (selectionChangeListenerRef.current) {
373
+ selectionChangeListenerRef.current();
374
+ selectionChangeListenerRef.current = null;
375
+ }
376
+
377
+ // Clean up any cursor indicators on unmount
378
+ const iframeDocument =
379
+ pageViewContext.editorIframeRef.current?.contentWindow?.document;
380
+ const cursorElement = iframeDocument?.getElementById(cursorSpanId);
381
+ if (cursorElement) {
382
+ cursorElement.parentNode?.removeChild(cursorElement);
383
+ }
225
384
  };
226
385
  }, [context?.inlineEditingFieldElement]);
227
386
 
@@ -558,7 +717,7 @@ export function InlineEditor({
558
717
 
559
718
  allFieldEls.forEach(async (el) => {
560
719
  if (context.mode === "suggestions" || context.showSuggestedEdits) return;
561
- // dont stomp on the one thats live‑editing
720
+ // don't stomp on the one that's live‑editing
562
721
  if (el === context.inlineEditingFieldElement) return;
563
722
 
564
723
  const fieldId = el.getAttribute("data-fieldid")!;