@edgedev/create-edge-app 1.2.33 → 1.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/agents.md +95 -2
- package/deploy.sh +136 -0
- package/edge/components/cms/block.vue +977 -305
- package/edge/components/cms/blockApi.vue +3 -3
- package/edge/components/cms/blockEditor.vue +688 -86
- package/edge/components/cms/blockPicker.vue +31 -5
- package/edge/components/cms/blockRender.vue +3 -3
- package/edge/components/cms/blocksManager.vue +790 -82
- package/edge/components/cms/codeEditor.vue +15 -6
- package/edge/components/cms/fontUpload.vue +318 -2
- package/edge/components/cms/htmlContent.vue +825 -93
- package/edge/components/cms/init_blocks/contact_us.html +55 -47
- package/edge/components/cms/init_blocks/newsletter.html +56 -96
- package/edge/components/cms/menu.vue +96 -34
- package/edge/components/cms/page.vue +902 -58
- package/edge/components/cms/posts.vue +13 -4
- package/edge/components/cms/site.vue +638 -87
- package/edge/components/cms/siteSettingsForm.vue +19 -9
- package/edge/components/cms/sitesManager.vue +5 -4
- package/edge/components/cms/themeDefaultMenu.vue +20 -2
- 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/siteSettingsTemplate.js +2 -0
- 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/edge-pull.sh +16 -2
- package/edge-push.sh +9 -1
- package/edge-remote.sh +20 -0
- package/edge-status.sh +9 -5
- package/edge-update-all.sh +127 -0
- package/firebase.json +4 -0
- package/nuxt.config.ts +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="js">
|
|
2
2
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
3
3
|
import * as z from 'zod'
|
|
4
|
-
import { ArrowLeft, CircleAlert, FileCheck, FilePenLine, FileStack, FolderCog, FolderDown, FolderUp, FolderX, Inbox, Loader2, Mail, MailOpen, MoreHorizontal } from 'lucide-vue-next'
|
|
4
|
+
import { ArrowLeft, CircleAlert, FileCheck, FilePenLine, FileStack, FolderCog, FolderDown, FolderUp, FolderX, Inbox, Loader2, Mail, MailOpen, MoreHorizontal, Upload } from 'lucide-vue-next'
|
|
5
5
|
import { useStructuredDataTemplates } from '@/edge/composables/structuredDataTemplates'
|
|
6
6
|
|
|
7
7
|
const props = defineProps({
|
|
@@ -67,6 +67,16 @@ const state = reactive({
|
|
|
67
67
|
userFilter: 'all',
|
|
68
68
|
newDocs: {
|
|
69
69
|
sites: createSiteSettingsNewDocSchema(),
|
|
70
|
+
pages: {
|
|
71
|
+
name: { bindings: { 'field-type': 'text', 'label': 'Name', 'helper': 'Name' }, cols: '12', value: '' },
|
|
72
|
+
content: { value: [] },
|
|
73
|
+
postContent: { value: [] },
|
|
74
|
+
structure: { value: [] },
|
|
75
|
+
postStructure: { value: [] },
|
|
76
|
+
metaTitle: { value: '' },
|
|
77
|
+
metaDescription: { value: '' },
|
|
78
|
+
structuredData: { value: buildPageStructuredData() },
|
|
79
|
+
},
|
|
70
80
|
},
|
|
71
81
|
mounted: false,
|
|
72
82
|
page: {},
|
|
@@ -81,8 +91,19 @@ const state = reactive({
|
|
|
81
91
|
submissionFilter: '',
|
|
82
92
|
selectedSubmissionId: '',
|
|
83
93
|
publishSiteLoading: false,
|
|
94
|
+
importingPages: false,
|
|
95
|
+
importPageDocIdDialogOpen: false,
|
|
96
|
+
importPageDocIdValue: '',
|
|
97
|
+
importPageConflictDialogOpen: false,
|
|
98
|
+
importPageConflictDocId: '',
|
|
99
|
+
importPageErrorDialogOpen: false,
|
|
100
|
+
importPageErrorMessage: '',
|
|
84
101
|
})
|
|
85
102
|
|
|
103
|
+
const pageImportInputRef = ref(null)
|
|
104
|
+
const pageImportDocIdResolver = ref(null)
|
|
105
|
+
const pageImportConflictResolver = ref(null)
|
|
106
|
+
|
|
86
107
|
const pageInit = {
|
|
87
108
|
name: '',
|
|
88
109
|
content: [],
|
|
@@ -124,6 +145,7 @@ const schemas = {
|
|
|
124
145
|
trackingFacebookPixel: z.string().optional(),
|
|
125
146
|
trackingGoogleAnalytics: z.string().optional(),
|
|
126
147
|
trackingAdroll: z.string().optional(),
|
|
148
|
+
sureFeedURL: z.string().optional(),
|
|
127
149
|
socialFacebook: z.string().optional(),
|
|
128
150
|
socialInstagram: z.string().optional(),
|
|
129
151
|
socialTwitter: z.string().optional(),
|
|
@@ -154,6 +176,44 @@ const canCreateSite = computed(() => {
|
|
|
154
176
|
return true
|
|
155
177
|
return isOrgAdmin.value
|
|
156
178
|
})
|
|
179
|
+
const cmsMultiOrg = useState('cmsMultiOrg', () => false)
|
|
180
|
+
const canEditSiteSettings = computed(() => {
|
|
181
|
+
if (!cmsMultiOrg.value)
|
|
182
|
+
return true
|
|
183
|
+
return currentOrgRoleName.value === 'admin' || currentOrgRoleName.value === 'site admin'
|
|
184
|
+
})
|
|
185
|
+
const useMenuPublishLabels = computed(() => {
|
|
186
|
+
return cmsMultiOrg.value && !canEditSiteSettings.value
|
|
187
|
+
})
|
|
188
|
+
const cmsSiteTabs = useState('cmsSiteTabs', () => ({
|
|
189
|
+
pages: true,
|
|
190
|
+
posts: true,
|
|
191
|
+
inbox: true,
|
|
192
|
+
}))
|
|
193
|
+
const cmsTabAccess = computed(() => {
|
|
194
|
+
const normalized = {
|
|
195
|
+
pages: cmsSiteTabs.value?.pages !== false,
|
|
196
|
+
posts: cmsSiteTabs.value?.posts !== false,
|
|
197
|
+
inbox: cmsSiteTabs.value?.inbox !== false,
|
|
198
|
+
}
|
|
199
|
+
if (!normalized.pages && !normalized.posts && !normalized.inbox) {
|
|
200
|
+
normalized.inbox = true
|
|
201
|
+
}
|
|
202
|
+
return normalized
|
|
203
|
+
})
|
|
204
|
+
const canViewPagesTab = computed(() => cmsTabAccess.value.pages)
|
|
205
|
+
const canViewPostsTab = computed(() => cmsTabAccess.value.posts)
|
|
206
|
+
const canViewInboxTab = computed(() => cmsTabAccess.value.inbox)
|
|
207
|
+
const hidePublishStatusAndActions = computed(() => cmsMultiOrg.value && !canViewPagesTab.value)
|
|
208
|
+
const defaultViewMode = computed(() => {
|
|
209
|
+
if (canViewPagesTab.value)
|
|
210
|
+
return 'pages'
|
|
211
|
+
if (canViewPostsTab.value)
|
|
212
|
+
return 'posts'
|
|
213
|
+
if (canViewInboxTab.value)
|
|
214
|
+
return 'submissions'
|
|
215
|
+
return 'pages'
|
|
216
|
+
})
|
|
157
217
|
|
|
158
218
|
const siteData = computed(() => {
|
|
159
219
|
return edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`]?.[props.site] || {}
|
|
@@ -580,8 +640,10 @@ const buildMenusFromDefaultPages = (defaultPages = []) => {
|
|
|
580
640
|
if (!entry?.pageId)
|
|
581
641
|
continue
|
|
582
642
|
const slug = ensureUniqueSlug(entry?.name || '', null, usedSlugs)
|
|
643
|
+
const menuTitle = String(entry?.menuTitle || entry?.name || '').trim() || titleFromSlug(slug)
|
|
583
644
|
menus['Site Root'].push({
|
|
584
645
|
name: slug,
|
|
646
|
+
menuTitle,
|
|
585
647
|
item: entry.pageId,
|
|
586
648
|
disableRename: !!entry?.disableRename,
|
|
587
649
|
disableDelete: !!entry?.disableDelete,
|
|
@@ -651,7 +713,9 @@ const duplicateEntriesWithPages = async (entries = [], options) => {
|
|
|
651
713
|
}
|
|
652
714
|
if (typeof entry.item === 'string' || entry.item === '') {
|
|
653
715
|
const templateDoc = templatePages?.[entry.item] || null
|
|
654
|
-
const
|
|
716
|
+
const entryMenuTitle = String(entry?.menuTitle || '').trim()
|
|
717
|
+
const slugSource = entry.name || entryMenuTitle
|
|
718
|
+
const slug = ensureUniqueSlug(slugSource || '', templateDoc, usedSlugs)
|
|
655
719
|
const payload = buildPagePayloadFromTemplateDoc(templateDoc, slug, entry.name || '')
|
|
656
720
|
try {
|
|
657
721
|
const result = await edgeFirebase.storeDoc(`${edgeGlobal.edgeState.organizationDocPath}/sites/${siteId}/pages`, payload)
|
|
@@ -660,6 +724,7 @@ const duplicateEntriesWithPages = async (entries = [], options) => {
|
|
|
660
724
|
next.push({
|
|
661
725
|
...entry,
|
|
662
726
|
name: slug,
|
|
727
|
+
menuTitle: entryMenuTitle || titleFromSlug(slug),
|
|
663
728
|
item: docId,
|
|
664
729
|
})
|
|
665
730
|
}
|
|
@@ -793,6 +858,7 @@ const isSiteDiff = computed(() => {
|
|
|
793
858
|
trackingFacebookPixel: publishedSite.trackingFacebookPixel,
|
|
794
859
|
trackingGoogleAnalytics: publishedSite.trackingGoogleAnalytics,
|
|
795
860
|
trackingAdroll: publishedSite.trackingAdroll,
|
|
861
|
+
sureFeedURL: publishedSite.sureFeedURL,
|
|
796
862
|
socialFacebook: publishedSite.socialFacebook,
|
|
797
863
|
socialInstagram: publishedSite.socialInstagram,
|
|
798
864
|
socialTwitter: publishedSite.socialTwitter,
|
|
@@ -821,6 +887,7 @@ const isSiteDiff = computed(() => {
|
|
|
821
887
|
trackingFacebookPixel: siteData.value.trackingFacebookPixel,
|
|
822
888
|
trackingGoogleAnalytics: siteData.value.trackingGoogleAnalytics,
|
|
823
889
|
trackingAdroll: siteData.value.trackingAdroll,
|
|
890
|
+
sureFeedURL: siteData.value.sureFeedURL,
|
|
824
891
|
socialFacebook: siteData.value.socialFacebook,
|
|
825
892
|
socialInstagram: siteData.value.socialInstagram,
|
|
826
893
|
socialTwitter: siteData.value.socialTwitter,
|
|
@@ -854,7 +921,7 @@ const discardSiteSettings = async () => {
|
|
|
854
921
|
brandLogoLight: publishedSite.brandLogoLight || '',
|
|
855
922
|
favicon: publishedSite.favicon || '',
|
|
856
923
|
menuPosition: publishedSite.menuPosition || '',
|
|
857
|
-
forwardApex: publishedSite.forwardApex
|
|
924
|
+
forwardApex: publishedSite.forwardApex !== false,
|
|
858
925
|
contactEmail: publishedSite.contactEmail || '',
|
|
859
926
|
contactPhone: publishedSite.contactPhone || '',
|
|
860
927
|
metaTitle: publishedSite.metaTitle || '',
|
|
@@ -863,6 +930,7 @@ const discardSiteSettings = async () => {
|
|
|
863
930
|
trackingFacebookPixel: publishedSite.trackingFacebookPixel || '',
|
|
864
931
|
trackingGoogleAnalytics: publishedSite.trackingGoogleAnalytics || '',
|
|
865
932
|
trackingAdroll: publishedSite.trackingAdroll || '',
|
|
933
|
+
sureFeedURL: publishedSite.sureFeedURL || '',
|
|
866
934
|
socialFacebook: publishedSite.socialFacebook || '',
|
|
867
935
|
socialInstagram: publishedSite.socialInstagram || '',
|
|
868
936
|
socialTwitter: publishedSite.socialTwitter || '',
|
|
@@ -925,6 +993,327 @@ const pageList = computed(() => {
|
|
|
925
993
|
.sort((a, b) => (b.lastUpdated ?? 0) - (a.lastUpdated ?? 0))
|
|
926
994
|
})
|
|
927
995
|
|
|
996
|
+
const INVALID_PAGE_IMPORT_MESSAGE = 'Invalid file. Please import a valid page file.'
|
|
997
|
+
const pageImportCollectionPath = computed(() => `${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/pages`)
|
|
998
|
+
|
|
999
|
+
const readTextFile = file => new Promise((resolve, reject) => {
|
|
1000
|
+
if (typeof FileReader === 'undefined') {
|
|
1001
|
+
reject(new Error('File import is only available in the browser.'))
|
|
1002
|
+
return
|
|
1003
|
+
}
|
|
1004
|
+
const reader = new FileReader()
|
|
1005
|
+
reader.onload = () => resolve(String(reader.result || ''))
|
|
1006
|
+
reader.onerror = () => reject(new Error('Could not read the selected file.'))
|
|
1007
|
+
reader.readAsText(file)
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
const isPlainObject = value => !!value && typeof value === 'object' && !Array.isArray(value)
|
|
1011
|
+
|
|
1012
|
+
const normalizeImportedPageDoc = (payload, fallbackDocId = '') => {
|
|
1013
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
1014
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1015
|
+
|
|
1016
|
+
if (payload.document && typeof payload.document === 'object' && !Array.isArray(payload.document)) {
|
|
1017
|
+
const normalized = { ...payload.document }
|
|
1018
|
+
if (!normalized.docId && payload.docId)
|
|
1019
|
+
normalized.docId = payload.docId
|
|
1020
|
+
if (!normalized.docId && fallbackDocId)
|
|
1021
|
+
normalized.docId = fallbackDocId
|
|
1022
|
+
return normalized
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const normalized = { ...payload }
|
|
1026
|
+
if (!normalized.docId && fallbackDocId)
|
|
1027
|
+
normalized.docId = fallbackDocId
|
|
1028
|
+
return normalized
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const cloneSchemaValue = (value) => {
|
|
1032
|
+
if (isPlainObject(value) || Array.isArray(value))
|
|
1033
|
+
return edgeGlobal.dupObject(value)
|
|
1034
|
+
return value
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const getDocDefaultsFromSchema = (schema = {}) => {
|
|
1038
|
+
const defaults = {}
|
|
1039
|
+
for (const [key, schemaEntry] of Object.entries(schema || {})) {
|
|
1040
|
+
const hasValueProp = isPlainObject(schemaEntry) && Object.prototype.hasOwnProperty.call(schemaEntry, 'value')
|
|
1041
|
+
const baseValue = hasValueProp ? schemaEntry.value : schemaEntry
|
|
1042
|
+
defaults[key] = cloneSchemaValue(baseValue)
|
|
1043
|
+
}
|
|
1044
|
+
return defaults
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const getPageDocDefaults = () => getDocDefaultsFromSchema(state.newDocs?.pages || {})
|
|
1048
|
+
|
|
1049
|
+
const isBlankString = value => String(value || '').trim() === ''
|
|
1050
|
+
|
|
1051
|
+
const applyImportedPageSeoDefaults = (doc) => {
|
|
1052
|
+
if (!isPlainObject(doc))
|
|
1053
|
+
return doc
|
|
1054
|
+
|
|
1055
|
+
if (isBlankString(doc.structuredData))
|
|
1056
|
+
doc.structuredData = buildPageStructuredData()
|
|
1057
|
+
|
|
1058
|
+
if (doc.post && isBlankString(doc.postStructuredData))
|
|
1059
|
+
doc.postStructuredData = doc.structuredData || buildPageStructuredData()
|
|
1060
|
+
|
|
1061
|
+
return doc
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const validateImportedPageDoc = (doc) => {
|
|
1065
|
+
if (!isPlainObject(doc))
|
|
1066
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1067
|
+
|
|
1068
|
+
const requiredKeys = Object.keys(state.newDocs?.pages || {})
|
|
1069
|
+
const missing = requiredKeys.filter(key => !Object.prototype.hasOwnProperty.call(doc, key))
|
|
1070
|
+
if (missing.length)
|
|
1071
|
+
throw new Error(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1072
|
+
|
|
1073
|
+
return doc
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const normalizeMenusForImport = (menus) => {
|
|
1077
|
+
const normalized = isPlainObject(menus) ? edgeGlobal.dupObject(menus) : {}
|
|
1078
|
+
if (!Array.isArray(normalized['Site Root']))
|
|
1079
|
+
normalized['Site Root'] = []
|
|
1080
|
+
if (!Array.isArray(normalized['Not In Menu']))
|
|
1081
|
+
normalized['Not In Menu'] = []
|
|
1082
|
+
return normalized
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const walkMenuEntries = (items, callback) => {
|
|
1086
|
+
if (!Array.isArray(items))
|
|
1087
|
+
return
|
|
1088
|
+
for (const entry of items) {
|
|
1089
|
+
if (!entry || typeof entry !== 'object')
|
|
1090
|
+
continue
|
|
1091
|
+
callback(entry)
|
|
1092
|
+
if (isPlainObject(entry.item)) {
|
|
1093
|
+
for (const nested of Object.values(entry.item)) {
|
|
1094
|
+
if (Array.isArray(nested))
|
|
1095
|
+
walkMenuEntries(nested, callback)
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const menuIncludesDocId = (menus, docId) => {
|
|
1102
|
+
let found = false
|
|
1103
|
+
const checkEntry = (entry) => {
|
|
1104
|
+
if (found)
|
|
1105
|
+
return
|
|
1106
|
+
if (typeof entry?.item === 'string' && entry.item === docId)
|
|
1107
|
+
found = true
|
|
1108
|
+
}
|
|
1109
|
+
for (const menuItems of Object.values(menus || {})) {
|
|
1110
|
+
walkMenuEntries(menuItems, checkEntry)
|
|
1111
|
+
if (found)
|
|
1112
|
+
return true
|
|
1113
|
+
}
|
|
1114
|
+
return false
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const collectMenuPageNames = (menus) => {
|
|
1118
|
+
const names = new Set()
|
|
1119
|
+
const collectEntry = (entry) => {
|
|
1120
|
+
if (typeof entry?.item !== 'string')
|
|
1121
|
+
return
|
|
1122
|
+
const name = String(entry?.name || '').trim()
|
|
1123
|
+
if (name)
|
|
1124
|
+
names.add(name)
|
|
1125
|
+
}
|
|
1126
|
+
for (const menuItems of Object.values(menus || {}))
|
|
1127
|
+
walkMenuEntries(menuItems, collectEntry)
|
|
1128
|
+
return names
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const slugifyMenuPageName = (value) => {
|
|
1132
|
+
return String(value || '')
|
|
1133
|
+
.trim()
|
|
1134
|
+
.toLowerCase()
|
|
1135
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1136
|
+
.replace(/(^-|-$)+/g, '') || 'page'
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const makeUniqueMenuPageName = (value, existingNames = new Set()) => {
|
|
1140
|
+
const base = slugifyMenuPageName(value)
|
|
1141
|
+
let candidate = base
|
|
1142
|
+
let suffix = 2
|
|
1143
|
+
while (existingNames.has(candidate)) {
|
|
1144
|
+
candidate = `${base}-${suffix}`
|
|
1145
|
+
suffix += 1
|
|
1146
|
+
}
|
|
1147
|
+
return candidate
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const addImportedPageToSiteMenu = (docId, pageName = '') => {
|
|
1151
|
+
if (isTemplateSite.value)
|
|
1152
|
+
return
|
|
1153
|
+
|
|
1154
|
+
const nextDocId = String(docId || '').trim()
|
|
1155
|
+
if (!nextDocId)
|
|
1156
|
+
return
|
|
1157
|
+
|
|
1158
|
+
const menus = normalizeMenusForImport(siteData.value?.menus || state.menus)
|
|
1159
|
+
if (menuIncludesDocId(menus, nextDocId)) {
|
|
1160
|
+
state.menus = menus
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const existingNames = collectMenuPageNames(menus)
|
|
1165
|
+
const menuName = makeUniqueMenuPageName(pageName || nextDocId, existingNames)
|
|
1166
|
+
const menuTitle = String(pageName || '').trim() || titleFromSlug(menuName)
|
|
1167
|
+
menus['Site Root'].push({ name: menuName, menuTitle, item: nextDocId })
|
|
1168
|
+
state.menus = menus
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const makeRandomPageDocId = (docsMap = {}) => {
|
|
1172
|
+
let nextDocId = String(edgeGlobal.generateShortId() || '').trim()
|
|
1173
|
+
while (!nextDocId || docsMap[nextDocId])
|
|
1174
|
+
nextDocId = String(edgeGlobal.generateShortId() || '').trim()
|
|
1175
|
+
return nextDocId
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const makeImportedPageNameForNew = (baseName, docsMap = {}) => {
|
|
1179
|
+
const normalizedBase = String(baseName || '').trim() || 'page'
|
|
1180
|
+
const existingNames = new Set(
|
|
1181
|
+
Object.values(docsMap || {})
|
|
1182
|
+
.map(doc => String(doc?.name || '').trim().toLowerCase())
|
|
1183
|
+
.filter(Boolean),
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
let suffix = 1
|
|
1187
|
+
let candidate = `${normalizedBase}-${suffix}`
|
|
1188
|
+
while (existingNames.has(candidate.toLowerCase())) {
|
|
1189
|
+
suffix += 1
|
|
1190
|
+
candidate = `${normalizedBase}-${suffix}`
|
|
1191
|
+
}
|
|
1192
|
+
return candidate
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const requestPageImportDocId = (initialValue = '') => {
|
|
1196
|
+
state.importPageDocIdValue = String(initialValue || '')
|
|
1197
|
+
state.importPageDocIdDialogOpen = true
|
|
1198
|
+
return new Promise((resolve) => {
|
|
1199
|
+
pageImportDocIdResolver.value = resolve
|
|
1200
|
+
})
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const resolvePageImportDocId = (value = '') => {
|
|
1204
|
+
const resolver = pageImportDocIdResolver.value
|
|
1205
|
+
pageImportDocIdResolver.value = null
|
|
1206
|
+
state.importPageDocIdDialogOpen = false
|
|
1207
|
+
if (resolver)
|
|
1208
|
+
resolver(String(value || '').trim())
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const requestPageImportConflict = (docId) => {
|
|
1212
|
+
state.importPageConflictDocId = String(docId || '')
|
|
1213
|
+
state.importPageConflictDialogOpen = true
|
|
1214
|
+
return new Promise((resolve) => {
|
|
1215
|
+
pageImportConflictResolver.value = resolve
|
|
1216
|
+
})
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const resolvePageImportConflict = (action = 'cancel') => {
|
|
1220
|
+
const resolver = pageImportConflictResolver.value
|
|
1221
|
+
pageImportConflictResolver.value = null
|
|
1222
|
+
state.importPageConflictDialogOpen = false
|
|
1223
|
+
if (resolver)
|
|
1224
|
+
resolver(action)
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const getImportDocId = async (incomingDoc, fallbackDocId = '') => {
|
|
1228
|
+
let nextDocId = String(incomingDoc?.docId || '').trim()
|
|
1229
|
+
if (!nextDocId)
|
|
1230
|
+
nextDocId = await requestPageImportDocId(fallbackDocId)
|
|
1231
|
+
if (!nextDocId)
|
|
1232
|
+
throw new Error('Import canceled. A docId is required.')
|
|
1233
|
+
if (nextDocId.includes('/'))
|
|
1234
|
+
throw new Error('docId cannot include "/".')
|
|
1235
|
+
return nextDocId
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const openImportErrorDialog = (message) => {
|
|
1239
|
+
state.importPageErrorMessage = String(message || 'Failed to import page.')
|
|
1240
|
+
state.importPageErrorDialogOpen = true
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
const triggerPageImport = () => {
|
|
1244
|
+
pageImportInputRef.value?.click()
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const importSinglePageFile = async (file, existingPages = {}, fallbackDocId = '') => {
|
|
1248
|
+
const fileText = await readTextFile(file)
|
|
1249
|
+
const parsed = JSON.parse(fileText)
|
|
1250
|
+
const importedDoc = applyImportedPageSeoDefaults(validateImportedPageDoc(normalizeImportedPageDoc(parsed, fallbackDocId)))
|
|
1251
|
+
const incomingDocId = await getImportDocId(importedDoc, fallbackDocId)
|
|
1252
|
+
let targetDocId = incomingDocId
|
|
1253
|
+
let importDecision = 'create'
|
|
1254
|
+
|
|
1255
|
+
if (existingPages[targetDocId]) {
|
|
1256
|
+
const decision = await requestPageImportConflict(targetDocId)
|
|
1257
|
+
if (decision === 'cancel')
|
|
1258
|
+
return
|
|
1259
|
+
if (decision === 'new') {
|
|
1260
|
+
targetDocId = makeRandomPageDocId(existingPages)
|
|
1261
|
+
importedDoc.name = makeImportedPageNameForNew(importedDoc.name || incomingDocId, existingPages)
|
|
1262
|
+
importDecision = 'new'
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
importDecision = 'overwrite'
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const payload = { ...getPageDocDefaults(), ...importedDoc, docId: targetDocId }
|
|
1270
|
+
await edgeFirebase.storeDoc(pageImportCollectionPath.value, payload, targetDocId)
|
|
1271
|
+
existingPages[targetDocId] = payload
|
|
1272
|
+
addImportedPageToSiteMenu(targetDocId, payload.name)
|
|
1273
|
+
|
|
1274
|
+
if (importDecision === 'overwrite')
|
|
1275
|
+
edgeFirebase?.toast?.success?.(`Overwrote page "${targetDocId}".`)
|
|
1276
|
+
else if (importDecision === 'new')
|
|
1277
|
+
edgeFirebase?.toast?.success?.(`Imported page as new "${targetDocId}".`)
|
|
1278
|
+
else
|
|
1279
|
+
edgeFirebase?.toast?.success?.(`Imported page "${targetDocId}".`)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const handlePageImport = async (event) => {
|
|
1283
|
+
const input = event?.target
|
|
1284
|
+
const files = Array.from(input?.files || [])
|
|
1285
|
+
if (!files.length)
|
|
1286
|
+
return
|
|
1287
|
+
|
|
1288
|
+
state.importingPages = true
|
|
1289
|
+
const existingPages = { ...(pages.value || {}) }
|
|
1290
|
+
try {
|
|
1291
|
+
if (!edgeFirebase.data?.[pageImportCollectionPath.value])
|
|
1292
|
+
await edgeFirebase.startSnapshot(pageImportCollectionPath.value)
|
|
1293
|
+
|
|
1294
|
+
for (const file of files) {
|
|
1295
|
+
try {
|
|
1296
|
+
await importSinglePageFile(file, existingPages, '')
|
|
1297
|
+
}
|
|
1298
|
+
catch (error) {
|
|
1299
|
+
console.error('Failed to import page file', error)
|
|
1300
|
+
const message = error?.message || 'Failed to import page file.'
|
|
1301
|
+
if (/^Import canceled\./i.test(message))
|
|
1302
|
+
continue
|
|
1303
|
+
if (error instanceof SyntaxError || message === INVALID_PAGE_IMPORT_MESSAGE)
|
|
1304
|
+
openImportErrorDialog(INVALID_PAGE_IMPORT_MESSAGE)
|
|
1305
|
+
else
|
|
1306
|
+
openImportErrorDialog(message)
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
finally {
|
|
1311
|
+
state.importingPages = false
|
|
1312
|
+
if (input)
|
|
1313
|
+
input.value = ''
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
928
1317
|
const formatTimestamp = (input) => {
|
|
929
1318
|
if (!input)
|
|
930
1319
|
return 'Not yet saved'
|
|
@@ -978,10 +1367,39 @@ const isPublishedPageDiff = (pageId) => {
|
|
|
978
1367
|
|
|
979
1368
|
const pageStatusLabel = pageId => (isPublishedPageDiff(pageId) ? 'Draft' : 'Published')
|
|
980
1369
|
const hasSelection = computed(() => Boolean(props.page) || Boolean(state.selectedPostId))
|
|
981
|
-
const showSplitView = computed(() => isTemplateSite.value || state.viewMode === 'pages' || hasSelection.value)
|
|
982
|
-
const isEditingPost = computed(() => state.viewMode === 'posts' && Boolean(state.selectedPostId))
|
|
1370
|
+
const showSplitView = computed(() => isTemplateSite.value || (canViewPagesTab.value && (state.viewMode === 'pages' || hasSelection.value)))
|
|
1371
|
+
const isEditingPost = computed(() => canViewPostsTab.value && state.viewMode === 'posts' && Boolean(state.selectedPostId))
|
|
1372
|
+
|
|
1373
|
+
const ensureValidViewMode = () => {
|
|
1374
|
+
let nextMode = state.viewMode
|
|
1375
|
+
if (nextMode === 'pages' && !canViewPagesTab.value)
|
|
1376
|
+
nextMode = defaultViewMode.value
|
|
1377
|
+
if (nextMode === 'posts' && !canViewPostsTab.value)
|
|
1378
|
+
nextMode = defaultViewMode.value
|
|
1379
|
+
if (nextMode === 'submissions' && !canViewInboxTab.value)
|
|
1380
|
+
nextMode = defaultViewMode.value
|
|
1381
|
+
|
|
1382
|
+
if (state.viewMode !== nextMode)
|
|
1383
|
+
state.viewMode = nextMode
|
|
1384
|
+
|
|
1385
|
+
if (state.viewMode !== 'posts') {
|
|
1386
|
+
state.selectedPostId = ''
|
|
1387
|
+
}
|
|
1388
|
+
if (state.viewMode !== 'submissions') {
|
|
1389
|
+
state.selectedSubmissionId = ''
|
|
1390
|
+
}
|
|
1391
|
+
if (props.page && state.viewMode !== 'pages') {
|
|
1392
|
+
router.replace(pageRouteBase.value)
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
983
1395
|
|
|
984
1396
|
const setViewMode = (mode) => {
|
|
1397
|
+
if (mode === 'pages' && !canViewPagesTab.value)
|
|
1398
|
+
return
|
|
1399
|
+
if (mode === 'posts' && !canViewPostsTab.value)
|
|
1400
|
+
return
|
|
1401
|
+
if (mode === 'submissions' && !canViewInboxTab.value)
|
|
1402
|
+
return
|
|
985
1403
|
if (state.viewMode === mode)
|
|
986
1404
|
return
|
|
987
1405
|
state.viewMode = mode
|
|
@@ -995,6 +1413,8 @@ const setViewMode = (mode) => {
|
|
|
995
1413
|
const handlePostSelect = (postId) => {
|
|
996
1414
|
if (!postId)
|
|
997
1415
|
return
|
|
1416
|
+
if (!canViewPostsTab.value)
|
|
1417
|
+
return
|
|
998
1418
|
state.selectedPostId = postId
|
|
999
1419
|
state.viewMode = 'posts'
|
|
1000
1420
|
if (props.page)
|
|
@@ -1005,6 +1425,22 @@ const clearPostSelection = () => {
|
|
|
1005
1425
|
state.selectedPostId = ''
|
|
1006
1426
|
}
|
|
1007
1427
|
|
|
1428
|
+
watch(() => state.importPageDocIdDialogOpen, (open) => {
|
|
1429
|
+
if (!open && pageImportDocIdResolver.value) {
|
|
1430
|
+
const resolver = pageImportDocIdResolver.value
|
|
1431
|
+
pageImportDocIdResolver.value = null
|
|
1432
|
+
resolver('')
|
|
1433
|
+
}
|
|
1434
|
+
})
|
|
1435
|
+
|
|
1436
|
+
watch(() => state.importPageConflictDialogOpen, (open) => {
|
|
1437
|
+
if (!open && pageImportConflictResolver.value) {
|
|
1438
|
+
const resolver = pageImportConflictResolver.value
|
|
1439
|
+
pageImportConflictResolver.value = null
|
|
1440
|
+
resolver('cancel')
|
|
1441
|
+
}
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1008
1444
|
watch (() => siteData.value, () => {
|
|
1009
1445
|
if (isTemplateSite.value)
|
|
1010
1446
|
return
|
|
@@ -1040,14 +1476,33 @@ watch(pages, (pagesCollection) => {
|
|
|
1040
1476
|
watch(() => props.page, (next) => {
|
|
1041
1477
|
if (next) {
|
|
1042
1478
|
state.selectedPostId = ''
|
|
1043
|
-
|
|
1479
|
+
if (canViewPagesTab.value) {
|
|
1480
|
+
state.viewMode = 'pages'
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
state.viewMode = defaultViewMode.value
|
|
1484
|
+
if (props.page)
|
|
1485
|
+
router.replace(pageRouteBase.value)
|
|
1486
|
+
}
|
|
1044
1487
|
return
|
|
1045
1488
|
}
|
|
1046
|
-
if (state.selectedPostId) {
|
|
1489
|
+
if (state.selectedPostId && canViewPostsTab.value) {
|
|
1047
1490
|
state.viewMode = 'posts'
|
|
1491
|
+
return
|
|
1048
1492
|
}
|
|
1493
|
+
ensureValidViewMode()
|
|
1049
1494
|
})
|
|
1050
1495
|
|
|
1496
|
+
watch(cmsTabAccess, () => {
|
|
1497
|
+
ensureValidViewMode()
|
|
1498
|
+
}, { immediate: true, deep: true })
|
|
1499
|
+
|
|
1500
|
+
watch(canEditSiteSettings, (allowed) => {
|
|
1501
|
+
if (!allowed && state.siteSettings) {
|
|
1502
|
+
state.siteSettings = false
|
|
1503
|
+
}
|
|
1504
|
+
}, { immediate: true })
|
|
1505
|
+
|
|
1051
1506
|
watch([isViewingSubmissions, sortedSubmissionIds], () => {
|
|
1052
1507
|
if (!isViewingSubmissions.value)
|
|
1053
1508
|
return
|
|
@@ -1293,7 +1748,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1293
1748
|
@update:model-value="value => (slotProps.workingDoc.theme = value || '')"
|
|
1294
1749
|
/>
|
|
1295
1750
|
<edge-shad-select-tags
|
|
1296
|
-
v-if="Object.keys(orgUsers).length > 0"
|
|
1751
|
+
v-if="!cmsMultiOrg && Object.keys(orgUsers).length > 0"
|
|
1297
1752
|
:model-value="getSiteUsersModel(slotProps.workingDoc)"
|
|
1298
1753
|
:disabled="shouldForceCurrentUserForNewSite || !edgeGlobal.isAdminGlobal(edgeFirebase).value"
|
|
1299
1754
|
:items="userOptions"
|
|
@@ -1324,6 +1779,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1324
1779
|
</div>
|
|
1325
1780
|
<div class="space-y-3">
|
|
1326
1781
|
<edge-shad-select
|
|
1782
|
+
v-if="!cmsMultiOrg"
|
|
1327
1783
|
:model-value="slotProps.workingDoc.aiAgentUserId || ''"
|
|
1328
1784
|
name="aiAgentUserId"
|
|
1329
1785
|
label="User Data for AI to use to build initial site"
|
|
@@ -1350,16 +1806,20 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1350
1806
|
Only organization admins can create sites.
|
|
1351
1807
|
</div>
|
|
1352
1808
|
<div v-else class="flex flex-col h-[calc(100vh-58px)] overflow-hidden">
|
|
1353
|
-
<div
|
|
1809
|
+
<div
|
|
1810
|
+
class="grid grid-cols-[1fr_auto_1fr] items-center gap-3 px-4 py-2 border bg-secondary"
|
|
1811
|
+
:class="isTemplateSite ? 'min-h-[68px]' : ''"
|
|
1812
|
+
>
|
|
1354
1813
|
<div class="flex items-center gap-3">
|
|
1355
1814
|
<FileStack class="w-5 h-5" />
|
|
1356
|
-
<span class="text-lg font-
|
|
1815
|
+
<span class="text-lg font-normal">
|
|
1357
1816
|
{{ siteData.name || 'Templates' }}
|
|
1358
1817
|
</span>
|
|
1359
1818
|
</div>
|
|
1360
1819
|
<div class="flex justify-center">
|
|
1361
|
-
<div v-if="!isTemplateSite" class="flex items-center rounded-full border border-border bg-background p-1 shadow-sm">
|
|
1820
|
+
<div v-if="!isTemplateSite && (canViewPagesTab || canViewPostsTab || canViewInboxTab)" class="flex items-center rounded-full border border-border bg-background p-1 shadow-sm">
|
|
1362
1821
|
<edge-shad-button
|
|
1822
|
+
v-if="canViewPagesTab"
|
|
1363
1823
|
variant="ghost"
|
|
1364
1824
|
size="sm"
|
|
1365
1825
|
class="h-8 px-4 text-xs gap-2 rounded-full"
|
|
@@ -1370,6 +1830,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1370
1830
|
Pages
|
|
1371
1831
|
</edge-shad-button>
|
|
1372
1832
|
<edge-shad-button
|
|
1833
|
+
v-if="canViewPostsTab"
|
|
1373
1834
|
variant="ghost"
|
|
1374
1835
|
size="sm"
|
|
1375
1836
|
class="h-8 px-4 text-xs gap-2 rounded-full"
|
|
@@ -1380,6 +1841,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1380
1841
|
Posts
|
|
1381
1842
|
</edge-shad-button>
|
|
1382
1843
|
<edge-shad-button
|
|
1844
|
+
v-if="canViewInboxTab"
|
|
1383
1845
|
variant="ghost"
|
|
1384
1846
|
size="sm"
|
|
1385
1847
|
class="h-8 px-4 text-xs gap-2 rounded-full"
|
|
@@ -1397,76 +1859,98 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1397
1859
|
</edge-shad-button>
|
|
1398
1860
|
</div>
|
|
1399
1861
|
</div>
|
|
1400
|
-
<div
|
|
1401
|
-
<
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1862
|
+
<div class="flex items-center gap-3 justify-end">
|
|
1863
|
+
<input
|
|
1864
|
+
ref="pageImportInputRef"
|
|
1865
|
+
type="file"
|
|
1866
|
+
multiple
|
|
1867
|
+
accept=".json,application/json"
|
|
1868
|
+
class="hidden"
|
|
1869
|
+
@change="handlePageImport"
|
|
1870
|
+
>
|
|
1871
|
+
<edge-shad-button
|
|
1872
|
+
type="button"
|
|
1873
|
+
size="icon"
|
|
1874
|
+
variant="outline"
|
|
1875
|
+
class="h-9 w-9"
|
|
1876
|
+
:disabled="state.importingPages"
|
|
1877
|
+
title="Import Page"
|
|
1878
|
+
aria-label="Import Page"
|
|
1879
|
+
@click="triggerPageImport"
|
|
1880
|
+
>
|
|
1881
|
+
<Loader2 v-if="state.importingPages" class="h-3.5 w-3.5 animate-spin" />
|
|
1882
|
+
<Upload v-else class="h-3.5 w-3.5" />
|
|
1883
|
+
</edge-shad-button>
|
|
1884
|
+
<template v-if="!isTemplateSite && !hidePublishStatusAndActions">
|
|
1885
|
+
<Transition name="fade" mode="out-in">
|
|
1886
|
+
<div v-if="isSiteDiff || isAnyPagesDiff" key="unpublished" class="flex gap-2 items-center">
|
|
1887
|
+
<div class="flex gap-1 items-center bg-yellow-100 text-xs py-1 px-3 text-yellow-800 rounded">
|
|
1888
|
+
<CircleAlert class="!text-yellow-800 w-3 h-6" />
|
|
1889
|
+
<span class="font-medium text-[10px]">
|
|
1890
|
+
{{ isSiteDiff ? (useMenuPublishLabels ? 'Unpublished Menu' : 'Unpublished Settings') : 'Unpublished Pages' }}
|
|
1891
|
+
</span>
|
|
1892
|
+
</div>
|
|
1893
|
+
<edge-shad-button
|
|
1894
|
+
class="h-8 px-4 text-xs gap-2 bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm"
|
|
1895
|
+
:disabled="state.publishSiteLoading"
|
|
1896
|
+
@click="publishSiteAndSettings"
|
|
1897
|
+
>
|
|
1898
|
+
<Loader2 v-if="state.publishSiteLoading" class="h-3.5 w-3.5 animate-spin" />
|
|
1899
|
+
<FolderUp v-else class="h-3.5 w-3.5" />
|
|
1900
|
+
Publish Site
|
|
1901
|
+
</edge-shad-button>
|
|
1902
|
+
</div>
|
|
1903
|
+
<div v-else key="published" class="flex gap-1 items-center bg-green-100 text-xs py-1 px-3 text-green-800 rounded">
|
|
1904
|
+
<FileCheck class="!text-green-800 w-3 h-6" />
|
|
1405
1905
|
<span class="font-medium text-[10px]">
|
|
1406
|
-
{{
|
|
1906
|
+
{{ useMenuPublishLabels ? 'Menu Published' : 'Settings Published' }}
|
|
1407
1907
|
</span>
|
|
1408
1908
|
</div>
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
<
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
<DropdownMenuItem v-if="isAnyPagesDiff" @click="publishSite">
|
|
1452
|
-
<FolderUp />
|
|
1453
|
-
Publish All Pages
|
|
1454
|
-
</DropdownMenuItem>
|
|
1455
|
-
<DropdownMenuItem v-if="isSiteSettingPublished || isAnyPagesPublished" @click="unPublishSite">
|
|
1456
|
-
<FolderDown />
|
|
1457
|
-
Unpublish Site
|
|
1458
|
-
</DropdownMenuItem>
|
|
1459
|
-
|
|
1460
|
-
<DropdownMenuItem @click="state.siteSettings = true">
|
|
1461
|
-
<FolderCog />
|
|
1462
|
-
<span>Settings</span>
|
|
1463
|
-
</DropdownMenuItem>
|
|
1464
|
-
</DropdownMenuContent>
|
|
1465
|
-
</DropdownMenu>
|
|
1909
|
+
</Transition>
|
|
1910
|
+
<DropdownMenu>
|
|
1911
|
+
<DropdownMenuTrigger as-child>
|
|
1912
|
+
<edge-shad-button variant="outline" size="icon" class="h-9 w-9">
|
|
1913
|
+
<MoreHorizontal />
|
|
1914
|
+
</edge-shad-button>
|
|
1915
|
+
</DropdownMenuTrigger>
|
|
1916
|
+
<DropdownMenuContent side="right" align="start">
|
|
1917
|
+
<DropdownMenuLabel class="flex items-center gap-2">
|
|
1918
|
+
<FileStack class="w-5 h-5" />{{ siteData.name || 'Templates' }}
|
|
1919
|
+
</DropdownMenuLabel>
|
|
1920
|
+
|
|
1921
|
+
<DropdownMenuSeparator v-if="isSiteDiff" />
|
|
1922
|
+
<DropdownMenuLabel v-if="isSiteDiff" class="flex items-center gap-2">
|
|
1923
|
+
Site Settings
|
|
1924
|
+
</DropdownMenuLabel>
|
|
1925
|
+
|
|
1926
|
+
<DropdownMenuItem v-if="isSiteDiff" class="pl-4 text-xs" @click="publishSiteSettings">
|
|
1927
|
+
<FolderUp />
|
|
1928
|
+
Publish
|
|
1929
|
+
</DropdownMenuItem>
|
|
1930
|
+
<DropdownMenuItem v-if="isSiteDiff && isSiteSettingPublished" class="pl-4 text-xs" @click="discardSiteSettings">
|
|
1931
|
+
<FolderX />
|
|
1932
|
+
Discard Changes
|
|
1933
|
+
</DropdownMenuItem>
|
|
1934
|
+
<DropdownMenuSeparator />
|
|
1935
|
+
<DropdownMenuItem v-if="isAnyPagesDiff" @click="publishSite">
|
|
1936
|
+
<FolderUp />
|
|
1937
|
+
Publish All Pages
|
|
1938
|
+
</DropdownMenuItem>
|
|
1939
|
+
<DropdownMenuItem v-if="isSiteSettingPublished || isAnyPagesPublished" @click="unPublishSite">
|
|
1940
|
+
<FolderDown />
|
|
1941
|
+
Unpublish Site
|
|
1942
|
+
</DropdownMenuItem>
|
|
1943
|
+
|
|
1944
|
+
<DropdownMenuItem v-if="canEditSiteSettings" @click="state.siteSettings = true">
|
|
1945
|
+
<FolderCog />
|
|
1946
|
+
<span>Settings</span>
|
|
1947
|
+
</DropdownMenuItem>
|
|
1948
|
+
</DropdownMenuContent>
|
|
1949
|
+
</DropdownMenu>
|
|
1950
|
+
</template>
|
|
1466
1951
|
</div>
|
|
1467
|
-
<div v-else />
|
|
1468
1952
|
</div>
|
|
1469
|
-
<div class="flex-1">
|
|
1953
|
+
<div class="flex-1 min-h-0">
|
|
1470
1954
|
<Transition name="fade" mode="out-in">
|
|
1471
1955
|
<div v-if="isViewingSubmissions" class="flex-1 overflow-y-auto p-6">
|
|
1472
1956
|
<edge-dashboard
|
|
@@ -1616,12 +2100,12 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1616
2100
|
@update:selected-post-id="clearPostSelection"
|
|
1617
2101
|
/>
|
|
1618
2102
|
</div>
|
|
1619
|
-
<ResizablePanelGroup v-else-if="showSplitView" direction="horizontal" class="w-full h-full flex-1">
|
|
1620
|
-
<ResizablePanel class="bg-
|
|
1621
|
-
<SidebarGroup class="mt-0 pt-0">
|
|
1622
|
-
<SidebarGroupContent>
|
|
1623
|
-
<SidebarMenu>
|
|
1624
|
-
<template v-if="isTemplateSite || state.viewMode === 'pages'">
|
|
2103
|
+
<ResizablePanelGroup v-else-if="showSplitView" direction="horizontal" class="w-full h-full flex-1 min-h-0">
|
|
2104
|
+
<ResizablePanel class="bg-primary-foreground text-black min-h-0 overflow-hidden" :default-size="16">
|
|
2105
|
+
<SidebarGroup class="mt-0 pt-0 h-full min-h-0">
|
|
2106
|
+
<SidebarGroupContent class="h-full min-h-0 overflow-y-auto">
|
|
2107
|
+
<SidebarMenu class="pb-4">
|
|
2108
|
+
<template v-if="isTemplateSite || (canViewPagesTab && state.viewMode === 'pages')">
|
|
1625
2109
|
<edge-cms-menu
|
|
1626
2110
|
v-if="state.menus"
|
|
1627
2111
|
v-model="state.menus"
|
|
@@ -1645,7 +2129,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1645
2129
|
</SidebarGroupContent>
|
|
1646
2130
|
</SidebarGroup>
|
|
1647
2131
|
</ResizablePanel>
|
|
1648
|
-
<ResizablePanel ref="mainPanel">
|
|
2132
|
+
<ResizablePanel ref="mainPanel" class="min-h-0">
|
|
1649
2133
|
<Transition name="fade" mode="out-in">
|
|
1650
2134
|
<div v-if="props.page && !state.updating" :key="props.page" class="max-h-[calc(100vh-100px)] overflow-y-auto w-full">
|
|
1651
2135
|
<NuxtPage class="flex flex-col flex-1 px-0 mx-0 pt-0" />
|
|
@@ -1658,11 +2142,12 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1658
2142
|
</Transition>
|
|
1659
2143
|
</ResizablePanel>
|
|
1660
2144
|
</ResizablePanelGroup>
|
|
1661
|
-
<div v-else class="flex-1
|
|
1662
|
-
<div class="mx-auto w-full max-w-5xl
|
|
2145
|
+
<div v-else class="flex-1 min-h-0 overflow-hidden p-6">
|
|
2146
|
+
<div class="mx-auto w-full max-w-5xl h-full min-h-0">
|
|
1663
2147
|
<edge-cms-posts
|
|
1664
2148
|
mode="list"
|
|
1665
2149
|
list-variant="full"
|
|
2150
|
+
class="h-full min-h-0"
|
|
1666
2151
|
:site="props.site"
|
|
1667
2152
|
@updating="isUpdating => state.updating = isUpdating"
|
|
1668
2153
|
@update:selected-post-id="handlePostSelect"
|
|
@@ -1672,7 +2157,73 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1672
2157
|
</Transition>
|
|
1673
2158
|
</div>
|
|
1674
2159
|
</div>
|
|
1675
|
-
<
|
|
2160
|
+
<edge-shad-dialog v-model="state.importPageDocIdDialogOpen">
|
|
2161
|
+
<DialogContent class="pt-8">
|
|
2162
|
+
<DialogHeader>
|
|
2163
|
+
<DialogTitle class="text-left">
|
|
2164
|
+
Enter Page Doc ID
|
|
2165
|
+
</DialogTitle>
|
|
2166
|
+
<DialogDescription>
|
|
2167
|
+
This file does not include a <code>docId</code>. Enter the doc ID you want to import into this site.
|
|
2168
|
+
</DialogDescription>
|
|
2169
|
+
</DialogHeader>
|
|
2170
|
+
<edge-shad-input
|
|
2171
|
+
v-model="state.importPageDocIdValue"
|
|
2172
|
+
name="site-page-import-doc-id"
|
|
2173
|
+
label="Doc ID"
|
|
2174
|
+
placeholder="example-page-id"
|
|
2175
|
+
/>
|
|
2176
|
+
<DialogFooter class="pt-2 flex justify-between">
|
|
2177
|
+
<edge-shad-button variant="outline" @click="resolvePageImportDocId('')">
|
|
2178
|
+
Cancel
|
|
2179
|
+
</edge-shad-button>
|
|
2180
|
+
<edge-shad-button @click="resolvePageImportDocId(state.importPageDocIdValue)">
|
|
2181
|
+
Continue
|
|
2182
|
+
</edge-shad-button>
|
|
2183
|
+
</DialogFooter>
|
|
2184
|
+
</DialogContent>
|
|
2185
|
+
</edge-shad-dialog>
|
|
2186
|
+
<edge-shad-dialog v-model="state.importPageConflictDialogOpen">
|
|
2187
|
+
<DialogContent class="pt-8">
|
|
2188
|
+
<DialogHeader>
|
|
2189
|
+
<DialogTitle class="text-left">
|
|
2190
|
+
Page Already Exists
|
|
2191
|
+
</DialogTitle>
|
|
2192
|
+
<DialogDescription>
|
|
2193
|
+
<code>{{ state.importPageConflictDocId }}</code> already exists in this {{ isTemplateSite ? 'template library' : 'site' }}. Choose to overwrite it or import as a new page.
|
|
2194
|
+
</DialogDescription>
|
|
2195
|
+
</DialogHeader>
|
|
2196
|
+
<DialogFooter class="pt-2 flex justify-between">
|
|
2197
|
+
<edge-shad-button variant="outline" @click="resolvePageImportConflict('cancel')">
|
|
2198
|
+
Cancel
|
|
2199
|
+
</edge-shad-button>
|
|
2200
|
+
<edge-shad-button variant="outline" @click="resolvePageImportConflict('new')">
|
|
2201
|
+
Add As New
|
|
2202
|
+
</edge-shad-button>
|
|
2203
|
+
<edge-shad-button @click="resolvePageImportConflict('overwrite')">
|
|
2204
|
+
Overwrite
|
|
2205
|
+
</edge-shad-button>
|
|
2206
|
+
</DialogFooter>
|
|
2207
|
+
</DialogContent>
|
|
2208
|
+
</edge-shad-dialog>
|
|
2209
|
+
<edge-shad-dialog v-model="state.importPageErrorDialogOpen">
|
|
2210
|
+
<DialogContent class="pt-8">
|
|
2211
|
+
<DialogHeader>
|
|
2212
|
+
<DialogTitle class="text-left">
|
|
2213
|
+
Import Failed
|
|
2214
|
+
</DialogTitle>
|
|
2215
|
+
<DialogDescription class="text-left">
|
|
2216
|
+
{{ state.importPageErrorMessage }}
|
|
2217
|
+
</DialogDescription>
|
|
2218
|
+
</DialogHeader>
|
|
2219
|
+
<DialogFooter class="pt-2">
|
|
2220
|
+
<edge-shad-button @click="state.importPageErrorDialogOpen = false">
|
|
2221
|
+
Close
|
|
2222
|
+
</edge-shad-button>
|
|
2223
|
+
</DialogFooter>
|
|
2224
|
+
</DialogContent>
|
|
2225
|
+
</edge-shad-dialog>
|
|
2226
|
+
<Sheet v-if="canEditSiteSettings" v-model:open="state.siteSettings">
|
|
1676
2227
|
<SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
|
|
1677
2228
|
<SheetHeader>
|
|
1678
2229
|
<SheetTitle>{{ siteData.name || 'Site' }}</SheetTitle>
|
|
@@ -1697,7 +2248,7 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
1697
2248
|
:theme-options="themeOptions"
|
|
1698
2249
|
:user-options="userOptions"
|
|
1699
2250
|
:has-users="Object.keys(orgUsers).length > 0"
|
|
1700
|
-
:show-users="
|
|
2251
|
+
:show-users="!cmsMultiOrg"
|
|
1701
2252
|
:show-theme-fields="true"
|
|
1702
2253
|
:is-admin="isAdmin"
|
|
1703
2254
|
:enable-media-picker="true"
|