maglevcms 1.7.3 → 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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/.yarn/install-state.gz +0 -0
  3. data/README.md +4 -4
  4. data/Rakefile +3 -1
  5. data/app/components/maglev/base_component.rb +6 -0
  6. data/app/components/maglev/block_component.rb +11 -1
  7. data/app/components/maglev/content/base.rb +9 -1
  8. data/app/components/maglev/content/link.rb +4 -0
  9. data/app/components/maglev/inspector.rb +18 -0
  10. data/app/components/maglev/page_component.rb +4 -0
  11. data/app/components/maglev/section_component.rb +6 -2
  12. data/app/controllers/concerns/maglev/fetchers_concern.rb +3 -1
  13. data/app/controllers/concerns/maglev/resource_id_concern.rb +10 -0
  14. data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
  15. data/app/controllers/concerns/maglev/{ui_locale_concern.rb → user_interface_locale_concern.rb} +1 -1
  16. data/app/controllers/maglev/api/collection_items_controller.rb +7 -0
  17. data/app/controllers/maglev/api/page_clones_controller.rb +1 -2
  18. data/app/controllers/maglev/api_controller.rb +1 -1
  19. data/app/controllers/maglev/application_controller.rb +1 -5
  20. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +17 -0
  21. data/app/controllers/maglev/assets/proxy_controller.rb +21 -0
  22. data/app/controllers/maglev/editor_controller.rb +1 -1
  23. data/app/controllers/maglev/page_preview_controller.rb +3 -3
  24. data/app/frontend/admin/controllers/screenshot_controller.js +29 -1
  25. data/app/frontend/editor/assets/remixicons/clipboard-line.svg +1 -0
  26. data/app/frontend/editor/assets/remixicons/format-superscript.svg +1 -0
  27. data/app/frontend/editor/assets/remixicons/ri-draggable.svg +1 -0
  28. data/app/frontend/editor/assets/remixicons/ri-folders-line.svg +1 -0
  29. data/app/frontend/editor/assets/remixicons/ri-more-fill.svg +1 -0
  30. data/app/frontend/editor/assets/remixicons/ri-shadow-line.svg +1 -0
  31. data/app/frontend/editor/components/dynamic-form/dynamic-input.vue +1 -1
  32. data/app/frontend/editor/components/errors/forbidden.vue +24 -0
  33. data/app/frontend/editor/components/errors/stale-record.vue +2 -2
  34. data/app/frontend/editor/components/header-nav/device-toggler.vue +6 -3
  35. data/app/frontend/editor/components/header-nav/index.vue +13 -42
  36. data/app/frontend/editor/components/header-nav/locale-toggler/index.vue +1 -1
  37. data/app/frontend/editor/components/header-nav/page-info.vue +41 -0
  38. data/app/frontend/editor/components/header-nav/preview-button.vue +6 -1
  39. data/app/frontend/editor/components/header-nav/separator.vue +1 -1
  40. data/app/frontend/editor/components/image-library/index.vue +26 -29
  41. data/app/frontend/editor/components/kit/collection-item-input.vue +9 -0
  42. data/app/frontend/editor/components/kit/copy-paste-button.vue +26 -0
  43. data/app/frontend/editor/components/kit/dropdown.vue +5 -0
  44. data/app/frontend/editor/components/kit/icon-button.vue +24 -0
  45. data/app/frontend/editor/components/kit/icon.vue +2 -3
  46. data/app/frontend/editor/components/kit/index.js +4 -2
  47. data/app/frontend/editor/components/kit/link-input.vue +1 -1
  48. data/app/frontend/editor/components/kit/modal.vue +1 -1
  49. data/app/frontend/editor/components/kit/page-icon.vue +1 -1
  50. data/app/frontend/editor/components/kit/rich-text-input/extensions/marks/Superscript.js +43 -0
  51. data/app/frontend/editor/components/kit/rich-text-input/format-buttons.vue +9 -1
  52. data/app/frontend/editor/components/kit/rich-text-input/link-buttons.vue +1 -1
  53. data/app/frontend/editor/components/kit/rich-text-input.vue +11 -5
  54. data/app/frontend/editor/components/kit/select-input.vue +4 -2
  55. data/app/frontend/editor/components/link-picker/actions.vue +1 -1
  56. data/app/frontend/editor/components/link-picker/index.vue +1 -1
  57. data/app/frontend/editor/components/link-picker/page.vue +2 -0
  58. data/app/frontend/editor/components/page/form/main.vue +1 -1
  59. data/app/frontend/editor/components/page/list/actions-button.vue +159 -0
  60. data/app/frontend/editor/components/page/list/index.vue +15 -24
  61. data/app/frontend/editor/components/page/list/list-item.vue +10 -61
  62. data/app/frontend/editor/components/section-highlighter/index.vue +47 -46
  63. data/app/frontend/editor/components/section-list/add-button.vue +39 -0
  64. data/app/frontend/editor/components/section-list/index.vue +30 -15
  65. data/app/frontend/editor/components/section-list/list-item.vue +23 -8
  66. data/app/frontend/editor/components/section-pane/block-list/index.vue +1 -0
  67. data/app/frontend/editor/components/section-pane/block-list/list-item.vue +9 -6
  68. data/app/frontend/editor/components/section-pane/block-tree/new-nested-block-button.vue +2 -2
  69. data/app/frontend/editor/components/section-pane/block-tree/tree-node.vue +2 -2
  70. data/app/frontend/editor/components/sidebar-nav/index.vue +102 -0
  71. data/app/frontend/editor/components/sidebar-nav/link.vue +60 -0
  72. data/app/frontend/editor/components/theme-section-list/category.vue +47 -0
  73. data/app/frontend/editor/components/theme-section-list/index.vue +7 -31
  74. data/app/frontend/editor/components/theme-section-list/list-item.vue +10 -3
  75. data/app/frontend/editor/design/components/modal.scss +1 -1
  76. data/app/frontend/editor/design/components/tooltip.scss +6 -0
  77. data/app/frontend/editor/design/transitions.scss +1 -1
  78. data/app/frontend/editor/layouts/app.vue +1 -1
  79. data/app/frontend/editor/layouts/default.vue +2 -2
  80. data/app/frontend/editor/layouts/slide-pane.vue +1 -1
  81. data/app/frontend/editor/locales/editor.ar.json +265 -0
  82. data/app/frontend/editor/locales/editor.en.json +23 -15
  83. data/app/frontend/editor/locales/editor.es.json +18 -10
  84. data/app/frontend/editor/locales/editor.fr.json +28 -13
  85. data/app/frontend/editor/locales/editor.pt-BR.json +18 -10
  86. data/app/frontend/editor/locales/index.js +3 -0
  87. data/app/frontend/editor/mixins/error-modal.js +7 -2
  88. data/app/frontend/editor/mixins/preview-transformation.js +0 -1
  89. data/app/frontend/editor/plugins/i18n.js +2 -17
  90. data/app/frontend/editor/services/__tests__/section.spec.js +6 -0
  91. data/app/frontend/editor/services/collection-item.js +8 -0
  92. data/app/frontend/editor/services/page.js +1 -1
  93. data/app/frontend/editor/services/section.js +30 -0
  94. data/app/frontend/editor/services/site.js +4 -4
  95. data/app/frontend/editor/services/theme.js +1 -0
  96. data/app/frontend/editor/spec/__mocks__/page.js +3 -1
  97. data/app/frontend/editor/spec/__mocks__/services.js +1 -0
  98. data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
  99. data/app/frontend/editor/store/actions/index.js +1 -0
  100. data/app/frontend/editor/store/actions/site.js +8 -4
  101. data/app/frontend/editor/store/getters.js +11 -0
  102. data/app/frontend/editor/views/page-preview.vue +2 -1
  103. data/app/frontend/images/favicon.svg +11 -0
  104. data/app/frontend/images/logo.svg +14 -0
  105. data/app/frontend/live-preview-client/iframe-decorator.js +17 -15
  106. data/app/frontend/live-preview-client/rails.js +1 -1
  107. data/app/helpers/maglev/application_helper.rb +7 -2
  108. data/app/helpers/maglev/editor_helper.rb +12 -5
  109. data/app/models/concerns/maglev/sections_concern.rb +4 -0
  110. data/app/models/maglev/asset.rb +13 -14
  111. data/app/models/maglev/page/path_concern.rb +23 -8
  112. data/app/models/maglev/page.rb +17 -18
  113. data/app/models/maglev/page_path.rb +18 -18
  114. data/app/models/maglev/section/setting.rb +1 -1
  115. data/app/models/maglev/section.rb +18 -9
  116. data/app/models/maglev/setting_types/link.rb +1 -1
  117. data/app/models/maglev/site/locales_concern.rb +1 -1
  118. data/app/models/maglev/site.rb +13 -24
  119. data/app/services/maglev/app_container.rb +2 -2
  120. data/app/services/maglev/extract_locale.rb +3 -2
  121. data/app/services/maglev/fetch_page.rb +2 -0
  122. data/app/services/maglev/fetch_theme_layout.rb +2 -0
  123. data/app/services/maglev/get_page_fullpath.rb +1 -2
  124. data/app/services/maglev/remove_section_type.rb +50 -0
  125. data/app/services/maglev/rename_section_type.rb +57 -0
  126. data/app/services/maglev/search_pages.rb +2 -1
  127. data/app/views/layouts/maglev/admin/application.html.erb +1 -1
  128. data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
  129. data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
  130. data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
  131. data/app/views/maglev/editor/show.html.erb +6 -5
  132. data/config/locales/activerecord.ar.yml +13 -0
  133. data/config/locales/activerecord.en.yml +8 -2
  134. data/config/locales/activerecord.es.yml +4 -0
  135. data/config/locales/activerecord.fr.yml +6 -2
  136. data/config/locales/activerecord.pt-BR.yml +13 -0
  137. data/config/routes.rb +4 -5
  138. data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
  139. data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
  140. data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
  141. data/db/migrate/20211013210954_translate_section_content.rb +16 -2
  142. data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
  143. data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
  144. data/lib/commands/maglev/change_site_locales_command.rb +61 -0
  145. data/lib/commands/maglev/create_site_command.rb +28 -0
  146. data/lib/commands/maglev/sections/remove_command.rb +48 -0
  147. data/lib/commands/maglev/sections/rename_command.rb +49 -0
  148. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
  149. data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
  150. data/lib/maglev/errors.rb +1 -0
  151. data/lib/maglev/preview_constraint.rb +6 -2
  152. data/lib/maglev/theme_filesystem_loader.rb +7 -0
  153. data/lib/maglev/version.rb +1 -1
  154. data/lib/maglev.rb +13 -3
  155. data/lib/tasks/maglev/icons.rake +123 -0
  156. data/lib/tasks/maglev/vite.rake +62 -0
  157. data/lib/tasks/maglev_tasks.rake +9 -112
  158. data/tailwind.config.js +2 -1
  159. metadata +54 -35
  160. data/app/controllers/maglev/assets_controller.rb +0 -10
  161. data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
  162. data/app/frontend/editor/components/sidebar-nav.vue +0 -125
  163. data/app/frontend/images/favicon.png +0 -0
  164. data/app/frontend/images/logo.png +0 -0
