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,6 +1,6 @@
1
1
  <template>
2
2
  <a
3
- :href="currentPage.previewUrl"
3
+ :href="currentPageUrl"
4
4
  target="_blank"
5
5
  class="px-6 flex items-center hover:bg-editor-primary/5 transition-colors duration-200"
6
6
  >
@@ -11,5 +11,10 @@
11
11
  <script>
12
12
  export default {
13
13
  name: 'PreviewButton',
14
+ computed: {
15
+ currentPageUrl() {
16
+ return this.currentPage.liveUrl
17
+ },
18
+ },
14
19
  }
15
20
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="h-full py-4 w-px">
2
+ <div class="h-full py-4 w-px shrink-0">
3
3
  <div class="h-full w-full bg-gray-300" />
4
4
  </div>
5
5
  </template>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div>
3
- <div class="flex items-center" v-if="!hasNoImagesYet">
2
+ <div class="h-156 flex flex-col space-y-5">
3
+ <div class="flex items-center h-10" v-if="!hasNoImagesYet">
4
4
  <image-uploader
5
5
  :multiple="true"
6
6
  @uploaded="refresh"
@@ -12,34 +12,31 @@
12
12
  @search="search"
13
13
  />
14
14
  </div>
15
- <div class="mt-5">
16
- <div
17
- class="overflow-y-auto h-128"
18
- :class="{ invisible: images === null }"
19
- >
20
- <div class="mt-10 flex flex-col items-center" v-if="hasNoImagesYet">
21
- <p class="text-center">{{ $t('imageLibrary.none') }}</p>
22
- <image-uploader @uploaded="refresh" :multiple="true" class="mt-4" />
23
- </div>
24
- <transition :name="slideDirection" mode="out-in" v-else>
25
- <image-list
26
- :images="images"
27
- :key="activePage"
28
- :pickerMode="pickerMode"
29
- v-on="$listeners"
30
- @destroy="destroyImage"
31
- />
32
- </transition>
15
+ <div class="grow overflow-y-auto">
16
+ <div class="mt-10 flex flex-col items-center" v-if="hasNoImagesYet">
17
+ <p class="text-center">{{ $t('imageLibrary.none') }}</p>
18
+ <image-uploader @uploaded="refresh" :multiple="true" class="mt-4" />
33
19
  </div>
34
- <uikit-pagination
35
- labelI18nKey="imageLibrary.pagination.label"
36
- noItemsI18nKey="imageLibrary.pagination.noItems"
37
- :activePage="activePage"
38
- :totalItems="totalItems"
39
- :perPage="perPage"
40
- @change="(page) => (this.activePage = page)"
41
- />
20
+ <transition :name="slideDirection" mode="out-in" v-else>
21
+ <image-list
22
+ :images="images"
23
+ :key="activePage"
24
+ :pickerMode="pickerMode"
25
+ v-on="$listeners"
26
+ @destroy="destroyImage"
27
+ />
28
+ </transition>
42
29
  </div>
30
+ <uikit-pagination
31
+ class="shrink-0"
32
+ labelI18nKey="imageLibrary.pagination.label"
33
+ noItemsI18nKey=""
34
+ :activePage="activePage"
35
+ :totalItems="totalItems"
36
+ :perPage="perPage"
37
+ @change="(page) => (this.activePage = page)"
38
+ v-if="!hasNoImagesYet"
39
+ />
43
40
  </div>
44
41
  </template>
45
42
 
@@ -70,7 +67,7 @@ export default {
70
67
  },
71
68
  computed: {
72
69
  hasNoImagesYet() {
73
- return this.isBlank(this.images) && this.query === null && !this.loading
70
+ return this.isBlank(this.images) && this.query === null
74
71
  },
75
72
  },
