maglevcms 1.8.0 → 2.0.0.beta1

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.yarn/install-state.gz +0 -0
  3. data/README.md +4 -4
  4. data/Rakefile +2 -1
  5. data/app/components/maglev/block_component.rb +1 -1
  6. data/app/components/maglev/content/base.rb +1 -1
  7. data/app/components/maglev/content/link.rb +4 -0
  8. data/app/components/maglev/section_component.rb +2 -2
  9. data/app/controllers/concerns/maglev/fetchers_concern.rb +3 -1
  10. data/app/controllers/concerns/maglev/resource_id_concern.rb +10 -0
  11. data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
  12. data/app/controllers/concerns/maglev/{ui_locale_concern.rb → user_interface_locale_concern.rb} +1 -1
  13. data/app/controllers/maglev/api/collection_items_controller.rb +7 -0
  14. data/app/controllers/maglev/api/page_clones_controller.rb +1 -2
  15. data/app/controllers/maglev/api_controller.rb +1 -1
  16. data/app/controllers/maglev/application_controller.rb +1 -5
  17. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +17 -0
  18. data/app/controllers/maglev/assets/proxy_controller.rb +21 -0
  19. data/app/controllers/maglev/editor_controller.rb +1 -1
  20. data/app/controllers/maglev/page_preview_controller.rb +3 -3
  21. data/app/frontend/admin/controllers/screenshot_controller.js +29 -1
  22. data/app/frontend/editor/assets/remixicons/clipboard-line.svg +1 -0
  23. data/app/frontend/editor/assets/remixicons/ri-draggable.svg +1 -0
  24. data/app/frontend/editor/assets/remixicons/ri-folders-line.svg +1 -0
  25. data/app/frontend/editor/assets/remixicons/ri-more-fill.svg +1 -0
  26. data/app/frontend/editor/assets/remixicons/ri-shadow-line.svg +1 -0
  27. data/app/frontend/editor/components/errors/forbidden.vue +24 -0
  28. data/app/frontend/editor/components/errors/stale-record.vue +2 -2
  29. data/app/frontend/editor/components/header-nav/device-toggler.vue +6 -3
  30. data/app/frontend/editor/components/header-nav/index.vue +13 -42
  31. data/app/frontend/editor/components/header-nav/locale-toggler/index.vue +1 -1
  32. data/app/frontend/editor/components/header-nav/page-info.vue +41 -0
  33. data/app/frontend/editor/components/header-nav/preview-button.vue +6 -1
  34. data/app/frontend/editor/components/header-nav/separator.vue +1 -1
  35. data/app/frontend/editor/components/image-library/index.vue +26 -29
  36. data/app/frontend/editor/components/kit/collection-item-input.vue +9 -0
  37. data/app/frontend/editor/components/kit/copy-paste-button.vue +26 -0
  38. data/app/frontend/editor/components/kit/dropdown.vue +5 -0
  39. data/app/frontend/editor/components/kit/icon-button.vue +24 -0
  40. data/app/frontend/editor/components/kit/icon.vue +2 -3
  41. data/app/frontend/editor/components/kit/index.js +4 -2
  42. data/app/frontend/editor/components/kit/link-input.vue +1 -1
  43. data/app/frontend/editor/components/kit/modal.vue +1 -1
  44. data/app/frontend/editor/components/kit/page-icon.vue +1 -1
  45. data/app/frontend/editor/components/kit/rich-text-input/link-buttons.vue +1 -1
  46. data/app/frontend/editor/components/kit/select-input.vue +4 -2
  47. data/app/frontend/editor/components/link-picker/actions.vue +1 -1
  48. data/app/frontend/editor/components/link-picker/index.vue +1 -1
  49. data/app/frontend/editor/components/link-picker/page.vue +2 -0
  50. data/app/frontend/editor/components/page/form/main.vue +1 -1
  51. data/app/frontend/editor/components/page/list/actions-button.vue +159 -0
  52. data/app/frontend/editor/components/page/list/index.vue +15 -24
  53. data/app/frontend/editor/components/page/list/list-item.vue +10 -61
  54. data/app/frontend/editor/components/section-highlighter/index.vue +47 -46
  55. data/app/frontend/editor/components/section-list/add-button.vue +39 -0
  56. data/app/frontend/editor/components/section-list/index.vue +30 -15
  57. data/app/frontend/editor/components/section-list/list-item.vue +23 -8
  58. data/app/frontend/editor/components/section-pane/block-list/index.vue +1 -0
  59. data/app/frontend/editor/components/section-pane/block-list/list-item.vue +9 -6
  60. data/app/frontend/editor/components/section-pane/block-tree/new-nested-block-button.vue +2 -2
  61. data/app/frontend/editor/components/section-pane/block-tree/tree-node.vue +2 -2
  62. data/app/frontend/editor/components/sidebar-nav/index.vue +102 -0
  63. data/app/frontend/editor/components/sidebar-nav/link.vue +60 -0
  64. data/app/frontend/editor/components/theme-section-list/category.vue +47 -0
  65. data/app/frontend/editor/components/theme-section-list/index.vue +7 -31
  66. data/app/frontend/editor/components/theme-section-list/list-item.vue +10 -3
  67. data/app/frontend/editor/design/components/modal.scss +1 -1
  68. data/app/frontend/editor/design/components/tooltip.scss +6 -0
  69. data/app/frontend/editor/design/transitions.scss +1 -1
  70. data/app/frontend/editor/layouts/app.vue +1 -1
  71. data/app/frontend/editor/layouts/default.vue +2 -2
  72. data/app/frontend/editor/layouts/slide-pane.vue +1 -1
  73. data/app/frontend/editor/locales/editor.ar.json +265 -0
  74. data/app/frontend/editor/locales/editor.en.json +23 -15
  75. data/app/frontend/editor/locales/editor.es.json +18 -10
  76. data/app/frontend/editor/locales/editor.fr.json +28 -13
  77. data/app/frontend/editor/locales/editor.pt-BR.json +18 -10
  78. data/app/frontend/editor/locales/index.js +3 -0
  79. data/app/frontend/editor/mixins/error-modal.js +7 -2
  80. data/app/frontend/editor/mixins/preview-transformation.js +0 -1
  81. data/app/frontend/editor/plugins/i18n.js +2 -17
  82. data/app/frontend/editor/services/__tests__/section.spec.js +6 -0
  83. data/app/frontend/editor/services/collection-item.js +8 -0
  84. data/app/frontend/editor/services/page.js +1 -1
  85. data/app/frontend/editor/services/section.js +30 -0
  86. data/app/frontend/editor/services/site.js +4 -4
  87. data/app/frontend/editor/services/theme.js +1 -0
  88. data/app/frontend/editor/spec/__mocks__/page.js +3 -1
  89. data/app/frontend/editor/spec/__mocks__/services.js +1 -0
  90. data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
  91. data/app/frontend/editor/store/actions/index.js +1 -0
  92. data/app/frontend/editor/store/actions/site.js +8 -4
  93. data/app/frontend/editor/store/getters.js +11 -0
  94. data/app/frontend/editor/views/page-preview.vue +2 -1
  95. data/app/frontend/images/favicon.svg +11 -0
  96. data/app/frontend/images/logo.svg +14 -0
  97. data/app/frontend/live-preview-client/iframe-decorator.js +17 -15
  98. data/app/frontend/live-preview-client/rails.js +1 -1
  99. data/app/helpers/maglev/application_helper.rb +7 -2
  100. data/app/helpers/maglev/editor_helper.rb +12 -5
  101. data/app/models/concerns/maglev/sections_concern.rb +4 -0
  102. data/app/models/maglev/asset.rb +13 -14
  103. data/app/models/maglev/page/path_concern.rb +23 -8
  104. data/app/models/maglev/page.rb +17 -18
  105. data/app/models/maglev/page_path.rb +18 -18
  106. data/app/models/maglev/section/setting.rb +1 -1
  107. data/app/models/maglev/section.rb +18 -9
  108. data/app/models/maglev/setting_types/link.rb +1 -1
  109. data/app/models/maglev/site.rb +13 -24
  110. data/app/services/maglev/app_container.rb +2 -2
  111. data/app/services/maglev/extract_locale.rb +3 -2
  112. data/app/services/maglev/fetch_page.rb +2 -0
  113. data/app/services/maglev/fetch_theme_layout.rb +2 -0
  114. data/app/services/maglev/get_page_fullpath.rb +1 -2
  115. data/app/services/maglev/remove_section_type.rb +50 -0
  116. data/app/services/maglev/rename_section_type.rb +57 -0
  117. data/app/services/maglev/search_pages.rb +2 -1
  118. data/app/views/layouts/maglev/admin/application.html.erb +1 -1
  119. data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
  120. data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
  121. data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
  122. data/app/views/maglev/editor/show.html.erb +6 -5
  123. data/config/locales/activerecord.ar.yml +13 -0
  124. data/config/locales/activerecord.en.yml +8 -2
  125. data/config/locales/activerecord.es.yml +4 -0
  126. data/config/locales/activerecord.fr.yml +6 -2
  127. data/config/locales/activerecord.pt-BR.yml +13 -0
  128. data/config/routes.rb +3 -4
  129. data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
  130. data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
  131. data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
  132. data/db/migrate/20211013210954_translate_section_content.rb +16 -2
  133. data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
  134. data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
  135. data/lib/commands/maglev/change_site_locales_command.rb +61 -0
  136. data/lib/commands/maglev/create_site_command.rb +28 -0
  137. data/lib/commands/maglev/sections/remove_command.rb +48 -0
  138. data/lib/commands/maglev/sections/rename_command.rb +49 -0
  139. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
  140. data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
  141. data/lib/maglev/errors.rb +1 -0
  142. data/lib/maglev/preview_constraint.rb +5 -1
  143. data/lib/maglev/theme_filesystem_loader.rb +7 -0
  144. data/lib/maglev/version.rb +1 -1
  145. data/lib/maglev.rb +13 -3
  146. data/lib/tasks/maglev/icons.rake +123 -0
  147. data/lib/tasks/maglev/vite.rake +62 -0
  148. data/lib/tasks/maglev_tasks.rake +9 -107
  149. data/tailwind.config.js +1 -0
  150. metadata +48 -33
  151. data/app/controllers/maglev/assets_controller.rb +0 -10
  152. data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
  153. data/app/frontend/editor/components/sidebar-nav.vue +0 -125
  154. data/app/frontend/editor/plugins/maglev_dummy.js +0 -2
  155. data/app/frontend/images/favicon.png +0 -0
  156. data/app/frontend/images/logo.png +0 -0
