@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.
- package/dist/config/config.js +17 -0
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ContentTree.js +0 -1
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +5 -6
- package/dist/editor/ai/AiResponseMessage.js +8 -6
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +13 -3
- package/dist/editor/ai/AiTerminal.js +107 -71
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/ai/AiToolCall.d.ts +3 -7
- package/dist/editor/ai/AiToolCall.js +15 -3
- package/dist/editor/ai/AiToolCall.js.map +1 -1
- package/dist/editor/ai/GhostWriter.d.ts +1 -0
- package/dist/editor/ai/GhostWriter.js +307 -0
- package/dist/editor/ai/GhostWriter.js.map +1 -0
- package/dist/editor/client/EditorClient.js +3 -0
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearch.js +2 -2
- package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -2
- package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.js +124 -11
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +7 -0
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +600 -0
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -0
- package/dist/editor/page-viewer/PageViewer.js +6 -1
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +0 -1
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +6 -1
- package/dist/editor/services/aiService.js +2 -3
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/page-wizard/steps/CreatePage.js +5 -2
- package/dist/page-wizard/steps/CreatePage.js.map +1 -1
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +2 -1
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
- package/dist/page-wizard/steps/ImagesStep.js +1 -3
- package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
- package/dist/page-wizard/steps/LayoutStep.js +5 -5
- package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
- package/dist/page-wizard/steps/SelectStep.js +5 -5
- package/dist/page-wizard/steps/SelectStep.js.map +1 -1
- package/dist/styles.css +9 -0
- package/package.json +1 -1
- package/src/config/config.tsx +20 -2
- package/src/editor/ContentTree.tsx +0 -2
- package/src/editor/ai/AiResponseMessage.tsx +43 -19
- package/src/editor/ai/AiTerminal.tsx +153 -105
- package/src/editor/ai/AiToolCall.tsx +37 -21
- package/src/editor/ai/GhostWriter.tsx +432 -0
- package/src/editor/client/EditorClient.tsx +4 -0
- package/src/editor/client/editContext.ts +3 -0
- package/src/editor/media-selector/AiImageSearch.tsx +8 -9
- package/src/editor/media-selector/AiImageSearchPrompt.tsx +4 -5
- package/src/editor/page-editor-chrome/FrameMenu.tsx +1 -0
- package/src/editor/page-editor-chrome/InlineEditor.tsx +177 -18
- package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +734 -0
- package/src/editor/page-viewer/PageViewer.tsx +21 -6
- package/src/editor/page-viewer/PageViewerFrame.tsx +0 -1
- package/src/editor/services/aiService.ts +10 -6
- package/src/page-wizard/steps/CreatePage.tsx +21 -26
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +2 -2
- package/src/page-wizard/steps/ImagesStep.tsx +2 -5
- package/src/page-wizard/steps/LayoutStep.tsx +12 -13
- 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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
// don
|
|
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")!;
|