@edgedev/create-edge-app 1.2.32 → 1.2.34
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/deploy.sh +77 -0
- package/edge/components/cms/block.vue +228 -18
- package/edge/components/cms/blockApi.vue +3 -3
- package/edge/components/cms/blockEditor.vue +374 -85
- package/edge/components/cms/blockPicker.vue +29 -3
- package/edge/components/cms/blockRender.vue +3 -3
- package/edge/components/cms/blocksManager.vue +755 -82
- package/edge/components/cms/codeEditor.vue +15 -6
- package/edge/components/cms/fontUpload.vue +318 -2
- package/edge/components/cms/htmlContent.vue +230 -89
- package/edge/components/cms/menu.vue +5 -4
- package/edge/components/cms/page.vue +750 -21
- package/edge/components/cms/site.vue +624 -84
- package/edge/components/cms/sitesManager.vue +5 -4
- package/edge/components/cms/themeEditor.vue +196 -162
- package/edge/components/editor.vue +5 -1
- package/edge/composables/global.ts +37 -5
- package/edge/composables/useCmsNewDocs.js +100 -0
- package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
- package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
- package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
- package/edge/routes/cms/dashboard/media/index.vue +5 -0
- package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
- package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
- package/edge/routes/cms/dashboard/sites/index.vue +4 -0
- package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
- package/edge/routes/cms/dashboard/templates/index.vue +4 -0
- package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
- package/edge/routes/cms/dashboard/themes/index.vue +330 -1
- package/firebase.json +4 -0
- package/nuxt.config.ts +1 -1
- package/package.json +2 -2
- package/pages/app.vue +12 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { AlertTriangle, ArrowDown, ArrowUp, Maximize2, Monitor, Smartphone, Sparkles, Tablet, UploadCloud } from 'lucide-vue-next'
|
|
2
|
+
import { AlertTriangle, ArrowDown, ArrowUp, Download, Maximize2, Monitor, Smartphone, Sparkles, Tablet, UploadCloud } from 'lucide-vue-next'
|
|
3
3
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
4
4
|
import * as z from 'zod'
|
|
5
5
|
const props = defineProps({
|
|
@@ -20,6 +20,7 @@ const props = defineProps({
|
|
|
20
20
|
const emit = defineEmits(['head'])
|
|
21
21
|
|
|
22
22
|
const edgeFirebase = inject('edgeFirebase')
|
|
23
|
+
const router = useRouter()
|
|
23
24
|
const { buildPageStructuredData } = useStructuredDataTemplates()
|
|
24
25
|
|
|
25
26
|
const state = reactive({
|
|
@@ -41,6 +42,13 @@ const state = reactive({
|
|
|
41
42
|
workingDoc: {},
|
|
42
43
|
seoAiLoading: false,
|
|
43
44
|
seoAiError: '',
|
|
45
|
+
importingJson: false,
|
|
46
|
+
importDocIdDialogOpen: false,
|
|
47
|
+
importDocIdValue: '',
|
|
48
|
+
importConflictDialogOpen: false,
|
|
49
|
+
importConflictDocId: '',
|
|
50
|
+
importErrorDialogOpen: false,
|
|
51
|
+
importErrorMessage: '',
|
|
44
52
|
previewViewport: 'full',
|
|
45
53
|
newRowLayout: '6',
|
|
46
54
|
newPostRowLayout: '6',
|
|
@@ -68,6 +76,10 @@ const state = reactive({
|
|
|
68
76
|
},
|
|
69
77
|
})
|
|
70
78
|
|
|
79
|
+
const pageImportInputRef = ref(null)
|
|
80
|
+
const pageImportDocIdResolver = ref(null)
|
|
81
|
+
const pageImportConflictResolver = ref(null)
|
|
82
|
+
|
|
71
83
|
const schemas = {
|
|
72
84
|
pages: toTypedSchema(z.object({
|
|
73
85
|
name: z.string({
|
|
@@ -97,6 +109,14 @@ const previewViewportStyle = computed(() => {
|
|
|
97
109
|
}
|
|
98
110
|
})
|
|
99
111
|
|
|
112
|
+
const previewViewportContainStyle = computed(() => {
|
|
113
|
+
const shouldContain = !state.editMode
|
|
114
|
+
return {
|
|
115
|
+
...(previewViewportStyle.value || {}),
|
|
116
|
+
...(shouldContain ? { transform: 'translateZ(0)' } : {}),
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
100
120
|
const setPreviewViewport = (viewportId) => {
|
|
101
121
|
state.previewViewport = viewportId
|
|
102
122
|
}
|
|
@@ -108,6 +128,13 @@ const previewViewportMode = computed(() => {
|
|
|
108
128
|
})
|
|
109
129
|
|
|
110
130
|
const isMobilePreview = computed(() => previewViewportMode.value === 'mobile')
|
|
131
|
+
const pagePreviewRenderKey = computed(() => {
|
|
132
|
+
const siteKey = String(props.site || '')
|
|
133
|
+
const pageKey = String(props.page || '')
|
|
134
|
+
const themeKey = String(effectiveThemeId.value || selectedThemeId.value || 'no-theme')
|
|
135
|
+
const modeKey = state.editMode ? 'edit' : 'preview'
|
|
136
|
+
return `${siteKey}:${pageKey}:${themeKey}:${modeKey}`
|
|
137
|
+
})
|
|
111
138
|
|
|
112
139
|
const GRID_CLASSES = {
|
|
113
140
|
1: 'grid grid-cols-1 gap-4',
|
|
@@ -493,6 +520,41 @@ onMounted(() => {
|
|
|
493
520
|
}
|
|
494
521
|
})
|
|
495
522
|
|
|
523
|
+
const previewSnapshotsBootstrapping = ref(false)
|
|
524
|
+
|
|
525
|
+
const ensurePreviewSnapshots = async () => {
|
|
526
|
+
const orgId = String(edgeGlobal.edgeState.currentOrganization || '').trim()
|
|
527
|
+
if (!orgId)
|
|
528
|
+
return
|
|
529
|
+
|
|
530
|
+
if (previewSnapshotsBootstrapping.value)
|
|
531
|
+
return
|
|
532
|
+
previewSnapshotsBootstrapping.value = true
|
|
533
|
+
|
|
534
|
+
const themesPath = `organizations/${orgId}/themes`
|
|
535
|
+
const sitesPath = `organizations/${orgId}/sites`
|
|
536
|
+
|
|
537
|
+
// Non-blocking bootstrap: never hold page render on snapshot latency.
|
|
538
|
+
try {
|
|
539
|
+
if (!edgeFirebase.data?.[themesPath]) {
|
|
540
|
+
await edgeFirebase.startSnapshot(themesPath)
|
|
541
|
+
}
|
|
542
|
+
if (!edgeFirebase.data?.[sitesPath]) {
|
|
543
|
+
await edgeFirebase.startSnapshot(sitesPath)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.error('Failed to start page preview snapshots', error)
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
previewSnapshotsBootstrapping.value = false
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
onBeforeMount(() => {
|
|
555
|
+
ensurePreviewSnapshots()
|
|
556
|
+
})
|
|
557
|
+
|
|
496
558
|
const editorDocUpdates = (workingDoc) => {
|
|
497
559
|
ensureStructureDefaults(workingDoc, false)
|
|
498
560
|
if (workingDoc?.post || (Array.isArray(workingDoc?.postContent) && workingDoc.postContent.length > 0) || Array.isArray(workingDoc?.postStructure))
|
|
@@ -528,19 +590,204 @@ const selectedThemeId = computed(() => {
|
|
|
528
590
|
return edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`]?.[props.site]?.theme || ''
|
|
529
591
|
})
|
|
530
592
|
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
593
|
+
const themePreviewCache = useState('edge-cms-page-theme-preview-cache', () => ({}))
|
|
594
|
+
const themeCacheKey = computed(() => {
|
|
595
|
+
const orgId = String(edgeGlobal.edgeState.currentOrganization || 'no-org').trim() || 'no-org'
|
|
596
|
+
const siteKey = props.isTemplateSite ? 'templates' : String(props.site || 'no-site').trim() || 'no-site'
|
|
597
|
+
return `${orgId}:${siteKey}`
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
const hydrateThemeCache = () => {
|
|
601
|
+
const cache = themePreviewCache.value?.[themeCacheKey.value] || {}
|
|
602
|
+
return {
|
|
603
|
+
themeId: typeof cache?.themeId === 'string' ? cache.themeId : '',
|
|
604
|
+
theme: cache?.theme && typeof cache.theme === 'object' ? cache.theme : null,
|
|
605
|
+
head: cache?.head && typeof cache.head === 'object' ? cache.head : {},
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const writeThemeCache = (patch = {}) => {
|
|
610
|
+
const current = themePreviewCache.value?.[themeCacheKey.value] || {}
|
|
611
|
+
themePreviewCache.value = {
|
|
612
|
+
...(themePreviewCache.value || {}),
|
|
613
|
+
[themeCacheKey.value]: {
|
|
614
|
+
...current,
|
|
615
|
+
...patch,
|
|
616
|
+
},
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const initialThemeCache = hydrateThemeCache()
|
|
621
|
+
const lastStableThemeId = ref(initialThemeCache.themeId)
|
|
622
|
+
const lastResolvedTheme = ref(initialThemeCache.theme)
|
|
623
|
+
const lastResolvedHead = ref(initialThemeCache.head)
|
|
624
|
+
|
|
625
|
+
const parseThemeDoc = (themeDoc) => {
|
|
626
|
+
const themeContents = themeDoc?.theme || null
|
|
536
627
|
if (!themeContents)
|
|
537
628
|
return null
|
|
629
|
+
const extraCSS = typeof themeDoc?.extraCSS === 'string' ? themeDoc.extraCSS : ''
|
|
538
630
|
try {
|
|
539
|
-
|
|
631
|
+
const parsed = typeof themeContents === 'string' ? JSON.parse(themeContents) : themeContents
|
|
632
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
633
|
+
return null
|
|
634
|
+
return { ...parsed, extraCSS }
|
|
540
635
|
}
|
|
541
|
-
catch
|
|
636
|
+
catch {
|
|
637
|
+
return null
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const parseHeadDoc = (themeDoc) => {
|
|
642
|
+
try {
|
|
643
|
+
const parsed = JSON.parse(themeDoc?.headJSON || '{}')
|
|
644
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
645
|
+
return parsed
|
|
646
|
+
}
|
|
647
|
+
catch {}
|
|
648
|
+
return {}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const applyResolvedTheme = (themeDoc, themeId = '') => {
|
|
652
|
+
const normalizedThemeId = String(themeId || themeDoc?.docId || '').trim()
|
|
653
|
+
if (normalizedThemeId)
|
|
654
|
+
lastStableThemeId.value = normalizedThemeId
|
|
655
|
+
|
|
656
|
+
const parsedTheme = parseThemeDoc(themeDoc)
|
|
657
|
+
if (parsedTheme && typeof parsedTheme === 'object') {
|
|
658
|
+
lastResolvedTheme.value = parsedTheme
|
|
659
|
+
writeThemeCache({ theme: parsedTheme })
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const parsedHead = parseHeadDoc(themeDoc)
|
|
663
|
+
if (parsedHead && typeof parsedHead === 'object') {
|
|
664
|
+
lastResolvedHead.value = parsedHead
|
|
665
|
+
writeThemeCache({ head: parsedHead })
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (normalizedThemeId)
|
|
669
|
+
writeThemeCache({ themeId: normalizedThemeId })
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const themeFallbackLoading = ref(false)
|
|
673
|
+
const loadSiteThemeFallback = async () => {
|
|
674
|
+
if (themeFallbackLoading.value)
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
const orgPath = String(edgeGlobal.edgeState.organizationDocPath || '').trim()
|
|
678
|
+
if (!orgPath)
|
|
679
|
+
return
|
|
680
|
+
|
|
681
|
+
const selectedId = String(selectedThemeId.value || '').trim()
|
|
682
|
+
if (props.isTemplateSite) {
|
|
683
|
+
if (!selectedId)
|
|
684
|
+
return
|
|
685
|
+
const fromSnapshot = edgeFirebase.data?.[`${orgPath}/themes`]?.[selectedId] || null
|
|
686
|
+
if (fromSnapshot)
|
|
687
|
+
applyResolvedTheme(fromSnapshot, selectedId)
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const siteId = String(props.site || '').trim()
|
|
692
|
+
if (!siteId || siteId === 'new')
|
|
693
|
+
return
|
|
694
|
+
|
|
695
|
+
themeFallbackLoading.value = true
|
|
696
|
+
try {
|
|
697
|
+
let themeId = selectedId
|
|
698
|
+
if (!themeId) {
|
|
699
|
+
const siteDoc = await edgeFirebase.getDocData(`${orgPath}/sites`, siteId)
|
|
700
|
+
themeId = String(siteDoc?.theme || '').trim()
|
|
701
|
+
}
|
|
702
|
+
if (!themeId)
|
|
703
|
+
return
|
|
704
|
+
|
|
705
|
+
writeThemeCache({ themeId })
|
|
706
|
+
lastStableThemeId.value = themeId
|
|
707
|
+
|
|
708
|
+
const fromSnapshot = edgeFirebase.data?.[`${orgPath}/themes`]?.[themeId] || null
|
|
709
|
+
if (fromSnapshot) {
|
|
710
|
+
applyResolvedTheme(fromSnapshot, themeId)
|
|
711
|
+
if (lastResolvedTheme.value)
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const themeDoc = await edgeFirebase.getDocData(`${orgPath}/themes`, themeId)
|
|
716
|
+
if (themeDoc)
|
|
717
|
+
applyResolvedTheme(themeDoc, themeId)
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
console.error('Failed to load fallback theme for page preview', error)
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
themeFallbackLoading.value = false
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
watch(
|
|
728
|
+
() => edgeGlobal.edgeState.currentOrganization,
|
|
729
|
+
() => {
|
|
730
|
+
ensurePreviewSnapshots()
|
|
731
|
+
loadSiteThemeFallback()
|
|
732
|
+
},
|
|
733
|
+
{ immediate: true },
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
watch(
|
|
737
|
+
() => [props.site, props.page, props.isTemplateSite],
|
|
738
|
+
() => {
|
|
739
|
+
loadSiteThemeFallback()
|
|
740
|
+
},
|
|
741
|
+
{ immediate: true },
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
watch(
|
|
745
|
+
themeCacheKey,
|
|
746
|
+
() => {
|
|
747
|
+
const hydrated = hydrateThemeCache()
|
|
748
|
+
if (hydrated.themeId)
|
|
749
|
+
lastStableThemeId.value = hydrated.themeId
|
|
750
|
+
if (hydrated.theme && typeof hydrated.theme === 'object')
|
|
751
|
+
lastResolvedTheme.value = hydrated.theme
|
|
752
|
+
if (hydrated.head && typeof hydrated.head === 'object')
|
|
753
|
+
lastResolvedHead.value = hydrated.head
|
|
754
|
+
},
|
|
755
|
+
{ immediate: true },
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
watch(selectedThemeId, (themeId) => {
|
|
759
|
+
const normalized = String(themeId || '').trim()
|
|
760
|
+
if (normalized) {
|
|
761
|
+
lastStableThemeId.value = normalized
|
|
762
|
+
writeThemeCache({ themeId: normalized })
|
|
763
|
+
}
|
|
764
|
+
loadSiteThemeFallback()
|
|
765
|
+
}, { immediate: true })
|
|
766
|
+
|
|
767
|
+
const effectiveThemeId = computed(() => {
|
|
768
|
+
const normalized = String(selectedThemeId.value || '').trim()
|
|
769
|
+
if (normalized)
|
|
770
|
+
return normalized
|
|
771
|
+
return lastStableThemeId.value
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
const parsedTheme = computed(() => {
|
|
775
|
+
const themeId = effectiveThemeId.value
|
|
776
|
+
if (!themeId)
|
|
542
777
|
return null
|
|
778
|
+
const themeDoc = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/themes`]?.[themeId] || null
|
|
779
|
+
return parseThemeDoc(themeDoc)
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
watch(parsedTheme, (nextTheme) => {
|
|
783
|
+
if (nextTheme && typeof nextTheme === 'object') {
|
|
784
|
+
lastResolvedTheme.value = nextTheme
|
|
785
|
+
writeThemeCache({ theme: nextTheme })
|
|
543
786
|
}
|
|
787
|
+
}, { immediate: true, deep: true })
|
|
788
|
+
|
|
789
|
+
const theme = computed(() => {
|
|
790
|
+
return parsedTheme.value || lastResolvedTheme.value || null
|
|
544
791
|
})
|
|
545
792
|
|
|
546
793
|
const themeColorMap = computed(() => {
|
|
@@ -847,14 +1094,20 @@ const addRowAt = (workingDoc, layoutValue = '6', insertIndex = 0, isPost = false
|
|
|
847
1094
|
}
|
|
848
1095
|
|
|
849
1096
|
const headObject = computed(() => {
|
|
850
|
-
const themeId =
|
|
1097
|
+
const themeId = effectiveThemeId.value
|
|
851
1098
|
if (!themeId)
|
|
852
|
-
return {}
|
|
1099
|
+
return lastResolvedHead.value || {}
|
|
853
1100
|
try {
|
|
854
|
-
|
|
1101
|
+
const parsedHead = parseHeadDoc(edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/themes`]?.[themeId] || null)
|
|
1102
|
+
if (parsedHead && typeof parsedHead === 'object') {
|
|
1103
|
+
lastResolvedHead.value = parsedHead
|
|
1104
|
+
writeThemeCache({ head: parsedHead })
|
|
1105
|
+
return parsedHead
|
|
1106
|
+
}
|
|
1107
|
+
return lastResolvedHead.value || {}
|
|
855
1108
|
}
|
|
856
1109
|
catch (e) {
|
|
857
|
-
return {}
|
|
1110
|
+
return lastResolvedHead.value || {}
|
|
858
1111
|
}
|
|
859
1112
|
})
|
|
860
1113
|
|
|
@@ -918,6 +1171,397 @@ const currentPage = computed(() => {
|
|
|
918
1171
|
return edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/pages`]?.[props.page] || null
|
|
919
1172
|
})
|
|
920
1173
|
|
|
1174
|
+
const pagesCollectionPath = computed(() => `${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/pages`)
|
|
1175
|
+
const pagesCollection = computed(() => edgeFirebase.data?.[pagesCollectionPath.value] || {})
|
|
1176
|
+
const pageEditorBasePath = computed(() => (props.isTemplateSite ? '/app/dashboard/templates' : `/app/dashboard/sites/${props.site}`))
|
|
1177
|
+
const INVALID_PAGE_IMPORT_MESSAGE = 'Invalid file. Please import a valid page file.'
|
|
1178
|
+
|
|
1179
|
+
const downloadJsonFile = (payload, filename) => {
|
|
1180
|
+
if (typeof window === 'undefined')
|
|
1181
|
+
return
|
|
1182
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
|
|
1183
|
+
const objectUrl = URL.createObjectURL(blob)
|
|
1184
|
+
const anchor = document.createElement('a')
|
|
1185
|
+
anchor.href = objectUrl
|
|
1186
|
+
anchor.download = filename
|
|
1187
|
+
document.body.appendChild(anchor)
|
|
1188
|
+
anchor.click()
|
|
1189
|
+
anchor.remove()
|
|
1190
|
+
URL.revokeObjectURL(objectUrl)
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const readTextFile = file => new Promise((resolve, reject) => {
|
|
1194
|
+
if (typeof FileReader === 'undefined') {
|
|
1195
|
+
reject(new Error('File import is only available in the browser.'))
|
|
1196
|
+
return
|
|
1197
|
+
}
|
|
1198
|
+
const reader = new FileReader()
|
|
1199
|
+
reader.onload = () => resolve(String(reader.result || ''))
|
|
1200
|
+
reader.onerror = () => reject(new Error('Could not read the selected file.'))
|
|
1201
|
+
reader.readAsText(file)
|
|
1202
|
+
})
|
|
1203
|
+
|
|
1204
|
+
const normalizeImportedDoc = (payload, fallbackDocId = '') => {
|
|
1205
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
1206
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1207
|
+
|
|
1208
|
+
if (payload.document && typeof payload.document === 'object' && !Array.isArray(payload.document)) {
|
|
1209
|
+
const normalized = { ...payload.document }
|
|
1210
|
+
if (!normalized.docId && payload.docId)
|
|
1211
|
+
normalized.docId = payload.docId
|
|
1212
|
+
if (!normalized.docId && fallbackDocId)
|
|
1213
|
+
normalized.docId = fallbackDocId
|
|
1214
|
+
return normalized
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const normalized = { ...payload }
|
|
1218
|
+
if (!normalized.docId && fallbackDocId)
|
|
1219
|
+
normalized.docId = fallbackDocId
|
|
1220
|
+
return normalized
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const isPlainObject = value => !!value && typeof value === 'object' && !Array.isArray(value)
|
|
1224
|
+
|
|
1225
|
+
const cloneSchemaValue = (value) => {
|
|
1226
|
+
if (isPlainObject(value) || Array.isArray(value))
|
|
1227
|
+
return edgeGlobal.dupObject(value)
|
|
1228
|
+
return value
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const getDocDefaultsFromSchema = (schema = {}) => {
|
|
1232
|
+
const defaults = {}
|
|
1233
|
+
for (const [key, schemaEntry] of Object.entries(schema || {})) {
|
|
1234
|
+
const hasValueProp = isPlainObject(schemaEntry) && Object.prototype.hasOwnProperty.call(schemaEntry, 'value')
|
|
1235
|
+
const baseValue = hasValueProp ? schemaEntry.value : schemaEntry
|
|
1236
|
+
defaults[key] = cloneSchemaValue(baseValue)
|
|
1237
|
+
}
|
|
1238
|
+
return defaults
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const getPageDocDefaults = () => getDocDefaultsFromSchema(state.newDocs?.pages || {})
|
|
1242
|
+
|
|
1243
|
+
const isBlankString = value => String(value || '').trim() === ''
|
|
1244
|
+
|
|
1245
|
+
const applyImportedPageSeoDefaults = (doc) => {
|
|
1246
|
+
if (!isPlainObject(doc))
|
|
1247
|
+
return doc
|
|
1248
|
+
|
|
1249
|
+
if (isBlankString(doc.structuredData))
|
|
1250
|
+
doc.structuredData = buildPageStructuredData()
|
|
1251
|
+
|
|
1252
|
+
if (doc.post && isBlankString(doc.postStructuredData))
|
|
1253
|
+
doc.postStructuredData = doc.structuredData || buildPageStructuredData()
|
|
1254
|
+
|
|
1255
|
+
return doc
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const validateImportedPageDoc = (doc) => {
|
|
1259
|
+
if (!isPlainObject(doc))
|
|
1260
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1261
|
+
|
|
1262
|
+
const requiredKeys = Object.keys(state.newDocs?.pages || {})
|
|
1263
|
+
const missing = requiredKeys.filter(key => !Object.prototype.hasOwnProperty.call(doc, key))
|
|
1264
|
+
if (missing.length)
|
|
1265
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1266
|
+
|
|
1267
|
+
return doc
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const normalizeMenusForImport = (menus) => {
|
|
1271
|
+
const normalized = isPlainObject(menus) ? edgeGlobal.dupObject(menus) : {}
|
|
1272
|
+
if (!Array.isArray(normalized['Site Root']))
|
|
1273
|
+
normalized['Site Root'] = []
|
|
1274
|
+
if (!Array.isArray(normalized['Not In Menu']))
|
|
1275
|
+
normalized['Not In Menu'] = []
|
|
1276
|
+
return normalized
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const walkMenuEntries = (items, callback) => {
|
|
1280
|
+
if (!Array.isArray(items))
|
|
1281
|
+
return
|
|
1282
|
+
for (const entry of items) {
|
|
1283
|
+
if (!entry || typeof entry !== 'object')
|
|
1284
|
+
continue
|
|
1285
|
+
callback(entry)
|
|
1286
|
+
if (isPlainObject(entry.item)) {
|
|
1287
|
+
for (const nested of Object.values(entry.item)) {
|
|
1288
|
+
if (Array.isArray(nested))
|
|
1289
|
+
walkMenuEntries(nested, callback)
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const menuIncludesDocId = (menus, docId) => {
|
|
1296
|
+
let found = false
|
|
1297
|
+
const checkEntry = (entry) => {
|
|
1298
|
+
if (found)
|
|
1299
|
+
return
|
|
1300
|
+
if (typeof entry?.item === 'string' && entry.item === docId)
|
|
1301
|
+
found = true
|
|
1302
|
+
}
|
|
1303
|
+
for (const menuItems of Object.values(menus || {})) {
|
|
1304
|
+
walkMenuEntries(menuItems, checkEntry)
|
|
1305
|
+
if (found)
|
|
1306
|
+
return true
|
|
1307
|
+
}
|
|
1308
|
+
return false
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const collectMenuPageNames = (menus) => {
|
|
1312
|
+
const names = new Set()
|
|
1313
|
+
const collectEntry = (entry) => {
|
|
1314
|
+
if (typeof entry?.item !== 'string')
|
|
1315
|
+
return
|
|
1316
|
+
const name = String(entry?.name || '').trim()
|
|
1317
|
+
if (name)
|
|
1318
|
+
names.add(name)
|
|
1319
|
+
}
|
|
1320
|
+
for (const menuItems of Object.values(menus || {}))
|
|
1321
|
+
walkMenuEntries(menuItems, collectEntry)
|
|
1322
|
+
return names
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const slugifyMenuPageName = (value) => {
|
|
1326
|
+
return String(value || '')
|
|
1327
|
+
.trim()
|
|
1328
|
+
.toLowerCase()
|
|
1329
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1330
|
+
.replace(/(^-|-$)+/g, '') || 'page'
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const makeUniqueMenuPageName = (value, existingNames = new Set()) => {
|
|
1334
|
+
const base = slugifyMenuPageName(value)
|
|
1335
|
+
let candidate = base
|
|
1336
|
+
let suffix = 2
|
|
1337
|
+
while (existingNames.has(candidate)) {
|
|
1338
|
+
candidate = `${base}-${suffix}`
|
|
1339
|
+
suffix += 1
|
|
1340
|
+
}
|
|
1341
|
+
return candidate
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const addImportedPageToSiteMenu = async (docId, pageName = '') => {
|
|
1345
|
+
const nextDocId = String(docId || '').trim()
|
|
1346
|
+
if (!nextDocId)
|
|
1347
|
+
return
|
|
1348
|
+
const siteId = String(props.site || '').trim()
|
|
1349
|
+
if (!siteId)
|
|
1350
|
+
return
|
|
1351
|
+
|
|
1352
|
+
const sitesCollectionPath = `${edgeGlobal.edgeState.organizationDocPath}/sites`
|
|
1353
|
+
const siteDoc = edgeFirebase.data?.[sitesCollectionPath]?.[siteId] || {}
|
|
1354
|
+
const menus = normalizeMenusForImport(siteDoc?.menus)
|
|
1355
|
+
if (menuIncludesDocId(menus, nextDocId))
|
|
1356
|
+
return
|
|
1357
|
+
|
|
1358
|
+
const existingNames = collectMenuPageNames(menus)
|
|
1359
|
+
const menuName = makeUniqueMenuPageName(pageName || nextDocId, existingNames)
|
|
1360
|
+
menus['Site Root'].push({ name: menuName, item: nextDocId })
|
|
1361
|
+
|
|
1362
|
+
const results = await edgeFirebase.changeDoc(sitesCollectionPath, siteId, { menus })
|
|
1363
|
+
if (results?.success === false)
|
|
1364
|
+
throw new Error('Could not save updated site menu.')
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const makeRandomPageDocId = (docsMap = {}) => {
|
|
1368
|
+
let nextDocId = String(edgeGlobal.generateShortId() || '').trim()
|
|
1369
|
+
while (!nextDocId || docsMap[nextDocId])
|
|
1370
|
+
nextDocId = String(edgeGlobal.generateShortId() || '').trim()
|
|
1371
|
+
return nextDocId
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const makeImportedPageNameForNew = (baseName, docsMap = {}) => {
|
|
1375
|
+
const normalizedBase = String(baseName || '').trim() || 'page'
|
|
1376
|
+
const existingNames = new Set(
|
|
1377
|
+
Object.values(docsMap || {})
|
|
1378
|
+
.map(doc => String(doc?.name || '').trim().toLowerCase())
|
|
1379
|
+
.filter(Boolean),
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
let suffix = 1
|
|
1383
|
+
let candidate = `${normalizedBase}-${suffix}`
|
|
1384
|
+
while (existingNames.has(candidate.toLowerCase())) {
|
|
1385
|
+
suffix += 1
|
|
1386
|
+
candidate = `${normalizedBase}-${suffix}`
|
|
1387
|
+
}
|
|
1388
|
+
return candidate
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const requestPageImportDocId = (initialValue = '') => {
|
|
1392
|
+
state.importDocIdValue = String(initialValue || '')
|
|
1393
|
+
state.importDocIdDialogOpen = true
|
|
1394
|
+
return new Promise((resolve) => {
|
|
1395
|
+
pageImportDocIdResolver.value = resolve
|
|
1396
|
+
})
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const resolvePageImportDocId = (value = '') => {
|
|
1400
|
+
const resolver = pageImportDocIdResolver.value
|
|
1401
|
+
pageImportDocIdResolver.value = null
|
|
1402
|
+
state.importDocIdDialogOpen = false
|
|
1403
|
+
if (resolver)
|
|
1404
|
+
resolver(String(value || '').trim())
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const requestPageImportConflict = (docId) => {
|
|
1408
|
+
state.importConflictDocId = String(docId || '')
|
|
1409
|
+
state.importConflictDialogOpen = true
|
|
1410
|
+
return new Promise((resolve) => {
|
|
1411
|
+
pageImportConflictResolver.value = resolve
|
|
1412
|
+
})
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const resolvePageImportConflict = (action = 'cancel') => {
|
|
1416
|
+
const resolver = pageImportConflictResolver.value
|
|
1417
|
+
pageImportConflictResolver.value = null
|
|
1418
|
+
state.importConflictDialogOpen = false
|
|
1419
|
+
if (resolver)
|
|
1420
|
+
resolver(action)
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
watch(() => state.importDocIdDialogOpen, (open) => {
|
|
1424
|
+
if (!open && pageImportDocIdResolver.value) {
|
|
1425
|
+
const resolver = pageImportDocIdResolver.value
|
|
1426
|
+
pageImportDocIdResolver.value = null
|
|
1427
|
+
resolver('')
|
|
1428
|
+
}
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
watch(() => state.importConflictDialogOpen, (open) => {
|
|
1432
|
+
if (!open && pageImportConflictResolver.value) {
|
|
1433
|
+
const resolver = pageImportConflictResolver.value
|
|
1434
|
+
pageImportConflictResolver.value = null
|
|
1435
|
+
resolver('cancel')
|
|
1436
|
+
}
|
|
1437
|
+
})
|
|
1438
|
+
|
|
1439
|
+
const getImportDocId = async (incomingDoc, fallbackDocId = '') => {
|
|
1440
|
+
let nextDocId = String(incomingDoc?.docId || '').trim()
|
|
1441
|
+
if (!nextDocId)
|
|
1442
|
+
nextDocId = await requestPageImportDocId(fallbackDocId)
|
|
1443
|
+
if (!nextDocId)
|
|
1444
|
+
throw new Error('Import canceled. A docId is required.')
|
|
1445
|
+
if (nextDocId.includes('/'))
|
|
1446
|
+
throw new Error('docId cannot include "/".')
|
|
1447
|
+
return nextDocId
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const notifySuccess = (message) => {
|
|
1451
|
+
edgeFirebase?.toast?.success?.(message)
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const notifyError = (message) => {
|
|
1455
|
+
edgeFirebase?.toast?.error?.(message)
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const openImportErrorDialog = (message) => {
|
|
1459
|
+
state.importErrorMessage = String(message || 'Failed to import page JSON.')
|
|
1460
|
+
state.importErrorDialogOpen = true
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const exportCurrentPage = () => {
|
|
1464
|
+
const doc = currentPage.value
|
|
1465
|
+
if (!doc || !props.page || props.page === 'new') {
|
|
1466
|
+
notifyError('Save this page before exporting.')
|
|
1467
|
+
return
|
|
1468
|
+
}
|
|
1469
|
+
const docId = String(doc.docId || props.page).trim()
|
|
1470
|
+
const exportPayload = { ...getPageDocDefaults(), ...doc, docId }
|
|
1471
|
+
downloadJsonFile(exportPayload, `page-${docId}.json`)
|
|
1472
|
+
notifySuccess(`Exported page "${docId}".`)
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const triggerPageImport = () => {
|
|
1476
|
+
pageImportInputRef.value?.click()
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const importSinglePageFile = async (file, existingPages = {}, fallbackDocId = '') => {
|
|
1480
|
+
const fileText = await readTextFile(file)
|
|
1481
|
+
const parsed = JSON.parse(fileText)
|
|
1482
|
+
const importedDoc = applyImportedPageSeoDefaults(validateImportedPageDoc(normalizeImportedDoc(parsed, fallbackDocId)))
|
|
1483
|
+
const incomingDocId = await getImportDocId(importedDoc, fallbackDocId)
|
|
1484
|
+
let targetDocId = incomingDocId
|
|
1485
|
+
let importDecision = 'create'
|
|
1486
|
+
|
|
1487
|
+
if (existingPages[targetDocId]) {
|
|
1488
|
+
const decision = await requestPageImportConflict(targetDocId)
|
|
1489
|
+
if (decision === 'cancel')
|
|
1490
|
+
return ''
|
|
1491
|
+
if (decision === 'new') {
|
|
1492
|
+
targetDocId = makeRandomPageDocId(existingPages)
|
|
1493
|
+
importedDoc.name = makeImportedPageNameForNew(importedDoc.name || incomingDocId, existingPages)
|
|
1494
|
+
importDecision = 'new'
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
importDecision = 'overwrite'
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const isCreatingNewPage = !existingPages[targetDocId]
|
|
1502
|
+
const payload = { ...getPageDocDefaults(), ...importedDoc, docId: targetDocId }
|
|
1503
|
+
await edgeFirebase.storeDoc(pagesCollectionPath.value, payload, targetDocId)
|
|
1504
|
+
existingPages[targetDocId] = payload
|
|
1505
|
+
|
|
1506
|
+
if (isCreatingNewPage) {
|
|
1507
|
+
try {
|
|
1508
|
+
await addImportedPageToSiteMenu(targetDocId, importedDoc.name)
|
|
1509
|
+
}
|
|
1510
|
+
catch (menuError) {
|
|
1511
|
+
console.error('Imported page but failed to update site menu', menuError)
|
|
1512
|
+
openImportErrorDialog('Imported page, but could not add it to Site Menu automatically.')
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
if (importDecision === 'overwrite')
|
|
1517
|
+
notifySuccess(`Overwrote page "${targetDocId}".`)
|
|
1518
|
+
else if (importDecision === 'new')
|
|
1519
|
+
notifySuccess(`Imported page as new "${targetDocId}".`)
|
|
1520
|
+
else
|
|
1521
|
+
notifySuccess(`Imported page "${targetDocId}".`)
|
|
1522
|
+
|
|
1523
|
+
return targetDocId
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const handlePageImport = async (event) => {
|
|
1527
|
+
const input = event?.target
|
|
1528
|
+
const files = Array.from(input?.files || [])
|
|
1529
|
+
if (!files.length)
|
|
1530
|
+
return
|
|
1531
|
+
|
|
1532
|
+
state.importingJson = true
|
|
1533
|
+
const fallbackDocId = props.page !== 'new' ? props.page : ''
|
|
1534
|
+
const existingPages = { ...(pagesCollection.value || {}) }
|
|
1535
|
+
let lastImportedDocId = ''
|
|
1536
|
+
try {
|
|
1537
|
+
for (const file of files) {
|
|
1538
|
+
try {
|
|
1539
|
+
const importedDocId = await importSinglePageFile(file, existingPages, fallbackDocId)
|
|
1540
|
+
if (importedDocId)
|
|
1541
|
+
lastImportedDocId = importedDocId
|
|
1542
|
+
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
console.error('Failed to import page JSON', error)
|
|
1545
|
+
const message = error?.message || 'Failed to import page JSON.'
|
|
1546
|
+
if (/^Import canceled\./i.test(message))
|
|
1547
|
+
continue
|
|
1548
|
+
if (error instanceof SyntaxError || message === INVALID_PAGE_IMPORT_MESSAGE)
|
|
1549
|
+
openImportErrorDialog(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1550
|
+
else
|
|
1551
|
+
openImportErrorDialog(message)
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
if (files.length === 1 && lastImportedDocId && lastImportedDocId !== props.page)
|
|
1556
|
+
await router.push(`${pageEditorBasePath.value}/${lastImportedDocId}`)
|
|
1557
|
+
}
|
|
1558
|
+
finally {
|
|
1559
|
+
state.importingJson = false
|
|
1560
|
+
if (input)
|
|
1561
|
+
input.value = ''
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
921
1565
|
watch (currentPage, (newPage) => {
|
|
922
1566
|
state.workingDoc.last_updated = newPage?.last_updated
|
|
923
1567
|
state.workingDoc.metaTitle = newPage?.metaTitle
|
|
@@ -1141,7 +1785,7 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1141
1785
|
@unsaved-changes="hasUnsavedChanges"
|
|
1142
1786
|
>
|
|
1143
1787
|
<template #header="slotProps">
|
|
1144
|
-
<div class="relative flex items-center
|
|
1788
|
+
<div class="relative flex items-center p-2 justify-between sticky top-0 z-50 bg-gray-100 rounded h-[50px]">
|
|
1145
1789
|
<span class="text-lg font-semibold whitespace-nowrap pr-1">{{ pageName }}</span>
|
|
1146
1790
|
|
|
1147
1791
|
<div class="flex w-full items-center">
|
|
@@ -1188,6 +1832,21 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1188
1832
|
</div>
|
|
1189
1833
|
<div class="w-full border-t border-border" aria-hidden="true" />
|
|
1190
1834
|
|
|
1835
|
+
<div class="flex items-center gap-2 px-3">
|
|
1836
|
+
<edge-shad-button
|
|
1837
|
+
type="button"
|
|
1838
|
+
size="icon"
|
|
1839
|
+
variant="outline"
|
|
1840
|
+
class="h-8 w-8"
|
|
1841
|
+
:disabled="!currentPage || !props.page || props.page === 'new'"
|
|
1842
|
+
title="Export Page"
|
|
1843
|
+
aria-label="Export Page"
|
|
1844
|
+
@click="exportCurrentPage"
|
|
1845
|
+
>
|
|
1846
|
+
<Download class="w-3.5 h-3.5" />
|
|
1847
|
+
</edge-shad-button>
|
|
1848
|
+
</div>
|
|
1849
|
+
|
|
1191
1850
|
<div class="flex items-center gap-1 pr-3">
|
|
1192
1851
|
<span class="text-[11px] uppercase tracking-wide text-muted-foreground">Viewport</span>
|
|
1193
1852
|
<edge-shad-button
|
|
@@ -1276,10 +1935,10 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1276
1935
|
<TabsContent value="list">
|
|
1277
1936
|
<Separator class="my-4" />
|
|
1278
1937
|
<div
|
|
1279
|
-
:key="
|
|
1280
|
-
class="w-full mx-auto bg-card border border-border
|
|
1281
|
-
:class="{ 'transition-all duration-300': !state.editMode }"
|
|
1282
|
-
:style="
|
|
1938
|
+
:key="`${pagePreviewRenderKey}:list`"
|
|
1939
|
+
class="w-full mx-auto bg-card border border-border shadow-sm md:shadow-md p-0 space-y-6"
|
|
1940
|
+
:class="[{ 'transition-all duration-300': !state.editMode }, state.editMode ? 'rounded-lg' : 'rounded-none']"
|
|
1941
|
+
:style="previewViewportContainStyle"
|
|
1283
1942
|
>
|
|
1284
1943
|
<edge-button-divider v-if="state.editMode" class="my-2">
|
|
1285
1944
|
<Popover v-model:open="state.addRowPopoverOpen.listTop">
|
|
@@ -1419,9 +2078,11 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1419
2078
|
<div :key="blockId" class="relative group">
|
|
1420
2079
|
<edge-cms-block
|
|
1421
2080
|
v-if="blockIndex(slotProps.workingDoc, blockId, false) !== -1"
|
|
2081
|
+
:key="`${pagePreviewRenderKey}:${blockId}:${effectiveThemeId}:list`"
|
|
1422
2082
|
v-model="slotProps.workingDoc.content[blockIndex(slotProps.workingDoc, blockId, false)]"
|
|
1423
2083
|
:site-id="props.site"
|
|
1424
2084
|
:edit-mode="state.editMode"
|
|
2085
|
+
:contain-fixed="true"
|
|
1425
2086
|
:viewport-mode="previewViewportMode"
|
|
1426
2087
|
:block-id="blockId"
|
|
1427
2088
|
:theme="theme"
|
|
@@ -1527,10 +2188,10 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1527
2188
|
<TabsContent value="post">
|
|
1528
2189
|
<Separator class="my-4" />
|
|
1529
2190
|
<div
|
|
1530
|
-
:key="`${
|
|
1531
|
-
class="w-full mx-auto bg-card border border-border
|
|
1532
|
-
:class="{ 'transition-all duration-300': !state.editMode }"
|
|
1533
|
-
:style="
|
|
2191
|
+
:key="`${pagePreviewRenderKey}:post`"
|
|
2192
|
+
class="w-full mx-auto bg-card border border-border shadow-sm md:shadow-md p-4 space-y-6"
|
|
2193
|
+
:class="[{ 'transition-all duration-300': !state.editMode }, state.editMode ? 'rounded-lg' : 'rounded-none']"
|
|
2194
|
+
:style="previewViewportContainStyle"
|
|
1534
2195
|
>
|
|
1535
2196
|
<edge-button-divider v-if="state.editMode" class="my-2">
|
|
1536
2197
|
<Popover v-model:open="state.addRowPopoverOpen.postTop">
|
|
@@ -1670,8 +2331,10 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1670
2331
|
<div :key="blockId" class="relative group">
|
|
1671
2332
|
<edge-cms-block
|
|
1672
2333
|
v-if="blockIndex(slotProps.workingDoc, blockId, true) !== -1"
|
|
2334
|
+
:key="`${pagePreviewRenderKey}:${blockId}:${effectiveThemeId}:post`"
|
|
1673
2335
|
v-model="slotProps.workingDoc.postContent[blockIndex(slotProps.workingDoc, blockId, true)]"
|
|
1674
2336
|
:edit-mode="state.editMode"
|
|
2337
|
+
:contain-fixed="true"
|
|
1675
2338
|
:viewport-mode="previewViewportMode"
|
|
1676
2339
|
:block-id="blockId"
|
|
1677
2340
|
:theme="theme"
|
|
@@ -1846,6 +2509,72 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1846
2509
|
</Sheet>
|
|
1847
2510
|
</template>
|
|
1848
2511
|
</edge-editor>
|
|
2512
|
+
<edge-shad-dialog v-model="state.importDocIdDialogOpen">
|
|
2513
|
+
<DialogContent class="pt-8">
|
|
2514
|
+
<DialogHeader>
|
|
2515
|
+
<DialogTitle class="text-left">
|
|
2516
|
+
Enter Page Doc ID
|
|
2517
|
+
</DialogTitle>
|
|
2518
|
+
<DialogDescription>
|
|
2519
|
+
This JSON file does not include a <code>docId</code>. Enter the doc ID you want to import into this site.
|
|
2520
|
+
</DialogDescription>
|
|
2521
|
+
</DialogHeader>
|
|
2522
|
+
<edge-shad-input
|
|
2523
|
+
v-model="state.importDocIdValue"
|
|
2524
|
+
name="page-import-doc-id"
|
|
2525
|
+
label="Doc ID"
|
|
2526
|
+
placeholder="example-page-id"
|
|
2527
|
+
/>
|
|
2528
|
+
<DialogFooter class="pt-2 flex justify-between">
|
|
2529
|
+
<edge-shad-button variant="outline" @click="resolvePageImportDocId('')">
|
|
2530
|
+
Cancel
|
|
2531
|
+
</edge-shad-button>
|
|
2532
|
+
<edge-shad-button @click="resolvePageImportDocId(state.importDocIdValue)">
|
|
2533
|
+
Continue
|
|
2534
|
+
</edge-shad-button>
|
|
2535
|
+
</DialogFooter>
|
|
2536
|
+
</DialogContent>
|
|
2537
|
+
</edge-shad-dialog>
|
|
2538
|
+
<edge-shad-dialog v-model="state.importConflictDialogOpen">
|
|
2539
|
+
<DialogContent class="pt-8">
|
|
2540
|
+
<DialogHeader>
|
|
2541
|
+
<DialogTitle class="text-left">
|
|
2542
|
+
Page Already Exists
|
|
2543
|
+
</DialogTitle>
|
|
2544
|
+
<DialogDescription>
|
|
2545
|
+
<code>{{ state.importConflictDocId }}</code> already exists in this site. Choose to overwrite it or import as a new page.
|
|
2546
|
+
</DialogDescription>
|
|
2547
|
+
</DialogHeader>
|
|
2548
|
+
<DialogFooter class="pt-2 flex justify-between">
|
|
2549
|
+
<edge-shad-button variant="outline" @click="resolvePageImportConflict('cancel')">
|
|
2550
|
+
Cancel
|
|
2551
|
+
</edge-shad-button>
|
|
2552
|
+
<edge-shad-button variant="outline" @click="resolvePageImportConflict('new')">
|
|
2553
|
+
Add As New
|
|
2554
|
+
</edge-shad-button>
|
|
2555
|
+
<edge-shad-button @click="resolvePageImportConflict('overwrite')">
|
|
2556
|
+
Overwrite
|
|
2557
|
+
</edge-shad-button>
|
|
2558
|
+
</DialogFooter>
|
|
2559
|
+
</DialogContent>
|
|
2560
|
+
</edge-shad-dialog>
|
|
2561
|
+
<edge-shad-dialog v-model="state.importErrorDialogOpen">
|
|
2562
|
+
<DialogContent class="pt-8">
|
|
2563
|
+
<DialogHeader>
|
|
2564
|
+
<DialogTitle class="text-left">
|
|
2565
|
+
Import Failed
|
|
2566
|
+
</DialogTitle>
|
|
2567
|
+
<DialogDescription class="text-left">
|
|
2568
|
+
{{ state.importErrorMessage }}
|
|
2569
|
+
</DialogDescription>
|
|
2570
|
+
</DialogHeader>
|
|
2571
|
+
<DialogFooter class="pt-2">
|
|
2572
|
+
<edge-shad-button @click="state.importErrorDialogOpen = false">
|
|
2573
|
+
Close
|
|
2574
|
+
</edge-shad-button>
|
|
2575
|
+
</DialogFooter>
|
|
2576
|
+
</DialogContent>
|
|
2577
|
+
</edge-shad-dialog>
|
|
1849
2578
|
<edge-shad-dialog v-model="state.showUnpublishedChangesDialog">
|
|
1850
2579
|
<DialogContent class="max-w-2xl">
|
|
1851
2580
|
<DialogHeader>
|