@parhelia/core 0.1.12390 → 0.1.12397
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/editor/Editor.js +32 -17
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/PictureCropper.js +9 -4
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/PictureEditor.js +12 -13
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/SetupWizard.js +20 -2
- package/dist/editor/SetupWizard.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +14 -3
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +1 -1
- package/dist/editor/client/editContext.d.ts +4 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +283 -298
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
- package/dist/editor/pictureRawValue.d.ts +3 -0
- package/dist/editor/pictureRawValue.js +30 -0
- package/dist/editor/pictureRawValue.js.map +1 -0
- package/dist/editor/services/templateBuilderService.d.ts +7 -0
- package/dist/editor/services/templateBuilderService.js +7 -1
- package/dist/editor/services/templateBuilderService.js.map +1 -1
- package/dist/editor/settings/About.js +25 -19
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/panels/AgentProfileEditorPanel.d.ts +14 -0
- package/dist/editor/settings/panels/AgentProfileEditorPanel.js +7 -0
- package/dist/editor/settings/panels/AgentProfileEditorPanel.js.map +1 -0
- package/dist/editor/settings/panels/AgentsPanel.js +2 -2
- package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js +146 -8
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
- package/dist/editor/setup-wizard/steps/LicenseActivationStep.d.ts +9 -0
- package/dist/editor/setup-wizard/steps/LicenseActivationStep.js +160 -0
- package/dist/editor/setup-wizard/steps/LicenseActivationStep.js.map +1 -0
- package/dist/editor/setup-wizard/steps/LicenseEmailStep.d.ts +10 -0
- package/dist/editor/setup-wizard/steps/LicenseEmailStep.js +101 -0
- package/dist/editor/setup-wizard/steps/LicenseEmailStep.js.map +1 -0
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +422 -65
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
- package/dist/licensing/EmailEntry.js +1 -1
- package/dist/licensing/EmailEntry.js.map +1 -1
- package/dist/licensing/LicenseActivationForm.js +1 -1
- package/dist/licensing/LicenseActivationForm.js.map +1 -1
- package/dist/licensing/LicenseCodeEntry.js +2 -2
- package/dist/licensing/LicenseCodeEntry.js.map +1 -1
- package/dist/licensing/LicenseContext.js +18 -9
- package/dist/licensing/LicenseContext.js.map +1 -1
- package/dist/licensing/LicenseOverlay.js +2 -1
- package/dist/licensing/LicenseOverlay.js.map +1 -1
- package/dist/licensing/licenseService.d.ts +10 -0
- package/dist/licensing/licenseService.js +28 -0
- package/dist/licensing/licenseService.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/TaskBoardWorkspace.js +3 -2
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/components/ProjectDashboard.d.ts +4 -0
- package/dist/task-board/components/ProjectDashboard.js +1 -1
- package/dist/task-board/components/ProjectDashboard.js.map +1 -1
- package/dist/task-board/components/ProjectListContent.d.ts +1 -1
- package/dist/task-board/components/ProjectListContent.js +4 -1
- package/dist/task-board/components/ProjectListContent.js.map +1 -1
- package/dist/task-board/components/ProjectOverviewContent.d.ts +17 -0
- package/dist/task-board/components/ProjectOverviewContent.js +134 -0
- package/dist/task-board/components/ProjectOverviewContent.js.map +1 -0
- package/dist/task-board/components/ProjectSelector.d.ts +1 -1
- package/dist/task-board/components/ProjectSelector.js +1 -1
- package/dist/task-board/components/ProjectSelector.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.js +59 -9
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/services/taskService.d.ts +4 -1
- package/dist/task-board/services/taskService.js +3 -0
- package/dist/task-board/services/taskService.js.map +1 -1
- package/dist/task-board/taskBoardNavStore.d.ts +3 -1
- package/dist/task-board/taskBoardNavStore.js.map +1 -1
- package/dist/task-board/types.d.ts +30 -0
- package/package.json +1 -1
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
2
|
import { useEffect, useCallback, useState, useMemo, useRef, } from "react";
|
|
2
3
|
import { useDebouncedCallback } from "use-debounce";
|
|
3
4
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
4
5
|
import { generatePageContext, getCachedContext, } from "../services/contextService";
|
|
6
|
+
import { WandSparkles } from "lucide-react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
function InlineCompletionHint({ hintText, isMobile, onAccept, positionStyle, }) {
|
|
9
|
+
return (_jsxs("div", { className: "shadow-[0_4px_14px_rgba(15,23,42,0.14),0_0_0_1px_rgba(15,23,42,0.06)]pointer-events-auto fixed z-95 inline-flex max-w-none cursor-pointer items-center gap-2 rounded-md border border-slate-400 bg-slate-100 px-2.5 py-2 text-xs leading-snug font-medium whitespace-nowrap text-slate-900", style: positionStyle, onClick: isMobile ? onAccept : undefined, role: isMobile ? "button" : undefined, children: [_jsx(WandSparkles, { className: "size-3.5 shrink-0 text-violet-600", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { children: hintText })] }));
|
|
10
|
+
}
|
|
5
11
|
export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatingRef, }) {
|
|
6
12
|
const editContext = useEditContext();
|
|
7
13
|
const fieldsContext = useFieldsEditContext();
|
|
8
14
|
const [currentCompletion, setCurrentCompletion] = useState(null);
|
|
9
|
-
const [
|
|
15
|
+
const [, setIsLoading] = useState(false);
|
|
10
16
|
const abortControllerRef = useRef(null);
|
|
11
17
|
const loadingAnimationRef = useRef(null);
|
|
18
|
+
const applyCompletionRef = useRef(null);
|
|
19
|
+
const hintReactRootRef = useRef(null);
|
|
12
20
|
// Clean up hint element on unmount
|
|
13
21
|
useEffect(() => {
|
|
14
22
|
return () => {
|
|
23
|
+
hintReactRootRef.current?.unmount();
|
|
24
|
+
hintReactRootRef.current = null;
|
|
15
25
|
const hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
16
26
|
if (hintElement) {
|
|
17
27
|
hintElement.remove();
|
|
@@ -23,7 +33,7 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
23
33
|
}, [cursorSpanId]);
|
|
24
34
|
const lastCaretPosRef = useRef(null);
|
|
25
35
|
// Simple function to track caret position without inserting spans
|
|
26
|
-
const positionCursorSpan = () => {
|
|
36
|
+
const positionCursorSpan = useCallback(() => {
|
|
27
37
|
if (isUpdatingRef.current)
|
|
28
38
|
return;
|
|
29
39
|
isUpdatingRef.current = true;
|
|
@@ -97,55 +107,14 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
97
107
|
isUpdatingRef.current = false;
|
|
98
108
|
}, 10);
|
|
99
109
|
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
e.key === "ArrowUp" ||
|
|
107
|
-
e.key === "ArrowDown" ||
|
|
108
|
-
e.key === " " ||
|
|
109
|
-
e.key === "Tab" ||
|
|
110
|
-
e.key === "End" ||
|
|
111
|
-
e.key === "Backspace" ||
|
|
112
|
-
e.key === "Delete" // Add Delete key handling
|
|
113
|
-
) {
|
|
114
|
-
// Clear the completion when arrow keys are used
|
|
115
|
-
setCurrentCompletion(null);
|
|
116
|
-
clearCursorSpan();
|
|
117
|
-
// Let the browser handle the cursor movement/deletion
|
|
118
|
-
// Then update our cursor span after a small delay
|
|
119
|
-
setTimeout(() => {
|
|
120
|
-
if (!isUpdatingRef.current) {
|
|
121
|
-
positionCursorSpan();
|
|
122
|
-
}
|
|
123
|
-
}, 10);
|
|
124
|
-
// Special handling for right arrow and delete which seems to have issues
|
|
125
|
-
if (e.key === "ArrowRight" || e.key === "Delete") {
|
|
126
|
-
// Make sure the cursor span doesn't block the movement/deletion
|
|
127
|
-
const cursorSpan = pageViewContext.editorIframe?.contentWindow?.document.getElementById(cursorSpanId);
|
|
128
|
-
if (cursorSpan) {
|
|
129
|
-
// Temporarily make it display none so it doesn't interfere with selection
|
|
130
|
-
const originalDisplay = cursorSpan.style.display;
|
|
131
|
-
cursorSpan.style.display = "none";
|
|
132
|
-
// Restore after the browser has processed the movement/deletion
|
|
133
|
-
setTimeout(() => {
|
|
134
|
-
if (cursorSpan.parentNode) {
|
|
135
|
-
cursorSpan.style.display = originalDisplay;
|
|
136
|
-
}
|
|
137
|
-
}, 0);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
pageViewContext.editorIframe?.contentWindow?.document.addEventListener("keydown", keyHandler);
|
|
143
|
-
return () => {
|
|
144
|
-
pageViewContext.editorIframe?.contentWindow?.document.removeEventListener("keydown", keyHandler);
|
|
145
|
-
};
|
|
146
|
-
}, [currentCompletion, pageViewContext.page]);
|
|
110
|
+
}, [
|
|
111
|
+
pageViewContext.editorIframe?.contentWindow,
|
|
112
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
113
|
+
cursorSpanId,
|
|
114
|
+
isUpdatingRef,
|
|
115
|
+
]);
|
|
147
116
|
// Extracts the text up to the cursor position in the editable element
|
|
148
|
-
const getContentUpToCursor = (element) => {
|
|
117
|
+
const getContentUpToCursor = useCallback((element) => {
|
|
149
118
|
const iframeWindow = pageViewContext.editorIframe?.contentWindow;
|
|
150
119
|
const selection = iframeWindow?.getSelection();
|
|
151
120
|
if (!element || !selection || selection.rangeCount === 0)
|
|
@@ -168,19 +137,17 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
168
137
|
contentUpToCursor = tempRange.toString();
|
|
169
138
|
}
|
|
170
139
|
return contentUpToCursor;
|
|
171
|
-
};
|
|
140
|
+
}, [pageViewContext.editorIframe?.contentWindow]);
|
|
172
141
|
// Loading animation with three dots changing color
|
|
173
|
-
const startLoadingAnimation = () => {
|
|
142
|
+
const startLoadingAnimation = useCallback(() => {
|
|
174
143
|
const doc = pageViewContext.editorIframe?.contentWindow?.document;
|
|
175
144
|
const span = doc?.getElementById(cursorSpanId);
|
|
176
145
|
if (!doc || !span)
|
|
177
146
|
return;
|
|
178
|
-
// Create dots container
|
|
179
147
|
span.innerHTML = "";
|
|
180
148
|
span.style.display = "inline-flex";
|
|
181
149
|
span.style.gap = "4px";
|
|
182
150
|
span.style.alignItems = "center";
|
|
183
|
-
// Create three dots
|
|
184
151
|
for (let i = 0; i < 3; i++) {
|
|
185
152
|
const dot = doc.createElement("span");
|
|
186
153
|
dot.textContent = "•";
|
|
@@ -189,7 +156,6 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
189
156
|
dot.style.fontSize = "16px";
|
|
190
157
|
span.appendChild(dot);
|
|
191
158
|
}
|
|
192
|
-
// Animate dots
|
|
193
159
|
let step = 0;
|
|
194
160
|
const animate = () => {
|
|
195
161
|
const dots = span.querySelectorAll("span");
|
|
@@ -203,7 +169,6 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
203
169
|
});
|
|
204
170
|
step++;
|
|
205
171
|
loadingAnimationRef.current = requestAnimationFrame(() => {
|
|
206
|
-
// Slow down animation by only updating every 15 frames (~250ms at 60fps)
|
|
207
172
|
if (step % 15 === 0) {
|
|
208
173
|
animate();
|
|
209
174
|
}
|
|
@@ -213,34 +178,27 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
213
178
|
});
|
|
214
179
|
};
|
|
215
180
|
animate();
|
|
216
|
-
};
|
|
217
|
-
const stopLoadingAnimation = () => {
|
|
181
|
+
}, [pageViewContext.editorIframe?.contentWindow?.document, cursorSpanId]);
|
|
182
|
+
const stopLoadingAnimation = useCallback(() => {
|
|
218
183
|
if (loadingAnimationRef.current) {
|
|
219
184
|
cancelAnimationFrame(loadingAnimationRef.current);
|
|
220
185
|
loadingAnimationRef.current = null;
|
|
221
186
|
}
|
|
222
|
-
};
|
|
223
|
-
const getCompletion = async (element, isManualTrigger = false) => {
|
|
187
|
+
}, []);
|
|
188
|
+
const getCompletion = useCallback(async (element, isManualTrigger = false) => {
|
|
224
189
|
const contentUpToCursor = getContentUpToCursor(element);
|
|
225
190
|
if (!contentUpToCursor?.trim())
|
|
226
191
|
return null;
|
|
227
|
-
// Abort any in-flight request
|
|
228
192
|
if (abortControllerRef.current) {
|
|
229
193
|
abortControllerRef.current.abort();
|
|
230
194
|
}
|
|
231
|
-
// Create a new abort controller for this request
|
|
232
195
|
abortControllerRef.current = new AbortController();
|
|
233
|
-
const signal = abortControllerRef.current.signal;
|
|
234
|
-
// Get field attributes
|
|
235
196
|
const fieldId = element.getAttribute("data-fieldid");
|
|
236
|
-
const fieldName = element.getAttribute("data-fieldname");
|
|
237
197
|
const itemId = element.getAttribute("data-itemid");
|
|
238
198
|
const language = element.getAttribute("data-language");
|
|
239
199
|
const version = element.getAttribute("data-version");
|
|
240
200
|
if (!fieldId || !itemId || !language || !version)
|
|
241
201
|
return null;
|
|
242
|
-
// Only trigger completion after a space for automatic completions
|
|
243
|
-
// Manual triggers (Ctrl+Space) can work anywhere
|
|
244
202
|
if (!isManualTrigger) {
|
|
245
203
|
const lastChar = contentUpToCursor.slice(-1);
|
|
246
204
|
if (lastChar !== " " && lastChar !== "\u00A0") {
|
|
@@ -249,7 +207,6 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
249
207
|
}
|
|
250
208
|
if (!editContext)
|
|
251
209
|
return null;
|
|
252
|
-
// Get page context for better completions
|
|
253
210
|
let pageContext = getCachedContext(editContext);
|
|
254
211
|
if (!pageContext) {
|
|
255
212
|
try {
|
|
@@ -257,22 +214,20 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
257
214
|
}
|
|
258
215
|
catch (error) {
|
|
259
216
|
console.warn("Failed to generate page context:", error);
|
|
260
|
-
// Continue without context
|
|
261
217
|
}
|
|
262
218
|
}
|
|
263
219
|
let contextString = "";
|
|
264
220
|
if (pageContext) {
|
|
265
221
|
contextString = `Page Name: ${pageContext.pageTitle} (${pageContext.pageType})\n PageSummary: ${pageContext.abstract}`;
|
|
266
222
|
}
|
|
267
|
-
// Show loading indicator
|
|
268
223
|
setIsLoading(true);
|
|
269
224
|
startLoadingAnimation();
|
|
270
225
|
try {
|
|
271
226
|
const endpoint = `/parhelia/agent/GetTextCompletion`;
|
|
272
227
|
const response = await fetch(endpoint, {
|
|
273
|
-
method:
|
|
228
|
+
method: "POST",
|
|
274
229
|
headers: {
|
|
275
|
-
|
|
230
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
276
231
|
},
|
|
277
232
|
body: new URLSearchParams({
|
|
278
233
|
textToComplete: contentUpToCursor,
|
|
@@ -283,18 +238,22 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
283
238
|
return data.completion || null;
|
|
284
239
|
}
|
|
285
240
|
catch (error) {
|
|
286
|
-
// Ignore AbortError as it's expected when cancelling
|
|
287
241
|
if (error instanceof Error && error.name !== "AbortError") {
|
|
288
242
|
console.error("Error getting completion:", error);
|
|
289
243
|
}
|
|
290
244
|
return null;
|
|
291
245
|
}
|
|
292
246
|
finally {
|
|
293
|
-
// Hide loading indicator
|
|
294
247
|
setIsLoading(false);
|
|
295
248
|
stopLoadingAnimation();
|
|
296
249
|
}
|
|
297
|
-
}
|
|
250
|
+
}, [
|
|
251
|
+
getContentUpToCursor,
|
|
252
|
+
editContext,
|
|
253
|
+
pageViewContext,
|
|
254
|
+
startLoadingAnimation,
|
|
255
|
+
stopLoadingAnimation,
|
|
256
|
+
]);
|
|
298
257
|
// Debounced AI call: recompute the sentence, call getCompletion, and extract only the "tail" for the ghost text
|
|
299
258
|
const getCompletionDebounced = useDebouncedCallback(async (isManualTrigger = false) => {
|
|
300
259
|
const el = fieldsContext?.inlineEditingFieldElement;
|
|
@@ -317,50 +276,17 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
317
276
|
setCurrentCompletion(suggestion);
|
|
318
277
|
updateCursorSpan(suggestion.substring(sentence.length));
|
|
319
278
|
}, 250);
|
|
320
|
-
// Manual completion trigger (non-debounced for immediate response)
|
|
321
|
-
const getCompletionManual = async () => {
|
|
322
|
-
const el = fieldsContext?.inlineEditingFieldElement;
|
|
323
|
-
if (!el)
|
|
324
|
-
return;
|
|
325
|
-
// 1) Recompute the exact sentence at this moment
|
|
326
|
-
const full = getContentUpToCursor(el) || "";
|
|
327
|
-
const sentence = full.split(/[.?!]\s*/).pop() || "";
|
|
328
|
-
// 2) Ask AI for a completion
|
|
329
|
-
const rawSuggestion = await getCompletion(el, true);
|
|
330
|
-
if (!rawSuggestion) {
|
|
331
|
-
setCurrentCompletion(null);
|
|
332
|
-
clearCursorSpan();
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
// 3) Strip off the already-typed sentence to leave just the "completion tail"
|
|
336
|
-
const suggestion = rawSuggestion.startsWith(sentence)
|
|
337
|
-
? rawSuggestion
|
|
338
|
-
: sentence + rawSuggestion;
|
|
339
|
-
setCurrentCompletion(suggestion);
|
|
340
|
-
updateCursorSpan(suggestion.substring(sentence.length));
|
|
341
|
-
};
|
|
342
279
|
// Inserts or clears the ghost text inside the cursor span
|
|
343
|
-
const updateCursorSpan = (text) => {
|
|
280
|
+
const updateCursorSpan = useCallback((text) => {
|
|
344
281
|
const doc = pageViewContext.editorIframe?.contentWindow?.document;
|
|
345
282
|
const span = doc?.getElementById(cursorSpanId);
|
|
346
283
|
if (!doc || !span)
|
|
347
284
|
return;
|
|
348
285
|
// Update the completion text
|
|
349
286
|
if (text) {
|
|
350
|
-
// // Create a temporary span to measure the text width
|
|
351
|
-
// const measureSpan = doc.createElement("span");
|
|
352
|
-
// measureSpan.style.visibility = "hidden";
|
|
353
|
-
// measureSpan.style.position = "absolute";
|
|
354
|
-
// measureSpan.style.whiteSpace = "pre"; // Preserve whitespace
|
|
355
|
-
// measureSpan.style.font = window.getComputedStyle(span).font; // Match the font
|
|
356
|
-
// measureSpan.textContent = text;
|
|
357
|
-
// doc.body.appendChild(measureSpan);
|
|
358
|
-
// const textWidth = measureSpan.getBoundingClientRect().width;
|
|
359
|
-
// measureSpan.remove();
|
|
360
287
|
span.textContent = text;
|
|
361
288
|
span.style.color = "#888";
|
|
362
289
|
span.style.fontStyle = "italic";
|
|
363
|
-
//span.innerHTML = "hello";
|
|
364
290
|
// Create or update hint element in the main document
|
|
365
291
|
let hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
366
292
|
if (!hintElement) {
|
|
@@ -368,68 +294,264 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
368
294
|
hintElement.id = `${cursorSpanId}-hint`;
|
|
369
295
|
document.body.appendChild(hintElement);
|
|
370
296
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
padding: "0.5rem",
|
|
380
|
-
marginLeft: "0.25rem",
|
|
381
|
-
marginRight: "0.25rem",
|
|
382
|
-
borderRadius: "0.25rem",
|
|
383
|
-
border: "1px solid rgb(229, 231, 235)",
|
|
384
|
-
fontStyle: "normal",
|
|
385
|
-
display: "block",
|
|
386
|
-
lineHeight: "1.4",
|
|
387
|
-
zIndex: "95",
|
|
388
|
-
pointerEvents: "none",
|
|
389
|
-
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
390
|
-
});
|
|
391
|
-
// Position the hint element relative to the iframe and cursor span
|
|
297
|
+
if (!hintReactRootRef.current) {
|
|
298
|
+
hintElement.replaceChildren();
|
|
299
|
+
hintReactRootRef.current = createRoot(hintElement);
|
|
300
|
+
}
|
|
301
|
+
const hintLabel = editContext?.isMobile
|
|
302
|
+
? "Tap to accept"
|
|
303
|
+
: "Press Tab to accept ⇥";
|
|
304
|
+
const isMobile = editContext?.isMobile ?? false;
|
|
392
305
|
const iframeRect = pageViewContext.editorIframe?.getBoundingClientRect();
|
|
393
306
|
const spanRect = span.getBoundingClientRect();
|
|
307
|
+
let positionStyle = {
|
|
308
|
+
marginLeft: "0.25rem",
|
|
309
|
+
marginRight: "0.25rem",
|
|
310
|
+
};
|
|
394
311
|
if (iframeRect) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const absoluteTop = iframeRect.top + rangeRect.top;
|
|
405
|
-
hintElement.style.left = `${absoluteLeft}px`;
|
|
406
|
-
hintElement.style.top = `${absoluteTop}px`;
|
|
312
|
+
if (isMobile) {
|
|
313
|
+
const centerX = (spanRect.left + spanRect.right) / 2;
|
|
314
|
+
const gap = 6;
|
|
315
|
+
positionStyle = {
|
|
316
|
+
...positionStyle,
|
|
317
|
+
left: centerX,
|
|
318
|
+
top: spanRect.top - gap,
|
|
319
|
+
transform: "translate(-50%, -100%)",
|
|
320
|
+
};
|
|
407
321
|
}
|
|
408
322
|
else {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
323
|
+
const textLength = span.textContent?.length || 0;
|
|
324
|
+
let left;
|
|
325
|
+
let top;
|
|
326
|
+
if (textLength > 0 && span.firstChild) {
|
|
327
|
+
const range = doc.createRange();
|
|
328
|
+
range.setStart(span.firstChild, Math.max(0, textLength - 1));
|
|
329
|
+
range.setEnd(span.firstChild, textLength);
|
|
330
|
+
const rangeRect = range.getBoundingClientRect();
|
|
331
|
+
left = iframeRect.left + rangeRect.right;
|
|
332
|
+
top = iframeRect.top + rangeRect.top;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
left = iframeRect.left + spanRect.right;
|
|
336
|
+
top = iframeRect.top + spanRect.top;
|
|
337
|
+
}
|
|
338
|
+
positionStyle = {
|
|
339
|
+
...positionStyle,
|
|
340
|
+
left,
|
|
341
|
+
top,
|
|
342
|
+
transform: undefined,
|
|
343
|
+
};
|
|
414
344
|
}
|
|
415
345
|
}
|
|
346
|
+
hintReactRootRef.current.render(_jsx(InlineCompletionHint, { hintText: hintLabel, isMobile: isMobile, onAccept: () => applyCompletionRef.current?.(), positionStyle: positionStyle }));
|
|
416
347
|
}
|
|
417
348
|
else {
|
|
418
349
|
span.textContent = "";
|
|
419
350
|
// Remove hint element if it exists
|
|
420
351
|
const hintElement = document.getElementById(`${cursorSpanId}-hint`);
|
|
421
352
|
if (hintElement) {
|
|
353
|
+
hintReactRootRef.current?.unmount();
|
|
354
|
+
hintReactRootRef.current = null;
|
|
422
355
|
hintElement.remove();
|
|
423
356
|
}
|
|
424
357
|
}
|
|
425
|
-
};
|
|
426
|
-
const clearCursorSpan = () => {
|
|
358
|
+
}, [pageViewContext.editorIframe, cursorSpanId, editContext?.isMobile]);
|
|
359
|
+
const clearCursorSpan = useCallback(() => {
|
|
427
360
|
const doc = pageViewContext.editorIframe?.contentWindow?.document;
|
|
428
361
|
if (!doc)
|
|
429
362
|
return;
|
|
430
363
|
// Clear the completion text
|
|
431
364
|
updateCursorSpan("");
|
|
432
|
-
};
|
|
365
|
+
}, [pageViewContext.editorIframe?.contentWindow?.document, updateCursorSpan]);
|
|
366
|
+
// Handle keydown events for cursor movement (arrow keys, etc.)
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
const keyHandler = (e) => {
|
|
369
|
+
if (e.key === "ArrowLeft" ||
|
|
370
|
+
e.key === "ArrowRight" ||
|
|
371
|
+
e.key === "ArrowUp" ||
|
|
372
|
+
e.key === "ArrowDown" ||
|
|
373
|
+
e.key === " " ||
|
|
374
|
+
e.key === "Tab" ||
|
|
375
|
+
e.key === "End" ||
|
|
376
|
+
e.key === "Backspace" ||
|
|
377
|
+
e.key === "Delete") {
|
|
378
|
+
setCurrentCompletion(null);
|
|
379
|
+
clearCursorSpan();
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
if (!isUpdatingRef.current) {
|
|
382
|
+
positionCursorSpan();
|
|
383
|
+
}
|
|
384
|
+
}, 10);
|
|
385
|
+
if (e.key === "ArrowRight" || e.key === "Delete") {
|
|
386
|
+
const cursorSpan = pageViewContext.editorIframe?.contentWindow?.document.getElementById(cursorSpanId);
|
|
387
|
+
if (cursorSpan) {
|
|
388
|
+
const originalDisplay = cursorSpan.style.display;
|
|
389
|
+
cursorSpan.style.display = "none";
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
if (cursorSpan.parentNode) {
|
|
392
|
+
cursorSpan.style.display = originalDisplay;
|
|
393
|
+
}
|
|
394
|
+
}, 0);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const doc = pageViewContext.editorIframe?.contentWindow?.document;
|
|
400
|
+
if (!doc)
|
|
401
|
+
return;
|
|
402
|
+
doc.addEventListener("keydown", keyHandler);
|
|
403
|
+
return () => doc.removeEventListener("keydown", keyHandler);
|
|
404
|
+
}, [
|
|
405
|
+
clearCursorSpan,
|
|
406
|
+
cursorSpanId,
|
|
407
|
+
isUpdatingRef,
|
|
408
|
+
pageViewContext.editorIframe?.contentWindow?.document,
|
|
409
|
+
pageViewContext.page,
|
|
410
|
+
positionCursorSpan,
|
|
411
|
+
]);
|
|
412
|
+
// Function to apply the completion (must be before handleInput)
|
|
413
|
+
const applyCompletion = useCallback(() => {
|
|
414
|
+
const iframeWindow = pageViewContext.editorIframe?.contentWindow;
|
|
415
|
+
const iframeDocument = iframeWindow?.document;
|
|
416
|
+
if (!iframeWindow ||
|
|
417
|
+
!iframeDocument ||
|
|
418
|
+
!fieldsContext?.inlineEditingFieldElement)
|
|
419
|
+
return;
|
|
420
|
+
const cursorSpan = iframeDocument.getElementById(cursorSpanId);
|
|
421
|
+
if (!cursorSpan)
|
|
422
|
+
return;
|
|
423
|
+
const completionToApply = cursorSpan.textContent || "";
|
|
424
|
+
if (!completionToApply)
|
|
425
|
+
return;
|
|
426
|
+
const element = fieldsContext.inlineEditingFieldElement;
|
|
427
|
+
const fieldId = element.getAttribute("data-fieldid");
|
|
428
|
+
const fieldName = element.getAttribute("data-fieldname");
|
|
429
|
+
const itemId = element.getAttribute("data-itemid");
|
|
430
|
+
const language = element.getAttribute("data-language");
|
|
431
|
+
const versionStr = element.getAttribute("data-version");
|
|
432
|
+
const isRichText = element.getAttribute("data-is-richtext") === "true";
|
|
433
|
+
const version = versionStr ? parseInt(versionStr, 10) : undefined;
|
|
434
|
+
if (!fieldId || !itemId || !language || !version)
|
|
435
|
+
return;
|
|
436
|
+
const selection = iframeWindow.getSelection();
|
|
437
|
+
if (!selection || selection.rangeCount === 0)
|
|
438
|
+
return;
|
|
439
|
+
const range = selection.getRangeAt(0);
|
|
440
|
+
const tempRange = document.createRange();
|
|
441
|
+
tempRange.selectNodeContents(element);
|
|
442
|
+
tempRange.setEnd(range.startContainer, range.startOffset);
|
|
443
|
+
const textUpToCursor = tempRange.toString();
|
|
444
|
+
const wordBoundaryRegex = /[\s.,;:!?"'()[\]{}<>/|=+\-*&^%$#@~`](?=[^\s.,;:!?"'()[\]{}<>/|=+\-*&^%$#@~`]*$)/;
|
|
445
|
+
const match = textUpToCursor.match(wordBoundaryRegex);
|
|
446
|
+
const lastWordBoundaryIndex = match && match.index !== undefined ? match.index + 1 : 0;
|
|
447
|
+
const currentPartialWord = textUpToCursor
|
|
448
|
+
.substring(lastWordBoundaryIndex)
|
|
449
|
+
.trim();
|
|
450
|
+
const isOverlapping = currentPartialWord.length > 0 &&
|
|
451
|
+
completionToApply
|
|
452
|
+
.toLowerCase()
|
|
453
|
+
.startsWith(currentPartialWord.toLowerCase());
|
|
454
|
+
if (isOverlapping) {
|
|
455
|
+
const wordRange = document.createRange();
|
|
456
|
+
const startContainer = range.startContainer;
|
|
457
|
+
const startOffset = range.startOffset - currentPartialWord.length;
|
|
458
|
+
if (startOffset >= 0 && startContainer.nodeType === Node.TEXT_NODE) {
|
|
459
|
+
wordRange.setStart(startContainer, startOffset);
|
|
460
|
+
wordRange.setEnd(range.startContainer, range.startOffset);
|
|
461
|
+
wordRange.deleteContents();
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
if (textUpToCursor.length > 0 && !textUpToCursor.endsWith(" ")) {
|
|
465
|
+
const spaceNode = document.createTextNode(" ");
|
|
466
|
+
range.insertNode(spaceNode);
|
|
467
|
+
range.setStartAfter(spaceNode);
|
|
468
|
+
range.setEndAfter(spaceNode);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
if (textUpToCursor.length > 0 &&
|
|
474
|
+
!textUpToCursor.endsWith(" ") &&
|
|
475
|
+
!textUpToCursor.endsWith("\n") &&
|
|
476
|
+
!/[.!?\-—:;({[\s]$/.test(textUpToCursor)) {
|
|
477
|
+
const spaceNode = document.createTextNode(" ");
|
|
478
|
+
range.insertNode(spaceNode);
|
|
479
|
+
range.setStartAfter(spaceNode);
|
|
480
|
+
range.setEndAfter(spaceNode);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const textNode = document.createTextNode(completionToApply);
|
|
484
|
+
range.insertNode(textNode);
|
|
485
|
+
range.setStartAfter(textNode);
|
|
486
|
+
range.setEndAfter(textNode);
|
|
487
|
+
selection.removeAllRanges();
|
|
488
|
+
selection.addRange(range);
|
|
489
|
+
setCurrentCompletion(null);
|
|
490
|
+
clearCursorSpan();
|
|
491
|
+
setTimeout(() => {
|
|
492
|
+
let valueToSave;
|
|
493
|
+
if (isRichText) {
|
|
494
|
+
const clone = element.cloneNode(true);
|
|
495
|
+
const cursorElem = clone.querySelector(`#${cursorSpanId}`);
|
|
496
|
+
if (cursorElem)
|
|
497
|
+
cursorElem.parentNode?.removeChild(cursorElem);
|
|
498
|
+
const ownerDoc = clone.ownerDocument || document;
|
|
499
|
+
const walker = ownerDoc.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
500
|
+
const toClean = [];
|
|
501
|
+
while (walker.nextNode()) {
|
|
502
|
+
const tn = walker.currentNode;
|
|
503
|
+
if (tn.nodeValue && tn.nodeValue.includes("\u200B"))
|
|
504
|
+
toClean.push(tn);
|
|
505
|
+
}
|
|
506
|
+
toClean.forEach((tn) => (tn.nodeValue = tn.nodeValue?.replaceAll("\u200B", "") || ""));
|
|
507
|
+
valueToSave = clone.innerHTML;
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
valueToSave = (element.innerText || "").replaceAll("\u200B", "");
|
|
511
|
+
}
|
|
512
|
+
editContext?.operations.editField({
|
|
513
|
+
field: {
|
|
514
|
+
fieldId,
|
|
515
|
+
fieldName: fieldName ?? undefined,
|
|
516
|
+
item: { id: itemId, language, version },
|
|
517
|
+
},
|
|
518
|
+
refresh: "none",
|
|
519
|
+
value: valueToSave,
|
|
520
|
+
});
|
|
521
|
+
}, 0);
|
|
522
|
+
}, [
|
|
523
|
+
pageViewContext.editorIframe?.contentWindow,
|
|
524
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
525
|
+
cursorSpanId,
|
|
526
|
+
clearCursorSpan,
|
|
527
|
+
editContext?.operations,
|
|
528
|
+
]);
|
|
529
|
+
applyCompletionRef.current = applyCompletion;
|
|
530
|
+
// Manual completion trigger (non-debounced)
|
|
531
|
+
const getCompletionManual = useCallback(async () => {
|
|
532
|
+
const el = fieldsContext?.inlineEditingFieldElement;
|
|
533
|
+
if (!el)
|
|
534
|
+
return;
|
|
535
|
+
const full = getContentUpToCursor(el) || "";
|
|
536
|
+
const sentence = full.split(/[.?!]\s*/).pop() || "";
|
|
537
|
+
const rawSuggestion = await getCompletion(el, true);
|
|
538
|
+
if (!rawSuggestion) {
|
|
539
|
+
setCurrentCompletion(null);
|
|
540
|
+
clearCursorSpan();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const suggestion = rawSuggestion.startsWith(sentence)
|
|
544
|
+
? rawSuggestion
|
|
545
|
+
: sentence + rawSuggestion;
|
|
546
|
+
setCurrentCompletion(suggestion);
|
|
547
|
+
updateCursorSpan(suggestion.substring(sentence.length));
|
|
548
|
+
}, [
|
|
549
|
+
fieldsContext?.inlineEditingFieldElement,
|
|
550
|
+
getContentUpToCursor,
|
|
551
|
+
getCompletion,
|
|
552
|
+
clearCursorSpan,
|
|
553
|
+
updateCursorSpan,
|
|
554
|
+
]);
|
|
433
555
|
// On every input: either reuse the existing suggestion or fire a new one
|
|
434
556
|
const handleInput = useCallback((e) => {
|
|
435
557
|
const el = fieldsContext?.inlineEditingFieldElement;
|
|
@@ -518,9 +640,15 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
518
640
|
}
|
|
519
641
|
}, [
|
|
520
642
|
fieldsContext?.inlineEditingFieldElement,
|
|
643
|
+
getContentUpToCursor,
|
|
521
644
|
currentCompletion,
|
|
522
|
-
|
|
645
|
+
clearCursorSpan,
|
|
523
646
|
getCompletionManual,
|
|
647
|
+
pageViewContext.editorIframe?.contentWindow,
|
|
648
|
+
cursorSpanId,
|
|
649
|
+
applyCompletion,
|
|
650
|
+
updateCursorSpan,
|
|
651
|
+
getCompletionDebounced,
|
|
524
652
|
]);
|
|
525
653
|
// Wire up the input listener
|
|
526
654
|
useEffect(() => {
|
|
@@ -558,6 +686,9 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
558
686
|
}, [
|
|
559
687
|
fieldsContext?.inlineEditingFieldElement,
|
|
560
688
|
editContext?.enableCompletions,
|
|
689
|
+
isUpdatingRef,
|
|
690
|
+
positionCursorSpan,
|
|
691
|
+
clearCursorSpan,
|
|
561
692
|
]);
|
|
562
693
|
// Clean up abort controller on unmount
|
|
563
694
|
useEffect(() => {
|
|
@@ -572,157 +703,11 @@ export function useInlineAiCompletion({ pageViewContext, cursorSpanId, isUpdatin
|
|
|
572
703
|
}
|
|
573
704
|
};
|
|
574
705
|
}, []);
|
|
575
|
-
// Function to apply the completion
|
|
576
|
-
const applyCompletion = () => {
|
|
577
|
-
// Get the cursor span to read the most up-to-date completion
|
|
578
|
-
const iframeWindow = pageViewContext.editorIframe?.contentWindow;
|
|
579
|
-
const iframeDocument = iframeWindow?.document;
|
|
580
|
-
if (!iframeWindow ||
|
|
581
|
-
!iframeDocument ||
|
|
582
|
-
!fieldsContext?.inlineEditingFieldElement)
|
|
583
|
-
return;
|
|
584
|
-
const cursorSpan = iframeDocument.getElementById(cursorSpanId);
|
|
585
|
-
if (!cursorSpan)
|
|
586
|
-
return;
|
|
587
|
-
// Get the completion text directly from the cursor span, which should be the most current
|
|
588
|
-
const completionToApply = cursorSpan.textContent || "";
|
|
589
|
-
if (!completionToApply)
|
|
590
|
-
return;
|
|
591
|
-
const element = fieldsContext?.inlineEditingFieldElement;
|
|
592
|
-
// Get field attributes for saving
|
|
593
|
-
const fieldId = element.getAttribute("data-fieldid");
|
|
594
|
-
const fieldName = element.getAttribute("data-fieldname");
|
|
595
|
-
const itemId = element.getAttribute("data-itemid");
|
|
596
|
-
const language = element.getAttribute("data-language");
|
|
597
|
-
const versionStr = element.getAttribute("data-version");
|
|
598
|
-
const isRichText = element.getAttribute("data-is-richtext") === "true";
|
|
599
|
-
const version = versionStr ? parseInt(versionStr, 10) : undefined;
|
|
600
|
-
if (!fieldId || !itemId || !language || !version)
|
|
601
|
-
return;
|
|
602
|
-
// Get the current selection position
|
|
603
|
-
const selection = iframeWindow.getSelection();
|
|
604
|
-
if (!selection || selection.rangeCount === 0)
|
|
605
|
-
return;
|
|
606
|
-
// Get the text up to the cursor to analyze current word
|
|
607
|
-
const range = selection.getRangeAt(0);
|
|
608
|
-
const tempRange = document.createRange();
|
|
609
|
-
tempRange.selectNodeContents(element);
|
|
610
|
-
tempRange.setEnd(range.startContainer, range.startOffset);
|
|
611
|
-
const textUpToCursor = tempRange.toString();
|
|
612
|
-
// Get the current partial word by finding text from the last word boundary to cursor
|
|
613
|
-
// Look for last word boundary (space, punctuation, etc.)
|
|
614
|
-
const wordBoundaryRegex = /[\s.,;:!?"'()[\]{}<>\/\\|=+\-*&^%$#@~`](?=[^\s.,;:!?"'()[\]{}<>\/\\|=+\-*&^%$#@~`]*$)/;
|
|
615
|
-
const match = textUpToCursor.match(wordBoundaryRegex);
|
|
616
|
-
const lastWordBoundaryIndex = match && match.index !== undefined ? match.index + 1 : 0;
|
|
617
|
-
const currentPartialWord = textUpToCursor
|
|
618
|
-
.substring(lastWordBoundaryIndex)
|
|
619
|
-
.trim();
|
|
620
|
-
// Check if completion overlaps with current partial word
|
|
621
|
-
// (e.g., if user typed "int" and completion is "integer")
|
|
622
|
-
const isOverlapping = currentPartialWord.length > 0 &&
|
|
623
|
-
completionToApply
|
|
624
|
-
.toLowerCase()
|
|
625
|
-
.startsWith(currentPartialWord.toLowerCase());
|
|
626
|
-
console.log("Is overlapping:", isOverlapping);
|
|
627
|
-
// If there's overlap, we need to delete the current partial word
|
|
628
|
-
if (isOverlapping) {
|
|
629
|
-
// Create a range to select the current partial word
|
|
630
|
-
const wordRange = document.createRange();
|
|
631
|
-
// Position where the current word starts
|
|
632
|
-
let startContainer = range.startContainer;
|
|
633
|
-
let startOffset = range.startOffset - currentPartialWord.length;
|
|
634
|
-
// We need to handle the case where the word spans multiple text nodes
|
|
635
|
-
// For simplicity, we'll try to handle the common case first
|
|
636
|
-
if (startOffset >= 0 && startContainer.nodeType === Node.TEXT_NODE) {
|
|
637
|
-
// Simple case: word is in the same text node as cursor
|
|
638
|
-
wordRange.setStart(startContainer, startOffset);
|
|
639
|
-
wordRange.setEnd(range.startContainer, range.startOffset);
|
|
640
|
-
wordRange.deleteContents(); // Delete the partial word
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
// Complex case: use a simpler approach - just insert, user can delete manually if needed
|
|
644
|
-
// This is a fallback for complex DOM structures
|
|
645
|
-
// Add a space if we're in the middle of a sentence
|
|
646
|
-
if (textUpToCursor.length > 0 && !textUpToCursor.endsWith(" ")) {
|
|
647
|
-
const spaceNode = document.createTextNode(" ");
|
|
648
|
-
range.insertNode(spaceNode);
|
|
649
|
-
range.setStartAfter(spaceNode);
|
|
650
|
-
range.setEndAfter(spaceNode);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
// Not overlapping, add a space if we're in the middle of text
|
|
656
|
-
// and not already at the beginning of text or after a space
|
|
657
|
-
if (textUpToCursor.length > 0 &&
|
|
658
|
-
!textUpToCursor.endsWith(" ") &&
|
|
659
|
-
// Don't add space at the start of a line or after punctuation that shouldn't have a space
|
|
660
|
-
!textUpToCursor.endsWith("\n") &&
|
|
661
|
-
!/[.!?\-—:;({[\s]$/.test(textUpToCursor)) {
|
|
662
|
-
const spaceNode = document.createTextNode(" ");
|
|
663
|
-
range.insertNode(spaceNode);
|
|
664
|
-
range.setStartAfter(spaceNode);
|
|
665
|
-
range.setEndAfter(spaceNode);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
// Now insert the completion text
|
|
669
|
-
const textNode = document.createTextNode(isOverlapping
|
|
670
|
-
? completionToApply // If overlapping, use the full completion
|
|
671
|
-
: completionToApply);
|
|
672
|
-
range.insertNode(textNode);
|
|
673
|
-
// Move the cursor after the inserted text
|
|
674
|
-
range.setStartAfter(textNode);
|
|
675
|
-
range.setEndAfter(textNode);
|
|
676
|
-
selection.removeAllRanges();
|
|
677
|
-
selection.addRange(range);
|
|
678
|
-
setCurrentCompletion(null);
|
|
679
|
-
clearCursorSpan();
|
|
680
|
-
// Explicitly save the field value since the MutationObserver may miss this change
|
|
681
|
-
// when isUpdatingRef is true during cursor positioning
|
|
682
|
-
setTimeout(() => {
|
|
683
|
-
// Get the final value from the element (excluding cursor span content)
|
|
684
|
-
let valueToSave;
|
|
685
|
-
if (isRichText) {
|
|
686
|
-
const clone = element.cloneNode(true);
|
|
687
|
-
const cursorElem = clone.querySelector(`#${cursorSpanId}`);
|
|
688
|
-
if (cursorElem)
|
|
689
|
-
cursorElem.parentNode?.removeChild(cursorElem);
|
|
690
|
-
// Clean up zero-width spaces
|
|
691
|
-
const ownerDoc = clone.ownerDocument || document;
|
|
692
|
-
const walker = ownerDoc.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
693
|
-
const toClean = [];
|
|
694
|
-
while (walker.nextNode()) {
|
|
695
|
-
const tn = walker.currentNode;
|
|
696
|
-
if (tn.nodeValue && tn.nodeValue.includes("\u200B"))
|
|
697
|
-
toClean.push(tn);
|
|
698
|
-
}
|
|
699
|
-
toClean.forEach((tn) => (tn.nodeValue = tn.nodeValue?.replaceAll("\u200B", "") || ""));
|
|
700
|
-
valueToSave = clone.innerHTML;
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
valueToSave = (element.innerText || "").replaceAll("\u200B", "");
|
|
704
|
-
}
|
|
705
|
-
// Call editField to save the value
|
|
706
|
-
editContext?.operations.editField({
|
|
707
|
-
field: {
|
|
708
|
-
fieldId,
|
|
709
|
-
fieldName: fieldName ?? undefined,
|
|
710
|
-
item: {
|
|
711
|
-
id: itemId,
|
|
712
|
-
language,
|
|
713
|
-
version,
|
|
714
|
-
},
|
|
715
|
-
},
|
|
716
|
-
refresh: "none",
|
|
717
|
-
value: valueToSave,
|
|
718
|
-
});
|
|
719
|
-
}, 0);
|
|
720
|
-
};
|
|
721
706
|
// Exposed manual trigger (if needed)
|
|
722
707
|
return useMemo(() => () => {
|
|
723
708
|
setCurrentCompletion(null);
|
|
724
709
|
clearCursorSpan();
|
|
725
710
|
getCompletionManual();
|
|
726
|
-
}, [getCompletionManual]);
|
|
711
|
+
}, [clearCursorSpan, getCompletionManual]);
|
|
727
712
|
}
|
|
728
713
|
//# sourceMappingURL=useInlineAICompletion.js.map
|