vue-wswg-editor 0.0.12 → 0.0.14

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.
Files changed (60) hide show
  1. package/README.md +23 -8
  2. package/dist/style.css +1 -1
  3. package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +1 -0
  4. package/dist/types/components/EditorPageRenderer/EditorPageRenderer.vue.d.ts +21 -0
  5. package/dist/types/components/EmptyState/EmptyState.vue.d.ts +2 -8
  6. package/dist/types/components/IframePreview/IframePreview.vue.d.ts +26 -0
  7. package/dist/types/components/IframePreview/iframeContent.d.ts +9 -0
  8. package/dist/types/components/IframePreview/iframePreviewApp.d.ts +36 -0
  9. package/dist/types/components/IframePreview/messageHandler.d.ts +55 -0
  10. package/dist/types/components/IframePreview/types.d.ts +77 -0
  11. package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +2 -0
  12. package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +2 -0
  13. package/dist/types/components/PageSettings/PageSettings.vue.d.ts +2 -0
  14. package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.vue.d.ts → WswgPageBuilder/WswgPageBuilder.vue.d.ts} +2 -0
  15. package/dist/types/index.d.ts +8 -2
  16. package/dist/types/util/registry.d.ts +2 -0
  17. package/dist/types/util/theme-registry.d.ts +42 -0
  18. package/dist/types/util/validation.d.ts +2 -2
  19. package/dist/vite-plugin.js +33 -29
  20. package/dist/vue-wswg-editor.es.js +2783 -1905
  21. package/package.json +1 -2
  22. package/src/assets/styles/_mixins.scss +15 -0
  23. package/src/components/AddBlockItem/AddBlockItem.vue +13 -4
  24. package/src/components/BlockBrowser/BlockBrowser.vue +5 -5
  25. package/src/components/BlockComponent/BlockComponent.vue +23 -50
  26. package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +12 -10
  27. package/src/components/BlockEditorFields/BlockEditorFields.vue +24 -4
  28. package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +9 -4
  29. package/src/components/BrowserNavigation/BrowserNavigation.vue +1 -1
  30. package/src/components/EditorPageRenderer/EditorPageRenderer.vue +641 -0
  31. package/src/components/EmptyState/EmptyState.vue +3 -12
  32. package/src/components/IframePreview/IframePreview.vue +211 -0
  33. package/src/components/IframePreview/iframeContent.ts +230 -0
  34. package/src/components/IframePreview/iframePreviewApp.ts +308 -0
  35. package/src/components/IframePreview/messageHandler.ts +219 -0
  36. package/src/components/IframePreview/types.ts +126 -0
  37. package/src/components/PageBlockList/PageBlockList.vue +8 -6
  38. package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +5 -3
  39. package/src/components/PageRenderer/PageRenderer.vue +18 -38
  40. package/src/components/PageSettings/PageSettings.vue +10 -6
  41. package/src/components/ResizeHandle/ResizeHandle.vue +68 -10
  42. package/src/components/{WswgJsonEditor/WswgJsonEditor.test.ts → WswgPageBuilder/WswgPageBuilder.test.ts} +8 -8
  43. package/src/components/WswgPageBuilder/WswgPageBuilder.vue +375 -0
  44. package/src/index.ts +10 -2
  45. package/src/shims.d.ts +4 -0
  46. package/src/types/Theme.d.ts +15 -0
  47. package/src/util/registry.ts +2 -2
  48. package/src/util/theme-registry.ts +397 -0
  49. package/src/util/validation.ts +102 -11
  50. package/src/vite-plugin.ts +8 -4
  51. package/types/vue-wswg-editor.d.ts +4 -0
  52. package/dist/types/components/PageRenderer/blockModules.d.ts +0 -3
  53. package/dist/types/components/PageRenderer/layoutModules.d.ts +0 -3
  54. package/src/components/PageRenderer/blockModules-alternative.ts.example +0 -9
  55. package/src/components/PageRenderer/blockModules-manual.ts.example +0 -19
  56. package/src/components/PageRenderer/blockModules-runtime.ts.example +0 -23
  57. package/src/components/PageRenderer/blockModules.ts +0 -32
  58. package/src/components/PageRenderer/layoutModules.ts +0 -32
  59. package/src/components/WswgJsonEditor/WswgJsonEditor.vue +0 -595
  60. /package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.test.d.ts → WswgPageBuilder/WswgPageBuilder.test.d.ts} +0 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Iframe Preview Vue Application
