@edgedev/create-edge-app 1.2.33 → 1.2.35

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 (45) hide show
  1. package/README.md +1 -0
  2. package/agents.md +95 -2
  3. package/deploy.sh +136 -0
  4. package/edge/components/cms/block.vue +977 -305
  5. package/edge/components/cms/blockApi.vue +3 -3
  6. package/edge/components/cms/blockEditor.vue +688 -86
  7. package/edge/components/cms/blockPicker.vue +31 -5
  8. package/edge/components/cms/blockRender.vue +3 -3
  9. package/edge/components/cms/blocksManager.vue +790 -82
  10. package/edge/components/cms/codeEditor.vue +15 -6
  11. package/edge/components/cms/fontUpload.vue +318 -2
  12. package/edge/components/cms/htmlContent.vue +825 -93
  13. package/edge/components/cms/init_blocks/contact_us.html +55 -47
  14. package/edge/components/cms/init_blocks/newsletter.html +56 -96
  15. package/edge/components/cms/menu.vue +96 -34
  16. package/edge/components/cms/page.vue +902 -58
  17. package/edge/components/cms/posts.vue +13 -4
  18. package/edge/components/cms/site.vue +638 -87
  19. package/edge/components/cms/siteSettingsForm.vue +19 -9
  20. package/edge/components/cms/sitesManager.vue +5 -4
  21. package/edge/components/cms/themeDefaultMenu.vue +20 -2
  22. package/edge/components/cms/themeEditor.vue +196 -162
  23. package/edge/components/editor.vue +5 -1
  24. package/edge/composables/global.ts +37 -5
  25. package/edge/composables/siteSettingsTemplate.js +2 -0
  26. package/edge/composables/useCmsNewDocs.js +100 -0
  27. package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
  28. package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
  29. package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
  30. package/edge/routes/cms/dashboard/media/index.vue +5 -0
  31. package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
  32. package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
  33. package/edge/routes/cms/dashboard/sites/index.vue +4 -0
  34. package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
  35. package/edge/routes/cms/dashboard/templates/index.vue +4 -0
  36. package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
  37. package/edge/routes/cms/dashboard/themes/index.vue +330 -1
  38. package/edge-pull.sh +16 -2
  39. package/edge-push.sh +9 -1
  40. package/edge-remote.sh +20 -0
  41. package/edge-status.sh +9 -5
  42. package/edge-update-all.sh +127 -0
  43. package/firebase.json +4 -0
  44. package/nuxt.config.ts +1 -1
  45. package/package.json +2 -2
@@ -480,13 +480,17 @@ watch(() => props.settings?.forwardApex, (value) => {
480
480
  </div>
481
481
  <div class="space-y-2 text-sm">
482
482
  <div class="grid grid-cols-[70px_1fr] gap-3">
483
- <div class="text-muted-foreground">CNAME</div>
483
+ <div class="text-muted-foreground">
484
+ CNAME
485
+ </div>
484
486
  <div class="font-mono">
485
487
  {{ entry?.dnsRecords?.www?.name || 'www' }} → {{ entry?.dnsRecords?.www?.value || pagesDomain }}
486
488
  </div>
487
489
  </div>
488
490
  <div class="grid grid-cols-[70px_1fr] gap-3">
489
- <div class="text-muted-foreground">CNAME</div>
491
+ <div class="text-muted-foreground">
492
+ CNAME
493
+ </div>
490
494
  <div class="font-mono">
491
495
  {{ entry?.dnsRecords?.apex?.name || '@' }} → {{ entry?.dnsRecords?.apex?.value || pagesDomain }}
492
496
  </div>
@@ -532,7 +536,7 @@ watch(() => props.settings?.forwardApex, (value) => {
532
536
  item-value="value"
533
537
  @update:model-value="value => (props.settings.theme = value || '')"
534
538
  />
535
- <edge-shad-select
539
+ <!-- <edge-shad-select
536
540
  :model-value="props.settings.menuPosition || ''"
537
541
  name="menuPosition"
538
542
  label="Menu Position"
@@ -542,7 +546,7 @@ watch(() => props.settings?.forwardApex, (value) => {
542
546
  item-title="label"
543
547
  item-value="value"
544
548
  @update:model-value="value => (props.settings.menuPosition = value || '')"
545
- />
549
+ /> -->
546
550
  </TabsContent>
547
551
  <TabsContent value="branding" class="pt-4 space-y-4">
548
552
  <div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
@@ -562,7 +566,7 @@ watch(() => props.settings?.forwardApex, (value) => {
562
566
  <img
563
567
  :src="props.settings.logo"
564
568
  alt="Logo preview"
565
- :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logo)]"
569
+ class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.logo)]"
566
570
  >
567
571
  <edge-shad-button
568
572
  type="button"
@@ -612,7 +616,7 @@ watch(() => props.settings?.forwardApex, (value) => {
612
616
  <img
613
617
  :src="props.settings.logoLight"
614
618
  alt="Light logo preview"
615
- :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logoLight)]"
619
+ class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.logoLight)]"
616
620
  >
617
621
  <edge-shad-button
618
622
  type="button"
@@ -666,7 +670,7 @@ watch(() => props.settings?.forwardApex, (value) => {
666
670
  <img
667
671
  :src="props.settings.brandLogoDark"
668
672
  alt="Brand dark logo preview"
669
- :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoDark)]"
673
+ class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.brandLogoDark)]"
670
674
  >
