maglevcms 1.2.2 → 1.4.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/maglev/content/builder.rb +3 -1
  3. data/app/components/maglev/content/checkbox.rb +7 -1
  4. data/app/components/maglev/content/void.rb +11 -0
  5. data/app/controllers/concerns/maglev/fetchers_concern.rb +2 -1
  6. data/app/controllers/concerns/maglev/rendering_concern.rb +0 -2
  7. data/app/controllers/maglev/editor_controller.rb +4 -0
  8. data/app/frontend/admin/controllers/screenshot_controller.js +1 -1
  9. data/app/frontend/editor/components/dynamic-form/dynamic-input.vue +30 -10
  10. data/app/frontend/editor/components/dynamic-form/index.vue +2 -0
  11. data/app/frontend/editor/components/kit/color-input/core-input.vue +1 -1
  12. data/app/frontend/editor/components/kit/color-input.vue +1 -1
  13. data/app/frontend/editor/components/kit/divider.vue +22 -0
  14. data/app/frontend/editor/components/kit/hint.vue +12 -0
  15. data/app/frontend/editor/components/kit/index.js +4 -0
  16. data/app/frontend/editor/components/page/form/seo.vue +1 -1
  17. data/app/frontend/editor/components/section-block-pane/setting-list.vue +4 -0
  18. data/app/frontend/editor/components/section-highlighter/top-left-actions.vue +7 -1
  19. data/app/frontend/editor/components/section-list/list-item.vue +6 -1
  20. data/app/frontend/editor/components/section-pane/index.vue +2 -1
  21. data/app/frontend/editor/components/section-pane/setting-list.vue +4 -0
  22. data/app/frontend/editor/components/style-pane/index.vue +4 -0
  23. data/app/frontend/editor/locales/editor.fr.json +8 -2
  24. data/app/frontend/editor/locales/editor.pt-BR.json +257 -0
  25. data/app/frontend/editor/locales/index.js +3 -0
  26. data/app/frontend/editor/mixins/global.js +16 -0
  27. data/app/frontend/editor/router/index.js +1 -2
  28. data/app/frontend/editor/views/content-pane.vue +2 -2
  29. data/app/models/concerns/maglev/sections_concern.rb +17 -13
  30. data/app/models/concerns/maglev/translatable.rb +4 -0
  31. data/app/models/maglev/page.rb +6 -0
  32. data/app/models/maglev/section/setting.rb +22 -25
  33. data/app/models/maglev/setting_types/base.rb +10 -0
  34. data/app/models/maglev/setting_types/checkbox.rb +9 -0
  35. data/app/models/maglev/setting_types/collection_item.rb +13 -0
  36. data/app/models/maglev/setting_types/color.rb +6 -0
  37. data/app/models/maglev/setting_types/divider.rb +9 -0
  38. data/app/models/maglev/setting_types/hint.rb +9 -0
  39. data/app/models/maglev/setting_types/icon.rb +9 -0
  40. data/app/models/maglev/setting_types/image.rb +13 -0
  41. data/app/models/maglev/setting_types/link.rb +13 -0
  42. data/app/models/maglev/setting_types/select.rb +6 -0
  43. data/app/models/maglev/setting_types/text.rb +6 -0
  44. data/app/models/maglev/site/locales_concern.rb +12 -1
  45. data/app/models/maglev/site.rb +4 -0
  46. data/app/models/maglev/theme/style_setting.rb +1 -1
  47. data/app/models/maglev/theme.rb +35 -0
  48. data/app/services/maglev/add_site_locale.rb +64 -0
  49. data/app/services/maglev/app_container.rb +1 -0
  50. data/app/services/maglev/change_site_locales.rb +1 -1
  51. data/app/services/maglev/fetch_page.rb +7 -1
  52. data/app/services/maglev/fetch_section_screenshot_path.rb +1 -1
  53. data/app/services/maglev/get_page_fullpath.rb +5 -1
  54. data/app/services/maglev/persist_page.rb +15 -11
  55. data/lib/maglev/active_storage.rb +2 -2
  56. data/lib/maglev/engine.rb +6 -2
  57. data/lib/maglev/errors.rb +7 -0
  58. data/lib/maglev/preview_constraint.rb +15 -1
  59. data/lib/maglev/version.rb +1 -1
  60. metadata +23 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 248515cbe62cad407de20c6cffc54b333b764b997051916dcc25db54744536ae
