@edgedev/create-edge-app 1.1.26 → 1.1.28

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.
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
- import { computed, reactive, watchEffect } from 'vue'
2
+ import { computed, reactive, watch, watchEffect } from 'vue'
3
3
  import { useVModel } from '@vueuse/core'
4
- import { File as FileIcon, FileMinus2, FilePen, Folder, FolderMinus, FolderPen, FolderPlus, GripVertical, Plus } from 'lucide-vue-next'
4
+ import { FileCog, File as FileIcon, FileMinus2, FilePen, Folder, FolderMinus, FolderPen, FolderPlus, GripVertical, Link, Plus } from 'lucide-vue-next'
5
5
 
6
6
  const props = defineProps({
7
7
  modelValue: {
@@ -31,6 +31,15 @@ const state = reactive({
31
31
  menu: '',
32
32
  value: '',
33
33
  },
34
+ linkDialog: {
35
+ open: false,
36
+ mode: 'add',
37
+ menu: '',
38
+ folder: '',
39
+ index: -1,
40
+ name: '',
41
+ url: '',
42
+ },
34
43
  renameDialog: {
35
44
  open: false,
36
45
  type: 'page',
@@ -84,7 +93,25 @@ const uniqueSlug = (value, siblings = [], current = '', options = {}) => {
84
93
  return candidate
85
94
  }
86
95
 
87
- const isFolder = entry => entry && typeof entry.item === 'object'
96
+ const uniqueDisplayName = (value, siblings = [], current = '') => {
97
+ const base = String(value || '').trim() || 'Link'
98
+ const siblingSet = new Set(siblings.filter(Boolean))
99
+ if (current)
100
+ siblingSet.delete(current)
101
+ if (!siblingSet.has(base))
102
+ return base
103
+ let suffix = 2
104
+ let candidate = `${base} (${suffix})`
105
+ while (siblingSet.has(candidate)) {
106
+ suffix += 1
107
+ candidate = `${base} (${suffix})`
108
+ }
109
+ return candidate
110
+ }
111
+
112
+ const isExternalLinkEntry = entry => entry?.item && typeof entry.item === 'object' && entry.item.type === 'external'
113
+ const isFolder = entry => entry && typeof entry.item === 'object' && !isExternalLinkEntry(entry)
114
+ const isPageEntry = entry => typeof entry?.item === 'string'
88
115
 
89
116
  const getFolderName = (entry) => {
90
117
  if (!isFolder(entry))
@@ -112,7 +139,13 @@ const resolveTemplateTitle = (pageId) => {
112
139
 
113
140
  const siblingSlugs = (list = [], excludeIndex = -1) => {
114
141
  return list
115
- .map((entry, idx) => (idx === excludeIndex || typeof entry?.item !== 'string') ? null : entry.name)
142
+ .map((entry, idx) => (idx === excludeIndex || !entry?.name) ? null : entry.name)
143
+ .filter(Boolean)
144
+ }
145
+
146
+ const siblingNames = (list = [], excludeIndex = -1) => {
147
+ return list
148
+ .map((entry, idx) => (idx === excludeIndex ? null : entry?.name))
116
149
  .filter(Boolean)
117
150
  }
118
151
 
@@ -124,6 +157,8 @@ const addPageToList = (list, pageId, nameHint) => {
124
157
  list.push({
125
158
  name: slug,
126
159
  item: pageId,
160
+ disableRename: false,
161
+ disableDelete: false,
127
162
  })
128
163
  }
129
164
 
@@ -136,6 +171,21 @@ const addPageToMenu = (menuName, pageId, folderName = null) => {
136
171
  addPageToList(targetList, pageId, resolveTemplateTitle(pageId))
137
172
  }
138
173
 
174
+ const addLinkToMenu = (menuName, label, url, folderName = null) => {
175
+ const targetList = getParentList(menuName, folderName)
176
+ if (!targetList)
177
+ return
178
+ const siblings = siblingNames(targetList)
179
+ const name = uniqueDisplayName(label, siblings)
180
+ targetList.push({
181
+ name,
182
+ item: {
183
+ type: 'external',
184
+ url,
185
+ },
186
+ })
187
+ }
188
+
139
189
  const removePage = (menuName, index, folderName = null) => {
140
190
  const targetList = getParentList(menuName, folderName)
141
191
  if (!targetList)
@@ -199,7 +249,7 @@ const deleteFolder = (menuName, index) => {
199
249
 
200
250
  const openRenameDialogForPage = (menuName, index, folderName = null) => {
201
251
  const parentList = getParentList(menuName, folderName)
202
- if (!parentList?.[index])
252
+ if (!parentList?.[index] || isExternalLinkEntry(parentList[index]))
203
253
  return
204
254
  state.renameDialog = {
205
255
  open: true,
@@ -222,6 +272,62 @@ const openRenameDialogForFolder = (menuName, folderName, index) => {
222
272
  }
223
273
  }
224
274
 
275
+ const resetLinkDialog = () => {
276
+ state.linkDialog.mode = 'add'
277
+ state.linkDialog.menu = ''
278
+ state.linkDialog.folder = ''
279
+ state.linkDialog.index = -1
280
+ state.linkDialog.name = ''
281
+ state.linkDialog.url = ''
282
+ }
283
+
284
+ watch(() => state.linkDialog.open, (open) => {
285
+ if (!open)
286
+ resetLinkDialog()
287
+ })
288
+
289
+ const openAddLinkDialog = (menuName, folderName = null) => {
290
+ state.linkDialog.mode = 'add'
291
+ state.linkDialog.menu = menuName
292
+ state.linkDialog.folder = folderName || ''
293
+ state.linkDialog.index = -1
294
+ state.linkDialog.name = ''
295
+ state.linkDialog.url = ''
296
+ state.linkDialog.open = true
297
+ }
298
+
299
+ const openEditLinkDialog = (menuName, index, entry, folderName = null) => {
300
+ state.linkDialog.mode = 'edit'
301
+ state.linkDialog.menu = menuName
302
+ state.linkDialog.folder = folderName || ''
303
+ state.linkDialog.index = index
304
+ state.linkDialog.name = entry?.name || ''
305
+ state.linkDialog.url = entry?.item?.url || ''
306
+ state.linkDialog.open = true
307
+ }
308
+
309
+ const submitLinkDialog = () => {
310
+ const label = state.linkDialog.name?.trim() || ''
311
+ const url = state.linkDialog.url?.trim() || ''
312
+ if (!label || !url)
313
+ return
314
+ const targetList = getParentList(state.linkDialog.menu, state.linkDialog.folder || null)
315
+ if (!targetList)
316
+ return
317
+ if (state.linkDialog.mode === 'edit') {
318
+ const target = targetList[state.linkDialog.index]
319
+ if (!target || !isExternalLinkEntry(target))
320
+ return
321
+ const siblings = siblingNames(targetList, state.linkDialog.index)
322
+ target.name = uniqueDisplayName(label, siblings, target.name)
323
+ target.item = { type: 'external', url }
324
+ }
325
+ else {
326
+ addLinkToMenu(state.linkDialog.menu, label, url, state.linkDialog.folder || null)
327
+ }
328
+ state.linkDialog.open = false
329
+ }
330
+
225
331
  const submitRenameDialog = () => {
226
332
  if (!state.renameDialog.open)
227
333
  return
@@ -298,6 +404,11 @@ const hasEntries = computed(() => {
298
404
  <div v-else class="px-3 py-2 text-xs text-muted-foreground">
299
405
  All template pages are already assigned.
300
406
  </div>
407
+ <DropdownMenuSeparator />
408
+ <DropdownMenuItem @click="openAddLinkDialog(menuName)">
409
+ <Link class="w-4 h-4" />
410
+ <span>External Link</span>
411
+ </DropdownMenuItem>
301
412
  </DropdownMenuContent>
302
413
  </DropdownMenu>
303
414
  <edge-shad-button
@@ -377,11 +488,16 @@ const hasEntries = computed(() => {
377
488
  <div v-else class="px-3 py-2 text-xs text-muted-foreground">
378
489
  All template pages are already assigned.
379
490
  </div>
491
+ <DropdownMenuSeparator />
492
+ <DropdownMenuItem @click="openAddLinkDialog(menuName, getFolderName(element))">
493
+ <Link class="w-4 h-4" />
494
+ <span>External Link</span>
495
+ </DropdownMenuItem>
380
496
  </DropdownMenuContent>
381
497
  </DropdownMenu>
382
498
  </div>
383
499
  </div>
384
- <div class="space-y-3 border-l border-dashed border-border/80 px-3 py-3 ml-4">
500
+ <div class="space-y-2 border-l border-dashed border-border/80 px-2 py-2 ml-3">
385
501
  <div class="flex justify-end" />
386
502
  <draggable
387
503
  :list="element.item[getFolderName(element)]"
@@ -391,21 +507,42 @@ const hasEntries = computed(() => {
391
507
  :group="dragGroup"
392
508
  >
393
509
  <template #item="{ element: child, index: childIndex }">
394
- <div class="flex items-center justify-between gap-3 rounded-md border border-border/70 bg-card px-3 py-2">
395
- <div class="flex items-center gap-2">
510
+ <div class="flex items-center justify-between gap-2 rounded-md border border-border/70 bg-card px-2 py-1.5">
511
+ <div class="flex items-center gap-2 flex-1 min-w-0">
396
512
  <GripVertical class="w-4 h-4 text-muted-foreground drag-handle" />
397
- <div>
398
- <div class="text-sm font-semibold flex items-center gap-1">
399
- <FileIcon class="w-4 h-4" />
400
- {{ element.item[getFolderName(element)][childIndex].name }}
513
+ <div class="min-w-0">
514
+ <div class="text-xs font-semibold flex items-center gap-1 leading-tight">
515
+ <template v-if="isExternalLinkEntry(child)">
516
+ <Link class="w-3.5 h-3.5" />
517
+ {{ child.name }}
518
+ </template>
519
+ <template v-else>
520
+ <FileIcon class="w-3.5 h-3.5" />
521
+ {{ child.name }}
522
+ </template>
401
523
  </div>
402
- <div class="text-[11px] text-muted-foreground">
403
- Template: {{ resolveTemplateTitle(element.item[getFolderName(element)][childIndex].item) }}
524
+ <div class="text-[10px] text-muted-foreground leading-tight">
525
+ <template v-if="isExternalLinkEntry(child)">
526
+ {{ child.item?.url || 'External link' }}
527
+ </template>
528
+ <template v-else>
529
+ {{ resolveTemplateTitle(child.item) }}
530
+ </template>
404
531
  </div>
405
532
  </div>
406
533
  </div>
407
534
  <div class="flex gap-1">
408
535
  <edge-shad-button
536
+ v-if="isExternalLinkEntry(child)"
537
+ variant="ghost"
538
+ size="icon"
539
+ class="h-7 w-7"
540
+ @click="openEditLinkDialog(menuName, childIndex, child, getFolderName(element))"
541
+ >
542
+ <Link class="w-3.5 h-3.5" />
543
+ </edge-shad-button>
544
+ <edge-shad-button
545
+ v-else
409
546
  variant="ghost"
410
547
  size="icon"
411
548
  class="h-7 w-7"
@@ -413,6 +550,25 @@ const hasEntries = computed(() => {
413
550
  >
414
551
  <FilePen class="w-3.5 h-3.5" />
415
552
  </edge-shad-button>
553
+ <DropdownMenu v-if="isPageEntry(child)">
554
+ <DropdownMenuTrigger as-child>
555
+ <edge-shad-button
556
+ variant="ghost"
557
+ size="icon"
558
+ class="h-7 w-7"
559
+ >
560
+ <FileCog class="w-3.5 h-3.5" />
561
+ </edge-shad-button>
562
+ </DropdownMenuTrigger>
563
+ <DropdownMenuContent align="end" class="w-48">
564
+ <DropdownMenuCheckboxItem v-model="child.disableRename">
565
+ Disable Rename
566
+ </DropdownMenuCheckboxItem>
567
+ <DropdownMenuCheckboxItem v-model="child.disableDelete">
568
+ Disable Delete
569
+ </DropdownMenuCheckboxItem>
570
+ </DropdownMenuContent>
571
+ </DropdownMenu>
416
572
  <edge-shad-button
417
573
  variant="ghost"
418
574
  size="icon"
@@ -435,21 +591,42 @@ const hasEntries = computed(() => {
435
591
  </draggable>
436
592
  </div>
437
593
  </div>
438
- <div v-else class="flex items-center justify-between gap-3 rounded-md border border-border/70 bg-card px-3 py-2">
439
- <div class="flex items-center gap-2">
594
+ <div v-else class="flex items-center justify-between gap-2 rounded-md border border-border/70 bg-card px-2 py-1.5">
595
+ <div class="flex items-center gap-2 flex-1 min-w-0">
440
596
  <GripVertical class="w-4 h-4 text-muted-foreground drag-handle" />
441
- <div>
442
- <div class="text-sm font-semibold flex items-center gap-1">
443
- <FileIcon class="w-4 h-4" />
444
- {{ element.name }}
597
+ <div class="min-w-0">
598
+ <div class="text-xs font-semibold flex items-center gap-1 leading-tight">
599
+ <template v-if="isExternalLinkEntry(element)">
600
+ <Link class="w-3.5 h-3.5" />
601
+ {{ element.name }}
602
+ </template>
603
+ <template v-else>
604
+ <FileIcon class="w-3.5 h-3.5" />
605
+ {{ element.name }}
606
+ </template>
445
607
  </div>
446
- <div class="text-[11px] text-muted-foreground">
447
- Template: {{ resolveTemplateTitle(element.item) }}
608
+ <div class="text-[10px] text-muted-foreground leading-tight">
609
+ <template v-if="isExternalLinkEntry(element)">
610
+ {{ element.item?.url || 'External link' }}
611
+ </template>
612
+ <template v-else>
613
+ {{ resolveTemplateTitle(element.item) }}
614
+ </template>
448
615
  </div>
449
616
  </div>
450
617
  </div>
451
618
  <div class="flex gap-1">
452
619
  <edge-shad-button
620
+ v-if="isExternalLinkEntry(element)"
621
+ variant="ghost"
622
+ size="icon"
623
+ class="h-7 w-7"
624
+ @click="openEditLinkDialog(menuName, index, element)"
625
+ >
626
+ <Link class="w-3.5 h-3.5" />
627
+ </edge-shad-button>
628
+ <edge-shad-button
629
+ v-else
453
630
  variant="ghost"
454
631
  size="icon"
455
632
  class="h-7 w-7"
@@ -457,6 +634,25 @@ const hasEntries = computed(() => {
457
634
  >
458
635
  <FilePen class="w-3.5 h-3.5" />
459
636
  </edge-shad-button>
637
+ <DropdownMenu v-if="isPageEntry(element)">
638
+ <DropdownMenuTrigger as-child>
639
+ <edge-shad-button
640
+ variant="ghost"
641
+ size="icon"
642
+ class="h-7 w-7"
643
+ >
644
+ <FileCog class="w-3.5 h-3.5" />
645
+ </edge-shad-button>
646
+ </DropdownMenuTrigger>
647
+ <DropdownMenuContent align="end" class="w-48">
648
+ <DropdownMenuCheckboxItem v-model="element.disableRename">
649
+ Disable Rename
650
+ </DropdownMenuCheckboxItem>
651
+ <DropdownMenuCheckboxItem v-model="element.disableDelete">
652
+ Disable Delete
653
+ </DropdownMenuCheckboxItem>
654
+ </DropdownMenuContent>
655
+ </DropdownMenu>
460
656
  <edge-shad-button
461
657
  variant="ghost"
462
658
  size="icon"
@@ -514,6 +710,46 @@ const hasEntries = computed(() => {
514
710
  </DialogContent>
515
711
  </edge-shad-dialog>
516
712
 
713
+ <edge-shad-dialog v-model="state.linkDialog.open">
714
+ <DialogContent class="pt-10">
715
+ <DialogHeader>
716
+ <DialogTitle>
717
+ {{ state.linkDialog.mode === 'edit' ? 'Edit External Link' : 'Add External Link' }}
718
+ </DialogTitle>
719
+ </DialogHeader>
720
+ <div class="space-y-4">
721
+ <edge-shad-input
722
+ v-model="state.linkDialog.name"
723
+ name="linkName"
724
+ label="Label"
725
+ placeholder="Link label"
726
+ />
727
+ <edge-shad-input
728
+ v-model="state.linkDialog.url"
729
+ name="linkUrl"
730
+ label="URL"
731
+ placeholder="https://example.com or tel:123-456-7890"
732
+ />
733
+ </div>
734
+ <DialogFooter class="pt-2">
735
+ <edge-shad-button
736
+ variant="destructive"
737
+ @click="state.linkDialog.open = false"
738
+ >
739
+ Cancel
740
+ </edge-shad-button>
741
+ <edge-shad-button
742
+ type="button"
743
+ class="bg-slate-800 hover:bg-slate-500 text-white"
744
+ :disabled="!state.linkDialog.name?.trim()?.length || !state.linkDialog.url?.trim()?.length"
745
+ @click="submitLinkDialog"
746
+ >
747
+ {{ state.linkDialog.mode === 'edit' ? 'Update Link' : 'Add Link' }}
748
+ </edge-shad-button>
749
+ </DialogFooter>
750
+ </DialogContent>
751
+ </edge-shad-dialog>
752
+
517
753
  <edge-shad-dialog v-model="state.renameDialog.open">
518
754
  <DialogContent class="pt-10">
519
755
  <DialogHeader>
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { toTypedSchema } from '@vee-validate/zod'
3
+ import { FolderCog } from 'lucide-vue-next'
3
4
  import * as z from 'zod'
4
5
  const props = defineProps({
5
6
  themeId: {
@@ -10,6 +11,7 @@ const props = defineProps({
10
11
 
11
12
  const emit = defineEmits(['head'])
12
13
  const edgeFirebase = inject('edgeFirebase')
14
+ const { createDefaults: createSiteSettingsDefaults } = useSiteSettingsTemplate()
13
15
  const state = reactive({
14
16
  filter: '',
15
17
  workingDoc: {},
@@ -57,7 +59,10 @@ const state = reactive({
57
59
  "navBorder": "",
58
60
  "navActive": "#3B82F6",
59
61
  "navHoverBg": "",
60
- "navActiveBg": ""
62
+ "navActiveBg": "",
63
+ "sideNavBg": "#FFFFFF",
64
+ "sideNavText": "#000000",
65
+ "sideNavActive": "#AFBD23"
61
66
  },
62
67
  "fontFamily": {
63
68
  "sans": ["Overpass", "sans-serif"],
@@ -79,6 +84,9 @@ const state = reactive({
79
84
  }
80
85
  }`,
81
86
  },
87
+ extraCSS: {
88
+ value: '',
89
+ },
82
90
  version: 1,
83
91
  defaultPages: { value: [] },
84
92
  defaultMenus: {
@@ -87,10 +95,12 @@ const state = reactive({
87
95
  'Not In Menu': [],
88
96
  },
89
97
  },
98
+ defaultSiteSettings: { value: createSiteSettingsDefaults() },
90
99
  },
91
100
  },
92
101
  mounted: false,
93
102
  loading: false,
103
+ defaultSettingsOpen: false,
94
104
  })
95
105
 
96
106
  const blockSchema = toTypedSchema(z.object({
@@ -103,14 +113,19 @@ onMounted(() => {
103
113
  // state.mounted = true
104
114
  })
105
115
 
106
- const headObject = computed(() => {
116
+ const lastValidHead = ref({})
117
+ const lastValidTheme = ref({})
118
+
119
+ const parseJsonSafe = (value, fallback) => {
107
120
  try {
108
- return JSON.parse(state.workingDoc.headJSON || '{}')
121
+ return JSON.parse(value || '{}')
109
122
  }
110
- catch (e) {
111
- return {}
123
+ catch (error) {
124
+ return fallback
112
125
  }
113
- })
126
+ }
127
+
128
+ const headObject = computed(() => lastValidHead.value)
114
129
 
115
130
  watch(headObject, (newHeadElements) => {
116
131
  emit('head', newHeadElements)
@@ -150,6 +165,19 @@ const ensureDefaultMenusObject = (doc = state.workingDoc) => {
150
165
  return doc.defaultMenus
151
166
  }
152
167
 
168
+ const ensureDefaultSiteSettings = (doc = state.workingDoc) => {
169
+ if (!doc)
170
+ return {}
171
+ if (!doc.defaultSiteSettings || typeof doc.defaultSiteSettings !== 'object' || Array.isArray(doc.defaultSiteSettings))
172
+ doc.defaultSiteSettings = createSiteSettingsDefaults()
173
+ const defaults = createSiteSettingsDefaults()
174
+ for (const key of Object.keys(defaults)) {
175
+ if (doc.defaultSiteSettings[key] === undefined)
176
+ doc.defaultSiteSettings[key] = defaults[key]
177
+ }
178
+ return doc.defaultSiteSettings
179
+ }
180
+
153
181
  const collectTemplateIdsFromMenus = (menus = {}) => {
154
182
  const ids = new Set()
155
183
  const traverse = (items = []) => {
@@ -226,6 +254,8 @@ const hydrateMenusFromDefaultPages = (doc = state.workingDoc) => {
226
254
  return {
227
255
  name: slug,
228
256
  item: entry.pageId,
257
+ disableRename: !!entry?.disableRename,
258
+ disableDelete: !!entry?.disableDelete,
229
259
  }
230
260
  })
231
261
  }
@@ -242,6 +272,8 @@ const flattenMenusToDefaultPages = (menus = {}) => {
242
272
  collected.push({
243
273
  pageId: entry.item,
244
274
  name: templatePageName(entry.item, entry.name),
275
+ disableRename: !!entry?.disableRename,
276
+ disableDelete: !!entry?.disableDelete,
245
277
  })
246
278
  }
247
279
  else if (typeof entry.item === 'object') {
@@ -263,7 +295,11 @@ const syncDefaultPagesFromMenus = () => {
263
295
  const defaults = ensureDefaultPagesArray()
264
296
  const sameLength = defaults.length === normalized.length
265
297
  const sameOrder = sameLength && defaults.every((entry, index) => entry.pageId === normalized[index].pageId)
266
- if (sameLength && sameOrder)
298
+ const sameFlags = sameLength && defaults.every((entry, index) => (
299
+ !!entry.disableRename === !!normalized[index]?.disableRename
300
+ && !!entry.disableDelete === !!normalized[index]?.disableDelete
301
+ ))
302
+ if (sameLength && sameOrder && sameFlags)
267
303
  return
268
304
  defaults.splice(0, defaults.length, ...normalized)
269
305
  }
@@ -272,6 +308,7 @@ const editorDocUpdates = (workingDoc) => {
272
308
  state.workingDoc = workingDoc
273
309
  ensureDefaultPagesArray(state.workingDoc)
274
310
  ensureDefaultMenusObject(state.workingDoc)
311
+ ensureDefaultSiteSettings(state.workingDoc)
275
312
  hydrateMenusFromDefaultPages(state.workingDoc)
276
313
  syncDefaultPagesFromMenus()
277
314
  }
@@ -309,6 +346,14 @@ watch(() => state.workingDoc?.defaultMenus, () => {
309
346
  syncDefaultPagesFromMenus()
310
347
  }, { deep: true })
311
348
 
349
+ watch(() => state.workingDoc?.headJSON, (value) => {
350
+ lastValidHead.value = parseJsonSafe(value, lastValidHead.value)
351
+ }, { immediate: true })
352
+
353
+ watch(() => state.workingDoc?.theme, (value) => {
354
+ lastValidTheme.value = parseJsonSafe(value, lastValidTheme.value)
355
+ }, { immediate: true })
356
+
312
357
  onBeforeMount(async () => {
313
358
  if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/sites`]) {
314
359
  await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/sites`)
@@ -356,12 +401,21 @@ onBeforeMount(async () => {
356
401
  </template>
357
402
  <template #main="slotProps">
358
403
  <div class="pt-4 flex flex-col gap-6 lg:flex-row">
359
- <div class="lg:w-1/3 lg:max-w-sm w-full space-y-4">
404
+ <div class="lg:w-72 lg:max-w-xs w-full space-y-4">
360
405
  <Card class="h-full">
361
406
  <CardHeader class="pb-2">
362
- <CardTitle class="text-base">
363
- Default Template Pages
364
- </CardTitle>
407
+ <div class="flex items-center justify-between gap-2">
408
+ <CardTitle class="text-base">
409
+ Default Template Pages
410
+ </CardTitle>
411
+ <edge-shad-button
412
+ size="icon"
413
+ type="text"
414
+ @click="state.defaultSettingsOpen = true"
415
+ >
416
+ <FolderCog class="h-4 w-4" />
417
+ </edge-shad-button>
418
+ </div>
365
419
  <CardDescription class="text-xs">
366
420
  Choose which template pages are created for new sites and organize them into Site Menu or Not In Menu.
367
421
  </CardDescription>
@@ -406,6 +460,14 @@ onBeforeMount(async () => {
406
460
  height="400px"
407
461
  class="mb-4 w-full"
408
462
  />
463
+ <edge-cms-code-editor
464
+ v-model="slotProps.workingDoc.extraCSS"
465
+ title="Extra CSS"
466
+ language="css"
467
+ name="extraCSS"
468
+ height="300px"
469
+ class="mb-4 w-full"
470
+ />
409
471
  </div>
410
472
  <div class="w-1/2">
411
473
  <div class="w-full mx-auto bg-white drop-shadow-[4px_4px_6px_rgba(0,0,0,0.5)] shadow-lg shadow-black/30">
@@ -413,7 +475,7 @@ onBeforeMount(async () => {
413
475
  :site-id="edgeGlobal.edgeState.blockEditorSite"
414
476
  class="!h-[calc(100vh-220px)] overflow-y-auto"
415
477
  list-only
416
- :theme="JSON.parse(slotProps.workingDoc.theme)"
478
+ :theme="lastValidTheme"
417
479
  />
418
480
  </div>
419
481
  </div>
@@ -422,5 +484,30 @@ onBeforeMount(async () => {
422
484
  </div>
423
485
  </template>
424
486
  </edge-editor>
487
+ <Sheet v-model:open="state.defaultSettingsOpen">
488
+ <SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
489
+ <SheetHeader>
490
+ <SheetTitle>Default Site Settings</SheetTitle>
491
+ <SheetDescription>
492
+ These values seed new sites created with this theme.
493
+ </SheetDescription>
494
+ </SheetHeader>
495
+ <div class="p-6 h-[calc(100vh-140px)] overflow-y-auto">
496
+ <edge-cms-site-settings-form
497
+ v-if="state.workingDoc"
498
+ :settings="state.workingDoc.defaultSiteSettings"
499
+ :show-users="false"
500
+ :show-theme-fields="false"
501
+ :is-admin="true"
502
+ :enable-media-picker="false"
503
+ />
504
+ </div>
505
+ <SheetFooter class="pt-2">
506
+ <edge-shad-button class="w-full" @click="state.defaultSettingsOpen = false">
507
+ Done
508
+ </edge-shad-button>
509
+ </SheetFooter>
510
+ </SheetContent>
511
+ </Sheet>
425
512
  </div>
426
513
  </template>
@@ -569,6 +569,7 @@ const onError = async () => {
569
569
  <AlertDescription>
570
570
  {{ state.successMessage }}
571
571
  </AlertDescription>
572
+ <slot name="success-alert" :message="state.successMessage" :working-doc="state.workingDoc" />
572
573
  </Alert>
573
574
  <slot name="main" :title="title" :on-cancel="onCancel" :submitting="state.submitting" :unsaved-changes="unsavedChanges" :on-submit="triggerSubmit" :working-doc="state.workingDoc">
574
575
  <div class="flex flex-wrap justify-between">