@@ -1,28 +1,19 @@
1
1
  <template>
2
2
  <div>
3
- <transition name="fade" mode="out-in">
4
- <div key="list-placeholder" v-if="isLoading">
5
- <div class="flex flex-col items-center w-full animate-pulse">
6
- <div class="h-12 bg-gray-200 rounded w-full mb-3"></div>
7
- <div class="h-12 bg-gray-200 rounded w-full mb-3"></div>
8
- <div class="h-12 bg-gray-200 rounded w-full mb-3"></div>
9
- </div>
10
- </div>
11
- <div key="empty-list" class="pt-4 text-center" v-else-if="isEmpty">
12
- No pages found
13
- </div>
14
- <div key="list" v-else>
15
- <list-item
16
- v-for="page in previewablePages"
17
- :key="page.id"
18
- :page="page"
19
- @on-update="fetch"
20
- @on-clone="fetch"
21
- @on-delete="fetch"
22
- @on-dropdown-toggle="onDropdownToggle"
23
- />
24
- </div>
25
- </transition>
3
+ <div key="empty-list" class="pt-4 text-center" v-if="isEmpty">
4
+ No pages found
5
+ </div>
6
+ <div key="list" v-else>
7
+ <list-item
8
+ v-for="page in previewablePages"
9
+ :key="page.id"
10
+ :page="page"
11
+ @on-update="fetch"
12
+ @on-clone="fetch"
13
+ @on-delete="fetch"
14
+ @on-dropdown-toggle="onDropdownToggle"
15
+ />
16
+ </div>
26
17
  </div>
