@edgedev/create-edge-app 1.1.29 → 1.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -382,12 +382,82 @@ const resolveBlockForPreview = (block) => {
382
382
  return null
383
383
  }
384
384
 
385
- const templateHasBlocks = template => Array.isArray(template?.content) && template.content.length > 0
385
+ const normalizePreviewColumns = (row) => {
386
+ if (!Array.isArray(row?.columns) || !row.columns.length)
387
+ return []
388
+ return row.columns.map((column, idx) => ({
389
+ id: column?.id || `${row?.id || 'row'}-col-${idx}`,
390
+ span: Number(column?.span) || null,
391
+ blocks: Array.isArray(column?.blocks) ? column.blocks.filter(Boolean) : [],
392
+ }))
393
+ }
394
+
395
+ const templatePreviewRows = (template) => {
396
+ const structureRows = Array.isArray(template?.structure) ? template.structure : []
397
+ if (structureRows.length) {
398
+ return structureRows
399
+ .map((row, rowIndex) => ({
400
+ id: row?.id || `${template?.docId || 'template'}-row-${rowIndex}`,
401
+ columns: normalizePreviewColumns(row),
402
+ }))
403
+ .filter(row => row.columns.length > 0)
404
+ }
386
405
 
387
- const templatePreviewBlocks = (template) => {
388
- if (!templateHasBlocks(template))
406
+ const legacyBlocks = Array.isArray(template?.content) ? template.content.filter(Boolean) : []
407
+ if (!legacyBlocks.length)
389
408
  return []
390
- return template.content
409
+ return [{
410
+ id: `${template?.docId || 'template'}-legacy-row`,
411
+ columns: [{
412
+ id: `${template?.docId || 'template'}-legacy-col`,
413
+ span: null,
414
+ blocks: legacyBlocks,
415
+ }],
416
+ }]
417
+ }
418
+
419
+ const templateHasPreview = template => templatePreviewRows(template).length > 0
420
+
421
+ const resolveTemplateBlockSource = (template, blockRef) => {
422
+ if (!blockRef)
423
+ return null
424
+ if (typeof blockRef === 'object')
425
+ return blockRef
426
+ const lookupId = String(blockRef).trim()
427
+ if (!lookupId)
428
+ return null
429
+ const templateBlocks = Array.isArray(template?.content) ? template.content : []
430
+ return templateBlocks.find(block => block?.id === lookupId || block?.blockId === lookupId) || null
431
+ }
432
+
433
+ const resolveTemplateBlockForPreview = (template, blockRef) => {
434
+ const source = resolveTemplateBlockSource(template, blockRef)
435
+ return resolveBlockForPreview(source)
436
+ }
437
+
438
+ const hasPreviewSpans = row => (row?.columns || []).some(column => Number.isFinite(Number(column?.span)))
439
+
440
+ const previewGridClass = (row) => {
441
+ if (hasPreviewSpans(row))
442
+ return 'grid grid-cols-1 sm:grid-cols-6 gap-4'
443
+ const count = row?.columns?.length || 1
444
+ const map = {
445
+ 1: 'grid grid-cols-1 gap-4',
446
+ 2: 'grid grid-cols-1 sm:grid-cols-2 gap-4',
447
+ 3: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4',
448
+ 4: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4',
449
+ 5: 'grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-5 gap-4',
450
+ 6: 'grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-6 gap-4',
451
+ }
452
+ return map[count] || map[1]
453
+ }
454
+
455
+ const previewColumnStyle = (column) => {
456
+ const span = Number(column?.span)
457
+ if (!Number.isFinite(span))
458
+ return {}
459
+ const safeSpan = Math.min(Math.max(span, 1), 6)
460
+ return { gridColumn: `span ${safeSpan} / span ${safeSpan}` }
391
461
  }
392
462
 
393
463
  const renameFolderOrPageShow = (item) => {
@@ -1155,19 +1225,34 @@ const theme = computed(() => {
1155
1225
  Blank page
1156
1226
  </div>
1157
1227
  </template>
1158
- <template v-else-if="templateHasBlocks(template)">
1228
+ <template v-else-if="templateHasPreview(template)">
1159
1229
  <div
1160
- v-for="(block, idx) in templatePreviewBlocks(template)"
1161
- :key="`${template.docId}-block-${idx}`"
1230
+ v-for="(row, rowIndex) in templatePreviewRows(template)"
1231
+ :key="`${template.docId}-row-${row.id || rowIndex}`"
1232
+ class="w-full"
1162
1233
  >
1163
- <edge-cms-block-api
1164
- v-if="resolveBlockForPreview(block)"
1165
- :content="resolveBlockForPreview(block).content"
1166
- :values="resolveBlockForPreview(block).values"
1167
- :meta="resolveBlockForPreview(block).meta"
1168
- :theme="theme"
1169
- :isolated="true"
1170
- />
1234
+ <div :class="previewGridClass(row)">
1235
+ <div
1236
+ v-for="(column, colIndex) in row.columns"
1237
+ :key="`${template.docId}-row-${row.id || rowIndex}-col-${column.id || colIndex}`"
1238
+ class="min-w-0"
1239
+ :style="previewColumnStyle(column)"
1240
+ >
1241
+ <div
1242
+ v-for="(blockRef, blockIdx) in column.blocks || []"
1243
+ :key="`${template.docId}-row-${row.id || rowIndex}-col-${column.id || colIndex}-block-${blockIdx}`"
1244
+ >
1245
+ <edge-cms-block-api
1246
+ v-if="resolveTemplateBlockForPreview(template, blockRef)"
1247
+ :content="resolveTemplateBlockForPreview(template, blockRef).content"
1248
+ :values="resolveTemplateBlockForPreview(template, blockRef).values"
1249
+ :meta="resolveTemplateBlockForPreview(template, blockRef).meta"
1250
+ :theme="theme"
1251
+ :isolated="true"
1252
+ />
1253
+ </div>
1254
+ </div>
1255
+ </div>
1171
1256
  </div>
1172
1257
  </template>
1173
1258
  <template v-else>
@@ -14,6 +14,11 @@ const props = defineProps({
14
14
  required: false,
15
15
  default: '',
16
16
  },
17
+ disableAddSiteForNonAdmin: {
18
+ type: Boolean,
19
+ required: false,
20
+ default: false,
21
+ },
17
22
  })
18
23
  const edgeFirebase = inject('edgeFirebase')
19
24
  const { createDefaults: createSiteSettingsDefaults, createNewDocSchema: createSiteSettingsNewDocSchema } = useSiteSettingsTemplate()
@@ -137,6 +142,17 @@ const schemas = {
137
142
  const isAdmin = computed(() => {
138
143
  return edgeGlobal.isAdminGlobal(edgeFirebase).value
139
144
  })
145
+ const currentOrgRoleName = computed(() => {
146
+ return String(edgeGlobal.getRoleName(edgeFirebase?.user?.roles || [], edgeGlobal.edgeState.currentOrganization) || '').toLowerCase()
147
+ })
148
+ const isOrgAdmin = computed(() => {
149
+ return currentOrgRoleName.value === 'admin'
150
+ })
151
+ const canCreateSite = computed(() => {
152
+ if (!props.disableAddSiteForNonAdmin)
153
+ return true
154
+ return isOrgAdmin.value
155
+ })
140
156
 
141
157
  const siteData = computed(() => {
142
158
  return edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`]?.[props.site] || {}
@@ -362,6 +378,65 @@ const userOptions = computed(() => {
362
378
  }))
363
379
  .sort((a, b) => a.label.localeCompare(b.label))
364
380
  })
381
+ const authUid = computed(() => String(edgeFirebase?.user?.uid || '').trim())
382
+ const currentOrgUser = computed(() => {
383
+ if (!authUid.value)
384
+ return null
385
+ const users = Object.values(orgUsers.value || {})
386
+ return users.find((user) => {
387
+ const userId = String(user?.userId || '').trim()
388
+ const docId = String(user?.docId || '').trim()
389
+ const uid = String(user?.uid || '').trim()
390
+ return userId === authUid.value || docId === authUid.value || uid === authUid.value
391
+ }) || null
392
+ })
393
+ const currentOrgUserId = computed(() => {
394
+ return String(
395
+ currentOrgUser.value?.userId
396
+ || currentOrgUser.value?.docId
397
+ || authUid.value
398
+ || '',
399
+ ).trim()
400
+ })
401
+ const currentUserOption = computed(() => {
402
+ if (!currentOrgUserId.value)
403
+ return null
404
+ return {
405
+ value: currentOrgUserId.value,
406
+ label: currentOrgUser.value?.meta?.name || currentOrgUser.value?.meta?.email || currentOrgUserId.value,
407
+ }
408
+ })
409
+ const shouldForceCurrentUserForNewSite = computed(() => !isAdmin.value && props.site === 'new')
410
+ const aiUserOptions = computed(() => {
411
+ if (!shouldForceCurrentUserForNewSite.value)
412
+ return userOptions.value
413
+ return currentUserOption.value ? [currentUserOption.value] : []
414
+ })
415
+ const normalizeUserIds = items => (Array.isArray(items) ? items : [])
416
+ .map(item => String(item || '').trim())
417
+ .filter(Boolean)
418
+ const getSiteUsersModel = (workingDoc) => {
419
+ if (!workingDoc || typeof workingDoc !== 'object')
420
+ return []
421
+ const users = normalizeUserIds(workingDoc?.users)
422
+ if (!shouldForceCurrentUserForNewSite.value)
423
+ return users
424
+ if (!currentOrgUserId.value)
425
+ return users
426
+ if (users.length === 1 && users[0] === currentOrgUserId.value)
427
+ return users
428
+ workingDoc.users = [currentOrgUserId.value]
429
+ return workingDoc.users
430
+ }
431
+ const updateSiteUsersModel = (workingDoc, value) => {
432
+ if (!workingDoc || typeof workingDoc !== 'object')
433
+ return
434
+ if (shouldForceCurrentUserForNewSite.value) {
435
+ workingDoc.users = currentOrgUserId.value ? [currentOrgUserId.value] : []
436
+ return
437
+ }
438
+ workingDoc.users = normalizeUserIds(value)
439
+ }
365
440
 
366
441
  const themeItemsForAllowed = (allowed, current) => {
367
442
  const base = themeOptions.value
@@ -1133,7 +1208,7 @@ const pageSettingsUpdated = async (pageData) => {
1133
1208
  v-if="edgeGlobal.edgeState.organizationDocPath"
1134
1209
  >
1135
1210
  <edge-editor
1136
- v-if="!props.page && props.site === 'new'"
1211
+ v-if="!props.page && props.site === 'new' && canCreateSite"
1137
1212
  collection="sites"
1138
1213
  :doc-id="props.site"
1139
1214
  :schema="schemas.sites"
@@ -1215,7 +1290,8 @@ const pageSettingsUpdated = async (pageData) => {
1215
1290
  />
1216
1291
  <edge-shad-select-tags
1217
1292
  v-if="Object.keys(orgUsers).length > 0"
1218
- v-model="slotProps.workingDoc.users" :disabled="!edgeGlobal.isAdminGlobal(edgeFirebase).value"
1293
+ :model-value="getSiteUsersModel(slotProps.workingDoc)"
1294
+ :disabled="shouldForceCurrentUserForNewSite || !edgeGlobal.isAdminGlobal(edgeFirebase).value"
1219
1295
  :items="userOptions"
1220
1296
  name="users"
1221
1297
  label="Users"
@@ -1224,6 +1300,7 @@ const pageSettingsUpdated = async (pageData) => {
1224
1300
  placeholder="Select users"
1225
1301
  class="w-full"
1226
1302
  :multiple="true"
1303
+ @update:model-value="value => updateSiteUsersModel(slotProps.workingDoc, value)"
1227
1304
  />
1228
1305
  <div class="rounded-lg border border-dashed border-slate-200 p-4 ">
1229
1306
  <div class="flex items-start justify-between gap-3">
@@ -1248,7 +1325,7 @@ const pageSettingsUpdated = async (pageData) => {
1248
1325
  label="User Data for AI to use to build initial site"
1249
1326
  placeholder="- select one -"
1250
1327
  class="w-full"
1251
- :items="userOptions"
1328
+ :items="aiUserOptions"
1252
1329
  item-title="label"
1253
1330
  item-value="value"
1254
1331
  @update:model-value="value => (slotProps.workingDoc.aiAgentUserId = value || '')"
@@ -1265,6 +1342,9 @@ const pageSettingsUpdated = async (pageData) => {
1265
1342
  </div>
1266
1343
  </template>
1267
1344
  </edge-editor>
1345
+ <div v-else-if="!props.page && props.site === 'new' && !canCreateSite" class="p-6 text-sm text-red-600">
1346
+ Only organization admins can create sites.
1347
+ </div>
1268
1348
  <div v-else class="flex flex-col h-[calc(100vh-58px)] overflow-hidden">
1269
1349
  <div class="grid grid-cols-[1fr_auto_1fr] items-center gap-3 px-4 py-2 border-b bg-secondary">
1270
1350
  <div class="flex items-center gap-3">
@@ -132,7 +132,10 @@ watch(headObject, (newHeadElements) => {
132
132
  }, { immediate: true, deep: true })
133
133
 
134
134
  const sites = computed(() => {
135
- return Object.values(edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`] || {})
135
+ const sitesMap = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`] || {}
136
+ return Object.entries(sitesMap)
137
+ .map(([docId, data]) => ({ docId, ...(data || {}) }))
138
+ .filter(site => site.docId && site.docId !== 'templates')
136
139
  })
137
140
 
138
141
  const templatePages = computed(() => {
@@ -324,9 +327,12 @@ const templatePageOptions = computed(() => {
324
327
 
325
328
  watch (sites, async (newSites) => {
326
329
  state.loading = true
327
- if (!edgeGlobal.edgeState.blockEditorSite && newSites.length > 0) {
330
+ const selectedSite = String(edgeGlobal.edgeState.blockEditorSite || '').trim()
331
+ const hasSelectedSite = newSites.some(site => site.docId === selectedSite)
332
+ if ((!selectedSite || !hasSelectedSite) && newSites.length > 0)
328
333
  edgeGlobal.edgeState.blockEditorSite = newSites[0].docId
329
- }
334
+ else if (!newSites.length)
335
+ edgeGlobal.edgeState.blockEditorSite = ''
330
336
  await nextTick()
331
337
  state.loading = false
332
338
  }, { immediate: true, deep: true })
@@ -170,6 +170,20 @@ const getByPath = (obj, path) => {
170
170
  }, obj)
171
171
  }
172
172
 
173
+ const normalizeCollectionItems = (collectionMap = {}) => {
174
+ const items = Object.entries(collectionMap).map(([docId, data]) => {
175
+ const source = (data && typeof data === 'object') ? data : {}
176
+ return {
177
+ ...source,
178
+ docId: String(source.docId || docId || ''),
179
+ }
180
+ })
181
+ if (props.collection === 'sites') {
182
+ return items.filter(item => item.docId !== 'templates')
183
+ }
184
+ return items
185
+ }
186
+
173
187
  const snapShotQuery = computed(() => {
174
188
  if (state.queryField && state.queryValue) {
175
189
  // console.log('snapShotQuery', state.queryField, state.queryOperator, state.queryValue)
@@ -232,7 +246,10 @@ const allData = computed(() => {
232
246
  if (!edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]) {
233
247
  return []
234
248
  }
235
- return Object.values(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`])
249
+ return normalizeCollectionItems(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`])
250
+ }
251
+ if (props.collection === 'sites') {
252
+ return state.paginatedResults.filter(item => String(item?.docId || '') !== 'templates')
236
253
  }
237
254
  return state.paginatedResults
238
255
  })
@@ -243,10 +260,12 @@ const filtered = computed(() => {
243
260
  if (!edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]) {
244
261
  return []
245
262
  }
246
- allData = Object.values(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`])
263
+ allData = normalizeCollectionItems(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`])
247
264
  }
248
265
  else {
249
- allData = state.paginatedResults
266
+ allData = props.collection === 'sites'
267
+ ? state.paginatedResults.filter(item => String(item?.docId || '') !== 'templates')
268
+ : state.paginatedResults
250
269
  }
251
270
 
252
271
  const qRaw = filterText.value