3
+ *
4
+ * This file creates a Vue application that runs inside the iframe preview.
5
+ * Since the library is consumed as source code, consuming apps' Vite builds
6
+ * will process this file, giving it access to virtual modules (blocks/layouts).
7
+ *
8
+ * Usage in consuming apps:
9
+ * ```ts
10
+ * import { createIframeApp, getIframeAppModuleUrl } from 'vue-wswg-editor/IframePreviewApp'
11
+ * ```
12
+ */
13
+
14
+ import { createApp, ref, watch, h, type App } from "vue";
15
+ import EditorPageRenderer from "../EditorPageRenderer/EditorPageRenderer.vue";
16
+ import type { Block } from "../../types/Block";
17
+
18
+ /**
19
+ * Get the URL of this module
20
+ * This can be used to import the module in the iframe HTML
21
+ */
22
+ export function getIframeAppModuleUrl(): string {
23
+ // Use import.meta.url to get the current module's URL
24
+ // In development, this will be the source file URL
25
+ // In production (after Vite build), this will be the bundled module URL
26
+ return import.meta.url;
27
+ }
28
+
29
+ export interface IframeAppState {
30
+ pageData: Record<string, any> | null;
31
+ activeBlock: Block | null;
32
+ hoveredBlockId: string | null;
33
+ blocksKey: string;
34
+ settingsKey: string;
35
+ settingsOpen: boolean;
36
+ theme: string;
37
+ }
38
+
39
+ export interface IframeAppCallbacks {
40
+ onBlockClick?: (blockId: string, block: Block | null) => void;
41
+ onBlockHover?: (blockId: string | null) => void;
42
+ }
43
+
44
+ /**
45
+ * Create and mount the Vue app for the iframe preview
46
+ */
47
+ export async function createIframeApp(container: HTMLElement): Promise<App> {
48
+ // State
49
+ const pageData = ref<Record<string, any> | null>(null);
50
+ const activeBlock = ref<Block | null>(null);
51
+ const hoveredBlockId = ref<string | null>(null);
52
+ const settingsOpen = ref<boolean>(false);
53
+ const blocksKey = ref<string>("blocks");
54
+ const settingsKey = ref<string>("settings");
55
+ const theme = ref<string>("default");
56
+ // Serialize data for postMessage (handles Vue reactive proxies)
57
+ function serializeForPostMessage(data: any): any {
58
+ try {
59
+ return JSON.parse(JSON.stringify(data));
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ // Message handler
66
+ function sendToParent(message: any) {
67
+ if (window.parent) {
68
+ // Serialize message to handle Vue reactive proxies
69
+ const serializedMessage = serializeForPostMessage(message);
70
+ if (!serializedMessage) {
71
+ return;
72
+ }
73
+ window.parent.postMessage(serializedMessage, "*");
74
+ }
75
+ }
76
+
77
+ // Handle messages from parent
78
+ window.addEventListener("message", (event: MessageEvent) => {
79
+ const msg = event.data;
80
+ if (!msg || !msg.type) return;
81
+
82
+ switch (msg.type) {
83
+ case "UPDATE_PAGE_DATA":
84
+ if (msg.pageData) {
85
+ pageData.value = msg.pageData;
86
+ }
87
+ if (msg.blocksKey) {
88
+ blocksKey.value = msg.blocksKey;
89
+ }
90
+ if (msg.settingsKey) {
91
+ settingsKey.value = msg.settingsKey;
92
+ }
93
+ if (msg.theme) {
94
+ theme.value = msg.theme;
95
+ }
96
+ break;
97
+ case "SET_ACTIVE_BLOCK":
98
+ activeBlock.value = msg.block;
99
+ break;
100
+ case "SET_HOVERED_BLOCK":
101
+ hoveredBlockId.value = msg.blockId;
102
+ break;
103
+ case "SET_SETTINGS_OPEN":
104
+ settingsOpen.value = msg.settingsOpen;
105
+ break;
106
+ case "SCROLL_TO_BLOCK": {
107
+ const block = document.querySelector(`[data-block-id="${msg.blockId}"]`);
108
+ if (block) {
109
+ block.scrollIntoView({ behavior: "smooth", block: "center" });
110
+ }
111
+ break;
112
+ }
113
+ }
114
+ });
115
+
116
+ // Create Vue app using render function (since we're using runtime-only Vue)
117
+ const app = createApp({
118
+ components: {
119
+ EditorPageRenderer,
120
+ },
121
+ setup() {
122
+ // Ensure PageRenderer has time to load blocks before rendering
123
+ const isPageReady = ref(false);
124
+
125
+ // Watch for pageData changes
126
+ watch(
127
+ () => pageData.value,
128
+ async (newPageData) => {
129
+ if (newPageData && newPageData[blocksKey.value]) {
130
+ // Give PageRenderer time to load block modules
131
+ // PageRenderer loads blocks in onBeforeMount, so we need to wait
132
+ await new Promise((resolve) => setTimeout(resolve, 200));
133
+ isPageReady.value = true;
134
+ } else {
135
+ isPageReady.value = false;
136
+ }
137
+ },
138
+ { immediate: true }
139
+ );
140
+
141
+ // Render function
142
+ return () => {
143
+ const currentPageData = pageData.value;
144
+ const hasPageData = currentPageData && currentPageData[blocksKey.value];
145
+ const blocks = hasPageData && currentPageData ? currentPageData[blocksKey.value] : [];
146
+
147
+ if (isPageReady.value && currentPageData) {
148
+ return h(EditorPageRenderer, {
149
+ blocks: blocks,
150
+ layout: currentPageData[settingsKey.value]?.layout,
151
+ settings: currentPageData[settingsKey.value],
152
+ activeBlock: activeBlock.value,
153
+ hoveredBlockId: hoveredBlockId.value,
154
+ settingsOpen: settingsOpen.value,
155
+ editable: true,
156
+ theme: theme.value,
157
+ });
158
+ } else {
159
+ // Show loading state while PageRenderer loads blocks
160
+ return h("div", { class: "bg-white px-5 py-12 md:py-20" }, [
161
+ h("div", { class: "mx-auto max-w-md pb-7 text-center" }, [
162
+ h(
163
+ "svg",
164
+ {
165
+ class: "mx-auto size-8 animate-spin text-blue-600",
166
+ xmlns: "http://www.w3.org/2000/svg",
167
+ fill: "none",
168
+ viewBox: "0 0 24 24",
169
+ },
170
+ [
171
+ h("circle", {
172
+ class: "opacity-25",
173
+ cx: "12",
174
+ cy: "12",
175
+ r: "10",
176
+ stroke: "currentColor",
177
+ "stroke-width": "4",
178
+ }),
179
+ h("path", {
180
+ class: "opacity-75",
181
+ fill: "currentColor",
182
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z",
183
+ }),
184
+ ]
185
+ ),
186
+ h("span", { class: "mt-4 text-gray-700" }, "Loading preview..."),
187
+ ]),
188
+ ]);
189
+ }
190
+ };
191
+ },
192
+ });
193
+
194
+ // Dynamic import helper - using Function constructor to create a truly dynamic import
195
+ // that Vite cannot analyze statically, preventing build-time resolution errors
196
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
197
+ // @ts-ignore - modules may not be available in all consuming apps
198
+ const dynamicImport = new Function("modulePath", "return import(modulePath)");
199
+
200
+ // Try to install Unhead plugin if available
201
+ // This is needed for layouts that use useHead from @vueuse/head
202
+ try {
203
+ // Dynamic import to check if @vueuse/head is available
204
+ // This module is externalized in vite.config.ts so it won't be bundled
205
+ const headModule = await dynamicImport("@vueuse/head");
206
+ if (headModule && typeof headModule.createHead === "function") {
207
+ const head = headModule.createHead();
208
+ app.use(head);
209
+ }
210
+ } catch {
211
+ // @vueuse/head not available - layouts using useHead will show warnings but won't break
212
+ // This is expected if the consuming app doesn't use @vueuse/head
213
+ }
214
+
215
+ // Try to use vue-router - first try from CDN, but components use consuming app's vue-router
216
+ // The issue is that components from the consuming app use their own vue-router instance
217
+ // with different Symbol keys. We need to use the same instance they're using.
218
+ // Try to import vue-router dynamically - this will use the consuming app's vue-router
219
+ // if it's available in the module resolution context
220
+ let VueRouter: any = null;
221
+ let routerInstalled = false;
222
+
223
+ try {
224
+ // Try dynamic import - this should resolve to the consuming app's vue-router
225
+ // when the library is consumed as source code
226
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
227
+ // @ts-ignore - vue-router may not be available in all consuming apps
228
+ const routerModule = await import("vue-router");
229
+ VueRouter = routerModule;
230
+ } catch {
231
+ // Fallback to CDN version
232
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
233
+ // @ts-ignore
234
+ VueRouter = typeof window !== "undefined" ? (window as any).VueRouter : null;
235
+ }
236
+
237
+ if (VueRouter && typeof VueRouter.createRouter === "function") {
238
+ try {
239
+ // Create a minimal router instance
240
+ const router = VueRouter.createRouter({
241
+ history: VueRouter.createMemoryHistory(),
242
+ routes: [
243
+ {
244
+ path: "/",
245
+ component: { template: "<div></div>" },
246
+ },
247
+ {
248
+ path: "/:pathMatch(.*)*",
249
+ component: { template: "<div></div>" },
250
+ },
251
+ ],
252
+ });
253
+
254
+ // Store original resolve to call it first, then ensure matched array exists
255
+ const originalResolve = router.resolve.bind(router);
256
+ router.resolve = (to: any) => {
257
+ // Call original resolve to get proper route structure
258
+ const resolved = originalResolve(to);
259
+ // Ensure the resolved route has a matched array (useLink requires this)
260
+ if (resolved.route && (!resolved.route.matched || resolved.route.matched.length === 0)) {
261
+ resolved.route.matched = [
262
+ {
263
+ path: resolved.route.path || "/",
264
+ name: resolved.route.name,
265
+ meta: resolved.route.meta || {},
266
+ components: {},
267
+ children: [],
268
+ },
269
+ ];
270
+ }
271
+ return resolved;
272
+ };
273
+
274
+ // Install router BEFORE mounting - this provides the router injection
275
+ // The app.use() call should handle the Symbol keys automatically
276
+ app.use(router);
277
+
278
+ // Wait for router to be ready before mounting
279
+ await router.isReady();
280
+
281
+ // Ensure currentRoute has proper structure with matched array
282
+ // currentRoute.value is readonly, so we navigate to ensure route is properly resolved
283
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
284
+ // @ts-ignore
285
+ const currentRoute = router.currentRoute.value;
286
+ if (currentRoute && (!currentRoute.matched || currentRoute.matched.length === 0)) {
287
+ // Navigate to ensure route has proper matched array
288
+ await router.push("/");
289
+ }
290
+
291
+ routerInstalled = true;
292
+ } catch {
293
+ // Fall through to mount without router
294
+ }
295
+ }
296
+
297
+ if (!routerInstalled) {
298
+ // Router was not installed - RouterLink components will fail
299
+ console.warn("[iframe preview] Router was not installed - RouterLink components will fail");
300
+ }
301
+ // Mount the app
302
+ app.mount(container);
303
+
304
+ // Notify parent that iframe is ready
305
+ sendToParent({ type: "IFRAME_READY" });
306
+
307
+ return app;
308
+ }
@@ -0,0 +1,219 @@
1
+ import type {
2
+ IframeMessage,
3
+ ParentMessage,
4
+ UpdatePageDataMessage,
5
+ UpdateHTMLMessage,
6
+ SetActiveBlockMessage,
7
+ SetHoveredBlockMessage,
8
+ SetSettingsOpenMessage,
9
+ SetViewportMessage,
10
+ ScrollToBlockMessage,
11
+ } from "./types";
12
+ import type { Block } from "../../types/Block";
13
+
14
+ /**
15
+ * Serialize data for postMessage (handles Vue reactive proxies)
16
+ */
17
+ function serializeForPostMessage(data: any): any {
18
+ try {
19
+ return JSON.parse(JSON.stringify(data));
20
+ } catch (error) {
21
+ console.warn("Failed to serialize data for postMessage:", error);
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Send a message to the iframe
28
+ */
29
+ export function sendToIframe(iframe: HTMLIFrameElement | null, message: IframeMessage): void {
30
+ if (!iframe || !iframe.contentWindow) {
31
+ console.warn("Cannot send message: iframe not ready");
32
+ return;
33
+ }
34
+
35
+ // Serialize message to handle Vue reactive proxies
36
+ const serializedMessage = serializeForPostMessage(message) as IframeMessage;
37
+ if (!serializedMessage) {
38
+ console.error("Failed to serialize message for postMessage");
39
+ return;
40
+ }
41
+
42
+ // Security: Only send to same origin
43
+ try {
44
+ iframe.contentWindow.postMessage(serializedMessage, "*");
45
+ } catch (error) {
46
+ console.error("Error sending message to iframe:", error);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Send page data update to iframe
52
+ */
53
+ export function sendPageDataUpdate(
54
+ iframe: HTMLIFrameElement | null,
55
+ pageData: Record<string, any>,
56
+ blocksKey: string,
57
+ settingsKey: string,
58
+ theme: string
59
+ ): void {
60
+ // Serialize pageData to avoid Vue reactive proxy issues
61
+ const serializedPageData = serializeForPostMessage(pageData);
62
+ const message: UpdatePageDataMessage = {
63
+ type: "UPDATE_PAGE_DATA",
64
+ pageData: serializedPageData,
65
+ blocksKey,
66
+ settingsKey,
67
+ theme,
68
+ };
69
+ sendToIframe(iframe, message);
70
+ }
71
+
72
+ /**
73
+ * Send rendered HTML to iframe
74
+ */
75
+ export function sendHTMLUpdate(
76
+ iframe: HTMLIFrameElement | null,
77
+ html: string,
78
+ pageData?: Record<string, any>,
79
+ blocksKey?: string,
80
+ settingsKey?: string
81
+ ): void {
82
+ // Serialize pageData to avoid Vue reactive proxy issues
83
+ const serializedPageData = pageData ? serializeForPostMessage(pageData) : undefined;
84
+ const message: UpdateHTMLMessage = {
85
+ type: "UPDATE_HTML",
86
+ html,
87
+ pageData: serializedPageData,
88
+ blocksKey,
89
+ settingsKey,
90
+ };
91
+ sendToIframe(iframe, message);
92
+ }
93
+
94
+ /**
95
+ * Send active block update to iframe
96
+ */
97
+ export function sendActiveBlock(iframe: HTMLIFrameElement | null, block: Block | null): void {
98
+ const message: SetActiveBlockMessage = {
99
+ type: "SET_ACTIVE_BLOCK",
100
+ block,
101
+ };
102
+ sendToIframe(iframe, message);
103
+ }
104
+
105
+ /**
106
+ * Send hovered block update to iframe
107
+ */
108
+ export function sendHoveredBlock(iframe: HTMLIFrameElement | null, blockId: string | null): void {
109
+ const message: SetHoveredBlockMessage = {
110
+ type: "SET_HOVERED_BLOCK",
111
+ blockId,
112
+ };
113
+ sendToIframe(iframe, message);
114
+ }
115
+
116
+ /**
117
+ * Send settings open state update to iframe
118
+ */
119
+ export function sendSettingsOpen(iframe: HTMLIFrameElement | null, settingsOpen: boolean): void {
120
+ const message: SetSettingsOpenMessage = {
121
+ type: "SET_SETTINGS_OPEN",
122
+ settingsOpen,
123
+ };
124
+ sendToIframe(iframe, message);
125
+ }
126
+
127
+ /**
128
+ * Send viewport update to iframe
129
+ */
130
+ export function sendViewport(iframe: HTMLIFrameElement | null, viewport: "desktop" | "mobile"): void {
131
+ const message: SetViewportMessage = {
132
+ type: "SET_VIEWPORT",
133
+ viewport,
134
+ };
135
+ sendToIframe(iframe, message);
136
+ }
137
+
138
+ /**
139
+ * Send scroll to block command to iframe
140
+ */
141
+ export function sendScrollToBlock(iframe: HTMLIFrameElement | null, blockId: string): void {
142
+ const message: ScrollToBlockMessage = {
143
+ type: "SCROLL_TO_BLOCK",
144
+ blockId,
145
+ };
146
+ sendToIframe(iframe, message);
147
+ }
148
+
149
+ /**
150
+ * Validate incoming message from iframe
151
+ */
152
+ export function isValidParentMessage(message: any): message is ParentMessage {
153
+ if (!message || typeof message !== "object" || !message.type) {
154
+ return false;
155
+ }
156
+
157
+ const validTypes: string[] = [
158
+ "BLOCK_CLICK",
159
+ "BLOCK_HOVER",
160
+ "BLOCK_REORDER",
161
+ "BLOCK_ADD",
162
+ "IFRAME_READY",
163
+ "BLOCK_ELEMENT_POSITION",
164
+ "CLICK_PARTIAL",
165
+ ];
166
+
167
+ return validTypes.includes(message.type);
168
+ }
169
+
170
+ /**
171
+ * Handle incoming message from iframe
172
+ */
173
+ export function handleIframeMessage(
174
+ event: MessageEvent,
175
+ callbacks: {
176
+ onBlockClick?: (blockId: string, block: any) => void;
177
+ onBlockHover?: (blockId: string | null) => void;
178
+ onBlockReorder?: (oldIndex: number, newIndex: number) => void;
179
+ onBlockAdd?: (blockType: string, index: number) => void;
180
+ onPartialClick?: (partialValue: string) => void;
181
+ onIframeReady?: () => void;
182
+ onBlockPosition?: (
183
+ blockId: string,
184
+ position: { top: number; left: number; width: number; height: number }
185
+ ) => void;
186
+ }
187
+ ): void {
188
+ // Security: Validate origin (in production, check against expected origin)
189
+ // For now, we'll accept messages from any origin since we're using blob URLs
190
+ if (!isValidParentMessage(event.data)) {
191
+ return;
192
+ }
193
+
194
+ const message = event.data as ParentMessage;
195
+
196
+ switch (message.type) {
197
+ case "BLOCK_CLICK":
198
+ callbacks.onBlockClick?.(message.blockId, message.block);
199
+ break;
200
+ case "BLOCK_HOVER":
201
+ callbacks.onBlockHover?.(message.blockId);
202
+ break;
203
+ case "BLOCK_REORDER":
204
+ callbacks.onBlockReorder?.(message.oldIndex, message.newIndex);
205
+ break;
206
+ case "BLOCK_ADD":
207
+ callbacks.onBlockAdd?.(message.blockType, message.index);
208
+ break;
209
+ case "CLICK_PARTIAL":
210
+ callbacks.onPartialClick?.(message.partial);
211
+ break;
212
+ case "IFRAME_READY":
213
+ callbacks.onIframeReady?.();
214
+ break;
215
+ case "BLOCK_ELEMENT_POSITION":
216
+ callbacks.onBlockPosition?.(message.blockId, message.position);
217
+ break;
218
+ }
219
+ }
@@ -0,0 +1,126 @@
1
+ // Message types for parent ↔ iframe communication
2
+
3
+ import type { Block } from "../../types/Block";
4
+
5
+ export type MessageType =
6
+ | "UPDATE_PAGE_DATA"
7
+ | "UPDATE_HTML"
8
+ | "SET_ACTIVE_BLOCK"
9
+ | "SET_HOVERED_BLOCK"
10
+ | "SET_SETTINGS_OPEN"
11
+ | "SET_VIEWPORT"
12
+ | "CLICK_PARTIAL"
13
+ | "SCROLL_TO_BLOCK"
14
+ | "BLOCK_CLICK"
15
+ | "BLOCK_HOVER"
16
+ | "BLOCK_REORDER"
17
+ | "BLOCK_ADD"
18
+ | "IFRAME_READY"
19
+ | "BLOCK_ELEMENT_POSITION";
20
+
21
+ export interface BaseMessage {
22
+ type: MessageType;
23
+ }
24
+
25
+ export interface UpdatePageDataMessage extends BaseMessage {
26
+ type: "UPDATE_PAGE_DATA";
27
+ pageData: Record<string, any>;
28
+ blocksKey: string;
29
+ settingsKey: string;
30
+ theme: string;
31
+ }
32
+
33
+ export interface UpdateHTMLMessage extends BaseMessage {
34
+ type: "UPDATE_HTML";
35
+ html: string;
36
+ pageData?: Record<string, any>;
37
+ blocksKey?: string;
38
+ settingsKey?: string;
39
+ }
40
+
41
+ export interface SetActiveBlockMessage extends BaseMessage {
42
+ type: "SET_ACTIVE_BLOCK";
43
+ block: Block | null;
44
+ }
45
+
46
+ export interface SetHoveredBlockMessage extends BaseMessage {
47
+ type: "SET_HOVERED_BLOCK";
48
+ blockId: string | null;
49
+ }
50
+
51
+ export interface SetSettingsOpenMessage extends BaseMessage {
52
+ type: "SET_SETTINGS_OPEN";
53
+ settingsOpen: boolean;
54
+ }
55
+
56
+ export interface SetViewportMessage extends BaseMessage {
57
+ type: "SET_VIEWPORT";
58
+ viewport: "desktop" | "mobile";
59
+ }
60
+
61
+ export interface PartialClickMessage extends BaseMessage {
62
+ type: "CLICK_PARTIAL";
63
+ partial: string;
64
+ }
65
+
66
+ export interface ScrollToBlockMessage extends BaseMessage {
67
+ type: "SCROLL_TO_BLOCK";
68
+ blockId: string;
69
+ }
70
+
71
+ export interface BlockClickMessage extends BaseMessage {
72
+ type: "BLOCK_CLICK";
73
+ blockId: string;
74
+ block: any;
75
+ }
76
+
77
+ export interface BlockHoverMessage extends BaseMessage {
78
+ type: "BLOCK_HOVER";
79
+ blockId: string | null;
80
+ }
81
+
82
+ export interface BlockReorderMessage extends BaseMessage {
83
+ type: "BLOCK_REORDER";
84
+ oldIndex: number;
85
+ newIndex: number;
86
+ }
87
+
88
+ export interface BlockAddMessage extends BaseMessage {
89
+ type: "BLOCK_ADD";
90
+ blockType: string;
91
+ index: number;
92
+ }
93
+
94
+ export interface IframeReadyMessage extends BaseMessage {
95
+ type: "IFRAME_READY";
96
+ }
97
+
98
+ export interface BlockElementPositionMessage extends BaseMessage {
99
+ type: "BLOCK_ELEMENT_POSITION";
100
+ blockId: string;
101
+ position: {
102
+ top: number;
103
+ left: number;
104
+ width: number;
105
+ height: number;
106
+ };
107
+ }
108
+
109
+ export type IframeMessage =
110
+ | UpdatePageDataMessage
111
+ | UpdateHTMLMessage
112
+ | SetActiveBlockMessage
113
+ | SetHoveredBlockMessage
114
+ | SetSettingsOpenMessage
115
+ | SetViewportMessage
116
+ | ScrollToBlockMessage
117
+ | PartialClickMessage;
118
+
119
+ export type ParentMessage =
120
+ | BlockClickMessage
121
+ | BlockHoverMessage
122
+ | BlockReorderMessage
123
+ | BlockAddMessage
124
+ | IframeReadyMessage
125
+ | BlockElementPositionMessage
126
+ | PartialClickMessage;
@@ -4,6 +4,7 @@
4
4
  <div
5
5
  v-for="block in pageBlocks"
6
6
  :key="block.id"
7
+ data-prevent-drop="true"
7
8
  :class="{ 'bg-blue-100 text-blue-600': hoveredBlockId === block.id }"
8
9
  class="block-item -mx-2.5 flex cursor-pointer items-center gap-1 rounded-md p-2.5 text-sm text-neutral-900"
9
10
  @mouseenter="setHoveredBlockId(block.id)"
@@ -24,9 +25,9 @@
24
25
 
25
26
  <script setup lang="ts">
26
27
  import { computed, defineEmits, onMounted, ref } from "vue";
27
- import { pageBuilderBlocks } from "../../util/registry";
28
+ import { getBlock } from "../../util/theme-registry";
28
29
  import type { Block } from "../../types/Block";
29
- import { toCamelCase, toNiceName } from "../../util/helpers";
30
+ import { toNiceName } from "../../util/helpers";
30
31
  import Sortable from "sortablejs";
31
32
 
32
33
  const emit = defineEmits<{
@@ -45,9 +46,12 @@ const pageBlocks = computed(() => {
45
46
  if (!pageData.value?.[props.blocksKey]) return [];
46
47
  // loop through pageData[blocksKey] and get the block data from registry or return a default block data
47
48
  return pageData.value[props.blocksKey].map((block: any) => {
48
- if (pageBuilderBlocks.value[toCamelCase(block.type)]) {
49
+ // Find the corresponding block in the registry
50
+ const registryBlock = getBlock(block.type);
51
+
52
+ if (registryBlock) {
49
53
  return {
50
- ...pageBuilderBlocks.value[toCamelCase(block.type)],
54
+ ...registryBlock,
51
55
  ...block,
52
56
  };
53
57
  }
@@ -56,8 +60,6 @@ const pageBlocks = computed(() => {
56
60
  id: block.id,
57
61
  name: block.type,
58
62
  label: toNiceName(block.type),
59
- icon: "question-mark",
60
- thumbnail: "https://via.placeholder.com/150",
61
63
  };
62
64
  });
63
65
  });