@edgedev/create-edge-app 1.2.33 → 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 +136 -0
- package/edge/components/cms/block.vue +977 -305
- package/edge/components/cms/blockApi.vue +3 -3
- package/edge/components/cms/blockEditor.vue +688 -86
- package/edge/components/cms/blockPicker.vue +31 -5
- package/edge/components/cms/blockRender.vue +3 -3
- package/edge/components/cms/blocksManager.vue +790 -82
- package/edge/components/cms/codeEditor.vue +15 -6
- package/edge/components/cms/fontUpload.vue +318 -2
- package/edge/components/cms/htmlContent.vue +825 -93
- 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 +96 -34
- package/edge/components/cms/page.vue +902 -58
- package/edge/components/cms/posts.vue +13 -4
- package/edge/components/cms/site.vue +638 -87
- package/edge/components/cms/siteSettingsForm.vue +19 -9
- package/edge/components/cms/sitesManager.vue +5 -4
- package/edge/components/cms/themeDefaultMenu.vue +20 -2
- package/edge/components/cms/themeEditor.vue +196 -162
- package/edge/components/editor.vue +5 -1
- package/edge/composables/global.ts +37 -5
- package/edge/composables/siteSettingsTemplate.js +2 -0
- package/edge/composables/useCmsNewDocs.js +100 -0
- package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
- package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
- package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
- package/edge/routes/cms/dashboard/media/index.vue +5 -0
- package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
- package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
- package/edge/routes/cms/dashboard/sites/index.vue +4 -0
- package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
- package/edge/routes/cms/dashboard/templates/index.vue +4 -0
- package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
- package/edge/routes/cms/dashboard/themes/index.vue +330 -1
- 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/firebase.json +4 -0
- package/nuxt.config.ts +1 -1
- package/package.json +2 -2
|
@@ -2,19 +2,7 @@
|
|
|
2
2
|
class="relative cms-block cms-block-contact-form-placeholder rounded-2xl border border-dashed border-slate-300 bg-slate-50/70 px-4 py-6 sm:px-6 sm:py-8"
|
|
3
3
|
data-block-type="contact-form-placeholder"
|
|
4
4
|
>
|
|
5
|
-
<!-- OVERLAY BADGE -->
|
|
6
|
-
<div class="pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center pt-3">
|
|
7
|
-
<div
|
|
8
|
-
class="rounded-full border border-slate-300 bg-white/95 px-4 py-1 text-[11px] font-semibold uppercase tracking-wide text-slate-600 shadow-sm"
|
|
9
|
-
>
|
|
10
|
-
Contact Form Placeholder
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
5
|
<div class="mx-auto max-w-3xl pt-6">
|
|
15
|
-
<!-- Hidden config field: emailTo -->
|
|
16
|
-
|
|
17
|
-
<!-- Header -->
|
|
18
6
|
<div class="mb-6 space-y-2 text-center sm:text-left">
|
|
19
7
|
<h2 class="text-xl font-semibold text-slate-900">
|
|
20
8
|
{{{#text {"field":"formHeader","title":"Form Header","value":"Contact Us"}}}}
|
|
@@ -24,46 +12,66 @@
|
|
|
24
12
|
</p>
|
|
25
13
|
</div>
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
<form
|
|
16
|
+
class="cms-form space-y-4"
|
|
17
|
+
data-cms-form
|
|
18
|
+
data-cms-required-message="Please complete all required fields."
|
|
19
|
+
data-cms-success-message="Thanks! Your message has been sent."
|
|
20
|
+
data-cms-error-message="Sorry, we could not send your message. Please try again."
|
|
21
|
+
data-cms-success-class="cms-form-message cms-form-message-success"
|
|
22
|
+
data-cms-error-class="cms-form-message cms-form-message-error"
|
|
23
|
+
data-cms-invalid-class="cms-form-field-invalid"
|
|
24
|
+
data-cms-working-class="cms-form-submitting"
|
|
25
|
+
>
|
|
26
|
+
<!-- Honeypot (optional, used by helper if present) -->
|
|
27
|
+
<div class="pointer-events-none absolute -left-[9999px] top-auto h-px w-px overflow-hidden opacity-0" aria-hidden="true">
|
|
28
|
+
<label for="cms-company">Company</label>
|
|
29
|
+
<input id="cms-company" name="company" type="text" tabindex="-1" autocomplete="off" />
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="space-y-4">
|
|
33
|
+
{{{#array {"field":"formFields","schema":[{"field":"fieldName","type":"text","title":"Field Label"},{"field":"fieldType","type":"option","title":"Field Type","option":{"optionsKey":"title","optionsValue":"value","options":[{"title":"Text","value":"text"},{"title":"Email","value":"email"},{"title":"Phone","value":"tel"},{"title":"Textarea","value":"textarea"}]},"value":"text"},{"field":"fieldRequired","type":"option","title":"Required","option":{"optionsKey":"title","optionsValue":"value","options":[{"title":"Yes","value":"true"},{"title":"No","value":"false"}]},"value":"true"}],"value":[{"fieldName":"Name","fieldType":"text","fieldRequired":"true"},{"fieldName":"Email","fieldType":"email","fieldRequired":"true"},{"fieldName":"Message","fieldType":"textarea","fieldRequired":"true"}]}}}}
|
|
34
|
+
<div class="space-y-1">
|
|
35
|
+
<label class="text-xs font-medium uppercase tracking-wide text-slate-600">
|
|
36
|
+
{{item.fieldName}}
|
|
37
|
+
</label>
|
|
38
|
+
|
|
34
39
|
{{{#if {"cond":"item.fieldType == 'textarea'"}}}}
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
<textarea
|
|
41
|
+
class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900"
|
|
42
|
+
data-cms-required="{{item.fieldRequired}}"
|
|
43
|
+
name="{{item.fieldName}}"
|
|
44
|
+
placeholder="{{item.fieldName}}"
|
|
45
|
+
rows="6"
|
|
46
|
+
></textarea>
|
|
39
47
|
{{{#else}}}
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
<input
|
|
49
|
+
class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900"
|
|
50
|
+
data-cms-required="{{item.fieldRequired}}"
|
|
51
|
+
type="{{item.fieldType}}"
|
|
52
|
+
name="{{item.fieldName}}"
|
|
53
|
+
placeholder="{{item.fieldName}}"
|
|
54
|
+
/>
|
|
44
55
|
{{{/if}}}
|
|
45
|
-
</
|
|
56
|
+
</div>
|
|
57
|
+
{{{/array}}}
|
|
58
|
+
</div>
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
<div class="mt-6">
|
|
61
|
+
<button
|
|
62
|
+
type="submit"
|
|
63
|
+
class="cms-form-submit inline-flex w-full items-center justify-center rounded-lg bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white shadow-sm disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
|
64
|
+
data-cms-form-submit
|
|
65
|
+
>
|
|
66
|
+
{{{#text {"field":"buttonText","title":"Button Text","value":"Send Message"}}}}
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
class="inline-flex w-full items-center justify-center rounded-lg bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white shadow-sm disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
|
58
|
-
disabled
|
|
59
|
-
>
|
|
60
|
-
{{{#text {"field":"buttonText","title":"Button Text","value":"Send Message"}}}}
|
|
61
|
-
</button>
|
|
62
|
-
</div>
|
|
70
|
+
<p class="cms-form-message hidden text-sm" data-cms-form-message></p>
|
|
71
|
+
</form>
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</p>
|
|
73
|
+
<div class="hidden">
|
|
74
|
+
{{{#text {"field":"emailTo","title":"Email To","value":"test@testing.com"}}}}
|
|
75
|
+
</div>
|
|
68
76
|
</div>
|
|
69
77
|
</section>
|
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
class="relative cms-block cms-block-newsletter-placeholder rounded-2xl border border-dashed border-slate-300 bg-slate-50/70 px-4 py-6 sm:px-6 sm:py-8"
|
|
3
3
|
data-block-type="newsletter-placeholder"
|
|
4
4
|
>
|
|
5
|
-
<!-- OVERLAY BADGE -->
|
|
6
|
-
<div
|
|
7
|
-
class="pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center pt-3"
|
|
8
|
-
>
|
|
9
|
-
<div
|
|
10
|
-
class="rounded-full border border-slate-300 bg-white/95 px-4 py-1 text-[11px] font-semibold uppercase tracking-wide text-slate-600 shadow-sm"
|
|
11
|
-
>
|
|
12
|
-
Newsletter Placeholder
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
5
|
<div class="mx-auto max-w-2xl pt-6">
|
|
17
|
-
<!-- Hidden config field: emailTo -->
|
|
18
|
-
|
|
19
6
|
<!-- Header -->
|
|
20
7
|
<div class="mb-6 space-y-2 text-center sm:text-left">
|
|
21
8
|
<h2 class="text-xl font-semibold text-slate-900">
|
|
@@ -26,92 +13,65 @@
|
|
|
26
13
|
</p>
|
|
27
14
|
</div>
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"value":"tel"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"title":"Textarea",
|
|
62
|
-
"value":"textarea"
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
},
|
|
66
|
-
"value":"text"
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
"value":[
|
|
70
|
-
{
|
|
71
|
-
"fieldName":"Name",
|
|
72
|
-
"fieldType":"text"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"fieldName":"Email",
|
|
76
|
-
"fieldType":"email"
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
}}}}
|
|
80
|
-
<div class="space-y-1">
|
|
81
|
-
<!-- Label preview -->
|
|
82
|
-
<p class="text-xs font-medium uppercase tracking-wide text-slate-600">
|
|
83
|
-
{{item.fieldName}}
|
|
16
|
+
<form
|
|
17
|
+
class="cms-form space-y-4"
|
|
18
|
+
data-cms-form
|
|
19
|
+
data-cms-required-message="Please complete all required fields."
|
|
20
|
+
data-cms-success-message="Thanks! You’re subscribed."
|
|
21
|
+
data-cms-error-message="Sorry, we could not submit this right now."
|
|
22
|
+
data-cms-success-class="cms-form-message cms-form-message-success"
|
|
23
|
+
data-cms-error-class="cms-form-message cms-form-message-error"
|
|
24
|
+
data-cms-invalid-class="cms-form-field-invalid"
|
|
25
|
+
data-cms-working-class="cms-form-submitting"
|
|
26
|
+
>
|
|
27
|
+
<div class="space-y-4">
|
|
28
|
+
{{{#array {
|
|
29
|
+
"field":"newsletterFields",
|
|
30
|
+
"title":"Form Fields",
|
|
31
|
+
"schema":[
|
|
32
|
+
{"field":"fieldName","type":"text","title":"Field Label"},
|
|
33
|
+
{"field":"fieldType","type":"option","title":"Field Type","option":{"optionsKey":"title","optionsValue":"value","options":[{"title":"Text","value":"text"},{"title":"Email","value":"email"},{"title":"Phone","value":"tel"},{"title":"Textarea","value":"textarea"}]},"value":"text"}
|
|
34
|
+
],
|
|
35
|
+
"value":[
|
|
36
|
+
{"fieldName":"Name","fieldType":"text"},
|
|
37
|
+
{"fieldName":"Email","fieldType":"email"}
|
|
38
|
+
]
|
|
39
|
+
}}}}
|
|
40
|
+
<div class="space-y-1 cms-form-required" data-cms-required="true">
|
|
41
|
+
<label class="block text-xs font-medium uppercase tracking-wide text-slate-600">
|
|
42
|
+
{{item.fieldName}}
|
|
43
|
+
</label>
|
|
44
|
+
|
|
84
45
|
{{{#if {"cond":"item.fieldType == 'textarea'"}}}}
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
46
|
+
<textarea
|
|
47
|
+
name="{{item.fieldName}}"
|
|
48
|
+
placeholder="{{item.fieldName}}"
|
|
49
|
+
rows="5"
|
|
50
|
+
class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-brand/30 focus:border-brand"
|
|
51
|
+
></textarea>
|
|
89
52
|
{{{#else}}}
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
53
|
+
<input
|
|
54
|
+
type="{{item.fieldType}}"
|
|
55
|
+
name="{{item.fieldName}}"
|
|
56
|
+
placeholder="{{item.fieldName}}"
|
|
57
|
+
class="h-10 w-full rounded-lg border border-slate-300 bg-white px-3 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-brand/30 focus:border-brand"
|
|
58
|
+
/>
|
|
94
59
|
{{{/if}}}
|
|
95
|
-
</
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</div>
|
|
60
|
+
</div>
|
|
61
|
+
{{{/array}}}
|
|
62
|
+
</div>
|
|
99
63
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
</
|
|
109
|
-
</div>
|
|
64
|
+
<div class="mt-6">
|
|
65
|
+
<button
|
|
66
|
+
type="submit"
|
|
67
|
+
data-cms-form-submit
|
|
68
|
+
class="cms-form-submit inline-flex w-full items-center justify-center rounded-lg bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white shadow-sm disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
|
69
|
+
>
|
|
70
|
+
{{{#text {"field":"buttonText","title":"Button Text","value":"Subscribe"}}}}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
110
73
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
The CMS uses <code>emailTo</code> and <code>newsletterFields</code> (array of fieldName + fieldType)
|
|
114
|
-
to build the real newsletter signup form on the live site.
|
|
115
|
-
</p>
|
|
74
|
+
<p class="cms-form-message hidden text-sm" data-cms-form-message></p>
|
|
75
|
+
</form>
|
|
116
76
|
</div>
|
|
117
|
-
</section>
|
|
77
|
+
</section>
|
|
@@ -60,6 +60,53 @@ const isDeleteDisabled = entry => isPageEntry(entry) && !!entry?.disableDelete
|
|
|
60
60
|
const isLinkUrlSpecial = url => /^tel:|^mailto:/i.test(String(url || '').trim())
|
|
61
61
|
const linkTarget = url => (isLinkUrlSpecial(url) ? null : '_blank')
|
|
62
62
|
const linkRel = url => (isLinkUrlSpecial(url) ? null : 'noopener noreferrer')
|
|
63
|
+
const titleFromSlug = (slug) => {
|
|
64
|
+
if (!slug)
|
|
65
|
+
return ''
|
|
66
|
+
return slug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
|
67
|
+
}
|
|
68
|
+
const displayEntryName = (entry) => {
|
|
69
|
+
if (!entry)
|
|
70
|
+
return ''
|
|
71
|
+
if (entry?.name === 'Deleting...')
|
|
72
|
+
return 'Deleting...'
|
|
73
|
+
if (isExternalLinkEntry(entry))
|
|
74
|
+
return String(entry?.name || '').trim()
|
|
75
|
+
const menuTitle = String(entry?.menuTitle || '').trim()
|
|
76
|
+
if (menuTitle)
|
|
77
|
+
return menuTitle
|
|
78
|
+
const slug = String(entry?.name || '').trim()
|
|
79
|
+
if (!slug)
|
|
80
|
+
return ''
|
|
81
|
+
return slug
|
|
82
|
+
}
|
|
83
|
+
const folderEntryForMenu = (menuName) => {
|
|
84
|
+
if (!props.prevMenu || !Number.isInteger(props.prevIndex) || props.prevIndex < 0)
|
|
85
|
+
return null
|
|
86
|
+
const parentList = props.prevModelValue?.[props.prevMenu]
|
|
87
|
+
if (!Array.isArray(parentList))
|
|
88
|
+
return null
|
|
89
|
+
const parentEntry = parentList[props.prevIndex]
|
|
90
|
+
if (!parentEntry || typeof parentEntry !== 'object' || isExternalLinkEntry(parentEntry))
|
|
91
|
+
return null
|
|
92
|
+
if (!parentEntry.item || typeof parentEntry.item !== 'object')
|
|
93
|
+
return null
|
|
94
|
+
const folderSlug = Object.keys(parentEntry.item || {})[0]
|
|
95
|
+
if (!folderSlug || folderSlug !== menuName)
|
|
96
|
+
return null
|
|
97
|
+
return parentEntry
|
|
98
|
+
}
|
|
99
|
+
const displayMenuName = (menuName) => {
|
|
100
|
+
if (menuName === 'Site Root')
|
|
101
|
+
return 'Site Menu'
|
|
102
|
+
const folderEntry = folderEntryForMenu(menuName)
|
|
103
|
+
if (folderEntry) {
|
|
104
|
+
const title = String(folderEntry?.menuTitle || folderEntry?.folderTitle || '').trim()
|
|
105
|
+
if (title)
|
|
106
|
+
return title
|
|
107
|
+
}
|
|
108
|
+
return menuName
|
|
109
|
+
}
|
|
63
110
|
|
|
64
111
|
const normalizeForCompare = (value) => {
|
|
65
112
|
if (Array.isArray(value))
|
|
@@ -466,6 +513,11 @@ const renameFolderOrPageShow = (item) => {
|
|
|
466
513
|
// Work on a copy so edits in the dialog do not mutate the live menu entry.
|
|
467
514
|
state.renameItem = edgeGlobal.dupObject(item || {})
|
|
468
515
|
state.renameItem.previousName = item?.name
|
|
516
|
+
state.renameItem.previousMenuTitle = displayEntryName(item)
|
|
517
|
+
if (state.renameItem.item === '')
|
|
518
|
+
state.renameItem.name = String(item?.menuTitle || item?.folderTitle || item?.name || '').trim()
|
|
519
|
+
if (state.renameItem.item !== '' && !isExternalLinkEntry(state.renameItem))
|
|
520
|
+
state.renameItem.name = state.renameItem.previousMenuTitle
|
|
469
521
|
state.renameFolderOrPageDialog = true
|
|
470
522
|
}
|
|
471
523
|
|
|
@@ -558,10 +610,11 @@ const slugGenerator = (name, excludeName = '') => {
|
|
|
558
610
|
console.log('Existing slugs:', existing)
|
|
559
611
|
|
|
560
612
|
const base = name ? name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, '') : ''
|
|
561
|
-
|
|
613
|
+
const baseSlug = base || 'page'
|
|
614
|
+
let unique = baseSlug
|
|
562
615
|
let suffix = 1
|
|
563
616
|
while (existing.has(unique)) {
|
|
564
|
-
unique = `${
|
|
617
|
+
unique = `${baseSlug}-${suffix}`
|
|
565
618
|
suffix += 1
|
|
566
619
|
}
|
|
567
620
|
return unique
|
|
@@ -686,20 +739,21 @@ const renameFolderOrPageAction = async () => {
|
|
|
686
739
|
state.renameItem = {}
|
|
687
740
|
return
|
|
688
741
|
}
|
|
689
|
-
const newSlug = slugGenerator(state.renameItem.name, state.renameItem.previousName || '')
|
|
690
|
-
|
|
691
|
-
if (state.renameItem.name === state.renameItem.previousName) {
|
|
692
|
-
state.renameFolderOrPageDialog = false
|
|
693
|
-
state.renameItem = {}
|
|
694
|
-
return
|
|
695
|
-
}
|
|
696
|
-
|
|
697
742
|
// If the item is an empty string, we are renaming a top-level folder (handled here)
|
|
698
743
|
if (state.renameItem.item === '') {
|
|
699
|
-
const
|
|
744
|
+
const nextFolderTitle = String(state.renameItem.name || '').trim()
|
|
745
|
+
const newSlug = slugGenerator(nextFolderTitle, state.renameItem.previousName || '')
|
|
746
|
+
const folderEntry = folderEntryForMenu(state.renameItem.previousName || '')
|
|
747
|
+
const previousFolderTitle = String(folderEntry?.menuTitle || folderEntry?.folderTitle || '').trim()
|
|
748
|
+
const resolvedFolderTitle = nextFolderTitle || titleFromSlug(newSlug)
|
|
749
|
+
if (newSlug === state.renameItem.previousName && resolvedFolderTitle === previousFolderTitle) {
|
|
750
|
+
state.renameFolderOrPageDialog = false
|
|
751
|
+
state.renameItem = {}
|
|
752
|
+
return
|
|
753
|
+
}
|
|
700
754
|
const originalItem = edgeGlobal.dupObject(modelValue.value[state.renameItem.previousName])
|
|
701
755
|
// Renaming a folder: if the new name is empty, abort and reset dialog state
|
|
702
|
-
if (!
|
|
756
|
+
if (!nextFolderTitle) {
|
|
703
757
|
state.renameFolderOrPageDialog = false
|
|
704
758
|
state.renameItem = {}
|
|
705
759
|
return
|
|
@@ -708,6 +762,11 @@ const renameFolderOrPageAction = async () => {
|
|
|
708
762
|
modelValue.value[newSlug] = originalItem
|
|
709
763
|
console.log('updated modelValue:', modelValue.value)
|
|
710
764
|
delete modelValue.value[state.renameItem.previousName]
|
|
765
|
+
if (folderEntry) {
|
|
766
|
+
folderEntry.menuTitle = resolvedFolderTitle
|
|
767
|
+
if (Object.prototype.hasOwnProperty.call(folderEntry, 'folderTitle'))
|
|
768
|
+
delete folderEntry.folderTitle
|
|
769
|
+
}
|
|
711
770
|
state.renameFolderOrPageDialog = false
|
|
712
771
|
state.renameItem = {}
|
|
713
772
|
return
|
|
@@ -716,7 +775,14 @@ const renameFolderOrPageAction = async () => {
|
|
|
716
775
|
// Renaming a page: the page is uniquely identified by its docId in `state.renameItem.item`.
|
|
717
776
|
// Traverse all menus and submenus; update the `name` where the `item` matches that docId (strings only).
|
|
718
777
|
const targetDocId = state.renameItem.item
|
|
719
|
-
|
|
778
|
+
const nextMenuTitle = String(state.renameItem.name || '').trim()
|
|
779
|
+
const previousMenuTitle = String(state.renameItem.previousMenuTitle || '').trim()
|
|
780
|
+
const newSlug = slugGenerator(nextMenuTitle, state.renameItem.previousName || '')
|
|
781
|
+
if (nextMenuTitle === previousMenuTitle && newSlug === state.renameItem.previousName) {
|
|
782
|
+
state.renameFolderOrPageDialog = false
|
|
783
|
+
state.renameItem = {}
|
|
784
|
+
return
|
|
785
|
+
}
|
|
720
786
|
|
|
721
787
|
let renamed = false
|
|
722
788
|
for (const [menuName, items] of Object.entries(modelValue.value)) {
|
|
@@ -725,6 +791,7 @@ const renameFolderOrPageAction = async () => {
|
|
|
725
791
|
const results = await edgeFirebase.changeDoc(`${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}/pages`, targetDocId, { name: newSlug })
|
|
726
792
|
if (results.success) {
|
|
727
793
|
item.name = newSlug
|
|
794
|
+
item.menuTitle = nextMenuTitle || titleFromSlug(newSlug)
|
|
728
795
|
renamed = true
|
|
729
796
|
}
|
|
730
797
|
break
|
|
@@ -755,7 +822,7 @@ const addPageAction = async () => {
|
|
|
755
822
|
modelValue.value[state.menuName] = []
|
|
756
823
|
|
|
757
824
|
if (state.addMenu) {
|
|
758
|
-
modelValue.value[state.menuName].push({ item: { [slug]: [] } })
|
|
825
|
+
modelValue.value[state.menuName].push({ menuTitle: state.newPageName, item: { [slug]: [] } })
|
|
759
826
|
}
|
|
760
827
|
else {
|
|
761
828
|
const templateDoc = getTemplateDoc(state.selectedTemplateId)
|
|
@@ -766,7 +833,7 @@ const addPageAction = async () => {
|
|
|
766
833
|
const targetMenu = modelValue.value[state.menuName]
|
|
767
834
|
const alreadyExists = Array.isArray(targetMenu) && targetMenu.some(entry => entry?.item === docId)
|
|
768
835
|
if (!alreadyExists)
|
|
769
|
-
targetMenu.push({ name: slug, item: docId })
|
|
836
|
+
targetMenu.push({ name: slug, menuTitle: state.newPageName, item: docId })
|
|
770
837
|
}
|
|
771
838
|
}
|
|
772
839
|
|
|
@@ -936,12 +1003,6 @@ const onSubmit = () => {
|
|
|
936
1003
|
state.pageSettings = false
|
|
937
1004
|
}
|
|
938
1005
|
}
|
|
939
|
-
const titleFromSlug = (slug) => {
|
|
940
|
-
if (!slug)
|
|
941
|
-
return ''
|
|
942
|
-
return slug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
|
943
|
-
}
|
|
944
|
-
|
|
945
1006
|
const theme = computed(() => {
|
|
946
1007
|
const theme = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`]?.[props.site]?.theme || ''
|
|
947
1008
|
console.log(`${edgeGlobal.edgeState.organizationDocPath}/sites/${props.site}`)
|
|
@@ -960,13 +1021,13 @@ const theme = computed(() => {
|
|
|
960
1021
|
<SidebarMenuItem v-for="({ menu, name: menuName }) in orderedMenus" :key="menuName">
|
|
961
1022
|
<SidebarMenuButton class="group !px-0 hover:!bg-transparent">
|
|
962
1023
|
<FolderOpen
|
|
963
|
-
class="mr-2 group-hover:text-
|
|
1024
|
+
class="mr-2 group-hover:text-foreground"
|
|
964
1025
|
/>
|
|
965
|
-
<span v-if="!props.isTemplateSite" class="
|
|
1026
|
+
<span v-if="!props.isTemplateSite" class="!text-foreground">{{ displayMenuName(menuName) }}</span>
|
|
966
1027
|
<SidebarGroupAction class="absolute right-2 top-0 hover:!bg-transparent">
|
|
967
1028
|
<DropdownMenu>
|
|
968
1029
|
<DropdownMenuTrigger as-child>
|
|
969
|
-
<SidebarMenuAction>
|
|
1030
|
+
<SidebarMenuAction class="hover:bg-primary text-foreground hover:text-primary-foreground">
|
|
970
1031
|
<PlusIcon />
|
|
971
1032
|
</SidebarMenuAction>
|
|
972
1033
|
</DropdownMenuTrigger>
|
|
@@ -990,7 +1051,7 @@ const theme = computed(() => {
|
|
|
990
1051
|
<FolderPlus />
|
|
991
1052
|
<span>New Folder</span>
|
|
992
1053
|
</DropdownMenuItem>
|
|
993
|
-
<DropdownMenuItem v-if="canRename(menuName)" @click="renameFolderOrPageShow({ name: menuName, item: '' })">
|
|
1054
|
+
<DropdownMenuItem v-if="canRename(menuName)" @click="renameFolderOrPageShow({ name: menuName, menuTitle: displayMenuName(menuName), item: '' })">
|
|
994
1055
|
<FolderPen />
|
|
995
1056
|
<span>Rename Folder</span>
|
|
996
1057
|
</DropdownMenuItem>
|
|
@@ -1029,6 +1090,7 @@ const theme = computed(() => {
|
|
|
1029
1090
|
:class="{ 'text-gray-400': element.item === '' }"
|
|
1030
1091
|
as-child
|
|
1031
1092
|
:is-active="!isExternalLinkEntry(element) && element.item === props.page"
|
|
1093
|
+
class="text-foreground hover:bg-primary hover:text-primary-foreground"
|
|
1032
1094
|
>
|
|
1033
1095
|
<NuxtLink
|
|
1034
1096
|
v-if="!isExternalLinkEntry(element)"
|
|
@@ -1040,7 +1102,7 @@ const theme = computed(() => {
|
|
|
1040
1102
|
<Loader2 v-if="element.item === '' || element.name === 'Deleting...'" :class="{ '!text-red-500': element.name === 'Deleting...' }" class="w-4 h-4 animate-spin" />
|
|
1041
1103
|
<FileWarning v-else-if="isPublishedPageDiff(element.item) && !props.isTemplateSite" class="!text-yellow-600" />
|
|
1042
1104
|
<FileCheck v-else class="text-xs !text-green-700 font-normal" />
|
|
1043
|
-
<span>{{ element
|
|
1105
|
+
<span>{{ displayEntryName(element) }}</span>
|
|
1044
1106
|
</NuxtLink>
|
|
1045
1107
|
<a
|
|
1046
1108
|
v-else
|
|
@@ -1050,22 +1112,22 @@ const theme = computed(() => {
|
|
|
1050
1112
|
:rel="linkRel(element.item?.url)"
|
|
1051
1113
|
>
|
|
1052
1114
|
<Link class="w-4 h-4 text-muted-foreground" />
|
|
1053
|
-
<span>{{ element
|
|
1115
|
+
<span>{{ displayEntryName(element) }}</span>
|
|
1054
1116
|
</a>
|
|
1055
1117
|
</SidebarMenuSubButton>
|
|
1056
1118
|
<div class="absolute right-0 -top-0.5">
|
|
1057
1119
|
<DropdownMenu>
|
|
1058
1120
|
<DropdownMenuTrigger as-child>
|
|
1059
|
-
<SidebarMenuAction>
|
|
1121
|
+
<SidebarMenuAction class="hover:bg-primary text-foreground hover:text-primary-foreground">
|
|
1060
1122
|
<MoreHorizontal />
|
|
1061
1123
|
</SidebarMenuAction>
|
|
1062
1124
|
</DropdownMenuTrigger>
|
|
1063
1125
|
<DropdownMenuContent side="right" align="start">
|
|
1064
1126
|
<DropdownMenuLabel v-if="props.prevMenu" class="flex items-center gap-2">
|
|
1065
|
-
<File class="w-5 h-5" /> {{ ROOT_MENUS.includes(props.prevMenu) ? '' : props.prevMenu }}/{{ menuName }}/{{ element
|
|
1127
|
+
<File class="w-5 h-5" /> {{ ROOT_MENUS.includes(props.prevMenu) ? '' : props.prevMenu }}/{{ menuName }}/{{ displayEntryName(element) }}
|
|
1066
1128
|
</DropdownMenuLabel>
|
|
1067
1129
|
<DropdownMenuLabel v-else class="flex items-center gap-2">
|
|
1068
|
-
<File class="w-5 h-5" /> {{ ROOT_MENUS.includes(menuName) ? '' : menuName }}/{{ element
|
|
1130
|
+
<File class="w-5 h-5" /> {{ ROOT_MENUS.includes(menuName) ? '' : menuName }}/{{ displayEntryName(element) }}
|
|
1069
1131
|
</DropdownMenuLabel>
|
|
1070
1132
|
<DropdownMenuSeparator />
|
|
1071
1133
|
<template v-if="!isExternalLinkEntry(element)">
|
|
@@ -1129,13 +1191,13 @@ const theme = computed(() => {
|
|
|
1129
1191
|
<DialogHeader>
|
|
1130
1192
|
<DialogTitle class="text-left">
|
|
1131
1193
|
<span v-if="state.deletePage.item === ''">Delete Folder "{{ state.deletePage.name }}"</span>
|
|
1132
|
-
<span v-else-if="isExternalLinkEntry(state.deletePage)">Delete Link "{{ state.deletePage
|
|
1133
|
-
<span v-else>Delete Page "{{ state.deletePage
|
|
1194
|
+
<span v-else-if="isExternalLinkEntry(state.deletePage)">Delete Link "{{ displayEntryName(state.deletePage) }}"</span>
|
|
1195
|
+
<span v-else>Delete Page "{{ displayEntryName(state.deletePage) }}"</span>
|
|
1134
1196
|
</DialogTitle>
|
|
1135
1197
|
<DialogDescription />
|
|
1136
1198
|
</DialogHeader>
|
|
1137
1199
|
<div class="text-left px-1">
|
|
1138
|
-
Are you sure you want to delete "{{ state.deletePage.name }}"? This action cannot be undone.
|
|
1200
|
+
Are you sure you want to delete "{{ state.deletePage.item === '' ? state.deletePage.name : displayEntryName(state.deletePage) }}"? This action cannot be undone.
|
|
1139
1201
|
</div>
|
|
1140
1202
|
<DialogFooter class="pt-2 flex justify-between">
|
|
1141
1203
|
<edge-shad-button
|
|
@@ -1337,7 +1399,7 @@ const theme = computed(() => {
|
|
|
1337
1399
|
<Sheet v-model:open="state.pageSettings">
|
|
1338
1400
|
<SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
|
|
1339
1401
|
<SheetHeader>
|
|
1340
|
-
<SheetTitle>{{ state.pageData
|
|
1402
|
+
<SheetTitle>{{ displayEntryName(state.pageData) || 'Site' }}</SheetTitle>
|
|
1341
1403
|
<SheetDescription />
|
|
1342
1404
|
</SheetHeader>
|
|
1343
1405
|
<edge-editor
|