vue-wswg-editor 0.0.13 → 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.
- package/dist/style.css +1 -1
- package/dist/vue-wswg-editor.es.js +898 -846
- package/package.json +1 -1
- package/src/components/IframePreview/iframeContent.ts +23 -3
- package/src/components/IframePreview/iframePreviewApp.ts +94 -7
- package/src/components/PageRenderer/PageRenderer.vue +9 -5
- package/src/util/theme-registry.ts +2 -2
package/package.json
CHANGED
|
@@ -106,6 +106,7 @@ export function generateIframeHTML(iframeAppModuleUrl?: string): string {
|
|
|
106
106
|
const parentStylesheets = extractParentStylesheets();
|
|
107
107
|
const parentCSSVariables = extractParentCSSVariables();
|
|
108
108
|
const vueCdnUrl = "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
|
|
109
|
+
const vueRouterCdnUrl = "https://unpkg.com/vue-router@4/dist/vue-router.esm-browser.js";
|
|
109
110
|
// Get parent origin to use as base URL for relative asset paths
|
|
110
111
|
const parentOrigin = typeof window !== "undefined" ? window.location.origin : "";
|
|
111
112
|
|
|
@@ -125,10 +126,15 @@ export function generateIframeHTML(iframeAppModuleUrl?: string): string {
|
|
|
125
126
|
window.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false;
|
|
126
127
|
</script>`;
|
|
127
128
|
|
|
128
|
-
// Generate script that loads Vue and initializes the iframe app
|
|
129
|
+
// Generate script that loads Vue, vue-router, and initializes the iframe app
|
|
130
|
+
// The import map above allows vue-router to resolve "vue" as a bare module specifier
|
|
129
131
|
const appScript = iframeAppModuleUrl
|
|
130
132
|
? `<script type="module">
|
|
131
|
-
import { createApp } from '
|
|
133
|
+
import { createApp } from 'vue';
|
|
134
|
+
import * as VueRouter from 'vue-router';
|
|
135
|
+
// Make vue-router available globally so it can be accessed in the iframe app
|
|
136
|
+
window.VueRouter = VueRouter;
|
|
137
|
+
|
|
132
138
|
import { createIframeApp } from '${iframeAppModuleUrl}';
|
|
133
139
|
|
|
134
140
|
const appEl = document.getElementById('app');
|
|
@@ -140,7 +146,10 @@ export function generateIframeHTML(iframeAppModuleUrl?: string): string {
|
|
|
140
146
|
</script>`
|
|
141
147
|
: `<script type="module">
|
|
142
148
|
// Fallback: Wait for parent to send module URL via postMessage
|
|
143
|
-
import { createApp } from '
|
|
149
|
+
import { createApp } from 'vue';
|
|
150
|
+
import * as VueRouter from 'vue-router';
|
|
151
|
+
// Make vue-router available globally so it can be imported in the iframe app
|
|
152
|
+
window.VueRouter = VueRouter;
|
|
144
153
|
|
|
145
154
|
let appInitialized = false;
|
|
146
155
|
|
|
@@ -179,6 +188,17 @@ export function generateIframeHTML(iframeAppModuleUrl?: string): string {
|
|
|
179
188
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
180
189
|
<title>Page Preview</title>
|
|
181
190
|
${parentOrigin ? `<base href="${parentOrigin}/">` : ""}
|
|
191
|
+
<!-- Import map to allow vue-router to resolve "vue" -->
|
|
192
|
+
<!-- Stub for @vue/devtools-api to avoid loading devtools in iframe -->
|
|
193
|
+
<script type="importmap">
|
|
194
|
+
{
|
|
195
|
+
"imports": {
|
|
196
|
+
"vue": "${vueCdnUrl}",
|
|
197
|
+
"vue-router": "${vueRouterCdnUrl}",
|
|
198
|
+
"@vue/devtools-api": "data:text/javascript,export const setupDevtoolsPlugin=()=>{};export const on=()=>{};export const off=()=>{};export const once=()=>{};export const emit=()=>{};export const notifyComponentUpdate=()=>{};export const addTimelineLayer=()=>{};export const addCustomCommand=()=>{};export const addCustomTab=()=>{};"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</script>
|
|
182
202
|
${parentStylesheets}
|
|
183
203
|
${parentCSSVariables}
|
|
184
204
|
<style>
|
|
@@ -57,8 +57,7 @@ export async function createIframeApp(container: HTMLElement): Promise<App> {
|
|
|
57
57
|
function serializeForPostMessage(data: any): any {
|
|
58
58
|
try {
|
|
59
59
|
return JSON.parse(JSON.stringify(data));
|
|
60
|
-
} catch
|
|
61
|
-
console.warn("[iframe] Failed to serialize data for postMessage:", error);
|
|
60
|
+
} catch {
|
|
62
61
|
return undefined;
|
|
63
62
|
}
|
|
64
63
|
}
|
|
@@ -69,7 +68,6 @@ export async function createIframeApp(container: HTMLElement): Promise<App> {
|
|
|
69
68
|
// Serialize message to handle Vue reactive proxies
|
|
70
69
|
const serializedMessage = serializeForPostMessage(message);
|
|
71
70
|
if (!serializedMessage) {
|
|
72
|
-
console.error("[iframe] Failed to serialize message for postMessage");
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
75
73
|
window.parent.postMessage(serializedMessage, "*");
|
|
@@ -193,15 +191,18 @@ export async function createIframeApp(container: HTMLElement): Promise<App> {
|
|
|
193
191
|
},
|
|
194
192
|
});
|
|
195
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
|
+
|
|
196
200
|
// Try to install Unhead plugin if available
|
|
197
201
|
// This is needed for layouts that use useHead from @vueuse/head
|
|
198
202
|
try {
|
|
199
203
|
// Dynamic import to check if @vueuse/head is available
|
|
200
204
|
// This module is externalized in vite.config.ts so it won't be bundled
|
|
201
|
-
|
|
202
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
203
|
-
// @ts-ignore - @vueuse/head may not be available in all consuming apps
|
|
204
|
-
const headModule = await import(/* @vite-ignore */ "@vueuse/head");
|
|
205
|
+
const headModule = await dynamicImport("@vueuse/head");
|
|
205
206
|
if (headModule && typeof headModule.createHead === "function") {
|
|
206
207
|
const head = headModule.createHead();
|
|
207
208
|
app.use(head);
|
|
@@ -211,6 +212,92 @@ export async function createIframeApp(container: HTMLElement): Promise<App> {
|
|
|
211
212
|
// This is expected if the consuming app doesn't use @vueuse/head
|
|
212
213
|
}
|
|
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
|
+
}
|
|
214
301
|
// Mount the app
|
|
215
302
|
app.mount(container);
|
|
216
303
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div id="page-viewport" class="page-renderer-wrapper relative">
|
|
3
3
|
<template v-if="isReady">
|
|
4
|
-
<component :is="layoutComponent" v-if="withLayout && layoutComponent" v-bind="settings">
|
|
4
|
+
<component :is="layoutComponent" v-if="withLayout && layoutComponent" v-bind="settings" :blocks="blocks">
|
|
5
5
|
<template #default>
|
|
6
6
|
<div id="page-blocks-wrapper">
|
|
7
7
|
<div
|
|
@@ -54,7 +54,7 @@ const isReady = ref(false);
|
|
|
54
54
|
|
|
55
55
|
// Get the layout component based on the layout prop
|
|
56
56
|
const layoutComponent = computed(() => {
|
|
57
|
-
return getLayout(props.layout);
|
|
57
|
+
return getLayout(props.layout || props.settings?.layout);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
// Get the margin class for the block
|
|
@@ -84,10 +84,14 @@ onBeforeMount(async () => {
|
|
|
84
84
|
});
|
|
85
85
|
</script>
|
|
86
86
|
|
|
87
|
-
<style
|
|
87
|
+
<style lang="scss">
|
|
88
88
|
@use "../../assets/styles/mixins" as *;
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Styles are scoped to the component using the root class selector instead of Vue's scoped attribute
|
|
91
|
+
// This ensures styles work correctly when the component is consumed from a library
|
|
92
|
+
.page-renderer-wrapper {
|
|
93
|
+
.block-wrapper {
|
|
94
|
+
@include block-margin-classes;
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
97
|
</style>
|
|
@@ -178,8 +178,8 @@ export async function initialiseLayoutRegistry(): Promise<void> {
|
|
|
178
178
|
// Get the active theme
|
|
179
179
|
const activeTheme = getActiveTheme();
|
|
180
180
|
|
|
181
|
-
// Use virtual module to load all layouts (scans all
|
|
182
|
-
// Then filter to only process layouts from the active theme
|
|
181
|
+
// Use virtual module to load all layouts (scans all theme directories at build time)
|
|
182
|
+
// Then filter to only process layouts from the active theme directory
|
|
183
183
|
const { modules: layoutModules } = await import("vue-wswg-editor:layouts");
|
|
184
184
|
|
|
185
185
|
// Filter to only layouts from the active theme
|