@@ -1,40 +1,37 @@
1
1
  <template>
2
2
  <div
3
3
  class="h-48 absolute pointer-events-none transition duration-200 ease-in-out"
4
- :style="style"
4
+ :style="{...style}"
5
5
  v-if="previewReady"
6
6
  >
7
7
  <div
8
8
  class="w-full h-full relative mx-auto border-solid border-0 border-t-4 border-b-4"
9
9
  :class="{
10
- tablet: hasEnoughWidthForTablet,
11
- mobile: hasEnoughWidthForMobile,
12
- 'border-transparent': !hoveredSection,
13
- 'border-editor-primary': hoveredSection,
10
+ 'border-transparent': !mustBeDisplayed,
11
+ 'border-editor-primary': mustBeDisplayed,
14
12
  }"
15
13
  >
16
14
  <transition
17
15
  name="slide-fade"
18
16
  mode="out-in"
19
- v-on:after-leave="afterAnimationDone"
20
17
  >
21
18
  <top-left-actions
22
19
  :hovered-section="hoveredSection"
23
- v-if="hoveredSection"
20
+ v-if="mustBeDisplayed"
24
21
  />
25
22
  </transition>
26
23
 
27
24
  <transition name="reverse-slide-fade" mode="out-in">
28
25
  <top-right-actions
