@edgedev/create-edge-app 1.1.27 → 1.1.29
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/auth/register.vue +51 -0
- package/edge/components/cms/block.vue +363 -42
- package/edge/components/cms/blockEditor.vue +50 -3
- package/edge/components/cms/codeEditor.vue +39 -2
- package/edge/components/cms/htmlContent.vue +10 -2
- 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 +35 -5
- package/edge/components/cms/menu.vue +384 -61
- package/edge/components/cms/optionsSelect.vue +20 -3
- package/edge/components/cms/page.vue +160 -18
- package/edge/components/cms/site.vue +548 -374
- package/edge/components/cms/siteSettingsForm.vue +623 -0
- package/edge/components/cms/themeDefaultMenu.vue +258 -22
- package/edge/components/cms/themeEditor.vue +95 -11
- package/edge/components/editor.vue +1 -0
- package/edge/components/formSubtypes/myOrgs.vue +112 -1
- package/edge/components/imagePicker.vue +126 -0
- package/edge/components/myAccount.vue +1 -0
- package/edge/components/myProfile.vue +345 -61
- package/edge/components/orgSwitcher.vue +1 -1
- package/edge/components/organizationMembers.vue +620 -235
- package/edge/components/shad/html.vue +6 -0
- package/edge/components/shad/number.vue +2 -2
- package/edge/components/sideBar.vue +7 -4
- package/edge/components/sideBarContent.vue +1 -1
- package/edge/components/userMenu.vue +50 -14
- package/edge/composables/global.ts +4 -1
- 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: {},
|
|
@@ -82,6 +84,9 @@ const state = reactive({
|
|
|
82
84
|
}
|
|
83
85
|
}`,
|
|
84
86
|
},
|
|
87
|
+
extraCSS: {
|
|
88
|
+
value: '',
|
|
89
|
+
},
|
|
85
90
|
version: 1,
|
|
86
91
|
defaultPages: { value: [] },
|
|
87
92
|
defaultMenus: {
|
|
@@ -90,10 +95,12 @@ const state = reactive({
|
|
|
90
95
|
'Not In Menu': [],
|
|
91
96
|
},
|
|
92
97
|
},
|
|
98
|
+
defaultSiteSettings: { value: createSiteSettingsDefaults() },
|
|
93
99
|
},
|
|
94
100
|
},
|
|
95
101
|
mounted: false,
|
|
96
102
|
loading: false,
|
|
103
|
+
defaultSettingsOpen: false,
|
|
97
104
|
})
|
|
98
105
|
|
|
99
106
|
const blockSchema = toTypedSchema(z.object({
|
|
@@ -106,14 +113,19 @@ onMounted(() => {
|
|
|
106
113
|
// state.mounted = true
|
|
107
114
|
})
|
|
108
115
|
|
|
109
|
-
const
|
|
116
|
+
const lastValidHead = ref({})
|
|
117
|
+
const lastValidTheme = ref({})
|
|
118
|
+
|
|
119
|
+
const parseJsonSafe = (value, fallback) => {
|
|
110
120
|
try {
|
|
111
|
-
return JSON.parse(
|
|
121
|
+
return JSON.parse(value || '{}')
|
|
112
122
|
}
|
|
113
|
-
catch (
|
|
114
|
-
return
|
|
123
|
+
catch (error) {
|
|
124
|
+
return fallback
|
|
115
125
|
}
|
|
116
|
-
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const headObject = computed(() => lastValidHead.value)
|
|
117
129
|
|
|
118
130
|
watch(headObject, (newHeadElements) => {
|
|
119
131
|
emit('head', newHeadElements)
|
|
@@ -153,6 +165,19 @@ const ensureDefaultMenusObject = (doc = state.workingDoc) => {
|
|
|
153
165
|
return doc.defaultMenus
|
|
154
166
|
}
|
|
155
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
|
+
|
|
156
181
|
const collectTemplateIdsFromMenus = (menus = {}) => {
|
|
157
182
|
const ids = new Set()
|
|
158
183
|
const traverse = (items = []) => {
|
|
@@ -229,6 +254,8 @@ const hydrateMenusFromDefaultPages = (doc = state.workingDoc) => {
|
|
|
229
254
|
return {
|
|
230
255
|
name: slug,
|
|
231
256
|
item: entry.pageId,
|
|
257
|
+
disableRename: !!entry?.disableRename,
|
|
258
|
+
disableDelete: !!entry?.disableDelete,
|
|
232
259
|
}
|
|
233
260
|
})
|
|
234
261
|
}
|
|
@@ -245,6 +272,8 @@ const flattenMenusToDefaultPages = (menus = {}) => {
|
|
|
245
272
|
collected.push({
|
|
246
273
|
pageId: entry.item,
|
|
247
274
|
name: templatePageName(entry.item, entry.name),
|
|
275
|
+
disableRename: !!entry?.disableRename,
|
|
276
|
+
disableDelete: !!entry?.disableDelete,
|
|
248
277
|
})
|
|
249
278
|
}
|
|
250
279
|
else if (typeof entry.item === 'object') {
|
|
@@ -266,7 +295,11 @@ const syncDefaultPagesFromMenus = () => {
|
|
|
266
295
|
const defaults = ensureDefaultPagesArray()
|
|
267
296
|
const sameLength = defaults.length === normalized.length
|
|
268
297
|
const sameOrder = sameLength && defaults.every((entry, index) => entry.pageId === normalized[index].pageId)
|
|
269
|
-
|
|
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)
|
|
270
303
|
return
|
|
271
304
|
defaults.splice(0, defaults.length, ...normalized)
|
|
272
305
|
}
|
|
@@ -275,6 +308,7 @@ const editorDocUpdates = (workingDoc) => {
|
|
|
275
308
|
state.workingDoc = workingDoc
|
|
276
309
|
ensureDefaultPagesArray(state.workingDoc)
|
|
277
310
|
ensureDefaultMenusObject(state.workingDoc)
|
|
311
|
+
ensureDefaultSiteSettings(state.workingDoc)
|
|
278
312
|
hydrateMenusFromDefaultPages(state.workingDoc)
|
|
279
313
|
syncDefaultPagesFromMenus()
|
|
280
314
|
}
|
|
@@ -312,6 +346,14 @@ watch(() => state.workingDoc?.defaultMenus, () => {
|
|
|
312
346
|
syncDefaultPagesFromMenus()
|
|
313
347
|
}, { deep: true })
|
|
314
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
|
+
|
|
315
357
|
onBeforeMount(async () => {
|
|
316
358
|
if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/sites`]) {
|
|
317
359
|
await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/sites`)
|
|
@@ -359,12 +401,21 @@ onBeforeMount(async () => {
|
|
|
359
401
|
</template>
|
|
360
402
|
<template #main="slotProps">
|
|
361
403
|
<div class="pt-4 flex flex-col gap-6 lg:flex-row">
|
|
362
|
-
<div class="lg:w-
|
|
404
|
+
<div class="lg:w-72 lg:max-w-xs w-full space-y-4">
|
|
363
405
|
<Card class="h-full">
|
|
364
406
|
<CardHeader class="pb-2">
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
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>
|
|
368
419
|
<CardDescription class="text-xs">
|
|
369
420
|
Choose which template pages are created for new sites and organize them into Site Menu or Not In Menu.
|
|
370
421
|
</CardDescription>
|
|
@@ -409,6 +460,14 @@ onBeforeMount(async () => {
|
|
|
409
460
|
height="400px"
|
|
410
461
|
class="mb-4 w-full"
|
|
411
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
|
+
/>
|
|
412
471
|
</div>
|
|
413
472
|
<div class="w-1/2">
|
|
414
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">
|
|
@@ -416,7 +475,7 @@ onBeforeMount(async () => {
|
|
|
416
475
|
:site-id="edgeGlobal.edgeState.blockEditorSite"
|
|
417
476
|
class="!h-[calc(100vh-220px)] overflow-y-auto"
|
|
418
477
|
list-only
|
|
419
|
-
:theme="
|
|
478
|
+
:theme="lastValidTheme"
|
|
420
479
|
/>
|
|
421
480
|
</div>
|
|
422
481
|
</div>
|
|
@@ -425,5 +484,30 @@ onBeforeMount(async () => {
|
|
|
425
484
|
</div>
|
|
426
485
|
</template>
|
|
427
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>
|
|
428
512
|
</div>
|
|
429
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">
|