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.
- package/README.md +23 -8
- package/dist/style.css +1 -1
- package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +1 -0
- package/dist/types/components/EditorPageRenderer/EditorPageRenderer.vue.d.ts +21 -0
- package/dist/types/components/EmptyState/EmptyState.vue.d.ts +2 -8
- package/dist/types/components/IframePreview/IframePreview.vue.d.ts +26 -0
- package/dist/types/components/IframePreview/iframeContent.d.ts +9 -0
- package/dist/types/components/IframePreview/iframePreviewApp.d.ts +36 -0
- package/dist/types/components/IframePreview/messageHandler.d.ts +55 -0
- package/dist/types/components/IframePreview/types.d.ts +77 -0
- package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +2 -0
- package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +2 -0
- package/dist/types/components/PageSettings/PageSettings.vue.d.ts +2 -0
- package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.vue.d.ts → WswgPageBuilder/WswgPageBuilder.vue.d.ts} +2 -0
- package/dist/types/index.d.ts +8 -2
- package/dist/types/util/registry.d.ts +2 -0
- package/dist/types/util/theme-registry.d.ts +42 -0
- package/dist/types/util/validation.d.ts +2 -2
- package/dist/vite-plugin.js +33 -29
- package/dist/vue-wswg-editor.es.js +2783 -1905
- package/package.json +1 -2
- package/src/assets/styles/_mixins.scss +15 -0
- package/src/components/AddBlockItem/AddBlockItem.vue +13 -4
- package/src/components/BlockBrowser/BlockBrowser.vue +5 -5
- package/src/components/BlockComponent/BlockComponent.vue +23 -50
- package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +12 -10
- package/src/components/BlockEditorFields/BlockEditorFields.vue +24 -4
- package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +9 -4
- package/src/components/BrowserNavigation/BrowserNavigation.vue +1 -1
- package/src/components/EditorPageRenderer/EditorPageRenderer.vue +641 -0
- package/src/components/EmptyState/EmptyState.vue +3 -12
- package/src/components/IframePreview/IframePreview.vue +211 -0
- package/src/components/IframePreview/iframeContent.ts +230 -0
- package/src/components/IframePreview/iframePreviewApp.ts +308 -0
- package/src/components/IframePreview/messageHandler.ts +219 -0
- package/src/components/IframePreview/types.ts +126 -0
- package/src/components/PageBlockList/PageBlockList.vue +8 -6
- package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +5 -3
- package/src/components/PageRenderer/PageRenderer.vue +18 -38
- package/src/components/PageSettings/PageSettings.vue +10 -6
- package/src/components/ResizeHandle/ResizeHandle.vue +68 -10
- package/src/components/{WswgJsonEditor/WswgJsonEditor.test.ts → WswgPageBuilder/WswgPageBuilder.test.ts} +8 -8
- package/src/components/WswgPageBuilder/WswgPageBuilder.vue +375 -0
- package/src/index.ts +10 -2
- package/src/shims.d.ts +4 -0
- package/src/types/Theme.d.ts +15 -0
- package/src/util/registry.ts +2 -2
- package/src/util/theme-registry.ts +397 -0
- package/src/util/validation.ts +102 -11
- package/src/vite-plugin.ts +8 -4
- package/types/vue-wswg-editor.d.ts +4 -0
- package/dist/types/components/PageRenderer/blockModules.d.ts +0 -3
- package/dist/types/components/PageRenderer/layoutModules.d.ts +0 -3
- package/src/components/PageRenderer/blockModules-alternative.ts.example +0 -9
- package/src/components/PageRenderer/blockModules-manual.ts.example +0 -19
- package/src/components/PageRenderer/blockModules-runtime.ts.example +0 -23
- package/src/components/PageRenderer/blockModules.ts +0 -32
- package/src/components/PageRenderer/layoutModules.ts +0 -32
- package/src/components/WswgJsonEditor/WswgJsonEditor.vue +0 -595
- /package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.test.d.ts → WswgPageBuilder/WswgPageBuilder.test.d.ts} +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="containerRef" class="iframe-preview-container">
|
|
3
|
+
<iframe ref="iframeRef" title="Page preview" :src="iframeSrc" class="iframe-preview" frameborder="0"></iframe>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { ref, watch, onMounted, onBeforeUnmount, nextTick, computed } from "vue";
|
|
9
|
+
import { generateIframeHTML } from "./iframeContent";
|
|
10
|
+
import {
|
|
11
|
+
sendPageDataUpdate,
|
|
12
|
+
sendActiveBlock,
|
|
13
|
+
sendHoveredBlock,
|
|
14
|
+
sendSettingsOpen,
|
|
15
|
+
sendScrollToBlock,
|
|
16
|
+
handleIframeMessage,
|
|
17
|
+
} from "./messageHandler";
|
|
18
|
+
import type { Block } from "../../types/Block";
|
|
19
|
+
import { getIframeAppModuleUrl } from "./iframePreviewApp";
|
|
20
|
+
|
|
21
|
+
// Get the iframe app module URL
|
|
22
|
+
// This will be processed by the consuming app's Vite build
|
|
23
|
+
const iframeAppModuleUrl = getIframeAppModuleUrl();
|
|
24
|
+
|
|
25
|
+
const props = defineProps<{
|
|
26
|
+
pageData?: Record<string, any>;
|
|
27
|
+
activeBlock: Block | null;
|
|
28
|
+
hoveredBlockId: string | null;
|
|
29
|
+
viewport: "desktop" | "mobile";
|
|
30
|
+
editable?: boolean;
|
|
31
|
+
blocksKey?: string;
|
|
32
|
+
settingsKey?: string;
|
|
33
|
+
settingsOpen?: boolean;
|
|
34
|
+
theme?: string;
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
(e: "click-block", block: Block | null): void;
|
|
39
|
+
(e: "hover-block", blockId: string | null): void;
|
|
40
|
+
(e: "block-reorder", oldIndex: number, newIndex: number): void;
|
|
41
|
+
(e: "block-add", blockType: string, index: number): void;
|
|
42
|
+
(e: "click-partial", partialValue: string): void;
|
|
43
|
+
}>();
|
|
44
|
+
|
|
45
|
+
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
|
46
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
47
|
+
const iframeReady = ref(false);
|
|
48
|
+
const iframeSrc = ref<string>("");
|
|
49
|
+
|
|
50
|
+
const blocksKey = computed(() => props.blocksKey || "blocks");
|
|
51
|
+
const settingsKey = computed(() => props.settingsKey || "settings");
|
|
52
|
+
|
|
53
|
+
// Generate blob URL for iframe
|
|
54
|
+
function createIframeSrc(): string {
|
|
55
|
+
const html = generateIframeHTML(iframeAppModuleUrl);
|
|
56
|
+
const blob = new Blob([html], { type: "text/html" });
|
|
57
|
+
return URL.createObjectURL(blob);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Update iframe content - send pageData to Vue app in iframe
|
|
61
|
+
async function updateIframeContent() {
|
|
62
|
+
if (!iframeRef.value || !iframeReady.value) return;
|
|
63
|
+
if (!props.pageData || !props.pageData[blocksKey.value]) return;
|
|
64
|
+
|
|
65
|
+
await nextTick();
|
|
66
|
+
sendPageDataUpdate(iframeRef.value, props.pageData, blocksKey.value, settingsKey.value, props.theme || "default");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Setup message listener
|
|
70
|
+
let messageListener: ((event: MessageEvent) => void) | null = null;
|
|
71
|
+
|
|
72
|
+
function setupMessageListener() {
|
|
73
|
+
messageListener = (event: MessageEvent) => {
|
|
74
|
+
// Only handle messages from our iframe
|
|
75
|
+
if (event.source !== iframeRef.value?.contentWindow) return;
|
|
76
|
+
|
|
77
|
+
handleIframeMessage(event, {
|
|
78
|
+
onBlockClick: (_blockId: string, block: any) => {
|
|
79
|
+
emit("click-block", block);
|
|
80
|
+
},
|
|
81
|
+
onBlockHover: (blockId: string | null) => {
|
|
82
|
+
emit("hover-block", blockId);
|
|
83
|
+
},
|
|
84
|
+
onBlockReorder: (oldIndex: number, newIndex: number) => {
|
|
85
|
+
emit("block-reorder", oldIndex, newIndex);
|
|
86
|
+
},
|
|
87
|
+
onBlockAdd: (blockType: string, index: number) => {
|
|
88
|
+
emit("block-add", blockType, index);
|
|
89
|
+
},
|
|
90
|
+
onPartialClick: (partialValue: string) => {
|
|
91
|
+
emit("click-partial", partialValue);
|
|
92
|
+
},
|
|
93
|
+
onIframeReady: () => {
|
|
94
|
+
iframeReady.value = true;
|
|
95
|
+
// Wait a bit for iframe Vue app to be fully ready
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
updateIframeContent();
|
|
98
|
+
// Send initial settingsOpen state
|
|
99
|
+
if (iframeRef.value && props.settingsOpen !== undefined) {
|
|
100
|
+
sendSettingsOpen(iframeRef.value, props.settingsOpen);
|
|
101
|
+
}
|
|
102
|
+
}, 100);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
window.addEventListener("message", messageListener);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cleanupMessageListener() {
|
|
111
|
+
if (messageListener) {
|
|
112
|
+
window.removeEventListener("message", messageListener);
|
|
113
|
+
messageListener = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Watch for pageData changes
|
|
118
|
+
watch(
|
|
119
|
+
[() => props.pageData, () => props.theme],
|
|
120
|
+
async () => {
|
|
121
|
+
await nextTick();
|
|
122
|
+
updateIframeContent();
|
|
123
|
+
},
|
|
124
|
+
{ deep: true }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Watch for activeBlock changes
|
|
128
|
+
watch(
|
|
129
|
+
() => props.activeBlock,
|
|
130
|
+
async (block) => {
|
|
131
|
+
if (iframeRef.value && iframeReady.value) {
|
|
132
|
+
sendActiveBlock(iframeRef.value, block || null);
|
|
133
|
+
// Also scroll to block
|
|
134
|
+
if (block?.id) {
|
|
135
|
+
await nextTick();
|
|
136
|
+
sendScrollToBlock(iframeRef.value, block.id);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Watch for hoveredBlockId changes
|
|
143
|
+
watch(
|
|
144
|
+
() => props.hoveredBlockId,
|
|
145
|
+
(blockId) => {
|
|
146
|
+
if (iframeRef.value && iframeReady.value) {
|
|
147
|
+
sendHoveredBlock(iframeRef.value, blockId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Watch for settingsOpen changes
|
|
153
|
+
watch(
|
|
154
|
+
() => props.settingsOpen,
|
|
155
|
+
(settingsOpen) => {
|
|
156
|
+
if (iframeRef.value && iframeReady.value) {
|
|
157
|
+
sendSettingsOpen(iframeRef.value, settingsOpen ?? false);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
onMounted(() => {
|
|
163
|
+
iframeSrc.value = createIframeSrc();
|
|
164
|
+
setupMessageListener();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
onBeforeUnmount(() => {
|
|
168
|
+
cleanupMessageListener();
|
|
169
|
+
// Cleanup blob URL
|
|
170
|
+
if (iframeSrc.value.startsWith("blob:")) {
|
|
171
|
+
URL.revokeObjectURL(iframeSrc.value);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<style scoped lang="scss">
|
|
177
|
+
.iframe-preview-container {
|
|
178
|
+
// position: relative;
|
|
179
|
+
// width: 100%;
|
|
180
|
+
// overflow: hidden;
|
|
181
|
+
// background-color: #ededed;
|
|
182
|
+
height: -webkit-fill-available;
|
|
183
|
+
|
|
184
|
+
&.mobile-viewport {
|
|
185
|
+
.iframe-preview {
|
|
186
|
+
max-width: 384px;
|
|
187
|
+
margin: 0 auto;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.iframe-preview {
|
|
193
|
+
display: block;
|
|
194
|
+
width: 100%;
|
|
195
|
+
height: 100%;
|
|
196
|
+
background: white;
|
|
197
|
+
border: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.hidden-renderer {
|
|
201
|
+
position: absolute;
|
|
202
|
+
top: -9999px;
|
|
203
|
+
left: -9999px;
|
|
204
|
+
visibility: hidden;
|
|
205
|
+
width: 1px;
|
|
206
|
+
height: 1px;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
pointer-events: none;
|
|
209
|
+
opacity: 0;
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract CSS variables from the parent document
|
|
3
|
+
* Extracts variables from :root and specific selectors like .wswg-page-builder
|
|
4
|
+
*/
|
|
5
|
+
function extractParentCSSVariables(): string {
|
|
6
|
+
if (typeof document === "undefined" || typeof window === "undefined") return "";
|
|
7
|
+
|
|
8
|
+
const variables: string[] = [];
|
|
9
|
+
|
|
10
|
+
// Get all CSS variables from :root
|
|
11
|
+
const rootVariables = new Set<string>();
|
|
12
|
+
const allStyles = Array.from(document.styleSheets);
|
|
13
|
+
|
|
14
|
+
// Extract from stylesheets
|
|
15
|
+
allStyles.forEach((sheet) => {
|
|
16
|
+
try {
|
|
17
|
+
const rules = Array.from(sheet.cssRules || sheet.rules || []);
|
|
18
|
+
rules.forEach((rule) => {
|
|
19
|
+
if (rule instanceof CSSStyleRule) {
|
|
20
|
+
const { selectorText, style } = rule;
|
|
21
|
+
// Check if this rule targets :root or html
|
|
22
|
+
if (selectorText === ":root" || selectorText === "html" || selectorText === "html:root") {
|
|
23
|
+
for (let i = 0; i < style.length; i++) {
|
|
24
|
+
const property = style[i];
|
|
25
|
+
if (property.startsWith("--")) {
|
|
26
|
+
const value = style.getPropertyValue(property);
|
|
27
|
+
rootVariables.add(`${property}: ${value};`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
// Cross-origin stylesheets will throw errors, ignore them
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Also extract from inline styles in <style> tags
|
|
39
|
+
const styleTags = document.querySelectorAll("head style");
|
|
40
|
+
styleTags.forEach((style) => {
|
|
41
|
+
const content = style.textContent || style.innerHTML;
|
|
42
|
+
if (content) {
|
|
43
|
+
// Match CSS variable declarations
|
|
44
|
+
const varRegex = /--[\w-]+\s*:\s*[^;]+;/g;
|
|
45
|
+
const matches = content.match(varRegex);
|
|
46
|
+
if (matches) {
|
|
47
|
+
matches.forEach((match) => {
|
|
48
|
+
rootVariables.add(match.trim());
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (rootVariables.size > 0) {
|
|
55
|
+
variables.push(`:root {`);
|
|
56
|
+
rootVariables.forEach((variable) => {
|
|
57
|
+
variables.push(` ${variable}`);
|
|
58
|
+
});
|
|
59
|
+
variables.push(`}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return variables.length > 0 ? `<style>${variables.join("\n")}</style>` : "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract stylesheets from the parent document
|
|
67
|
+
* This includes both <link rel="stylesheet"> tags and <style> tags
|
|
68
|
+
*/
|
|
69
|
+
function extractParentStylesheets(): string {
|
|
70
|
+
if (typeof document === "undefined") return "";
|
|
71
|
+
|
|
72
|
+
const stylesheets: string[] = [];
|
|
73
|
+
|
|
74
|
+
// Extract <link rel="stylesheet"> tags
|
|
75
|
+
const linkTags = document.querySelectorAll('head link[rel="stylesheet"]');
|
|
76
|
+
linkTags.forEach((link) => {
|
|
77
|
+
const href = link.getAttribute("href");
|
|
78
|
+
if (href) {
|
|
79
|
+
// Convert relative URLs to absolute URLs
|
|
80
|
+
const absoluteHref = new URL(href, window.location.href).href;
|
|
81
|
+
stylesheets.push(`<link rel="stylesheet" href="${absoluteHref}">`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Extract <style> tags
|
|
86
|
+
const styleTags = document.querySelectorAll("head style");
|
|
87
|
+
styleTags.forEach((style) => {
|
|
88
|
+
const content = style.textContent || style.innerHTML;
|
|
89
|
+
if (content) {
|
|
90
|
+
stylesheets.push(`<style>${content}</style>`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return stylesheets.join("\n ");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate HTML content for the iframe
|
|
99
|
+
* This creates a standalone HTML page with a Vue app that will be loaded in the iframe
|
|
100
|
+
* The Vue app uses PageRenderer to render blocks reactively
|
|
101
|
+
*
|
|
102
|
+
* @param iframeAppModuleUrl - URL to the iframe app module bundle
|
|
103
|
+
* This should be obtained using import.meta.url from iframePreviewApp.ts
|
|
104
|
+
*/
|
|
105
|
+
export function generateIframeHTML(iframeAppModuleUrl?: string): string {
|
|
106
|
+
const parentStylesheets = extractParentStylesheets();
|
|
107
|
+
const parentCSSVariables = extractParentCSSVariables();
|
|
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";
|
|
110
|
+
// Get parent origin to use as base URL for relative asset paths
|
|
111
|
+
const parentOrigin = typeof window !== "undefined" ? window.location.origin : "";
|
|
112
|
+
|
|
113
|
+
// Define Vue feature flags before Vue is loaded
|
|
114
|
+
// These must be defined as global constants before any Vue code runs
|
|
115
|
+
const vueFeatureFlagsScript = `<script>
|
|
116
|
+
// Define Vue feature flags to prevent warnings
|
|
117
|
+
// These must be defined before Vue or the library code is imported
|
|
118
|
+
// Using var to make them available globally (not just on window)
|
|
119
|
+
var __VUE_OPTIONS_API__ = true;
|
|
120
|
+
var __VUE_PROD_DEVTOOLS__ = false;
|
|
121
|
+
var __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false;
|
|
122
|
+
|
|
123
|
+
// Also set on window for compatibility
|
|
124
|
+
window.__VUE_OPTIONS_API__ = true;
|
|
125
|
+
window.__VUE_PROD_DEVTOOLS__ = false;
|
|
126
|
+
window.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false;
|
|
127
|
+
</script>`;
|
|
128
|
+
|
|
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
|
|
131
|
+
const appScript = iframeAppModuleUrl
|
|
132
|
+
? `<script type="module">
|
|
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
|
+
|
|
138
|
+
import { createIframeApp } from '${iframeAppModuleUrl}';
|
|
139
|
+
|
|
140
|
+
const appEl = document.getElementById('app');
|
|
141
|
+
if (appEl) {
|
|
142
|
+
createIframeApp(appEl).catch(error => {
|
|
143
|
+
console.error('Failed to create iframe app:', error);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
</script>`
|
|
147
|
+
: `<script type="module">
|
|
148
|
+
// Fallback: Wait for parent to send module URL via postMessage
|
|
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;
|
|
153
|
+
|
|
154
|
+
let appInitialized = false;
|
|
155
|
+
|
|
156
|
+
window.addEventListener('message', async (event) => {
|
|
157
|
+
if (event.data.type === 'INIT_IFRAME_APP' && event.data.moduleUrl && !appInitialized) {
|
|
158
|
+
appInitialized = true;
|
|
159
|
+
try {
|
|
160
|
+
const { createIframeApp } = await import(event.data.moduleUrl);
|
|
161
|
+
const appEl = document.getElementById('app');
|
|
162
|
+
if (appEl) {
|
|
163
|
+
createIframeApp(appEl);
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('Failed to load iframe app module:', error);
|
|
167
|
+
// Fallback: create minimal Vue app
|
|
168
|
+
const appEl = document.getElementById('app');
|
|
169
|
+
if (appEl) {
|
|
170
|
+
createApp({
|
|
171
|
+
template: '<div class="p-4">Loading preview...</div>'
|
|
172
|
+
}).mount(appEl);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Notify parent that we're ready to receive module URL
|
|
179
|
+
if (window.parent) {
|
|
180
|
+
window.parent.postMessage({ type: 'IFRAME_READY_FOR_APP' }, '*');
|
|
181
|
+
}
|
|
182
|
+
</script>`;
|
|
183
|
+
|
|
184
|
+
return `<!DOCTYPE html>
|
|
185
|
+
<html lang="en">
|
|
186
|
+
<head>
|
|
187
|
+
<meta charset="UTF-8">
|
|
188
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
189
|
+
<title>Page Preview</title>
|
|
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>
|
|
202
|
+
${parentStylesheets}
|
|
203
|
+
${parentCSSVariables}
|
|
204
|
+
<style>
|
|
205
|
+
/* Additional iframe-specific styles */
|
|
206
|
+
html, body {
|
|
207
|
+
margin: 0;
|
|
208
|
+
padding: 0;
|
|
209
|
+
width: 100%;
|
|
210
|
+
height: 100%;
|
|
211
|
+
overflow-x: hidden;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#app {
|
|
215
|
+
width: 100%;
|
|
216
|
+
min-height: 100vh;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.page-renderer-wrapper {
|
|
220
|
+
width: 100%;
|
|
221
|
+
}
|
|
222
|
+
</style>
|
|
223
|
+
</head>
|
|
224
|
+
<body>
|
|
225
|
+
<div id="app"></div>
|
|
226
|
+
${vueFeatureFlagsScript}
|
|
227
|
+
${appScript}
|
|
228
|
+
</body>
|
|
229
|
+
</html>`;
|
|
230
|
+
}
|