4
- data.tar.gz: 1d7b14f11bde0e7efb1eb88b8804dc5028b3d55da858e963e538c92640377b5c
3
+ metadata.gz: 03a2b641a732256c52996191d8b8b5acd6fade31e3da46c7400b4ddd19d572b8
4
+ data.tar.gz: 7707349dac583ee782baf2b5eb6e2b6c4007d0935529c301376a8876a6a985f4
5
5
  SHA512:
6
- metadata.gz: b3409a4c7c5712a9e7bfa0541ca7d683e3ce17ace4a0113f9eaafeb2c3ad0d9a24a5bc07717f6ec2b5942a94539ebb9312a0cf18f3f9c9ec6bf1516e9b560dce
7
- data.tar.gz: a72a1e948ef6d9ad0003c987a745959a2da567ad78d0db890e1b207f45c4a50d3c86629537bec27bd891d6f4e48f333a0cef22b9bfa3c72168ec64b9a3a5966e
6
+ metadata.gz: 48519ed7bfc8b5281297b2996d92a11fa0f9114266e577d05953f507006be5a64d0619ee2cce6c56471c0c4776f3d0706822860f1161163a6ab52ec1e17add3a
7
+ data.tar.gz: ef616f5afbf2f8a25ffdf59cbb75cc72b0276f723ed68eca78bcc8c6bc9781fa54833dd7617a322a1f704807a4b06cf6d4c2e90bcc2058dcf1bd6c3f2a522fec
@@ -11,7 +11,9 @@ module Maglev
11
11
  color: Maglev::Content::Color,
12
12
  select: Maglev::Content::Select,
13
13
  collection_item: Maglev::Content::CollectionItem,
14
- icon: Maglev::Content::Icon
14
+ icon: Maglev::Content::Icon,
15
+ divider: Maglev::Content::Void,
16
+ hint: Maglev::Content::Void
15
17
  }.freeze
16
18
 
17
19
  def build(scope, content, setting)
@@ -4,7 +4,7 @@ module Maglev
4
4
  module Content
5
5
  class Checkbox < Base
6
6
  def true?
7
- !!content
7
+ !!cast_content
8
8
  end
9
9
 
10
10
  def false?
@@ -14,6 +14,12 @@ module Maglev
14
14
  def to_s
15
15
  content
16
16
  end
17
+
18
+ private
19
+
20
+ def cast_content
21
+ @cast_content ||= ActiveModel::Type::Boolean.new.cast(content)
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Content
5
+ class Void < Base
6
+ def value
7
+ ''
8
+ end
9
+ end
10
+ end
11
+ end
@@ -30,7 +30,8 @@ module Maglev
30
30
  path: maglev_page_path_from_params,
31
31
  locale: content_locale,
32
32
  default_locale: default_content_locale,
33
- fallback_to_default_locale: fallback_to_default_locale
33
+ fallback_to_default_locale: fallback_to_default_locale,
34
+ only_visible: maglev_rendering_mode == :live
34
35
  )
35
36
  end
36
37
 
@@ -11,8 +11,6 @@ module Maglev
11
11
  private
12
12
 
13
13
  def render_maglev_page
14
- raise ActionController::UnknownFormat, 'Maglev renders HTML pages only' if request.format != 'html'
15
-
16
14
  fetch_maglev_page_content
17
15
 
18
16
  verify_canonical_path and return
@@ -42,5 +42,9 @@ module Maglev
42
42
  def fallback_to_default_locale
43
43
  true
44
44
  end
45
+
46
+ def maglev_rendering_mode
47
+ :editor
48
+ end
45
49
  end
46
50
  end
@@ -21,7 +21,7 @@ export default class extends Controller {
21
21
  '[data-maglev-dropzone]',
22
22
  )
23
23
  this.sourceTarget.contentWindow
