@edgedev/create-edge-app 1.1.25 → 1.1.27
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 +55 -20
- package/{agent.md → agents.md} +2 -0
- package/bin/cli.js +6 -6
- package/edge/components/auth/login.vue +384 -0
- package/edge/components/auth/register.vue +396 -0
- package/edge/components/auth.vue +108 -0
- package/edge/components/autoFileUpload.vue +215 -0
- package/edge/components/billing.vue +8 -0
- package/edge/components/buttonDivider.vue +14 -0
- package/edge/components/chip.vue +34 -0
- package/edge/components/clipboardButton.vue +42 -0
- package/edge/components/cms/block.vue +529 -0
- package/edge/components/cms/blockApi.vue +212 -0
- package/edge/components/cms/blockEditor.vue +725 -0
- package/edge/components/cms/blockInput.vue +66 -0
- package/edge/components/cms/blockPicker.vue +486 -0
- package/edge/components/cms/blockRender.vue +78 -0
- package/edge/components/cms/blockSheetContent.vue +28 -0
- package/edge/components/cms/codeEditor.vue +466 -0
- package/edge/components/cms/fontUpload.vue +327 -0
- package/edge/components/cms/htmlContent.vue +807 -0
- package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
- package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
- package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
- package/edge/components/cms/init_blocks/carousel.html +103 -0
- package/edge/components/cms/init_blocks/contact_us.html +69 -0
- package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
- package/edge/components/cms/init_blocks/footer.html +24 -0
- package/edge/components/cms/init_blocks/header_divider.html +7 -0
- package/edge/components/cms/init_blocks/hero.html +35 -0
- package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
- package/edge/components/cms/init_blocks/newsletter.html +117 -0
- package/edge/components/cms/init_blocks/post_content.html +7 -0
- package/edge/components/cms/init_blocks/post_title_header.html +21 -0
- package/edge/components/cms/init_blocks/posts_list.html +20 -0
- package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
- package/edge/components/cms/init_blocks/property_carousel.html +59 -0
- package/edge/components/cms/init_blocks/property_detail.html +112 -0
- package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
- package/edge/components/cms/init_blocks/property_results.html +137 -0
- package/edge/components/cms/init_blocks/property_search.html +75 -0
- package/edge/components/cms/init_blocks/simple_array.html +7 -0
- package/edge/components/cms/mediaCard.vue +116 -0
- package/edge/components/cms/mediaManager.vue +386 -0
- package/edge/components/cms/menu.vue +1103 -0
- package/edge/components/cms/optionsSelect.vue +107 -0
- package/edge/components/cms/page.vue +1785 -0
- package/edge/components/cms/posts.vue +1083 -0
- package/edge/components/cms/site.vue +1475 -0
- package/edge/components/cms/themeDefaultMenu.vue +548 -0
- package/edge/components/cms/themeEditor.vue +429 -0
- package/edge/components/dashboard.vue +776 -0
- package/edge/components/editor.vue +671 -0
- package/edge/components/fileTree.vue +72 -0
- package/edge/components/files.vue +89 -0
- package/edge/components/formSubtypes/myOrgs.vue +214 -0
- package/edge/components/formSubtypes/users.vue +336 -0
- package/edge/components/functionChips.vue +57 -0
- package/edge/components/gError.vue +98 -0
- package/edge/components/gHelper.vue +67 -0
- package/edge/components/gInput.vue +1331 -0
- package/edge/components/loggingIn.vue +41 -0
- package/edge/components/menu.vue +137 -0
- package/edge/components/menuContent.vue +132 -0
- package/edge/components/myAccount.vue +317 -0
- package/edge/components/myOrganizations.vue +75 -0
- package/edge/components/myProfile.vue +122 -0
- package/edge/components/orgSwitcher.vue +25 -0
- package/edge/components/organizationMembers.vue +522 -0
- package/edge/components/organizationSettings.vue +271 -0
- package/edge/components/shad/breadcrumbs.vue +35 -0
- package/edge/components/shad/button.vue +43 -0
- package/edge/components/shad/checkbox.vue +73 -0
- package/edge/components/shad/combobox.vue +238 -0
- package/edge/components/shad/datepicker.vue +184 -0
- package/edge/components/shad/dialog.vue +32 -0
- package/edge/components/shad/dropdownMenu.vue +54 -0
- package/edge/components/shad/dropdownMenuItem.vue +21 -0
- package/edge/components/shad/form.vue +59 -0
- package/edge/components/shad/html.vue +877 -0
- package/edge/components/shad/input.vue +139 -0
- package/edge/components/shad/number.vue +109 -0
- package/edge/components/shad/select.vue +151 -0
- package/edge/components/shad/selectTags.vue +278 -0
- package/edge/components/shad/switch.vue +67 -0
- package/edge/components/shad/tags.vue +137 -0
- package/edge/components/shad/textarea.vue +102 -0
- package/edge/components/shad/typeMoney.vue +167 -0
- package/edge/components/sideBar.vue +288 -0
- package/edge/components/sideBarContent.vue +268 -0
- package/edge/components/sidebarProvider.vue +33 -0
- package/edge/components/tooltip.vue +16 -0
- package/edge/components/userMenu.vue +148 -0
- package/edge/components/v/alert.vue +59 -0
- package/edge/components/v/alertTitle.vue +18 -0
- package/edge/components/v/card.vue +53 -0
- package/edge/components/v/cardActions.vue +18 -0
- package/edge/components/v/cardText.vue +18 -0
- package/edge/components/v/cardTitle.vue +20 -0
- package/edge/components/v/col.vue +56 -0
- package/edge/components/v/list.vue +46 -0
- package/edge/components/v/listItem.vue +26 -0
- package/edge/components/v/listItemTitle.vue +18 -0
- package/edge/components/v/row.vue +42 -0
- package/edge/components/v/toolbar.vue +24 -0
- package/edge/composables/global.ts +519 -0
- package/edge-pull.sh +2 -0
- package/edge-push.sh +1 -0
- package/edge-status.sh +14 -0
- package/package.json +1 -1
- package/edge-components-install.sh +0 -1
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useVModel } from '@vueuse/core'
|
|
3
|
+
import { ImagePlus, Plus } from 'lucide-vue-next'
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
modelValue: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
blockId: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
editMode: {
|
|
14
|
+
type: Boolean,
|
|
15
|
+
default: true,
|
|
16
|
+
},
|
|
17
|
+
theme: {
|
|
18
|
+
type: Object,
|
|
19
|
+
default: null,
|
|
20
|
+
},
|
|
21
|
+
siteId: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: '',
|
|
24
|
+
},
|
|
25
|
+
viewportMode: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: 'auto',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
const emit = defineEmits(['update:modelValue', 'delete'])
|
|
31
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
32
|
+
function extractFieldsInOrder(template) {
|
|
33
|
+
if (!template || typeof template !== 'string')
|
|
34
|
+
return []
|
|
35
|
+
const fields = []
|
|
36
|
+
const seen = new Set()
|
|
37
|
+
const TAG_RE = /\{\{\{#[^\s]+\s+(\{[\s\S]*?\})\}\}\}/g
|
|
38
|
+
let m = TAG_RE.exec(template)
|
|
39
|
+
while (m) {
|
|
40
|
+
const cfg = m[1]
|
|
41
|
+
const fm = cfg.match(/"field"\s*:\s*"([^"]+)"/)
|
|
42
|
+
if (fm && !seen.has(fm[1])) {
|
|
43
|
+
fields.push(fm[1])
|
|
44
|
+
seen.add(fm[1])
|
|
45
|
+
}
|
|
46
|
+
m = TAG_RE.exec(template)
|
|
47
|
+
}
|
|
48
|
+
return fields
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const modelValue = useVModel(props, 'modelValue', emit)
|
|
52
|
+
|
|
53
|
+
const state = reactive({
|
|
54
|
+
open: false,
|
|
55
|
+
draft: {},
|
|
56
|
+
delete: false,
|
|
57
|
+
meta: {},
|
|
58
|
+
arrayItems: {},
|
|
59
|
+
reload: false,
|
|
60
|
+
metaUpdate: {},
|
|
61
|
+
loading: true,
|
|
62
|
+
afterLoad: false,
|
|
63
|
+
imageOpen: false,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const ensureQueryItemsDefaults = (meta) => {
|
|
67
|
+
Object.keys(meta || {}).forEach((key) => {
|
|
68
|
+
const cfg = meta[key]
|
|
69
|
+
if (!cfg?.queryOptions || cfg.queryOptions.length === 0)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
if (!cfg.queryItems)
|
|
73
|
+
cfg.queryItems = {}
|
|
74
|
+
|
|
75
|
+
for (const option of cfg.queryOptions) {
|
|
76
|
+
const hasField = Object.prototype.hasOwnProperty.call(cfg.queryItems, option.field)
|
|
77
|
+
if (!hasField) {
|
|
78
|
+
cfg.queryItems[option.field] = (cfg.collection?.path === 'posts' && option.field === 'tags') ? [] : null
|
|
79
|
+
}
|
|
80
|
+
else if (cfg.queryItems[option.field] === '') {
|
|
81
|
+
// Normalize empty strings from older saves so "(none)" stays unset
|
|
82
|
+
cfg.queryItems[option.field] = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sanitizeQueryItems = (meta) => {
|
|
89
|
+
const cleaned = JSON.parse(JSON.stringify(meta || {}))
|
|
90
|
+
for (const key of Object.keys(cleaned)) {
|
|
91
|
+
const cfg = cleaned[key]
|
|
92
|
+
if (!cfg?.queryItems || typeof cfg.queryItems !== 'object')
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
for (const field of Object.keys(cfg.queryItems)) {
|
|
96
|
+
const value = cfg.queryItems[field]
|
|
97
|
+
const isEmptyArray = Array.isArray(value) && value.length === 0
|
|
98
|
+
if (value === null || value === '' || isEmptyArray) {
|
|
99
|
+
delete cfg.queryItems[field]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (cfg.queryItems && Object.keys(cfg.queryItems).length === 0)
|
|
104
|
+
delete cfg.queryItems
|
|
105
|
+
}
|
|
106
|
+
return cleaned
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const resetArrayItems = (field) => {
|
|
110
|
+
if (!state.arrayItems?.[field]) {
|
|
111
|
+
state.arrayItems[field] = {}
|
|
112
|
+
}
|
|
113
|
+
for (const schemaItem of modelValue.value.meta[field].schema) {
|
|
114
|
+
if (schemaItem.type === 'text') {
|
|
115
|
+
state.arrayItems[field][schemaItem.field] = ''
|
|
116
|
+
}
|
|
117
|
+
else if (schemaItem.type === 'number') {
|
|
118
|
+
state.arrayItems[field][schemaItem.field] = 0
|
|
119
|
+
}
|
|
120
|
+
else if (schemaItem.type === 'richtext') {
|
|
121
|
+
state.arrayItems[field][schemaItem.field] = ''
|
|
122
|
+
}
|
|
123
|
+
else if (schemaItem.type === 'textarea') {
|
|
124
|
+
state.arrayItems[field][schemaItem.field] = ''
|
|
125
|
+
}
|
|
126
|
+
else if (schemaItem.type === 'image') {
|
|
127
|
+
state.arrayItems[field][schemaItem.field] = ''
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const openEditor = async () => {
|
|
133
|
+
if (!props.editMode)
|
|
134
|
+
return
|
|
135
|
+
for (const key of Object.keys(modelValue.value?.meta || {})) {
|
|
136
|
+
if (modelValue.value.meta[key]?.type === 'array' && modelValue.value.meta[key]?.schema) {
|
|
137
|
+
if (!modelValue.value.meta[key]?.api) {
|
|
138
|
+
resetArrayItems(key)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
state.draft = JSON.parse(JSON.stringify(modelValue.value?.values || {}))
|
|
143
|
+
state.meta = JSON.parse(JSON.stringify(modelValue.value?.meta || {}))
|
|
144
|
+
ensureQueryItemsDefaults(state.meta)
|
|
145
|
+
const blockData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]?.[modelValue.value.blockId]
|
|
146
|
+
state.metaUpdate = edgeGlobal.dupObject(modelValue.value?.meta) || {}
|
|
147
|
+
if (blockData?.meta) {
|
|
148
|
+
for (const key of Object.keys(blockData.meta)) {
|
|
149
|
+
if (!(key in state.metaUpdate)) {
|
|
150
|
+
state.metaUpdate[key] = blockData.meta[key]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (blockData?.values) {
|
|
155
|
+
for (const key of Object.keys(blockData.values)) {
|
|
156
|
+
if (!(key in state.draft)) {
|
|
157
|
+
state.draft[key] = blockData.values[key]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
modelValue.value.blockUpdatedAt = new Date().toISOString()
|
|
162
|
+
state.open = true
|
|
163
|
+
state.afterLoad = true
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const save = () => {
|
|
167
|
+
const updated = {
|
|
168
|
+
...modelValue.value,
|
|
169
|
+
values: JSON.parse(JSON.stringify(state.draft)),
|
|
170
|
+
meta: sanitizeQueryItems(state.meta),
|
|
171
|
+
}
|
|
172
|
+
modelValue.value = updated
|
|
173
|
+
state.open = false
|
|
174
|
+
}
|
|
175
|
+
const orderedMeta = computed(() => {
|
|
176
|
+
const metaObj = state.metaUpdate || {}
|
|
177
|
+
const tpl = modelValue.value?.content || ''
|
|
178
|
+
const orderedFields = extractFieldsInOrder(tpl)
|
|
179
|
+
|
|
180
|
+
const out = []
|
|
181
|
+
const picked = new Set()
|
|
182
|
+
|
|
183
|
+
for (const f of orderedFields) {
|
|
184
|
+
if (f in metaObj) {
|
|
185
|
+
out.push({ field: f, meta: metaObj[f] })
|
|
186
|
+
picked.add(f)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const f of Object.keys(metaObj)) {
|
|
191
|
+
if (!picked.has(f)) {
|
|
192
|
+
out.push({ field: f, meta: metaObj[f] })
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return out
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const genTitleFromField = (field) => {
|
|
200
|
+
if (field?.title)
|
|
201
|
+
return field.title
|
|
202
|
+
if (field?.meta?.title)
|
|
203
|
+
return field.meta.title
|
|
204
|
+
// Insert space before a capital only if it's followed by a lowercase
|
|
205
|
+
return field.field
|
|
206
|
+
// Insert space before a capital only if it's followed by a lowercase
|
|
207
|
+
.replace('_', ' ')
|
|
208
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
209
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
210
|
+
.replace(/^./, str => str.toUpperCase())
|
|
211
|
+
}
|
|
212
|
+
const addToArray = async (field) => {
|
|
213
|
+
state.reload = true
|
|
214
|
+
state.draft[field].push(JSON.parse(JSON.stringify(state.arrayItems[field])))
|
|
215
|
+
resetArrayItems(field)
|
|
216
|
+
await nextTick()
|
|
217
|
+
state.reload = false
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const loadingRender = (content) => {
|
|
221
|
+
if (state.loading) {
|
|
222
|
+
content = content.replaceAll('{{loading}}', '')
|
|
223
|
+
content = content.replaceAll('{{loaded}}', 'hidden')
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
content = content.replaceAll('{{loading}}', 'hidden')
|
|
227
|
+
content = content.replaceAll('{{loaded}}', '')
|
|
228
|
+
}
|
|
229
|
+
return content
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const postsList = computed(() => {
|
|
233
|
+
const postsCollectionPath = `${edgeGlobal.edgeState.organizationDocPath}/sites/${props.siteId}/posts`
|
|
234
|
+
return Object.values(edgeFirebase.data[postsCollectionPath] || {}).sort((a, b) => {
|
|
235
|
+
if (a.publishDate && b.publishDate) {
|
|
236
|
+
return new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime()
|
|
237
|
+
}
|
|
238
|
+
return 0
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
const getTagsFromPosts = computed(() => {
|
|
242
|
+
const tagMap = new Map()
|
|
243
|
+
postsList.value.forEach((post) => {
|
|
244
|
+
if (Array.isArray(post.tags)) {
|
|
245
|
+
post.tags.forEach((tag) => {
|
|
246
|
+
if (tag && typeof tag === 'string' && !tagMap.has(tag)) {
|
|
247
|
+
tagMap.set(tag, { name: tag, title: tag })
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
return Array.from(tagMap.values()).sort((a, b) => a.title.localeCompare(b.title))
|
|
253
|
+
})
|
|
254
|
+
</script>
|
|
255
|
+
|
|
256
|
+
<template>
|
|
257
|
+
<div>
|
|
258
|
+
<div
|
|
259
|
+
:class="{ 'cursor-pointer': props.editMode }"
|
|
260
|
+
class="relative group "
|
|
261
|
+
@click="openEditor"
|
|
262
|
+
>
|
|
263
|
+
<!-- Content -->
|
|
264
|
+
<edge-cms-block-api :site-id="props.siteId" :theme="props.theme" :content="modelValue?.content" :values="modelValue?.values" :meta="modelValue?.meta" :viewport-mode="props.viewportMode" @pending="state.loading = $event" />
|
|
265
|
+
<edge-cms-block-render
|
|
266
|
+
v-if="state.loading"
|
|
267
|
+
:content="loadingRender(modelValue?.content)"
|
|
268
|
+
:values="modelValue?.values"
|
|
269
|
+
:meta="modelValue?.meta"
|
|
270
|
+
:theme="props.theme"
|
|
271
|
+
:viewport-mode="props.viewportMode"
|
|
272
|
+
/>
|
|
273
|
+
<!-- Darken overlay on hover -->
|
|
274
|
+
<div v-if="props.editMode" class="pointer-events-none absolute inset-0 bg-black/50 opacity-0 transition-opacity duration-200 group-hover:opacity-100 z-10" />
|
|
275
|
+
|
|
276
|
+
<!-- Hover controls -->
|
|
277
|
+
<div v-if="props.editMode" class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-20">
|
|
278
|
+
<!-- Delete button top right -->
|
|
279
|
+
<div class="absolute top-2 right-2">
|
|
280
|
+
<edge-shad-button
|
|
281
|
+
variant="destructive"
|
|
282
|
+
size="icon"
|
|
283
|
+
@click.stop.prevent="state.delete = true"
|
|
284
|
+
>
|
|
285
|
+
<Trash class="h-4 w-4" />
|
|
286
|
+
</edge-shad-button>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<!-- Edit button centered -->
|
|
290
|
+
<div class="flex items-center justify-center h-full">
|
|
291
|
+
<!-- <edge-shad-button class="text-xl py-6 px-8" @click.stop.prevent="openEditor">
|
|
292
|
+
<Pencil class="w-4 h-4 mr-1" />
|
|
293
|
+
Edit
|
|
294
|
+
</edge-shad-button> -->
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
<edge-shad-dialog v-model="state.delete">
|
|
299
|
+
<DialogContent class="max-w-md">
|
|
300
|
+
<DialogHeader>
|
|
301
|
+
<DialogTitle>Delete Block</DialogTitle>
|
|
302
|
+
</DialogHeader>
|
|
303
|
+
<DialogDescription>
|
|
304
|
+
Are you sure you want to delete this block?
|
|
305
|
+
</DialogDescription>
|
|
306
|
+
<DialogFooter class="pt-2 flex justify-between">
|
|
307
|
+
<edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.delete = false">
|
|
308
|
+
Cancel
|
|
309
|
+
</edge-shad-button>
|
|
310
|
+
<edge-shad-button variant="destructive" class="text-white w-full" @click="emit('delete', props.blockId); state.delete = false">
|
|
311
|
+
Delete
|
|
312
|
+
</edge-shad-button>
|
|
313
|
+
</DialogFooter>
|
|
314
|
+
</DialogContent>
|
|
315
|
+
</edge-shad-dialog>
|
|
316
|
+
|
|
317
|
+
<Sheet v-model:open="state.open">
|
|
318
|
+
<edge-cms-block-sheet-content v-if="state.afterLoad" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
|
|
319
|
+
<SheetHeader>
|
|
320
|
+
<SheetTitle>Edit Block</SheetTitle>
|
|
321
|
+
<SheetDescription v-if="modelValue.synced" class="text-sm text-red-500">
|
|
322
|
+
This is a synced block. Changes made here will be reflected across all instances of this block on your site.
|
|
323
|
+
</SheetDescription>
|
|
324
|
+
</SheetHeader>
|
|
325
|
+
|
|
326
|
+
<edge-shad-form>
|
|
327
|
+
<div v-if="orderedMeta.length === 0">
|
|
328
|
+
<Alert variant="info" class="mt-4 mb-4">
|
|
329
|
+
<AlertTitle>No editable fields found</AlertTitle>
|
|
330
|
+
<AlertDescription class="text-sm">
|
|
331
|
+
This block does not have any editable fields defined.
|
|
332
|
+
</AlertDescription>
|
|
333
|
+
</Alert>
|
|
334
|
+
</div>
|
|
335
|
+
<div :class="modelValue.synced ? 'h-[calc(100vh-160px)]' : 'h-[calc(100vh-130px)]'" class="p-6 space-y-4 overflow-y-auto">
|
|
336
|
+
<template v-for="entry in orderedMeta" :key="entry.field">
|
|
337
|
+
<div v-if="entry.meta.type === 'array'">
|
|
338
|
+
<div v-if="!entry.meta?.api && !entry.meta?.collection">
|
|
339
|
+
<div v-if="entry.meta?.schema">
|
|
340
|
+
<Card v-if="!state.reload" class="mb-4 bg-white shadow-sm border border-gray-200 p-4">
|
|
341
|
+
<CardHeader class="p-0 mb-2">
|
|
342
|
+
<div class="relative flex items-center bg-secondary p-2 justify-between sticky top-0 z-10 bg-primary rounded">
|
|
343
|
+
<span class="text-lg font-semibold whitespace-nowrap pr-1"> {{ genTitleFromField(entry) }}</span>
|
|
344
|
+
<div class="flex w-full items-center">
|
|
345
|
+
<div class="w-full border-t border-gray-300 dark:border-white/15" aria-hidden="true" />
|
|
346
|
+
<edge-shad-button variant="text" class="hover:text-primary/50 text-xs h-[26px] text-primary" @click="state.editMode = !state.editMode">
|
|
347
|
+
<Popover>
|
|
348
|
+
<PopoverTrigger as-child>
|
|
349
|
+
<edge-shad-button
|
|
350
|
+
variant="text"
|
|
351
|
+
type="submit"
|
|
352
|
+
class="bg-secondary hover:text-primary/50 text-xs h-[26px] text-primary"
|
|
353
|
+
>
|
|
354
|
+
<Plus class="w-4 h-4" />
|
|
355
|
+
</edge-shad-button>
|
|
356
|
+
</PopoverTrigger>
|
|
357
|
+
<PopoverContent class="!w-80 mr-20">
|
|
358
|
+
<Card class="border-none shadow-none p-4">
|
|
359
|
+
<template v-for="schemaItem in entry.meta.schema" :key="schemaItem.field">
|
|
360
|
+
<edge-cms-block-input
|
|
361
|
+
v-model="state.arrayItems[entry.field][schemaItem.field]"
|
|
362
|
+
:type="schemaItem.type"
|
|
363
|
+
:field="schemaItem.field"
|
|
364
|
+
:schema="schemaItem"
|
|
365
|
+
:label="genTitleFromField(schemaItem)"
|
|
366
|
+
/>
|
|
367
|
+
</template>
|
|
368
|
+
<CardFooter class="mt-2 flex justify-end">
|
|
369
|
+
<edge-shad-button
|
|
370
|
+
class="bg-secondary hover:text-white text-xs h-[26px] text-primary"
|
|
371
|
+
@click="addToArray(entry.field)"
|
|
372
|
+
>
|
|
373
|
+
Add Entry
|
|
374
|
+
</edge-shad-button>
|
|
375
|
+
</CardFooter>
|
|
376
|
+
</Card>
|
|
377
|
+
</PopoverContent>
|
|
378
|
+
</Popover>
|
|
379
|
+
</edge-shad-button>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</CardHeader>
|
|
383
|
+
<draggable
|
|
384
|
+
v-if="state.draft?.[entry.field] && state.draft[entry.field].length > 0"
|
|
385
|
+
v-model="state.draft[entry.field]"
|
|
386
|
+
handle=".handle"
|
|
387
|
+
item-key="index"
|
|
388
|
+
>
|
|
389
|
+
<template #item="{ element, index }">
|
|
390
|
+
<div :key="index" class="">
|
|
391
|
+
<div class="flex gap-2 w-full items-center w-full border-1 border-dotted py-1 mb-1">
|
|
392
|
+
<div class="text-left px-2">
|
|
393
|
+
<Grip class="handle pointer" />
|
|
394
|
+
</div>
|
|
395
|
+
<div class="px-2 py-2 w-[98%] flex gap-1">
|
|
396
|
+
<template v-for="schemaItem in entry.meta.schema" :key="schemaItem.field">
|
|
397
|
+
<Popover>
|
|
398
|
+
<PopoverTrigger as-child>
|
|
399
|
+
<Alert class="w-[200px] text-xs py-1 px-2 cursor-pointer hover:bg-primary hover:text-white">
|
|
400
|
+
<AlertTitle> {{ genTitleFromField(schemaItem) }}</AlertTitle>
|
|
401
|
+
<AlertDescription class="text-sm truncate max-w-[200px]">
|
|
402
|
+
{{ element[schemaItem.field] }}
|
|
403
|
+
</AlertDescription>
|
|
404
|
+
</Alert>
|
|
405
|
+
</PopoverTrigger>
|
|
406
|
+
<PopoverContent class="!w-80 mr-20">
|
|
407
|
+
<Card class="border-none shadow-none p-4">
|
|
408
|
+
<edge-cms-block-input
|
|
409
|
+
v-model="element[schemaItem.field]"
|
|
410
|
+
:type="schemaItem.type"
|
|
411
|
+
:schema="schemaItem"
|
|
412
|
+
:field="`${schemaItem.field}-${index}-entry`"
|
|
413
|
+
:label="genTitleFromField(schemaItem)"
|
|
414
|
+
/>
|
|
415
|
+
</Card>
|
|
416
|
+
</PopoverContent>
|
|
417
|
+
</Popover>
|
|
418
|
+
</template>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="pr-2">
|
|
421
|
+
<edge-shad-button
|
|
422
|
+
variant="destructive"
|
|
423
|
+
size="icon"
|
|
424
|
+
@click="state.draft[entry.field].splice(index, 1)"
|
|
425
|
+
>
|
|
426
|
+
<Trash class="h-4 w-4" />
|
|
427
|
+
</edge-shad-button>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
</template>
|
|
432
|
+
</draggable>
|
|
433
|
+
</Card>
|
|
434
|
+
</div>
|
|
435
|
+
<edge-cms-block-input
|
|
436
|
+
v-else
|
|
437
|
+
v-model="state.draft[entry.field]"
|
|
438
|
+
:type="entry.meta.type"
|
|
439
|
+
:field="entry.field"
|
|
440
|
+
:label="genTitleFromField(entry)"
|
|
441
|
+
/>
|
|
442
|
+
</div>
|
|
443
|
+
<div v-else>
|
|
444
|
+
<template v-if="entry.meta?.queryOptions">
|
|
445
|
+
<div v-for="option in entry.meta.queryOptions" :key="option.field" class="mb-2">
|
|
446
|
+
<edge-shad-select-tags
|
|
447
|
+
v-if="entry.meta?.collection?.path === 'posts' && option.field === 'tags'"
|
|
448
|
+
v-model="state.meta[entry.field].queryItems[option.field]"
|
|
449
|
+
:items="getTagsFromPosts"
|
|
450
|
+
:label="`${genTitleFromField(option)}`"
|
|
451
|
+
:name="option.field"
|
|
452
|
+
:placeholder="`Select ${genTitleFromField(option)}`"
|
|
453
|
+
/>
|
|
454
|
+
<edge-cms-options-select
|
|
455
|
+
v-else-if="entry.meta?.collection?.path !== 'post'"
|
|
456
|
+
v-model="state.meta[entry.field].queryItems[option.field]"
|
|
457
|
+
:option="option"
|
|
458
|
+
:label="genTitleFromField(option)"
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
</template>
|
|
462
|
+
<edge-shad-number v-if="entry.meta?.collection?.path !== 'post'" v-model="state.meta[entry.field].limit" name="limit" label="Limit" />
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
<div v-else-if="entry.meta?.type === 'image'" class="w-full">
|
|
466
|
+
<div class="relative bg-muted py-2 rounded-md">
|
|
467
|
+
<div class="bg-black/80 absolute left-0 top-0 w-full h-full opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center z-10 cursor-pointer">
|
|
468
|
+
<Dialog v-model:open="state.imageOpen">
|
|
469
|
+
<DialogTrigger as-child>
|
|
470
|
+
<edge-shad-button variant="outline" class="bg-white text-black hover:bg-gray-200">
|
|
471
|
+
<ImagePlus class="h-5 w-5 mr-2" />
|
|
472
|
+
Select Image
|
|
473
|
+
</edge-shad-button>
|
|
474
|
+
</DialogTrigger>
|
|
475
|
+
<DialogContent class="w-full max-w-[1200px] max-h-[80vh] overflow-y-auto">
|
|
476
|
+
<DialogHeader>
|
|
477
|
+
<DialogTitle>Select Image</DialogTitle>
|
|
478
|
+
<DialogDescription />
|
|
479
|
+
</DialogHeader>
|
|
480
|
+
<edge-cms-media-manager
|
|
481
|
+
v-if="entry.meta?.tags && entry.meta.tags.length > 0"
|
|
482
|
+
:site="props.siteId"
|
|
483
|
+
:select-mode="true"
|
|
484
|
+
:default-tags="entry.meta.tags"
|
|
485
|
+
@select="(url) => { state.draft[entry.field] = url; state.imageOpen = false; }"
|
|
486
|
+
/>
|
|
487
|
+
<edge-cms-media-manager
|
|
488
|
+
v-else
|
|
489
|
+
:site="props.siteId"
|
|
490
|
+
:select-mode="true"
|
|
491
|
+
@select="(url) => { state.draft[entry.field] = url; state.imageOpen = false; }"
|
|
492
|
+
/>
|
|
493
|
+
</DialogContent>
|
|
494
|
+
</Dialog>
|
|
495
|
+
</div>
|
|
496
|
+
<img v-if="state.draft[entry.field]" :src="state.draft[entry.field]" class="mb-2 max-h-40 mx-auto object-contain">
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div v-else-if="entry.meta?.option">
|
|
500
|
+
<edge-cms-options-select
|
|
501
|
+
v-model="state.draft[entry.field]"
|
|
502
|
+
:option="entry.meta.option"
|
|
503
|
+
:label="genTitleFromField(entry)"
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
506
|
+
<div v-else>
|
|
507
|
+
<edge-cms-block-input
|
|
508
|
+
v-model="state.draft[entry.field]"
|
|
509
|
+
:type="entry.meta.type"
|
|
510
|
+
:field="entry.field"
|
|
511
|
+
:label="genTitleFromField(entry)"
|
|
512
|
+
/>
|
|
513
|
+
</div>
|
|
514
|
+
</template>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<SheetFooter class="pt-2 flex justify-between">
|
|
518
|
+
<edge-shad-button variant="destructive" class="text-white" @click="state.open = false">
|
|
519
|
+
Cancel
|
|
520
|
+
</edge-shad-button>
|
|
521
|
+
<edge-shad-button class=" bg-slate-800 hover:bg-slate-400 w-full" @click="save">
|
|
522
|
+
Save changes
|
|
523
|
+
</edge-shad-button>
|
|
524
|
+
</SheetFooter>
|
|
525
|
+
</edge-shad-form>
|
|
526
|
+
</edge-cms-block-sheet-content>
|
|
527
|
+
</Sheet>
|
|
528
|
+
</div>
|
|
529
|
+
</template>
|