maglevcms 1.8.0 → 2.0.0

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 (158) 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 +14 -2
  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/live-preview.js +11 -1
  85. data/app/frontend/editor/services/page.js +1 -1
  86. data/app/frontend/editor/services/section.js +30 -0
  87. data/app/frontend/editor/services/site.js +4 -4
  88. data/app/frontend/editor/services/theme.js +1 -0
  89. data/app/frontend/editor/spec/__mocks__/page.js +3 -1
  90. data/app/frontend/editor/spec/__mocks__/services.js +1 -0
  91. data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
  92. data/app/frontend/editor/store/actions/index.js +1 -0
  93. data/app/frontend/editor/store/actions/site.js +8 -4
  94. data/app/frontend/editor/store/getters.js +11 -0
  95. data/app/frontend/editor/views/page-preview.vue +2 -1
  96. data/app/frontend/images/favicon.svg +11 -0
  97. data/app/frontend/images/logo.svg +14 -0
  98. data/app/frontend/live-preview-client/iframe-decorator.js +20 -17
  99. data/app/frontend/live-preview-client/message.js +1 -0
  100. data/app/frontend/live-preview-client/rails.js +12 -2
  101. data/app/helpers/maglev/application_helper.rb +7 -2
  102. data/app/helpers/maglev/editor_helper.rb +12 -5
  103. data/app/models/concerns/maglev/sections_concern.rb +4 -0
  104. data/app/models/maglev/asset.rb +13 -14
  105. data/app/models/maglev/page/path_concern.rb +23 -8
  106. data/app/models/maglev/page.rb +17 -18
  107. data/app/models/maglev/page_path.rb +18 -18
  108. data/app/models/maglev/section/setting.rb +1 -1
  109. data/app/models/maglev/section.rb +18 -9
  110. data/app/models/maglev/setting_types/link.rb +1 -1
  111. data/app/models/maglev/site.rb +13 -24
  112. data/app/services/maglev/app_container.rb +2 -2
  113. data/app/services/maglev/extract_locale.rb +3 -2
  114. data/app/services/maglev/fetch_page.rb +2 -0
  115. data/app/services/maglev/fetch_theme_layout.rb +2 -0
  116. data/app/services/maglev/get_page_fullpath.rb +1 -2
  117. data/app/services/maglev/remove_section_type.rb +50 -0
  118. data/app/services/maglev/rename_section_type.rb +57 -0
  119. data/app/services/maglev/search_pages.rb +2 -1
  120. data/app/views/layouts/maglev/admin/application.html.erb +1 -1
  121. data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
  122. data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
  123. data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
  124. data/app/views/maglev/editor/show.html.erb +6 -5
  125. data/config/locales/activerecord.ar.yml +13 -0
  126. data/config/locales/activerecord.en.yml +8 -2
  127. data/config/locales/activerecord.es.yml +4 -0
  128. data/config/locales/activerecord.fr.yml +6 -2
  129. data/config/locales/activerecord.pt-BR.yml +13 -0
  130. data/config/routes.rb +3 -4
  131. data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
  132. data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
  133. data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
  134. data/db/migrate/20211013210954_translate_section_content.rb +16 -2
  135. data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
  136. data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
  137. data/lib/commands/maglev/change_site_locales_command.rb +61 -0
  138. data/lib/commands/maglev/create_site_command.rb +28 -0
  139. data/lib/commands/maglev/sections/remove_command.rb +48 -0
  140. data/lib/commands/maglev/sections/rename_command.rb +49 -0
  141. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
  142. data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
  143. data/lib/maglev/errors.rb +1 -0
  144. data/lib/maglev/preview_constraint.rb +5 -1
  145. data/lib/maglev/theme_filesystem_loader.rb +7 -0
  146. data/lib/maglev/version.rb +1 -1
  147. data/lib/maglev.rb +13 -3
  148. data/lib/tasks/maglev/icons.rake +123 -0
  149. data/lib/tasks/maglev/vite.rake +62 -0
  150. data/lib/tasks/maglev_tasks.rake +9 -107
  151. data/tailwind.config.js +1 -0
  152. metadata +48 -33
  153. data/app/controllers/maglev/assets_controller.rb +0 -10
  154. data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
  155. data/app/frontend/editor/components/sidebar-nav.vue +0 -125
  156. data/app/frontend/editor/plugins/maglev_dummy.js +0 -2
  157. data/app/frontend/images/favicon.png +0 -0
  158. data/app/frontend/images/logo.png +0 -0
