@beforesemicolon/site-builder 0.34.0 → 0.36.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/cjs/build-templates.js +5 -5
- package/dist/esm/build-templates.js +5 -5
- package/netlify/functions/auth-middleware.js +183 -0
- package/netlify/functions/auth0-config.js +77 -0
- package/netlify/functions/build.js +214 -0
- package/netlify/functions/copy-file-local.js +88 -0
- package/netlify/functions/github.js +771 -0
- package/netlify/functions/validate-user.js +109 -0
- package/netlify/functions/widgets.js +403 -0
- package/netlify.toml +67 -0
- package/package.json +2 -2
- package/scaffolds/.env.example +18 -0
- package/scaffolds/_redirects +12 -0
- package/scaffolds/admin/app.js +244 -0
- package/scaffolds/admin/auth-manager.js +275 -0
- package/scaffolds/admin/controls/code.control.js +22 -0
- package/scaffolds/admin/controls/controls.js +249 -0
- package/scaffolds/admin/controls/file.control.js +829 -0
- package/scaffolds/admin/controls/html.control.js +43 -0
- package/scaffolds/admin/controls/markdown.control.js +31 -0
- package/scaffolds/admin/data.js +543 -0
- package/scaffolds/admin/flashbar.js +104 -0
- package/scaffolds/admin/index.html +44 -0
- package/scaffolds/admin/modal.js +123 -0
- package/scaffolds/admin/preview-widget.js +102 -0
- package/scaffolds/admin/preview.js +197 -0
- package/scaffolds/admin/repository-manager.js +329 -0
- package/scaffolds/admin/styles.css +1526 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const { html, effect, state, repeat } = window.BFS.MARKUP
|
|
2
|
+
|
|
3
|
+
const [flashbarMessages, setFlashbarMessages] = state([]) // {id: string, title: string, message: string, type: 'error' | 'warning' | 'info'}
|
|
4
|
+
|
|
5
|
+
const dismissTimers = new Map()
|
|
6
|
+
|
|
7
|
+
effect(() => {
|
|
8
|
+
const messages = flashbarMessages()
|
|
9
|
+
|
|
10
|
+
messages.forEach((message) => {
|
|
11
|
+
if (message.type === 'success' && !dismissTimers.has(message.id)) {
|
|
12
|
+
const timerId = setTimeout(() => {
|
|
13
|
+
setFlashbarMessages((prev) =>
|
|
14
|
+
prev.filter((msg) => msg.id !== message.id)
|
|
15
|
+
)
|
|
16
|
+
dismissTimers.delete(message.id)
|
|
17
|
+
}, 5000)
|
|
18
|
+
dismissTimers.set(message.id, timerId)
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const showFlashbar = ({
|
|
24
|
+
id,
|
|
25
|
+
type,
|
|
26
|
+
title,
|
|
27
|
+
message,
|
|
28
|
+
loading,
|
|
29
|
+
dismissable = true,
|
|
30
|
+
}) => {
|
|
31
|
+
id = id || Date.now()
|
|
32
|
+
setFlashbarMessages((prev) => [
|
|
33
|
+
{
|
|
34
|
+
id,
|
|
35
|
+
type,
|
|
36
|
+
title,
|
|
37
|
+
message,
|
|
38
|
+
loading,
|
|
39
|
+
},
|
|
40
|
+
...prev,
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
return id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const hideFlashbar = (id) => {
|
|
47
|
+
setFlashbarMessages((prev) => prev.filter((msg) => msg.id !== id))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dismissFlashbar = (message) => {
|
|
51
|
+
// Clear timer if exists
|
|
52
|
+
if (dismissTimers.has(message.id)) {
|
|
53
|
+
clearTimeout(dismissTimers.get(message.id))
|
|
54
|
+
dismissTimers.delete(message.id)
|
|
55
|
+
}
|
|
56
|
+
setFlashbarMessages((prev) => prev.filter((msg) => msg.id !== message.id))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Render flashbar messages
|
|
60
|
+
export const Flashbars = html`
|
|
61
|
+
<div class="flashbar-container">
|
|
62
|
+
${repeat(
|
|
63
|
+
flashbarMessages,
|
|
64
|
+
(message) => html`
|
|
65
|
+
<div
|
|
66
|
+
class="flashbar flashbar-${message.type}"
|
|
67
|
+
data-flashbar-id="${message.id}"
|
|
68
|
+
>
|
|
69
|
+
<div class="flashbar-content">
|
|
70
|
+
<div class="flashbar-icon">
|
|
71
|
+
${message.type === 'info' && message.loading
|
|
72
|
+
? html`<div class="spinner-small"></div>`
|
|
73
|
+
: message.type === 'success'
|
|
74
|
+
? '✓'
|
|
75
|
+
: message.type === 'error'
|
|
76
|
+
? '✕'
|
|
77
|
+
: 'ℹ'}
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flashbar-text">
|
|
80
|
+
<strong class="flashbar-title"
|
|
81
|
+
>${message.title}</strong
|
|
82
|
+
>
|
|
83
|
+
${message.message
|
|
84
|
+
? html`<p class="flashbar-message">
|
|
85
|
+
${message.message}
|
|
86
|
+
</p>`
|
|
87
|
+
: ''}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
${!message.loading &&
|
|
91
|
+
html`
|
|
92
|
+
<button
|
|
93
|
+
class="flashbar-dismiss"
|
|
94
|
+
onclick="${() => dismissFlashbar(message)}"
|
|
95
|
+
aria-label="Dismiss"
|
|
96
|
+
>
|
|
97
|
+
✕
|
|
98
|
+
</button>
|
|
99
|
+
`}
|
|
100
|
+
</div>
|
|
101
|
+
`
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>BFS Content Management System</title>
|
|
7
|
+
<link rel="stylesheet" href="/admin/styles.css" />
|
|
8
|
+
<link
|
|
9
|
+
rel="icon"
|
|
10
|
+
type="image/x-icon"
|
|
11
|
+
href="/assets/favicons/favicon.ico"
|
|
12
|
+
/>
|
|
13
|
+
<script src="https://unpkg.com/@beforesemicolon/markup/dist/client.js"></script>
|
|
14
|
+
<script src="https://unpkg.com/@beforesemicolon/site-builder/dist/client.js"></script>
|
|
15
|
+
<!-- Auth0 SPA SDK -->
|
|
16
|
+
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js"></script>
|
|
17
|
+
|
|
18
|
+
<!-- Enhanced Admin Controls Libraries -->
|
|
19
|
+
<!-- Quill Rich Text Editor -->
|
|
20
|
+
<link
|
|
21
|
+
href="https://cdn.quilljs.com/1.3.6/quill.snow.css"
|
|
22
|
+
rel="stylesheet"
|
|
23
|
+
/>
|
|
24
|
+
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
|
25
|
+
|
|
26
|
+
<!-- Prism Code Syntax Highlighting -->
|
|
27
|
+
<link
|
|
28
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css"
|
|
29
|
+
rel="stylesheet"
|
|
30
|
+
/>
|
|
31
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
|
32
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
33
|
+
|
|
34
|
+
<!-- TinyMDE Markdown Editor -->
|
|
35
|
+
<script src="https://unpkg.com/tiny-markdown-editor@0.1.0/dist/tiny-mde.min.js"></script>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<!-- /#app -->
|
|
39
|
+
<div id="app"></div>
|
|
40
|
+
|
|
41
|
+
<!-- Scripts -->
|
|
42
|
+
<script type="module" src="/admin/app.js"></script>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const { html, state, when, pick } = window.BFS.MARKUP
|
|
2
|
+
|
|
3
|
+
const [currentModal, setCurrentModal] = state(null) // null | {title: string, size: 'lg' | 'md', description?: string, actionLabel?: string, action?: () => void, loading: boolean, content: string | HTMLTemplate, actions?: Array}
|
|
4
|
+
|
|
5
|
+
export const showModal = (options) => {
|
|
6
|
+
setCurrentModal({
|
|
7
|
+
id: Date.now(),
|
|
8
|
+
dismissable: true,
|
|
9
|
+
...options,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const hideModal = () => {
|
|
14
|
+
setCurrentModal(null)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Modal = html`
|
|
18
|
+
${when(currentModal, () => {
|
|
19
|
+
return html`
|
|
20
|
+
<div class="app-dialog-backdrop">
|
|
21
|
+
<dialog
|
|
22
|
+
ref="dialog"
|
|
23
|
+
class="app-dialog ${pick(currentModal, 'size')}"
|
|
24
|
+
>
|
|
25
|
+
<header class="app-dialog-header">
|
|
26
|
+
<h4>${pick(currentModal, 'title')}</h4>
|
|
27
|
+
${when(
|
|
28
|
+
pick(currentModal, 'description'),
|
|
29
|
+
() =>
|
|
30
|
+
html`<p class="app-dialog-description">
|
|
31
|
+
${pick(currentModal, 'description')}
|
|
32
|
+
</p>`
|
|
33
|
+
)}
|
|
34
|
+
</header>
|
|
35
|
+
<div class="app-dialog-content">
|
|
36
|
+
${pick(currentModal, 'content')}
|
|
37
|
+
</div>
|
|
38
|
+
<footer class="app-dialog-footer">
|
|
39
|
+
${when(
|
|
40
|
+
pick(currentModal, 'dismissable'),
|
|
41
|
+
() =>
|
|
42
|
+
html` <button
|
|
43
|
+
type="button"
|
|
44
|
+
onclick="${hideModal}"
|
|
45
|
+
>
|
|
46
|
+
Cancel
|
|
47
|
+
</button>`
|
|
48
|
+
)}
|
|
49
|
+
${when(
|
|
50
|
+
pick(currentModal, 'actions'),
|
|
51
|
+
() => html`
|
|
52
|
+
${pick(currentModal, 'actions', (actions) =>
|
|
53
|
+
actions.map(
|
|
54
|
+
(action) => html`
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
disabled="${typeof action.loading ===
|
|
58
|
+
'function'
|
|
59
|
+
? action.loading()
|
|
60
|
+
: action.loading}"
|
|
61
|
+
onclick="${async () => {
|
|
62
|
+
await action.onClick?.()
|
|
63
|
+
if (
|
|
64
|
+
action.dismiss !== false
|
|
65
|
+
) {
|
|
66
|
+
hideModal()
|
|
67
|
+
}
|
|
68
|
+
}}"
|
|
69
|
+
>
|
|
70
|
+
<span class="row">
|
|
71
|
+
${when(
|
|
72
|
+
typeof action.loading ===
|
|
73
|
+
'function'
|
|
74
|
+
? action.loading()
|
|
75
|
+
: action.loading,
|
|
76
|
+
() =>
|
|
77
|
+
html`<div
|
|
78
|
+
class="spinner-small"
|
|
79
|
+
></div> `
|
|
80
|
+
)}
|
|
81
|
+
${action.label || 'Ok'}
|
|
82
|
+
</span>
|
|
83
|
+
</button>
|
|
84
|
+
`
|
|
85
|
+
)
|
|
86
|
+
)}
|
|
87
|
+
`,
|
|
88
|
+
html`
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
disabled="${currentModal().loading}"
|
|
92
|
+
onclick="${async () => {
|
|
93
|
+
await currentModal().action?.()
|
|
94
|
+
hideModal()
|
|
95
|
+
}}"
|
|
96
|
+
>
|
|
97
|
+
<span class="row">
|
|
98
|
+
${when(
|
|
99
|
+
currentModal().loading,
|
|
100
|
+
() =>
|
|
101
|
+
html`<div
|
|
102
|
+
class="spinner-small"
|
|
103
|
+
></div> `
|
|
104
|
+
)}
|
|
105
|
+
${pick(
|
|
106
|
+
currentModal,
|
|
107
|
+
'actionLabel',
|
|
108
|
+
(lbl) => lbl || 'Ok'
|
|
109
|
+
)}
|
|
110
|
+
</span>
|
|
111
|
+
</button>
|
|
112
|
+
`
|
|
113
|
+
)}
|
|
114
|
+
</footer>
|
|
115
|
+
</dialog>
|
|
116
|
+
</div>
|
|
117
|
+
`.onMount((tmp) => {
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
tmp.refs['dialog'][0].showModal()
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})}
|
|
123
|
+
`
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config,
|
|
3
|
+
currentPage,
|
|
4
|
+
currentWidget,
|
|
5
|
+
currentWidgetId,
|
|
6
|
+
hasPendingChanges,
|
|
7
|
+
isLocalDevelopment,
|
|
8
|
+
} from './data.js'
|
|
9
|
+
|
|
10
|
+
const { MARKUP, SITE_BUILDER } = window.BFS
|
|
11
|
+
const { html, effect } = MARKUP
|
|
12
|
+
const { parseStyle, inputDefinitionsToObject } = SITE_BUILDER
|
|
13
|
+
|
|
14
|
+
const buildWidgetPreview = (widget, skipStyles = false) => {
|
|
15
|
+
const inputValues = inputDefinitionsToObject(widget?.inputs ?? [])
|
|
16
|
+
|
|
17
|
+
const basePage =
|
|
18
|
+
currentPage() ??
|
|
19
|
+
config()?.pages.find((pg) => pg.type === 'base') ??
|
|
20
|
+
null
|
|
21
|
+
const env = {
|
|
22
|
+
assetsOrigin: window.location.origin + '/',
|
|
23
|
+
prod: !isLocalDevelopment,
|
|
24
|
+
...basePage,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const content = widget.render({ ...inputValues, env }) ?? ''
|
|
28
|
+
|
|
29
|
+
let styles = ''
|
|
30
|
+
if (!skipStyles && widget.style) {
|
|
31
|
+
const styleObject =
|
|
32
|
+
typeof widget.style === 'function'
|
|
33
|
+
? widget.style({ ...inputValues, env })
|
|
34
|
+
: widget.style
|
|
35
|
+
styles = parseStyle(styleObject, widget.cssSelector ?? '')
|
|
36
|
+
}
|
|
37
|
+
return { content, styles }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let commonHeader = ''
|
|
41
|
+
|
|
42
|
+
const getHeadContent = async () => {
|
|
43
|
+
if (commonHeader) {
|
|
44
|
+
return commonHeader
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const res = await fetch(location.origin)
|
|
48
|
+
const str = await res.text()
|
|
49
|
+
|
|
50
|
+
const m = str.match(new RegExp('<head>(.*)</head>'))
|
|
51
|
+
|
|
52
|
+
if (m) {
|
|
53
|
+
commonHeader = m[1]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return commonHeader
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const WidgetPreview = html`
|
|
60
|
+
<iframe
|
|
61
|
+
ref="iframe"
|
|
62
|
+
style="width: 100%; height: 100%; border: none; background: none;"
|
|
63
|
+
></iframe>
|
|
64
|
+
`.onMount((temp) => {
|
|
65
|
+
const [iframe] = temp.refs['iframe']
|
|
66
|
+
|
|
67
|
+
return effect(() => {
|
|
68
|
+
hasPendingChanges() // just to trigger updates
|
|
69
|
+
const w = currentWidget()
|
|
70
|
+
const wId = currentWidgetId()
|
|
71
|
+
|
|
72
|
+
if (w) {
|
|
73
|
+
const { content, styles } = buildWidgetPreview(
|
|
74
|
+
w,
|
|
75
|
+
Boolean(iframe.srcdoc)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
getHeadContent().then((headContent) => {
|
|
79
|
+
if (iframe.srcdoc) {
|
|
80
|
+
const doc =
|
|
81
|
+
iframe.contentDocument || iframe.contentWindow?.document
|
|
82
|
+
doc.body.innerHTML = `<widget id="${wId}" style="display: block;">${content}</widget>`
|
|
83
|
+
} else {
|
|
84
|
+
iframe.srcdoc = `
|
|
85
|
+
<!DOCTYPE html>
|
|
86
|
+
<html lang="en">
|
|
87
|
+
<head>
|
|
88
|
+
${headContent}
|
|
89
|
+
<style>${styles}</style>
|
|
90
|
+
</head>
|
|
91
|
+
<body>
|
|
92
|
+
<widget id="${wId}" style="display: block;">${content}</widget>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
`
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
} else {
|
|
99
|
+
iframe.srcdoc = ''
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config,
|
|
3
|
+
currentPage,
|
|
4
|
+
currentWidget,
|
|
5
|
+
currentWidgetId,
|
|
6
|
+
hasPendingChanges,
|
|
7
|
+
isLocalDevelopment,
|
|
8
|
+
loadWidget,
|
|
9
|
+
widgetElements,
|
|
10
|
+
widgets,
|
|
11
|
+
} from './data.js'
|
|
12
|
+
|
|
13
|
+
const { html, element, effect } = window.BFS.MARKUP
|
|
14
|
+
|
|
15
|
+
const currentPageUrl = () => currentPage()?.url ?? ''
|
|
16
|
+
|
|
17
|
+
export const getWidgetById = (id) => widgets.get(id)
|
|
18
|
+
|
|
19
|
+
const getHighestZIndex = (root) => {
|
|
20
|
+
let max = 0
|
|
21
|
+
const nodes = [root, ...root.querySelectorAll('*')]
|
|
22
|
+
nodes.forEach((node) => {
|
|
23
|
+
const value = window.getComputedStyle(node).zIndex
|
|
24
|
+
const numeric = Number.parseInt(value, 10)
|
|
25
|
+
if (Number.isFinite(numeric)) {
|
|
26
|
+
max = Math.max(max, numeric)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
return max
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const initPreviewContent = (e) => {
|
|
33
|
+
const currentIframe = e.target
|
|
34
|
+
if (currentIframe) {
|
|
35
|
+
const iframeDoc =
|
|
36
|
+
currentIframe.contentDocument ||
|
|
37
|
+
currentIframe.contentWindow?.document
|
|
38
|
+
|
|
39
|
+
if (!iframeDoc) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const style = element('style', {
|
|
44
|
+
attributes: {
|
|
45
|
+
class: 'cms-widget-preview',
|
|
46
|
+
},
|
|
47
|
+
textContent: `
|
|
48
|
+
widget {
|
|
49
|
+
position: relative;
|
|
50
|
+
transition: all 0.2s ease;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
box-sizing: border-box;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
widget .__overlay__ {
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
display: inline-block;
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: 0;
|
|
60
|
+
left: 0;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: 100%;
|
|
63
|
+
background: #007bff7a;
|
|
64
|
+
display: none;
|
|
65
|
+
border: 2px solid #007bff !important;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
widget:hover .__overlay__ {
|
|
69
|
+
display: block;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
widget.cms-widget-selected .__overlay__ {
|
|
73
|
+
border: 2px solid #28a745 !important;
|
|
74
|
+
background: none;
|
|
75
|
+
display: block;
|
|
76
|
+
}
|
|
77
|
+
`,
|
|
78
|
+
})
|
|
79
|
+
iframeDoc.head.appendChild(style)
|
|
80
|
+
|
|
81
|
+
let selectedWidget
|
|
82
|
+
|
|
83
|
+
iframeDoc.querySelectorAll('widget').forEach((el) => {
|
|
84
|
+
const widgetId = el.getAttribute('id')
|
|
85
|
+
|
|
86
|
+
if (widgetId) {
|
|
87
|
+
widgetElements.set(widgetId, el)
|
|
88
|
+
|
|
89
|
+
requestAnimationFrame(() => {
|
|
90
|
+
const highestZIndex = getHighestZIndex(el)
|
|
91
|
+
const overlayZIndex = highestZIndex + 1
|
|
92
|
+
|
|
93
|
+
el.appendChild(
|
|
94
|
+
element('span', {
|
|
95
|
+
attributes: {
|
|
96
|
+
class: '__overlay__',
|
|
97
|
+
style: `z-index: ${overlayZIndex};`,
|
|
98
|
+
onclick: (e) => {
|
|
99
|
+
e.preventDefault()
|
|
100
|
+
e.stopPropagation()
|
|
101
|
+
|
|
102
|
+
selectedWidget?.classList.remove(
|
|
103
|
+
'cms-widget-selected'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
el.classList.add('cms-widget-selected')
|
|
107
|
+
|
|
108
|
+
// Scroll widget into view if needed
|
|
109
|
+
el.scrollIntoView({
|
|
110
|
+
behavior: 'smooth',
|
|
111
|
+
block: 'center',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
selectedWidget = el
|
|
115
|
+
|
|
116
|
+
loadWidget(widgetId)
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const Preview = html`
|
|
128
|
+
<iframe
|
|
129
|
+
src="${currentPageUrl}"
|
|
130
|
+
onload="${initPreviewContent}"
|
|
131
|
+
style="width: 100%; height: 100%; border: none; background: none;"
|
|
132
|
+
id="preview-iframe"
|
|
133
|
+
></iframe>
|
|
134
|
+
`.onMount(() => {
|
|
135
|
+
return effect(() => {
|
|
136
|
+
hasPendingChanges() // just to force trigger update
|
|
137
|
+
const widget = currentWidget()
|
|
138
|
+
const widgetId = currentWidgetId()
|
|
139
|
+
|
|
140
|
+
if (!widgetId) return
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const widgetElement = widgetElements.get(widgetId)
|
|
144
|
+
|
|
145
|
+
if (!widgetElement) {
|
|
146
|
+
console.error(`Widget element not found for ${widgetId}`)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function extractValue(input) {
|
|
151
|
+
if (input.type === 'list') {
|
|
152
|
+
return (input.definitions ?? []).map(extractValue)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (input.type === 'group') {
|
|
156
|
+
return (input.definitions ?? []).reduce((acc, i) => {
|
|
157
|
+
return {
|
|
158
|
+
...acc,
|
|
159
|
+
[i.name]: extractValue(i),
|
|
160
|
+
}
|
|
161
|
+
}, {})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return input.value
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const inputValues = (widget?.inputs ?? []).reduce((acc, input) => {
|
|
168
|
+
return {
|
|
169
|
+
...acc,
|
|
170
|
+
[input.name]: extractValue(input),
|
|
171
|
+
}
|
|
172
|
+
}, {})
|
|
173
|
+
|
|
174
|
+
const basePage =
|
|
175
|
+
currentPage() ??
|
|
176
|
+
config()?.pages.find((p) => p.type === 'base') ??
|
|
177
|
+
null
|
|
178
|
+
|
|
179
|
+
const env = {
|
|
180
|
+
assetsOrigin: window.location.origin + '/',
|
|
181
|
+
prod: !isLocalDevelopment,
|
|
182
|
+
...basePage,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
requestAnimationFrame(() => {
|
|
186
|
+
const newHTML = widget.render({ ...inputValues, env })
|
|
187
|
+
|
|
188
|
+
const overlay = widgetElement.querySelector('.__overlay__')
|
|
189
|
+
|
|
190
|
+
widgetElement.innerHTML = newHTML
|
|
191
|
+
widgetElement.appendChild(overlay)
|
|
192
|
+
})
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Error updating preview:', error)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
})
|