@base44/vite-plugin 0.2.26 → 0.2.28
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/capabilities/inline-edit/controller.d.ts +3 -0
- package/dist/capabilities/inline-edit/controller.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/controller.js +200 -0
- package/dist/capabilities/inline-edit/controller.js.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts +7 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.js +59 -0
- package/dist/capabilities/inline-edit/dom-utils.js.map +1 -0
- package/dist/capabilities/inline-edit/index.d.ts +3 -0
- package/dist/capabilities/inline-edit/index.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/index.js +2 -0
- package/dist/capabilities/inline-edit/index.js.map +1 -0
- package/dist/capabilities/inline-edit/types.d.ts +25 -0
- package/dist/capabilities/inline-edit/types.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/types.js +2 -0
- package/dist/capabilities/inline-edit/types.js.map +1 -0
- package/dist/injections/visual-edit-agent.d.ts.map +1 -1
- package/dist/injections/visual-edit-agent.js +86 -16
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/statics/index.mjs +5 -1
- package/dist/statics/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/inline-edit/controller.ts +251 -0
- package/src/capabilities/inline-edit/dom-utils.ts +58 -0
- package/src/capabilities/inline-edit/index.ts +2 -0
- package/src/capabilities/inline-edit/types.ts +30 -0
- package/src/injections/visual-edit-agent.ts +105 -16
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { InlineEditHost, InlineEditController } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
injectFocusOutlineCSS,
|
|
4
|
+
removeFocusOutlineCSS,
|
|
5
|
+
selectText,
|
|
6
|
+
shouldEnterInlineEditingMode,
|
|
7
|
+
isStaticArrayTextElement,
|
|
8
|
+
} from "./dom-utils.js";
|
|
9
|
+
|
|
10
|
+
const DEBOUNCE_MS = 500;
|
|
11
|
+
|
|
12
|
+
export function createInlineEditController(
|
|
13
|
+
host: InlineEditHost
|
|
14
|
+
): InlineEditController {
|
|
15
|
+
let currentEditingElement: HTMLElement | null = null;
|
|
16
|
+
let debouncedSendTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
17
|
+
let enabled = false;
|
|
18
|
+
const listenerAbortControllers = new WeakMap<HTMLElement, AbortController>();
|
|
19
|
+
|
|
20
|
+
// --- Private helpers ---
|
|
21
|
+
|
|
22
|
+
const repositionOverlays = () => {
|
|
23
|
+
const selectedId = host.getSelectedElementId();
|
|
24
|
+
if (!selectedId) return;
|
|
25
|
+
const elements = host.findElementsById(selectedId);
|
|
26
|
+
const overlays = host.getSelectedOverlays();
|
|
27
|
+
overlays.forEach((overlay, i) => {
|
|
28
|
+
if (i < elements.length && elements[i]) {
|
|
29
|
+
host.positionOverlay(overlay, elements[i]);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const reportEdit = (element: HTMLElement) => {
|
|
35
|
+
const originalContent = element.dataset.originalTextContent;
|
|
36
|
+
const newContent = element.textContent;
|
|
37
|
+
|
|
38
|
+
const svgElement = element as unknown as SVGElement;
|
|
39
|
+
const rect = element.getBoundingClientRect();
|
|
40
|
+
|
|
41
|
+
const message: Record<string, unknown> = {
|
|
42
|
+
type: "inline-edit",
|
|
43
|
+
elementInfo: {
|
|
44
|
+
tagName: element.tagName,
|
|
45
|
+
classes:
|
|
46
|
+
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
47
|
+
element.className ||
|
|
48
|
+
"",
|
|
49
|
+
visualSelectorId: host.getSelectedElementId(),
|
|
50
|
+
content: newContent,
|
|
51
|
+
dataSourceLocation: element.dataset.sourceLocation,
|
|
52
|
+
isDynamicContent: element.dataset.dynamicContent === "true",
|
|
53
|
+
linenumber: element.dataset.linenumber,
|
|
54
|
+
filename: element.dataset.filename,
|
|
55
|
+
position: {
|
|
56
|
+
top: rect.top,
|
|
57
|
+
left: rect.left,
|
|
58
|
+
right: rect.right,
|
|
59
|
+
bottom: rect.bottom,
|
|
60
|
+
width: rect.width,
|
|
61
|
+
height: rect.height,
|
|
62
|
+
centerX: rect.left + rect.width / 2,
|
|
63
|
+
centerY: rect.top + rect.height / 2,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
originalContent,
|
|
67
|
+
newContent,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (isStaticArrayTextElement(element)) {
|
|
71
|
+
message.arrIndex = element.dataset.arrIndex;
|
|
72
|
+
message.arrVariableName = element.dataset.arrVariableName;
|
|
73
|
+
message.arrField = element.dataset.arrField;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
window.parent.postMessage(message, "*");
|
|
77
|
+
|
|
78
|
+
element.dataset.originalTextContent = newContent || "";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const debouncedReport = (element: HTMLElement) => {
|
|
82
|
+
if (debouncedSendTimeout) clearTimeout(debouncedSendTimeout);
|
|
83
|
+
debouncedSendTimeout = setTimeout(() => reportEdit(element), DEBOUNCE_MS);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const onTextInput = (element: HTMLElement) => {
|
|
87
|
+
repositionOverlays();
|
|
88
|
+
debouncedReport(element);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleInputEvent = function (this: HTMLElement) {
|
|
92
|
+
onTextInput(this);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const makeEditable = (element: HTMLElement) => {
|
|
96
|
+
injectFocusOutlineCSS();
|
|
97
|
+
|
|
98
|
+
element.dataset.originalTextContent = element.textContent || "";
|
|
99
|
+
element.dataset.originalCursor = element.style.cursor;
|
|
100
|
+
element.contentEditable = "true";
|
|
101
|
+
|
|
102
|
+
const abortController = new AbortController();
|
|
103
|
+
listenerAbortControllers.set(element, abortController);
|
|
104
|
+
element.addEventListener("input", handleInputEvent, {
|
|
105
|
+
signal: abortController.signal,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
element.style.cursor = "text";
|
|
109
|
+
selectText(element);
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (element.isConnected) {
|
|
112
|
+
element.focus();
|
|
113
|
+
}
|
|
114
|
+
}, 0);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const makeNonEditable = (element: HTMLElement) => {
|
|
118
|
+
const abortController = listenerAbortControllers.get(element);
|
|
119
|
+
if (abortController) {
|
|
120
|
+
abortController.abort();
|
|
121
|
+
listenerAbortControllers.delete(element);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!element.isConnected) return;
|
|
125
|
+
|
|
126
|
+
removeFocusOutlineCSS();
|
|
127
|
+
element.contentEditable = "false";
|
|
128
|
+
delete element.dataset.originalTextContent;
|
|
129
|
+
|
|
130
|
+
if (element.dataset.originalCursor !== undefined) {
|
|
131
|
+
element.style.cursor = element.dataset.originalCursor;
|
|
132
|
+
delete element.dataset.originalCursor;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// --- Public API ---
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
get enabled() {
|
|
140
|
+
return enabled;
|
|
141
|
+
},
|
|
142
|
+
set enabled(value: boolean) {
|
|
143
|
+
enabled = value;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
isEditing() {
|
|
147
|
+
return currentEditingElement !== null;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
getCurrentElement() {
|
|
151
|
+
return currentEditingElement;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
canEdit(element: Element) {
|
|
155
|
+
return shouldEnterInlineEditingMode(element);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
startEditing(element: HTMLElement) {
|
|
159
|
+
currentEditingElement = element;
|
|
160
|
+
|
|
161
|
+
host.getSelectedOverlays().forEach((o) => {
|
|
162
|
+
o.style.display = "none";
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
makeEditable(element);
|
|
166
|
+
|
|
167
|
+
window.parent.postMessage(
|
|
168
|
+
{
|
|
169
|
+
type: "content-editing-started",
|
|
170
|
+
visualSelectorId: host.getSelectedElementId(),
|
|
171
|
+
},
|
|
172
|
+
"*"
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
stopEditing() {
|
|
177
|
+
if (!currentEditingElement) return;
|
|
178
|
+
|
|
179
|
+
if (debouncedSendTimeout) {
|
|
180
|
+
clearTimeout(debouncedSendTimeout);
|
|
181
|
+
debouncedSendTimeout = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const element = currentEditingElement;
|
|
185
|
+
makeNonEditable(element);
|
|
186
|
+
|
|
187
|
+
host.getSelectedOverlays().forEach((o) => {
|
|
188
|
+
o.style.display = "";
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
repositionOverlays();
|
|
192
|
+
|
|
193
|
+
window.parent.postMessage(
|
|
194
|
+
{
|
|
195
|
+
type: "content-editing-ended",
|
|
196
|
+
visualSelectorId: host.getSelectedElementId(),
|
|
197
|
+
},
|
|
198
|
+
"*"
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
currentEditingElement = null;
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
markElementsSelected(elements: Element[]) {
|
|
205
|
+
elements.forEach((el) => {
|
|
206
|
+
if (el instanceof HTMLElement) {
|
|
207
|
+
el.dataset.selected = "true";
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
clearSelectedMarks(elementId: string | null) {
|
|
213
|
+
if (!elementId) return;
|
|
214
|
+
host.findElementsById(elementId).forEach((el) => {
|
|
215
|
+
if (el instanceof HTMLElement) {
|
|
216
|
+
delete el.dataset.selected;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
handleToggleMessage(data: { dataSourceLocation: string; inlineEditingMode: boolean }) {
|
|
222
|
+
if (!enabled) return;
|
|
223
|
+
|
|
224
|
+
const elements = host.findElementsById(data.dataSourceLocation);
|
|
225
|
+
if (elements.length === 0 || !(elements[0] instanceof HTMLElement)) return;
|
|
226
|
+
|
|
227
|
+
const element = elements[0];
|
|
228
|
+
|
|
229
|
+
if (data.inlineEditingMode) {
|
|
230
|
+
if (!shouldEnterInlineEditingMode(element)) return;
|
|
231
|
+
|
|
232
|
+
// Select the element first if not already selected
|
|
233
|
+
if (host.getSelectedElementId() !== data.dataSourceLocation) {
|
|
234
|
+
this.stopEditing();
|
|
235
|
+
host.clearSelection();
|
|
236
|
+
this.markElementsSelected(elements);
|
|
237
|
+
host.createSelectionOverlays(elements, data.dataSourceLocation);
|
|
238
|
+
}
|
|
239
|
+
this.startEditing(element);
|
|
240
|
+
} else {
|
|
241
|
+
if (currentEditingElement === element) {
|
|
242
|
+
this.stopEditing();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
cleanup() {
|
|
248
|
+
this.stopEditing();
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const FOCUS_STYLE_ID = "visual-edit-focus-styles";
|
|
2
|
+
|
|
3
|
+
const EDITABLE_TAGS = [
|
|
4
|
+
"div", "p", "h1", "h2", "h3", "h4", "h5", "h6",
|
|
5
|
+
"span", "li", "td", "a", "button", "label",
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export const isStaticArrayTextElement = (element: HTMLElement): boolean => {
|
|
9
|
+
return !!element.dataset.arrField;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const passesStructuralChecks = (element: HTMLElement): boolean => {
|
|
13
|
+
if (!EDITABLE_TAGS.includes(element.tagName.toLowerCase())) return false;
|
|
14
|
+
if (!element.textContent?.trim()) return false;
|
|
15
|
+
if (element.querySelector("img, video, canvas, svg")) return false;
|
|
16
|
+
if (element.children?.length > 0) return false;
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const injectFocusOutlineCSS = () => {
|
|
21
|
+
if (document.getElementById(FOCUS_STYLE_ID)) return;
|
|
22
|
+
|
|
23
|
+
const style = document.createElement("style");
|
|
24
|
+
style.id = FOCUS_STYLE_ID;
|
|
25
|
+
style.textContent = `
|
|
26
|
+
[data-selected="true"][contenteditable="true"]:focus {
|
|
27
|
+
outline: none !important;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
document.head.appendChild(style);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const removeFocusOutlineCSS = () => {
|
|
34
|
+
document.getElementById(FOCUS_STYLE_ID)?.remove();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const selectText = (element: HTMLElement) => {
|
|
38
|
+
const range = document.createRange();
|
|
39
|
+
range.selectNodeContents(element);
|
|
40
|
+
const selection = window.getSelection();
|
|
41
|
+
selection?.removeAllRanges();
|
|
42
|
+
selection?.addRange(range);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const isEditableTextElement = (element: Element): boolean => {
|
|
46
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
47
|
+
if (!passesStructuralChecks(element)) return false;
|
|
48
|
+
if (isStaticArrayTextElement(element)) return true;
|
|
49
|
+
if (element.dataset.dynamicContent === "true") return false;
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const shouldEnterInlineEditingMode = (element: Element): boolean => {
|
|
54
|
+
if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return isEditableTextElement(element);
|
|
58
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface InlineEditHost {
|
|
2
|
+
findElementsById(id: string | null): Element[];
|
|
3
|
+
getSelectedElementId(): string | null;
|
|
4
|
+
getSelectedOverlays(): HTMLDivElement[];
|
|
5
|
+
positionOverlay(
|
|
6
|
+
overlay: HTMLDivElement,
|
|
7
|
+
element: Element,
|
|
8
|
+
isSelected?: boolean
|
|
9
|
+
): void;
|
|
10
|
+
clearSelection(): void;
|
|
11
|
+
createSelectionOverlays(elements: Element[], elementId: string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ToggleInlineEditData {
|
|
15
|
+
dataSourceLocation: string;
|
|
16
|
+
inlineEditingMode: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface InlineEditController {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
isEditing(): boolean;
|
|
22
|
+
getCurrentElement(): HTMLElement | null;
|
|
23
|
+
canEdit(element: Element): boolean;
|
|
24
|
+
startEditing(element: HTMLElement): void;
|
|
25
|
+
stopEditing(): void;
|
|
26
|
+
markElementsSelected(elements: Element[]): void;
|
|
27
|
+
clearSelectedMarks(elementId: string | null): void;
|
|
28
|
+
handleToggleMessage(data: ToggleInlineEditData): void;
|
|
29
|
+
cleanup(): void;
|
|
30
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
|
|
2
2
|
import { createLayerController } from "./layer-dropdown/controller.js";
|
|
3
3
|
import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
|
|
4
|
+
import { createInlineEditController } from "../capabilities/inline-edit/index.js";
|
|
4
5
|
|
|
5
6
|
export function setupVisualEditAgent() {
|
|
6
7
|
// State variables (replacing React useState/useRef)
|
|
@@ -12,6 +13,8 @@ export function setupVisualEditAgent() {
|
|
|
12
13
|
let currentHighlightedElements: Element[] = [];
|
|
13
14
|
let selectedElementId: string | null = null;
|
|
14
15
|
|
|
16
|
+
const REPOSITION_DELAY_MS = 50;
|
|
17
|
+
|
|
15
18
|
// Create overlay element
|
|
16
19
|
const createOverlay = (isSelected = false): HTMLDivElement => {
|
|
17
20
|
const overlay = document.createElement("div");
|
|
@@ -69,6 +72,34 @@ export function setupVisualEditAgent() {
|
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
// --- Inline edit controller ---
|
|
76
|
+
const inlineEdit = createInlineEditController({
|
|
77
|
+
findElementsById,
|
|
78
|
+
getSelectedElementId: () => selectedElementId,
|
|
79
|
+
getSelectedOverlays: () => selectedOverlays,
|
|
80
|
+
positionOverlay,
|
|
81
|
+
clearSelection: () => {
|
|
82
|
+
inlineEdit.clearSelectedMarks(selectedElementId);
|
|
83
|
+
clearSelectedOverlays();
|
|
84
|
+
selectedElementId = null;
|
|
85
|
+
},
|
|
86
|
+
createSelectionOverlays: (elements, elementId) => {
|
|
87
|
+
elements.forEach((el) => {
|
|
88
|
+
const overlay = createOverlay(true);
|
|
89
|
+
document.body.appendChild(overlay);
|
|
90
|
+
selectedOverlays.push(overlay);
|
|
91
|
+
positionOverlay(overlay, el, true);
|
|
92
|
+
});
|
|
93
|
+
selectedElementId = elementId;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const clearSelection = () => {
|
|
98
|
+
inlineEdit.clearSelectedMarks(selectedElementId);
|
|
99
|
+
clearSelectedOverlays();
|
|
100
|
+
selectedElementId = null;
|
|
101
|
+
};
|
|
102
|
+
|
|
72
103
|
// Clear hover overlays
|
|
73
104
|
const clearHoverOverlays = () => {
|
|
74
105
|
hoverOverlays.forEach((overlay) => {
|
|
@@ -96,6 +127,13 @@ export function setupVisualEditAgent() {
|
|
|
96
127
|
const rect = element.getBoundingClientRect();
|
|
97
128
|
const svgElement = element as SVGElement;
|
|
98
129
|
const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
|
|
130
|
+
|
|
131
|
+
const arrEl = htmlElement.closest("[data-arr-variable-name]") as HTMLElement | null;
|
|
132
|
+
const staticArrayName = arrEl?.dataset?.arrVariableName || null;
|
|
133
|
+
const rawIdx = arrEl?.dataset?.arrIndex;
|
|
134
|
+
const staticArrayIndex = rawIdx != null ? parseInt(rawIdx, 10) : null;
|
|
135
|
+
const staticArrayField = htmlElement.dataset?.arrField || null;
|
|
136
|
+
|
|
99
137
|
window.parent.postMessage({
|
|
100
138
|
type: "element-selected",
|
|
101
139
|
tagName: element.tagName,
|
|
@@ -121,6 +159,9 @@ export function setupVisualEditAgent() {
|
|
|
121
159
|
},
|
|
122
160
|
attributes: collectAllowedAttributes(element, ALLOWED_ATTRIBUTES),
|
|
123
161
|
isTextElement,
|
|
162
|
+
staticArrayName,
|
|
163
|
+
staticArrayIndex,
|
|
164
|
+
staticArrayField,
|
|
124
165
|
}, "*");
|
|
125
166
|
};
|
|
126
167
|
|
|
@@ -152,7 +193,8 @@ export function setupVisualEditAgent() {
|
|
|
152
193
|
|
|
153
194
|
// Handle mouse over event
|
|
154
195
|
const handleMouseOver = (e: MouseEvent) => {
|
|
155
|
-
if (!isVisualEditMode || isPopoverDragging) return;
|
|
196
|
+
if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
|
|
197
|
+
|
|
156
198
|
|
|
157
199
|
const target = e.target as Element;
|
|
158
200
|
|
|
@@ -221,6 +263,21 @@ export function setupVisualEditAgent() {
|
|
|
221
263
|
// Let layer dropdown clicks pass through without interference
|
|
222
264
|
if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
|
|
223
265
|
|
|
266
|
+
// Let clicks inside the editable element pass through to the browser
|
|
267
|
+
// so the user can reposition the cursor and select text naturally.
|
|
268
|
+
if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Clicking outside the editable element exits inline editing mode.
|
|
273
|
+
if (inlineEdit.isEditing()) {
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
e.stopPropagation();
|
|
276
|
+
e.stopImmediatePropagation();
|
|
277
|
+
inlineEdit.stopEditing();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
224
281
|
// Close dropdowns when clicking anywhere in iframe if a dropdown is open
|
|
225
282
|
if (isDropdownOpen) {
|
|
226
283
|
e.preventDefault();
|
|
@@ -249,14 +306,31 @@ export function setupVisualEditAgent() {
|
|
|
249
306
|
return;
|
|
250
307
|
}
|
|
251
308
|
|
|
309
|
+
const htmlElement = element as HTMLElement;
|
|
310
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
311
|
+
|
|
312
|
+
const isAlreadySelected =
|
|
313
|
+
selectedElementId === visualSelectorId &&
|
|
314
|
+
htmlElement.dataset.selected === "true";
|
|
315
|
+
|
|
316
|
+
if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
|
|
317
|
+
inlineEdit.startEditing(htmlElement);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
inlineEdit.stopEditing();
|
|
322
|
+
|
|
323
|
+
if (inlineEdit.enabled) {
|
|
324
|
+
inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
|
|
325
|
+
}
|
|
326
|
+
|
|
252
327
|
const selectedOverlay = selectElement(element);
|
|
253
328
|
layerController.attachToOverlay(selectedOverlay, element);
|
|
254
329
|
};
|
|
255
330
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
selectedElementId = null;
|
|
331
|
+
const unselectElement = () => {
|
|
332
|
+
inlineEdit.stopEditing();
|
|
333
|
+
clearSelection();
|
|
260
334
|
};
|
|
261
335
|
|
|
262
336
|
const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
|
|
@@ -288,7 +362,7 @@ export function setupVisualEditAgent() {
|
|
|
288
362
|
});
|
|
289
363
|
}
|
|
290
364
|
}
|
|
291
|
-
},
|
|
365
|
+
}, REPOSITION_DELAY_MS);
|
|
292
366
|
};
|
|
293
367
|
|
|
294
368
|
// Update element attribute by visual selector ID
|
|
@@ -311,17 +385,22 @@ export function setupVisualEditAgent() {
|
|
|
311
385
|
}
|
|
312
386
|
});
|
|
313
387
|
}
|
|
314
|
-
},
|
|
388
|
+
}, REPOSITION_DELAY_MS);
|
|
315
389
|
};
|
|
316
390
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const elements = findElementsById(visualSelectorId);
|
|
391
|
+
const updateElementContent = (visualSelectorId: string, content: string, arrIndex?: number) => {
|
|
392
|
+
let elements = findElementsById(visualSelectorId);
|
|
320
393
|
|
|
321
394
|
if (elements.length === 0) {
|
|
322
395
|
return;
|
|
323
396
|
}
|
|
324
397
|
|
|
398
|
+
if (arrIndex != null) {
|
|
399
|
+
elements = elements.filter(
|
|
400
|
+
(el) => (el as HTMLElement).dataset.arrIndex === String(arrIndex)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
325
404
|
elements.forEach((element) => {
|
|
326
405
|
(element as HTMLElement).innerText = content;
|
|
327
406
|
});
|
|
@@ -334,7 +413,7 @@ export function setupVisualEditAgent() {
|
|
|
334
413
|
}
|
|
335
414
|
});
|
|
336
415
|
}
|
|
337
|
-
},
|
|
416
|
+
}, REPOSITION_DELAY_MS);
|
|
338
417
|
};
|
|
339
418
|
|
|
340
419
|
// --- Layer dropdown controller ---
|
|
@@ -356,12 +435,12 @@ export function setupVisualEditAgent() {
|
|
|
356
435
|
isVisualEditMode = isEnabled;
|
|
357
436
|
|
|
358
437
|
if (!isEnabled) {
|
|
438
|
+
inlineEdit.stopEditing();
|
|
439
|
+
clearSelection();
|
|
359
440
|
layerController.cleanup();
|
|
360
441
|
clearHoverOverlays();
|
|
361
|
-
clearSelectedOverlays();
|
|
362
442
|
|
|
363
443
|
currentHighlightedElements = [];
|
|
364
|
-
selectedElementId = null;
|
|
365
444
|
document.body.style.cursor = "default";
|
|
366
445
|
|
|
367
446
|
document.removeEventListener("mouseover", handleMouseOver);
|
|
@@ -422,6 +501,9 @@ export function setupVisualEditAgent() {
|
|
|
422
501
|
switch (message.type) {
|
|
423
502
|
case "toggle-visual-edit-mode":
|
|
424
503
|
toggleVisualEditMode(message.data.enabled);
|
|
504
|
+
if (message.data.specs?.newInlineEditEnabled !== undefined) {
|
|
505
|
+
inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
|
|
506
|
+
}
|
|
425
507
|
break;
|
|
426
508
|
|
|
427
509
|
case "update-classes":
|
|
@@ -459,7 +541,7 @@ export function setupVisualEditAgent() {
|
|
|
459
541
|
break;
|
|
460
542
|
|
|
461
543
|
case "unselect-element":
|
|
462
|
-
|
|
544
|
+
unselectElement();
|
|
463
545
|
break;
|
|
464
546
|
|
|
465
547
|
case "refresh-page":
|
|
@@ -470,7 +552,8 @@ export function setupVisualEditAgent() {
|
|
|
470
552
|
if (message.data && message.data.content !== undefined) {
|
|
471
553
|
updateElementContent(
|
|
472
554
|
message.data.visualSelectorId,
|
|
473
|
-
message.data.content
|
|
555
|
+
message.data.content,
|
|
556
|
+
message.data.arrIndex
|
|
474
557
|
);
|
|
475
558
|
} else {
|
|
476
559
|
console.warn(
|
|
@@ -537,6 +620,12 @@ export function setupVisualEditAgent() {
|
|
|
537
620
|
}
|
|
538
621
|
break;
|
|
539
622
|
|
|
623
|
+
case "toggle-inline-edit-mode":
|
|
624
|
+
if (message.data) {
|
|
625
|
+
inlineEdit.handleToggleMessage(message.data);
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
|
|
540
629
|
default:
|
|
541
630
|
break;
|
|
542
631
|
}
|
|
@@ -601,7 +690,7 @@ export function setupVisualEditAgent() {
|
|
|
601
690
|
});
|
|
602
691
|
|
|
603
692
|
if (needsUpdate) {
|
|
604
|
-
setTimeout(handleResize,
|
|
693
|
+
setTimeout(handleResize, REPOSITION_DELAY_MS);
|
|
605
694
|
}
|
|
606
695
|
});
|
|
607
696
|
|