@edgedev/create-edge-app 1.2.34 → 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 +60 -1
- package/edge/components/cms/block.vue +763 -301
- package/edge/components/cms/blockEditor.vue +342 -29
- package/edge/components/cms/blockPicker.vue +3 -3
- package/edge/components/cms/blocksManager.vue +48 -13
- package/edge/components/cms/htmlContent.vue +601 -10
- 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 +92 -31
- package/edge/components/cms/page.vue +165 -50
- package/edge/components/cms/posts.vue +13 -4
- package/edge/components/cms/site.vue +15 -4
- package/edge/components/cms/siteSettingsForm.vue +19 -9
- package/edge/components/cms/themeDefaultMenu.vue +20 -2
- package/edge/composables/siteSettingsTemplate.js +2 -0
- 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/package.json +1 -1
|
@@ -22,6 +22,16 @@ const emit = defineEmits(['head'])
|
|
|
22
22
|
const edgeFirebase = inject('edgeFirebase')
|
|
23
23
|
const router = useRouter()
|
|
24
24
|
const { buildPageStructuredData } = useStructuredDataTemplates()
|
|
25
|
+
const cmsMultiOrg = useState('cmsMultiOrg', () => true)
|
|
26
|
+
const isAdmin = computed(() => edgeGlobal.isAdminGlobal(edgeFirebase).value)
|
|
27
|
+
const isDevModeEnabled = computed(() => process.dev || Boolean(edgeGlobal.edgeState.devOverride))
|
|
28
|
+
const canOpenPreviewBlockContentEditor = computed(() => {
|
|
29
|
+
if (!isAdmin.value)
|
|
30
|
+
return false
|
|
31
|
+
if (cmsMultiOrg.value)
|
|
32
|
+
return true
|
|
33
|
+
return isDevModeEnabled.value
|
|
34
|
+
})
|
|
25
35
|
|
|
26
36
|
const state = reactive({
|
|
27
37
|
newDocs: {
|
|
@@ -50,6 +60,7 @@ const state = reactive({
|
|
|
50
60
|
importErrorDialogOpen: false,
|
|
51
61
|
importErrorMessage: '',
|
|
52
62
|
previewViewport: 'full',
|
|
63
|
+
previewPageView: 'list',
|
|
53
64
|
newRowLayout: '6',
|
|
54
65
|
newPostRowLayout: '6',
|
|
55
66
|
rowSettings: {
|
|
@@ -110,10 +121,8 @@ const previewViewportStyle = computed(() => {
|
|
|
110
121
|
})
|
|
111
122
|
|
|
112
123
|
const previewViewportContainStyle = computed(() => {
|
|
113
|
-
const shouldContain = !state.editMode
|
|
114
124
|
return {
|
|
115
125
|
...(previewViewportStyle.value || {}),
|
|
116
|
-
...(shouldContain ? { transform: 'translateZ(0)' } : {}),
|
|
117
126
|
}
|
|
118
127
|
})
|
|
119
128
|
|
|
@@ -121,6 +130,18 @@ const setPreviewViewport = (viewportId) => {
|
|
|
121
130
|
state.previewViewport = viewportId
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
const hasPostView = (workingDoc) => {
|
|
134
|
+
if (!workingDoc || typeof workingDoc !== 'object')
|
|
135
|
+
return false
|
|
136
|
+
return Boolean(workingDoc.post)
|
|
137
|
+
|| (Array.isArray(workingDoc.postContent) && workingDoc.postContent.length > 0)
|
|
138
|
+
|| (Array.isArray(workingDoc.postStructure) && workingDoc.postStructure.length > 0)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const setPreviewPageView = (view) => {
|
|
142
|
+
state.previewPageView = view === 'post' ? 'post' : 'list'
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
const previewViewportMode = computed(() => {
|
|
125
146
|
if (state.previewViewport === 'full')
|
|
126
147
|
return 'auto'
|
|
@@ -162,6 +183,14 @@ const ROW_GAP_OPTIONS = [
|
|
|
162
183
|
{ name: '8', title: 'X-Large' },
|
|
163
184
|
]
|
|
164
185
|
|
|
186
|
+
const ROW_GAP_CLASS_MAP = {
|
|
187
|
+
0: 'gap-0 sm:gap-0',
|
|
188
|
+
2: 'gap-0 sm:gap-2',
|
|
189
|
+
4: 'gap-0 sm:gap-4',
|
|
190
|
+
6: 'gap-0 sm:gap-6',
|
|
191
|
+
8: 'gap-0 sm:gap-8',
|
|
192
|
+
}
|
|
193
|
+
|
|
165
194
|
const ROW_MOBILE_STACK_OPTIONS = [
|
|
166
195
|
{ name: 'normal', title: 'Left first' },
|
|
167
196
|
{ name: 'reverse', title: 'Right first' },
|
|
@@ -467,17 +496,51 @@ const blockPick = (block, index, slotProps, post = false) => {
|
|
|
467
496
|
}
|
|
468
497
|
|
|
469
498
|
const applyCollectionUniqueKeys = (workingDoc) => {
|
|
499
|
+
const hasTemplateToken = (value) => {
|
|
500
|
+
if (typeof value === 'string')
|
|
501
|
+
return value.includes('{orgId}') || value.includes('{siteId}')
|
|
502
|
+
if (Array.isArray(value))
|
|
503
|
+
return value.some(entry => hasTemplateToken(entry))
|
|
504
|
+
if (value && typeof value === 'object')
|
|
505
|
+
return Object.values(value).some(entry => hasTemplateToken(entry))
|
|
506
|
+
return false
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const resolveTokens = (value) => {
|
|
510
|
+
if (typeof value === 'string') {
|
|
511
|
+
let resolved = value
|
|
512
|
+
const orgId = edgeGlobal.edgeState.currentOrganization || ''
|
|
513
|
+
const siteId = props.site || ''
|
|
514
|
+
if (resolved.includes('{orgId}') && orgId)
|
|
515
|
+
resolved = resolved.replaceAll('{orgId}', orgId)
|
|
516
|
+
if (resolved.includes('{siteId}') && siteId)
|
|
517
|
+
resolved = resolved.replaceAll('{siteId}', siteId)
|
|
518
|
+
return resolved
|
|
519
|
+
}
|
|
520
|
+
if (Array.isArray(value))
|
|
521
|
+
return value.map(entry => resolveTokens(entry))
|
|
522
|
+
if (value && typeof value === 'object') {
|
|
523
|
+
const out = {}
|
|
524
|
+
Object.keys(value).forEach((key) => {
|
|
525
|
+
out[key] = resolveTokens(value[key])
|
|
526
|
+
})
|
|
527
|
+
return out
|
|
528
|
+
}
|
|
529
|
+
return value
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const isEmptyQueryItem = (value) => {
|
|
533
|
+
if (value === undefined || value === null || value === '')
|
|
534
|
+
return true
|
|
535
|
+
if (Array.isArray(value))
|
|
536
|
+
return value.length === 0
|
|
537
|
+
return false
|
|
538
|
+
}
|
|
539
|
+
|
|
470
540
|
const resolveUniqueKey = (template) => {
|
|
471
541
|
if (!template || typeof template !== 'string')
|
|
472
542
|
return ''
|
|
473
|
-
|
|
474
|
-
const orgId = edgeGlobal.edgeState.currentOrganization || ''
|
|
475
|
-
const siteId = props.site || ''
|
|
476
|
-
if (resolved.includes('{orgId}') && orgId)
|
|
477
|
-
resolved = resolved.replaceAll('{orgId}', orgId)
|
|
478
|
-
if (resolved.includes('{siteId}') && siteId)
|
|
479
|
-
resolved = resolved.replaceAll('{siteId}', siteId)
|
|
480
|
-
return resolved
|
|
543
|
+
return resolveTokens(template)
|
|
481
544
|
}
|
|
482
545
|
|
|
483
546
|
const applyToBlocks = (blocks) => {
|
|
@@ -489,6 +552,27 @@ const applyCollectionUniqueKeys = (workingDoc) => {
|
|
|
489
552
|
return
|
|
490
553
|
Object.keys(meta).forEach((fieldKey) => {
|
|
491
554
|
const cfg = meta[fieldKey]
|
|
555
|
+
if (!cfg || typeof cfg !== 'object')
|
|
556
|
+
return
|
|
557
|
+
|
|
558
|
+
// Materialize tokenized collection.query filters (e.g. {siteId}) into queryItems
|
|
559
|
+
// so frontend hydration receives concrete runtime filter selections.
|
|
560
|
+
if (Array.isArray(cfg?.collection?.query)) {
|
|
561
|
+
if (!cfg.queryItems || typeof cfg.queryItems !== 'object')
|
|
562
|
+
cfg.queryItems = {}
|
|
563
|
+
for (const queryFilter of cfg.collection.query) {
|
|
564
|
+
const queryField = queryFilter?.field
|
|
565
|
+
if (!queryField || typeof queryField !== 'string')
|
|
566
|
+
continue
|
|
567
|
+
const rawValue = queryFilter?.value
|
|
568
|
+
if (!hasTemplateToken(rawValue))
|
|
569
|
+
continue
|
|
570
|
+
if (!isEmptyQueryItem(cfg.queryItems[queryField]))
|
|
571
|
+
continue
|
|
572
|
+
cfg.queryItems[queryField] = resolveTokens(rawValue)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
492
576
|
if (!cfg?.collection?.uniqueKey)
|
|
493
577
|
return
|
|
494
578
|
const resolved = resolveUniqueKey(cfg.collection.uniqueKey)
|
|
@@ -560,6 +644,8 @@ const editorDocUpdates = (workingDoc) => {
|
|
|
560
644
|
if (workingDoc?.post || (Array.isArray(workingDoc?.postContent) && workingDoc.postContent.length > 0) || Array.isArray(workingDoc?.postStructure))
|
|
561
645
|
ensureStructureDefaults(workingDoc, true)
|
|
562
646
|
applyCollectionUniqueKeys(workingDoc)
|
|
647
|
+
if (!hasPostView(workingDoc) && state.previewPageView === 'post')
|
|
648
|
+
state.previewPageView = 'list'
|
|
563
649
|
const blockIds = (workingDoc.content || []).map(block => block.blockId).filter(id => id)
|
|
564
650
|
const postBlockIds = workingDoc.postContent ? workingDoc.postContent.map(block => block.blockId).filter(id => id) : []
|
|
565
651
|
blockIds.push(...postBlockIds)
|
|
@@ -859,11 +945,7 @@ const rowUsesSpans = row => (row?.columns || []).some(col => Number.isFinite(col
|
|
|
859
945
|
|
|
860
946
|
const rowGapClass = (row) => {
|
|
861
947
|
const gap = Number(row?.gap)
|
|
862
|
-
|
|
863
|
-
const safeGap = allowed.has(gap) ? gap : 4
|
|
864
|
-
if (safeGap === 0)
|
|
865
|
-
return 'gap-0'
|
|
866
|
-
return ['gap-0', `sm:gap-${safeGap}`].join(' ')
|
|
948
|
+
return ROW_GAP_CLASS_MAP[gap] || ROW_GAP_CLASS_MAP[4]
|
|
867
949
|
}
|
|
868
950
|
|
|
869
951
|
const rowGridClass = (row) => {
|
|
@@ -1329,6 +1411,11 @@ const slugifyMenuPageName = (value) => {
|
|
|
1329
1411
|
.replace(/[^a-z0-9]+/g, '-')
|
|
1330
1412
|
.replace(/(^-|-$)+/g, '') || 'page'
|
|
1331
1413
|
}
|
|
1414
|
+
const titleFromSlug = (slug) => {
|
|
1415
|
+
if (!slug)
|
|
1416
|
+
return ''
|
|
1417
|
+
return String(slug).replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
|
1418
|
+
}
|
|
1332
1419
|
|
|
1333
1420
|
const makeUniqueMenuPageName = (value, existingNames = new Set()) => {
|
|
1334
1421
|
const base = slugifyMenuPageName(value)
|
|
@@ -1357,7 +1444,8 @@ const addImportedPageToSiteMenu = async (docId, pageName = '') => {
|
|
|
1357
1444
|
|
|
1358
1445
|
const existingNames = collectMenuPageNames(menus)
|
|
1359
1446
|
const menuName = makeUniqueMenuPageName(pageName || nextDocId, existingNames)
|
|
1360
|
-
|
|
1447
|
+
const menuTitle = String(pageName || '').trim() || titleFromSlug(menuName)
|
|
1448
|
+
menus['Site Root'].push({ name: menuName, menuTitle, item: nextDocId })
|
|
1361
1449
|
|
|
1362
1450
|
const results = await edgeFirebase.changeDoc(sitesCollectionPath, siteId, { menus })
|
|
1363
1451
|
if (results?.success === false)
|
|
@@ -1776,7 +1864,7 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1776
1864
|
:doc-id="page"
|
|
1777
1865
|
:schema="schemas.pages"
|
|
1778
1866
|
:new-doc-schema="state.newDocs.pages"
|
|
1779
|
-
class="w-full mx-auto flex-1 bg-transparent flex flex-col border-none shadow-none pt-0 px-0"
|
|
1867
|
+
class="w-full mx-auto flex-1 bg-transparent flex flex-col border-none shadow-none pt-0 px-0" :class="[!state.editMode ? 'cms-page-preview-mode' : '']"
|
|
1780
1868
|
:show-footer="false"
|
|
1781
1869
|
:save-redirect-override="`/app/dashboard/sites/${site}`"
|
|
1782
1870
|
:no-close-after-save="true"
|
|
@@ -1785,7 +1873,7 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1785
1873
|
@unsaved-changes="hasUnsavedChanges"
|
|
1786
1874
|
>
|
|
1787
1875
|
<template #header="slotProps">
|
|
1788
|
-
<div class="relative flex items-center p-2 justify-between
|
|
1876
|
+
<div class="relative flex items-center p-2 justify-between top-0 z-50 bg-gray-100 rounded h-[50px]">
|
|
1789
1877
|
<span class="text-lg font-semibold whitespace-nowrap pr-1">{{ pageName }}</span>
|
|
1790
1878
|
|
|
1791
1879
|
<div class="flex w-full items-center">
|
|
@@ -1847,20 +1935,45 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1847
1935
|
</edge-shad-button>
|
|
1848
1936
|
</div>
|
|
1849
1937
|
|
|
1850
|
-
<div class="flex items-center gap-1
|
|
1851
|
-
<
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1938
|
+
<div class="flex flex-col items-center gap-1 px-2">
|
|
1939
|
+
<div class="flex items-center gap-1">
|
|
1940
|
+
<edge-shad-button
|
|
1941
|
+
v-for="option in previewViewportOptions"
|
|
1942
|
+
:key="option.id"
|
|
1943
|
+
type="button"
|
|
1944
|
+
variant="ghost"
|
|
1945
|
+
size="icon"
|
|
1946
|
+
class="h-[26px] w-[26px] text-xs gap-1 border transition-colors"
|
|
1947
|
+
:class="state.previewViewport === option.id ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-muted text-foreground border-border hover:bg-muted/80'"
|
|
1948
|
+
@click="setPreviewViewport(option.id)"
|
|
1949
|
+
>
|
|
1950
|
+
<component :is="option.icon" class="w-3.5 h-3.5" />
|
|
1951
|
+
</edge-shad-button>
|
|
1952
|
+
</div>
|
|
1953
|
+
<span class="text-[10px] leading-tight text-muted-foreground">Viewport</span>
|
|
1954
|
+
</div>
|
|
1955
|
+
<div v-if="hasPostView(slotProps.workingDoc)" class="flex flex-col items-center gap-1 px-2">
|
|
1956
|
+
<div class="flex items-center gap-1">
|
|
1957
|
+
<edge-shad-button
|
|
1958
|
+
type="button"
|
|
1959
|
+
variant="ghost"
|
|
1960
|
+
class="h-[26px] px-2 text-xs border transition-colors"
|
|
1961
|
+
:class="state.previewPageView === 'list' ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-muted text-foreground border-border hover:bg-muted/80'"
|
|
1962
|
+
@click="setPreviewPageView('list')"
|
|
1963
|
+
>
|
|
1964
|
+
Index
|
|
1965
|
+
</edge-shad-button>
|
|
1966
|
+
<edge-shad-button
|
|
1967
|
+
type="button"
|
|
1968
|
+
variant="ghost"
|
|
1969
|
+
class="h-[26px] px-2 text-xs border transition-colors"
|
|
1970
|
+
:class="state.previewPageView === 'post' ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-muted text-foreground border-border hover:bg-muted/80'"
|
|
1971
|
+
@click="setPreviewPageView('post')"
|
|
1972
|
+
>
|
|
1973
|
+
Detail
|
|
1974
|
+
</edge-shad-button>
|
|
1975
|
+
</div>
|
|
1976
|
+
<span class="text-[10px] leading-tight text-muted-foreground">View</span>
|
|
1864
1977
|
</div>
|
|
1865
1978
|
|
|
1866
1979
|
<edge-shad-button variant="text" class="hover:text-primary/50 text-xs h-[26px] text-primary" @click="state.editMode = !state.editMode">
|
|
@@ -1906,7 +2019,7 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1906
2019
|
</div>
|
|
1907
2020
|
</template>
|
|
1908
2021
|
<template #success-alert>
|
|
1909
|
-
<div v-if="!props.isTemplateSite" class="mt-2 flex flex-wrap items-center gap-2">
|
|
2022
|
+
<div v-if="state.editMode && !props.isTemplateSite" class="mt-2 flex flex-wrap items-center gap-2">
|
|
1910
2023
|
<edge-shad-button
|
|
1911
2024
|
variant="outline"
|
|
1912
2025
|
class="text-xs h-[28px] gap-1"
|
|
@@ -1923,20 +2036,13 @@ const hasUnsavedChanges = (changes) => {
|
|
|
1923
2036
|
</div>
|
|
1924
2037
|
</template>
|
|
1925
2038
|
<template #main="slotProps">
|
|
1926
|
-
<Tabs class="w-full"
|
|
1927
|
-
<
|
|
1928
|
-
<TabsTrigger value="list">
|
|
1929
|
-
Index Page
|
|
1930
|
-
</TabsTrigger>
|
|
1931
|
-
<TabsTrigger value="post">
|
|
1932
|
-
Detail Page
|
|
1933
|
-
</TabsTrigger>
|
|
1934
|
-
</TabsList>
|
|
1935
|
-
<TabsContent value="list">
|
|
1936
|
-
<Separator class="my-4" />
|
|
2039
|
+
<Tabs class="w-full" :model-value="hasPostView(slotProps.workingDoc) ? state.previewPageView : 'list'">
|
|
2040
|
+
<TabsContent value="list" class="mt-0">
|
|
1937
2041
|
<div
|
|
1938
2042
|
:key="`${pagePreviewRenderKey}:list`"
|
|
1939
|
-
|
|
2043
|
+
data-cms-preview-surface="page"
|
|
2044
|
+
:data-cms-preview-mode="state.editMode ? 'edit' : 'preview'"
|
|
2045
|
+
class="w-full h-[calc(100vh-180px)] mt-2 overflow-y-auto mx-auto bg-card border border-border shadow-sm md:shadow-md p-0 space-y-6"
|
|
1940
2046
|
:class="[{ 'transition-all duration-300': !state.editMode }, state.editMode ? 'rounded-lg' : 'rounded-none']"
|
|
1941
2047
|
:style="previewViewportContainStyle"
|
|
1942
2048
|
>
|
|
@@ -2082,7 +2188,9 @@ const hasUnsavedChanges = (changes) => {
|
|
|
2082
2188
|
v-model="slotProps.workingDoc.content[blockIndex(slotProps.workingDoc, blockId, false)]"
|
|
2083
2189
|
:site-id="props.site"
|
|
2084
2190
|
:edit-mode="state.editMode"
|
|
2085
|
-
:
|
|
2191
|
+
:override-clicks-in-edit-mode="state.editMode"
|
|
2192
|
+
:allow-preview-content-edit="!state.editMode && canOpenPreviewBlockContentEditor"
|
|
2193
|
+
:contain-fixed="state.editMode"
|
|
2086
2194
|
:viewport-mode="previewViewportMode"
|
|
2087
2195
|
:block-id="blockId"
|
|
2088
2196
|
:theme="theme"
|
|
@@ -2185,11 +2293,12 @@ const hasUnsavedChanges = (changes) => {
|
|
|
2185
2293
|
</edge-button-divider>
|
|
2186
2294
|
</div>
|
|
2187
2295
|
</TabsContent>
|
|
2188
|
-
<TabsContent value="post">
|
|
2189
|
-
<Separator class="my-4" />
|
|
2296
|
+
<TabsContent v-if="hasPostView(slotProps.workingDoc)" value="post" class="mt-0">
|
|
2190
2297
|
<div
|
|
2191
2298
|
:key="`${pagePreviewRenderKey}:post`"
|
|
2192
|
-
|
|
2299
|
+
data-cms-preview-surface="page"
|
|
2300
|
+
:data-cms-preview-mode="state.editMode ? 'edit' : 'preview'"
|
|
2301
|
+
class="w-full h-[calc(100vh-180px)] mt-2 overflow-y-auto mx-auto bg-card border border-border shadow-sm md:shadow-md p-0 space-y-6"
|
|
2193
2302
|
:class="[{ 'transition-all duration-300': !state.editMode }, state.editMode ? 'rounded-lg' : 'rounded-none']"
|
|
2194
2303
|
:style="previewViewportContainStyle"
|
|
2195
2304
|
>
|
|
@@ -2334,7 +2443,9 @@ const hasUnsavedChanges = (changes) => {
|
|
|
2334
2443
|
:key="`${pagePreviewRenderKey}:${blockId}:${effectiveThemeId}:post`"
|
|
2335
2444
|
v-model="slotProps.workingDoc.postContent[blockIndex(slotProps.workingDoc, blockId, true)]"
|
|
2336
2445
|
:edit-mode="state.editMode"
|
|
2337
|
-
:
|
|
2446
|
+
:override-clicks-in-edit-mode="state.editMode"
|
|
2447
|
+
:allow-preview-content-edit="!state.editMode && canOpenPreviewBlockContentEditor"
|
|
2448
|
+
:contain-fixed="state.editMode"
|
|
2338
2449
|
:viewport-mode="previewViewportMode"
|
|
2339
2450
|
:block-id="blockId"
|
|
2340
2451
|
:theme="theme"
|
|
@@ -2653,4 +2764,8 @@ const hasUnsavedChanges = (changes) => {
|
|
|
2653
2764
|
.block-drag-handle:active {
|
|
2654
2765
|
cursor: grabbing;
|
|
2655
2766
|
}
|
|
2767
|
+
|
|
2768
|
+
.cms-page-preview-mode :deep(.border-emerald-200.bg-emerald-50) {
|
|
2769
|
+
display: none !important;
|
|
2770
|
+
}
|
|
2656
2771
|
</style>
|
|
@@ -542,7 +542,10 @@ const unPublishPost = async (postId) => {
|
|
|
542
542
|
</script>
|
|
543
543
|
|
|
544
544
|
<template>
|
|
545
|
-
<div
|
|
545
|
+
<div
|
|
546
|
+
v-if="props.mode !== 'editor'"
|
|
547
|
+
:class="isFullList ? 'h-full min-h-0 flex flex-col gap-4 overflow-hidden' : 'space-y-4 h-full min-h-0 overflow-y-auto'"
|
|
548
|
+
>
|
|
546
549
|
<edge-shad-button
|
|
547
550
|
variant="outline"
|
|
548
551
|
:class="isFullList ? 'h-8 px-3' : 'w-full mt-2 py-0 h-[28px]'"
|
|
@@ -552,7 +555,10 @@ const unPublishPost = async (postId) => {
|
|
|
552
555
|
New Post
|
|
553
556
|
</edge-shad-button>
|
|
554
557
|
|
|
555
|
-
<div
|
|
558
|
+
<div
|
|
559
|
+
v-if="isFullList"
|
|
560
|
+
class="rounded-lg border bg-card overflow-hidden flex flex-col h-[calc(100vh-180px)] max-h-[calc(100vh-180px)]"
|
|
561
|
+
>
|
|
556
562
|
<div class="flex items-center justify-between px-4 py-3 border-b bg-muted/40">
|
|
557
563
|
<div class="text-sm font-semibold">
|
|
558
564
|
Posts
|
|
@@ -561,7 +567,10 @@ const unPublishPost = async (postId) => {
|
|
|
561
567
|
{{ postsList.length }} total
|
|
562
568
|
</div>
|
|
563
569
|
</div>
|
|
564
|
-
<div
|
|
570
|
+
<div
|
|
571
|
+
v-if="hasPosts"
|
|
572
|
+
class="divide-y overflow-y-auto h-[calc(100vh-260px)] max-h-[calc(100vh-260px)]"
|
|
573
|
+
>
|
|
565
574
|
<div
|
|
566
575
|
v-for="post in postsList"
|
|
567
576
|
:key="post.id"
|
|
@@ -646,7 +655,7 @@ const unPublishPost = async (postId) => {
|
|
|
646
655
|
</div>
|
|
647
656
|
<div
|
|
648
657
|
v-else
|
|
649
|
-
class="flex flex-col items-center justify-center gap-3 px-6 py-12 text-center"
|
|
658
|
+
class="flex-1 flex flex-col items-center justify-center gap-3 px-6 py-12 text-center"
|
|
650
659
|
>
|
|
651
660
|
<File class="h-8 w-8 text-muted-foreground/60" />
|
|
652
661
|
<div class="space-y-1">
|
|
@@ -145,6 +145,7 @@ const schemas = {
|
|
|
145
145
|
trackingFacebookPixel: z.string().optional(),
|
|
146
146
|
trackingGoogleAnalytics: z.string().optional(),
|
|
147
147
|
trackingAdroll: z.string().optional(),
|
|
148
|
+
sureFeedURL: z.string().optional(),
|
|
148
149
|
socialFacebook: z.string().optional(),
|
|
149
150
|
socialInstagram: z.string().optional(),
|
|
150
151
|
socialTwitter: z.string().optional(),
|
|
@@ -639,8 +640,10 @@ const buildMenusFromDefaultPages = (defaultPages = []) => {
|
|
|
639
640
|
if (!entry?.pageId)
|
|
640
641
|
continue
|
|
641
642
|
const slug = ensureUniqueSlug(entry?.name || '', null, usedSlugs)
|
|
643
|
+
const menuTitle = String(entry?.menuTitle || entry?.name || '').trim() || titleFromSlug(slug)
|
|
642
644
|
menus['Site Root'].push({
|
|
643
645
|
name: slug,
|
|
646
|
+
menuTitle,
|
|
644
647
|
item: entry.pageId,
|
|
645
648
|
disableRename: !!entry?.disableRename,
|
|
646
649
|
disableDelete: !!entry?.disableDelete,
|
|
@@ -710,7 +713,9 @@ const duplicateEntriesWithPages = async (entries = [], options) => {
|
|
|
710
713
|
}
|
|
711
714
|
if (typeof entry.item === 'string' || entry.item === '') {
|
|
712
715
|
const templateDoc = templatePages?.[entry.item] || null
|
|
713
|
-
const
|
|
716
|
+
const entryMenuTitle = String(entry?.menuTitle || '').trim()
|
|
717
|
+
const slugSource = entry.name || entryMenuTitle
|
|
718
|
+
const slug = ensureUniqueSlug(slugSource || '', templateDoc, usedSlugs)
|
|
714
719
|
const payload = buildPagePayloadFromTemplateDoc(templateDoc, slug, entry.name || '')
|
|
715
720
|
try {
|
|
716
721
|
const result = await edgeFirebase.storeDoc(`${edgeGlobal.edgeState.organizationDocPath}/sites/${siteId}/pages`, payload)
|
|
@@ -719,6 +724,7 @@ const duplicateEntriesWithPages = async (entries = [], options) => {
|
|
|
719
724
|
next.push({
|
|
720
725
|
...entry,
|
|
721
726
|
name: slug,
|
|
727
|
+
menuTitle: entryMenuTitle || titleFromSlug(slug),
|
|
722
728
|
item: docId,
|
|
723
729
|
})
|
|
724
730
|
}
|
|
@@ -852,6 +858,7 @@ const isSiteDiff = computed(() => {
|
|
|
852
858
|
trackingFacebookPixel: publishedSite.trackingFacebookPixel,
|
|
853
859
|
trackingGoogleAnalytics: publishedSite.trackingGoogleAnalytics,
|
|
854
860
|
trackingAdroll: publishedSite.trackingAdroll,
|
|
861
|
+
sureFeedURL: publishedSite.sureFeedURL,
|
|
855
862
|
socialFacebook: publishedSite.socialFacebook,
|
|
856
863
|
socialInstagram: publishedSite.socialInstagram,
|
|
857
864
|
socialTwitter: publishedSite.socialTwitter,
|
|
@@ -880,6 +887,7 @@ const isSiteDiff = computed(() => {
|
|
|
880
887
|
trackingFacebookPixel: siteData.value.trackingFacebookPixel,
|
|
881
888
|
trackingGoogleAnalytics: siteData.value.trackingGoogleAnalytics,
|
|
882
889
|
trackingAdroll: siteData.value.trackingAdroll,
|
|
890
|
+
sureFeedURL: siteData.value.sureFeedURL,
|
|
883
891
|
socialFacebook: siteData.value.socialFacebook,
|
|
884
892
|
socialInstagram: siteData.value.socialInstagram,
|
|
885
893
|
socialTwitter: siteData.value.socialTwitter,
|
|
@@ -922,6 +930,7 @@ const discardSiteSettings = async () => {
|
|
|
922
930
|
trackingFacebookPixel: publishedSite.trackingFacebookPixel || '',
|
|
923
931
|
trackingGoogleAnalytics: publishedSite.trackingGoogleAnalytics || '',
|
|
924
932
|
trackingAdroll: publishedSite.trackingAdroll || '',
|
|
933
|
+
sureFeedURL: publishedSite.sureFeedURL || '',
|
|
925
934
|
socialFacebook: publishedSite.socialFacebook || '',
|
|
926
935
|
socialInstagram: publishedSite.socialInstagram || '',
|
|
927
936
|
socialTwitter: publishedSite.socialTwitter || '',
|
|
@@ -1154,7 +1163,8 @@ const addImportedPageToSiteMenu = (docId, pageName = '') => {
|
|
|
1154
1163
|
|
|
1155
1164
|
const existingNames = collectMenuPageNames(menus)
|
|
1156
1165
|
const menuName = makeUniqueMenuPageName(pageName || nextDocId, existingNames)
|
|
1157
|
-
|
|
1166
|
+
const menuTitle = String(pageName || '').trim() || titleFromSlug(menuName)
|
|
1167
|
+
menus['Site Root'].push({ name: menuName, menuTitle, item: nextDocId })
|
|
1158
1168
|
state.menus = menus
|
|
1159
1169
|
}
|
|
1160
1170
|
|
|
@@ -2132,11 +2142,12 @@ const pageSettingsUpdated = async (pageData) => {
|
|
|
2132
2142
|
</Transition>
|
|
2133
2143
|
</ResizablePanel>
|
|
2134
2144
|
</ResizablePanelGroup>
|
|
2135
|
-
<div v-else class="flex-1
|
|
2136
|
-
<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">
|
|
2137
2147
|
<edge-cms-posts
|
|
2138
2148
|
mode="list"
|
|
2139
2149
|
list-variant="full"
|
|
2150
|
+
class="h-full min-h-0"
|
|
2140
2151
|
:site="props.site"
|
|
2141
2152
|
@updating="isUpdating => state.updating = isUpdating"
|
|
2142
2153
|
@update:selected-post-id="handlePostSelect"
|
|
@@ -480,13 +480,17 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
480
480
|
</div>
|
|
481
481
|
<div class="space-y-2 text-sm">
|
|
482
482
|
<div class="grid grid-cols-[70px_1fr] gap-3">
|
|
483
|
-
<div class="text-muted-foreground">
|
|
483
|
+
<div class="text-muted-foreground">
|
|
484
|
+
CNAME
|
|
485
|
+
</div>
|
|
484
486
|
<div class="font-mono">
|
|
485
487
|
{{ entry?.dnsRecords?.www?.name || 'www' }} → {{ entry?.dnsRecords?.www?.value || pagesDomain }}
|
|
486
488
|
</div>
|
|
487
489
|
</div>
|
|
488
490
|
<div class="grid grid-cols-[70px_1fr] gap-3">
|
|
489
|
-
<div class="text-muted-foreground">
|
|
491
|
+
<div class="text-muted-foreground">
|
|
492
|
+
CNAME
|
|
493
|
+
</div>
|
|
490
494
|
<div class="font-mono">
|
|
491
495
|
{{ entry?.dnsRecords?.apex?.name || '@' }} → {{ entry?.dnsRecords?.apex?.value || pagesDomain }}
|
|
492
496
|
</div>
|
|
@@ -532,7 +536,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
532
536
|
item-value="value"
|
|
533
537
|
@update:model-value="value => (props.settings.theme = value || '')"
|
|
534
538
|
/>
|
|
535
|
-
<edge-shad-select
|
|
539
|
+
<!-- <edge-shad-select
|
|
536
540
|
:model-value="props.settings.menuPosition || ''"
|
|
537
541
|
name="menuPosition"
|
|
538
542
|
label="Menu Position"
|
|
@@ -542,7 +546,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
542
546
|
item-title="label"
|
|
543
547
|
item-value="value"
|
|
544
548
|
@update:model-value="value => (props.settings.menuPosition = value || '')"
|
|
545
|
-
/>
|
|
549
|
+
/> -->
|
|
546
550
|
</TabsContent>
|
|
547
551
|
<TabsContent value="branding" class="pt-4 space-y-4">
|
|
548
552
|
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
@@ -562,7 +566,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
562
566
|
<img
|
|
563
567
|
:src="props.settings.logo"
|
|
564
568
|
alt="Logo preview"
|
|
565
|
-
|
|
569
|
+
class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.logo)]"
|
|
566
570
|
>
|
|
567
571
|
<edge-shad-button
|
|
568
572
|
type="button"
|
|
@@ -612,7 +616,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
612
616
|
<img
|
|
613
617
|
:src="props.settings.logoLight"
|
|
614
618
|
alt="Light logo preview"
|
|
615
|
-
|
|
619
|
+
class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.logoLight)]"
|
|
616
620
|
>
|
|
617
621
|
<edge-shad-button
|
|
618
622
|
type="button"
|
|
@@ -666,7 +670,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
666
670
|
<img
|
|
667
671
|
:src="props.settings.brandLogoDark"
|
|
668
672
|
alt="Brand dark logo preview"
|
|
669
|
-
|
|
673
|
+
class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.brandLogoDark)]"
|
|
670
674
|
>
|
|
671
675
|
<edge-shad-button
|
|
672
676
|
type="button"
|
|
@@ -716,7 +720,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
716
720
|
<img
|
|
717
721
|
:src="props.settings.brandLogoLight"
|
|
718
722
|
alt="Brand light logo preview"
|
|
719
|
-
|
|
723
|
+
class="max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.brandLogoLight)]"
|
|
720
724
|
>
|
|
721
725
|
<edge-shad-button
|
|
722
726
|
type="button"
|
|
@@ -767,7 +771,7 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
767
771
|
<img
|
|
768
772
|
:src="props.settings.favicon"
|
|
769
773
|
alt="Favicon preview"
|
|
770
|
-
|
|
774
|
+
class="max-h-12 max-w-12 h-auto w-auto rounded-md border border-border object-contain" :class="[previewBackgroundClass(props.settings.favicon)]"
|
|
771
775
|
>
|
|
772
776
|
<edge-shad-button
|
|
773
777
|
type="button"
|
|
@@ -856,6 +860,12 @@ watch(() => props.settings?.forwardApex, (value) => {
|
|
|
856
860
|
name="trackingAdroll"
|
|
857
861
|
placeholder="ADROLL-ID"
|
|
858
862
|
/>
|
|
863
|
+
<edge-shad-input
|
|
864
|
+
v-model="props.settings.sureFeedURL"
|
|
865
|
+
label="Sure Feedback"
|
|
866
|
+
name="sureFeedURL"
|
|
867
|
+
placeholder=""
|
|
868
|
+
/>
|
|
859
869
|
</div>
|
|
860
870
|
</TabsContent>
|
|
861
871
|
<TabsContent value="social" class="pt-4">
|
|
@@ -118,6 +118,17 @@ const getFolderName = (entry) => {
|
|
|
118
118
|
return ''
|
|
119
119
|
return Object.keys(entry.item || {})[0] || ''
|
|
120
120
|
}
|
|
121
|
+
const getFolderTitle = (entry) => {
|
|
122
|
+
if (!isFolder(entry))
|
|
123
|
+
return ''
|
|
124
|
+
const title = String(entry?.menuTitle || entry?.folderTitle || '').trim()
|
|
125
|
+
if (title)
|
|
126
|
+
return title
|
|
127
|
+
const folderName = getFolderName(entry)
|
|
128
|
+
if (!folderName)
|
|
129
|
+
return ''
|
|
130
|
+
return folderName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
|
131
|
+
}
|
|
121
132
|
|
|
122
133
|
const getFolderList = (menuName, folderName) => {
|
|
123
134
|
const targetMenu = modelValue.value[menuName] || []
|
|
@@ -228,6 +239,7 @@ const submitFolderDialog = () => {
|
|
|
228
239
|
return
|
|
229
240
|
const slug = uniqueFolderSlug(value, state.folderDialog.menu)
|
|
230
241
|
modelValue.value[state.folderDialog.menu].push({
|
|
242
|
+
menuTitle: value,
|
|
231
243
|
item: { [slug]: [] },
|
|
232
244
|
})
|
|
233
245
|
state.folderDialog.open = false
|
|
@@ -262,13 +274,16 @@ const openRenameDialogForPage = (menuName, index, folderName = null) => {
|
|
|
262
274
|
}
|
|
263
275
|
|
|
264
276
|
const openRenameDialogForFolder = (menuName, folderName, index) => {
|
|
277
|
+
const folderList = modelValue.value[menuName] || []
|
|
278
|
+
const target = folderList[index]
|
|
279
|
+
const currentTitle = String(target?.menuTitle || target?.folderTitle || '').trim()
|
|
265
280
|
state.renameDialog = {
|
|
266
281
|
open: true,
|
|
267
282
|
type: 'folder',
|
|
268
283
|
menu: menuName,
|
|
269
284
|
folder: folderName,
|
|
270
285
|
index,
|
|
271
|
-
value: folderName,
|
|
286
|
+
value: currentTitle || folderName,
|
|
272
287
|
}
|
|
273
288
|
}
|
|
274
289
|
|
|
@@ -347,6 +362,9 @@ const submitRenameDialog = () => {
|
|
|
347
362
|
return
|
|
348
363
|
const currentName = getFolderName(target)
|
|
349
364
|
const slug = uniqueFolderSlug(value || currentName, state.renameDialog.menu, currentName)
|
|
365
|
+
target.menuTitle = value || target.menuTitle || target.folderTitle || currentName
|
|
366
|
+
if (Object.prototype.hasOwnProperty.call(target, 'folderTitle'))
|
|
367
|
+
delete target.folderTitle
|
|
350
368
|
if (slug !== currentName) {
|
|
351
369
|
target.item[slug] = target.item[currentName]
|
|
352
370
|
delete target.item[currentName]
|
|
@@ -438,7 +456,7 @@ const hasEntries = computed(() => {
|
|
|
438
456
|
<div>
|
|
439
457
|
<div class="text-sm font-semibold flex items-center gap-1">
|
|
440
458
|
<Folder class="w-4 h-4" />
|
|
441
|
-
{{
|
|
459
|
+
{{ getFolderTitle(element) }}
|
|
442
460
|
</div>
|
|
443
461
|
<div class="text-[11px] text-muted-foreground">
|
|
444
462
|
Folder
|
|
@@ -24,6 +24,7 @@ export const useSiteSettingsTemplate = () => {
|
|
|
24
24
|
trackingFacebookPixel: '',
|
|
25
25
|
trackingGoogleAnalytics: '',
|
|
26
26
|
trackingAdroll: '',
|
|
27
|
+
sureFeedURL: '',
|
|
27
28
|
socialFacebook: '',
|
|
28
29
|
socialInstagram: '',
|
|
29
30
|
socialTwitter: '',
|
|
@@ -59,6 +60,7 @@ export const useSiteSettingsTemplate = () => {
|
|
|
59
60
|
trackingFacebookPixel: { bindings: { 'field-type': 'text', 'label': 'Facebook Pixel ID' }, cols: '12', value: defaults.trackingFacebookPixel },
|
|
60
61
|
trackingGoogleAnalytics: { bindings: { 'field-type': 'text', 'label': 'Google Analytics ID' }, cols: '12', value: defaults.trackingGoogleAnalytics },
|
|
61
62
|
trackingAdroll: { bindings: { 'field-type': 'text', 'label': 'AdRoll ID' }, cols: '12', value: defaults.trackingAdroll },
|
|
63
|
+
sureFeedURL: { bindings: { 'field-type': 'text', 'label': 'Sure Feedback' }, cols: '12', value: defaults.sureFeedURL },
|
|
62
64
|
socialFacebook: { bindings: { 'field-type': 'text', 'label': 'Facebook URL' }, cols: '12', value: defaults.socialFacebook },
|
|
63
65
|
socialInstagram: { bindings: { 'field-type': 'text', 'label': 'Instagram URL' }, cols: '12', value: defaults.socialInstagram },
|
|
64
66
|
socialTwitter: { bindings: { 'field-type': 'text', 'label': 'X (Twitter) URL' }, cols: '12', value: defaults.socialTwitter },
|