@bagelink/blox 1.5.15 → 1.5.20
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 +105 -1
- package/dist/blox.css +720 -68
- package/dist/components/base/Button.vue.d.ts.map +1 -1
- package/dist/components/base/Container.vue.d.ts.map +1 -1
- package/dist/components/base/Image.vue.d.ts.map +1 -1
- package/dist/components/base/Spacer.vue.d.ts.map +1 -1
- package/dist/components/base/Text.vue.d.ts.map +1 -1
- package/dist/components/base/Title.vue.d.ts.map +1 -1
- package/dist/components/blocks/BigImage.vue.d.ts +12 -0
- package/dist/components/blocks/BigImage.vue.d.ts.map +1 -0
- package/dist/components/blocks/BigQuote.vue.d.ts +14 -0
- package/dist/components/blocks/BigQuote.vue.d.ts.map +1 -0
- package/dist/components/blocks/BlockFooter.vue.d.ts +17 -0
- package/dist/components/blocks/BlockFooter.vue.d.ts.map +1 -0
- package/dist/components/blocks/BlockNav.vue.d.ts +22 -0
- package/dist/components/blocks/BlockNav.vue.d.ts.map +1 -0
- package/dist/components/blocks/Cards.vue.d.ts +18 -0
- package/dist/components/blocks/Cards.vue.d.ts.map +1 -0
- package/dist/components/blocks/Contact.vue.d.ts +22 -0
- package/dist/components/blocks/Contact.vue.d.ts.map +1 -0
- package/dist/components/blocks/CountDown.vue.d.ts +76 -0
- package/dist/components/blocks/CountDown.vue.d.ts.map +1 -0
- package/dist/components/blocks/Cta.vue.d.ts +15 -0
- package/dist/components/blocks/Cta.vue.d.ts.map +1 -0
- package/dist/components/blocks/Faq.vue.d.ts +20 -0
- package/dist/components/blocks/Faq.vue.d.ts.map +1 -0
- package/dist/components/blocks/Grid.vue.d.ts +24 -0
- package/dist/components/blocks/Grid.vue.d.ts.map +1 -0
- package/dist/components/blocks/Icons.vue.d.ts +16 -0
- package/dist/components/blocks/Icons.vue.d.ts.map +1 -0
- package/dist/components/blocks/IconsList.vue.d.ts +22 -0
- package/dist/components/blocks/IconsList.vue.d.ts.map +1 -0
- package/dist/components/blocks/ImageCarousel.vue.d.ts +16 -0
- package/dist/components/blocks/ImageCarousel.vue.d.ts.map +1 -0
- package/dist/components/blocks/ImageLinkBoxes.vue.d.ts +14 -0
- package/dist/components/blocks/ImageLinkBoxes.vue.d.ts.map +1 -0
- package/dist/components/blocks/Logos.vue.d.ts +17 -0
- package/dist/components/blocks/Logos.vue.d.ts.map +1 -0
- package/dist/components/blocks/PopUp.vue.d.ts +23 -0
- package/dist/components/blocks/PopUp.vue.d.ts.map +1 -0
- package/dist/components/blocks/PriceTable.vue.d.ts +24 -0
- package/dist/components/blocks/PriceTable.vue.d.ts.map +1 -0
- package/dist/components/blocks/Space.vue.d.ts +25 -0
- package/dist/components/blocks/Space.vue.d.ts.map +1 -0
- package/dist/components/blocks/Tabs.vue.d.ts +15 -0
- package/dist/components/blocks/Tabs.vue.d.ts.map +1 -0
- package/dist/components/blocks/Team.vue.d.ts +19 -0
- package/dist/components/blocks/Team.vue.d.ts.map +1 -0
- package/dist/components/blocks/Testimonials.vue.d.ts +18 -0
- package/dist/components/blocks/Testimonials.vue.d.ts.map +1 -0
- package/dist/components/blocks/Text.vue.d.ts +10 -0
- package/dist/components/blocks/Text.vue.d.ts.map +1 -0
- package/dist/components/blocks/TextImage.vue.d.ts +20 -0
- package/dist/components/blocks/TextImage.vue.d.ts.map +1 -0
- package/dist/components/blocks/TextSide.vue.d.ts +19 -0
- package/dist/components/blocks/TextSide.vue.d.ts.map +1 -0
- package/dist/components/blocks/Title.vue.d.ts +16 -0
- package/dist/components/blocks/Title.vue.d.ts.map +1 -0
- package/dist/components/blocks/TitleSide.vue.d.ts +21 -0
- package/dist/components/blocks/TitleSide.vue.d.ts.map +1 -0
- package/dist/components/blocks/VideoBox.vue.d.ts +15 -0
- package/dist/components/blocks/VideoBox.vue.d.ts.map +1 -0
- package/dist/components/blocks/blocks.d.ts +27 -0
- package/dist/components/blocks/blocks.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/config/baseComponents.d.ts +12 -2
- package/dist/config/baseComponents.d.ts.map +1 -1
- package/dist/config/blockComponents.d.ts +41 -0
- package/dist/config/blockComponents.d.ts.map +1 -0
- package/dist/core/communication.d.ts.map +1 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/renderer.d.ts.map +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.cjs +9420 -480
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +9325 -478
- package/dist/setup.d.ts +114 -6
- package/dist/setup.d.ts.map +1 -1
- package/dist/utils/componentPreviewGenerator.d.ts +113 -0
- package/dist/utils/componentPreviewGenerator.d.ts.map +1 -0
- package/dist/utils/normalizer.d.ts.map +1 -1
- package/dist/utils/styles.d.ts.map +1 -1
- package/dist/views/ExternalPreview.vue.d.ts.map +1 -1
- package/dist/views/RenderPage.vue.d.ts.map +1 -1
- package/package.json +5 -2
- package/components/base/Button.vue +0 -140
- package/components/base/Container.vue +0 -64
- package/components/base/Image.vue +0 -75
- package/components/base/Spacer.vue +0 -33
- package/components/base/Text.vue +0 -37
- package/components/base/Title.vue +0 -55
- package/components/index.ts +0 -20
- package/config/baseComponents.ts +0 -342
- package/core/communication.ts +0 -140
- package/core/registry.ts +0 -108
- package/core/renderer.ts +0 -217
- package/core/types.ts +0 -148
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.d.ts.map +0 -1
- package/index.ts +0 -33
- package/setup.ts +0 -56
- package/utils/normalizer.ts +0 -74
- package/utils/styles.ts +0 -228
- package/views/ExternalPreview.vue +0 -420
- package/views/RenderPage.vue +0 -127
package/utils/styles.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate CSS styles from block data
|
|
3
|
-
*/
|
|
4
|
-
export function generateBlockStyles(
|
|
5
|
-
data: Record<string, any>,
|
|
6
|
-
isMobile: boolean = false
|
|
7
|
-
): Record<string, string> {
|
|
8
|
-
const styles: Record<string, string> = {}
|
|
9
|
-
|
|
10
|
-
// Helper to coerce numeric-like values
|
|
11
|
-
function toNumber(v: any): number | null {
|
|
12
|
-
if (v === undefined || v === null || v === '') return null
|
|
13
|
-
const n = Number(v)
|
|
14
|
-
return Number.isNaN(n) ? null : n
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Helper to get value with mobile override
|
|
18
|
-
function getValue(desktopKey: string, mobileKey: string) {
|
|
19
|
-
if (isMobile && data[mobileKey] !== undefined && data[mobileKey] !== null) {
|
|
20
|
-
return data[mobileKey]
|
|
21
|
-
}
|
|
22
|
-
return data[desktopKey]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Margin and padding (use rem units) with mobile overrides
|
|
26
|
-
const mTop = toNumber(getValue('marginTop', 'marginTopMobile'))
|
|
27
|
-
const mBottom = toNumber(getValue('marginBottom', 'marginBottomMobile'))
|
|
28
|
-
const pad = toNumber(getValue('padding', 'paddingMobile'))
|
|
29
|
-
|
|
30
|
-
// Set margin values
|
|
31
|
-
if (mTop !== null) {
|
|
32
|
-
styles['margin-top'] = `${mTop}rem`
|
|
33
|
-
styles['--margin-top'] = `${mTop}rem`
|
|
34
|
-
} else {
|
|
35
|
-
styles['--margin-top'] = '0rem'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (mBottom !== null) {
|
|
39
|
-
styles['margin-bottom'] = `${mBottom}rem`
|
|
40
|
-
styles['--margin-bottom'] = `${mBottom}rem`
|
|
41
|
-
} else {
|
|
42
|
-
styles['--margin-bottom'] = '0rem'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (pad !== null) {
|
|
46
|
-
styles.padding = `${pad}rem`
|
|
47
|
-
styles['--block-padding'] = `${pad}rem`
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Width behavior
|
|
51
|
-
const wDesktop = toNumber(data.width)
|
|
52
|
-
const wMobile = toNumber(data.widthMobile)
|
|
53
|
-
const wPercentDesktop = toNumber(data.widthPercent) || 96
|
|
54
|
-
const wPercentMobile = toNumber(data.widthPercentMobile) || wPercentDesktop
|
|
55
|
-
const fullWidthDesktop = data.fullWidth
|
|
56
|
-
|
|
57
|
-
// Set CSS variables for responsive width
|
|
58
|
-
styles['--max-width-desktop'] = wDesktop !== null ? `${wDesktop}px` : '800px'
|
|
59
|
-
styles['--max-width-mobile'] = wMobile !== null ? `${wMobile}px` : wDesktop !== null ? `${wDesktop}px` : '800px'
|
|
60
|
-
styles['--width-percent-desktop'] = `${wPercentDesktop}%`
|
|
61
|
-
styles['--width-percent-mobile'] = `${wPercentMobile}%`
|
|
62
|
-
|
|
63
|
-
// Set margin centering for non-full-width blocks
|
|
64
|
-
if (!(fullWidthDesktop === true || fullWidthDesktop === 'true') && wDesktop !== null) {
|
|
65
|
-
styles['margin-left'] = 'auto'
|
|
66
|
-
styles['margin-right'] = 'auto'
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Border styles
|
|
70
|
-
const borderW = toNumber(getValue('borderWidth', 'borderWidthMobile'))
|
|
71
|
-
if (borderW !== null && borderW > 0) {
|
|
72
|
-
styles['border-width'] = `${borderW}px`
|
|
73
|
-
styles['border-style'] = getValue('borderStyle', 'borderStyleMobile') || 'solid'
|
|
74
|
-
const borderColor = getValue('borderColor', 'borderColorMobile')
|
|
75
|
-
if (borderColor) {
|
|
76
|
-
styles['border-color'] = borderColor
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Center alignment
|
|
81
|
-
const isCenter = getValue('center', 'centerMobile')
|
|
82
|
-
if (isCenter === true || isCenter === 'true') {
|
|
83
|
-
styles['text-align'] = 'center'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Border radius
|
|
87
|
-
const borderR = toNumber(getValue('borderRadius', 'borderRadiusMobile'))
|
|
88
|
-
if (borderR !== null) {
|
|
89
|
-
styles['border-radius'] = `${borderR}px`
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Colors with CSS variables
|
|
93
|
-
const bgColorDesktop = data.backgroundColor
|
|
94
|
-
const bgColorMobile = data.backgroundColorMobile
|
|
95
|
-
const textColorDesktop = data.textColor
|
|
96
|
-
const { textColorMobile } = data
|
|
97
|
-
|
|
98
|
-
if (bgColorDesktop) {
|
|
99
|
-
styles['--bg-color-desktop'] = bgColorDesktop
|
|
100
|
-
}
|
|
101
|
-
if (bgColorMobile) {
|
|
102
|
-
styles['--bg-color-mobile'] = bgColorMobile
|
|
103
|
-
}
|
|
104
|
-
if (textColorDesktop) {
|
|
105
|
-
styles['--text-color-desktop'] = textColorDesktop
|
|
106
|
-
}
|
|
107
|
-
if (textColorMobile) {
|
|
108
|
-
styles['--text-color-mobile'] = textColorMobile
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Shadow types
|
|
112
|
-
const shadowMap = {
|
|
113
|
-
none: 'none',
|
|
114
|
-
sm: '0 0 10px 0 rgba(0, 0, 0, 0.05)',
|
|
115
|
-
md: '0 0 20px 0 rgba(0, 0, 0, 0.15)',
|
|
116
|
-
lg: '0 0 30px 0 rgba(0, 0, 0, 0.15)',
|
|
117
|
-
xl: '0 0 40px 0 rgba(0, 0, 0, 0.5)',
|
|
118
|
-
custom: '0 4px 6px -1px rgba(0, 0, 0, 0.15)',
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const shadowType = getValue('shadowType', 'shadowTypeMobile')
|
|
122
|
-
if (shadowType && shadowType !== 'none') {
|
|
123
|
-
styles['box-shadow'] = shadowMap[shadowType as keyof typeof shadowMap] || shadowMap.md
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Z-index
|
|
127
|
-
const z = toNumber(getValue('zIndex', 'zIndexMobile'))
|
|
128
|
-
if (z !== null && z > 0) {
|
|
129
|
-
styles.position = 'relative'
|
|
130
|
-
styles['z-index'] = z.toString()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Visibility
|
|
134
|
-
if (!isMobile && data.showDesktop === false) {
|
|
135
|
-
styles.display = 'none'
|
|
136
|
-
} else if (isMobile && data.showMobile === false) {
|
|
137
|
-
styles.display = 'none'
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Font family
|
|
141
|
-
const fontFamilyDesktop = data.fontFamily
|
|
142
|
-
const { fontFamilyMobile } = data
|
|
143
|
-
|
|
144
|
-
if (isMobile && fontFamilyMobile && fontFamilyMobile.trim() !== '') {
|
|
145
|
-
styles['font-family'] = `"${fontFamilyMobile}", sans-serif`
|
|
146
|
-
} else if (fontFamilyDesktop && fontFamilyDesktop.trim() !== '') {
|
|
147
|
-
styles['font-family'] = `"${fontFamilyDesktop}", sans-serif`
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Custom CSS
|
|
151
|
-
if (data.customCSS) {
|
|
152
|
-
try {
|
|
153
|
-
const customRules = data.customCSS
|
|
154
|
-
.split(';')
|
|
155
|
-
.filter((rule: string) => rule.trim())
|
|
156
|
-
.map((rule: string) => rule.trim().split(':'))
|
|
157
|
-
.filter((parts: string[]) => parts.length === 2)
|
|
158
|
-
|
|
159
|
-
customRules.forEach(([property, value]: [string, string]) => {
|
|
160
|
-
const prop = property.trim()
|
|
161
|
-
const val = value.trim()
|
|
162
|
-
if (prop && val) {
|
|
163
|
-
styles[prop] = val
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
} catch (e) {
|
|
167
|
-
console.warn('Invalid custom CSS:', data.customCSS)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return styles
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Generate responsive CSS classes
|
|
176
|
-
*/
|
|
177
|
-
export function getResponsiveClasses(data: Record<string, any>): string[] {
|
|
178
|
-
const classes = ['blox-block', 'responsive-colors']
|
|
179
|
-
|
|
180
|
-
const effectiveDesktopFullWidth = Boolean(data.fullWidth)
|
|
181
|
-
const effectiveMobileFullWidth
|
|
182
|
-
= data.fullWidthMobile !== null ? Boolean(data.fullWidthMobile) : effectiveDesktopFullWidth
|
|
183
|
-
|
|
184
|
-
if (effectiveDesktopFullWidth) {
|
|
185
|
-
classes.push('full-width-desktop')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (effectiveMobileFullWidth) {
|
|
189
|
-
classes.push('full-width-mobile')
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return classes
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Generate the responsive CSS to be injected into the page
|
|
197
|
-
*/
|
|
198
|
-
export function getResponsiveCSS(): string {
|
|
199
|
-
return `
|
|
200
|
-
/* Blox Responsive System */
|
|
201
|
-
.responsive-colors {
|
|
202
|
-
color: var(--text-color-desktop, inherit);
|
|
203
|
-
background-color: var(--bg-color-desktop, transparent);
|
|
204
|
-
margin-inline: auto;
|
|
205
|
-
max-width: var(--max-width-desktop, 800px);
|
|
206
|
-
width: var(--width-percent-desktop, 96%);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.responsive-colors.full-width-desktop {
|
|
210
|
-
width: 100% !important;
|
|
211
|
-
max-width: none !important;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
@media (max-width: 910px) {
|
|
215
|
-
.responsive-colors {
|
|
216
|
-
color: var(--text-color-mobile, var(--text-color-desktop, inherit));
|
|
217
|
-
background-color: var(--bg-color-mobile, var(--bg-color-desktop, transparent));
|
|
218
|
-
max-width: var(--max-width-mobile, var(--max-width-desktop, 800px));
|
|
219
|
-
width: var(--width-percent-mobile, var(--width-percent-desktop, 96%));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.responsive-colors.full-width-mobile {
|
|
223
|
-
width: 100% !important;
|
|
224
|
-
max-width: none !important;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
`
|
|
228
|
-
}
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* External Preview View
|
|
4
|
-
*
|
|
5
|
-
* This component runs on the target site and communicates with the editor
|
|
6
|
-
* via postMessage. It receives component updates and renders them using
|
|
7
|
-
* the site's registered components.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ComponentData, PageData } from '../core/types'
|
|
11
|
-
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
12
|
-
import { createCommunicationBridge, type CommunicationBridge } from '../core/communication'
|
|
13
|
-
import { getAllComponents } from '../core/registry'
|
|
14
|
-
import { initializePage } from '../core/renderer'
|
|
15
|
-
import { normalizeComponentData } from '../utils/normalizer'
|
|
16
|
-
import { generateBlockStyles, getResponsiveClasses } from '../utils/styles'
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
origin?: string // Allowed editor origin (default: *)
|
|
20
|
-
initialPageData?: PageData // Optional initial page data
|
|
21
|
-
componentConfigs?: any[] // Component configurations with schemas
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
25
|
-
origin: '*',
|
|
26
|
-
componentConfigs: () => [],
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// State
|
|
30
|
-
const components = ref<ComponentData[]>([])
|
|
31
|
-
const highlightID = ref('')
|
|
32
|
-
const selectedID = ref('')
|
|
33
|
-
const isMobile = ref(false)
|
|
34
|
-
const previewMode = ref(false)
|
|
35
|
-
const linksDisabled = ref(false)
|
|
36
|
-
const loading = ref(true)
|
|
37
|
-
const updateCounter = ref(0)
|
|
38
|
-
|
|
39
|
-
// Communication bridge
|
|
40
|
-
let bridge: CommunicationBridge | null = null
|
|
41
|
-
|
|
42
|
-
// Get registered components
|
|
43
|
-
const registeredComponents = getAllComponents()
|
|
44
|
-
|
|
45
|
-
// Reactive computed for block styles
|
|
46
|
-
const getBlockStyles = computed(() => {
|
|
47
|
-
const _ = updateCounter.value
|
|
48
|
-
return (data: Record<string, any>, mobile: boolean = false) => generateBlockStyles(data, mobile)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Handle focus on a block
|
|
53
|
-
*/
|
|
54
|
-
function setFocus(id: string, emit = false) {
|
|
55
|
-
selectedID.value = id
|
|
56
|
-
highlightID.value = id
|
|
57
|
-
|
|
58
|
-
const el = document.querySelector(`[data-block-id="${id}"]`)
|
|
59
|
-
el?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
60
|
-
|
|
61
|
-
if (emit && bridge) {
|
|
62
|
-
bridge.send('focus', id)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Handle highlight on a block
|
|
68
|
-
*/
|
|
69
|
-
function setHighlight(id: string, emit = false) {
|
|
70
|
-
highlightID.value = id
|
|
71
|
-
|
|
72
|
-
if (emit && bridge) {
|
|
73
|
-
bridge.send('highlight', id)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Handle keyboard navigation
|
|
79
|
-
*/
|
|
80
|
-
function keyboardHandler(event: KeyboardEvent) {
|
|
81
|
-
if (previewMode.value) return
|
|
82
|
-
|
|
83
|
-
if (event.key === 'ArrowDown') {
|
|
84
|
-
const next = components.value.findIndex(comp => comp.id === highlightID.value) + 1
|
|
85
|
-
if (next < components.value.length) {
|
|
86
|
-
setFocus(components.value[next].id, true)
|
|
87
|
-
}
|
|
88
|
-
} else if (event.key === 'ArrowUp') {
|
|
89
|
-
const prev = components.value.findIndex(comp => comp.id === highlightID.value) - 1
|
|
90
|
-
if (prev >= 0) {
|
|
91
|
-
setFocus(components.value[prev].id, true)
|
|
92
|
-
}
|
|
93
|
-
} else if (event.key === 'Enter') {
|
|
94
|
-
if (bridge) bridge.send('focus', highlightID.value)
|
|
95
|
-
} else if (event.key === 'Escape') {
|
|
96
|
-
setHighlight('')
|
|
97
|
-
selectedID.value = ''
|
|
98
|
-
} else if (event.key === 'Delete') {
|
|
99
|
-
if (bridge) bridge.send('delete', highlightID.value)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Handle click on page (prevent navigation in edit mode)
|
|
105
|
-
*/
|
|
106
|
-
function clickHandler(event: MouseEvent) {
|
|
107
|
-
if (!linksDisabled.value) return
|
|
108
|
-
|
|
109
|
-
let element: HTMLElement | null = event.target as HTMLElement
|
|
110
|
-
|
|
111
|
-
while (element) {
|
|
112
|
-
if (element.tagName === 'A' && element.getAttribute('href')) {
|
|
113
|
-
const isButton
|
|
114
|
-
= element.classList.contains('btn')
|
|
115
|
-
|| element.classList.contains('button')
|
|
116
|
-
|| element.getAttribute('role') === 'button'
|
|
117
|
-
|
|
118
|
-
if (!isButton) {
|
|
119
|
-
event.preventDefault()
|
|
120
|
-
event.stopPropagation()
|
|
121
|
-
return false
|
|
122
|
-
} else {
|
|
123
|
-
event.preventDefault()
|
|
124
|
-
return false
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
element = element.parentElement
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Setup communication bridge
|
|
133
|
-
*/
|
|
134
|
-
function setupBridge() {
|
|
135
|
-
bridge = createCommunicationBridge({
|
|
136
|
-
origin: props.origin,
|
|
137
|
-
targetWindow: window.parent,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Handle update messages
|
|
141
|
-
bridge.on('update', ({ message, data, isMobile: mobile }) => {
|
|
142
|
-
// Data can be in message.data or at top level (for backward compatibility)
|
|
143
|
-
const componentData = message?.data || data
|
|
144
|
-
const mobileMode = message?.isMobile !== undefined ? message.isMobile : mobile
|
|
145
|
-
|
|
146
|
-
if (componentData) {
|
|
147
|
-
components.value = [...componentData]
|
|
148
|
-
updateCounter.value++
|
|
149
|
-
console.log('📦 Updated components:', components.value.length)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (mobileMode !== undefined) {
|
|
153
|
-
isMobile.value = mobileMode
|
|
154
|
-
console.log('📱 Mobile mode:', mobileMode)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
loading.value = false
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
// Handle highlight messages
|
|
161
|
-
bridge.on('highlight', ({ message }) => {
|
|
162
|
-
setHighlight(message)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
// Handle focus messages
|
|
166
|
-
bridge.on('focus', ({ message }) => {
|
|
167
|
-
setFocus(message)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
// Handle preview mode toggle
|
|
171
|
-
bridge.on('preview', ({ message }) => {
|
|
172
|
-
previewMode.value = message
|
|
173
|
-
|
|
174
|
-
if (previewMode.value) {
|
|
175
|
-
document.body.classList.add('blox-preview-mode')
|
|
176
|
-
document.body.classList.remove('blox-edit-mode')
|
|
177
|
-
} else {
|
|
178
|
-
document.body.classList.remove('blox-preview-mode')
|
|
179
|
-
document.body.classList.add('blox-edit-mode')
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
// Handle link disable toggle
|
|
184
|
-
bridge.on('disableLinks', ({ message }) => {
|
|
185
|
-
linksDisabled.value = message
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// Send ready message with component configurations
|
|
189
|
-
const registeredTypes = Object.keys(registeredComponents)
|
|
190
|
-
|
|
191
|
-
// Prepare component configs to send to editor
|
|
192
|
-
const configsToSend = props.componentConfigs.map(config => ({
|
|
193
|
-
id: config.id,
|
|
194
|
-
label: config.label,
|
|
195
|
-
icon: config.icon,
|
|
196
|
-
img: config.img,
|
|
197
|
-
order: config.order || 999,
|
|
198
|
-
content: config.content || [],
|
|
199
|
-
settings: config.settings || [],
|
|
200
|
-
}))
|
|
201
|
-
|
|
202
|
-
bridge.send('ready', {
|
|
203
|
-
registeredTypes,
|
|
204
|
-
componentConfigs: configsToSend,
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
console.log('📤 Sent component configurations:', configsToSend)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
onMounted(async () => {
|
|
211
|
-
console.log('🚀 External Preview mounted')
|
|
212
|
-
console.log('📦 Registered components:', Object.keys(registeredComponents))
|
|
213
|
-
|
|
214
|
-
// Setup communication
|
|
215
|
-
setupBridge()
|
|
216
|
-
|
|
217
|
-
// Add event listeners
|
|
218
|
-
window.addEventListener('keydown', keyboardHandler)
|
|
219
|
-
document.addEventListener('click', clickHandler, true)
|
|
220
|
-
|
|
221
|
-
// Set edit mode initially
|
|
222
|
-
document.body.classList.add('blox-edit-mode')
|
|
223
|
-
|
|
224
|
-
// Initialize with initial page data if provided
|
|
225
|
-
if (props.initialPageData) {
|
|
226
|
-
await initializePage(props.initialPageData)
|
|
227
|
-
components.value = props.initialPageData.components
|
|
228
|
-
loading.value = false
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
onUnmounted(() => {
|
|
233
|
-
// Cleanup
|
|
234
|
-
if (bridge) {
|
|
235
|
-
bridge.destroy()
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
window.removeEventListener('keydown', keyboardHandler)
|
|
239
|
-
document.removeEventListener('click', clickHandler, true)
|
|
240
|
-
|
|
241
|
-
document.body.classList.remove('blox-preview-mode', 'blox-edit-mode')
|
|
242
|
-
})
|
|
243
|
-
</script>
|
|
244
|
-
|
|
245
|
-
<template>
|
|
246
|
-
<div class="blox-page-wrapper">
|
|
247
|
-
<div v-if="loading" class="blox-loading">
|
|
248
|
-
<p>Loading preview...</p>
|
|
249
|
-
</div>
|
|
250
|
-
|
|
251
|
-
<div v-else-if="!components.length && !previewMode" class="blox-empty-state">
|
|
252
|
-
<p>No blocks yet. Add a block from the editor to get started!</p>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<div v-else-if="!components.length && previewMode" class="blox-empty-preview">
|
|
256
|
-
<!-- Empty page in preview mode -->
|
|
257
|
-
</div>
|
|
258
|
-
|
|
259
|
-
<template v-for="comp in components" :key="comp.id">
|
|
260
|
-
<div
|
|
261
|
-
class="blox-block-wrapper"
|
|
262
|
-
:class="{
|
|
263
|
-
'blox-highlight': !previewMode && highlightID === comp.id,
|
|
264
|
-
'blox-selected': !previewMode && selectedID === comp.id,
|
|
265
|
-
}"
|
|
266
|
-
:data-block-id="comp.id"
|
|
267
|
-
@click="!previewMode ? setFocus(comp.id, true) : null"
|
|
268
|
-
@mouseenter="!previewMode ? setHighlight(comp.id, true) : null"
|
|
269
|
-
@focus="!previewMode ? setHighlight(comp.id, true) : null"
|
|
270
|
-
>
|
|
271
|
-
<!-- Block label (edit mode only) -->
|
|
272
|
-
<p v-if="!previewMode" class="blox-block-label">
|
|
273
|
-
{{ comp.type }}
|
|
274
|
-
<span v-if="comp.data?.title"> | {{ comp.data.title }}</span>
|
|
275
|
-
</p>
|
|
276
|
-
|
|
277
|
-
<!-- Block content -->
|
|
278
|
-
<div
|
|
279
|
-
:id="comp.data.customId"
|
|
280
|
-
:key="`${comp.id}-${updateCounter}`"
|
|
281
|
-
:style="getBlockStyles(comp.data, isMobile)"
|
|
282
|
-
:class="getResponsiveClasses(comp.data)"
|
|
283
|
-
>
|
|
284
|
-
<component
|
|
285
|
-
:is="registeredComponents[comp.type]"
|
|
286
|
-
v-if="registeredComponents[comp.type]"
|
|
287
|
-
v-bind="{ ...normalizeComponentData(comp.data), isMobile }"
|
|
288
|
-
/>
|
|
289
|
-
<div v-else class="blox-missing-component">
|
|
290
|
-
⚠️ Component not registered: {{ comp.type }}
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<!-- Overlay for click handling (edit mode only) -->
|
|
295
|
-
<div v-if="!previewMode" class="blox-block-overlay" @click.self="setFocus(comp.id, true)" />
|
|
296
|
-
</div>
|
|
297
|
-
</template>
|
|
298
|
-
</div>
|
|
299
|
-
</template>
|
|
300
|
-
|
|
301
|
-
<style>
|
|
302
|
-
/* Global styles for the Blox page */
|
|
303
|
-
.blox-page-wrapper {
|
|
304
|
-
min-height: 100vh;
|
|
305
|
-
background-color: #fff;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.blox-page-wrapper * {
|
|
309
|
-
box-sizing: border-box;
|
|
310
|
-
}
|
|
311
|
-
</style>
|
|
312
|
-
|
|
313
|
-
<style scoped>
|
|
314
|
-
/* Loading state */
|
|
315
|
-
.blox-loading {
|
|
316
|
-
display: flex;
|
|
317
|
-
align-items: center;
|
|
318
|
-
justify-content: center;
|
|
319
|
-
min-height: 100vh;
|
|
320
|
-
font-size: 1.2rem;
|
|
321
|
-
color: #666;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/* Empty states */
|
|
325
|
-
.blox-empty-state {
|
|
326
|
-
display: flex;
|
|
327
|
-
align-items: center;
|
|
328
|
-
justify-content: center;
|
|
329
|
-
min-height: 100vh;
|
|
330
|
-
text-align: center;
|
|
331
|
-
font-size: 1.2rem;
|
|
332
|
-
color: #666;
|
|
333
|
-
padding: 2rem;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.blox-empty-preview {
|
|
337
|
-
min-height: 100vh;
|
|
338
|
-
background: #fff;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/* Block wrapper */
|
|
342
|
-
.blox-block-wrapper {
|
|
343
|
-
position: relative;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/* Block overlay for click detection */
|
|
347
|
-
.blox-block-overlay {
|
|
348
|
-
position: absolute;
|
|
349
|
-
inset: 0;
|
|
350
|
-
pointer-events: none;
|
|
351
|
-
z-index: 9;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/* Interactive elements should be clickable */
|
|
355
|
-
.blox-block-wrapper button,
|
|
356
|
-
.blox-block-wrapper a,
|
|
357
|
-
.blox-block-wrapper input,
|
|
358
|
-
.blox-block-wrapper textarea,
|
|
359
|
-
.blox-block-wrapper select {
|
|
360
|
-
pointer-events: auto;
|
|
361
|
-
position: relative;
|
|
362
|
-
z-index: 10;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/* Preview mode - all interactions work */
|
|
366
|
-
.blox-preview-mode .blox-block-wrapper * {
|
|
367
|
-
pointer-events: auto !important;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/* Block label */
|
|
371
|
-
.blox-block-label {
|
|
372
|
-
position: absolute;
|
|
373
|
-
top: 0;
|
|
374
|
-
left: 0;
|
|
375
|
-
font-size: 0.625rem;
|
|
376
|
-
padding: 0.25rem 0.5rem;
|
|
377
|
-
color: white;
|
|
378
|
-
background: #9ca3af;
|
|
379
|
-
border-bottom-right-radius: 5px;
|
|
380
|
-
z-index: 10001;
|
|
381
|
-
opacity: 0;
|
|
382
|
-
transition: opacity 0.2s;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/* Highlight state */
|
|
386
|
-
.blox-highlight {
|
|
387
|
-
background: rgba(156, 163, 175, 0.1);
|
|
388
|
-
outline: 1px solid #9ca3af;
|
|
389
|
-
outline-offset: -1px;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.blox-highlight .blox-block-label {
|
|
393
|
-
opacity: 1;
|
|
394
|
-
background: #9ca3af;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/* Selected state */
|
|
398
|
-
.blox-selected {
|
|
399
|
-
background: rgba(88, 191, 235, 0.08) !important;
|
|
400
|
-
outline: 2px solid #3b82f6 !important;
|
|
401
|
-
outline-offset: -2px;
|
|
402
|
-
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
.blox-selected .blox-block-label {
|
|
406
|
-
opacity: 1;
|
|
407
|
-
background: #3b82f6;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/* Missing component warning */
|
|
411
|
-
.blox-missing-component {
|
|
412
|
-
padding: 2rem;
|
|
413
|
-
background: #fef3c7;
|
|
414
|
-
border: 2px dashed #f59e0b;
|
|
415
|
-
border-radius: 8px;
|
|
416
|
-
text-align: center;
|
|
417
|
-
font-size: 1rem;
|
|
418
|
-
color: #92400e;
|
|
419
|
-
}
|
|
420
|
-
</style>
|