@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.
Files changed (45) hide show
  1. package/README.md +1 -0
  2. package/agents.md +95 -2
  3. package/deploy.sh +136 -0
  4. package/edge/components/cms/block.vue +977 -305
  5. package/edge/components/cms/blockApi.vue +3 -3
  6. package/edge/components/cms/blockEditor.vue +688 -86
  7. package/edge/components/cms/blockPicker.vue +31 -5
  8. package/edge/components/cms/blockRender.vue +3 -3
  9. package/edge/components/cms/blocksManager.vue +790 -82
  10. package/edge/components/cms/codeEditor.vue +15 -6
  11. package/edge/components/cms/fontUpload.vue +318 -2
  12. package/edge/components/cms/htmlContent.vue +825 -93
  13. package/edge/components/cms/init_blocks/contact_us.html +55 -47
  14. package/edge/components/cms/init_blocks/newsletter.html +56 -96
  15. package/edge/components/cms/menu.vue +96 -34
  16. package/edge/components/cms/page.vue +902 -58
  17. package/edge/components/cms/posts.vue +13 -4
  18. package/edge/components/cms/site.vue +638 -87
  19. package/edge/components/cms/siteSettingsForm.vue +19 -9
  20. package/edge/components/cms/sitesManager.vue +5 -4
  21. package/edge/components/cms/themeDefaultMenu.vue +20 -2
  22. package/edge/components/cms/themeEditor.vue +196 -162
  23. package/edge/components/editor.vue +5 -1
  24. package/edge/composables/global.ts +37 -5
  25. package/edge/composables/siteSettingsTemplate.js +2 -0
  26. package/edge/composables/useCmsNewDocs.js +100 -0
  27. package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
  28. package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
  29. package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
  30. package/edge/routes/cms/dashboard/media/index.vue +5 -0
  31. package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
  32. package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
  33. package/edge/routes/cms/dashboard/sites/index.vue +4 -0
  34. package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
  35. package/edge/routes/cms/dashboard/templates/index.vue +4 -0
  36. package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
  37. package/edge/routes/cms/dashboard/themes/index.vue +330 -1
  38. package/edge-pull.sh +16 -2
  39. package/edge-push.sh +9 -1
  40. package/edge-remote.sh +20 -0
  41. package/edge-status.sh +9 -5
  42. package/edge-update-all.sh +127 -0
  43. package/firebase.json +4 -0
  44. package/nuxt.config.ts +1 -1
  45. 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
- <!-- Fields driven by CMS array (fieldName + fieldType as option) -->
28
- <div class="space-y-4">
29
- {{{#array {"field":"formFields","schema":[{"field":"fieldName","type":"text","title":"Field Label"},{"field":"fieldType","type":"option","option":{"optionsKey":"title","optionsValue":"value","options":[{"title":"Text","value":"text"},{"title":"Email","value":"email"},{"title":"Phone","value":"tel"},{"title":"Textarea","value":"textarea"}]},"value":"text"}],"value":[{"fieldName":"Name","fieldType":"text"},{"fieldName":"Email","fieldType":"email"},{"fieldName":"Message","fieldType":"textarea"}]}}}}
30
- <div class="space-y-1">
31
- <!-- Label preview -->
32
- <p class="text-xs font-medium uppercase tracking-wide text-slate-600">
33
- {{item.fieldName}}
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
- <span class="ml-1 text-[10px] h-[300px] font-normal text-slate-400">
36
- ({{item.fieldType}})
37
- </span>
38
- <div class="w-full rounded-lg bg-slate-100 h-20"></div>
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
- <span class="ml-1 text-[10px] font-normal text-slate-400">
41
- ({{item.fieldType}})
42
- </span>
43
- <div class="w-full rounded-lg bg-slate-100 h-10"></div>
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
- </p>
56
+ </div>
57
+ {{{/array}}}
58
+ </div>
46
59
 
47
- <!-- Input preview skeleton (visual only) -->
48
-
49
- </div>
50
- {{{/array}}}
51
- </div>
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
- <!-- Submit button preview -->
54
- <div class="mt-6">
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
- <!-- Tiny note -->
65
- <p class="mt-3 text-xs text-slate-400">
66
- The CMS uses <code>emailTo</code> <span class="font-bold underline">({{{#text {"field":"emailTo","title":"Email To","value":"test@testing.com"}}}})</span> and <code>formFields</code> (with fieldType options) to build the real form on the live site.
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
- <!-- Fields driven by CMS array (fieldName + fieldType as option) -->
30
- <div class="space-y-4">
31
- {{{#array {
32
- "field":"newsletterFields",
33
- "title":"Form Fields",
34
- "schema":[
35
- {
36
- "field":"fieldName",
37
- "type":"text",
38
- "title":"Field Label"
39
- },
40
- {
41
- "field":"fieldType",
42
- "type":"option",
43
- "title":"Field Type",
44
- "option":{
45
- "optionsKey":"title",
46
- "optionsValue":"value",
47
- "options":[
48
- {
49
- "title":"Text",
50
- "value":"text"
51
- },
52
- {
53
- "title":"Email",
54
- "value":"email"
55
- },
56
- {
57
- "title":"Phone",
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
- <span class="ml-1 text-[10px] font-normal text-slate-400">
86
- ({{item.fieldType}})
87
- </span>
88
- <div class="w-full rounded-lg bg-slate-100 h-20"></div>
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
- <span class="ml-1 text-[10px] font-normal text-slate-400">
91
- ({{item.fieldType}})
92
- </span>
93
- <div class="w-full rounded-lg bg-slate-100 h-10"></div>
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
- </p>
96
- </div>
97
- {{{/array}}}
98
- </div>
60
+ </div>
61
+ {{{/array}}}
62
+ </div>
99
63
 
100
- <!-- Submit button preview -->
101
- <div class="mt-6">
102
- <button
103
- type="button"
104
- 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"
105
- disabled
106
- >
107
- {{{#text {"field":"buttonText","title":"Button Text","value":"Subscribe"}}}}
108
- </button>
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
- <!-- Tiny note -->
112
- <p class="mt-3 text-xs text-slate-400">
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
- let unique = base
613
+ const baseSlug = base || 'page'
614
+ let unique = baseSlug
562
615
  let suffix = 1
563
616
  while (existing.has(unique)) {
564
- unique = `${base}-${suffix}`
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 original = edgeGlobal.dupObject(modelValue.value)
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 (!state.renameItem.name) {
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
- // const newName = state.renameItem.name || ''
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-black"
1024
+ class="mr-2 group-hover:text-foreground"
964
1025
  />
965
- <span v-if="!props.isTemplateSite" class="hover:text-black !text-black">{{ menuName === 'Site Root' ? 'Site Menu' : menuName }}</span>
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.name }}</span>
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.name }}</span>
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.name }}
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.name }}
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.name }}"</span>
1133
- <span v-else>Delete Page "{{ state.deletePage.name }}"</span>
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.name || 'Site' }}</SheetTitle>
1402
+ <SheetTitle>{{ displayEntryName(state.pageData) || 'Site' }}</SheetTitle>
1341
1403
  <SheetDescription />
1342
1404
  </SheetHeader>
1343
1405
  <edge-editor