@@ -66,5 +66,14 @@ export default {
66
66
  },
67
67
  },
68
68
  },
69
+ watch: {
70
+ value: {
71
+ async handler(value) {
72
+ if (value === 'any')
73
+ this.selectedCollectionItem = await this.services.collectionItem.findOne(this.collectionId, 'any')
74
+ },
75
+ immediate: true,
76
+ },
77
+ }
69
78
  }
70
79
  </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <button @click.stop.prevent="copyToClipboard">
3
+ <uikit-icon name="clipboard-line" size="0.75rem" v-if="!copied" class="text-gray-500 hover:text-gray-900 transition-colors duration-200" />
4
+ <uikit-icon name="ri-check-line" size="0.75rem" class="text-green-500" v-else />
5
+ </button>
6
+ </template>
7
+
8
+ <script>
9
+ export default {
10
+ name: 'CopyPasteButton',
11
+ props: {
12
+ textToCopy: { type: String, required: true },
13
+ },
14
+ data() {
15
+ return { copied: false }
16
+ },
17
+ methods: {
18
+ copyToClipboard() {
19
+ this.copied = true
20
+ navigator.clipboard.writeText(this.textToCopy)
21
+ const timeout = setTimeout((() => { this.copied = false }).bind(this), 2000)
22
+ this.$once('hook:beforeDestroy', () => clearTimeout(timeout))
23
+ },
24
+ },
25
+ }
26
+ </script>
@@ -6,6 +6,7 @@
6
6
  :placement="placement"
7
7
  class="flex"
8
8
  v-on:auto-hide="close"
9
+ :popoverClass="popoverClass"
9
10
  >
10
11
  <div
11
12
  class="z-10 relative flex items-center focus:outline-none select-none cursor-pointer w-full"
@@ -32,6 +33,10 @@ export default {
32
33
  validator: (value) =>
33
34
  ['top', 'bottom', 'right', 'left'].indexOf(value) !== -1,
34
35
  },
36
+ popoverClass: {
37
+ type: String,
38
+ default: ''
39
+ }
35
40
  },
36
41
  data() {
37
42
  return {
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <button
3
+ class="h-7 w-7 flex items-center justify-center rounded-full focus:outline-none transition-colors duration-200"
4
+ :class="{
5
+ 'bg-gray-600 text-gray-200 hover:bg-gray-900 hover:text-gray-100': dark,
6
+ 'bg-gray-600 bg-opacity-0 hover:text-gray-900 text-gray-800 hover:bg-opacity-10': !dark
7
+ }"
8
+ v-on="$listeners"
9
+ v-bind="$attrs"
10
+ >
11
+ <uikit-icon :name="iconName" size="1.15rem" />
12
+ </button>
13
+ </template>
14
+
15
+ <script>
16
+ export default {
17
+ name: 'UIKitIconButton',
18
+ inheritAttrs: false,
19
+ props: {
20
+ iconName: { type: String, required: true },
21
+ dark: { type: Boolean, default: false },
22
+ },
23
+ }
24
+ </script>
@@ -28,9 +28,8 @@ export default {
28
28
  },
29
29
  computed: {
30
30
  icon() {
31
- return defineAsyncComponent(() =>
32
- this.icons[`../../assets/${this.library}/${this.name}.svg`](),
33
- )
31
+ const path = `../../assets/${this.library}/${this.name}.svg`
32
+ return defineAsyncComponent(() => this.icons[path]())
34
33
  },
35
34
  },
36
35
  }