29
26
  :hoveredSection="hoveredSection"
30
- v-if="hoveredSection"
27
+ v-if="mustBeDisplayed"
31
28
  />
32
29
  </transition>
33
30
 
34
31
  <transition name="slide-up-fade" mode="out-in">
35
32
  <bottom-actions
36
33
  :hovered-section="hoveredSection"
37
- v-if="hoveredSection"
34
+ v-if="mustBeDisplayed"
38
35
  />
39
36
  </transition>
40
37
  </div>
@@ -42,11 +39,11 @@
42
39
  </template>
43
40
 
44
41
  <script>
45
- // import { mapState } from 'vuex'
46
42
  import TransformationMixin from '@/mixins/preview-transformation'
47
43
  import TopLeftActions from './top-left-actions.vue'
48
44
  import TopRightActions from './top-right-actions.vue'
49
45
  import BottomActions from './bottom-actions.vue'
46
+ import { debounce } from '@/misc/utils'
50
47
 
51
48
  export default {
52
49
  name: 'SectionHighlighter',
@@ -56,69 +53,73 @@ export default {
56
53
  hoveredSection: { type: Object },
57
54
  },
58
55
  data() {
59
- return { shadow: null }
56
+ return { style: {}, isScrolling: false, boundingRect: null }
60
57
  },
61
58
  mounted() {
62
- // NOTE: optimized version to update the highlighter when scrolling the iframe
63
59
  window.addEventListener('maglev:preview:scroll', this.onPreviewScroll)
60
+ this.waitUntilScrollingDone = debounce(this.onEndPreviewScrolling.bind(this), 800)
64
61
  },
65
62
  beforeDestroy() {
66
63
  window.removeEventListener('maglev:preview:scroll', this.onPreviewScroll)
67
64
  },
68
65
  computed: {
69
- style() {
70
- if (!this.hoveredSection && !this.shadow) return {}
71
- const { sectionRect } = this.hoveredSection || this.shadow
72
- return this.performStyle(sectionRect)
73
- },
74
66
  minTop() {
75
67
  return this.hoveredSection?.sectionOffsetTop || 0
76
68
  },
69
+ mustBeDisplayed() {
70
+ return !!this.hoveredSection && !this.isScrolling
71
+ }
77
72
  },
78
73
  methods: {
79
- afterAnimationDone() {
80
- this.shadow = null
74
+ onPreviewScroll(event) {
75
+ this.isScrolling = true
76
+ this.boundingRect = event.detail.boundingRect
77
+ this.waitUntilScrollingDone()
78
+ },
79
+ onEndPreviewScrolling() {
80
+ this.isScrolling = false
81
+ this.applyStyle(this.boundingRect)
81
82
  },
82
- onPreviewScroll(event) {
83
- let self = this
84
- window.requestAnimationFrame(() => {
85
- const newStyle = this.performStyle(event.detail.boundingRect)
86
- Object.entries(newStyle).forEach(
87
- ([key, value]) => (self.$el.style[key] = value),
88
- )
89
- })
83
+ applyStyle(boundingRect) {
84
+ if (!boundingRect || !this.$el?.style) return // when we switch pages, the element is not mounted yet
85
+ const self = this
86
+ const newStyle = this.calculateStyle(boundingRect)
87
+ Object.entries(newStyle).forEach(
88
+ ([key, value]) => (self.$el.style[key] = value),
89
+ )
90
90
  },
91
- performStyle(boundingRect) {
91
+ calculateStyle(boundingRect) {
92
92
  const isSticky = boundingRect.top < this.minTop
93
93
  const top = isSticky ? this.minTop : boundingRect.top
94
94
  const height = isSticky
95
95
  ? boundingRect.height - (this.minTop - boundingRect.top)
96
96
  : boundingRect.height
97
-
97
+
98
98
  return {
99
99
  top: `${top * this.previewScaleRatio}px`,
100
- left: `calc(50% - ${this.previewLeftPadding}px / 2 - (${boundingRect.width}px * ${this.previewScaleRatio}) / 2 + ${this.previewLeftPadding}px)`,
100
+ left: `calc(${this.calculateLeftOffset()}px + (${boundingRect.left}px * ${this.previewScaleRatio}))`,
101
101
  height: `${height * this.previewScaleRatio}px`,
102
102
  width: `calc(${boundingRect.width}px * ${this.previewScaleRatio})`,
103
103
  }
104
104
  },
105
+ calculateLeftOffset() {
106
+ const sidebarWidth =
107
+ document.querySelector('.content-area > aside')?.offsetWidth || 0
108
+ const iframePadding = document.getElementById('iframe-wrapper').getBoundingClientRect().left
109
+ return iframePadding - sidebarWidth
110
+ },
105
111
  },
106
112
  watch: {
107
- hoveredSection(value, oldValue) {
108
- if (!value) {
109
- this.shadow = { ...oldValue }
110
- }
111
- },
112
- },
113
- }
114
- </script>
113
+ hoveredSection: {
114
+ handler(value) {
115
+ if (!value) return
115
116
 
116
- <style scoped>
117
- .mobile {
118
- width: 375px;
119
- }
120
-
121
- .tablet {
122
- width: 1024px;
117
+ this.isScrolling = false
118
+ this.boundingRect = value.sectionRect
119
+ this.applyStyle(value.sectionRect)
120
+ },
121
+ immediate: true
122
+ }
123
+ },
123
124
  }
124
- </style>
125
+ </script>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <div
3
+ class="absolute left-0 w-full h-7 group bg-red-500/0 z-10"
4
+ :class="{
5
+ '-top-5': isTop,
6
+ '-bottom-5': !isTop
7
+ }"
8
+ >
9
+ <!-- <div class="relative hidden group-hover:block h-0.5 w-full bg-gray-600 top-[calc(theme('spacing[7]')/2+theme('spacing[0.5]')/-2)]"></div> -->
10
+
11
+ <div class="absolute top-0 left-0 w-full justify-center flex opacity-0 group-hover:opacity-100 transition-opacity duration-200">
12
+ <uikit-icon-button
13
+ iconName="ri-add-line"
14
+ dark
15
+ class="border-4 border-white w-10 h-10"
16
+ @click="addSection"
17
+ />
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ export default {
24
+ name: 'SectionListAddButton',
25
+ props: {
26
+ insertAt: { type: String, required: false },
27
+ },
28
+ computed: {
29
+ isTop() {
30
+ return this.insertAt === 'top'
31
+ },
32
+ },
33
+ methods: {
34
+ addSection() {
35
+ this.$router.push({ name: 'addSectionAfter', params: { sectionId: this.insertAt } })
36
+ },
37
+ },
38
+ }
39
+ </script>
@@ -1,20 +1,31 @@
1
1
  <template>
2
- <div>
3
- <div v-if="isListEmpty" class="text-center mt-8">
4
- <span class="text-gray-800">{{ $t('sections.listPane.empty') }}</span>
2
+ <div class="flex flex-col h-full">
3
+ <div class="relative flex-auto h-0 overflow-y-auto py-6">
4
+ <div v-if="isListEmpty" class="text-center mt-8">
5
+ <span class="text-gray-800">{{ $t('sections.listPane.empty') }}</span>
6
+ </div>
7
+ <draggable :list="list" @end="onSortEnd" v-bind="dragOptions" v-else>
8
+ <transition-group type="transition" name="flip-list">
9
+ <list-item
10
+ v-for="(section, index) in list"
11
+ :key="section.id"
12
+ :section="section"
13
+ :index="index"
14
+ @on-dropdown-toggle="onDropdownToggle"
15
+ class="mb-3"
16
+ />
17
+ </transition-group>
18
+ </draggable>
19
+ </div>
20
+ <div class="mt-auto relative">
21
+ <button
22
+ class="big-submit-button bg-editor-primary"
23
+ @click="addSection"
24
+ >
25
+ <uikit-icon name="ri-add-line" size="1.5rem" />
26
+ <span class="ml-3">{{ $t('sections.listPane.addButton') }}</span>
27
+ </button>
5
28
  </div>
6
- <draggable :list="list" @end="onSortEnd" v-bind="dragOptions" v-else>
7
- <transition-group type="transition" name="flip-list">
8
- <list-item
9
- v-for="(section, index) in list"
10
- :key="section.id"
11
- :section="section"
12
- :index="index"
13
- @on-dropdown-toggle="onDropdownToggle"
14
- class="mb-3"
15
- />
16
- </transition-group>
17
- </draggable>
18
29
  </div>
19
30
  </template>
20
31
 
@@ -41,11 +52,15 @@ export default {
41
52
  group: 'description',
42
53
  disabled: false,
43
54
  ghostClass: 'ghost',
55
+ handle: '.cursor-move',
44
56
  }
45
57
  },
