@edgedev/create-edge-app 1.1.27 → 1.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/edge/components/cms/block.vue +334 -26
- package/edge/components/cms/blockEditor.vue +50 -3
- package/edge/components/cms/codeEditor.vue +15 -0
- package/edge/components/cms/init_blocks/footer.html +111 -19
- package/edge/components/cms/init_blocks/image.html +8 -0
- package/edge/components/cms/init_blocks/post_content.html +3 -2
- package/edge/components/cms/init_blocks/post_title_header.html +8 -6
- package/edge/components/cms/init_blocks/posts_list.html +6 -5
- package/edge/components/cms/mediaCard.vue +13 -2
- package/edge/components/cms/mediaManager.vue +16 -2
- package/edge/components/cms/menu.vue +253 -42
- package/edge/components/cms/page.vue +151 -18
- package/edge/components/cms/site.vue +517 -372
- package/edge/components/cms/siteSettingsForm.vue +616 -0
- package/edge/components/cms/themeDefaultMenu.vue +258 -22
- package/edge/components/cms/themeEditor.vue +95 -11
- package/edge/components/editor.vue +1 -0
- package/edge/components/formSubtypes/myOrgs.vue +112 -1
- package/edge/components/orgSwitcher.vue +1 -1
- package/edge/components/organizationMembers.vue +171 -21
- package/edge/components/shad/html.vue +6 -0
- package/edge/components/sideBar.vue +7 -4
- package/edge/components/sideBarContent.vue +1 -1
- package/edge/components/userMenu.vue +50 -14
- package/edge/composables/siteSettingsTemplate.js +79 -0
- package/edge/composables/structuredDataTemplates.js +36 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { AlertTriangle, ArrowDown, ArrowUp, Maximize2, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
|
2
|
+
import { AlertTriangle, ArrowDown, ArrowUp, Maximize2, Monitor, Smartphone, Sparkles, Tablet, UploadCloud } from 'lucide-vue-next'
|
|
3
3
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
4
4
|
import * as z from 'zod'
|
|
5
5
|
const props = defineProps({
|
|
@@ -20,6 +20,7 @@ const props = defineProps({
|
|
|
20
20
|
const emit = defineEmits(['head'])
|
|
21
21
|
|
|
22
22
|
const edgeFirebase = inject('edgeFirebase')
|
|
23
|
+
const { buildPageStructuredData } = useStructuredDataTemplates()
|
|
23
24
|
|
|
24
25
|
const state = reactive({
|
|
25
26
|
newDocs: {
|
|
@@ -29,11 +30,17 @@ const state = reactive({
|
|
|
29
30
|
postContent: { value: [] },
|
|
30
31
|
structure: { value: [] },
|
|
31
32
|
postStructure: { value: [] },
|
|
33
|
+
metaTitle: { value: '' },
|
|
34
|
+
metaDescription: { value: '' },
|
|
35
|
+
structuredData: { value: buildPageStructuredData() },
|
|
32
36
|
},
|
|
33
37
|
},
|
|
34
38
|
editMode: false,
|
|
35
39
|
showUnpublishedChangesDialog: false,
|
|
40
|
+
publishLoading: false,
|
|
36
41
|
workingDoc: {},
|
|
42
|
+
seoAiLoading: false,
|
|
43
|
+
seoAiError: '',
|
|
37
44
|
previewViewport: 'full',
|
|
38
45
|
newRowLayout: '6',
|
|
39
46
|
newPostRowLayout: '6',
|
|
@@ -200,6 +207,40 @@ const ensureBlocksArray = (workingDoc, key) => {
|
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
|
|
210
|
+
const applySeoAiResults = (payload) => {
|
|
211
|
+
if (!payload || typeof payload !== 'object')
|
|
212
|
+
return
|
|
213
|
+
if (payload.metaTitle)
|
|
214
|
+
state.workingDoc.metaTitle = payload.metaTitle
|
|
215
|
+
if (payload.metaDescription)
|
|
216
|
+
state.workingDoc.metaDescription = payload.metaDescription
|
|
217
|
+
if (payload.structuredData)
|
|
218
|
+
state.workingDoc.structuredData = payload.structuredData
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const updateSeoWithAi = async () => {
|
|
222
|
+
if (!edgeFirebase?.user?.uid)
|
|
223
|
+
return
|
|
224
|
+
state.seoAiLoading = true
|
|
225
|
+
state.seoAiError = ''
|
|
226
|
+
try {
|
|
227
|
+
const results = await edgeFirebase.runFunction('cms-updateSeoFromAi', {
|
|
228
|
+
orgId: edgeGlobal.edgeState.currentOrganization,
|
|
229
|
+
siteId: props.site,
|
|
230
|
+
pageId: props.page,
|
|
231
|
+
uid: edgeFirebase.user.uid,
|
|
232
|
+
})
|
|
233
|
+
applySeoAiResults(results?.data || {})
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.error('Failed to update SEO with AI', error)
|
|
237
|
+
state.seoAiError = 'Failed to update SEO. Try again.'
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
state.seoAiLoading = false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
203
244
|
const createRow = (columns = 1) => {
|
|
204
245
|
const row = {
|
|
205
246
|
id: edgeGlobal.generateShortId(),
|
|
@@ -398,6 +439,54 @@ const blockPick = (block, index, slotProps, post = false) => {
|
|
|
398
439
|
}
|
|
399
440
|
}
|
|
400
441
|
|
|
442
|
+
const applyCollectionUniqueKeys = (workingDoc) => {
|
|
443
|
+
const resolveUniqueKey = (template) => {
|
|
444
|
+
if (!template || typeof template !== 'string')
|
|
445
|
+
return ''
|
|
446
|
+
let resolved = template
|
|
447
|
+
const orgId = edgeGlobal.edgeState.currentOrganization || ''
|
|
448
|
+
const siteId = props.site || ''
|
|
449
|
+
if (resolved.includes('{orgId}') && orgId)
|
|
450
|
+
resolved = resolved.replaceAll('{orgId}', orgId)
|
|
451
|
+
if (resolved.includes('{siteId}') && siteId)
|
|
452
|
+
resolved = resolved.replaceAll('{siteId}', siteId)
|
|
453
|
+
return resolved
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const applyToBlocks = (blocks) => {
|
|
457
|
+
if (!Array.isArray(blocks))
|
|
458
|
+
return
|
|
459
|
+
blocks.forEach((block) => {
|
|
460
|
+
const meta = block?.meta
|
|
461
|
+
if (!meta || typeof meta !== 'object')
|
|
462
|
+
return
|
|
463
|
+
Object.keys(meta).forEach((fieldKey) => {
|
|
464
|
+
const cfg = meta[fieldKey]
|
|
465
|
+
if (!cfg?.collection?.uniqueKey)
|
|
466
|
+
return
|
|
467
|
+
const resolved = resolveUniqueKey(cfg.collection.uniqueKey)
|
|
468
|
+
if (!resolved)
|
|
469
|
+
return
|
|
470
|
+
if (cfg.queryItems && !Object.prototype.hasOwnProperty.call(cfg, 'uniqueKey')) {
|
|
471
|
+
const reordered = {}
|
|
472
|
+
Object.keys(cfg).forEach((key) => {
|
|
473
|
+
reordered[key] = cfg[key]
|
|
474
|
+
if (key === 'queryItems')
|
|
475
|
+
reordered.uniqueKey = resolved
|
|
476
|
+
})
|
|
477
|
+
block.meta[fieldKey] = reordered
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
cfg.uniqueKey = resolved
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
applyToBlocks(workingDoc?.content)
|
|
487
|
+
applyToBlocks(workingDoc?.postContent)
|
|
488
|
+
}
|
|
489
|
+
|
|
401
490
|
onMounted(() => {
|
|
402
491
|
if (props.page === 'new') {
|
|
403
492
|
state.editMode = true
|
|
@@ -408,6 +497,7 @@ const editorDocUpdates = (workingDoc) => {
|
|
|
408
497
|
ensureStructureDefaults(workingDoc, false)
|
|
409
498
|
if (workingDoc?.post || (Array.isArray(workingDoc?.postContent) && workingDoc.postContent.length > 0) || Array.isArray(workingDoc?.postStructure))
|
|
410
499
|
ensureStructureDefaults(workingDoc, true)
|
|
500
|
+
applyCollectionUniqueKeys(workingDoc)
|
|
411
501
|
const blockIds = (workingDoc.content || []).map(block => block.blockId).filter(id => id)
|
|
412
502
|
const postBlockIds = workingDoc.postContent ? workingDoc.postContent.map(block => block.blockId).filter(id => id) : []
|
|
413
503
|
blockIds.push(...postBlockIds)
|
|
@@ -520,6 +610,15 @@ const layoutSpansFromString = (value, fallback = [6]) => {
|
|
|
520
610
|
|
|
521
611
|
const rowUsesSpans = row => (row?.columns || []).some(col => Number.isFinite(col?.span))
|
|
522
612
|
|
|
613
|
+
const rowGapClass = (row) => {
|
|
614
|
+
const gap = Number(row?.gap)
|
|
615
|
+
const allowed = new Set([0, 2, 4, 6, 8])
|
|
616
|
+
const safeGap = allowed.has(gap) ? gap : 4
|
|
617
|
+
if (safeGap === 0)
|
|
618
|
+
return 'gap-0'
|
|
619
|
+
return ['gap-0', `sm:gap-${safeGap}`].join(' ')
|
|
620
|
+
}
|
|
621
|
+
|
|
523
622
|
const rowGridClass = (row) => {
|
|
524
623
|
const base = isMobilePreview.value
|
|
525
624
|
? 'grid grid-cols-1'
|
|
@@ -542,15 +641,6 @@ const rowVerticalAlignClass = (row) => {
|
|
|
542
641
|
return map[row?.verticalAlign] || map.start
|
|
543
642
|
}
|
|
544
643
|
|
|
545
|
-
const rowGapClass = (row) => {
|
|
546
|
-
const gap = Number(row?.gap)
|
|
547
|
-
const allowed = new Set([0, 2, 4, 6, 8])
|
|
548
|
-
const safeGap = allowed.has(gap) ? gap : 4
|
|
549
|
-
if (safeGap === 0)
|
|
550
|
-
return 'gap-0'
|
|
551
|
-
return ['gap-0', `sm:gap-${safeGap}`].join(' ')
|
|
552
|
-
}
|
|
553
|
-
|
|
554
644
|
const rowGridStyle = (row) => {
|
|
555
645
|
if (isMobilePreview.value)
|
|
556
646
|
return {}
|
|
@@ -1001,6 +1091,21 @@ const unpublishedChangeDetails = computed(() => {
|
|
|
1001
1091
|
return changes
|
|
1002
1092
|
})
|
|
1003
1093
|
|
|
1094
|
+
const publishPage = async (pageId) => {
|
|
1095
|
+
if (state.publishLoading)
|
|
1096
|
+
return
|
|
1097
|
+
const pageData = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/pages`] || {}
|
|
1098
|
+
if (!pageData[pageId])
|
|
1099
|
+
return
|
|
1100
|
+
state.publishLoading = true
|
|
1101
|
+
try {
|
|
1102
|
+
await edgeFirebase.storeDoc(`${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/published`, pageData[pageId])
|
|
1103
|
+
}
|
|
1104
|
+
finally {
|
|
1105
|
+
state.publishLoading = false
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1004
1109
|
const hasUnsavedChanges = (changes) => {
|
|
1005
1110
|
console.log('Unsaved changes:', changes)
|
|
1006
1111
|
if (changes === true) {
|
|
@@ -1034,14 +1139,25 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1034
1139
|
<div class="w-full border-t border-gray-300 dark:border-white/15" aria-hidden="true" />
|
|
1035
1140
|
<div v-if="!props.isTemplateSite" class="px-4 text-gray-600 dark:text-gray-300 whitespace-nowrap text-center flex flex-col items-center gap-1">
|
|
1036
1141
|
<template v-if="isPublishedPageDiff(page)">
|
|
1037
|
-
<
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1142
|
+
<div class="flex items-center gap-2">
|
|
1143
|
+
<edge-shad-button
|
|
1144
|
+
variant="outline"
|
|
1145
|
+
class="bg-yellow-100 text-yellow-800 border-yellow-300 hover:bg-yellow-100 hover:text-yellow-900 text-xs h-[32px] gap-1"
|
|
1146
|
+
@click="state.showUnpublishedChangesDialog = true"
|
|
1147
|
+
>
|
|
1148
|
+
<AlertTriangle class="w-4 h-4" />
|
|
1149
|
+
Unpublished Changes
|
|
1150
|
+
</edge-shad-button>
|
|
1151
|
+
<edge-shad-button
|
|
1152
|
+
class="bg-primary text-primary-foreground hover:bg-primary/90 text-xs h-[32px] gap-1 shadow-sm"
|
|
1153
|
+
:disabled="state.publishLoading"
|
|
1154
|
+
@click="publishPage(page)"
|
|
1155
|
+
>
|
|
1156
|
+
<Loader2 v-if="state.publishLoading" class="w-4 h-4 animate-spin" />
|
|
1157
|
+
<UploadCloud v-else class="w-4 h-4" />
|
|
1158
|
+
Publish
|
|
1159
|
+
</edge-shad-button>
|
|
1160
|
+
</div>
|
|
1045
1161
|
</template>
|
|
1046
1162
|
<template v-else>
|
|
1047
1163
|
<edge-chip class="bg-green-100 text-green-800">
|
|
@@ -1121,6 +1237,23 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1121
1237
|
</div>
|
|
1122
1238
|
</div>
|
|
1123
1239
|
</template>
|
|
1240
|
+
<template #success-alert>
|
|
1241
|
+
<div v-if="!props.isTemplateSite" class="mt-2 flex flex-wrap items-center gap-2">
|
|
1242
|
+
<edge-shad-button
|
|
1243
|
+
variant="outline"
|
|
1244
|
+
class="text-xs h-[28px] gap-1"
|
|
1245
|
+
:disabled="state.seoAiLoading"
|
|
1246
|
+
@click="updateSeoWithAi"
|
|
1247
|
+
>
|
|
1248
|
+
<Loader2 v-if="state.seoAiLoading" class="w-3.5 h-3.5 animate-spin" />
|
|
1249
|
+
<Sparkles v-else class="w-3.5 h-3.5" />
|
|
1250
|
+
Update SEO with AI
|
|
1251
|
+
</edge-shad-button>
|
|
1252
|
+
<span v-if="state.seoAiError" class="text-xs text-destructive">
|
|
1253
|
+
{{ state.seoAiError }}
|
|
1254
|
+
</span>
|
|
1255
|
+
</div>
|
|
1256
|
+
</template>
|
|
1124
1257
|
<template #main="slotProps">
|
|
1125
1258
|
<Tabs class="w-full" default-value="list">
|
|
1126
1259
|
<TabsList v-if="slotProps.workingDoc?.post" class="w-full mt-3 bg-primary rounded-sm">
|