@@ -8,6 +8,7 @@ import Accordion from './accordion.vue'
8
8
  import Dropdown from './dropdown.vue'
9
9
  import ConfirmationButton from './confirmation-button.vue'
10
10
  import SubmitButton from './submit-button.vue'
11
+ import CopyPasteButton from './copy-paste-button.vue'
11
12
  import ImageInput from './image-input.vue'
12
13
  import IconInput from './icon-input.vue'
13
14
  import LinkInput from './link-input.vue'
@@ -22,9 +23,9 @@ import PageIcon from './page-icon.vue'
22
23
  import ColorInput from './color-input.vue'
23
24
  import SimpleSelect from './simple-select.vue'
24
25
  import CollectionItemInput from './collection-item-input.vue'
25
- import ListItemButton from './list-item-button.vue'
26
26
  import Divider from './divider.vue'
27
27
  import Hint from './hint.vue'
28
+ import IconButton from './icon-button.vue'
28
29
 
29
30
  Vue.component('v-popoper', VPopover)
30
31
  Vue.component('uikit-icon', Icon)
@@ -49,6 +50,7 @@ Vue.component('uikit-page-icon', PageIcon)
49
50
  Vue.component('uikit-color-input', ColorInput)
50
51
  Vue.component('uikit-simple-select', SimpleSelect)
51
52
  Vue.component('uikit-collection-item-input', CollectionItemInput)
52
- Vue.component('uikit-list-item-button', ListItemButton)
53
53
  Vue.component('uikit-divider', Divider)
54
54
  Vue.component('uikit-hint', Hint)