46
58
  },
47
59
  methods: {
48
60
  ...mapActions(['moveSection']),
61
+ addSection() {
62
+ this.$router.push({ name: 'addSection' })
63
+ },
49
64
  onSortEnd(event) {
50
65
  this.moveSection({
51
66
  from: event.oldIndex,
@@ -1,33 +1,42 @@
1
1
  <template>
2
2
  <div
3
- class="bg-gray-100 rounded-md px-4 py-3 flex items-center justify-between text-gray-800 cursor-move"
3
+ class="bg-gray-100 rounded-md pr-2 flex items-center text-gray-800 relative"
4
4
  >
5
+ <add-button insertAt="top" v-if="isFirst" />
6
+ <add-button :insertAt="section.id" />
7
+
8
+ <div class="flex flex-col cursor-move px-2 py-3">
9
+ <uikit-icon name="ri-draggable" />
10
+ </div>
11
+
5
12
  <router-link
6
13
  :to="{ name: 'editSection', params: { sectionId: section.id } }"
7
- class="flex items-center"
14
+ class="flex flex-col overflow-hidden py-3 pr-2 leading-0.5 flex-1"
8
15
  >
9
- <span>{{ name | truncate(40) }}</span>
16
+ <span :class="{ 'text-gray-500 text-xs': label }">{{ name | truncate(40) }}</span>
17
+ <span class="text-gray-800 truncate" v-if="label">{{ label | truncate(100) }}</span>
10
18
  </router-link>
19
+
11
20
  <uikit-confirmation-button
12
21
  @confirm="removeSection(section.id)"
13
22
  v-on="$listeners"
23
+ class="ml-auto"
14
24
  >
15
- <button
16
- class="px-1 py-1 rounded-full bg-gray-600 bg-opacity-0 hover:text-gray-900 text-gray-600 focus:outline-none hover:bg-opacity-10 transition-colors duration-200"
17
- >
18
- <uikit-icon name="ri-close-line" size="1.25rem" />
19
- </button>
25
+ <uikit-icon-button iconName="delete-bin-line" />
20
26
  </uikit-confirmation-button>
21
27
  </div>
22
28
  </template>
23
29
 
24
30
  <script>
25
31
  import { mapActions } from 'vuex'
32
+ import AddButton from './add-button.vue'
26
33
 
27
34
  export default {
28
35
  name: 'SectionListItem',
36
+ components: { AddButton },
29
37
  props: {
30
38
  section: { type: Object, required: true },
39
+ index: { type: Number, required: true },
31
40
  },
32
41
  computed: {
33
42
  name() {
@@ -37,6 +46,12 @@ export default {
37
46
  ) || this.section.name
38
47
  )
39
48
  },
49
+ label() {
50
+ return this.section.label
51
+ },
52
+ isFirst() {
53
+ return this.index === 0
54
+ },
40
55
  },
41
56
  methods: {
42
57
  ...mapActions(['removeSection']),
@@ -47,6 +47,7 @@ export default {
47
47
  group: 'description',
48
48
  disabled: false,
49
49
  ghostClass: 'ghost',
50
+ handle: '.cursor-move',
50
51
  }
51
52
  },
52
53
  },
@@ -1,14 +1,17 @@
1
1
  <template>
2
2
  <div
3
- class="bg-gray-100 rounded-md px-4 py-3 flex items-center justify-between text-gray-800"
4
- :class="{ 'cursor-move': isList }"
3
+ class="bg-gray-100 rounded-md pr-2 flex items-center text-gray-800"
4
+ :class="{ 'pl-4': !isList }"
5
5
  >
6
+ <div class="flex flex-col cursor-move px-2 py-3" v-if="isList">
7
+ <uikit-icon name="ri-draggable" />
8
+ </div>
6
9
  <router-link
7
10
  :to="{
8
11
  name: 'editSectionBlock',
9
12
  params: { sectionBlockId: sectionBlock.id },
10
13
  }"
11
- class="flex items-center"
14
+ class="flex items-center py-3 overflow-hidden"
12
15
  >
13
16
  <div class="h-8 w-8 bg-gray-400 mr-3" v-if="image">
14
17
  <img
@@ -18,15 +21,15 @@
18
21
  @load="() => (imageLoaded = true)"
19
22
  />
20
23
  </div>
21
- <span>{{ label | truncate(40) }}</span>
24
+ <span class="truncate">{{ label | truncate(40) }}</span>
22
25
  </router-link>
23
- <div class="flex items-center">
26
+ <div class="flex items-center ml-auto pl-2">
24
27
  <slot name="actions"></slot>
25
28
  <uikit-confirmation-button
26
29
  @confirm="removeSectionBlock(sectionBlock.id)"
27
30
  v-on="$listeners"
28
31
  >
29
- <uikit-list-item-button iconName="ri-close-line" />
32
+ <uikit-icon-button iconName="delete-bin-line" />
30
33
  </uikit-confirmation-button>
31
34
  </div>
32
35
  </div>
@@ -8,7 +8,7 @@
8
8
  v-if="hasMultipleTypes"
9
9
  >
10
10
  <template v-slot:button>
11
- <uikit-list-item-button iconName="ri-add-line" />
11
+ <uikit-icon-button iconName="ri-add-line" />
12
12
  </template>
13
13
  <template v-slot:content>
14
14
  <div class="w-full flex flex-col">
@@ -23,7 +23,7 @@
23
23
  </div>
24
24
  </template>
25
25
  </uikit-dropdown>
26
- <uikit-list-item-button
26
+ <uikit-icon-button
27
27
  iconName="ri-add-line"
28
28
  @click.native="addNestedSectionBlock"
29
29
  v-else
@@ -14,13 +14,13 @@
14
14
  v-on="$listeners"
15
15
  />
16
16
 
17
- <uikit-list-item-button
17
+ <uikit-icon-button
18
18
  iconName="arrow-up-s-line"
19
19
  @click.native="moveSectionBlockUp"
20
20
  v-if="index > 0"
21
21
  />
22
22
 
23
- <uikit-list-item-button
23
+ <uikit-icon-button
24
24
  iconName="arrow-down-s-line"
25
25
  @click.native="moveSectionBlockDown"
26
26
  v-if="!last"
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <nav class="w-16 flex flex-col justify-between">
3
+ <div
4
+ class="flex justify-center h-full w-full animate-pulse"
5
+ v-if="!currentPage"
6
+ >
7
+ <div class="w-6 bg-gray-200 rounded h-48 my-6"></div>
8
+ </div>
9
+ <ol class="divide-y divide-gray-300 px-4" v-else>
10
+ <li>
11
+ <sidebar-nav-link
12
+ routerLinkName="listPages"
13
+ :active="isListPagesActive"
14
+ iconName="ri-file-copy-line"
15
+ :tooltipMessage="$t('sidebarNav.listPagesTooltip')"
16
+ />
17
+ </li>
18
+ <li>
19
+ <sidebar-nav-link
20
+ :routerLinkName="'listSections'"
21
+ :active="isSectionListPaneActive"
22
+ iconName="ri-stack-line"
23
+ :tooltipMessage="$t('sidebarNav.managePageSectionsTooltip')"
24
+ />
25
+ </li>
26
+ <li v-if="hasStyle">
27
+ <sidebar-nav-link
28
+ :routerLinkName="'editStyle'"
29
+ :active="isEditStylePaneActive"
30
+ iconName="ri-drop-line"
31
+ iconSize="1.25rem"
32
+ :tooltipMessage="$t('sidebarNav.editStyleTooltip')"
33
+ />
34
+ </li>
35
+ <li>
36
+ <sidebar-nav-link
37
+ :isRouterLink="false"
38
+ iconName="image-line"
39
+ :tooltipMessage="$t('sidebarNav.openImageLibraryTooltip')"
40
+ @click.prevent="openImageLibraryModal"
41
+ />
42
+ </li>
43
+ <li></li>
44
+ </ol>
45
+
46
+ <ol class="divide-y divide-gray-300 px-4">
47
+ <li></li>
48
+ <li>
49
+ <sidebar-nav-link
50
+ :isRouterLink="false"
51
+ :linkUrl="leaveEditorUrl"
52
+ iconName="logout-box-r-line"
53
+ :tooltipMessage="$t('sidebarNav.leaveEditorTooltip')"
54
+ />
55
+ </li>
56
+ </ol>
57
+ </nav>
58
+ </template>
59
+
60
+ <script>
61
+ import ImageLibrary from '@/components/image-library/index.vue'
62
+ import SidebarNavLink from './link.vue'
63
+
64
+ export default {
65
+ name: 'SidebarNav',
66
+ components: {
67
+ SidebarNavLink
68
+ },
69
+ computed: {
70
+ hasStyle() {
71
+ return !this.isBlank(this.currentStyle)
72
+ },
73
+ isListPagesActive() {
74
+ return this.$route.name === 'listPages'
75
+ },
76
+ isAddSectionPaneActive() {
77
+ return this.$route.name === 'addSection'
78
+ },
79
+ isSectionListPaneActive() {
80
+ return this.$route.name === 'listSections'
81
+ },
82
+ isEditStylePaneActive() {
83
+ return this.$route.name === 'editStyle'
84
+ },
85
+ isEditPageActive() {
86
+ return this.$route.name === 'editPageSettings'
87
+ },
88
+ leaveEditorUrl() {
89
+ return window.leaveUrl
90
+ },
91
+ },
92
+ methods: {
93
+ openImageLibraryModal() {
94
+ this.openModal({
95
+ title: this.$t('imageLibrary.title'),
96
+ component: ImageLibrary,
97
+ props: { modalClass: 'w-216' },
98
+ })
99
+ },
100
+ },
101
+ }
102
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <component
3
+ :is="isRouterLink ? 'router-link' : 'a'"
4
+ v-bind="linkProps"
5
+ v-on="$listeners"
6
+ class="flex justify-center py-5 -ml-4 -mr-4 hover:bg-editor-primary hover:bg-opacity-5 transition-colors duration-200"
7
+ :class="{
8
+ 'bg-white': !active,
9
+ 'bg-editor-primary bg-opacity-5': active,
10
+ }"
11
+ v-tooltip.right="tooltipMessage"
12
+ >
13
+ <uikit-icon :name="iconName" :size="iconSize" />
14
+ </component>
15
+ </template>
16
+
17
+ <script>
18
+ export default {
19
+ name: 'SidebarNavLink',
20
+ props: {
21
+ isRouterLink: {
22
+ type: Boolean,
23
+ default: true,
24
+ },
25
+ routerLinkName: {
26
+ type: String,
27
+ required: false,
28
+ },
29
+ linkUrl: {
30
+ type: String,
31
+ default: '#',
32
+ required: false,
33
+ },
34
+ active: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ iconName: {
39
+ type: String,
40
+ required: true,
41
+ },
42
+ iconSize: {
43
+ type: String,
44
+ default: '1.5rem',
45
+ },
46
+ tooltipMessage: {
47
+ type: String,
48
+ default: null,
49
+ required: false,
50
+ },
51
+ },
52
+ computed: {
53
+ linkProps() {
54
+ return this.isRouterLink ? {
55
+ to: { name: this.routerLinkName },
56
+ } : { href: this.linkUrl }
57
+ },
58
+ },
59
+ }
60
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <uikit-accordion
3
+ :key="index"
4
+ headerClass="px-3 py-3 mb-3 bg-editor-primary text-white rounded-sm"
5
+ >
6
+ <div slot="header" class="flex items-center">
7
+ <div class="capitalize-first">{{ name }}</div>
8
+ <div class="ml-2 px-3 bg-white bg-opacity-25 text-xs rounded-full">
9
+ {{ category.children.length }}
10
+ </div>
11
+ </div>
12
+ <div class="pt-1">
13
+ <list-item
14
+ v-for="section in category.children"
15
+ :key="section.id"
16
+ :section="section"
17
+ :insertAfter="insertAfter"
18
+ />
19
+
20
+ <div
21
+ class="text-center pt-2 pb-6"
22
+ v-if="category.children.length === 0"
23
+ >
24
+ {{ $t('themeSectionList.emptyCategory') }}
25
+ </div>
26
+ </div>
27
+ </uikit-accordion>
28
+ </template>
29
+
30
+ <script>
31
+ import ListItem from './list-item.vue'
32
+
33
+ export default {
34
+ name: 'ThemeSectionListCategory',
35
+ components: { ListItem },
36
+ props: {
37
+ index: { type: Number, required: true },
38
+ category: { type: Object, required: true },
39
+ insertAfter: { type: String },
40
+ },
41
+ computed: {
42
+ name() {
43
+ return this.$st(`${this.currentI18nScope}.categories.${this.category.id}.name`) ?? this.category.name
44
+ },
45
+ },
46
+ }
47
+ </script>