@edgedev/create-edge-app 1.1.27 → 1.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/edge/components/auth/register.vue +51 -0
- package/edge/components/cms/block.vue +363 -42
- package/edge/components/cms/blockEditor.vue +50 -3
- package/edge/components/cms/codeEditor.vue +39 -2
- package/edge/components/cms/htmlContent.vue +10 -2
- 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 +35 -5
- package/edge/components/cms/menu.vue +384 -61
- package/edge/components/cms/optionsSelect.vue +20 -3
- package/edge/components/cms/page.vue +160 -18
- package/edge/components/cms/site.vue +548 -374
- package/edge/components/cms/siteSettingsForm.vue +623 -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/imagePicker.vue +126 -0
- package/edge/components/myAccount.vue +1 -0
- package/edge/components/myProfile.vue +345 -61
- package/edge/components/orgSwitcher.vue +1 -1
- package/edge/components/organizationMembers.vue +620 -235
- package/edge/components/shad/html.vue +6 -0
- package/edge/components/shad/number.vue +2 -2
- package/edge/components/sideBar.vue +7 -4
- package/edge/components/sideBarContent.vue +1 -1
- package/edge/components/userMenu.vue +50 -14
- package/edge/composables/global.ts +4 -1
- package/edge/composables/siteSettingsTemplate.js +79 -0
- package/edge/composables/structuredDataTemplates.js +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
<script setup lang="js">
|
|
2
|
+
import { CircleAlert } from 'lucide-vue-next'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
settings: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
themeOptions: {
|
|
10
|
+
type: Array,
|
|
11
|
+
default: () => [],
|
|
12
|
+
},
|
|
13
|
+
userOptions: {
|
|
14
|
+
type: Array,
|
|
15
|
+
default: () => [],
|
|
16
|
+
},
|
|
17
|
+
hasUsers: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
showUsers: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
showThemeFields: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true,
|
|
28
|
+
},
|
|
29
|
+
isAdmin: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
enableMediaPicker: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
siteId: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
domainError: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: '',
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
48
|
+
|
|
49
|
+
const state = reactive({
|
|
50
|
+
logoPickerOpen: false,
|
|
51
|
+
logoLightPickerOpen: false,
|
|
52
|
+
brandLogoDarkPickerOpen: false,
|
|
53
|
+
brandLogoLightPickerOpen: false,
|
|
54
|
+
faviconPickerOpen: false,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const themeOptionsMap = computed(() => {
|
|
58
|
+
const map = new Map()
|
|
59
|
+
for (const option of props.themeOptions) {
|
|
60
|
+
if (option?.value)
|
|
61
|
+
map.set(option.value, option)
|
|
62
|
+
}
|
|
63
|
+
return map
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const isLightName = (value) => {
|
|
67
|
+
if (!value)
|
|
68
|
+
return false
|
|
69
|
+
return String(value).toLowerCase().includes('light')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const previewBackgroundClass = value => (isLightName(value) ? 'bg-neutral-900/90' : 'bg-neutral-100')
|
|
73
|
+
|
|
74
|
+
const themeItemsForAllowed = (allowed, current) => {
|
|
75
|
+
const base = props.themeOptions || []
|
|
76
|
+
const allowedList = Array.isArray(allowed) ? allowed.filter(Boolean) : []
|
|
77
|
+
if (allowedList.length) {
|
|
78
|
+
const allowedSet = new Set(allowedList)
|
|
79
|
+
const filtered = base.filter(option => allowedSet.has(option.value))
|
|
80
|
+
if (current && !allowedSet.has(current)) {
|
|
81
|
+
const currentOption = themeOptionsMap.value.get(current)
|
|
82
|
+
if (currentOption)
|
|
83
|
+
filtered.push(currentOption)
|
|
84
|
+
}
|
|
85
|
+
return filtered
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (current) {
|
|
89
|
+
const currentOption = themeOptionsMap.value.get(current)
|
|
90
|
+
return currentOption ? [currentOption] : []
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const menuPositionOptions = [
|
|
97
|
+
{ value: 'left', label: 'Left' },
|
|
98
|
+
{ value: 'center', label: 'Center' },
|
|
99
|
+
{ value: 'right', label: 'Right' },
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
const domainError = computed(() => String(props.domainError || '').trim())
|
|
103
|
+
const serverPagesProject = ref('')
|
|
104
|
+
const pagesProject = computed(() => String(serverPagesProject.value || '').trim())
|
|
105
|
+
const pagesDomain = computed(() => (pagesProject.value ? `${pagesProject.value}.pages.dev` : '(CLOUDFLARE_PAGES_PROJECT).pages.dev'))
|
|
106
|
+
|
|
107
|
+
onMounted(async () => {
|
|
108
|
+
if (!edgeFirebase?.runFunction)
|
|
109
|
+
return
|
|
110
|
+
try {
|
|
111
|
+
const response = await edgeFirebase.runFunction('cms-getCloudflarePagesProject', {})
|
|
112
|
+
serverPagesProject.value = String(response?.data?.project || '').trim()
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
serverPagesProject.value = ''
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<Tabs class="w-full" default-value="general">
|
|
122
|
+
<TabsList class="w-full gap-2 bg-muted/40 p-1 rounded-lg">
|
|
123
|
+
<TabsTrigger value="general" class="text-sm uppercase font-medium">
|
|
124
|
+
General
|
|
125
|
+
</TabsTrigger>
|
|
126
|
+
<TabsTrigger value="appearance" class="text-sm uppercase font-medium">
|
|
127
|
+
Appearance
|
|
128
|
+
</TabsTrigger>
|
|
129
|
+
<TabsTrigger value="branding" class="text-sm uppercase font-medium">
|
|
130
|
+
Branding
|
|
131
|
+
</TabsTrigger>
|
|
132
|
+
<TabsTrigger value="seo" class="text-sm uppercase font-medium">
|
|
133
|
+
SEO
|
|
134
|
+
</TabsTrigger>
|
|
135
|
+
<TabsTrigger value="tracking" class="text-sm uppercase font-medium">
|
|
136
|
+
Tracking Pixels
|
|
137
|
+
</TabsTrigger>
|
|
138
|
+
<TabsTrigger value="social" class="text-sm uppercase font-medium">
|
|
139
|
+
Social Media
|
|
140
|
+
</TabsTrigger>
|
|
141
|
+
</TabsList>
|
|
142
|
+
<TabsContent value="general" class="pt-4 space-y-4">
|
|
143
|
+
<edge-shad-input
|
|
144
|
+
v-model="props.settings.name"
|
|
145
|
+
name="name"
|
|
146
|
+
label="Name"
|
|
147
|
+
placeholder="Enter name"
|
|
148
|
+
class="w-full"
|
|
149
|
+
/>
|
|
150
|
+
<edge-shad-tags
|
|
151
|
+
v-model="props.settings.domains"
|
|
152
|
+
name="domains"
|
|
153
|
+
label="Domains"
|
|
154
|
+
placeholder="Add or remove domains"
|
|
155
|
+
class="w-full"
|
|
156
|
+
/>
|
|
157
|
+
<Alert v-if="domainError" variant="destructive">
|
|
158
|
+
<CircleAlert class="h-4 w-4" />
|
|
159
|
+
<AlertTitle>Domain error</AlertTitle>
|
|
160
|
+
<AlertDescription class="text-sm">
|
|
161
|
+
{{ domainError }}
|
|
162
|
+
</AlertDescription>
|
|
163
|
+
</Alert>
|
|
164
|
+
<div class="rounded-lg border border-border/60 bg-muted/40 p-4 space-y-3">
|
|
165
|
+
<div class="flex items-center justify-between">
|
|
166
|
+
<div class="text-sm font-semibold text-foreground">
|
|
167
|
+
Domain DNS records
|
|
168
|
+
</div>
|
|
169
|
+
<div class="text-xs text-muted-foreground">
|
|
170
|
+
Target: <span class="font-mono">{{ pagesDomain }}</span>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<p class="text-sm text-muted-foreground">
|
|
174
|
+
Add these records at your DNS provider.
|
|
175
|
+
</p>
|
|
176
|
+
<div class="space-y-2 text-sm">
|
|
177
|
+
<div class="grid grid-cols-[70px_1fr] gap-3">
|
|
178
|
+
<div class="text-muted-foreground">CNAME</div>
|
|
179
|
+
<div class="font-mono">www → {{ pagesDomain }}</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="grid grid-cols-[70px_1fr] gap-3">
|
|
182
|
+
<div class="text-muted-foreground">CNAME</div>
|
|
183
|
+
<div class="font-mono">@ → {{ pagesDomain }}</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<p class="text-xs text-muted-foreground">
|
|
187
|
+
Then forward the root/apex (TLD) domain to <span class="font-mono">www</span> with a 301 redirect.
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
<edge-shad-input
|
|
191
|
+
v-model="props.settings.contactEmail"
|
|
192
|
+
name="contactEmail"
|
|
193
|
+
label="Contact Email"
|
|
194
|
+
placeholder="name@example.com"
|
|
195
|
+
class="w-full"
|
|
196
|
+
/>
|
|
197
|
+
<edge-shad-input
|
|
198
|
+
v-model="props.settings.contactPhone"
|
|
199
|
+
name="contactPhone"
|
|
200
|
+
type="tel"
|
|
201
|
+
label="Contact Phone"
|
|
202
|
+
placeholder="(555) 555-5555"
|
|
203
|
+
:mask-options="{ mask: '(###) ###-####' }"
|
|
204
|
+
class="w-full"
|
|
205
|
+
/>
|
|
206
|
+
<edge-shad-select-tags
|
|
207
|
+
v-if="props.showUsers && props.hasUsers && props.isAdmin"
|
|
208
|
+
v-model="props.settings.users"
|
|
209
|
+
:items="props.userOptions"
|
|
210
|
+
name="users"
|
|
211
|
+
label="Users"
|
|
212
|
+
item-title="label"
|
|
213
|
+
item-value="value"
|
|
214
|
+
placeholder="Select users"
|
|
215
|
+
class="w-full"
|
|
216
|
+
:multiple="true"
|
|
217
|
+
/>
|
|
218
|
+
<p v-else-if="props.showUsers" class="text-sm text-muted-foreground">
|
|
219
|
+
No organization users available for this site.
|
|
220
|
+
</p>
|
|
221
|
+
</TabsContent>
|
|
222
|
+
<TabsContent value="appearance" class="pt-4 space-y-4">
|
|
223
|
+
<edge-shad-select-tags
|
|
224
|
+
v-if="props.showThemeFields && props.isAdmin"
|
|
225
|
+
:model-value="Array.isArray(props.settings.allowedThemes) ? props.settings.allowedThemes : []"
|
|
226
|
+
name="allowedThemes"
|
|
227
|
+
label="Allowed Themes"
|
|
228
|
+
placeholder="Select allowed themes"
|
|
229
|
+
class="w-full"
|
|
230
|
+
:items="props.themeOptions"
|
|
231
|
+
item-title="label"
|
|
232
|
+
item-value="value"
|
|
233
|
+
@update:model-value="(value) => {
|
|
234
|
+
const normalized = Array.isArray(value) ? value : []
|
|
235
|
+
props.settings.allowedThemes = normalized
|
|
236
|
+
if (normalized.length && !normalized.includes(props.settings.theme)) {
|
|
237
|
+
props.settings.theme = normalized[0] || ''
|
|
238
|
+
}
|
|
239
|
+
}"
|
|
240
|
+
/>
|
|
241
|
+
<edge-shad-select
|
|
242
|
+
v-if="props.showThemeFields"
|
|
243
|
+
:model-value="props.settings.theme || ''"
|
|
244
|
+
name="theme"
|
|
245
|
+
label="Theme"
|
|
246
|
+
placeholder="Select a theme"
|
|
247
|
+
class="w-full"
|
|
248
|
+
:items="themeItemsForAllowed(props.settings.allowedThemes, props.settings.theme)"
|
|
249
|
+
item-title="label"
|
|
250
|
+
item-value="value"
|
|
251
|
+
@update:model-value="value => (props.settings.theme = value || '')"
|
|
252
|
+
/>
|
|
253
|
+
<edge-shad-select
|
|
254
|
+
:model-value="props.settings.menuPosition || ''"
|
|
255
|
+
name="menuPosition"
|
|
256
|
+
label="Menu Position"
|
|
257
|
+
placeholder="Select menu position"
|
|
258
|
+
class="w-full"
|
|
259
|
+
:items="menuPositionOptions"
|
|
260
|
+
item-title="label"
|
|
261
|
+
item-value="value"
|
|
262
|
+
@update:model-value="value => (props.settings.menuPosition = value || '')"
|
|
263
|
+
/>
|
|
264
|
+
</TabsContent>
|
|
265
|
+
<TabsContent value="branding" class="pt-4 space-y-4">
|
|
266
|
+
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
267
|
+
<label class="text-sm font-medium text-foreground flex items-center justify-between">
|
|
268
|
+
Dark logo
|
|
269
|
+
<edge-shad-button
|
|
270
|
+
type="button"
|
|
271
|
+
variant="link"
|
|
272
|
+
class="px-0 h-auto text-sm"
|
|
273
|
+
@click="state.logoPickerOpen = !state.logoPickerOpen"
|
|
274
|
+
>
|
|
275
|
+
{{ state.logoPickerOpen ? 'Hide picker' : 'Select logo' }}
|
|
276
|
+
</edge-shad-button>
|
|
277
|
+
</label>
|
|
278
|
+
<div class="flex items-center gap-4">
|
|
279
|
+
<div v-if="props.settings.logo" class="flex items-center gap-3">
|
|
280
|
+
<img
|
|
281
|
+
:src="props.settings.logo"
|
|
282
|
+
alt="Logo preview"
|
|
283
|
+
:class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logo)]"
|
|
284
|
+
>
|
|
285
|
+
<edge-shad-button
|
|
286
|
+
type="button"
|
|
287
|
+
variant="ghost"
|
|
288
|
+
class="h-8"
|
|
289
|
+
@click="props.settings.logo = ''"
|
|
290
|
+
>
|
|
291
|
+
Remove
|
|
292
|
+
</edge-shad-button>
|
|
293
|
+
</div>
|
|
294
|
+
<span v-else class="text-sm text-muted-foreground italic">No logo selected</span>
|
|
295
|
+
</div>
|
|
296
|
+
<div v-if="state.logoPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
|
|
297
|
+
<edge-cms-media-manager
|
|
298
|
+
:site="props.siteId"
|
|
299
|
+
:select-mode="true"
|
|
300
|
+
:default-tags="['Logos']"
|
|
301
|
+
@select="(url) => {
|
|
302
|
+
props.settings.logo = url
|
|
303
|
+
state.logoPickerOpen = false
|
|
304
|
+
}"
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<edge-shad-input
|
|
309
|
+
v-else
|
|
310
|
+
v-model="props.settings.logo"
|
|
311
|
+
name="logo"
|
|
312
|
+
label="Dark logo URL"
|
|
313
|
+
placeholder="https://..."
|
|
314
|
+
class="w-full"
|
|
315
|
+
/>
|
|
316
|
+
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
317
|
+
<label class="text-sm font-medium text-foreground flex items-center justify-between">
|
|
318
|
+
Light logo
|
|
319
|
+
<edge-shad-button
|
|
320
|
+
type="button"
|
|
321
|
+
variant="link"
|
|
322
|
+
class="px-0 h-auto text-sm"
|
|
323
|
+
@click="state.logoLightPickerOpen = !state.logoLightPickerOpen"
|
|
324
|
+
>
|
|
325
|
+
{{ state.logoLightPickerOpen ? 'Hide picker' : 'Select logo' }}
|
|
326
|
+
</edge-shad-button>
|
|
327
|
+
</label>
|
|
328
|
+
<div class="flex items-center gap-4">
|
|
329
|
+
<div v-if="props.settings.logoLight" class="flex items-center gap-3">
|
|
330
|
+
<img
|
|
331
|
+
:src="props.settings.logoLight"
|
|
332
|
+
alt="Light logo preview"
|
|
333
|
+
:class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.logoLight)]"
|
|
334
|
+
>
|
|
335
|
+
<edge-shad-button
|
|
336
|
+
type="button"
|
|
337
|
+
variant="ghost"
|
|
338
|
+
class="h-8"
|
|
339
|
+
@click="props.settings.logoLight = ''"
|
|
340
|
+
>
|
|
341
|
+
Remove
|
|
342
|
+
</edge-shad-button>
|
|
343
|
+
</div>
|
|
344
|
+
<span v-else class="text-sm text-muted-foreground italic">No light logo selected</span>
|
|
345
|
+
</div>
|
|
346
|
+
<div v-if="state.logoLightPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
|
|
347
|
+
<edge-cms-media-manager
|
|
348
|
+
:site="props.siteId"
|
|
349
|
+
:select-mode="true"
|
|
350
|
+
:default-tags="['Logos']"
|
|
351
|
+
@select="(url) => {
|
|
352
|
+
props.settings.logoLight = url
|
|
353
|
+
state.logoLightPickerOpen = false
|
|
354
|
+
}"
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<edge-shad-input
|
|
359
|
+
v-else
|
|
360
|
+
v-model="props.settings.logoLight"
|
|
361
|
+
name="logoLight"
|
|
362
|
+
label="Light logo URL"
|
|
363
|
+
placeholder="https://..."
|
|
364
|
+
class="w-full"
|
|
365
|
+
/>
|
|
366
|
+
<div v-if="props.isAdmin" class="space-y-4 border border-dashed rounded-lg p-4">
|
|
367
|
+
<div class="text-sm font-semibold text-foreground">
|
|
368
|
+
Umbrella Brand
|
|
369
|
+
</div>
|
|
370
|
+
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
371
|
+
<label class="text-sm font-medium text-foreground flex items-center justify-between">
|
|
372
|
+
Dark brand logo
|
|
373
|
+
<edge-shad-button
|
|
374
|
+
type="button"
|
|
375
|
+
variant="link"
|
|
376
|
+
class="px-0 h-auto text-sm"
|
|
377
|
+
@click="state.brandLogoDarkPickerOpen = !state.brandLogoDarkPickerOpen"
|
|
378
|
+
>
|
|
379
|
+
{{ state.brandLogoDarkPickerOpen ? 'Hide picker' : 'Select logo' }}
|
|
380
|
+
</edge-shad-button>
|
|
381
|
+
</label>
|
|
382
|
+
<div class="flex items-center gap-4">
|
|
383
|
+
<div v-if="props.settings.brandLogoDark" class="flex items-center gap-3">
|
|
384
|
+
<img
|
|
385
|
+
:src="props.settings.brandLogoDark"
|
|
386
|
+
alt="Brand dark logo preview"
|
|
387
|
+
:class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoDark)]"
|
|
388
|
+
>
|
|
389
|
+
<edge-shad-button
|
|
390
|
+
type="button"
|
|
391
|
+
variant="ghost"
|
|
392
|
+
class="h-8"
|
|
393
|
+
@click="props.settings.brandLogoDark = ''"
|
|
394
|
+
>
|
|
395
|
+
Remove
|
|
396
|
+
</edge-shad-button>
|
|
397
|
+
</div>
|
|
398
|
+
<span v-else class="text-sm text-muted-foreground italic">No brand dark logo selected</span>
|
|
399
|
+
</div>
|
|
400
|
+
<div v-if="state.brandLogoDarkPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
|
|
401
|
+
<edge-cms-media-manager
|
|
402
|
+
:site="props.siteId"
|
|
403
|
+
:select-mode="true"
|
|
404
|
+
:default-tags="['Logos']"
|
|
405
|
+
@select="(url) => {
|
|
406
|
+
props.settings.brandLogoDark = url
|
|
407
|
+
state.brandLogoDarkPickerOpen = false
|
|
408
|
+
}"
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
<edge-shad-input
|
|
413
|
+
v-else
|
|
414
|
+
v-model="props.settings.brandLogoDark"
|
|
415
|
+
name="brandLogoDark"
|
|
416
|
+
label="Dark brand logo URL"
|
|
417
|
+
placeholder="https://..."
|
|
418
|
+
class="w-full"
|
|
419
|
+
/>
|
|
420
|
+
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
421
|
+
<label class="text-sm font-medium text-foreground flex items-center justify-between">
|
|
422
|
+
Light brand logo
|
|
423
|
+
<edge-shad-button
|
|
424
|
+
type="button"
|
|
425
|
+
variant="link"
|
|
426
|
+
class="px-0 h-auto text-sm"
|
|
427
|
+
@click="state.brandLogoLightPickerOpen = !state.brandLogoLightPickerOpen"
|
|
428
|
+
>
|
|
429
|
+
{{ state.brandLogoLightPickerOpen ? 'Hide picker' : 'Select logo' }}
|
|
430
|
+
</edge-shad-button>
|
|
431
|
+
</label>
|
|
432
|
+
<div class="flex items-center gap-4">
|
|
433
|
+
<div v-if="props.settings.brandLogoLight" class="flex items-center gap-3">
|
|
434
|
+
<img
|
|
435
|
+
:src="props.settings.brandLogoLight"
|
|
436
|
+
alt="Brand light logo preview"
|
|
437
|
+
:class="['max-h-16 max-w-[220px] h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.brandLogoLight)]"
|
|
438
|
+
>
|
|
439
|
+
<edge-shad-button
|
|
440
|
+
type="button"
|
|
441
|
+
variant="ghost"
|
|
442
|
+
class="h-8"
|
|
443
|
+
@click="props.settings.brandLogoLight = ''"
|
|
444
|
+
>
|
|
445
|
+
Remove
|
|
446
|
+
</edge-shad-button>
|
|
447
|
+
</div>
|
|
448
|
+
<span v-else class="text-sm text-muted-foreground italic">No brand light logo selected</span>
|
|
449
|
+
</div>
|
|
450
|
+
<div v-if="state.brandLogoLightPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
|
|
451
|
+
<edge-cms-media-manager
|
|
452
|
+
:site="props.siteId"
|
|
453
|
+
:select-mode="true"
|
|
454
|
+
:default-tags="['Logos']"
|
|
455
|
+
@select="(url) => {
|
|
456
|
+
props.settings.brandLogoLight = url
|
|
457
|
+
state.brandLogoLightPickerOpen = false
|
|
458
|
+
}"
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
<edge-shad-input
|
|
463
|
+
v-else
|
|
464
|
+
v-model="props.settings.brandLogoLight"
|
|
465
|
+
name="brandLogoLight"
|
|
466
|
+
label="Light brand logo URL"
|
|
467
|
+
placeholder="https://..."
|
|
468
|
+
class="w-full"
|
|
469
|
+
/>
|
|
470
|
+
</div>
|
|
471
|
+
<div v-if="props.enableMediaPicker && props.siteId" class="space-y-2">
|
|
472
|
+
<label class="text-sm font-medium text-foreground flex items-center justify-between">
|
|
473
|
+
Favicon
|
|
474
|
+
<edge-shad-button
|
|
475
|
+
type="button"
|
|
476
|
+
variant="link"
|
|
477
|
+
class="px-0 h-auto text-sm"
|
|
478
|
+
@click="state.faviconPickerOpen = !state.faviconPickerOpen"
|
|
479
|
+
>
|
|
480
|
+
{{ state.faviconPickerOpen ? 'Hide picker' : 'Select favicon' }}
|
|
481
|
+
</edge-shad-button>
|
|
482
|
+
</label>
|
|
483
|
+
<div class="flex items-center gap-4">
|
|
484
|
+
<div v-if="props.settings.favicon" class="flex items-center gap-3">
|
|
485
|
+
<img
|
|
486
|
+
:src="props.settings.favicon"
|
|
487
|
+
alt="Favicon preview"
|
|
488
|
+
:class="['max-h-12 max-w-12 h-auto w-auto rounded-md border border-border object-contain', previewBackgroundClass(props.settings.favicon)]"
|
|
489
|
+
>
|
|
490
|
+
<edge-shad-button
|
|
491
|
+
type="button"
|
|
492
|
+
variant="ghost"
|
|
493
|
+
class="h-8"
|
|
494
|
+
@click="props.settings.favicon = ''"
|
|
495
|
+
>
|
|
496
|
+
Remove
|
|
497
|
+
</edge-shad-button>
|
|
498
|
+
</div>
|
|
499
|
+
<span v-else class="text-sm text-muted-foreground italic">No favicon selected</span>
|
|
500
|
+
</div>
|
|
501
|
+
<div v-if="state.faviconPickerOpen" class="mt-2 border border-dashed rounded-lg p-2">
|
|
502
|
+
<edge-cms-media-manager
|
|
503
|
+
:site="props.siteId"
|
|
504
|
+
:select-mode="true"
|
|
505
|
+
:default-tags="['Logos']"
|
|
506
|
+
@select="(url) => {
|
|
507
|
+
props.settings.favicon = url
|
|
508
|
+
state.faviconPickerOpen = false
|
|
509
|
+
}"
|
|
510
|
+
/>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
<edge-shad-input
|
|
514
|
+
v-else
|
|
515
|
+
v-model="props.settings.favicon"
|
|
516
|
+
name="favicon"
|
|
517
|
+
label="Favicon URL"
|
|
518
|
+
placeholder="https://..."
|
|
519
|
+
class="w-full"
|
|
520
|
+
/>
|
|
521
|
+
</TabsContent>
|
|
522
|
+
<TabsContent value="seo" class="pt-4">
|
|
523
|
+
<div class="space-y-4">
|
|
524
|
+
<p class="text-sm text-muted-foreground">
|
|
525
|
+
Default settings if the information is not entered on the page.
|
|
526
|
+
</p>
|
|
527
|
+
<edge-shad-input
|
|
528
|
+
v-model="props.settings.metaTitle"
|
|
529
|
+
label="Meta Title"
|
|
530
|
+
name="metaTitle"
|
|
531
|
+
/>
|
|
532
|
+
<edge-shad-textarea
|
|
533
|
+
v-model="props.settings.metaDescription"
|
|
534
|
+
label="Meta Description"
|
|
535
|
+
name="metaDescription"
|
|
536
|
+
/>
|
|
537
|
+
<div class="rounded-md border border-border/60 bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
|
|
538
|
+
CMS tokens in double curly braces are replaced on the front end.
|
|
539
|
+
Example: <span v-pre class="font-semibold text-foreground">"{{cms-site}}"</span> for the site URL,
|
|
540
|
+
<span v-pre class="font-semibold text-foreground">"{{cms-url}}"</span> for the page URL, and
|
|
541
|
+
<span v-pre class="font-semibold text-foreground">"{{cms-logo}}"</span> for the logo URL. Keep the tokens intact.
|
|
542
|
+
</div>
|
|
543
|
+
<edge-cms-code-editor
|
|
544
|
+
v-model="props.settings.structuredData"
|
|
545
|
+
title="Structured Data (JSON-LD)"
|
|
546
|
+
language="json"
|
|
547
|
+
name="structuredData"
|
|
548
|
+
validate-json
|
|
549
|
+
height="300px"
|
|
550
|
+
class="mb-4 w-full"
|
|
551
|
+
/>
|
|
552
|
+
</div>
|
|
553
|
+
</TabsContent>
|
|
554
|
+
<TabsContent value="tracking" class="pt-4">
|
|
555
|
+
<div class="space-y-4">
|
|
556
|
+
<p class="text-sm text-muted-foreground">
|
|
557
|
+
Add tracking IDs for this site.
|
|
558
|
+
</p>
|
|
559
|
+
<edge-shad-input
|
|
560
|
+
v-model="props.settings.trackingFacebookPixel"
|
|
561
|
+
label="Facebook Pixel ID"
|
|
562
|
+
name="trackingFacebookPixel"
|
|
563
|
+
placeholder="123456789012345"
|
|
564
|
+
/>
|
|
565
|
+
<edge-shad-input
|
|
566
|
+
v-model="props.settings.trackingGoogleAnalytics"
|
|
567
|
+
label="Google Analytics ID"
|
|
568
|
+
name="trackingGoogleAnalytics"
|
|
569
|
+
placeholder="G-XXXXXXX"
|
|
570
|
+
/>
|
|
571
|
+
<edge-shad-input
|
|
572
|
+
v-model="props.settings.trackingAdroll"
|
|
573
|
+
label="AdRoll ID"
|
|
574
|
+
name="trackingAdroll"
|
|
575
|
+
placeholder="ADROLL-ID"
|
|
576
|
+
/>
|
|
577
|
+
</div>
|
|
578
|
+
</TabsContent>
|
|
579
|
+
<TabsContent value="social" class="pt-4">
|
|
580
|
+
<div class="space-y-4">
|
|
581
|
+
<p class="text-sm text-muted-foreground">
|
|
582
|
+
Add social media links for this site.
|
|
583
|
+
</p>
|
|
584
|
+
<edge-shad-input
|
|
585
|
+
v-model="props.settings.socialFacebook"
|
|
586
|
+
label="Facebook URL"
|
|
587
|
+
name="socialFacebook"
|
|
588
|
+
placeholder="https://facebook.com/yourpage"
|
|
589
|
+
/>
|
|
590
|
+
<edge-shad-input
|
|
591
|
+
v-model="props.settings.socialInstagram"
|
|
592
|
+
label="Instagram URL"
|
|
593
|
+
name="socialInstagram"
|
|
594
|
+
placeholder="https://instagram.com/yourhandle"
|
|
595
|
+
/>
|
|
596
|
+
<edge-shad-input
|
|
597
|
+
v-model="props.settings.socialTwitter"
|
|
598
|
+
label="X (Twitter) URL"
|
|
599
|
+
name="socialTwitter"
|
|
600
|
+
placeholder="https://x.com/yourhandle"
|
|
601
|
+
/>
|
|
602
|
+
<edge-shad-input
|
|
603
|
+
v-model="props.settings.socialLinkedIn"
|
|
604
|
+
label="LinkedIn URL"
|
|
605
|
+
name="socialLinkedIn"
|
|
606
|
+
placeholder="https://linkedin.com/company/yourcompany"
|
|
607
|
+
/>
|
|
608
|
+
<edge-shad-input
|
|
609
|
+
v-model="props.settings.socialYouTube"
|
|
610
|
+
label="YouTube URL"
|
|
611
|
+
name="socialYouTube"
|
|
612
|
+
placeholder="https://youtube.com/@yourchannel"
|
|
613
|
+
/>
|
|
614
|
+
<edge-shad-input
|
|
615
|
+
v-model="props.settings.socialTikTok"
|
|
616
|
+
label="TikTok URL"
|
|
617
|
+
name="socialTikTok"
|
|
618
|
+
placeholder="https://tiktok.com/@yourhandle"
|
|
619
|
+
/>
|
|
620
|
+
</div>
|
|
621
|
+
</TabsContent>
|
|
622
|
+
</Tabs>
|
|
623
|
+
</template>
|