27
18
  </template>
28
19
 
@@ -42,7 +33,7 @@ export default {
42
33
  },
43
34
  computed: {
44
35
  isEmpty() {
45
- return this.previewablePages.length === 0
36
+ return this.previewablePages.length === 0 && !this.isLoading
46
37
  },
47
38
  previewablePages() {
48
39
  return this.pages.filter((page) => !!page.previewUrl && !page.static)
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div
3
- class="flex items-center py-3 pl-6 pr-2 hover:bg-editor-primary hover:bg-opacity-5 transition-colors duration-200"
3
+ class="flex items-center pl-6 pr-2 hover:bg-editor-primary hover:bg-opacity-5 transition-colors duration-200"
4
4
  >
5
5
  <router-link
6
6
  :to="{ name: 'editPage', params: { pageId: page.path } }"
7
- class="flex flex-grow items-center text-gray-800"
7
+ class="flex items-center text-gray-800 overflow-hidden w-full py-3.5"
8
+ :title="page.title"
8
9
  >
9
- <uikit-page-icon :page="page" />
10
- <span class="ml-4">{{ page.title }}</span>
10
+ <uikit-page-icon :page="page" class="shrink-0" />
11
+ <span class="ml-4 truncate">{{ page.title }}</span>
11
12
  <uikit-icon
12
13
  class="ml-4 text-gray-400"
13
14
  name="ri-eye-off-line"
@@ -16,72 +17,20 @@
16
17
  />
17
18
  </router-link>
18
19
  <div class="ml-auto pr-2 relative">
19
- <uikit-dropdown v-on="$listeners">
20
- <template v-slot:button>
21
- <button
22
- class="px-1 py-1 rounded-full bg-editor-primary bg-opacity-0 hover:text-gray-900 text-gray-600 focus:outline-none hover:bg-opacity-10 transition-colors duration-200"
23
- >
24
- <uikit-icon name="ri-more-2-fill" size="1.25rem" />
25
- </button>
26
- </template>
27
- <template v-slot:content>
28
- <div class="flex flex-col w-48 text-gray-800">
29
- <button
30
- class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
31
- @click.stop="openEditPageModal"
32
- >
33
- <uikit-icon name="ri-settings-5-line" />
34
- <span class="ml-2 whitespace-nowrap">{{
35
- $t('page.list.item.edit')
36
- }}</span>
37
- </button>
38
- <button
39
- class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
40
- @click.stop="clonePage"
41
- >
42
- <uikit-icon name="ri-file-copy-line" />
43
- <span class="ml-2">{{ $t('page.list.item.clone') }}</span>
44
- </button>
45
- <button
46
- class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
47
- @click.stop="hidePage"
48
- v-if="isVisible"
49
- >
50
- <uikit-icon name="ri-eye-off-line" />
51
- <span class="ml-2">{{ $t('page.list.item.hide') }}</span>
52
- </button>
53
- <button
54
- class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
55
- @click.stop="showPage"
56
- v-if="!isVisible"
57
- >
58
- <uikit-icon name="ri-eye-line" />
59
- <span class="ml-2">{{ $t('page.list.item.show') }}</span>
60
- </button>
61
- <uikit-confirmation-button
62
- @confirm="deletePage"
63
- ref="deleteDropdown"
64
- v-if="!isIndexPage"
65
- >
66
- <button
67
- class="flex items-center w-full px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
68
- >
69
- <uikit-icon name="delete-bin-line" />
70
- <span class="ml-2">{{ $t('page.list.item.delete') }}</span>
71
- </button>
72
- </uikit-confirmation-button>
73
- </div>
74
- </template>
75
- </uikit-dropdown>
20
+ <page-actions-button :page="page" v-on="$listeners" />
76
21
  </div>
77
22
  </div>
78
23
  </template>
79
24
 
80
25
  <script>
81
26
  import EditPageModal from '@/components/page/edit.vue'
27
+ import PageActionsButton from './actions-button.vue'
82
28
 
83
29
  export default {
84
30
  name: 'PageListItem',
31
+ components: {
32
+ PageActionsButton,
33
+ },
85
34
  props: {
86
35
  page: { type: Object, required: true },
87
36
  },
@@ -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>