671
675
  <edge-shad-button
672
676
  type="button"
@@ -716,7 +720,7 @@ watch(() => props.settings?.forwardApex, (value) => {
716
720
  <img
717
721
  :src="props.settings.brandLogoLight"
718
722
  alt="Brand light logo preview"
719
- :class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoLight)]"
723
+ class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.brandLogoLight)]"
720
724
  >
721
725
  <edge-shad-button
722
726
  type="button"
@@ -767,7 +771,7 @@ watch(() => props.settings?.forwardApex, (value) => {
767
771
  <img
768
772
  :src="props.settings.favicon"
769
773
  alt="Favicon preview"
770
- :class="['max-h-12 max-w-12 h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.favicon)]"
774
+ class="max-h-12 max-w-12 h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.favicon)]"
771
775
  >
772
776
  <edge-shad-button
773
777
  type="button"
@@ -856,6 +860,12 @@ watch(() => props.settings?.forwardApex, (value) => {
856
860
  name="trackingAdroll"
857
861
  placeholder="ADROLL-ID"
858
862
  />
863
+ <edge-shad-input
864
+ v-model="props.settings.sureFeedURL"
865
+ label="Sure Feedback"
866
+ name="sureFeedURL"
867
+ placeholder=""
868
+ />
859
869
  </div>
860
870
  </TabsContent>
861
871
  <TabsContent value="social" class="pt-4">
@@ -7,6 +7,7 @@ const isAiBusy = status => status === 'queued' || status === 'running'
7
7
  const state = reactive({
8
8
  filter: '',
9
9
  })
10
+ const cmsMultiOrg = useState('cmsMultiOrg', () => false)
10
11
 
11
12
  const isAdmin = computed(() => {
12
13
  return edgeGlobal.isAdminGlobal(edgeFirebase).value
@@ -25,21 +26,21 @@ const canAddSite = computed(() => {
25
26
  })
26
27
 
27
28
  const queryField = computed(() => {
28
- if (!isAdmin.value) {
29
+ if (!isAdmin.value && !cmsMultiOrg.value) {
29
30
  return 'users'
30
31
  }
31
32
  return ''
32
33
  })
33
34
 
34
35
  const queryValue = computed(() => {
35
- if (!isAdmin.value) {
36
+ if (!isAdmin.value && !cmsMultiOrg.value) {
36
37
  return [edgeFirebase?.user?.uid]
37
38
  }
38
39
  return ''
39
40
  })
40
41
 
41
42
  const queryOperator = computed(() => {
42
- if (!isAdmin.value) {
43
+ if (!isAdmin.value && !cmsMultiOrg.value) {
43
44
  return 'array-contains-any'
44
45
  }
45
46
  return ''
@@ -100,7 +101,7 @@ const queryOperator = computed(() => {
100
101
  <Separator class="dark:bg-slate-600" />
101
102
  </template>
102
103
  <div
103
- v-if="slotProps.filtered.length === 0 && !isAdmin && disableAddSiteForNonAdmin"
104
+ v-if="slotProps.filtered.length === 0 && !isAdmin && disableAddSiteForNonAdmin && !cmsMultiOrg"
104
105
  class="px-4 py-6 text-sm text-muted-foreground"
105
106
  >
106
107
  No sites are assigned to your account. Contact an organization admin to add a site for you.
@@ -118,6 +118,17 @@ const getFolderName = (entry) => {
118
118
  return ''
119
119
  return Object.keys(entry.item || {})[0] || ''
120
120
  }
121
+ const getFolderTitle = (entry) => {
122
+ if (!isFolder(entry))
123
+ return ''
124
+ const title = String(entry?.menuTitle || entry?.folderTitle || '').trim()
125
+ if (title)
126
+ return title
127
+ const folderName = getFolderName(entry)
128
+ if (!folderName)
129
+ return ''
130
+ return folderName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
131
+ }
121
132
 
122
133
  const getFolderList = (menuName, folderName) => {
123
134
  const targetMenu = modelValue.value[menuName] || []
@@ -228,6 +239,7 @@ const submitFolderDialog = () => {
228
239
  return
229
240
  const slug = uniqueFolderSlug(value, state.folderDialog.menu)
230
241
  modelValue.value[state.folderDialog.menu].push({
242
+ menuTitle: value,
231
243
  item: { [slug]: [] },
232
244
  })
233
245
  state.folderDialog.open = false
@@ -262,13 +274,16 @@ const openRenameDialogForPage = (menuName, index, folderName = null) => {
262
274
  }
263
275
 
264
276
  const openRenameDialogForFolder = (menuName, folderName, index) => {
277
+ const folderList = modelValue.value[menuName] || []
278
+ const target = folderList[index]
279
+ const currentTitle = String(target?.menuTitle || target?.folderTitle || '').trim()
265
280
  state.renameDialog = {
266
281
  open: true,
267
282
  type: 'folder',
268
283
  menu: menuName,
269
284
  folder: folderName,
270
285
  index,
271
- value: folderName,
286
+ value: currentTitle || folderName,
272
287
  }
273
288
  }
274
289
 
@@ -347,6 +362,9 @@ const submitRenameDialog = () => {
347
362
  return
348
363
  const currentName = getFolderName(target)
349
364
  const slug = uniqueFolderSlug(value || currentName, state.renameDialog.menu, currentName)
365
+ target.menuTitle = value || target.menuTitle || target.folderTitle || currentName
366
+ if (Object.prototype.hasOwnProperty.call(target, 'folderTitle'))
367
+ delete target.folderTitle
350
368
  if (slug !== currentName) {
351
369
  target.item[slug] = target.item[currentName]
352
370
  delete target.item[currentName]
@@ -438,7 +456,7 @@ const hasEntries = computed(() => {
438
456
  <div>
439
457
  <div class="text-sm font-semibold flex items-center gap-1">
440
458
  <Folder class="w-4 h-4" />
441
- {{ getFolderName(element) }}
459
+ {{ getFolderTitle(element) }}
442
460
  </div>
443
461
  <div class="text-[11px] text-muted-foreground">
444
462
  Folder
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { toTypedSchema } from '@vee-validate/zod'
3
- import { FolderCog } from 'lucide-vue-next'
3
+ import { Download, FolderCog } from 'lucide-vue-next'
4
4
  import * as z from 'zod'
5
5
  const props = defineProps({
6
6
  themeId: {
@@ -11,98 +11,21 @@ const props = defineProps({
11
11
 
12
12
  const emit = defineEmits(['head'])
13
13
  const edgeFirebase = inject('edgeFirebase')
14
+ const { themes: themeNewDocSchema } = useCmsNewDocs()
14
15
  const { createDefaults: createSiteSettingsDefaults } = useSiteSettingsTemplate()
15
16
  const state = reactive({
16
17
  filter: '',
17
18
  workingDoc: {},
18
19
  newDocs: {
19
- themes: {
20
- name: { value: '' },
21
- headJSON: {
22
- value: `{
23
- "link": [
24
- {
25
- "rel": "preconnect",
26
- "href": "https://fonts.googleapis.com"
27
- },
28
- {
29
- "rel": "preconnect",
30
- "href": "https://fonts.gstatic.com",
31
- "crossorigin": ""
32
- },
33
- {
34
- "rel": "stylesheet",
35
- "href": "https://fonts.googleapis.com/css2?family=Overpass:wght@400;700&family=Kode+Mono:wght@400;700&display=swap"
36
- }
37
- ]
38
- }`,
39
- },
40
- theme: {
41
- value: `{
42
- "extend": {
43
- "colors": {
44
- "brand": "#3B82F6",
45
- "accent": "#F59E0B",
46
- "surface": "#FAFAFA",
47
- "subtle": "#F3F4F6",
48
- "text": "#1F2937",
49
- "muted": "#9CA3AF",
50
- "success": "#22C55E",
51
- "danger": "#EF4444",
52
- "border": "#E5E7EB",
53
- "ring": "#93C5FD",
54
- "link": "#3B82F6",
55
- "linkHover": "#1D4ED8",
56
- "navBg": "#000000",
57
- "navText": "#FFFFFF",
58
- "navMuted": "#6B7280",
59
- "navBorder": "",
60
- "navActive": "#3B82F6",
61
- "navHoverBg": "",
62
- "navActiveBg": "",
63
- "sideNavBg": "#FFFFFF",
64
- "sideNavText": "#000000",
65
- "sideNavActive": "#AFBD23"
66
- },
67
- "fontFamily": {
68
- "sans": ["Overpass", "sans-serif"],
69
- "serif": ["Kode Mono", "monospace"],
70
- "mono": ["Overpass", "sans-serif"],
71
- "brand": ["Kode Mono", "monospace"]
72
- }
73
- },
74
- "apply": {},
75
- "slots": {},
76
- "variants": {
77
- "light": {
78
- "apply": {}
79
- },
80
- "dark": {
81
- "apply": {},
82
- "slots": {}
83
- }
84
- }
85
- }`,
86
- },
87
- extraCSS: {
88
- value: '',
89
- },
90
- version: 1,
91
- defaultPages: { value: [] },
92
- defaultMenus: {
93
- value: {
94
- 'Site Root': [],
95
- 'Not In Menu': [],
96
- },
97
- },
98
- defaultSiteSettings: { value: createSiteSettingsDefaults() },
99
- },
20
+ themes: themeNewDocSchema.value,
100
21
  },
101
22
  mounted: false,
102
23
  loading: false,
103
24
  defaultSettingsOpen: false,
104
25
  })
105
26
 
27
+ const editorViewportHeight = 'calc(100vh - 420px)'
28
+
106
29
  const blockSchema = toTypedSchema(z.object({
107
30
  name: z.string({
108
31
  required_error: 'Name is required',
@@ -126,6 +49,16 @@ const parseJsonSafe = (value, fallback) => {
126
49
  }
127
50
 
128
51
  const headObject = computed(() => lastValidHead.value)
52
+ const previewTheme = computed(() => {
53
+ const isObjectTheme = !!lastValidTheme.value
54
+ && typeof lastValidTheme.value === 'object'
55
+ && !Array.isArray(lastValidTheme.value)
56
+ const baseTheme = isObjectTheme ? lastValidTheme.value : {}
57
+ return {
58
+ ...baseTheme,
59
+ extraCSS: typeof state.workingDoc?.extraCSS === 'string' ? state.workingDoc.extraCSS : '',
60
+ }
61
+ })
129
62
 
130
63
  watch(headObject, (newHeadElements) => {
131
64
  emit('head', newHeadElements)
@@ -325,6 +258,62 @@ const templatePageOptions = computed(() => {
325
258
  .sort((a, b) => a.label.localeCompare(b.label))
326
259
  })
327
260
 
261
+ const themesCollectionPath = computed(() => `${edgeGlobal.edgeState.organizationDocPath}/themes`)
262
+ const themesCollection = computed(() => edgeFirebase.data?.[themesCollectionPath.value] || {})
263
+
264
+ const downloadJsonFile = (payload, filename) => {
265
+ if (typeof window === 'undefined')
266
+ return
267
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
268
+ const objectUrl = URL.createObjectURL(blob)
269
+ const anchor = document.createElement('a')
270
+ anchor.href = objectUrl
271
+ anchor.download = filename
272
+ document.body.appendChild(anchor)
273
+ anchor.click()
274
+ anchor.remove()
275
+ URL.revokeObjectURL(objectUrl)
276
+ }
277
+
278
+ const isPlainObject = value => !!value && typeof value === 'object' && !Array.isArray(value)
279
+
280
+ const cloneSchemaValue = (value) => {
281
+ if (isPlainObject(value) || Array.isArray(value))
282
+ return edgeGlobal.dupObject(value)
283
+ return value
284
+ }
285
+
286
+ const getDocDefaultsFromSchema = (schema = {}) => {
287
+ const defaults = {}
288
+ for (const [key, schemaEntry] of Object.entries(schema || {})) {
289
+ const hasValueProp = isPlainObject(schemaEntry) && Object.prototype.hasOwnProperty.call(schemaEntry, 'value')
290
+ const baseValue = hasValueProp ? schemaEntry.value : schemaEntry
291
+ defaults[key] = cloneSchemaValue(baseValue)
292
+ }
293
+ return defaults
294
+ }
295
+
296
+ const getThemeDocDefaults = () => getDocDefaultsFromSchema(themeNewDocSchema.value || {})
297
+
298
+ const notifySuccess = (message) => {
299
+ edgeFirebase?.toast?.success?.(message)
300
+ }
301
+
302
+ const notifyError = (message) => {
303
+ edgeFirebase?.toast?.error?.(message)
304
+ }
305
+
306
+ const exportCurrentTheme = () => {
307
+ const doc = themesCollection.value?.[props.themeId]
308
+ if (!doc || !doc.docId) {
309
+ notifyError('Save this theme before exporting.')
310
+ return
311
+ }
312
+ const exportPayload = { ...getThemeDocDefaults(), ...doc }
313
+ downloadJsonFile(exportPayload, `theme-${doc.docId}.json`)
314
+ notifySuccess(`Exported theme "${doc.docId}".`)
315
+ }
316
+
328
317
  watch (sites, async (newSites) => {
329
318
  state.loading = true
330
319
  const selectedSite = String(edgeGlobal.edgeState.blockEditorSite || '').trim()
@@ -380,7 +369,9 @@ onBeforeMount(async () => {
380
369
  :doc-id="props.themeId"
381
370
  :schema="blockSchema"
382
371
  :new-doc-schema="state.newDocs.themes"
383
- class="w-full mx-auto flex-1 bg-transparent flex flex-col border-none shadow-none"
372
+ header-class="py-2 bg-secondary text-foreground rounded-none sticky top-0 border"
373
+ class="w-full mx-auto flex-1 bg-transparent flex flex-col border-none shadow-none pt-0 px-0"
374
+ card-content-class="px-0"
384
375
  :show-footer="false"
385
376
  :no-close-after-save="true"
386
377
  :working-doc-overrides="state.workingDoc"
@@ -391,99 +382,142 @@ onBeforeMount(async () => {
391
382
  {{ slotProps.title }}
392
383
  </template>
393
384
  <template #header-center>
394
- <div class="w-full flex gap-1 px-4">
385
+ <div class="w-full flex gap-2 px-4 items-center">
395
386
  <div class="w-full">
396
387
  <edge-shad-select
397
388
  v-if="!state.loading"
398
389
  v-model="edgeGlobal.edgeState.blockEditorSite"
399
- label="Preview Site"
400
390
  name="site"
401
391
  :items="sites.map(s => ({ title: s.name, name: s.docId }))"
402
392
  placeholder="Select Site"
403
393
  class="w-full"
404
394
  />
405
395
  </div>
396
+ <div class="flex items-center gap-2">
397
+ <edge-shad-button
398
+ type="button"
399
+ size="icon"
400
+ variant="outline"
401
+ class="h-9 w-9"
402
+ :disabled="props.themeId === 'new' || !themesCollection?.[props.themeId]"
403
+ title="Export Theme"
404
+ aria-label="Export Theme"
405
+ @click="exportCurrentTheme"
406
+ >
407
+ <Download class="h-4 w-4" />
408
+ </edge-shad-button>
409
+ </div>
406
410
  </div>
407
411
  </template>
408
412
  <template #main="slotProps">
409
- <div class="pt-4 flex flex-col gap-6 lg:flex-row">
410
- <div class="lg:w-72 lg:max-w-xs w-full space-y-4">
411
- <Card class="h-full">
412
- <CardHeader class="pb-2">
413
- <div class="flex items-center justify-between gap-2">
414
- <CardTitle class="text-base">
415
- Default Template Pages
416
- </CardTitle>
417
- <edge-shad-button
418
- size="icon"
419
- type="text"
420
- @click="state.defaultSettingsOpen = true"
421
- >
422
- <FolderCog class="h-4 w-4" />
423
- </edge-shad-button>
424
- </div>
425
- <CardDescription class="text-xs">
426
- Choose which template pages are created for new sites and organize them into Site Menu or Not In Menu.
427
- </CardDescription>
428
- </CardHeader>
429
- <CardContent>
430
- <edge-cms-theme-default-menu
431
- v-if="slotProps.workingDoc"
432
- v-model="slotProps.workingDoc.defaultMenus"
433
- :template-options="templatePageOptions"
434
- :template-pages="templatePages"
435
- />
436
- </CardContent>
437
- </Card>
438
- </div>
439
- <div class="flex-1 space-y-4">
440
- <edge-shad-input
441
- v-model="slotProps.workingDoc.name"
442
- label="Theme Name"
443
- name="name"
444
- />
445
- <div class="flex flex-col gap-4 xl:flex-row">
446
- <div class="w-1/2">
447
- <edge-cms-code-editor
448
- v-model="slotProps.workingDoc.theme"
449
- title="Theme JSON"
450
- language="json"
451
- name="content"
452
- height="400px"
453
- class="mb-4 w-full"
454
- />
455
- <edge-cms-font-upload
456
- v-if="slotProps.workingDoc"
457
- v-model:head-json="slotProps.workingDoc.headJSON"
458
- :theme-id="props.themeId"
459
- class="mb-4"
460
- />
461
- <edge-cms-code-editor
462
- v-model="slotProps.workingDoc.headJSON"
463
- title="Head JSON"
464
- language="json"
465
- name="headJSON"
466
- height="400px"
467
- class="mb-4 w-full"
468
- />
469
- <edge-cms-code-editor
470
- v-model="slotProps.workingDoc.extraCSS"
471
- title="Extra CSS"
472
- language="css"
473
- name="extraCSS"
474
- height="300px"
475
- class="mb-4 w-full"
476
- />
477
- </div>
478
- <div class="w-1/2">
479
- <div class="w-full mx-auto bg-white drop-shadow-[4px_4px_6px_rgba(0,0,0,0.5)] shadow-lg shadow-black/30">
480
- <edge-cms-block-picker
481
- :site-id="edgeGlobal.edgeState.blockEditorSite"
482
- class="!h-[calc(100vh-220px)] overflow-y-auto"
483
- list-only
484
- :theme="lastValidTheme"
413
+ <div class="pt-4 space-y-4">
414
+ <edge-shad-input
415
+ v-model="slotProps.workingDoc.name"
416
+ label="Theme Name"
417
+ name="name"
418
+ />
419
+ <div class="flex flex-col gap-4 xl:flex-row">
420
+ <div class="w-full xl:w-1/2">
421
+ <Tabs class="w-full" default-value="theme-json">
422
+ <TabsList class="w-full mt-3 bg-secondary rounded-sm grid grid-cols-2 xl:grid-cols-5">
423
+ <TabsTrigger value="theme-json" class="w-full text-black data-[state=active]:bg-black data-[state=active]:text-white">
424
+ Theme JSON
425
+ </TabsTrigger>
426
+ <TabsTrigger value="head-json" class="w-full text-black data-[state=active]:bg-black data-[state=active]:text-white">
427
+ Head JSON
428
+ </TabsTrigger>
429
+ <TabsTrigger value="custom-fonts" class="w-full text-black data-[state=active]:bg-black data-[state=active]:text-white">
430
+ Custom Fonts
431
+ </TabsTrigger>
432
+ <TabsTrigger value="extra-css" class="w-full text-black data-[state=active]:bg-black data-[state=active]:text-white">
433
+ Extra CSS
434
+ </TabsTrigger>
435
+ <TabsTrigger value="default-templates" class="w-full text-black data-[state=active]:bg-black data-[state=active]:text-white">
436
+ Default Templates
437
+ </TabsTrigger>
438
+ </TabsList>
439
+
440
+ <TabsContent value="theme-json" class="mt-4">
441
+ <edge-cms-code-editor
442
+ v-model="slotProps.workingDoc.theme"
443
+ title="Theme JSON"
444
+ language="json"
445
+ name="content"
446
+ :height="editorViewportHeight"
447
+ class="w-full"
448
+ />
449
+ </TabsContent>
450
+
451
+ <TabsContent value="head-json" class="mt-4">
452
+ <edge-cms-code-editor
453
+ v-model="slotProps.workingDoc.headJSON"
454
+ title="Head JSON"
455
+ language="json"
456
+ name="headJSON"
457
+ :height="editorViewportHeight"
458
+ class="w-full"
459
+ />
460
+ </TabsContent>
461
+
462
+ <TabsContent value="custom-fonts" class="mt-4">
463
+ <edge-cms-font-upload
464
+ v-if="slotProps.workingDoc"
465
+ v-model:head-json="slotProps.workingDoc.headJSON"
466
+ :theme-id="props.themeId"
467
+ class="w-full"
485
468
  />
486
- </div>
469
+ </TabsContent>
470
+
471
+ <TabsContent value="extra-css" class="mt-4">
472
+ <edge-cms-code-editor
473
+ v-model="slotProps.workingDoc.extraCSS"
474
+ title="Extra CSS"
475
+ language="css"
476
+ name="extraCSS"
477
+ :height="editorViewportHeight"
478
+ class="w-full"
479
+ />
480
+ </TabsContent>
481
+
482
+ <TabsContent value="default-templates" class="mt-4">
483
+ <Card class="h-full">
484
+ <CardHeader class="pb-2">
485
+ <div class="flex items-center justify-between gap-2">
486
+ <CardTitle class="text-base">
487
+ Default Templates
488
+ </CardTitle>
489
+ <edge-shad-button
490
+ size="icon"
491
+ type="text"
492
+ @click="state.defaultSettingsOpen = true"
493
+ >
494
+ <FolderCog class="h-4 w-4" />
495
+ </edge-shad-button>
496
+ </div>
497
+ <CardDescription class="text-xs">
498
+ Choose which template pages are created for new sites and organize them into Site Menu or Not In Menu.
499
+ </CardDescription>
500
+ </CardHeader>
501
+ <CardContent>
502
+ <edge-cms-theme-default-menu
503
+ v-if="slotProps.workingDoc"
504
+ v-model="slotProps.workingDoc.defaultMenus"
505
+ :template-options="templatePageOptions"
506
+ :template-pages="templatePages"
507
+ />
508
+ </CardContent>
509
+ </Card>
510
+ </TabsContent>
511
+ </Tabs>
512
+ </div>
513
+ <div class="w-full xl:w-1/2">
514
+ <div class="w-full mx-auto bg-white drop-shadow-[4px_4px_6px_rgba(0,0,0,0.5)] shadow-lg shadow-black/30">
515
+ <edge-cms-block-picker
516
+ :site-id="edgeGlobal.edgeState.blockEditorSite"
517
+ class="!h-[calc(100vh-220px)] overflow-y-auto"
518
+ list-only
519
+ :theme="previewTheme"
520
+ />
487
521
  </div>
488
522
  </div>
489
523
  </div>
@@ -27,6 +27,10 @@ const props = defineProps({
27
27
  type: Boolean,
28
28
  default: true,
29
29
  },
30
+ headerClass: {
31
+ type: String,
32
+ default: 'py-4 bg-secondary text-foreground rounded-none sticky top-0',
33
+ },
30
34
  class: {
31
35
  type: String,
32
36
  default: '',
@@ -583,7 +587,7 @@ const onError = async () => {
583
587
  @error="onError"
584
588
  >
585
589
  <slot name="header" :on-submit="triggerSubmit" :on-cancel="onCancel" :submitting="state.submitting" :unsaved-changes="unsavedChanges" :title="title" :working-doc="state.workingDoc" :errors="state.errors">
586
- <edge-menu v-if="props.showHeader" class="py-4 bg-secondary text-foreground rounded-none sticky top-0">
590
+ <edge-menu v-if="props.showHeader" :class="props.headerClass">
587
591
  <template #start>
588
592
  <slot name="header-start" :unsaved-changes="unsavedChanges" :title="title" :errors="state.errors" :working-doc="state.workingDoc">
589
593
  <FilePenLine class="mr-2" />