24
- .html2canvas(realSource, { allowTaint: true, logging: true })
24
+ .html2canvas(realSource, { allowTaint: true, logging: true, useCORS: true })
25
25
  .then((canvas) => {
26
26
  this.outputTarget.src = canvas.toDataURL() // for debugging purpose
27
27
  axios
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div>
3
3
  <text-input
4
- :label="setting.label"
4
+ :label="label"
5
5
  :name="setting.id"
6
6
  :isFocused="isFocused"
7
7
  @blur="$emit('blur')"
@@ -13,7 +13,7 @@
13
13
  "
14
14
  />
15
15
  <textarea-input
16
- :label="setting.label"
16
+ :label="label"
17
17
  :name="setting.id"
18
18
  :isFocused="isFocused"
19
19
  :rows="options.nbRows"
@@ -24,7 +24,7 @@
24
24
  "
25
25
  />
26
26
  <rich-text-input
27
- :label="setting.label"
27
+ :label="label"
28
28
  :name="setting.id"
29
29
  :isFocused="isFocused"
30
30
  :lineBreak="options.lineBreak"
@@ -35,27 +35,27 @@
35
35
  v-if="setting.type == 'text' && options.html"
36
36
  />
37
37
  <image-input
38
- :label="setting.label"
38
+ :label="label"
39
39
  :name="setting.id"
40
40
  :isFocused="isFocused"
41
41
  v-model="inputValue"
42
42
  v-if="setting.type == 'image'"
43
43
  />
44
44
  <icon-input
45
- :label="setting.label"
45
+ :label="label"
46
46
  :name="setting.id"
47
47
  :isFocused="isFocused"
48
48
  v-model="inputValue"
49
49
  v-if="setting.type == 'icon'"
50
50
  />
51
51
  <checkbox-input
52
- :label="setting.label"
52
+ :label="label"
53
53
  :name="setting.id"
54
54
  v-model="inputValue"
55
55
  v-if="setting.type == 'checkbox'"
56
56
  />
57
57
  <link-input
58
- :label="setting.label"
58
+ :label="label"
59
59
  :name="setting.id"
60
60
  :isFocused="isFocused"
61
61
  :withText="options.withText"
@@ -63,26 +63,37 @@
63
63
  v-if="setting.type == 'link'"
64
64
  />
65
65
  <color-input
66
- :label="setting.label"
66
+ :label="label"
67
67
  :name="setting.id"
68
68
  v-model="inputValue"
69
69
  :presets="options.presets"
70
70
  v-if="setting.type == 'color'"
71
71
  />
72
72
  <simple-select
73
- :label="setting.label"
73
+ :label="label"
74
74
  :name="setting.id"
75
75
  v-model="inputValue"
76
76
  :selectOptions="options.selectOptions"
77
77
  v-if="setting.type == 'select'"
78
78
  />
79
79
  <collection-item-input
80
- :label="setting.label"
80
+ :label="label"
81
81
  :name="setting.id"
82
82
  v-model="inputValue"
83
83
  :collection-id="options.collectionId"
84
84
  v-if="setting.type == 'collection_item'"
85
85
  />
86
+
87
+ <divider
88
+ :text="label"
89
+ :withHint="options.withHint"
90
+ v-if="setting.type == 'divider'"
91
+ />
92
+
93
+ <hint
94
+ :text="label"
95
+ v-if="setting.type == 'hint'"
96
+ />
86
97
  </div>
87
98
  </template>
88
99
 
@@ -93,8 +104,17 @@ export default {
93
104
  setting: { type: Object, default: () => ({ type: 'text' }) },
94
105
  content: { type: Array, required: true },
95
106
  isFocused: { type: Boolean, default: false },
107
+ i18nScope: { type: String, required: false }
96
108
  },
97
109
  computed: {
110
+ label() {
111
+ // i18n key examples:
112
+ // - themes.simple.style.settings.main_color
113
+ // - themes.simple.sections.navbar_01.settings.title
114
+ const i18nKey = `${this.i18nScope}.${this.setting.id}`
115
+ const translation = !this.isBlank(this.i18nScope) ? this.$st(i18nKey) : null
116
+ return translation || this.setting.label
117
+ },
98
118
  options() {
99
119
  return this.setting.options
100
120
  },
@@ -7,6 +7,7 @@
7
7
  :setting="setting"
8
8
  :content="content"
9
9
  :isFocused="focusedSetting === setting.id"
10
+ :i18nScope="i18nScope"
10
11
  @blur="$emit('blur')"
11
12
  @change="onChange"
12
13
  />
@@ -24,6 +25,7 @@ export default {
24
25
  settings: { type: Array, default: () => [] },
25
26
  content: { type: Array, default: () => [] },
26
27
  focusedSetting: { type: String, default: undefined },
28
+ i18nScope: { type: String, required: false }
27
29
  },
28
30
  methods: {
29
31
  onChange(change) {
@@ -76,7 +76,7 @@ export default {
76
76
  },
77
77
  methods: {
78
78
  updateInput(event) {
79
- var value = event.target.value
79
+ var value = event.target.value.replace('#', '')
80
80
  if (value.length > 0) value = `#${value}`
81
81
  this.$emit('input', value)
82
82
  }
@@ -5,7 +5,7 @@
5
5
  :for="name"
6
6
  v-if="showLabel"
7
7
  >
8
- <span>{{ label }}</span>
8
+ <span class="pr-2">{{ label }}</span>
9
9
  <core-input :presets="presets" v-model="updatableValue" />
10
10
  </label>
11
11
  </div>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div :class="{ 'pt-4': withHint, 'pt-4 pb-2': !withHint }">
3
+ <div class="relative">
4
+ <div class="absolute inset-0 flex items-center" aria-hidden="true">
5
+ <div class="w-full border-t border-gray-200"></div>
6
+ </div>
7
+ <div class="relative flex justify-center">
8
+ <span class="bg-white px-3 leading-6 text-gray-800">{{ text }}</span>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ export default {
16
+ name: 'Divider',
17
+ props: {
18
+ text: { type: String, default: 'Text goes here' },
19
+ withHint: { type: Boolean, default: false }
20
+ }
21
+ }
22
+ </script>
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div class="text-gray-600 text-sm">{{ text }}</div>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'Hint',
8
+ props: {
9
+ text: { type: String, default: 'Hint goes here' },
10
+ }
11
+ }
12
+ </script>
@@ -23,6 +23,8 @@ import ColorInput from './color-input.vue'
23
23
  import SimpleSelect from './simple-select.vue'
24
24
  import CollectionItemInput from './collection-item-input.vue'
25
25
  import ListItemButton from './list-item-button.vue'
26
+ import Divider from './divider.vue'
27
+ import Hint from './hint.vue'
26
28
 
27
29
  Vue.component('icon', Icon)
28
30
  Vue.component('v-popoper', VPopover)
@@ -48,3 +50,5 @@ Vue.component('color-input', ColorInput)
48
50
  Vue.component('simple-select', SimpleSelect)
49
51
  Vue.component('collection-item-input', CollectionItemInput)
50
52
  Vue.component('list-item-button', ListItemButton)
53
+ Vue.component('divider', Divider)
54
+ Vue.component('hint', Hint)
@@ -86,7 +86,7 @@ export default {
86
86
  return { url: this.page.ogImageUrl }
87
87
  },
88
88
  set(ogImage) {
89
- this.$emit('on-change', { ogImageUrl: ogImage.url })
89
+ this.$emit('on-change', { ogImageUrl: ogImage?.url ?? null })
90
90
  },
91
91
  },
92
92
  },
@@ -5,6 +5,7 @@
5
5
  :settings="sectionBlockSettings"
6
6
  :content="currentSectionBlockContent"
7
7
  :focusedSetting="settingId"
8
+ :i18nScope="i18nScope"
8
9
  @blur="onBlur"
9
10
  @change="updateSectionBlockContent"
10
11
  />
@@ -31,6 +32,9 @@ export default {
31
32
  this.currentSectionBlockContent,
32
33
  )
33
34
  },
35
+ i18nScope() {
36
+ return `${this.currentSectionBlockI18nScope}.settings`
37
+ }
34
38
  },
35
39
  methods: {
36
40
  ...mapActions(['updateSectionBlockContent']),
@@ -4,7 +4,7 @@
4
4
  class="bg-editor-primary text-white py-1 px-3 rounded-l-2xl text-xs flex items-center"
5
5
  :class="{ 'rounded-r-2xl': !displayMoveArrows }"
6
6
  >
7
- <span>{{ hoveredSection.name }}</span>
7
+ <span>{{ name }}</span>
8
8
  </div>
9
9
  <button
10
10
  type="button"
@@ -34,6 +34,12 @@ export default {
34
34
  hoveredSection: { type: Object },
35
35
  },
36
36
  computed: {
37
+ name() {
38
+ return this.$st(`${this.currentI18nScope}.sections.${this.sectionType}.name`) || this.hoveredSection.name
39
+ },
40
+ sectionType() {
41
+ return this.hoveredSection.definition.id
42
+ },
37
43
  displayMoveArrows() {
38
44
  return !this.currentSection && !this.hoveredSection.definition.insertAt
39
45
  },
@@ -6,7 +6,7 @@
6
6
  :to="{ name: 'editSection', params: { sectionId: section.id } }"
7
7
  class="flex items-center"
8
8
  >
9
- <span>{{ section.name | truncate(40) }}</span>
9
+ <span>{{ name | truncate(40) }}</span>
10
10
  </router-link>
11
11
  <confirmation-button @confirm="removeSection(section.id)" v-on="$listeners">
12
12
  <button
@@ -26,6 +26,11 @@ export default {
26
26
  props: {
27
27
  section: { type: Object, required: true },
28
28
  },
29
+ computed: {
30
+ name() {
31
+ return this.$st(`${this.currentI18nScope}.sections.${this.section.type}.name`) || this.section.name
32
+ }
33
+ },
29
34
  methods: {
30
35
  ...mapActions(['removeSection']),
31
36
  },
@@ -62,6 +62,7 @@ export default {
62
62
  },
63
63
  blocksLabel() {
64
64
  return (
65
+ this.$st(`${this.currentSectionI18nScope}.blocks.label`) ||
65
66
  this.currentSectionDefinition.blocksLabel ||
66
67
  this.$t('sectionPane.tabs.blocks')
67
68
  )
@@ -70,7 +71,7 @@ export default {
70
71
  return this.currentSectionDefinition.blocksPresentation === 'tree'
71
72
  ? BlockTree
72
73
  : BlockList
73
- },
74
+ }
74
75
  },
75
76
  methods: {
76
77
  findTabIndexFromRoute() {
@@ -5,6 +5,7 @@
5
5
  :settings="sectionSettings"
6
6
  :content="currentSectionContent"
7
7
  :focusedSetting="settingId"
8
+ :i18nScope="i18nScope"
8
9
  @blur="onBlur"
9
10
  @change="updateSectionContent"
10
11
  />
@@ -31,6 +32,9 @@ export default {
31
32
  this.currentSectionContent,
32
33
  )
33
34
  },
35
+ i18nScope() {
36
+ return `${this.currentSectionI18nScope}.settings`
37
+ }
34
38
  },
35
39
  methods: {
36
40
  ...mapActions(['updateSectionContent']),
@@ -5,6 +5,7 @@
5
5
  parentKey="style"
6
6
  :settings="styleSettings"
7
7
  :content="style"
8
+ :i18nScope="i18nScope"
8
9
  @change="onChange"
9
10
  />
10
11
  </div>
@@ -28,6 +29,9 @@ export default {
28
29
  styleSettings() {
29
30
  return this.currentTheme.styleSettings
30
31
  },
32
+ i18nScope() {
33
+ return `${this.currentStyleI18nScope}.settings`
34
+ }
31
35
  },
32
36
  methods: {
33
37
  ...mapActions(['previewStyle']),
@@ -24,6 +24,7 @@
24
24
  "sidebarNav": {
25
25
  "addNewSectionTooltip": "Ajouter une nouvelle section au bas de la page",
26
26
  "managePageSectionsTooltip": "Gérer les sections de la page",
27
+ "editStyleTooltip": "Modifier le style du site",
27
28
  "openImageLibraryTooltip": "Ouvrir la galerie d'images",
28
29
  "leaveEditorTooltip": "Retour vers l'application principale"
29
30
  },
@@ -103,7 +104,7 @@
103
104
  "tabs": {
104
105
  "settings": "Contenu",
105
106
  "blocks": "Blocs",
106
- "advanced": "Paramètres avancés"
107
+ "advanced": "Paramètres"
107
108
  },
108
109
  "blockList": {
109
110
  "add": "Ajouter un nouvel élément"
@@ -112,12 +113,17 @@
112
113
  "sectionBlockPane": {
113
114
  "tabs": {
114
115
  "settings": "Contenu",
115
- "advanced": "Paramètres avancés"
116
+ "advanced": "Paramètres"
116
117
  }
117
118
  },
118
119
  "themeSectionList": {
119
120
  "emptyCategory": "Aucune section"
120
121
  },
122
+ "style": {
123
+ "edit": {
124
+ "title": "Styler site"
125
+ }
126
+ },
121
127
  "imageInput": {
122
128
  "addButton": "Ajouter une image",
123
129
  "replaceButton": "Remplacer l'image",