@base44/vite-plugin 1.0.21 → 1.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/vite-plugin",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "The Vite plugin for base44 based applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -50,6 +50,7 @@ export default function vitePlugin(
50
50
  : {}),
51
51
  ...(isRunningInSandbox
52
52
  ? ({
53
+ logLevel: "error",
53
54
  server: {
54
55
  cors: true,
55
56
  host: "0.0.0.0", // Bind to all interfaces for container access
@@ -212,3 +212,40 @@ export function resolveHoverTarget(x: number, y: number, selectedElementId: stri
212
212
 
213
213
  return selectorId;
214
214
  }
215
+
216
+ const VISUAL_EDIT_FONT_FACE_ID_PREFIX = "visual-edit-font-face-";
217
+
218
+ function isValidFontFaceCss(css: string): boolean {
219
+ const trimmed = css.trim();
220
+ if (!trimmed.startsWith("@font-face")) return false;
221
+
222
+ if (trimmed.includes("\\")) return false; // Prevent CSS escapes
223
+
224
+ const openBraces = (trimmed.match(/{/g) || []).length;
225
+ const closeBraces = (trimmed.match(/}/g) || []).length;
226
+ if (openBraces !== 1 || closeBraces !== 1 || !trimmed.endsWith("}")) return false;
227
+
228
+ const chunks = trimmed.split(/url\(/i);
229
+ for (let i = 1; i < chunks.length; i++) {
230
+ const end = chunks[i]!.indexOf(")");
231
+ if (end === -1) return false;
232
+ const url = chunks[i]!.slice(0, end).trim().replace(/^['"]|['"]$/g, "").trim();
233
+ if (!/^https:\/\//i.test(url)) return false;
234
+ }
235
+
236
+ return true;
237
+ }
238
+
239
+ export function injectFontFaceCss(id: string, css: string): void {
240
+ if (typeof id !== "string" || !id.trim()) return;
241
+ if (typeof css !== "string" || !isValidFontFaceCss(css)) return;
242
+
243
+ const styleId = `${VISUAL_EDIT_FONT_FACE_ID_PREFIX}${id}`;
244
+ if (document.getElementById(styleId)) return;
245
+
246
+ const style = document.createElement("style");
247
+ style.id = styleId;
248
+ style.dataset.visualEditFontFace = "true";
249
+ style.textContent = css;
250
+ document.head.appendChild(style);
251
+ }
@@ -1,4 +1,4 @@
1
- import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel } from "./utils.js";
1
+ import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
4
  import { createInlineEditController } from "../capabilities/inline-edit/index.js";
@@ -648,6 +648,12 @@ export function setupVisualEditAgent() {
648
648
  }
649
649
  break;
650
650
 
651
+ case "inject-font-face":
652
+ if (message.data) {
653
+ injectFontFaceCss(message.data.id, message.data.css);
654
+ }
655
+ break;
656
+
651
657
  case "toggle-inline-edit-mode":
652
658
  if (message.data) {
653
659
  inlineEdit.handleToggleMessage(message.data);
@@ -128,6 +128,9 @@ export class StaticArrayProcessor {
128
128
  expr: NodePath<t.MemberExpression>,
129
129
  callbackParam: string
130
130
  ): string | null {
131
+ const keyedLookup = this.extractKeyedLookupFieldPath(expr, callbackParam);
132
+ if (keyedLookup) return keyedLookup;
133
+
131
134
  const parts = this.collectMemberExpressionParts(expr);
132
135
  if (!parts) return null;
133
136
 
@@ -135,6 +138,20 @@ export class StaticArrayProcessor {
135
138
  return rootName === callbackParam ? propertyNames.join(".") : null;
136
139
  }
137
140
 
141
+ private extractKeyedLookupFieldPath(
142
+ expr: NodePath<t.MemberExpression>,
143
+ callbackParam: string
144
+ ): string | null {
145
+ const object = expr.get("object");
146
+ if (!expr.node.computed || !object.isIdentifier()) return null;
147
+
148
+ const property = expr.get("property");
149
+ if (!property.isMemberExpression()) return null;
150
+
151
+ const keyFieldPath = this.extractFieldPath(property, callbackParam);
152
+ return keyFieldPath ? `${object.node.name}[${keyFieldPath}]` : null;
153
+ }
154
+
138
155
  private collectMemberExpressionParts(
139
156
  expr: NodePath<t.MemberExpression>
140
157
  ): { rootName: string; propertyNames: string[] } | null {