@base44-preview/vite-plugin 0.2.26-pr.43.e9195be → 0.2.27-pr.43.93e5e43
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 +194 -0
- package/dist/capabilities/inline-edit/controller.js.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts +6 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.js +49 -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 +72 -12
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/processors/collection-item-field-processor.d.ts +8 -2
- package/dist/processors/collection-item-field-processor.d.ts.map +1 -1
- package/dist/processors/collection-item-field-processor.js +19 -13
- package/dist/processors/collection-item-field-processor.js.map +1 -1
- package/dist/processors/collection-tracing-utils.d.ts +5 -0
- package/dist/processors/collection-tracing-utils.d.ts.map +1 -1
- package/dist/processors/collection-tracing-utils.js +66 -2
- package/dist/processors/collection-tracing-utils.js.map +1 -1
- package/dist/processors/shared-utils.d.ts +13 -0
- package/dist/processors/shared-utils.d.ts.map +1 -1
- package/dist/processors/shared-utils.js +57 -0
- package/dist/processors/shared-utils.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 +245 -0
- package/src/capabilities/inline-edit/dom-utils.ts +48 -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 +85 -12
- package/src/processors/collection-item-field-processor.ts +49 -26
- package/src/processors/collection-tracing-utils.ts +73 -2
- package/src/processors/shared-utils.ts +75 -0
|
@@ -0,0 +1,48 @@
|
|
|
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 injectFocusOutlineCSS = () => {
|
|
9
|
+
if (document.getElementById(FOCUS_STYLE_ID)) return;
|
|
10
|
+
|
|
11
|
+
const style = document.createElement("style");
|
|
12
|
+
style.id = FOCUS_STYLE_ID;
|
|
13
|
+
style.textContent = `
|
|
14
|
+
[data-selected="true"][contenteditable="true"]:focus {
|
|
15
|
+
outline: none !important;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
document.head.appendChild(style);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const removeFocusOutlineCSS = () => {
|
|
22
|
+
document.getElementById(FOCUS_STYLE_ID)?.remove();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const selectText = (element: HTMLElement) => {
|
|
26
|
+
const range = document.createRange();
|
|
27
|
+
range.selectNodeContents(element);
|
|
28
|
+
const selection = window.getSelection();
|
|
29
|
+
selection?.removeAllRanges();
|
|
30
|
+
selection?.addRange(range);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const isEditableTextElement = (element: Element): boolean => {
|
|
34
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
35
|
+
if (!EDITABLE_TAGS.includes(element.tagName.toLowerCase())) return false;
|
|
36
|
+
if (!element.textContent?.trim()) return false;
|
|
37
|
+
if (element.querySelector("img, video, canvas, svg")) return false;
|
|
38
|
+
if (element.children?.length > 0) return false;
|
|
39
|
+
if (element.dataset.dynamicContent === "true") return false;
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const shouldEnterInlineEditingMode = (element: Element): boolean => {
|
|
44
|
+
if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return isEditableTextElement(element);
|
|
48
|
+
};
|
|
@@ -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) => {
|
|
@@ -152,7 +183,8 @@ export function setupVisualEditAgent() {
|
|
|
152
183
|
|
|
153
184
|
// Handle mouse over event
|
|
154
185
|
const handleMouseOver = (e: MouseEvent) => {
|
|
155
|
-
if (!isVisualEditMode || isPopoverDragging) return;
|
|
186
|
+
if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
|
|
187
|
+
|
|
156
188
|
|
|
157
189
|
const target = e.target as Element;
|
|
158
190
|
|
|
@@ -221,6 +253,21 @@ export function setupVisualEditAgent() {
|
|
|
221
253
|
// Let layer dropdown clicks pass through without interference
|
|
222
254
|
if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
|
|
223
255
|
|
|
256
|
+
// Let clicks inside the editable element pass through to the browser
|
|
257
|
+
// so the user can reposition the cursor and select text naturally.
|
|
258
|
+
if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clicking outside the editable element exits inline editing mode.
|
|
263
|
+
if (inlineEdit.isEditing()) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
e.stopImmediatePropagation();
|
|
267
|
+
inlineEdit.stopEditing();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
224
271
|
// Close dropdowns when clicking anywhere in iframe if a dropdown is open
|
|
225
272
|
if (isDropdownOpen) {
|
|
226
273
|
e.preventDefault();
|
|
@@ -249,14 +296,31 @@ export function setupVisualEditAgent() {
|
|
|
249
296
|
return;
|
|
250
297
|
}
|
|
251
298
|
|
|
299
|
+
const htmlElement = element as HTMLElement;
|
|
300
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
301
|
+
|
|
302
|
+
const isAlreadySelected =
|
|
303
|
+
selectedElementId === visualSelectorId &&
|
|
304
|
+
htmlElement.dataset.selected === "true";
|
|
305
|
+
|
|
306
|
+
if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
|
|
307
|
+
inlineEdit.startEditing(htmlElement);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
inlineEdit.stopEditing();
|
|
312
|
+
|
|
313
|
+
if (inlineEdit.enabled) {
|
|
314
|
+
inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
|
|
315
|
+
}
|
|
316
|
+
|
|
252
317
|
const selectedOverlay = selectElement(element);
|
|
253
318
|
layerController.attachToOverlay(selectedOverlay, element);
|
|
254
319
|
};
|
|
255
320
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
selectedElementId = null;
|
|
321
|
+
const unselectElement = () => {
|
|
322
|
+
inlineEdit.stopEditing();
|
|
323
|
+
clearSelection();
|
|
260
324
|
};
|
|
261
325
|
|
|
262
326
|
const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
|
|
@@ -288,7 +352,7 @@ export function setupVisualEditAgent() {
|
|
|
288
352
|
});
|
|
289
353
|
}
|
|
290
354
|
}
|
|
291
|
-
},
|
|
355
|
+
}, REPOSITION_DELAY_MS);
|
|
292
356
|
};
|
|
293
357
|
|
|
294
358
|
// Update element attribute by visual selector ID
|
|
@@ -311,7 +375,7 @@ export function setupVisualEditAgent() {
|
|
|
311
375
|
}
|
|
312
376
|
});
|
|
313
377
|
}
|
|
314
|
-
},
|
|
378
|
+
}, REPOSITION_DELAY_MS);
|
|
315
379
|
};
|
|
316
380
|
|
|
317
381
|
// Update element content by visual selector ID
|
|
@@ -334,7 +398,7 @@ export function setupVisualEditAgent() {
|
|
|
334
398
|
}
|
|
335
399
|
});
|
|
336
400
|
}
|
|
337
|
-
},
|
|
401
|
+
}, REPOSITION_DELAY_MS);
|
|
338
402
|
};
|
|
339
403
|
|
|
340
404
|
// --- Layer dropdown controller ---
|
|
@@ -356,12 +420,12 @@ export function setupVisualEditAgent() {
|
|
|
356
420
|
isVisualEditMode = isEnabled;
|
|
357
421
|
|
|
358
422
|
if (!isEnabled) {
|
|
423
|
+
inlineEdit.stopEditing();
|
|
424
|
+
clearSelection();
|
|
359
425
|
layerController.cleanup();
|
|
360
426
|
clearHoverOverlays();
|
|
361
|
-
clearSelectedOverlays();
|
|
362
427
|
|
|
363
428
|
currentHighlightedElements = [];
|
|
364
|
-
selectedElementId = null;
|
|
365
429
|
document.body.style.cursor = "default";
|
|
366
430
|
|
|
367
431
|
document.removeEventListener("mouseover", handleMouseOver);
|
|
@@ -422,6 +486,9 @@ export function setupVisualEditAgent() {
|
|
|
422
486
|
switch (message.type) {
|
|
423
487
|
case "toggle-visual-edit-mode":
|
|
424
488
|
toggleVisualEditMode(message.data.enabled);
|
|
489
|
+
if (message.data.specs?.newInlineEditEnabled !== undefined) {
|
|
490
|
+
inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
|
|
491
|
+
}
|
|
425
492
|
break;
|
|
426
493
|
|
|
427
494
|
case "update-classes":
|
|
@@ -459,7 +526,7 @@ export function setupVisualEditAgent() {
|
|
|
459
526
|
break;
|
|
460
527
|
|
|
461
528
|
case "unselect-element":
|
|
462
|
-
|
|
529
|
+
unselectElement();
|
|
463
530
|
break;
|
|
464
531
|
|
|
465
532
|
case "refresh-page":
|
|
@@ -537,6 +604,12 @@ export function setupVisualEditAgent() {
|
|
|
537
604
|
}
|
|
538
605
|
break;
|
|
539
606
|
|
|
607
|
+
case "toggle-inline-edit-mode":
|
|
608
|
+
if (message.data) {
|
|
609
|
+
inlineEdit.handleToggleMessage(message.data);
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
|
|
540
613
|
default:
|
|
541
614
|
break;
|
|
542
615
|
}
|
|
@@ -601,7 +674,7 @@ export function setupVisualEditAgent() {
|
|
|
601
674
|
});
|
|
602
675
|
|
|
603
676
|
if (needsUpdate) {
|
|
604
|
-
setTimeout(handleResize,
|
|
677
|
+
setTimeout(handleResize, REPOSITION_DELAY_MS);
|
|
605
678
|
}
|
|
606
679
|
});
|
|
607
680
|
|
|
@@ -229,21 +229,12 @@ export class DataItemFieldProcessor {
|
|
|
229
229
|
|
|
230
230
|
const idField = this.resolveIdFieldName(path);
|
|
231
231
|
|
|
232
|
-
// Simple identifier: {name} from ({ name, price }) => ...
|
|
233
|
-
// rootIdentifier IS the field value (a string), not an object with ._id/.id.
|
|
234
|
-
// We need to find the id field from sibling destructured params instead.
|
|
235
232
|
if (rootIdentifier.name === fieldPath) {
|
|
236
233
|
this.tryAddItemIdFromDestructuredParams(path, idField);
|
|
237
234
|
return;
|
|
238
235
|
}
|
|
239
236
|
|
|
240
|
-
|
|
241
|
-
const idExpr = this.types.optionalMemberExpression(
|
|
242
|
-
this.types.identifier(rootIdentifier.name),
|
|
243
|
-
this.types.identifier(idField),
|
|
244
|
-
false,
|
|
245
|
-
true
|
|
246
|
-
);
|
|
237
|
+
const idExpr = this.buildIdExpression(rootIdentifier.name, idField);
|
|
247
238
|
|
|
248
239
|
const binding = path.scope.getBinding(rootIdentifier.name);
|
|
249
240
|
if (!binding) {
|
|
@@ -264,12 +255,47 @@ export class DataItemFieldProcessor {
|
|
|
264
255
|
}
|
|
265
256
|
|
|
266
257
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
258
|
+
* Build the ID expression for a given root object name.
|
|
259
|
+
* If a specific idField was resolved from a key attribute, generates root?.id (or root?._id).
|
|
260
|
+
* If unknown (cross-file component), generates root?.id || root?._id to cover both conventions.
|
|
261
|
+
*/
|
|
262
|
+
private buildIdExpression(
|
|
263
|
+
rootName: string,
|
|
264
|
+
idField: string | null
|
|
265
|
+
): t.Expression {
|
|
266
|
+
if (idField) {
|
|
267
|
+
return this.types.optionalMemberExpression(
|
|
268
|
+
this.types.identifier(rootName),
|
|
269
|
+
this.types.identifier(idField),
|
|
270
|
+
false,
|
|
271
|
+
true
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return this.types.logicalExpression(
|
|
276
|
+
"||",
|
|
277
|
+
this.types.optionalMemberExpression(
|
|
278
|
+
this.types.identifier(rootName),
|
|
279
|
+
this.types.identifier("id"),
|
|
280
|
+
false,
|
|
281
|
+
true
|
|
282
|
+
),
|
|
283
|
+
this.types.optionalMemberExpression(
|
|
284
|
+
this.types.identifier(rootName),
|
|
285
|
+
this.types.identifier("_id"),
|
|
286
|
+
false,
|
|
287
|
+
true
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Walk up the JSX tree to find a key attribute that accesses .id or ._id.
|
|
294
|
+
* Returns null when no key is found (e.g. cross-file component receiving props).
|
|
269
295
|
*/
|
|
270
296
|
private resolveIdFieldName(
|
|
271
297
|
path: NodePath<t.JSXOpeningElement>
|
|
272
|
-
): string {
|
|
298
|
+
): string | null {
|
|
273
299
|
let current: NodePath | null = path.parentPath;
|
|
274
300
|
while (current) {
|
|
275
301
|
if (current.isJSXElement()) {
|
|
@@ -279,7 +305,7 @@ export class DataItemFieldProcessor {
|
|
|
279
305
|
}
|
|
280
306
|
current = current.parentPath;
|
|
281
307
|
}
|
|
282
|
-
return
|
|
308
|
+
return null;
|
|
283
309
|
}
|
|
284
310
|
|
|
285
311
|
private extractIdFieldFromKey(
|
|
@@ -312,7 +338,7 @@ export class DataItemFieldProcessor {
|
|
|
312
338
|
*/
|
|
313
339
|
private tryAddItemIdFromDestructuredParams(
|
|
314
340
|
path: NodePath<t.JSXOpeningElement>,
|
|
315
|
-
idField: string
|
|
341
|
+
idField: string | null
|
|
316
342
|
): void {
|
|
317
343
|
const fn = path.getFunctionParent();
|
|
318
344
|
if (!fn) return;
|
|
@@ -321,7 +347,6 @@ export class DataItemFieldProcessor {
|
|
|
321
347
|
for (const param of Array.isArray(params) ? params : [params]) {
|
|
322
348
|
if (!param.isObjectPattern()) continue;
|
|
323
349
|
|
|
324
|
-
// Check if the id field is already destructured
|
|
325
350
|
for (const prop of param.get("properties")) {
|
|
326
351
|
if (
|
|
327
352
|
prop.isObjectProperty() &&
|
|
@@ -338,11 +363,11 @@ export class DataItemFieldProcessor {
|
|
|
338
363
|
}
|
|
339
364
|
}
|
|
340
365
|
|
|
341
|
-
|
|
366
|
+
const fieldToInject = idField ?? "id";
|
|
342
367
|
param.node.properties.push(
|
|
343
368
|
this.types.objectProperty(
|
|
344
|
-
this.types.identifier(
|
|
345
|
-
this.types.identifier(
|
|
369
|
+
this.types.identifier(fieldToInject),
|
|
370
|
+
this.types.identifier(fieldToInject),
|
|
346
371
|
false,
|
|
347
372
|
true
|
|
348
373
|
)
|
|
@@ -350,7 +375,7 @@ export class DataItemFieldProcessor {
|
|
|
350
375
|
this.attributeUtils.addExpressionAttribute(
|
|
351
376
|
path,
|
|
352
377
|
DATA_COLLECTION_ITEM_ID,
|
|
353
|
-
this.types.identifier(
|
|
378
|
+
this.types.identifier(fieldToInject)
|
|
354
379
|
);
|
|
355
380
|
return;
|
|
356
381
|
}
|
|
@@ -359,7 +384,7 @@ export class DataItemFieldProcessor {
|
|
|
359
384
|
private tryAddItemIdFromParentComponent(
|
|
360
385
|
path: NodePath<t.JSXOpeningElement>,
|
|
361
386
|
rootIdentifier: t.Identifier,
|
|
362
|
-
idField: string
|
|
387
|
+
idField: string | null
|
|
363
388
|
): void {
|
|
364
389
|
let current: NodePath | null = path.parentPath;
|
|
365
390
|
while (current) {
|
|
@@ -369,11 +394,9 @@ export class DataItemFieldProcessor {
|
|
|
369
394
|
if (name && JSXUtils.isCustomComponent(name)) {
|
|
370
395
|
const keyAttr = this.findKeyWithId(opening);
|
|
371
396
|
if (keyAttr) {
|
|
372
|
-
const idExpr = this.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
false,
|
|
376
|
-
true
|
|
397
|
+
const idExpr = this.buildIdExpression(
|
|
398
|
+
rootIdentifier.name,
|
|
399
|
+
idField
|
|
377
400
|
);
|
|
378
401
|
this.attributeUtils.addExpressionAttribute(
|
|
379
402
|
path,
|
|
@@ -189,6 +189,9 @@ export class CollectionTracingUtils {
|
|
|
189
189
|
const directResult = this.checkDirectServiceCall(path);
|
|
190
190
|
if (directResult) return directResult;
|
|
191
191
|
|
|
192
|
+
const useQueryResult = this.traceUseQueryCall(path);
|
|
193
|
+
if (useQueryResult) return useQueryResult;
|
|
194
|
+
|
|
192
195
|
if (this.callUtils.isChainedArrayMethod(path.node)) {
|
|
193
196
|
const callee = path.get("callee");
|
|
194
197
|
if (callee.isMemberExpression()) {
|
|
@@ -200,6 +203,51 @@ export class CollectionTracingUtils {
|
|
|
200
203
|
return null;
|
|
201
204
|
}
|
|
202
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Trace useQuery({ queryFn: () => base44.entities.X.list() }) patterns.
|
|
208
|
+
* Extracts the collection from the queryFn return expression.
|
|
209
|
+
*/
|
|
210
|
+
private traceUseQueryCall(
|
|
211
|
+
path: NodePath<t.CallExpression>
|
|
212
|
+
): CollectionInfo | null {
|
|
213
|
+
const callee = path.get("callee");
|
|
214
|
+
if (!callee.isIdentifier() || callee.node.name !== "useQuery") return null;
|
|
215
|
+
|
|
216
|
+
const args = path.get("arguments");
|
|
217
|
+
const configArg = args[0];
|
|
218
|
+
if (!configArg?.isObjectExpression()) return null;
|
|
219
|
+
|
|
220
|
+
for (const prop of configArg.get("properties")) {
|
|
221
|
+
if (!prop.isObjectProperty()) continue;
|
|
222
|
+
const key = prop.get("key");
|
|
223
|
+
if (!key.isIdentifier() || key.node.name !== "queryFn") continue;
|
|
224
|
+
|
|
225
|
+
const value = prop.get("value");
|
|
226
|
+
|
|
227
|
+
if (value.isArrowFunctionExpression() || value.isFunctionExpression()) {
|
|
228
|
+
const fnBody = value.get("body") as NodePath<t.Node>;
|
|
229
|
+
if (fnBody.isCallExpression()) {
|
|
230
|
+
return this.checkDirectServiceCall(fnBody as NodePath<t.CallExpression>);
|
|
231
|
+
}
|
|
232
|
+
if (fnBody.isBlockStatement()) {
|
|
233
|
+
let result: CollectionInfo | null = null;
|
|
234
|
+
fnBody.traverse({
|
|
235
|
+
ReturnStatement: (retPath: NodePath<t.ReturnStatement>) => {
|
|
236
|
+
if (result) return;
|
|
237
|
+
const arg = retPath.get("argument");
|
|
238
|
+
if (arg.isCallExpression()) {
|
|
239
|
+
result = this.checkDirectServiceCall(arg);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
if (result) return result;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
203
251
|
private checkDirectServiceCall(
|
|
204
252
|
path: NodePath<t.CallExpression>
|
|
205
253
|
): CollectionInfo | null {
|
|
@@ -219,6 +267,16 @@ export class CollectionTracingUtils {
|
|
|
219
267
|
};
|
|
220
268
|
}
|
|
221
269
|
|
|
270
|
+
const base44List = this.callUtils.isBase44EntityListCall(path.node);
|
|
271
|
+
if (base44List) {
|
|
272
|
+
return { id: base44List.collectionName, references: [] };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const base44Get = this.callUtils.isBase44EntityGetCall(path.node);
|
|
276
|
+
if (base44Get) {
|
|
277
|
+
return { id: base44Get.collectionName, references: [] };
|
|
278
|
+
}
|
|
279
|
+
|
|
222
280
|
return null;
|
|
223
281
|
}
|
|
224
282
|
|
|
@@ -261,8 +319,12 @@ export class CollectionTracingUtils {
|
|
|
261
319
|
const prop = pattern.node.properties.find(
|
|
262
320
|
(p) =>
|
|
263
321
|
this.types.isObjectProperty(p) &&
|
|
264
|
-
|
|
265
|
-
|
|
322
|
+
(
|
|
323
|
+
(this.types.isIdentifier(p.value) && p.value.name === variableName) ||
|
|
324
|
+
(this.types.isAssignmentPattern(p.value) &&
|
|
325
|
+
this.types.isIdentifier(p.value.left) &&
|
|
326
|
+
p.value.left.name === variableName)
|
|
327
|
+
)
|
|
266
328
|
);
|
|
267
329
|
|
|
268
330
|
if (!prop || !this.types.isObjectProperty(prop)) return null;
|
|
@@ -365,6 +427,10 @@ export class CollectionTracingUtils {
|
|
|
365
427
|
},
|
|
366
428
|
});
|
|
367
429
|
|
|
430
|
+
if (!result) {
|
|
431
|
+
return { id: paramName, references: [] };
|
|
432
|
+
}
|
|
433
|
+
|
|
368
434
|
return result;
|
|
369
435
|
}
|
|
370
436
|
|
|
@@ -427,6 +493,11 @@ export class CollectionTracingUtils {
|
|
|
427
493
|
id: info.collectionName,
|
|
428
494
|
references: info.multiRefFields,
|
|
429
495
|
};
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const base44Get = this.callUtils.isBase44EntityGetCall(callPath.node);
|
|
499
|
+
if (base44Get) {
|
|
500
|
+
result = { id: base44Get.collectionName, references: [] };
|
|
430
501
|
}
|
|
431
502
|
},
|
|
432
503
|
});
|
|
@@ -530,6 +530,81 @@ export class CallExpressionUtils {
|
|
|
530
530
|
return chainMethods.some((m) => this.isArrayMethod(node, m));
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Detect base44.entities.EntityName.list() or .getAll() patterns.
|
|
535
|
+
* Returns the entity name as the collection name.
|
|
536
|
+
*/
|
|
537
|
+
isBase44EntityListCall(
|
|
538
|
+
node: t.CallExpression
|
|
539
|
+
): { collectionName: string } | null {
|
|
540
|
+
const callee = node.callee;
|
|
541
|
+
if (!this.types.isMemberExpression(callee)) return null;
|
|
542
|
+
|
|
543
|
+
const method = callee.property;
|
|
544
|
+
if (
|
|
545
|
+
!this.types.isIdentifier(method) ||
|
|
546
|
+
(method.name !== "list" && method.name !== "getAll")
|
|
547
|
+
) {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const entityAccess = callee.object;
|
|
552
|
+
if (!this.types.isMemberExpression(entityAccess)) return null;
|
|
553
|
+
|
|
554
|
+
const entityName = entityAccess.property;
|
|
555
|
+
if (!this.types.isIdentifier(entityName)) return null;
|
|
556
|
+
|
|
557
|
+
const entitiesAccess = entityAccess.object;
|
|
558
|
+
if (!this.types.isMemberExpression(entitiesAccess)) return null;
|
|
559
|
+
|
|
560
|
+
const entitiesProp = entitiesAccess.property;
|
|
561
|
+
if (
|
|
562
|
+
!this.types.isIdentifier(entitiesProp) ||
|
|
563
|
+
entitiesProp.name !== "entities"
|
|
564
|
+
) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { collectionName: entityName.name };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Detect base44.entities.EntityName.getById() or .get() patterns.
|
|
573
|
+
*/
|
|
574
|
+
isBase44EntityGetCall(
|
|
575
|
+
node: t.CallExpression
|
|
576
|
+
): { collectionName: string } | null {
|
|
577
|
+
const callee = node.callee;
|
|
578
|
+
if (!this.types.isMemberExpression(callee)) return null;
|
|
579
|
+
|
|
580
|
+
const method = callee.property;
|
|
581
|
+
if (
|
|
582
|
+
!this.types.isIdentifier(method) ||
|
|
583
|
+
(method.name !== "get" && method.name !== "getById")
|
|
584
|
+
) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const entityAccess = callee.object;
|
|
589
|
+
if (!this.types.isMemberExpression(entityAccess)) return null;
|
|
590
|
+
|
|
591
|
+
const entityName = entityAccess.property;
|
|
592
|
+
if (!this.types.isIdentifier(entityName)) return null;
|
|
593
|
+
|
|
594
|
+
const entitiesAccess = entityAccess.object;
|
|
595
|
+
if (!this.types.isMemberExpression(entitiesAccess)) return null;
|
|
596
|
+
|
|
597
|
+
const entitiesProp = entitiesAccess.property;
|
|
598
|
+
if (
|
|
599
|
+
!this.types.isIdentifier(entitiesProp) ||
|
|
600
|
+
entitiesProp.name !== "entities"
|
|
601
|
+
) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return { collectionName: entityName.name };
|
|
606
|
+
}
|
|
607
|
+
|
|
533
608
|
getCallbackArgument(
|
|
534
609
|
callExpr: t.CallExpression
|
|
535
610
|
): t.ArrowFunctionExpression | t.FunctionExpression | null {
|