@edgedev/create-edge-app 1.1.25 → 1.1.26
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 +55 -20
- package/{agent.md → agents.md} +2 -0
- package/bin/cli.js +6 -6
- package/edge/components/auth/login.vue +384 -0
- package/edge/components/auth/register.vue +396 -0
- package/edge/components/auth.vue +108 -0
- package/edge/components/autoFileUpload.vue +215 -0
- package/edge/components/billing.vue +8 -0
- package/edge/components/buttonDivider.vue +14 -0
- package/edge/components/chip.vue +34 -0
- package/edge/components/clipboardButton.vue +42 -0
- package/edge/components/cms/block.vue +529 -0
- package/edge/components/cms/blockApi.vue +212 -0
- package/edge/components/cms/blockEditor.vue +725 -0
- package/edge/components/cms/blockInput.vue +66 -0
- package/edge/components/cms/blockPicker.vue +486 -0
- package/edge/components/cms/blockRender.vue +78 -0
- package/edge/components/cms/blockSheetContent.vue +28 -0
- package/edge/components/cms/codeEditor.vue +466 -0
- package/edge/components/cms/fontUpload.vue +327 -0
- package/edge/components/cms/htmlContent.vue +807 -0
- package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
- package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
- package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
- package/edge/components/cms/init_blocks/carousel.html +103 -0
- package/edge/components/cms/init_blocks/contact_us.html +69 -0
- package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
- package/edge/components/cms/init_blocks/footer.html +24 -0
- package/edge/components/cms/init_blocks/header_divider.html +7 -0
- package/edge/components/cms/init_blocks/hero.html +35 -0
- package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
- package/edge/components/cms/init_blocks/newsletter.html +117 -0
- package/edge/components/cms/init_blocks/post_content.html +7 -0
- package/edge/components/cms/init_blocks/post_title_header.html +21 -0
- package/edge/components/cms/init_blocks/posts_list.html +20 -0
- package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
- package/edge/components/cms/init_blocks/property_carousel.html +59 -0
- package/edge/components/cms/init_blocks/property_detail.html +112 -0
- package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
- package/edge/components/cms/init_blocks/property_results.html +137 -0
- package/edge/components/cms/init_blocks/property_search.html +75 -0
- package/edge/components/cms/init_blocks/simple_array.html +7 -0
- package/edge/components/cms/mediaCard.vue +116 -0
- package/edge/components/cms/mediaManager.vue +386 -0
- package/edge/components/cms/menu.vue +1103 -0
- package/edge/components/cms/optionsSelect.vue +107 -0
- package/edge/components/cms/page.vue +1785 -0
- package/edge/components/cms/posts.vue +1083 -0
- package/edge/components/cms/site.vue +1298 -0
- package/edge/components/cms/themeDefaultMenu.vue +548 -0
- package/edge/components/cms/themeEditor.vue +426 -0
- package/edge/components/dashboard.vue +776 -0
- package/edge/components/editor.vue +671 -0
- package/edge/components/fileTree.vue +72 -0
- package/edge/components/files.vue +89 -0
- package/edge/components/formSubtypes/myOrgs.vue +214 -0
- package/edge/components/formSubtypes/users.vue +336 -0
- package/edge/components/functionChips.vue +57 -0
- package/edge/components/gError.vue +98 -0
- package/edge/components/gHelper.vue +67 -0
- package/edge/components/gInput.vue +1331 -0
- package/edge/components/loggingIn.vue +41 -0
- package/edge/components/menu.vue +137 -0
- package/edge/components/menuContent.vue +132 -0
- package/edge/components/myAccount.vue +317 -0
- package/edge/components/myOrganizations.vue +75 -0
- package/edge/components/myProfile.vue +122 -0
- package/edge/components/orgSwitcher.vue +25 -0
- package/edge/components/organizationMembers.vue +522 -0
- package/edge/components/organizationSettings.vue +271 -0
- package/edge/components/shad/breadcrumbs.vue +35 -0
- package/edge/components/shad/button.vue +43 -0
- package/edge/components/shad/checkbox.vue +73 -0
- package/edge/components/shad/combobox.vue +238 -0
- package/edge/components/shad/datepicker.vue +184 -0
- package/edge/components/shad/dialog.vue +32 -0
- package/edge/components/shad/dropdownMenu.vue +54 -0
- package/edge/components/shad/dropdownMenuItem.vue +21 -0
- package/edge/components/shad/form.vue +59 -0
- package/edge/components/shad/html.vue +877 -0
- package/edge/components/shad/input.vue +139 -0
- package/edge/components/shad/number.vue +109 -0
- package/edge/components/shad/select.vue +151 -0
- package/edge/components/shad/selectTags.vue +278 -0
- package/edge/components/shad/switch.vue +67 -0
- package/edge/components/shad/tags.vue +137 -0
- package/edge/components/shad/textarea.vue +102 -0
- package/edge/components/shad/typeMoney.vue +167 -0
- package/edge/components/sideBar.vue +288 -0
- package/edge/components/sideBarContent.vue +268 -0
- package/edge/components/sidebarProvider.vue +33 -0
- package/edge/components/tooltip.vue +16 -0
- package/edge/components/userMenu.vue +148 -0
- package/edge/components/v/alert.vue +59 -0
- package/edge/components/v/alertTitle.vue +18 -0
- package/edge/components/v/card.vue +53 -0
- package/edge/components/v/cardActions.vue +18 -0
- package/edge/components/v/cardText.vue +18 -0
- package/edge/components/v/cardTitle.vue +20 -0
- package/edge/components/v/col.vue +56 -0
- package/edge/components/v/list.vue +46 -0
- package/edge/components/v/listItem.vue +26 -0
- package/edge/components/v/listItemTitle.vue +18 -0
- package/edge/components/v/row.vue +42 -0
- package/edge/components/v/toolbar.vue +24 -0
- package/edge/composables/global.ts +519 -0
- package/edge-pull.sh +2 -0
- package/edge-push.sh +1 -0
- package/edge-status.sh +14 -0
- package/package.json +1 -1
- package/edge-components-install.sh +0 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useVModel } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
themeId: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
headJson: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: '',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const emits = defineEmits(['update:headJson'])
|
|
16
|
+
|
|
17
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
18
|
+
|
|
19
|
+
const headJson = useVModel(props, 'headJson', emits, {
|
|
20
|
+
passive: false,
|
|
21
|
+
prop: 'headJson',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const state = reactive({
|
|
25
|
+
files: [],
|
|
26
|
+
uploading: false,
|
|
27
|
+
errors: [],
|
|
28
|
+
fontFamily: '',
|
|
29
|
+
fontDisplay: 'swap',
|
|
30
|
+
fileMeta: {},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const acceptList = ['.woff', '.woff2', '.ttf', '.otf', 'font/woff', 'font/woff2', 'application/font-woff', 'application/font-woff2', 'application/x-font-ttf', 'application/x-font-otf']
|
|
34
|
+
const normalizedAccept = computed(() => acceptList.join(','))
|
|
35
|
+
const collectionPath = computed(() => `${edgeGlobal.edgeState.organizationDocPath}/files`)
|
|
36
|
+
|
|
37
|
+
const slugify = (value) => String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, '')
|
|
38
|
+
|
|
39
|
+
const parseHead = () => {
|
|
40
|
+
try {
|
|
41
|
+
return { ok: true, head: JSON.parse(headJson.value || '{}') || {} }
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
state.errors = ['Head JSON is invalid JSON. Fix it before adding fonts to avoid losing content.']
|
|
45
|
+
return { ok: false, head: null }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const setHeadJson = (head) => {
|
|
50
|
+
if (!head || typeof head !== 'object')
|
|
51
|
+
return
|
|
52
|
+
headJson.value = JSON.stringify(head, null, 2)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fileKey = file => file?.name || file?.file?.name || crypto.randomUUID()
|
|
56
|
+
|
|
57
|
+
const formatFromName = (name = '', contentType = '') => {
|
|
58
|
+
const lower = (name || '').toLowerCase()
|
|
59
|
+
const ext = lower.split('.').pop() || ''
|
|
60
|
+
if (ext === 'woff2')
|
|
61
|
+
return 'woff2'
|
|
62
|
+
if (ext === 'woff')
|
|
63
|
+
return 'woff'
|
|
64
|
+
if (ext === 'otf')
|
|
65
|
+
return 'opentype'
|
|
66
|
+
if (ext === 'ttf')
|
|
67
|
+
return 'truetype'
|
|
68
|
+
if ((contentType || '').includes('woff2'))
|
|
69
|
+
return 'woff2'
|
|
70
|
+
if ((contentType || '').includes('woff'))
|
|
71
|
+
return 'woff'
|
|
72
|
+
if ((contentType || '').includes('otf'))
|
|
73
|
+
return 'opentype'
|
|
74
|
+
if ((contentType || '').includes('ttf'))
|
|
75
|
+
return 'truetype'
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const guessWeight = (name = '') => {
|
|
80
|
+
const lower = name.toLowerCase()
|
|
81
|
+
if (lower.includes('thin'))
|
|
82
|
+
return '100'
|
|
83
|
+
if (lower.includes('extralight') || lower.includes('extra-light'))
|
|
84
|
+
return '200'
|
|
85
|
+
if (lower.includes('light'))
|
|
86
|
+
return '300'
|
|
87
|
+
if (lower.includes('regular') || lower.includes('book') || lower.includes('normal'))
|
|
88
|
+
return '400'
|
|
89
|
+
if (lower.includes('medium'))
|
|
90
|
+
return '500'
|
|
91
|
+
if (lower.includes('semibold') || lower.includes('semi-bold') || lower.includes('demibold'))
|
|
92
|
+
return '600'
|
|
93
|
+
if (lower.includes('bold'))
|
|
94
|
+
return '700'
|
|
95
|
+
if (lower.includes('extrabold') || lower.includes('extra-bold'))
|
|
96
|
+
return '800'
|
|
97
|
+
if (lower.includes('black') || lower.includes('heavy'))
|
|
98
|
+
return '900'
|
|
99
|
+
return '400'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const guessStyle = (name = '') => {
|
|
103
|
+
return name.toLowerCase().includes('italic') ? 'italic' : 'normal'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const ensureMetaForFiles = () => {
|
|
107
|
+
const map = state.fileMeta || {}
|
|
108
|
+
for (const file of state.files) {
|
|
109
|
+
const key = fileKey(file)
|
|
110
|
+
if (!map[key]) {
|
|
111
|
+
map[key] = {
|
|
112
|
+
weight: guessWeight(file?.name || file?.file?.name),
|
|
113
|
+
style: guessStyle(file?.name || file?.file?.name),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// prune removed files
|
|
118
|
+
const validKeys = new Set(state.files.map(f => fileKey(f)))
|
|
119
|
+
for (const key of Object.keys(map)) {
|
|
120
|
+
if (!validKeys.has(key))
|
|
121
|
+
delete map[key]
|
|
122
|
+
}
|
|
123
|
+
state.fileMeta = { ...map }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
watch(() => state.files, () => ensureMetaForFiles(), { deep: true })
|
|
127
|
+
|
|
128
|
+
const waitForR2 = async (docId) => {
|
|
129
|
+
if (!docId || !collectionPath.value)
|
|
130
|
+
return null
|
|
131
|
+
|
|
132
|
+
const docKey = `${collectionPath.value}/${docId}`
|
|
133
|
+
await edgeFirebase.startDocumentSnapshot(collectionPath.value, docId)
|
|
134
|
+
|
|
135
|
+
return await new Promise((resolve) => {
|
|
136
|
+
const stop = () => edgeFirebase.stopSnapshot(docKey)
|
|
137
|
+
const unwatch = watch(() => edgeFirebase.data?.[docKey], (fileDoc) => {
|
|
138
|
+
if (fileDoc?.uploadCompletedToR2 && fileDoc?.r2URL) {
|
|
139
|
+
stop()
|
|
140
|
+
unwatch()
|
|
141
|
+
resolve(fileDoc)
|
|
142
|
+
}
|
|
143
|
+
}, { immediate: true, deep: true })
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const applyFontFaceToHead = (uploadedDocs, groupId) => {
|
|
148
|
+
if (!uploadedDocs?.length)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
const family = state.fontFamily.trim() || slugify(uploadedDocs[0]?.fileName || uploadedDocs[0]?.name || 'Custom Font')
|
|
152
|
+
|
|
153
|
+
const encodeUrl = (url = '') => {
|
|
154
|
+
try {
|
|
155
|
+
return new URL(url).toString()
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return encodeURI(url)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const styles = []
|
|
163
|
+
for (const doc of uploadedDocs) {
|
|
164
|
+
const key = doc?.fileName || doc?.name || doc?.docId
|
|
165
|
+
const meta = state.fileMeta[key] || { weight: '400', style: 'normal' }
|
|
166
|
+
const format = formatFromName(doc.fileName || doc.name, doc.contentType)
|
|
167
|
+
if (!doc.r2URL || !format)
|
|
168
|
+
continue
|
|
169
|
+
const encodedUrl = encodeUrl(doc.r2URL)
|
|
170
|
+
const css = `@font-face {\n font-family: "${family}";\n src: url("${encodedUrl}") format("${format}");\n font-weight: ${meta.weight || '400'};\n font-style: ${meta.style || 'normal'};\n font-display: ${state.fontDisplay || 'swap'};\n}`
|
|
171
|
+
styles.push({ css, id: `font-${slugify(family)}-${groupId}-${meta.weight}-${meta.style}` })
|
|
172
|
+
}
|
|
173
|
+
if (!styles.length)
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
const { ok, head } = parseHead()
|
|
177
|
+
if (!ok || !head)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
const headStyles = Array.isArray(head.style) ? [...head.style] : []
|
|
181
|
+
const filtered = headStyles.filter(s => !s?.id || !s.id.startsWith(`font-${slugify(family)}-${groupId}`))
|
|
182
|
+
styles.forEach((s) => {
|
|
183
|
+
filtered.push({ id: s.id, children: s.css, innerHTML: s.css })
|
|
184
|
+
})
|
|
185
|
+
head.style = filtered
|
|
186
|
+
setHeadJson(head)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const processFonts = async () => {
|
|
190
|
+
state.errors = []
|
|
191
|
+
if (!state.files.length) {
|
|
192
|
+
state.errors = ['Add at least one font file before processing.']
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
state.uploading = true
|
|
197
|
+
const groupId = slugify(state.fontFamily || `font-${Date.now()}`)
|
|
198
|
+
const uploadResults = []
|
|
199
|
+
|
|
200
|
+
for (const file of state.files) {
|
|
201
|
+
try {
|
|
202
|
+
const key = fileKey(file)
|
|
203
|
+
const result = await edgeFirebase.uploadFile(
|
|
204
|
+
edgeGlobal.edgeState.currentOrganization,
|
|
205
|
+
file.file,
|
|
206
|
+
'fonts',
|
|
207
|
+
false,
|
|
208
|
+
true,
|
|
209
|
+
{
|
|
210
|
+
themeId: props.themeId,
|
|
211
|
+
cmsFont: true,
|
|
212
|
+
fontGroupId: groupId,
|
|
213
|
+
autoLink: false,
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
if (result?.meta?.fileDocId) {
|
|
217
|
+
uploadResults.push({ docId: result.meta.fileDocId, meta: state.fileMeta[key] })
|
|
218
|
+
}
|
|
219
|
+
else
|
|
220
|
+
state.errors.push(`Upload failed for "${file.name || file.file?.name}"`)
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
state.errors.push(`Upload failed for "${file.name || file.file?.name}": ${e?.message || e}`)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const docs = []
|
|
228
|
+
for (const { docId, meta } of uploadResults) {
|
|
229
|
+
const doc = await waitForR2(docId)
|
|
230
|
+
if (doc) {
|
|
231
|
+
const key = doc.fileName || doc.name || doc.docId
|
|
232
|
+
state.fileMeta[key] = meta || state.fileMeta[key] || { weight: '400', style: 'normal' }
|
|
233
|
+
docs.push(doc)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
applyFontFaceToHead(docs, groupId)
|
|
238
|
+
|
|
239
|
+
state.files = []
|
|
240
|
+
state.uploading = false
|
|
241
|
+
}
|
|
242
|
+
</script>
|
|
243
|
+
|
|
244
|
+
<template>
|
|
245
|
+
<div class="w-full space-y-2">
|
|
246
|
+
<div class="grid grid-cols-2 gap-2">
|
|
247
|
+
<edge-shad-input
|
|
248
|
+
v-model="state.fontFamily"
|
|
249
|
+
label="Font family"
|
|
250
|
+
name="fontFamily"
|
|
251
|
+
placeholder="e.g. Acme Sans"
|
|
252
|
+
class="col-span-2"
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
<file-upload
|
|
256
|
+
v-model="state.files"
|
|
257
|
+
:accept="normalizedAccept"
|
|
258
|
+
name="fonts"
|
|
259
|
+
:multiple="true"
|
|
260
|
+
drop
|
|
261
|
+
class="w-full"
|
|
262
|
+
>
|
|
263
|
+
<div class="w-full border border-dashed border-border bg-muted/40 text-foreground py-2 px-3 rounded-md cursor-pointer hover:border-primary/70 transition-colors min-h-[48px] flex items-center justify-between text-sm font-semibold">
|
|
264
|
+
<span>Drag or click to select font files</span>
|
|
265
|
+
<span class="text-xs font-normal text-muted-foreground">.woff / .woff2 / .ttf / .otf</span>
|
|
266
|
+
</div>
|
|
267
|
+
</file-upload>
|
|
268
|
+
<div v-if="state.files.length" class="space-y-1 text-xs">
|
|
269
|
+
<div class="flex justify-between text-muted-foreground">
|
|
270
|
+
<span>Variant settings</span>
|
|
271
|
+
<span>({{ state.files.length }} file{{ state.files.length === 1 ? '' : 's' }})</span>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="space-y-1">
|
|
274
|
+
<div
|
|
275
|
+
v-for="file in state.files"
|
|
276
|
+
:key="fileKey(file)"
|
|
277
|
+
class="flex items-center gap-2 border border-border/60 rounded px-2 py-1"
|
|
278
|
+
>
|
|
279
|
+
<div class="flex-1 truncate text-foreground">
|
|
280
|
+
{{ file.name || file.file?.name }}
|
|
281
|
+
</div>
|
|
282
|
+
<edge-shad-input
|
|
283
|
+
v-model="state.fileMeta[fileKey(file)].weight"
|
|
284
|
+
name="weight"
|
|
285
|
+
placeholder="400"
|
|
286
|
+
class="w-20"
|
|
287
|
+
label="Weight"
|
|
288
|
+
/>
|
|
289
|
+
<edge-shad-select
|
|
290
|
+
v-model="state.fileMeta[fileKey(file)].style"
|
|
291
|
+
name="style"
|
|
292
|
+
:items="[{ title: 'Normal', name: 'normal' }, { title: 'Italic', name: 'italic' }]"
|
|
293
|
+
class="w-28"
|
|
294
|
+
label="Style"
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
|
300
|
+
<div>
|
|
301
|
+
{{ state.files.length }} file{{ state.files.length === 1 ? '' : 's' }} queued
|
|
302
|
+
</div>
|
|
303
|
+
<edge-shad-button
|
|
304
|
+
size="sm"
|
|
305
|
+
:disabled="state.uploading || state.files.length === 0"
|
|
306
|
+
@click="processFonts"
|
|
307
|
+
>
|
|
308
|
+
{{ state.uploading ? 'Processing…' : 'Process fonts' }}
|
|
309
|
+
</edge-shad-button>
|
|
310
|
+
</div>
|
|
311
|
+
<template v-if="state.errors.length">
|
|
312
|
+
<Alert
|
|
313
|
+
v-for="(error, idx) in state.errors"
|
|
314
|
+
:key="idx"
|
|
315
|
+
variant="destructive"
|
|
316
|
+
class="text-xs py-1"
|
|
317
|
+
>
|
|
318
|
+
<AlertTitle class="text-xs">
|
|
319
|
+
Upload error
|
|
320
|
+
</AlertTitle>
|
|
321
|
+
<AlertDescription class="text-xs">
|
|
322
|
+
{{ error }}
|
|
323
|
+
</AlertDescription>
|
|
324
|
+
</Alert>
|
|
325
|
+
</template>
|
|
326
|
+
</div>
|
|
327
|
+
</template>
|