@edgedev/create-edge-app 1.2.29 → 1.2.30
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/{pages/app/dashboard/blocks/index.vue → edge/components/cms/blocksManager.vue} +9 -24
- package/edge/components/cms/sitesManager.vue +110 -0
- package/edge/components/editor.vue +100 -35
- package/edge/routes/cms/dashboard/blocks/index.vue +21 -0
- package/edge/routes/cms/dashboard/sites/index.vue +13 -0
- package/edge/routes/cms/nuxtHooks.js +52 -0
- package/edge/routes/cms/routes.js +56 -0
- package/nuxt.config.ts +19 -2
- package/package.json +1 -1
- package/pages/app/dashboard/sites/index.vue +0 -114
- /package/{pages/app → edge/routes/cms}/dashboard/blocks/[block].vue +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/media/index.vue +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/sites/[site]/[[page]].vue +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/sites/[site].vue +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/templates/[page].vue +0 -0
- /package/{pages/app/dashboard/templates.vue → edge/routes/cms/dashboard/templates/index.vue} +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/themes/[theme].vue +0 -0
- /package/{pages/app → edge/routes/cms}/dashboard/themes/index.vue +0 -0
|
@@ -7,15 +7,7 @@ const state = reactive({
|
|
|
7
7
|
themesFilter: [],
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
loaded: [],
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
definePageMeta({
|
|
15
|
-
middleware: 'auth',
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const rawInitBlockFiles = import.meta.glob('../../../../edge/components/cms/init_blocks/*.html', {
|
|
10
|
+
const rawInitBlockFiles = import.meta.glob('./init_blocks/*.html', {
|
|
19
11
|
as: 'raw',
|
|
20
12
|
eager: true,
|
|
21
13
|
})
|
|
@@ -74,14 +66,6 @@ const seedInitialBlocks = async () => {
|
|
|
74
66
|
return created
|
|
75
67
|
}
|
|
76
68
|
|
|
77
|
-
onBeforeMount(async () => {
|
|
78
|
-
restoreFilters()
|
|
79
|
-
if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]) {
|
|
80
|
-
await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`)
|
|
81
|
-
}
|
|
82
|
-
state.mounted = true
|
|
83
|
-
})
|
|
84
|
-
|
|
85
69
|
const getThemeFromId = (themeId) => {
|
|
86
70
|
const theme = edgeFirebase.data[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]?.[themeId]
|
|
87
71
|
console.log('getThemeFromId', themeId, theme.name)
|
|
@@ -93,13 +77,6 @@ const loadingRender = (content) => {
|
|
|
93
77
|
return safeContent.replaceAll('{{loading}}', '').replaceAll('{{loaded}}', 'hidden')
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
const markPreviewLoaded = (isLoading, id) => {
|
|
97
|
-
if (!isLoading && !previewState.loaded.includes(id))
|
|
98
|
-
previewState.loaded.push(id)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const hasPreviewLoaded = id => previewState.loaded.includes(id)
|
|
102
|
-
|
|
103
80
|
const FILTER_STORAGE_KEY = 'edge.blocks.filters'
|
|
104
81
|
|
|
105
82
|
const restoreFilters = () => {
|
|
@@ -136,6 +113,14 @@ watch(
|
|
|
136
113
|
{ deep: true },
|
|
137
114
|
)
|
|
138
115
|
|
|
116
|
+
onBeforeMount(async () => {
|
|
117
|
+
restoreFilters()
|
|
118
|
+
if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]) {
|
|
119
|
+
await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`)
|
|
120
|
+
}
|
|
121
|
+
state.mounted = true
|
|
122
|
+
})
|
|
123
|
+
|
|
139
124
|
const tagOptions = computed(() => {
|
|
140
125
|
const tagsSet = new Set()
|
|
141
126
|
const blocks = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/blocks`] || {}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Loader2 } from 'lucide-vue-next'
|
|
3
|
+
|
|
4
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
5
|
+
const isAiBusy = status => status === 'queued' || status === 'running'
|
|
6
|
+
|
|
7
|
+
const state = reactive({
|
|
8
|
+
filter: '',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const isAdmin = computed(() => {
|
|
12
|
+
return edgeGlobal.isAdminGlobal(edgeFirebase).value
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const disableAddSiteForNonAdmin = true
|
|
16
|
+
|
|
17
|
+
const currentOrgRoleName = computed(() => {
|
|
18
|
+
return String(edgeGlobal.getRoleName(edgeFirebase?.user?.roles || [], edgeGlobal.edgeState.currentOrganization) || '').toLowerCase()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const canAddSite = computed(() => {
|
|
22
|
+
if (!disableAddSiteForNonAdmin)
|
|
23
|
+
return true
|
|
24
|
+
return currentOrgRoleName.value === 'admin'
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const queryField = computed(() => {
|
|
28
|
+
if (!isAdmin.value) {
|
|
29
|
+
return 'users'
|
|
30
|
+
}
|
|
31
|
+
return ''
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const queryValue = computed(() => {
|
|
35
|
+
if (!isAdmin.value) {
|
|
36
|
+
return [edgeFirebase?.user?.uid]
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const queryOperator = computed(() => {
|
|
42
|
+
if (!isAdmin.value) {
|
|
43
|
+
return 'array-contains-any'
|
|
44
|
+
}
|
|
45
|
+
return ''
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<edge-dashboard
|
|
51
|
+
:filter="state.filter"
|
|
52
|
+
:query-field="queryField"
|
|
53
|
+
:query-value="queryValue"
|
|
54
|
+
:query-operator="queryOperator"
|
|
55
|
+
collection="sites"
|
|
56
|
+
class="flex-1 pt-0"
|
|
57
|
+
>
|
|
58
|
+
<template #header-end>
|
|
59
|
+
<edge-shad-button
|
|
60
|
+
v-if="canAddSite"
|
|
61
|
+
class="uppercase bg-primary"
|
|
62
|
+
to="/app/dashboard/sites/new"
|
|
63
|
+
>
|
|
64
|
+
Add Site
|
|
65
|
+
</edge-shad-button>
|
|
66
|
+
<div v-else class="hidden" />
|
|
67
|
+
</template>
|
|
68
|
+
<template #list="slotProps">
|
|
69
|
+
<template v-for="item in slotProps.filtered" :key="item.docId">
|
|
70
|
+
<edge-shad-button
|
|
71
|
+
variant="text"
|
|
72
|
+
class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
|
|
73
|
+
:to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
|
|
74
|
+
:disabled="isAiBusy(item.aiBootstrapStatus)"
|
|
75
|
+
>
|
|
76
|
+
<div>
|
|
77
|
+
<Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
|
|
78
|
+
<FilePenLine class="h-5 w-5" />
|
|
79
|
+
</Avatar>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="grow text-left">
|
|
82
|
+
<div class="text-lg">
|
|
83
|
+
{{ item.name }}
|
|
84
|
+
</div>
|
|
85
|
+
<div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
|
|
86
|
+
<Loader2 class="h-3 w-3 animate-spin" />
|
|
87
|
+
<span>AI is preparing this site</span>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div>
|
|
91
|
+
<edge-shad-button
|
|
92
|
+
size="icon"
|
|
93
|
+
class="bg-slate-600 h-7 w-7"
|
|
94
|
+
@click.stop="slotProps.deleteItem(item.docId)"
|
|
95
|
+
>
|
|
96
|
+
<Trash class="h-5 w-5" />
|
|
97
|
+
</edge-shad-button>
|
|
98
|
+
</div>
|
|
99
|
+
</edge-shad-button>
|
|
100
|
+
<Separator class="dark:bg-slate-600" />
|
|
101
|
+
</template>
|
|
102
|
+
<div
|
|
103
|
+
v-if="slotProps.filtered.length === 0 && !isAdmin && disableAddSiteForNonAdmin"
|
|
104
|
+
class="px-4 py-6 text-sm text-muted-foreground"
|
|
105
|
+
>
|
|
106
|
+
No sites are assigned to your account. Contact an organization admin to add a site for you.
|
|
107
|
+
</div>
|
|
108
|
+
</template>
|
|
109
|
+
</edge-dashboard>
|
|
110
|
+
</template>
|
|
@@ -96,6 +96,46 @@ const state = reactive({
|
|
|
96
96
|
const edgeFirebase = inject('edgeFirebase')
|
|
97
97
|
// const edgeGlobal = inject('edgeGlobal')
|
|
98
98
|
|
|
99
|
+
const normalizeObject = (value) => {
|
|
100
|
+
if (Array.isArray(value)) {
|
|
101
|
+
return value.map(item => normalizeObject(item))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (value && typeof value === 'object') {
|
|
105
|
+
return Object.keys(value)
|
|
106
|
+
.sort()
|
|
107
|
+
.reduce((acc, key) => {
|
|
108
|
+
acc[key] = normalizeObject(value[key])
|
|
109
|
+
return acc
|
|
110
|
+
}, {})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return value
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const withSchemaDefaults = (doc = {}) => {
|
|
117
|
+
const normalizedDoc = edgeGlobal.dupObject(doc || {})
|
|
118
|
+
Object.keys(newDoc.value).forEach((field) => {
|
|
119
|
+
if (!edgeGlobal.objHas(normalizedDoc, field)) {
|
|
120
|
+
normalizedDoc[field] = newDoc.value[field]
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
return normalizedDoc
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const comparableDoc = (doc = {}) => {
|
|
127
|
+
const sourceDoc = (doc && typeof doc === 'object') ? doc : {}
|
|
128
|
+
const comparable = {}
|
|
129
|
+
Object.keys(props.newDocSchema || {}).forEach((field) => {
|
|
130
|
+
if (edgeGlobal.objHas(sourceDoc, field)) {
|
|
131
|
+
comparable[field] = sourceDoc[field]
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
comparable[field] = newDoc.value[field]
|
|
135
|
+
})
|
|
136
|
+
return normalizeObject(comparable)
|
|
137
|
+
}
|
|
138
|
+
|
|
99
139
|
const unsavedChanges = computed(() => {
|
|
100
140
|
if (props.docId === 'new') {
|
|
101
141
|
return false
|
|
@@ -107,9 +147,9 @@ const unsavedChanges = computed(() => {
|
|
|
107
147
|
return false
|
|
108
148
|
}
|
|
109
149
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return JSON.stringify(
|
|
150
|
+
const workingComparable = comparableDoc(state.workingDoc)
|
|
151
|
+
const baselineComparable = comparableDoc(baselineDoc)
|
|
152
|
+
return JSON.stringify(workingComparable) !== JSON.stringify(baselineComparable)
|
|
113
153
|
})
|
|
114
154
|
|
|
115
155
|
onBeforeRouteLeave((to, from, next) => {
|
|
@@ -164,7 +204,7 @@ const discardChanges = async () => {
|
|
|
164
204
|
}
|
|
165
205
|
return
|
|
166
206
|
}
|
|
167
|
-
state.workingDoc =
|
|
207
|
+
state.workingDoc = withSchemaDefaults(state.collectionData[props.docId])
|
|
168
208
|
state.bypassUnsavedChanges = true
|
|
169
209
|
state.dialog = false
|
|
170
210
|
edgeGlobal.edgeState.changeTracker = {}
|
|
@@ -328,44 +368,63 @@ const initData = (newVal) => {
|
|
|
328
368
|
if (edgeGlobal.objHas(newVal, props.docId) === false) {
|
|
329
369
|
return
|
|
330
370
|
}
|
|
331
|
-
state.workingDoc =
|
|
332
|
-
Object.keys(newDoc.value).forEach((field) => {
|
|
333
|
-
if (!edgeGlobal.objHas(state.workingDoc, field)) {
|
|
334
|
-
state.workingDoc[field] = newDoc.value[field]
|
|
335
|
-
}
|
|
336
|
-
})
|
|
371
|
+
state.workingDoc = withSchemaDefaults(newVal[props.docId])
|
|
337
372
|
state.afterMount = true
|
|
338
373
|
}
|
|
339
374
|
else {
|
|
340
375
|
if (!state.afterMount) {
|
|
341
|
-
state.workingDoc =
|
|
376
|
+
state.workingDoc = withSchemaDefaults(newDoc.value)
|
|
342
377
|
}
|
|
343
378
|
state.afterMount = true
|
|
344
379
|
}
|
|
345
380
|
}
|
|
346
381
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
edgeGlobal.edgeState.changeTracker = {}
|
|
350
|
-
for (const field of Object.keys(props.newDocSchema)) {
|
|
382
|
+
const startCollectionSnapshots = async () => {
|
|
383
|
+
for (const field of Object.keys(props.newDocSchema || {})) {
|
|
351
384
|
if (props.newDocSchema[field].type === 'collection') {
|
|
352
385
|
await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${field}`)
|
|
353
386
|
}
|
|
354
387
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!edgeFirebase.data?.[
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
state.collectionData
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const setCollectionData = async () => {
|
|
391
|
+
const collectionPath = `${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`
|
|
392
|
+
if (!edgeFirebase.data?.[collectionPath]) {
|
|
393
|
+
if (props.docId !== 'new') {
|
|
394
|
+
const docData = await edgeFirebase.getDocData(collectionPath, props.docId)
|
|
395
|
+
state.collectionData = {
|
|
396
|
+
...(state.collectionData || {}),
|
|
397
|
+
[props.docId]: docData,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else if (!state.collectionData || typeof state.collectionData !== 'object') {
|
|
401
|
+
state.collectionData = {}
|
|
402
|
+
}
|
|
403
|
+
return
|
|
368
404
|
}
|
|
405
|
+
state.collectionData = edgeFirebase.data[collectionPath]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const resetEditorState = () => {
|
|
409
|
+
state.bypassUnsavedChanges = false
|
|
410
|
+
state.bypassRoute = ''
|
|
411
|
+
state.afterMount = false
|
|
412
|
+
state.dialog = false
|
|
413
|
+
state.successMessage = ''
|
|
414
|
+
state.skipNextValidation = props.docId === 'new'
|
|
415
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const refreshEditorData = async () => {
|
|
419
|
+
resetEditorState()
|
|
420
|
+
await startCollectionSnapshots()
|
|
421
|
+
await setCollectionData()
|
|
422
|
+
initData(state.collectionData)
|
|
423
|
+
emit('unsavedChanges', unsavedChanges.value)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
onBeforeMount(async () => {
|
|
427
|
+
await refreshEditorData()
|
|
369
428
|
if (props.noCloseAfterSave) {
|
|
370
429
|
state.overrideClose = true
|
|
371
430
|
}
|
|
@@ -374,6 +433,17 @@ onBeforeMount(async () => {
|
|
|
374
433
|
watch(() => state.collectionData, (newVal) => {
|
|
375
434
|
initData(newVal)
|
|
376
435
|
})
|
|
436
|
+
|
|
437
|
+
watch(() => [props.collection, props.docId], async (newVal, oldVal) => {
|
|
438
|
+
if (!oldVal) {
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
if (newVal[0] === oldVal[0] && newVal[1] === oldVal[1]) {
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
await refreshEditorData()
|
|
445
|
+
})
|
|
446
|
+
|
|
377
447
|
onActivated(() => {
|
|
378
448
|
// console.log('activated')
|
|
379
449
|
state.bypassUnsavedChanges = false
|
|
@@ -384,17 +454,12 @@ onActivated(() => {
|
|
|
384
454
|
if (edgeGlobal.objHas(state.collectionData, props.docId) === false) {
|
|
385
455
|
return
|
|
386
456
|
}
|
|
387
|
-
state.workingDoc =
|
|
388
|
-
Object.keys(newDoc.value).forEach((field) => {
|
|
389
|
-
if (!edgeGlobal.objHas(state.workingDoc, field)) {
|
|
390
|
-
state.workingDoc[field] = newDoc.value[field]
|
|
391
|
-
}
|
|
392
|
-
})
|
|
457
|
+
state.workingDoc = withSchemaDefaults(state.collectionData[props.docId])
|
|
393
458
|
|
|
394
459
|
// console.log('state.workingDoc', state.workingDoc)
|
|
395
460
|
}
|
|
396
461
|
else {
|
|
397
|
-
state.workingDoc =
|
|
462
|
+
state.workingDoc = withSchemaDefaults(newDoc.value)
|
|
398
463
|
Object.entries(route.query).forEach(([key, value]) => {
|
|
399
464
|
// Check if the key exists in state.workingDoc, and if so, set the value
|
|
400
465
|
if (key in state.workingDoc) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const state = reactive({
|
|
3
|
+
mounted: false,
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
definePageMeta({
|
|
7
|
+
middleware: 'auth',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
state.mounted = true
|
|
12
|
+
})
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div
|
|
17
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
18
|
+
>
|
|
19
|
+
<edge-cms-blocks-manager />
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { resolve as resolvePath } from 'node:path'
|
|
2
|
+
import { cmsRoutes } from './routes'
|
|
3
|
+
|
|
4
|
+
const resolveRouteFileFromCwd = (file) => {
|
|
5
|
+
const normalizedPath = String(file || '').replace(/^\.\//, '')
|
|
6
|
+
return resolvePath(process.cwd(), normalizedPath)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const createCmsNuxtHooks = (resolveFile) => {
|
|
10
|
+
const resolveRouteFile = typeof resolveFile === 'function'
|
|
11
|
+
? resolveFile
|
|
12
|
+
: resolveRouteFileFromCwd
|
|
13
|
+
|
|
14
|
+
const routeExists = (routeList, name) => {
|
|
15
|
+
for (const route of routeList) {
|
|
16
|
+
if (route.name === name)
|
|
17
|
+
return true
|
|
18
|
+
if (route.children?.length && routeExists(route.children, name))
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolveCmsRoute = (route, appRootRoute, isChild = false) => {
|
|
25
|
+
const shouldStripAppPrefix = Boolean(appRootRoute) && !isChild
|
|
26
|
+
const nextRoute = {
|
|
27
|
+
...route,
|
|
28
|
+
path: shouldStripAppPrefix
|
|
29
|
+
? route.path.replace(/^\/app\/?/, '')
|
|
30
|
+
: route.path,
|
|
31
|
+
file: resolveRouteFile(route.file),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (route.children?.length)
|
|
35
|
+
nextRoute.children = route.children.map(child => resolveCmsRoute(child, appRootRoute, true))
|
|
36
|
+
|
|
37
|
+
return nextRoute
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
'pages:extend': (pages) => {
|
|
42
|
+
const appRootRoute = pages.find(page => page.path === '/app')
|
|
43
|
+
const targetRoutes = appRootRoute?.children || pages
|
|
44
|
+
|
|
45
|
+
cmsRoutes.forEach((route) => {
|
|
46
|
+
const routeToAdd = resolveCmsRoute(route, appRootRoute)
|
|
47
|
+
if (!routeExists(targetRoutes, routeToAdd.name))
|
|
48
|
+
targetRoutes.push(routeToAdd)
|
|
49
|
+
})
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const cmsRoutes = [
|
|
2
|
+
{
|
|
3
|
+
name: 'cms-dashboard-sites-index',
|
|
4
|
+
path: '/app/dashboard/sites',
|
|
5
|
+
file: './edge/routes/cms/dashboard/sites/index.vue',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: 'cms-dashboard-blocks-index',
|
|
9
|
+
path: '/app/dashboard/blocks',
|
|
10
|
+
file: './edge/routes/cms/dashboard/blocks/index.vue',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'cms-dashboard-blocks-block',
|
|
14
|
+
path: '/app/dashboard/blocks/:block',
|
|
15
|
+
file: './edge/routes/cms/dashboard/blocks/[block].vue',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'cms-dashboard-media-index',
|
|
19
|
+
path: '/app/dashboard/media',
|
|
20
|
+
file: './edge/routes/cms/dashboard/media/index.vue',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'cms-dashboard-sites-site',
|
|
24
|
+
path: '/app/dashboard/sites/:site',
|
|
25
|
+
file: './edge/routes/cms/dashboard/sites/[site].vue',
|
|
26
|
+
children: [
|
|
27
|
+
{
|
|
28
|
+
name: 'cms-dashboard-sites-site-page',
|
|
29
|
+
path: ':page',
|
|
30
|
+
file: './edge/routes/cms/dashboard/sites/[site]/[[page]].vue',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'cms-dashboard-templates-index',
|
|
36
|
+
path: '/app/dashboard/templates',
|
|
37
|
+
file: './edge/routes/cms/dashboard/templates/index.vue',
|
|
38
|
+
children: [
|
|
39
|
+
{
|
|
40
|
+
name: 'cms-dashboard-templates-page',
|
|
41
|
+
path: ':page',
|
|
42
|
+
file: './edge/routes/cms/dashboard/templates/[page].vue',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'cms-dashboard-themes-index',
|
|
48
|
+
path: '/app/dashboard/themes',
|
|
49
|
+
file: './edge/routes/cms/dashboard/themes/index.vue',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'cms-dashboard-themes-theme',
|
|
53
|
+
path: '/app/dashboard/themes/:theme',
|
|
54
|
+
file: './edge/routes/cms/dashboard/themes/[theme].vue',
|
|
55
|
+
},
|
|
56
|
+
]
|
package/nuxt.config.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createCmsNuxtHooks } from './edge/routes/cms/nuxtHooks'
|
|
2
|
+
|
|
3
|
+
const cmsRouteHooks = {}
|
|
4
|
+
Object.assign(cmsRouteHooks, createCmsNuxtHooks()) // Comment out this line to disable CMS routes.
|
|
5
|
+
|
|
1
6
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
2
7
|
export default defineNuxtConfig({
|
|
3
8
|
ssr: false,
|
|
@@ -16,6 +21,12 @@ export default defineNuxtConfig({
|
|
|
16
21
|
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/favicon/apple-touch-icon.png' },
|
|
17
22
|
{ rel: 'manifest', href: '/favicon/site.webmanifest' },
|
|
18
23
|
],
|
|
24
|
+
meta: [
|
|
25
|
+
{
|
|
26
|
+
'http-equiv': 'Content-Security-Policy',
|
|
27
|
+
'content': 'font-src \'self\' https://files.edgemarketingdesign.com https://use.typekit.net https://p.typekit.net data:;',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
19
30
|
},
|
|
20
31
|
},
|
|
21
32
|
modules: ['@vant/nuxt', '@nuxtjs/tailwindcss', '@nuxtjs/color-mode', 'shadcn-nuxt'],
|
|
@@ -40,9 +51,11 @@ export default defineNuxtConfig({
|
|
|
40
51
|
},
|
|
41
52
|
components: {
|
|
42
53
|
dirs: [
|
|
43
|
-
{ path: '~/components
|
|
54
|
+
{ path: '~/components' },
|
|
55
|
+
|
|
56
|
+
// Namespaced wrappers — keep them, but lower priority is fine
|
|
44
57
|
{ path: '~/edge/components', global: true, prefix: 'edge' },
|
|
45
|
-
'~/components',
|
|
58
|
+
{ path: '~/components/formSubtypes', global: true, prefix: 'edge-form-subtypes' },
|
|
46
59
|
],
|
|
47
60
|
},
|
|
48
61
|
vite: {
|
|
@@ -50,11 +63,15 @@ export default defineNuxtConfig({
|
|
|
50
63
|
'process.env.DEBUG': false,
|
|
51
64
|
},
|
|
52
65
|
server: {
|
|
66
|
+
watch: {
|
|
67
|
+
ignored: ['**/.nuxt/**', '**/.output/**', '**/dist/**', '**/node_modules/**'],
|
|
68
|
+
},
|
|
53
69
|
hmr: {
|
|
54
70
|
port: 3000, // Make sure this port matches your Nuxt server port
|
|
55
71
|
clientPort: 3000, // Ensure this matches your Nuxt server port as well
|
|
56
72
|
},
|
|
57
73
|
},
|
|
58
74
|
},
|
|
75
|
+
hooks: cmsRouteHooks,
|
|
59
76
|
devtools: { enabled: false },
|
|
60
77
|
})
|
package/package.json
CHANGED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { toTypedSchema } from '@vee-validate/zod'
|
|
3
|
-
import * as z from 'zod'
|
|
4
|
-
import { Loader2 } from 'lucide-vue-next'
|
|
5
|
-
const route = useRoute()
|
|
6
|
-
// const edgeGlobal = inject('edgeGlobal')
|
|
7
|
-
|
|
8
|
-
const edgeFirebase = inject('edgeFirebase')
|
|
9
|
-
const isAiBusy = status => status === 'queued' || status === 'running'
|
|
10
|
-
|
|
11
|
-
const state = reactive({
|
|
12
|
-
filter: '',
|
|
13
|
-
newDocs: {
|
|
14
|
-
sites: {
|
|
15
|
-
name: { bindings: { 'field-type': 'text', 'label': 'Name', 'helper': 'Name' }, cols: '12', value: '' },
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const schemas = {
|
|
21
|
-
sites: toTypedSchema(z.object({
|
|
22
|
-
name: z.string({
|
|
23
|
-
required_error: 'Name is required',
|
|
24
|
-
}).min(1, { message: 'Name is required' }),
|
|
25
|
-
})),
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const collection = computed(() => {
|
|
29
|
-
if (route.params.collection) {
|
|
30
|
-
return route.params.collection
|
|
31
|
-
}
|
|
32
|
-
return ''
|
|
33
|
-
})
|
|
34
|
-
const docId = computed(() => {
|
|
35
|
-
if (route.params.docId) {
|
|
36
|
-
return route.params.docId
|
|
37
|
-
}
|
|
38
|
-
return ''
|
|
39
|
-
})
|
|
40
|
-
definePageMeta({
|
|
41
|
-
middleware: 'auth',
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
onBeforeMount(() => {
|
|
45
|
-
// edgeGlobal.showLeftPanel(true)
|
|
46
|
-
})
|
|
47
|
-
const isAdmin = computed(() => {
|
|
48
|
-
return edgeGlobal.isAdminGlobal(edgeFirebase).value
|
|
49
|
-
})
|
|
50
|
-
const queryField = computed(() => {
|
|
51
|
-
if (!isAdmin.value) {
|
|
52
|
-
return 'users'
|
|
53
|
-
}
|
|
54
|
-
return ''
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const queryValue = computed(() => {
|
|
58
|
-
if (!isAdmin.value) {
|
|
59
|
-
return [edgeFirebase?.user?.uid]
|
|
60
|
-
}
|
|
61
|
-
return ''
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const queryOperator = computed(() => {
|
|
65
|
-
if (!isAdmin.value) {
|
|
66
|
-
return 'array-contains-any'
|
|
67
|
-
}
|
|
68
|
-
return ''
|
|
69
|
-
})
|
|
70
|
-
</script>
|
|
71
|
-
|
|
72
|
-
<template>
|
|
73
|
-
<div
|
|
74
|
-
v-if="edgeGlobal.edgeState.organizationDocPath"
|
|
75
|
-
>
|
|
76
|
-
<edge-dashboard :load-first-if-one="!isAdmin" :filter="state.filter" :query-field="queryField" :query-value="queryValue" :query-operator="queryOperator" collection="sites" class="flex-1 pt-0">
|
|
77
|
-
<template #list="slotProps">
|
|
78
|
-
<template v-for="item in slotProps.filtered" :key="item.docId">
|
|
79
|
-
<edge-shad-button
|
|
80
|
-
variant="text"
|
|
81
|
-
class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
|
|
82
|
-
:to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
|
|
83
|
-
:disabled="isAiBusy(item.aiBootstrapStatus)"
|
|
84
|
-
>
|
|
85
|
-
<div>
|
|
86
|
-
<Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
|
|
87
|
-
<FilePenLine class="h-5 w-5" />
|
|
88
|
-
</Avatar>
|
|
89
|
-
</div>
|
|
90
|
-
<div class="grow text-left">
|
|
91
|
-
<div class="text-lg">
|
|
92
|
-
{{ item.name }}
|
|
93
|
-
</div>
|
|
94
|
-
<div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
|
|
95
|
-
<Loader2 class="h-3 w-3 animate-spin" />
|
|
96
|
-
<span>AI is preparing this site</span>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
<div>
|
|
100
|
-
<edge-shad-button
|
|
101
|
-
size="icon"
|
|
102
|
-
class="bg-slate-600 h-7 w-7"
|
|
103
|
-
@click.stop="slotProps.deleteItem(item.docId)"
|
|
104
|
-
>
|
|
105
|
-
<Trash class="h-5 w-5" />
|
|
106
|
-
</edge-shad-button>
|
|
107
|
-
</div>
|
|
108
|
-
</edge-shad-button>
|
|
109
|
-
<Separator class="dark:bg-slate-600" />
|
|
110
|
-
</template>
|
|
111
|
-
</template>
|
|
112
|
-
</edge-dashboard>
|
|
113
|
-
</div>
|
|
114
|
-
</template>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{pages/app/dashboard/templates.vue → edge/routes/cms/dashboard/templates/index.vue}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|