76
73
  methods: {
@@ -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>
@@ -0,0 +1,43 @@
1
+ import { Mark } from 'tiptap'
2
+ import { toggleMark, markInputRule, markPasteRule } from 'tiptap-commands'
3
+
4
+ export default class Superscript extends Mark {
5
+
6
+ get name() {
7
+ return 'sup'
8
+ }
9
+
10
+ get schema() {
11
+ return {
12
+ parseDOM: [
13
+ {
14
+ tag: 'sup',
15
+ },
16
+ ],
17
+ toDOM: () => ['sup', 0],
18
+ }
19
+ }
20
+
21
+ keys({ type }) {
22
+ return {
23
+ 'Mod-^': toggleMark(type),
24
+ }
25
+ }
26
+
27
+ commands({ type }) {
28
+ return () => toggleMark(type)
29
+ }
30
+
31
+ // NOTE: "^^" are used as delimiters to trigger the superscript formatting
32
+ inputRules({ type }) {
33
+ return [
34
+ markInputRule(/(?:\^\^|__)([^\^*_]+)(?:\^\^|__)$/, type),
35
+ ]
36
+ }
37
+
38
+ pasteRules({ type }) {
39
+ return [
40
+ markPasteRule(/(?:\^\^|__)([^\^*_]+)(?:\^\^|__)/g, type),
41
+ ]
42
+ }
43
+ }
@@ -22,6 +22,13 @@
22
22
  :isActive="isActive.strike()"
23
23
  @click="commands.strike"
24
24
  />
25
+ <editor-menu-button
26
+ iconName="format-superscript"
27
+ class="rounded-r-sm"
28
+ :isActive="isActive.sup()"
29
+ @click="commands.sup"
30
+ v-if="extraExtensions.superscript"
31
+ />
25
32
  </div>
26
33
  </template>
27
34
 
@@ -34,6 +41,7 @@ export default {
34
41
  props: {
35
42
  commands: { type: Object, required: true },
36
43
  isActive: { type: Object, required: true },
37
- },
44
+ extraExtensions: { type: Object, default: () => ({ table: false, superscript: false }) },
45
+ }
38
46
  }
39
47
  </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
  },
@@ -9,7 +9,7 @@
9
9
  </div>
10
10
  <div class="mt-1">
11
11
  <editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
12
- <div class="flex sticky top-0 z-10 pb-2 bg-white space-x-1">
12
+ <div class="flex sticky top-0 z-10 pb-2 bg-white space-x-1 overflow-x-auto">
13
13
  <editor-block-button
14
14
  :commands="commands"
15
15
  :isActive="isActive"
@@ -17,7 +17,11 @@
17
17
  v-if="!lineBreak"
18
18
  />
19
19
 
20
- <editor-format-buttons :commands="commands" :isActive="isActive" />
20
+ <editor-format-buttons
21
+ :commands="commands"
22
+ :isActive="isActive"
23
+ :extraExtensions="extraExtensions"
24
+ />
21
25
 
22
26
  <editor-list-buttons
23
27
  :commands="commands"
@@ -35,7 +39,7 @@
35
39
  :commands="commands"
36
40
  :isActive="isActive"
37
41
  class="relative"
38
- v-if="!lineBreak && table"
42
+ v-if="!lineBreak && extraExtensions.table"
39
43
  />
40
44
  </div>
41
45
  </editor-menu-bar>
@@ -69,11 +73,12 @@ import {
69
73
  Table,
70
74
  TableHeader,
71
75
  TableCell,
72
- TableRow,
76
+ TableRow
73
77
  } from 'tiptap-extensions'
74
78
  import Doc from './rich-text-input/extensions/Doc'
75
79
  import LineBreak from './rich-text-input/extensions/LineBreak'
76
80
  import Link from './rich-text-input/extensions/marks/Link'
81
+ import Superscript from './rich-text-input/extensions/marks/Superscript'
77
82
  import EditorBlockButton from './rich-text-input/block-button.vue'
78
83
  import EditorFormatButtons from './rich-text-input/format-buttons.vue'
79
84
  import EditorListButtons from './rich-text-input/list-buttons.vue'
@@ -98,7 +103,7 @@ export default {
98
103
  value: { type: String },
99
104
  lineBreak: { type: Boolean, default: false },
100
105
  rows: { type: Number, default: 2 },
101
- table: { type: Boolean, default: false },
106
+ extraExtensions: { type: Object, default: () => ({ table: false, superscript: false }) },
102
107
  },
103
108
  data() {
104
109
  return { editor: null }
@@ -151,6 +156,7 @@ export default {
151
156
  new Underline(),
152
157
  new Strike(),
153
158
  new Link({ openOnClick: false, target: null }),
159
+ new Superscript(),
154
160
  new History(),
155
161
  ]
156
162
  },
@@ -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>