@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.
- package/edge/components/cms/block.vue +334 -26
- package/edge/components/cms/blockEditor.vue +50 -3
- package/edge/components/cms/codeEditor.vue +15 -0
- package/edge/components/cms/init_blocks/footer.html +111 -19
- package/edge/components/cms/init_blocks/image.html +8 -0
- package/edge/components/cms/init_blocks/post_content.html +3 -2
- package/edge/components/cms/init_blocks/post_title_header.html +8 -6
- package/edge/components/cms/init_blocks/posts_list.html +6 -5
- package/edge/components/cms/mediaCard.vue +13 -2
- package/edge/components/cms/mediaManager.vue +16 -2
- package/edge/components/cms/menu.vue +253 -42
- package/edge/components/cms/page.vue +151 -18
- package/edge/components/cms/site.vue +537 -215
- package/edge/components/cms/siteSettingsForm.vue +616 -0
- package/edge/components/cms/themeDefaultMenu.vue +258 -22
- package/edge/components/cms/themeEditor.vue +99 -12
- package/edge/components/editor.vue +1 -0
- package/edge/components/formSubtypes/myOrgs.vue +112 -1
- package/edge/components/orgSwitcher.vue +1 -1
- package/edge/components/organizationMembers.vue +171 -21
- package/edge/components/shad/html.vue +6 -0
- package/edge/components/sideBar.vue +7 -4
- package/edge/components/sideBarContent.vue +1 -1
- package/edge/components/userMenu.vue +50 -14
- package/edge/composables/siteSettingsTemplate.js +79 -0
- package/edge/composables/structuredDataTemplates.js +36 -0
- package/package.json +1 -1
|
@@ -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
|
|
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 ||
|
|
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-
|
|
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-
|
|
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-
|
|
399
|
-
<
|
|
400
|
-
|
|
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-[
|
|
403
|
-
|
|
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-
|
|
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-
|
|
443
|
-
<
|
|
444
|
-
|
|
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-[
|
|
447
|
-
|
|
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
|
|
116
|
+
const lastValidHead = ref({})
|
|
117
|
+
const lastValidTheme = ref({})
|
|
118
|
+
|
|
119
|
+
const parseJsonSafe = (value, fallback) => {
|
|
107
120
|
try {
|
|
108
|
-
return JSON.parse(
|
|
121
|
+
return JSON.parse(value || '{}')
|
|
109
122
|
}
|
|
110
|
-
catch (
|
|
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
|
-
|
|
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-
|
|
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
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
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="
|
|
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">
|