@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.
Files changed (111) hide show
  1. package/README.md +55 -20
  2. package/{agent.md → agents.md} +2 -0
  3. package/bin/cli.js +6 -6
  4. package/edge/components/auth/login.vue +384 -0
  5. package/edge/components/auth/register.vue +396 -0
  6. package/edge/components/auth.vue +108 -0
  7. package/edge/components/autoFileUpload.vue +215 -0
  8. package/edge/components/billing.vue +8 -0
  9. package/edge/components/buttonDivider.vue +14 -0
  10. package/edge/components/chip.vue +34 -0
  11. package/edge/components/clipboardButton.vue +42 -0
  12. package/edge/components/cms/block.vue +529 -0
  13. package/edge/components/cms/blockApi.vue +212 -0
  14. package/edge/components/cms/blockEditor.vue +725 -0
  15. package/edge/components/cms/blockInput.vue +66 -0
  16. package/edge/components/cms/blockPicker.vue +486 -0
  17. package/edge/components/cms/blockRender.vue +78 -0
  18. package/edge/components/cms/blockSheetContent.vue +28 -0
  19. package/edge/components/cms/codeEditor.vue +466 -0
  20. package/edge/components/cms/fontUpload.vue +327 -0
  21. package/edge/components/cms/htmlContent.vue +807 -0
  22. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  23. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  24. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  25. package/edge/components/cms/init_blocks/carousel.html +103 -0
  26. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  27. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  28. package/edge/components/cms/init_blocks/footer.html +24 -0
  29. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  30. package/edge/components/cms/init_blocks/hero.html +35 -0
  31. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  32. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  33. package/edge/components/cms/init_blocks/post_content.html +7 -0
  34. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  35. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  36. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  37. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  38. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  39. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  40. package/edge/components/cms/init_blocks/property_results.html +137 -0
  41. package/edge/components/cms/init_blocks/property_search.html +75 -0
  42. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  43. package/edge/components/cms/mediaCard.vue +116 -0
  44. package/edge/components/cms/mediaManager.vue +386 -0
  45. package/edge/components/cms/menu.vue +1103 -0
  46. package/edge/components/cms/optionsSelect.vue +107 -0
  47. package/edge/components/cms/page.vue +1785 -0
  48. package/edge/components/cms/posts.vue +1083 -0
  49. package/edge/components/cms/site.vue +1298 -0
  50. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  51. package/edge/components/cms/themeEditor.vue +426 -0
  52. package/edge/components/dashboard.vue +776 -0
  53. package/edge/components/editor.vue +671 -0
  54. package/edge/components/fileTree.vue +72 -0
  55. package/edge/components/files.vue +89 -0
  56. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  57. package/edge/components/formSubtypes/users.vue +336 -0
  58. package/edge/components/functionChips.vue +57 -0
  59. package/edge/components/gError.vue +98 -0
  60. package/edge/components/gHelper.vue +67 -0
  61. package/edge/components/gInput.vue +1331 -0
  62. package/edge/components/loggingIn.vue +41 -0
  63. package/edge/components/menu.vue +137 -0
  64. package/edge/components/menuContent.vue +132 -0
  65. package/edge/components/myAccount.vue +317 -0
  66. package/edge/components/myOrganizations.vue +75 -0
  67. package/edge/components/myProfile.vue +122 -0
  68. package/edge/components/orgSwitcher.vue +25 -0
  69. package/edge/components/organizationMembers.vue +522 -0
  70. package/edge/components/organizationSettings.vue +271 -0
  71. package/edge/components/shad/breadcrumbs.vue +35 -0
  72. package/edge/components/shad/button.vue +43 -0
  73. package/edge/components/shad/checkbox.vue +73 -0
  74. package/edge/components/shad/combobox.vue +238 -0
  75. package/edge/components/shad/datepicker.vue +184 -0
  76. package/edge/components/shad/dialog.vue +32 -0
  77. package/edge/components/shad/dropdownMenu.vue +54 -0
  78. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  79. package/edge/components/shad/form.vue +59 -0
  80. package/edge/components/shad/html.vue +877 -0
  81. package/edge/components/shad/input.vue +139 -0
  82. package/edge/components/shad/number.vue +109 -0
  83. package/edge/components/shad/select.vue +151 -0
  84. package/edge/components/shad/selectTags.vue +278 -0
  85. package/edge/components/shad/switch.vue +67 -0
  86. package/edge/components/shad/tags.vue +137 -0
  87. package/edge/components/shad/textarea.vue +102 -0
  88. package/edge/components/shad/typeMoney.vue +167 -0
  89. package/edge/components/sideBar.vue +288 -0
  90. package/edge/components/sideBarContent.vue +268 -0
  91. package/edge/components/sidebarProvider.vue +33 -0
  92. package/edge/components/tooltip.vue +16 -0
  93. package/edge/components/userMenu.vue +148 -0
  94. package/edge/components/v/alert.vue +59 -0
  95. package/edge/components/v/alertTitle.vue +18 -0
  96. package/edge/components/v/card.vue +53 -0
  97. package/edge/components/v/cardActions.vue +18 -0
  98. package/edge/components/v/cardText.vue +18 -0
  99. package/edge/components/v/cardTitle.vue +20 -0
  100. package/edge/components/v/col.vue +56 -0
  101. package/edge/components/v/list.vue +46 -0
  102. package/edge/components/v/listItem.vue +26 -0
  103. package/edge/components/v/listItemTitle.vue +18 -0
  104. package/edge/components/v/row.vue +42 -0
  105. package/edge/components/v/toolbar.vue +24 -0
  106. package/edge/composables/global.ts +519 -0
  107. package/edge-pull.sh +2 -0
  108. package/edge-push.sh +1 -0
  109. package/edge-status.sh +14 -0
  110. package/package.json +1 -1
  111. 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>