55
+ Vue.component('uikit-icon-button', IconButton)
56
+ Vue.component('uikit-copy-paste-button', CopyPasteButton)
@@ -110,7 +110,7 @@ export default {
110
110
  this.openModal({
111
111
  title: this.$t('linkPicker.title'),
112
112
  component: LinkPicker,
113
- props: { currentLink: this.value },
113
+ props: { currentLink: this.value, modalClass: 'h-144 w-120' },
114
114
  listeners: {
115
115
  select: (link) => this.setLink(link),
116
116
  },
@@ -14,7 +14,7 @@
14
14
  type="button"
15
15
  @click="$emit('on-close', true)"
16
16
  >
17
- <uikit-icon name="ri-close-circle-line" />
17
+ <uikit-icon-button iconName="ri-close-line" class="relative -right-2" />
18
18
  </button>
19
19
  </div>
20
20
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <uikit-icon :name="iconName" />
2
+ <uikit-icon :name="iconName" :size="size" />
3
3
  </template>
4
4
 
5
5
  <script>
@@ -77,7 +77,7 @@ export default {
77
77
  this.openModal({
78
78
  title: this.$t('linkPicker.insertTitle'),
79
79
  component: LinkPicker,
80
- props: { currentLink: this.sanitizeLink(), mode: 'insert' },
80
+ props: { currentLink: this.sanitizeLink(), mode: 'insert', modalClass: 'h-144 w-120' },
81
81
  listeners: {
82
82
  select: (link) => this.setLink(link),
83
83
  },
@@ -45,7 +45,7 @@
45
45
  </div>
46
46
  </button>
47
47
  <div
48
- class="absolute w-full z-10 -mt-1 rounded-b shadow-sm bg-gray-100"
48
+ class="absolute w-full z-20 -mt-1 rounded-b shadow-sm bg-gray-100"
49
49
  @keydown="naviguate"
50
50
  v-if="isOpen"
51
51
  >
@@ -57,6 +57,7 @@
57
57
  v-model="q"
58
58
  :placeholder="searchPlaceholder"
59
59
  ref="input"
60
+ autocomplete="off"
60
61
  />
61
62
  </div>
62
63
 
@@ -64,7 +65,7 @@
64
65
  {{ emptyLabel }}
65
66
  </div>
66
67
 
67
- <div v-if="list">
68
+ <div v-if="list" :class="listClass">
68
69
  <div
69
70
  v-for="(item, index) in list"
70
71
  :key="item.id"
@@ -106,6 +107,7 @@ export default {
106
107
  clearEnabled: { type: Boolean, default: false },
107
108
  withLabel: { type: Boolean, default: true },
108
109
  buttonClass: { type: [Object, String], default: () => ({}) },
110
+ listClass: { type: [Object, String], default: () => ({}) },
109
111
  },
110
112
  data() {
111
113
  return {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="mt-10 flex flex-col">
2
+ <div class="mt-10 flex flex-col px-1">
3
3
  <button
4
4
  class="big-submit-button bg-editor-primary"
5
5
  type="button"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="mt-4">
2
+ <div class="flex flex-col h-full">
3
3
  <uikit-tabs
4
4
  :tabs="tabs"
5
5
  :firstIndex="firstTabIndex"
@@ -8,6 +8,7 @@
8
8
  :emptyLabel="$t(`linkPicker.page.input.emptyLabel`)"
9
9
  :fetchList="(q) => services.page.findAll({ q })"
10
10
  v-model="page"
11
+ listClass="overflow-y-scroll h-48"
11
12
  >
12
13
  <template v-slot:value>
13
14
  {{ page.title }}
@@ -33,6 +34,7 @@
33
34
  :fetchList="() => fetchPageSectionNames()"
34
35
  :clearEnabled="true"
35
36
  v-model="pageSection"
37
+ listClass="overflow-y-scroll h-48"
36
38
  >
37
39
  <template v-slot:value>
38
40
  {{ pageSection.name }}
@@ -11,7 +11,7 @@
11
11
  :label="$t(`page.form.path`)"
12
12
  name="path"
13
13
  v-model="pathInput"
14
- :error="errors.path || errors.pathsValue"
14
+ :error="errors.path || errors['paths.value'] || errors['pathsValue']"
15
15
  v-if="!isPageIndex || !page.id"
16
16
  />
17
17
 
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <uikit-dropdown v-on="$listeners" popoverClass="tooltip-menu" ref="dropdown">
3
+ <template v-slot:button>
4
+ <uikit-icon-button :iconName="compact ? 'ri-more-fill' : 'ri-more-2-fill'" class="shrink-0" />
5
+ </template>
6
+ <template v-slot:content>
7
+ <div class="flex flex-col w-48 text-gray-800">
8
+ <button
9
+ class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
10
+ @click.stop="editPage"
11
+ >
12
+ <uikit-icon name="ri-settings-5-line" />
13
+ <span class="ml-2 whitespace-nowrap">{{
14
+ $t('page.actions.edit')
15
+ }}</span>
16
+ </button>
17
+ <button
18
+ class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
19
+ @click.stop="clonePage"
20
+ >
21
+ <uikit-icon name="ri-shadow-line" />
22
+ <span class="ml-2">{{ $t('page.actions.clone') }}</span>
23
+ </button>
24
+ <button
25
+ class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
26
+ @click.stop="hidePage"
27
+ v-if="!compact &&isVisible"
28
+ >
29
+ <uikit-icon name="ri-eye-off-line" />
30
+ <span class="ml-2">{{ $t('page.actions.hide') }}</span>
31
+ </button>
32
+
33
+ <button
34
+ class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
35
+ @click.stop="showPage"
36
+ v-if="!compact && !isVisible"
37
+ >
38
+ <uikit-icon name="ri-eye-line" />
39
+ <span class="ml-2">{{ $t('page.actions.show') }}</span>
40
+ </button>
41
+
42
+ <button
43
+ class="flex items-center px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
44
+ :class="{ 'text-green-500': copied }"
45
+ @click.stop="copyUrlToClipboard"
46
+ >
47
+ <uikit-icon name="clipboard-line" v-if="!copied" />
48
+ <uikit-icon name="ri-check-line" v-else />
49
+ <span class="ml-2" v-if="!copied">{{ $t('page.actions.copyUrlToClipboard') }}</span>
50
+ <span class="ml-2" v-else>{{ $t('page.actions.copyUrlToClipboardSuccess') }}</span>
51
+ </button>
52
+
53
+ <uikit-confirmation-button
54
+ @confirm="deletePage"
55
+ ref="deleteDropdown"
56
+ v-if="!isIndexPage"
57
+ >
58
+ <button
59
+ class="flex items-center w-full px-4 py-4 hover:bg-gray-100 transition-colors duration-200 focus:outline-none"
60
+ >
61
+ <uikit-icon name="delete-bin-line" />
62
+ <span class="ml-2">{{ $t('page.actions.delete') }}</span>
63
+ </button>
64
+ </uikit-confirmation-button>
65
+ </div>
66
+ </template>
67
+ </uikit-dropdown>
68
+ </template>
69
+
70
+ <script>
71
+ import { mapGetters } from 'vuex'
72
+ import EditPageModal from '@/components/page/edit.vue'
73
+
74
+ export default {
75
+ name: 'PageActionsButton',
76
+ props: {
77
+ page: { type: Object, required: true },
78
+ compact: { type: Boolean, default: false },
79
+ },
80
+ data() {
81
+ return {
82
+ copied: false,
83
+ }
84
+ },
85
+ computed: {
86
+ ...mapGetters(['currentPageUrl']),
87
+ isIndexPage() {
88
+ return this.services.page.isIndex(this.page)
89
+ },
90
+ isCurrentPage() {
91
+ return this.currentPage.id === this.page.id
92
+ },
93
+ isVisible() {
94
+ return this.page.visible
95
+ },
96
+ },
97
+ methods: {
98
+ copyUrlToClipboard() {
99
+ this.copied = true
100
+ navigator.clipboard.writeText(this.currentPageUrl)
101
+ const timeout = setTimeout((() => { this.copied = false }).bind(this), 2000)
102
+ this.$once('hook:beforeDestroy', () => clearTimeout(timeout))
103
+ },
104
+ editPage() {
105
+ this.$refs.dropdown.close()
106
+ if (!this.compact) {
107
+ this.openEditPageModal()
108
+ } else if (this.$route.name !== 'editPageSettings') {
109
+ this.$router.push({ name: 'editPageSettings' })
110
+ }
111
+ },
112
+ openEditPageModal() {
113
+ this.openModal({
114
+ title: this.$t('page.edit.title'),
115
+ component: EditPageModal,
116
+ closeOnClick: false,
117
+ props: {
118
+ page: this.page,
119
+ insideModal: true,
120
+ modalClass: 'w-120 h-144',
121
+ },
122
+ listeners: {
123
+ 'on-update': (editedPage) => this.onUpdate(editedPage),
124
+ },
125
+ })
126
+ },
127
+ onUpdate(editedPage) {
128
+ this.closeDropdown()
129
+ if (this.isCurrentPage) this.setCurrentPageSettings(editedPage)
130
+ this.$emit('on-update')
131
+ },
132
+ clonePage() {
133
+ this.services.page.clone(this.page.id).then(page => this.$emit('on-clone', page))
134
+ },
135
+ showPage() {
136
+ this.services.page
137
+ .setVisible(this.page.id, true)
138
+ .then(() => this.$emit('on-update'))
139
+ },
140
+ hidePage() {
141
+ this.services.page
142
+ .setVisible(this.page.id, false)
143
+ .then(() => this.$emit('on-update'))
144
+ },
145
+ deletePage() {
146
+ this.closeDropdown()
147
+ this.services.page.destroy(this.page.id).then(() => {
148
+ this.$emit('on-delete')
149
+ if (this.isCurrentPage)
150
+ this.$router.push({ name: 'editPage', params: { pageId: 'index' } })
151
+ })
152
+ },
153
+ closeDropdown() {
154
+ if (!this.$refs.deleteDropdown) return
155
+ this.$refs.deleteDropdown.close()
156
+ },
157
+ }
158
+ }
159
+ </script>
@@ -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
  },