@yoamigo.com/core 0.1.0

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/lib.js ADDED
@@ -0,0 +1,736 @@
1
+ // src/lib/content-registry.ts
2
+ var contentMap = /* @__PURE__ */ new Map();
3
+ function registerContent(content) {
4
+ contentMap = new Map(Object.entries(content));
5
+ }
6
+ function getContent(fieldId) {
7
+ return contentMap.get(fieldId) ?? "";
8
+ }
9
+ function getAllContent() {
10
+ return Object.fromEntries(contentMap);
11
+ }
12
+ function hasContent(fieldId) {
13
+ return contentMap.has(fieldId);
14
+ }
15
+ var contentRegistry = {
16
+ registerContent,
17
+ getContent,
18
+ getAllContent,
19
+ hasContent
20
+ };
21
+
22
+ // src/lib/asset-resolver.ts
23
+ var assetResolver = (path) => path;
24
+ function setAssetResolver(resolver) {
25
+ assetResolver = resolver;
26
+ }
27
+ function resolveAssetUrl(path) {
28
+ if (!path) return "";
29
+ if (path.startsWith("http://") || path.startsWith("https://")) {
30
+ return path;
31
+ }
32
+ return assetResolver(path);
33
+ }
34
+
35
+ // src/lib/builder-selection.ts
36
+ var SELECTABLE_SELECTORS = [
37
+ // Interactive elements
38
+ "button",
39
+ // Buttons
40
+ "a[href]",
41
+ // Links with href
42
+ '[role="button"]',
43
+ // ARIA buttons
44
+ // Form elements
45
+ "input",
46
+ // Input fields (text, checkbox, etc.)
47
+ "textarea",
48
+ // Text areas
49
+ "select",
50
+ // Dropdown selects
51
+ "label",
52
+ // Form labels
53
+ "form",
54
+ // Forms as containers
55
+ // Media elements
56
+ "img",
57
+ // Images
58
+ "video",
59
+ // Videos
60
+ "audio",
61
+ // Audio players
62
+ "iframe",
63
+ // Embedded content
64
+ "svg",
65
+ // SVG graphics
66
+ "figure",
67
+ // Figures with captions
68
+ "figcaption",
69
+ // Figure captions
70
+ // Text/content elements
71
+ "h1",
72
+ "h2",
73
+ "h3",
74
+ "h4",
75
+ "h5",
76
+ "h6",
77
+ // Headings
78
+ "p",
79
+ // Paragraphs
80
+ "blockquote",
81
+ // Block quotes
82
+ "table",
83
+ // Tables
84
+ "tr",
85
+ // Table rows
86
+ "td",
87
+ // Table cells
88
+ "th",
89
+ // Table headers
90
+ // List elements
91
+ "li",
92
+ // List items (event cards, etc.)
93
+ "ol",
94
+ // Ordered lists
95
+ "ul",
96
+ // Unordered lists
97
+ // Other
98
+ "article",
99
+ // Article cards
100
+ "[data-mp-selectable]"
101
+ // Explicit selectables (backward compatible)
102
+ ];
103
+ var CONTAINER_SELECTORS = [
104
+ "section",
105
+ "[data-mp-editable]",
106
+ "nav",
107
+ "header",
108
+ "footer"
109
+ ];
110
+ var BuilderSelectionManager = class {
111
+ enabled = false;
112
+ selections = /* @__PURE__ */ new Map();
113
+ hoverOverlay = null;
114
+ currentHoveredElement = null;
115
+ // Cache element references for lookup when syncing selections
116
+ elementMap = /* @__PURE__ */ new Map();
117
+ // Current selections from parent (for re-rendering on mode change)
118
+ currentSelections = [];
119
+ constructor() {
120
+ if (window.parent === window) {
121
+ console.log("[BuilderSelection] Not in iframe, skipping initialization");
122
+ return;
123
+ }
124
+ console.log("[BuilderSelection] Initializing in iframe context");
125
+ this.setupMessageListener();
126
+ this.createHoverOverlay();
127
+ this.setupScrollResizeListeners();
128
+ this.setupKeyboardListener();
129
+ this.setupWheelForwarding();
130
+ this.notifyPageReady();
131
+ window.addEventListener("popstate", () => this.notifyPageReady());
132
+ }
133
+ notifyPageReady() {
134
+ this.sendToParent({
135
+ type: "PAGE_READY",
136
+ page: window.location.pathname
137
+ });
138
+ }
139
+ sendToParent(message) {
140
+ try {
141
+ window.parent.postMessage(message, "*");
142
+ } catch (error) {
143
+ console.error("[BuilderSelection] Failed to send message to parent:", error);
144
+ }
145
+ }
146
+ setupMessageListener() {
147
+ window.addEventListener("message", (event) => {
148
+ const data = event.data;
149
+ if (!data?.type) return;
150
+ switch (data.type) {
151
+ case "SELECTOR_MODE":
152
+ this.setEnabled(data.enabled ?? false);
153
+ break;
154
+ case "SELECTION_SYNC":
155
+ this.syncSelections(data.selections ?? [], data.currentPage ?? "");
156
+ break;
157
+ case "CLEAR_SELECTIONS":
158
+ this.clearAllSelections();
159
+ break;
160
+ }
161
+ });
162
+ }
163
+ setEnabled(enabled) {
164
+ console.log("[BuilderSelection] Selector mode:", enabled);
165
+ this.enabled = enabled;
166
+ window.__builderSelectModeEnabled = enabled;
167
+ if (enabled) {
168
+ document.body.style.cursor = "crosshair";
169
+ document.body.classList.add("builder-selector-active");
170
+ this.attachEventListeners();
171
+ this.renderSelectionMasks();
172
+ } else {
173
+ document.body.style.cursor = "";
174
+ document.body.classList.remove("builder-selector-active");
175
+ this.detachEventListeners();
176
+ this.hideHoverOverlay();
177
+ this.clearAllSelections();
178
+ }
179
+ }
180
+ attachEventListeners() {
181
+ document.addEventListener("mouseover", this.handleMouseOver);
182
+ document.addEventListener("mouseout", this.handleMouseOut);
183
+ document.addEventListener("click", this.handleClick, true);
184
+ }
185
+ detachEventListeners() {
186
+ document.removeEventListener("mouseover", this.handleMouseOver);
187
+ document.removeEventListener("mouseout", this.handleMouseOut);
188
+ document.removeEventListener("click", this.handleClick, true);
189
+ }
190
+ handleMouseOver = (e) => {
191
+ if (!this.enabled) return;
192
+ const target = this.findDeepestSelectableAt(e.target);
193
+ if (!target || target === this.currentHoveredElement) return;
194
+ this.currentHoveredElement = target;
195
+ this.showHoverOverlay(target);
196
+ const selectorId = this.generateSelectorId(target);
197
+ this.sendToParent({
198
+ type: "ELEMENT_HOVER",
199
+ selectorId
200
+ });
201
+ };
202
+ handleMouseOut = (e) => {
203
+ if (!this.enabled) return;
204
+ const relatedTarget = e.relatedTarget;
205
+ const stillInSelectable = relatedTarget ? this.findDeepestSelectableAt(relatedTarget) : null;
206
+ if (!stillInSelectable || stillInSelectable !== this.currentHoveredElement) {
207
+ this.currentHoveredElement = null;
208
+ this.hideHoverOverlay();
209
+ this.sendToParent({
210
+ type: "ELEMENT_HOVER",
211
+ selectorId: null
212
+ });
213
+ }
214
+ };
215
+ handleClick = (e) => {
216
+ if (!this.enabled) return;
217
+ const target = this.findDeepestSelectableAt(e.target);
218
+ if (!target) return;
219
+ e.preventDefault();
220
+ e.stopPropagation();
221
+ const selectorId = this.generateSelectorId(target);
222
+ const label = this.deriveLabel(target);
223
+ this.elementMap.set(selectorId, target);
224
+ this.sendToParent({
225
+ type: "ELEMENT_CLICKED",
226
+ selectorId,
227
+ label,
228
+ page: window.location.pathname,
229
+ modifier: e.shiftKey ? "shift" : e.metaKey || e.ctrlKey ? "cmd" : null
230
+ });
231
+ };
232
+ handleKeyDown = (e) => {
233
+ if (e.key === "Shift") {
234
+ const activeElement = document.activeElement;
235
+ const isEditing = activeElement?.closest(".ya-text-editing") || activeElement?.closest(".ya-link-editing");
236
+ if (!isEditing) {
237
+ this.sendToParent({ type: "SHIFT_KEY_PRESSED" });
238
+ }
239
+ }
240
+ };
241
+ /**
242
+ * Check if element matches any selectable selector
243
+ */
244
+ isSelectableElement(el) {
245
+ for (const selector of SELECTABLE_SELECTORS) {
246
+ if (el.matches(selector)) return true;
247
+ }
248
+ for (const selector of CONTAINER_SELECTORS) {
249
+ if (el.matches(selector)) return true;
250
+ }
251
+ return false;
252
+ }
253
+ /**
254
+ * Find the deepest selectable element from the click/hover target
255
+ * Walks up the DOM tree and returns the first (deepest) selectable found
256
+ */
257
+ findDeepestSelectableAt(target) {
258
+ if (target.closest(".mp-text-editing")) {
259
+ return null;
260
+ }
261
+ let current = target;
262
+ const selectables = [];
263
+ while (current && current !== document.body) {
264
+ if (this.isSelectableElement(current)) {
265
+ selectables.push(current);
266
+ }
267
+ current = current.parentElement;
268
+ }
269
+ return selectables[0] || null;
270
+ }
271
+ /**
272
+ * Generate a unique selector ID for an element
273
+ * Uses data-mp-selectable if available, otherwise generates from context
274
+ */
275
+ generateSelectorId(el) {
276
+ if (el.dataset.mpSelectable) return el.dataset.mpSelectable;
277
+ if (el.id) return el.id;
278
+ const tag = el.tagName.toLowerCase();
279
+ const parent = el.closest("section, [data-mp-selectable], [data-mp-editable]");
280
+ const parentId = parent?.dataset?.mpSelectable || parent?.dataset?.mpEditable || parent?.id || "page";
281
+ const siblings = parent ? Array.from(parent.querySelectorAll(tag)) : [];
282
+ const index = siblings.indexOf(el);
283
+ return `${parentId}.${tag}${index > 0 ? `.${index}` : ""}`;
284
+ }
285
+ /**
286
+ * Derive a human-readable label from the element
287
+ */
288
+ deriveLabel(el) {
289
+ if (el.dataset.mpSelectable) {
290
+ return this.toTitleCase(el.dataset.mpSelectable);
291
+ }
292
+ const tag = el.tagName.toLowerCase();
293
+ const text = el.textContent?.trim().slice(0, 30) || "";
294
+ if (tag === "button" || el.getAttribute("role") === "button") {
295
+ return text ? `"${text}" Button` : "Button";
296
+ }
297
+ if (tag === "a") {
298
+ return text ? `"${text}" Link` : "Link";
299
+ }
300
+ if (tag === "img") {
301
+ return el.alt || "Image";
302
+ }
303
+ if (tag === "video") {
304
+ return "Video";
305
+ }
306
+ if (tag === "audio") {
307
+ return "Audio Player";
308
+ }
309
+ if (tag === "iframe") {
310
+ return "Embedded Content";
311
+ }
312
+ if (tag === "svg") {
313
+ return "SVG Graphic";
314
+ }
315
+ if (tag === "figure") {
316
+ const caption = el.querySelector("figcaption")?.textContent?.trim();
317
+ return caption ? `Figure: ${caption.slice(0, 20)}` : "Figure";
318
+ }
319
+ if (tag === "figcaption") {
320
+ return text ? `Caption: ${text.slice(0, 20)}` : "Caption";
321
+ }
322
+ if (tag === "input") {
323
+ const type = el.type || "text";
324
+ const placeholder = el.placeholder;
325
+ if (type === "submit") return "Submit Button";
326
+ if (type === "checkbox") return "Checkbox";
327
+ if (type === "radio") return "Radio Button";
328
+ return placeholder ? `"${placeholder}" Input` : `${type.charAt(0).toUpperCase() + type.slice(1)} Input`;
329
+ }
330
+ if (tag === "textarea") {
331
+ const placeholder = el.placeholder;
332
+ return placeholder ? `"${placeholder}" Text Area` : "Text Area";
333
+ }
334
+ if (tag === "select") {
335
+ return "Dropdown";
336
+ }
337
+ if (tag === "label") {
338
+ return text ? `Label: ${text.slice(0, 20)}` : "Label";
339
+ }
340
+ if (tag === "form") {
341
+ return "Form";
342
+ }
343
+ if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
344
+ return text ? `Heading: ${text.slice(0, 25)}` : `${tag.toUpperCase()} Heading`;
345
+ }
346
+ if (tag === "p") {
347
+ return text ? `Paragraph: ${text.slice(0, 20)}...` : "Paragraph";
348
+ }
349
+ if (tag === "blockquote") {
350
+ return text ? `Quote: ${text.slice(0, 20)}...` : "Block Quote";
351
+ }
352
+ if (tag === "table") {
353
+ return "Table";
354
+ }
355
+ if (tag === "tr") {
356
+ return "Table Row";
357
+ }
358
+ if (tag === "td" || tag === "th") {
359
+ return text ? `Cell: ${text.slice(0, 15)}` : "Table Cell";
360
+ }
361
+ if (tag === "ol") {
362
+ return "Ordered List";
363
+ }
364
+ if (tag === "ul") {
365
+ return "Unordered List";
366
+ }
367
+ if (tag === "li") {
368
+ const preview = text.length > 20 ? text.slice(0, 20) + "..." : text;
369
+ return preview ? `List Item: ${preview}` : "List Item";
370
+ }
371
+ if (tag === "section") {
372
+ const heading = el.querySelector("h1, h2, h3")?.textContent?.trim();
373
+ return heading ? `${heading} Section` : "Section";
374
+ }
375
+ if (tag === "article") {
376
+ const heading = el.querySelector("h1, h2, h3, h4")?.textContent?.trim();
377
+ return heading ? heading : "Article";
378
+ }
379
+ if (tag === "nav") {
380
+ return "Navigation";
381
+ }
382
+ if (tag === "header") {
383
+ return "Header";
384
+ }
385
+ if (tag === "footer") {
386
+ return "Footer";
387
+ }
388
+ return text || this.toTitleCase(tag);
389
+ }
390
+ /**
391
+ * Convert kebab-case or tag names to Title Case
392
+ */
393
+ toTitleCase(str) {
394
+ return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
395
+ }
396
+ createHoverOverlay() {
397
+ this.hoverOverlay = document.createElement("div");
398
+ this.hoverOverlay.id = "builder-hover-overlay";
399
+ this.hoverOverlay.style.cssText = `
400
+ position: fixed;
401
+ pointer-events: none;
402
+ border: 2px dashed #3B82F6;
403
+ background: rgba(59, 130, 246, 0.05);
404
+ z-index: 9999;
405
+ display: none;
406
+ transition: all 0.15s ease-out;
407
+ border-radius: 4px;
408
+ `;
409
+ document.body.appendChild(this.hoverOverlay);
410
+ }
411
+ setupScrollResizeListeners() {
412
+ window.addEventListener("scroll", this.updateSelectionPositions, { passive: true });
413
+ window.addEventListener("resize", this.updateSelectionPositions, { passive: true });
414
+ }
415
+ setupKeyboardListener() {
416
+ document.addEventListener("keydown", this.handleKeyDown);
417
+ }
418
+ /**
419
+ * Forward wheel events with ctrlKey (pinch zoom) to parent
420
+ * This allows pinch-to-zoom gestures inside the iframe to control
421
+ * the parent's zoom state instead of zooming the iframe content
422
+ */
423
+ setupWheelForwarding() {
424
+ document.addEventListener("wheel", (e) => {
425
+ if (e.ctrlKey) {
426
+ e.preventDefault();
427
+ this.sendToParent({
428
+ type: "IFRAME_WHEEL",
429
+ deltaY: e.deltaY,
430
+ clientX: e.clientX,
431
+ clientY: e.clientY,
432
+ ctrlKey: true
433
+ });
434
+ }
435
+ }, { passive: false });
436
+ }
437
+ showHoverOverlay(element) {
438
+ if (!this.hoverOverlay) return;
439
+ const rect = element.getBoundingClientRect();
440
+ this.hoverOverlay.style.top = `${rect.top}px`;
441
+ this.hoverOverlay.style.left = `${rect.left}px`;
442
+ this.hoverOverlay.style.width = `${rect.width}px`;
443
+ this.hoverOverlay.style.height = `${rect.height}px`;
444
+ this.hoverOverlay.style.display = "block";
445
+ }
446
+ hideHoverOverlay() {
447
+ if (this.hoverOverlay) {
448
+ this.hoverOverlay.style.display = "none";
449
+ }
450
+ }
451
+ syncSelections(selections, _currentPage) {
452
+ console.log("[BuilderSelection] Syncing selections:", selections.length, "current page:", _currentPage);
453
+ this.currentSelections = selections;
454
+ if (!this.enabled) {
455
+ this.clearAllSelections();
456
+ return;
457
+ }
458
+ this.renderSelectionMasks();
459
+ }
460
+ /**
461
+ * Render selection masks for current selections (only when enabled)
462
+ */
463
+ renderSelectionMasks() {
464
+ this.clearAllSelections();
465
+ for (const sel of this.currentSelections) {
466
+ if (sel.page !== window.location.pathname) continue;
467
+ const element = this.findElementBySelectorId(sel.selectorId);
468
+ if (!element) {
469
+ console.warn(`[BuilderSelection] Element not found for selector: ${sel.selectorId}`);
470
+ continue;
471
+ }
472
+ this.renderSelectionIndicator(element, sel.id, sel.color);
473
+ }
474
+ }
475
+ /**
476
+ * Find element by selector ID (supports both explicit and auto-generated IDs)
477
+ */
478
+ findElementBySelectorId(selectorId) {
479
+ const explicit = document.querySelector(`[data-mp-selectable="${selectorId}"]`);
480
+ if (explicit) return explicit;
481
+ const byId = document.getElementById(selectorId);
482
+ if (byId) return byId;
483
+ const cached = this.elementMap.get(selectorId);
484
+ if (cached && document.body.contains(cached)) return cached;
485
+ const parts = selectorId.split(".");
486
+ if (parts.length >= 2) {
487
+ const parentId = parts[0];
488
+ const tag = parts[1];
489
+ const index = parts.length > 2 ? parseInt(parts[2], 10) : 0;
490
+ let parent = null;
491
+ if (parentId === "page") {
492
+ parent = document.body;
493
+ } else {
494
+ parent = document.querySelector(`[data-mp-selectable="${parentId}"]`) || document.querySelector(`[data-mp-editable="${parentId}"]`) || document.getElementById(parentId) || document.querySelector(`section#${parentId}`);
495
+ }
496
+ if (parent) {
497
+ const candidates = parent.querySelectorAll(tag);
498
+ if (candidates[index]) {
499
+ return candidates[index];
500
+ }
501
+ }
502
+ }
503
+ return null;
504
+ }
505
+ clearAllSelections() {
506
+ this.selections.forEach(({ container }) => {
507
+ container.remove();
508
+ });
509
+ this.selections.clear();
510
+ }
511
+ /**
512
+ * Update positions of selection overlays when viewport changes (scroll/resize)
513
+ */
514
+ updateSelectionPositions = () => {
515
+ this.selections.forEach(({ element, container }) => {
516
+ if (!document.body.contains(element)) {
517
+ return;
518
+ }
519
+ const rect = element.getBoundingClientRect();
520
+ container.style.top = `${rect.top}px`;
521
+ container.style.left = `${rect.left}px`;
522
+ container.style.width = `${rect.width}px`;
523
+ container.style.height = `${rect.height}px`;
524
+ });
525
+ };
526
+ renderSelectionIndicator(element, selectionId, color) {
527
+ const rect = element.getBoundingClientRect();
528
+ const container = document.createElement("div");
529
+ container.className = "builder-selection-container";
530
+ container.dataset.selectionId = selectionId;
531
+ container.style.cssText = `
532
+ position: fixed;
533
+ top: ${rect.top}px;
534
+ left: ${rect.left}px;
535
+ width: ${rect.width}px;
536
+ height: ${rect.height}px;
537
+ pointer-events: none;
538
+ z-index: 9999;
539
+ `;
540
+ const border = document.createElement("div");
541
+ border.className = "builder-selection-border";
542
+ border.style.cssText = `
543
+ position: absolute;
544
+ inset: 0;
545
+ border: 2px solid ${color};
546
+ border-radius: 4px;
547
+ pointer-events: none;
548
+ `;
549
+ container.appendChild(border);
550
+ const badge = document.createElement("div");
551
+ badge.textContent = selectionId;
552
+ badge.className = "builder-selection-badge";
553
+ badge.style.cssText = `
554
+ position: absolute;
555
+ top: 8px;
556
+ left: 8px;
557
+ background: ${color};
558
+ color: white;
559
+ font-size: 11px;
560
+ font-weight: 600;
561
+ padding: 4px 10px;
562
+ border-radius: 9999px;
563
+ pointer-events: none;
564
+ font-family: system-ui, -apple-system, sans-serif;
565
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
566
+ `;
567
+ container.appendChild(badge);
568
+ document.body.appendChild(container);
569
+ this.selections.set(selectionId, { element, container, badge, border });
570
+ }
571
+ };
572
+ function initBuilderSelection() {
573
+ if (typeof window !== "undefined" && window.parent !== window) {
574
+ if (document.readyState === "loading") {
575
+ document.addEventListener("DOMContentLoaded", () => new BuilderSelectionManager());
576
+ } else {
577
+ new BuilderSelectionManager();
578
+ }
579
+ }
580
+ }
581
+
582
+ // src/lib/image-cache.ts
583
+ var cache = /* @__PURE__ */ new Map();
584
+ function setCachedImages(images) {
585
+ cache.clear();
586
+ const timestamp = Date.now();
587
+ for (const img of images) {
588
+ cache.set(img.key, {
589
+ blobUrl: img.blobUrl,
590
+ originalUrl: img.originalUrl,
591
+ timestamp
592
+ });
593
+ }
594
+ }
595
+ function getCachedUrl(src) {
596
+ if (!src) return null;
597
+ if (src.startsWith("http://") || src.startsWith("https://")) {
598
+ const exact = cache.get(src);
599
+ if (exact) return exact.blobUrl;
600
+ return null;
601
+ }
602
+ const direct = cache.get(src);
603
+ if (direct) return direct.blobUrl;
604
+ const filename = src.split("/").pop();
605
+ if (filename) {
606
+ const byFilename = cache.get(filename);
607
+ if (byFilename) return byFilename.blobUrl;
608
+ const withPrefix = cache.get(`assets/${filename}`);
609
+ if (withPrefix) return withPrefix.blobUrl;
610
+ for (const [key, entry] of cache) {
611
+ const keyFilename = key.split("/").pop();
612
+ if (keyFilename === filename) {
613
+ return entry.blobUrl;
614
+ }
615
+ }
616
+ }
617
+ return null;
618
+ }
619
+ function clearImageCache() {
620
+ cache.clear();
621
+ }
622
+ function getCacheStats() {
623
+ return {
624
+ count: cache.size,
625
+ keys: Array.from(cache.keys())
626
+ };
627
+ }
628
+ function hasCachedImage(src) {
629
+ return getCachedUrl(src) !== null;
630
+ }
631
+
632
+ // src/lib/image-cache-listener.ts
633
+ var isListening = false;
634
+ function handleMessage(event) {
635
+ const data = event.data;
636
+ if (data?.type === "YA_IMAGE_CACHE_UPDATE") {
637
+ console.log("[ImageCache] Received cache update:", data.images.length, "images");
638
+ setCachedImages(data.images);
639
+ }
640
+ if (data?.type === "YA_IMAGE_CACHE_CLEAR") {
641
+ console.log("[ImageCache] Cache cleared by parent");
642
+ clearImageCache();
643
+ }
644
+ }
645
+ function initImageCacheListener() {
646
+ if (isListening) {
647
+ window.parent.postMessage({ type: "YA_IMAGE_CACHE_REQUEST" }, "*");
648
+ return () => {
649
+ };
650
+ }
651
+ window.addEventListener("message", handleMessage);
652
+ isListening = true;
653
+ window.parent.postMessage({ type: "YA_IMAGE_CACHE_REQUEST" }, "*");
654
+ return () => {
655
+ window.removeEventListener("message", handleMessage);
656
+ isListening = false;
657
+ clearImageCache();
658
+ };
659
+ }
660
+
661
+ // src/lib/api-client.ts
662
+ function getApiUrl() {
663
+ const runtimeConfig = window.YOAMIGO_CONFIG;
664
+ const apiUrl = runtimeConfig?.apiUrl || import.meta.env.YA_API_URL;
665
+ if (!apiUrl) {
666
+ throw new Error("API URL not configured (check YOAMIGO_CONFIG or YA_API_URL)");
667
+ }
668
+ return apiUrl;
669
+ }
670
+ function getSiteId() {
671
+ const runtimeConfig = window.YOAMIGO_CONFIG;
672
+ const siteId = runtimeConfig?.siteId || import.meta.env.YA_SITE_ID;
673
+ if (!siteId) {
674
+ throw new Error("Site ID not configured (check YOAMIGO_CONFIG or YA_SITE_ID)");
675
+ }
676
+ return siteId;
677
+ }
678
+ async function subscribeToNewsletter(email) {
679
+ const apiUrl = getApiUrl();
680
+ const siteId = getSiteId();
681
+ const response = await fetch(`${apiUrl}/api/websites/${siteId}/subscribe`, {
682
+ method: "POST",
683
+ headers: {
684
+ "Content-Type": "application/json"
685
+ },
686
+ body: JSON.stringify({
687
+ email,
688
+ emailOptIn: true,
689
+ smsOptIn: false
690
+ })
691
+ });
692
+ if (!response.ok) {
693
+ const error = await response.json().catch(() => ({ message: "Failed to subscribe" }));
694
+ throw new Error(error.message || "Failed to subscribe");
695
+ }
696
+ return response.json();
697
+ }
698
+ async function submitContactForm(data) {
699
+ const apiUrl = getApiUrl();
700
+ const siteId = getSiteId();
701
+ const response = await fetch(`${apiUrl}/api/websites/${siteId}/contact`, {
702
+ method: "POST",
703
+ headers: {
704
+ "Content-Type": "application/json"
705
+ },
706
+ body: JSON.stringify(data)
707
+ });
708
+ if (!response.ok) {
709
+ const error = await response.json().catch(() => ({ message: "Failed to submit form" }));
710
+ throw new Error(error.message || "Failed to submit form");
711
+ }
712
+ return response.json();
713
+ }
714
+ async function getSiteMetadata() {
715
+ const runtimeConfig = window.YOAMIGO_CONFIG;
716
+ return runtimeConfig || null;
717
+ }
718
+ export {
719
+ clearImageCache,
720
+ contentRegistry,
721
+ getAllContent,
722
+ getCacheStats,
723
+ getCachedUrl,
724
+ getContent,
725
+ getSiteMetadata,
726
+ hasCachedImage,
727
+ hasContent,
728
+ initBuilderSelection,
729
+ initImageCacheListener,
730
+ registerContent,
731
+ resolveAssetUrl,
732
+ setAssetResolver,
733
+ setCachedImages,
734
+ submitContactForm,
735
+ subscribeToNewsletter
736
+ };