@beforesemicolon/site-builder 0.35.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,249 @@
|
|
|
1
|
+
import { renderFileUploadControl } from './file.control.js'
|
|
2
|
+
import { renderQuillEditor } from './html.control.js'
|
|
3
|
+
import { renderCodeEditor } from './code.control.js'
|
|
4
|
+
import { renderMarkdownEditor } from './markdown.control.js'
|
|
5
|
+
import {
|
|
6
|
+
currentWidget,
|
|
7
|
+
currentWidgetId,
|
|
8
|
+
originalWidgetValues,
|
|
9
|
+
getPendingChange,
|
|
10
|
+
removePendingChange,
|
|
11
|
+
addPendingChange,
|
|
12
|
+
panel,
|
|
13
|
+
currentPage,
|
|
14
|
+
} from '../data.js'
|
|
15
|
+
|
|
16
|
+
const { html, repeat, pick, when, is } = window.BFS.MARKUP
|
|
17
|
+
|
|
18
|
+
const FILE_TYPE_PATTERN = /image|video|audio|file|font|icon|pdf/
|
|
19
|
+
|
|
20
|
+
const resolveChangeType = () => {
|
|
21
|
+
if (panel() === 'page') {
|
|
22
|
+
return currentWidgetId() ? 'page-widget' : 'page'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return 'widget'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const resolveChangeId = (changeType, widgetId, pageId) => {
|
|
29
|
+
if (changeType === 'page') {
|
|
30
|
+
return pageId
|
|
31
|
+
}
|
|
32
|
+
return widgetId
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleControlValueChange = (
|
|
36
|
+
path,
|
|
37
|
+
value,
|
|
38
|
+
inputType = 'text',
|
|
39
|
+
fileData = null
|
|
40
|
+
) => {
|
|
41
|
+
const pathIndexes = String(path).split('.')
|
|
42
|
+
const widget = { ...currentWidget() }
|
|
43
|
+
const widgetId = currentWidgetId()
|
|
44
|
+
const pageId = currentPage()?.id ?? ''
|
|
45
|
+
const changeType = resolveChangeType()
|
|
46
|
+
const changeId = resolveChangeId(changeType, widgetId, pageId)
|
|
47
|
+
const originalKey =
|
|
48
|
+
changeType === 'page-widget' ? `${pageId}:${widgetId}` : widgetId
|
|
49
|
+
|
|
50
|
+
const i = pathIndexes.shift()
|
|
51
|
+
|
|
52
|
+
// copy to force update
|
|
53
|
+
widget.inputs[i] = {
|
|
54
|
+
...widget.inputs[i],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// use pathIndexes to locate the input nested items
|
|
58
|
+
let currentInput = widget.inputs[i]
|
|
59
|
+
|
|
60
|
+
while (pathIndexes.length) {
|
|
61
|
+
if (!currentInput.definitions) {
|
|
62
|
+
currentInput = null
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
currentInput = currentInput.definitions[pathIndexes.shift()]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (currentInput) {
|
|
70
|
+
// Get the original value for this path
|
|
71
|
+
const originalValue =
|
|
72
|
+
originalWidgetValues.get(originalKey)?.[path] ?? null
|
|
73
|
+
// update in place
|
|
74
|
+
currentInput.value = value
|
|
75
|
+
|
|
76
|
+
// Get current widget changes or create new one
|
|
77
|
+
const currentWidgetChanges = getPendingChange(changeId) ?? {
|
|
78
|
+
type: changeType,
|
|
79
|
+
id: changeId,
|
|
80
|
+
changes: {},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if the new value matches the original value
|
|
84
|
+
if (value === originalValue) {
|
|
85
|
+
// Value reverted to original - remove from pending changes
|
|
86
|
+
delete currentWidgetChanges.changes[path]
|
|
87
|
+
|
|
88
|
+
// If no more changes for this widget, remove it entirely
|
|
89
|
+
if (Object.keys(currentWidgetChanges.changes).length === 0) {
|
|
90
|
+
removePendingChange(changeId)
|
|
91
|
+
} else {
|
|
92
|
+
addPendingChange(changeId, currentWidgetChanges)
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Value is different from original - add/update pending change
|
|
96
|
+
const changeEntry = {
|
|
97
|
+
originalValue,
|
|
98
|
+
newValue: value,
|
|
99
|
+
inputType,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add file data if this is a file upload (Requirements 1.3, 3.4)
|
|
103
|
+
if (fileData && FILE_TYPE_PATTERN.test(inputType)) {
|
|
104
|
+
changeEntry.fileData = fileData
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
currentWidgetChanges.changes[path] = changeEntry
|
|
108
|
+
addPendingChange(changeId, currentWidgetChanges)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const inputControl = ({ type, value, name, dir, readonly, ...otherProps }) => {
|
|
114
|
+
return html` <label class="control-field-block">
|
|
115
|
+
<span class="label">${name}</span>
|
|
116
|
+
<input ${otherProps} type="${type}" value="${value}" name="${name}" />
|
|
117
|
+
</label>`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const renderInputControl = (
|
|
121
|
+
input,
|
|
122
|
+
idx = 0,
|
|
123
|
+
disabled,
|
|
124
|
+
onChange = handleControlValueChange
|
|
125
|
+
) => {
|
|
126
|
+
const { type, value, name } = input
|
|
127
|
+
|
|
128
|
+
if (input.readonly) {
|
|
129
|
+
return ''
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// convert name from camel case to space between words as label
|
|
133
|
+
const label = (name ?? '')
|
|
134
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
135
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
|
136
|
+
|
|
137
|
+
switch (type) {
|
|
138
|
+
case 'text':
|
|
139
|
+
case 'email':
|
|
140
|
+
case 'url':
|
|
141
|
+
case 'tel':
|
|
142
|
+
case 'number':
|
|
143
|
+
case 'color':
|
|
144
|
+
case 'date':
|
|
145
|
+
case 'datetime-local':
|
|
146
|
+
case 'month':
|
|
147
|
+
case 'time':
|
|
148
|
+
case 'week':
|
|
149
|
+
case 'password':
|
|
150
|
+
return inputControl({
|
|
151
|
+
...input,
|
|
152
|
+
name: label,
|
|
153
|
+
oninput: (e) => onChange(idx, e.target.value, type),
|
|
154
|
+
disabled,
|
|
155
|
+
})
|
|
156
|
+
case 'html':
|
|
157
|
+
return renderQuillEditor(input, idx, disabled, label, onChange)
|
|
158
|
+
case 'code':
|
|
159
|
+
return renderCodeEditor(input, idx, disabled, label, onChange)
|
|
160
|
+
case 'markdown':
|
|
161
|
+
return renderMarkdownEditor(input, idx, disabled, label, onChange)
|
|
162
|
+
case 'textarea':
|
|
163
|
+
return html` <label class="control-field-block">
|
|
164
|
+
<span class="label">${label}</span>
|
|
165
|
+
<textarea
|
|
166
|
+
rows="3"
|
|
167
|
+
cols="30"
|
|
168
|
+
disabled="${disabled}"
|
|
169
|
+
name="${name}"
|
|
170
|
+
oninput="${(e) => onChange(idx, e.target.value, type)}"
|
|
171
|
+
>
|
|
172
|
+
${value}</textarea
|
|
173
|
+
>
|
|
174
|
+
</label>`
|
|
175
|
+
case 'list':
|
|
176
|
+
case 'group':
|
|
177
|
+
case 'options':
|
|
178
|
+
return html`
|
|
179
|
+
<div class="control-field-block">
|
|
180
|
+
${name ? html`<h4 class="label">${label}</h4>` : ''}
|
|
181
|
+
<div class="nested-control">
|
|
182
|
+
${input.definitions.map((item, i) =>
|
|
183
|
+
renderInputControl(
|
|
184
|
+
{
|
|
185
|
+
readonly: input.readonly ?? item.readonly,
|
|
186
|
+
...item,
|
|
187
|
+
},
|
|
188
|
+
`${idx}.${i}`,
|
|
189
|
+
disabled,
|
|
190
|
+
onChange
|
|
191
|
+
)
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
`
|
|
196
|
+
case 'boolean':
|
|
197
|
+
return inputControl({
|
|
198
|
+
...input,
|
|
199
|
+
type: 'checkbox',
|
|
200
|
+
name: label,
|
|
201
|
+
oninput: (e) => onChange(idx, e.target.checked, type),
|
|
202
|
+
disabled,
|
|
203
|
+
})
|
|
204
|
+
case 'embed':
|
|
205
|
+
return inputControl({
|
|
206
|
+
...input,
|
|
207
|
+
type: 'url',
|
|
208
|
+
name: label,
|
|
209
|
+
oninput: (e) => onChange(idx, e.target.value, type),
|
|
210
|
+
disabled,
|
|
211
|
+
})
|
|
212
|
+
case 'image':
|
|
213
|
+
case 'video':
|
|
214
|
+
case 'audio':
|
|
215
|
+
case 'file':
|
|
216
|
+
case 'font':
|
|
217
|
+
case 'icon':
|
|
218
|
+
case 'pdf':
|
|
219
|
+
return renderFileUploadControl(
|
|
220
|
+
input,
|
|
221
|
+
idx,
|
|
222
|
+
(path, value, inputType, fileData) =>
|
|
223
|
+
onChange(path, value, inputType, fileData)
|
|
224
|
+
)
|
|
225
|
+
default:
|
|
226
|
+
return ''
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const Controls = ({ disabled }) => html`
|
|
231
|
+
<header>
|
|
232
|
+
${when(
|
|
233
|
+
is(panel, 'page'),
|
|
234
|
+
html`<div class="info-alert">
|
|
235
|
+
All changes done to this page will only apply to this page and
|
|
236
|
+
its content.
|
|
237
|
+
</div>`,
|
|
238
|
+
html`<div class="info-warning">
|
|
239
|
+
All changes done to this widget will apply to everywhere this
|
|
240
|
+
widget is used.
|
|
241
|
+
</div>`
|
|
242
|
+
)}
|
|
243
|
+
</header>
|
|
244
|
+
<div class="content">
|
|
245
|
+
${repeat(pick(currentWidget, 'inputs'), (input, idx) =>
|
|
246
|
+
renderInputControl(input, idx, disabled)
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
`
|