@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.
Files changed (45) hide show
  1. package/README.md +1 -0
  2. package/agents.md +95 -2
  3. package/deploy.sh +136 -0
  4. package/edge/components/cms/block.vue +977 -305
  5. package/edge/components/cms/blockApi.vue +3 -3
  6. package/edge/components/cms/blockEditor.vue +688 -86
  7. package/edge/components/cms/blockPicker.vue +31 -5
  8. package/edge/components/cms/blockRender.vue +3 -3
  9. package/edge/components/cms/blocksManager.vue +790 -82
  10. package/edge/components/cms/codeEditor.vue +15 -6
  11. package/edge/components/cms/fontUpload.vue +318 -2
  12. package/edge/components/cms/htmlContent.vue +825 -93
  13. package/edge/components/cms/init_blocks/contact_us.html +55 -47
  14. package/edge/components/cms/init_blocks/newsletter.html +56 -96
  15. package/edge/components/cms/menu.vue +96 -34
  16. package/edge/components/cms/page.vue +902 -58
  17. package/edge/components/cms/posts.vue +13 -4
  18. package/edge/components/cms/site.vue +638 -87
  19. package/edge/components/cms/siteSettingsForm.vue +19 -9
  20. package/edge/components/cms/sitesManager.vue +5 -4
  21. package/edge/components/cms/themeDefaultMenu.vue +20 -2
  22. package/edge/components/cms/themeEditor.vue +196 -162
  23. package/edge/components/editor.vue +5 -1
  24. package/edge/composables/global.ts +37 -5
  25. package/edge/composables/siteSettingsTemplate.js +2 -0
  26. package/edge/composables/useCmsNewDocs.js +100 -0
  27. package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
  28. package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
  29. package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
  30. package/edge/routes/cms/dashboard/media/index.vue +5 -0
  31. package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
  32. package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
  33. package/edge/routes/cms/dashboard/sites/index.vue +4 -0
  34. package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
  35. package/edge/routes/cms/dashboard/templates/index.vue +4 -0
  36. package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
  37. package/edge/routes/cms/dashboard/themes/index.vue +330 -1
  38. package/edge-pull.sh +16 -2
  39. package/edge-push.sh +9 -1
  40. package/edge-remote.sh +20 -0
  41. package/edge-status.sh +9 -5
  42. package/edge-update-all.sh +127 -0
  43. package/firebase.json +4 -0
  44. package/nuxt.config.ts +1 -1
  45. 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 slug = ensureUniqueSlug(entry.name || '', templateDoc, usedSlugs)
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 === false ? false : true,
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
- state.viewMode = 'pages'
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 class="grid grid-cols-[1fr_auto_1fr] items-center gap-3 px-4 py-2 border-b bg-secondary">
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-semibold">
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 v-if="!isTemplateSite" class="flex items-center gap-3 justify-end">
1401
- <Transition name="fade" mode="out-in">
1402
- <div v-if="isSiteDiff || isAnyPagesDiff" key="unpublished" class="flex gap-2 items-center">
1403
- <div class="flex gap-1 items-center bg-yellow-100 text-xs py-1 px-3 text-yellow-800 rounded">
1404
- <CircleAlert class="!text-yellow-800 w-3 h-6" />
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
- {{ isSiteDiff ? 'Unpublished Settings' : 'Unpublished Pages' }}
1906
+ {{ useMenuPublishLabels ? 'Menu Published' : 'Settings Published' }}
1407
1907
  </span>
1408
1908
  </div>
1409
- <edge-shad-button
1410
- class="h-8 px-4 text-xs gap-2 bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm"
1411
- :disabled="state.publishSiteLoading"
1412
- @click="publishSiteAndSettings"
1413
- >
1414
- <Loader2 v-if="state.publishSiteLoading" class="h-3.5 w-3.5 animate-spin" />
1415
- <FolderUp v-else class="h-3.5 w-3.5" />
1416
- Publish Site
1417
- </edge-shad-button>
1418
- </div>
1419
- <div v-else key="published" class="flex gap-1 items-center bg-green-100 text-xs py-1 px-3 text-green-800 rounded">
1420
- <FileCheck class="!text-green-800 w-3 h-6" />
1421
- <span class="font-medium text-[10px]">
1422
- Settings Published
1423
- </span>
1424
- </div>
1425
- </Transition>
1426
- <DropdownMenu>
1427
- <DropdownMenuTrigger as-child>
1428
- <edge-shad-button variant="outline" size="icon" class="h-9 w-9">
1429
- <MoreHorizontal />
1430
- </edge-shad-button>
1431
- </DropdownMenuTrigger>
1432
- <DropdownMenuContent side="right" align="start">
1433
- <DropdownMenuLabel class="flex items-center gap-2">
1434
- <FileStack class="w-5 h-5" />{{ siteData.name || 'Templates' }}
1435
- </DropdownMenuLabel>
1436
-
1437
- <DropdownMenuSeparator v-if="isSiteDiff" />
1438
- <DropdownMenuLabel v-if="isSiteDiff" class="flex items-center gap-2">
1439
- Site Settings
1440
- </DropdownMenuLabel>
1441
-
1442
- <DropdownMenuItem v-if="isSiteDiff" class="pl-4 text-xs" @click="publishSiteSettings">
1443
- <FolderUp />
1444
- Publish
1445
- </DropdownMenuItem>
1446
- <DropdownMenuItem v-if="isSiteDiff && isSiteSettingPublished" class="pl-4 text-xs" @click="discardSiteSettings">
1447
- <FolderX />
1448
- Discard Changes
1449
- </DropdownMenuItem>
1450
- <DropdownMenuSeparator />
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-sidebar text-sidebar-foreground" :default-size="16">
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 overflow-y-auto p-6">
1662
- <div class="mx-auto w-full max-w-5xl space-y-6">
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
- <Sheet v-model:open="state.siteSettings">
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="true"
2251
+ :show-users="!cmsMultiOrg"
1701
2252
  :show-theme-fields="true"
1702
2253
  :is-admin="isAdmin"
1703
2254
  :enable-media-picker="true"