@edgedev/create-edge-app 1.2.34 → 1.2.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/agents.md +95 -2
- package/deploy.sh +60 -1
- package/edge/components/cms/block.vue +763 -301
- package/edge/components/cms/blockEditor.vue +342 -29
- package/edge/components/cms/blockPicker.vue +3 -3
- package/edge/components/cms/blocksManager.vue +48 -13
- package/edge/components/cms/htmlContent.vue +601 -10
- package/edge/components/cms/init_blocks/contact_us.html +55 -47
- package/edge/components/cms/init_blocks/newsletter.html +56 -96
- package/edge/components/cms/menu.vue +92 -31
- package/edge/components/cms/page.vue +165 -50
- package/edge/components/cms/posts.vue +13 -4
- package/edge/components/cms/site.vue +15 -4
- package/edge/components/cms/siteSettingsForm.vue +19 -9
- package/edge/components/cms/themeDefaultMenu.vue +20 -2
- package/edge/composables/siteSettingsTemplate.js +2 -0
- package/edge-pull.sh +16 -2
- package/edge-push.sh +9 -1
- package/edge-remote.sh +20 -0
- package/edge-status.sh +9 -5
- package/edge-update-all.sh +127 -0
- package/package.json +1 -1
|
@@ -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}`)
|
|
@@ -962,7 +1023,7 @@ const theme = computed(() => {
|
|
|
962
1023
|
<FolderOpen
|
|
963
1024
|
class="mr-2 group-hover:text-foreground"
|
|
964
1025
|
/>
|
|
965
|
-
<span v-if="!props.isTemplateSite" class="!text-foreground">{{ menuName
|
|
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>
|
|
@@ -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>
|
|
@@ -1041,7 +1102,7 @@ const theme = computed(() => {
|
|
|
1041
1102
|
<Loader2 v-if="element.item === '' || element.name === 'Deleting...'" :class="{ '!text-red-500': element.name === 'Deleting...' }" class="w-4 h-4 animate-spin" />
|
|
1042
1103
|
<FileWarning v-else-if="isPublishedPageDiff(element.item) && !props.isTemplateSite" class="!text-yellow-600" />
|
|
1043
1104
|
<FileCheck v-else class="text-xs !text-green-700 font-normal" />
|
|
1044
|
-
<span>{{ element
|
|
1105
|
+
<span>{{ displayEntryName(element) }}</span>
|
|
1045
1106
|
</NuxtLink>
|
|
1046
1107
|
<a
|
|
1047
1108
|
v-else
|
|
@@ -1051,7 +1112,7 @@ const theme = computed(() => {
|
|
|
1051
1112
|
:rel="linkRel(element.item?.url)"
|
|
1052
1113
|
>
|
|
1053
1114
|
<Link class="w-4 h-4 text-muted-foreground" />
|
|
1054
|
-
<span>{{ element
|
|
1115
|
+
<span>{{ displayEntryName(element) }}</span>
|
|
1055
1116
|
</a>
|
|
1056
1117
|
</SidebarMenuSubButton>
|
|
1057
1118
|
<div class="absolute right-0 -top-0.5">
|
|
@@ -1063,10 +1124,10 @@ const theme = computed(() => {
|
|
|
1063
1124
|
</DropdownMenuTrigger>
|
|
1064
1125
|
<DropdownMenuContent side="right" align="start">
|
|
1065
1126
|
<DropdownMenuLabel v-if="props.prevMenu" class="flex items-center gap-2">
|
|
1066
|
-
<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) }}
|
|
1067
1128
|
</DropdownMenuLabel>
|
|
1068
1129
|
<DropdownMenuLabel v-else class="flex items-center gap-2">
|
|
1069
|
-
<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) }}
|
|
1070
1131
|
</DropdownMenuLabel>
|
|
1071
1132
|
<DropdownMenuSeparator />
|
|
1072
1133
|
<template v-if="!isExternalLinkEntry(element)">
|
|
@@ -1130,13 +1191,13 @@ const theme = computed(() => {
|
|
|
1130
1191
|
<DialogHeader>
|
|
1131
1192
|
<DialogTitle class="text-left">
|
|
1132
1193
|
<span v-if="state.deletePage.item === ''">Delete Folder "{{ state.deletePage.name }}"</span>
|
|
1133
|
-
<span v-else-if="isExternalLinkEntry(state.deletePage)">Delete Link "{{ state.deletePage
|
|
1134
|
-
<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>
|
|
1135
1196
|
</DialogTitle>
|
|
1136
1197
|
<DialogDescription />
|
|
1137
1198
|
</DialogHeader>
|
|
1138
1199
|
<div class="text-left px-1">
|
|
1139
|
-
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.
|
|
1140
1201
|
</div>
|
|
1141
1202
|
<DialogFooter class="pt-2 flex justify-between">
|
|
1142
1203
|
<edge-shad-button
|
|
@@ -1338,7 +1399,7 @@ const theme = computed(() => {
|
|
|
1338
1399
|
<Sheet v-model:open="state.pageSettings">
|
|
1339
1400
|
<SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
|
|
1340
1401
|
<SheetHeader>
|
|
1341
|
-
<SheetTitle>{{ state.pageData
|
|
1402
|
+
<SheetTitle>{{ displayEntryName(state.pageData) || 'Site' }}</SheetTitle>
|
|
1342
1403
|
<SheetDescription />
|
|
1343
1404
|
</SheetHeader>
|
|
1344
1405
|
<edge-editor
|