maglevcms 1.2.2 → 1.3.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.
- checksums.yaml +4 -4
- data/app/components/maglev/content/builder.rb +3 -1
- data/app/components/maglev/content/checkbox.rb +7 -1
- data/app/components/maglev/content/void.rb +11 -0
- data/app/controllers/concerns/maglev/fetchers_concern.rb +2 -1
- data/app/controllers/concerns/maglev/rendering_concern.rb +0 -2
- data/app/controllers/maglev/editor_controller.rb +4 -0
- data/app/frontend/editor/components/dynamic-form/dynamic-input.vue +30 -10
- data/app/frontend/editor/components/dynamic-form/index.vue +2 -0
- data/app/frontend/editor/components/kit/color-input/core-input.vue +1 -1
- data/app/frontend/editor/components/kit/color-input.vue +1 -1
- data/app/frontend/editor/components/kit/divider.vue +22 -0
- data/app/frontend/editor/components/kit/hint.vue +12 -0
- data/app/frontend/editor/components/kit/index.js +4 -0
- data/app/frontend/editor/components/page/form/seo.vue +1 -1
- data/app/frontend/editor/components/section-block-pane/setting-list.vue +4 -0
- data/app/frontend/editor/components/section-highlighter/top-left-actions.vue +7 -1
- data/app/frontend/editor/components/section-list/list-item.vue +6 -1
- data/app/frontend/editor/components/section-pane/index.vue +2 -1
- data/app/frontend/editor/components/section-pane/setting-list.vue +4 -0
- data/app/frontend/editor/components/style-pane/index.vue +4 -0
- data/app/frontend/editor/locales/editor.fr.json +8 -2
- data/app/frontend/editor/mixins/global.js +16 -0
- data/app/frontend/editor/router/index.js +1 -2
- data/app/frontend/editor/views/content-pane.vue +2 -2
- data/app/models/concerns/maglev/translatable.rb +4 -0
- data/app/models/maglev/page.rb +6 -0
- data/app/models/maglev/section/setting.rb +1 -1
- data/app/models/maglev/site/locales_concern.rb +7 -0
- data/app/models/maglev/site.rb +4 -0
- data/app/models/maglev/theme/style_setting.rb +1 -1
- data/app/services/maglev/add_site_locale.rb +64 -0
- data/app/services/maglev/app_container.rb +1 -0
- data/app/services/maglev/change_site_locales.rb +1 -1
- data/app/services/maglev/fetch_page.rb +7 -1
- data/app/services/maglev/fetch_section_screenshot_path.rb +1 -1
- data/app/services/maglev/get_page_fullpath.rb +5 -1
- data/lib/maglev/engine.rb +6 -2
- data/lib/maglev/preview_constraint.rb +15 -1
- data/lib/maglev/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c3b9ff3b96ced9c9f3942ea0c0f26dfc29f9d82bccdb5e6077eea9cf0538c0a
|
4
|
+
data.tar.gz: 6973bba556d750a5e8caa6f80be7b57426cf4db7a919177e510c0d356645ee7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7f5ba949b5a13eb50b9d3b4fcf042fad60766decb28d8ff16ee076cf4135264eac84ec6348776913d72c3f1bbd72b751576b7445db2f88b0a86e6d5165caf30
|
7
|
+
data.tar.gz: 783b36620b0733ca8387fefd08274f63a2e6c98b65e370572dc6f49f7d7db3c6ad6219c9d0f25a34027db00ca66a684963921777e0a2553fc55af3f121388468
|
@@ -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
|
-
!!
|
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
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<template>
|
2
2
|
<div>
|
3
3
|
<text-input
|
4
|
-
: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="
|
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="
|
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="
|
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="
|
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="
|
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="
|
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="
|
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="
|
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="
|
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) {
|
@@ -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>
|
@@ -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)
|
@@ -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>{{
|
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>{{
|
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
|
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
|
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",
|
@@ -16,9 +16,15 @@ Vue.mixin({
|
|
16
16
|
currentSite() {
|
17
17
|
return this.$store.state.site
|
18
18
|
},
|
19
|
+
currentI18nScope() {
|
20
|
+
return `themes.${this.currentTheme.id}`
|
21
|
+
},
|
19
22
|
currentStyle() {
|
20
23
|
return this.$store.state.style
|
21
24
|
},
|
25
|
+
currentStyleI18nScope() {
|
26
|
+
return `${this.currentI18nScope}.style`
|
27
|
+
},
|
22
28
|
currentLocale() {
|
23
29
|
return this.$store.state.locale
|
24
30
|
},
|
@@ -34,6 +40,9 @@ Vue.mixin({
|
|
34
40
|
currentSection() {
|
35
41
|
return this.$store.state.section
|
36
42
|
},
|
43
|
+
currentSectionI18nScope() {
|
44
|
+
return `${this.currentI18nScope}.sections.${this.currentSection.type}`
|
45
|
+
},
|
37
46
|
currentSectionList() {
|
38
47
|
return this.$store.getters.sectionList
|
39
48
|
},
|
@@ -55,6 +64,9 @@ Vue.mixin({
|
|
55
64
|
currentSectionBlock() {
|
56
65
|
return this.$store.state.sectionBlock
|
57
66
|
},
|
67
|
+
currentSectionBlockI18nScope() {
|
68
|
+
return `${this.currentSectionI18nScope}.blocks.${this.currentSectionBlock.type}`
|
69
|
+
},
|
58
70
|
currentSectionBlockIndex() {
|
59
71
|
return this.$store.getters.sectionBlockIndex
|
60
72
|
},
|
@@ -120,5 +132,9 @@ Vue.mixin({
|
|
120
132
|
closeModal() {
|
121
133
|
ModalBus.$emit('close')
|
122
134
|
},
|
135
|
+
$st(key) {
|
136
|
+
// console.log('$st', key, this.$te(key) ? this.$t(key) : null)
|
137
|
+
return this.$te(key) ? this.$t(key) : null
|
138
|
+
}
|
123
139
|
},
|
124
140
|
})
|
@@ -15,7 +15,7 @@ const router = new VueRouter({
|
|
15
15
|
router.beforeEach((to, from, next) => {
|
16
16
|
// The router hasn't found a component to display so get back
|
17
17
|
// to the screen without any UI drawer opened.
|
18
|
-
if (to.matched.length === 0)
|
18
|
+
if (to.matched.length === 0)
|
19
19
|
return next({
|
20
20
|
name: 'editPage',
|
21
21
|
params: {
|
@@ -23,7 +23,6 @@ router.beforeEach((to, from, next) => {
|
|
23
23
|
locale: store.state.locale,
|
24
24
|
},
|
25
25
|
})
|
26
|
-
}
|
27
26
|
|
28
27
|
// When an user wants to edit another page or to edit the current page
|
29
28
|
// in a different locale, the router detects it and dispatch the new
|
@@ -53,10 +53,10 @@ export default {
|
|
53
53
|
else return null
|
54
54
|
},
|
55
55
|
sectionTitle() {
|
56
|
-
return this.currentSectionDefinition?.name
|
56
|
+
return this.$st(`${this.currentSectionI18nScope}.name`) || this.currentSectionDefinition?.name
|
57
57
|
},
|
58
58
|
sectionBlockTitle() {
|
59
|
-
return (
|
59
|
+
return this.$st(`${this.currentSectionI18nScope}.blocks.label`) || (
|
60
60
|
this.currentSectionBlockDefinition?.name +
|
61
61
|
' ' +
|
62
62
|
`#${this.currentSectionBlockIndex}`
|
@@ -11,6 +11,10 @@ module Maglev
|
|
11
11
|
public_send("#{attr}_translations")
|
12
12
|
end
|
13
13
|
|
14
|
+
def translate_attr_in(attr, locale, source_locale)
|
15
|
+
translations_for(attr)[locale.to_s] ||= translations_for(attr)[source_locale.to_s]
|
16
|
+
end
|
17
|
+
|
14
18
|
class_methods do
|
15
19
|
def order_by_translated(attr, direction)
|
16
20
|
order(Arel.sql("#{attr}_translations->>'#{Maglev::I18n.current_locale}'") => direction)
|
data/app/models/maglev/page.rb
CHANGED
@@ -34,5 +34,11 @@ module Maglev
|
|
34
34
|
def static?
|
35
35
|
false
|
36
36
|
end
|
37
|
+
|
38
|
+
def translate_in(locale, source_locale)
|
39
|
+
%i[title sections seo_title meta_description og_title og_description og_image_url].each do |attr|
|
40
|
+
translate_attr_in(attr, locale, source_locale)
|
41
|
+
end
|
42
|
+
end
|
37
43
|
end
|
38
44
|
end
|
@@ -10,7 +10,7 @@ class Maglev::Section::Setting
|
|
10
10
|
|
11
11
|
## validations ##
|
12
12
|
validates :id, :label, :type, :default, 'maglev/presence': true
|
13
|
-
validates :type, inclusion: { in: %w[text image checkbox link color select collection_item icon] }
|
13
|
+
validates :type, inclusion: { in: %w[text image checkbox link color select collection_item icon divider hint] }
|
14
14
|
|
15
15
|
## methods ##
|
16
16
|
|
@@ -12,6 +12,13 @@ module Maglev::Site::LocalesConcern
|
|
12
12
|
validates :locales, 'maglev/collection': true, length: { minimum: 1 }
|
13
13
|
end
|
14
14
|
|
15
|
+
def add_locale(locale)
|
16
|
+
return nil if locale_prefixes.include?(locale.prefix.to_sym)
|
17
|
+
|
18
|
+
locales << locale
|
19
|
+
locales
|
20
|
+
end
|
21
|
+
|
15
22
|
def default_locale
|
16
23
|
locales.first
|
17
24
|
end
|
data/app/models/maglev/site.rb
CHANGED
@@ -10,7 +10,7 @@ class Maglev::Theme::StyleSetting
|
|
10
10
|
|
11
11
|
## validations ##
|
12
12
|
validates :id, :label, :type, :default, 'maglev/presence': true
|
13
|
-
validates :type, inclusion: { in: %w[text color select checkbox] }
|
13
|
+
validates :type, inclusion: { in: %w[text color select checkbox divider hint] }
|
14
14
|
|
15
15
|
## methods ##
|
16
16
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maglev
|
4
|
+
# Add a locale (instace of Maglev::Site::Locale) to a site
|
5
|
+
class AddSiteLocale
|
6
|
+
include Injectable
|
7
|
+
|
8
|
+
argument :site
|
9
|
+
argument :locale
|
10
|
+
|
11
|
+
def call
|
12
|
+
return if locale.blank? || !site.add_locale(locale)
|
13
|
+
|
14
|
+
ActiveRecord::Base.transaction do
|
15
|
+
unsafe_call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def unsafe_call
|
22
|
+
# Set a default content for site_scoped sections
|
23
|
+
apply_to_site
|
24
|
+
|
25
|
+
site.save! # persist the new locale
|
26
|
+
|
27
|
+
# add a default content in the new locale to all the pages of the site
|
28
|
+
# based on the default locale. Also take care of the path.
|
29
|
+
apply_to_pages
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def apply_to_site
|
35
|
+
Maglev::I18n.with_locale(locale.prefix) do
|
36
|
+
site.translate_in(locale.prefix, site.default_locale_prefix)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def apply_to_pages
|
41
|
+
resources.find_each do |page|
|
42
|
+
# the path will be the same as in the default locale
|
43
|
+
Maglev::I18n.with_locale(locale.prefix) do
|
44
|
+
page.path = default_page_path(page)
|
45
|
+
end
|
46
|
+
|
47
|
+
# set a default content which will be the same as in the default locale
|
48
|
+
page.translate_in(locale.prefix, site.default_locale_prefix)
|
49
|
+
|
50
|
+
page.save!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_page_path(page)
|
55
|
+
# we can't rely on page.default_path because the service can be called outside a HTTP request
|
56
|
+
# and so we don't know for sure if Maglev::I18n.default_locale has been correctly set.
|
57
|
+
page.paths.find_by(locale: site.default_locale.prefix)&.value
|
58
|
+
end
|
59
|
+
|
60
|
+
def resources
|
61
|
+
::Maglev::Page
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -31,6 +31,7 @@ module Maglev
|
|
31
31
|
dependency :persist_section_screenshot, class: Maglev::PersistSectionScreenshot,
|
32
32
|
depends_on: %i[fetch_theme fetch_section_screenshot_path]
|
33
33
|
|
34
|
+
dependency :add_site_locale, class: Maglev::AddSiteLocale
|
34
35
|
dependency :change_site_locales, class: Maglev::ChangeSiteLocales
|
35
36
|
|
36
37
|
dependency :fetch_page, class: Maglev::FetchPage, depends_on: :fetch_site
|
@@ -10,6 +10,7 @@ module Maglev
|
|
10
10
|
argument :locale
|
11
11
|
argument :default_locale
|
12
12
|
argument :fallback_to_default_locale, default: false
|
13
|
+
argument :only_visible, default: false
|
13
14
|
|
14
15
|
def call
|
15
16
|
page = fetch_page(path, locale)
|
@@ -20,7 +21,12 @@ module Maglev
|
|
20
21
|
protected
|
21
22
|
|
22
23
|
def fetch_page(path, locale)
|
23
|
-
|
24
|
+
page = pages.by_path(path || 'index', locale).first
|
25
|
+
!only_visible || (page&.visible? && only_visible) ? page : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def pages
|
29
|
+
Maglev::Page
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
@@ -11,7 +11,7 @@ module Maglev
|
|
11
11
|
|
12
12
|
def call
|
13
13
|
path = "#{fetch_sections_path.call(theme: theme)}/#{section.category}/#{section.id}.jpg"
|
14
|
-
absolute ?
|
14
|
+
absolute ? Rails.root.join("public/#{path}").to_s : "/#{path}"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -44,7 +44,7 @@ module Maglev
|
|
44
44
|
|
45
45
|
def build_fullpath(base_url, path)
|
46
46
|
fullpath = [base_url]
|
47
|
-
fullpath.push(locale)
|
47
|
+
fullpath.push(locale) if prefix_by_default_locale?
|
48
48
|
fullpath.push(path) unless path == 'index' # for SEO purpose)
|
49
49
|
fullpath.push(nil) if fullpath == [nil] # avoid "" as the fullpath
|
50
50
|
fullpath.join('/')
|
@@ -54,6 +54,10 @@ module Maglev
|
|
54
54
|
@site ||= fetch_site.call
|
55
55
|
end
|
56
56
|
|
57
|
+
def prefix_by_default_locale?
|
58
|
+
!same_as_default_locale?
|
59
|
+
end
|
60
|
+
|
57
61
|
def same_as_default_locale?
|
58
62
|
locale.to_s == default_locale.to_s
|
59
63
|
end
|
data/lib/maglev/engine.rb
CHANGED
@@ -54,7 +54,9 @@ module Maglev
|
|
54
54
|
urls: ["/#{vite_ruby.config.public_output_dir}"],
|
55
55
|
root: root.join(vite_ruby.config.public_dir),
|
56
56
|
header_rules: [
|
57
|
-
|
57
|
+
# rubocop:disable Style/StringHashKeys
|
58
|
+
[:all, { 'Access-Control-Allow-Origin' => '*' }]
|
59
|
+
# rubocop:enable Style/StringHashKeys
|
58
60
|
]
|
59
61
|
else
|
60
62
|
# mostly when running the application in production behind NGINX or APACHE
|
@@ -63,7 +65,9 @@ module Maglev
|
|
63
65
|
urls: ["/#{vite_ruby.config.public_output_dir}"],
|
64
66
|
root: root.join(vite_ruby.config.public_dir),
|
65
67
|
header_rules: [
|
66
|
-
|
68
|
+
# rubocop:disable Style/StringHashKeys
|
69
|
+
[:all, { 'Access-Control-Allow-Origin' => '*' }]
|
70
|
+
# rubocop:enable Style/StringHashKeys
|
67
71
|
]
|
68
72
|
end
|
69
73
|
end
|
@@ -4,6 +4,8 @@ require 'uri'
|
|
4
4
|
|
5
5
|
module Maglev
|
6
6
|
class PreviewConstraint
|
7
|
+
CRAWLER_USER_AGENTS = /Googlebot|Twitterbot|facebookexternalhit/o.freeze
|
8
|
+
|
7
9
|
attr_reader :preview_host
|
8
10
|
|
9
11
|
def initialize(preview_host: nil)
|
@@ -11,7 +13,7 @@ module Maglev
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def matches?(request)
|
14
|
-
|
16
|
+
(accepted_format?(request) || crawler?(request)) && match_host?(request)
|
15
17
|
end
|
16
18
|
|
17
19
|
protected
|
@@ -21,5 +23,17 @@ module Maglev
|
|
21
23
|
|
22
24
|
URI.parse(Maglev.config.preview_host).host # make sure we get only the host here
|
23
25
|
end
|
26
|
+
|
27
|
+
def accepted_format?(request)
|
28
|
+
%i[html xml].include?(request.format.symbol)
|
29
|
+
end
|
30
|
+
|
31
|
+
def crawler?(request)
|
32
|
+
request.format.symbol.nil? && CRAWLER_USER_AGENTS.match?(request.user_agent)
|
33
|
+
end
|
34
|
+
|
35
|
+
def match_host?(request)
|
36
|
+
!preview_host || preview_host == request.host
|
37
|
+
end
|
24
38
|
end
|
25
39
|
end
|
data/lib/maglev/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maglevcms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Didier Lafforgue
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jbuilder
|
@@ -133,6 +133,7 @@ files:
|
|
133
133
|
- app/components/maglev/content/link.rb
|
134
134
|
- app/components/maglev/content/select.rb
|
135
135
|
- app/components/maglev/content/text.rb
|
136
|
+
- app/components/maglev/content/void.rb
|
136
137
|
- app/components/maglev/page_component.rb
|
137
138
|
- app/components/maglev/section_component.rb
|
138
139
|
- app/components/maglev/tag_helper.rb
|
@@ -551,7 +552,9 @@ files:
|
|
551
552
|
- app/frontend/editor/components/kit/color-input/preset-button.vue
|
552
553
|
- app/frontend/editor/components/kit/color-input/preset-dropdown.vue
|
553
554
|
- app/frontend/editor/components/kit/confirmation-button.vue
|
555
|
+
- app/frontend/editor/components/kit/divider.vue
|
554
556
|
- app/frontend/editor/components/kit/dropdown.vue
|
557
|
+
- app/frontend/editor/components/kit/hint.vue
|
555
558
|
- app/frontend/editor/components/kit/icon-input.vue
|
556
559
|
- app/frontend/editor/components/kit/icon.vue
|
557
560
|
- app/frontend/editor/components/kit/image-input.vue
|
@@ -730,6 +733,7 @@ files:
|
|
730
733
|
- app/services/concerns/maglev/get_page_sections/transform_collection_item_concern.rb
|
731
734
|
- app/services/concerns/maglev/get_page_sections/transform_link_concern.rb
|
732
735
|
- app/services/concerns/maglev/get_page_sections/transform_text_concern.rb
|
736
|
+
- app/services/maglev/add_site_locale.rb
|
733
737
|
- app/services/maglev/app_container.rb
|
734
738
|
- app/services/maglev/change_site_locales.rb
|
735
739
|
- app/services/maglev/clone_page.rb
|
@@ -845,7 +849,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
845
849
|
- !ruby/object:Gem::Version
|
846
850
|
version: '0'
|
847
851
|
requirements: []
|
848
|
-
rubygems_version: 3.
|
852
|
+
rubygems_version: 3.3.26
|
849
853
|
signing_key:
|
850
854
|
specification_version: 4
|
851
855
|
summary: Page builder Ruby on Rails
|