maglevcms 3.0.1 → 3.0.3
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/assets/builds/maglev/tailwind.css +3 -27
- data/app/assets/javascripts/maglev/client/dom-operations.js +9 -1
- data/app/assets/javascripts/maglev/editor/controllers/app/forms/section_form_controller.js +31 -6
- data/app/assets/javascripts/maglev/editor/controllers/app/page_preview_controller.js +20 -6
- data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js +10 -4
- data/app/components/maglev/uikit/app_layout/topbar/page_info_component.html.erb +5 -5
- data/app/controllers/concerns/maglev/editor/errors_concern.rb +2 -1
- data/app/controllers/concerns/maglev/editor/lock_version_concern.rb +27 -0
- data/app/controllers/maglev/editor/section_blocks_controller.rb +3 -9
- data/app/controllers/maglev/editor/sections_controller.rb +9 -15
- data/app/controllers/maglev/studio/section_preview_controller.rb +76 -0
- data/app/helpers/maglev/application_helper.rb +34 -0
- data/app/helpers/maglev/studio/section_preview_helper.rb +9 -0
- data/app/models/concerns/maglev/sections_concern.rb +0 -6
- data/app/services/concerns/maglev/content/helpers_concern.rb +1 -1
- data/app/services/maglev/app_container.rb +2 -0
- data/app/services/maglev/content/sort_sections_service.rb +67 -0
- data/app/services/maglev/fetch_section_screenshot_path.rb +11 -1
- data/app/views/maglev/editor/assets/_list.html.erb +0 -1
- data/app/views/maglev/editor/assets/index.html.erb +4 -1
- data/config/routes/maglev/studio.rb +6 -0
- data/config/routes.rb +3 -0
- data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +1 -1
- data/lib/maglev/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91a5ea2feecec6351e3e7042be72418ce8e95374e50108d6a798b2f179d47ee4
|
|
4
|
+
data.tar.gz: c674bf2fb233c7aec70823875c804e7f7741df83f809a09cc670ac13eedb9d74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f3301ae182676801e8fc2baecbc6774f1c3e10e072fa509a2272185ff6b62aa64c7cdd68a92426679b6545c14a40f376e0be3f038df1cb18d78cd6e55747da1
|
|
7
|
+
data.tar.gz: a1dce55e8a53f13e394b82f548a6726a06b2a3babe65e6e645d1ee5c00040022d10243f75c9eede599fe957e4b2bb0aafe5fd8a9becc32dfa8b35a3f4767a873
|
|
@@ -55,9 +55,7 @@
|
|
|
55
55
|
--container-xl: 36rem;
|
|
56
56
|
--container-2xl: 42rem;
|
|
57
57
|
--container-3xl: 48rem;
|
|
58
|
-
--container-4xl: 56rem;
|
|
59
58
|
--container-5xl: 64rem;
|
|
60
|
-
--container-6xl: 72rem;
|
|
61
59
|
--container-7xl: 80rem;
|
|
62
60
|
--text-xs: 0.75rem;
|
|
63
61
|
--text-xs--line-height: calc(1 / 0.75);
|
|
@@ -739,6 +737,9 @@
|
|
|
739
737
|
.max-w-xs {
|
|
740
738
|
max-width: var(--container-xs);
|
|
741
739
|
}
|
|
740
|
+
.min-w-0 {
|
|
741
|
+
min-width: calc(var(--spacing) * 0);
|
|
742
|
+
}
|
|
742
743
|
.min-w-64 {
|
|
743
744
|
min-width: calc(var(--spacing) * 64);
|
|
744
745
|
}
|
|
@@ -2717,36 +2718,16 @@
|
|
|
2717
2718
|
display: flex;
|
|
2718
2719
|
}
|
|
2719
2720
|
}
|
|
2720
|
-
.lg\:w-2xl {
|
|
2721
|
-
@media (width >= 64rem) {
|
|
2722
|
-
width: var(--container-2xl);
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
2721
|
.lg\:w-3xl {
|
|
2726
2722
|
@media (width >= 64rem) {
|
|
2727
2723
|
width: var(--container-3xl);
|
|
2728
2724
|
}
|
|
2729
2725
|
}
|
|
2730
|
-
.lg\:w-4xl {
|
|
2731
|
-
@media (width >= 64rem) {
|
|
2732
|
-
width: var(--container-4xl);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
.lg\:w-5xl {
|
|
2736
|
-
@media (width >= 64rem) {
|
|
2737
|
-
width: var(--container-5xl);
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
2726
|
.lg\:grid-cols-2 {
|
|
2741
2727
|
@media (width >= 64rem) {
|
|
2742
2728
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2743
2729
|
}
|
|
2744
2730
|
}
|
|
2745
|
-
.lg\:grid-cols-3 {
|
|
2746
|
-
@media (width >= 64rem) {
|
|
2747
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
2731
|
.lg\:space-x-32 {
|
|
2751
2732
|
@media (width >= 64rem) {
|
|
2752
2733
|
:where(& > :not(:last-child)) {
|
|
@@ -2787,11 +2768,6 @@
|
|
|
2787
2768
|
width: var(--container-5xl);
|
|
2788
2769
|
}
|
|
2789
2770
|
}
|
|
2790
|
-
.xl\:w-6xl {
|
|
2791
|
-
@media (width >= 80rem) {
|
|
2792
|
-
width: var(--container-6xl);
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
2771
|
.xl\:grid-cols-3 {
|
|
2796
2772
|
@media (width >= 80rem) {
|
|
2797
2773
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
@@ -35,7 +35,13 @@ export const start = () => {
|
|
|
35
35
|
|
|
36
36
|
const addSection = (event) => {
|
|
37
37
|
const { sectionId, insertAt } = event.detail
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
// Do not use debouncedUpdatePreviewDocument: it is shared with section:update and block
|
|
40
|
+
// refreshes. A follow-up call within the debounce window only passes { sectionId }, which
|
|
41
|
+
// replaces this invocation and drops insertAt — the new section is then appended.
|
|
42
|
+
void updatePreviewDocument({ sectionId, insertAt, scrollToSection: true }).catch((err) => {
|
|
43
|
+
log('[DOM Operations] addSection failed', err)
|
|
44
|
+
})
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
const moveSections = (event) => {
|
|
@@ -221,6 +227,8 @@ const updatePreviewDocument = async ({ sectionId, insertAt = undefined, scrollTo
|
|
|
221
227
|
scrollToSectionOrBlock(targetElement)
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
// Shared debouncer: last invocation within 150ms wins. A later call with a narrower payload
|
|
231
|
+
// (e.g. only { sectionId }) replaces a pending one and drops extra args — see addSection().
|
|
224
232
|
const debouncedUpdatePreviewDocument = debounce(updatePreviewDocument, 150)
|
|
225
233
|
|
|
226
234
|
const insertSectionInDOM = (element, insertAt) => {
|
|
@@ -9,8 +9,10 @@ export default class extends Controller {
|
|
|
9
9
|
static debounces = ['afterSettingUpdate']
|
|
10
10
|
|
|
11
11
|
connect() {
|
|
12
|
-
useDebounce(this)
|
|
12
|
+
useDebounce(this)
|
|
13
|
+
|
|
13
14
|
this.sourceId = this.sectionBlockIdValue !== '' ? this.sectionBlockIdValue : this.sectionIdValue
|
|
15
|
+
|
|
14
16
|
requestAnimationFrame(() => {
|
|
15
17
|
this.dispatch('connected', { bubbles: true, detail: {
|
|
16
18
|
sectionId: this.sectionIdValue,
|
|
@@ -18,6 +20,12 @@ export default class extends Controller {
|
|
|
18
20
|
lockVersion: this.sectionLockVersionValue }
|
|
19
21
|
})
|
|
20
22
|
})
|
|
23
|
+
|
|
24
|
+
// we need to keep track of the submitting state to avoid multiple requests being sent with a wrong lock version
|
|
25
|
+
this.submitting = false
|
|
26
|
+
this.element.addEventListener('turbo:submit-end', (event) => {
|
|
27
|
+
if (!event.detail.success) this.submitting = false
|
|
28
|
+
})
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
onSettingChange(event) {
|
|
@@ -33,15 +41,32 @@ export default class extends Controller {
|
|
|
33
41
|
onPersist(event) {
|
|
34
42
|
log('[SectionForm::onPersist]', event.detail, typeof event.detail)
|
|
35
43
|
this.lockVersionTarget.value = event.detail.lockVersion
|
|
44
|
+
|
|
45
|
+
// we are done submitting, so we can reset the submitting flag
|
|
46
|
+
this.submitting = false
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
afterSettingUpdate(event) {
|
|
50
|
+
// if the request is still running (flight), we need to debounce the call
|
|
51
|
+
// to avoid multiple requests being sent with a wrong lock version
|
|
52
|
+
if (this.submitting) {
|
|
53
|
+
this.afterSettingUpdate(event)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
log('[SectionForm::afterSettingUpdate] 🙌🙌', event.detail)
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
|
|
59
|
+
// we couldn't update the content just by modifying the DOM, so we have to refresh the whole section.
|
|
60
|
+
// The right time to do this is when the request is finished, so we listen for the turbo:submit-end event.
|
|
42
61
|
if (!event.detail.updated) {
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
this.element.addEventListener('turbo:submit-end', () => {
|
|
63
|
+
this.dispatch('updateSection', { detail: { sectionId: this.sectionIdValue } })
|
|
64
|
+
}, { once: true })
|
|
45
65
|
}
|
|
66
|
+
|
|
67
|
+
// better to set it to true instead of relying on the turbo:submit-start event
|
|
68
|
+
this.submitting = true
|
|
69
|
+
|
|
70
|
+
this.element.requestSubmit()
|
|
46
71
|
}
|
|
47
72
|
}
|
|
@@ -164,13 +164,14 @@ export default class extends Controller {
|
|
|
164
164
|
|
|
165
165
|
setupTransformations() {
|
|
166
166
|
this.calculateTransformations = this.calculateTransformations.bind(this)
|
|
167
|
-
this.
|
|
168
|
-
|
|
167
|
+
this.scheduleTransformations = this.scheduleTransformations.bind(this)
|
|
168
|
+
this.resizeObserver = new ResizeObserver(this.scheduleTransformations)
|
|
169
|
+
|
|
169
170
|
const sidebar = this.appLayoutSidebar
|
|
170
171
|
const pageLayout = document.querySelector('#page-layout')
|
|
171
|
-
if (sidebar) this.
|
|
172
|
-
if (pageLayout) this.
|
|
173
|
-
this.
|
|
172
|
+
if (sidebar) this.resizeObserver.observe(this.appLayoutSidebar())
|
|
173
|
+
if (pageLayout) this.resizeObserver.observe(pageLayout)
|
|
174
|
+
this.resizeObserver.observe(document.documentElement)
|
|
174
175
|
|
|
175
176
|
// Recompute on page layout toggles / VT renders
|
|
176
177
|
addEventListener("turbo:render", this.calculateTransformations)
|
|
@@ -180,7 +181,11 @@ export default class extends Controller {
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
teardownTransformations() {
|
|
183
|
-
this.
|
|
184
|
+
if (this.transformationsFrame) {
|
|
185
|
+
cancelAnimationFrame(this.transformationsFrame)
|
|
186
|
+
this.transformationsFrame = null
|
|
187
|
+
}
|
|
188
|
+
this.resizeObserver?.disconnect()
|
|
184
189
|
removeEventListener("turbo:render", this.calculateTransformations)
|
|
185
190
|
removeEventListener("turbo:before-render", this.calculateTransformations)
|
|
186
191
|
}
|
|
@@ -230,6 +235,15 @@ export default class extends Controller {
|
|
|
230
235
|
this.dispatch('scale-ratio-updated', { detail: { value: scaleRatio } })
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
scheduleTransformations() {
|
|
239
|
+
if (this.transformationsFrame) return
|
|
240
|
+
|
|
241
|
+
this.transformationsFrame = requestAnimationFrame(() => {
|
|
242
|
+
this.transformationsFrame = null
|
|
243
|
+
this.calculateTransformations()
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
calculateTransformX() {
|
|
234
248
|
if (!this.hasExpandedPageLayout()) return 0
|
|
235
249
|
return this.expandedPageLayout().offsetWidth - this.appLayoutSidebarWidth()
|
data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js
CHANGED
|
@@ -79,10 +79,16 @@ export default class extends Controller {
|
|
|
79
79
|
// === SECTIONS ===
|
|
80
80
|
|
|
81
81
|
addSection(event) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
82
|
+
if (!event.detail.success) return
|
|
83
|
+
|
|
84
|
+
const res = event.detail.fetchResponse.response
|
|
85
|
+
log('addSection', res.headers.get('X-Section-Id'), res.headers.get('X-Section-Position'))
|
|
86
|
+
|
|
87
|
+
const sectionId = res.headers.get('X-Section-Id')
|
|
88
|
+
const position = res.headers.get('X-Section-Position')
|
|
89
|
+
const insertAt = position === null || position === '' ? undefined : parseInt(position, 10)
|
|
90
|
+
|
|
91
|
+
this.postMessage('section:add', { sectionId, insertAt })
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
deleteSection(event) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<% else %>
|
|
4
4
|
<div class="flex flex-row items-center h-full w-full px-4 overflow-hidden">
|
|
5
5
|
<div class="flex flex-col leading-none overflow-hidden">
|
|
6
|
-
<div class="flex items-center">
|
|
7
|
-
<%= helpers.maglev_page_icon(page, size: '1.1rem', wrapper_class_names: 'text-gray-900') %>
|
|
8
|
-
<div class="text-base font-semibold truncate ml-1 mr-3">
|
|
6
|
+
<div class="flex items-center overflow-hidden">
|
|
7
|
+
<%= helpers.maglev_page_icon(page, size: '1.1rem', wrapper_class_names: 'text-gray-900 shrink-0') %>
|
|
8
|
+
<div class="text-base font-semibold truncate ml-1 mr-3 min-w-0">
|
|
9
9
|
<%= page.title.presence || page.default_title %>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
%>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<div class="text-xs text-gray-500 flex items-center space-x-1">
|
|
20
|
-
<p class="truncate text-gray-700">
|
|
19
|
+
<div class="text-xs text-gray-500 flex items-center space-x-1 overflow-hidden">
|
|
20
|
+
<p class="truncate text-gray-700 min-w-0">
|
|
21
21
|
<span class="text-gray-900 font-semibold">/</span><%= page_path %>
|
|
22
22
|
</p>
|
|
23
23
|
</div>
|
|
@@ -26,7 +26,7 @@ module Maglev
|
|
|
26
26
|
|
|
27
27
|
def handle_stale_object
|
|
28
28
|
respond_to do |format|
|
|
29
|
-
format.turbo_stream { render 'maglev/editor/shared/errors/stale_object_error' }
|
|
29
|
+
format.turbo_stream { render 'maglev/editor/shared/errors/stale_object_error', status: :conflict }
|
|
30
30
|
format.html { redirect_to editor_root_path }
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -39,6 +39,7 @@ module Maglev
|
|
|
39
39
|
def track_maglev_error(error)
|
|
40
40
|
Rails.logger.error "[Maglev] Error: #{error.message}"
|
|
41
41
|
Rails.logger.error error.backtrace.join("\n")
|
|
42
|
+
Rails.error.report(error, handled: true)
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
module Editor
|
|
5
|
+
module LockVersionConcern
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
helper_method :source_lock_version
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def lock_source
|
|
15
|
+
@section.site_scoped? ? maglev_site : current_maglev_page
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def source_lock_version
|
|
19
|
+
lock_source.lock_version || 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reload_lock_source
|
|
23
|
+
lock_source.reload if @section.site_scoped?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Maglev
|
|
4
4
|
module Editor
|
|
5
5
|
class SectionBlocksController < Maglev::Editor::BaseController
|
|
6
|
+
include Maglev::Editor::LockVersionConcern
|
|
7
|
+
|
|
6
8
|
helper Maglev::Editor::SettingsHelper
|
|
7
|
-
helper_method :source_lock_version
|
|
8
9
|
|
|
9
10
|
before_action :set_section
|
|
10
11
|
before_action :set_section_block, only: %i[edit update destroy]
|
|
@@ -32,6 +33,7 @@ module Maglev
|
|
|
32
33
|
|
|
33
34
|
def update
|
|
34
35
|
update_section_block
|
|
36
|
+
reload_lock_source
|
|
35
37
|
flash.now[:notice] = flash_t(:success)
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -77,14 +79,6 @@ module Maglev
|
|
|
77
79
|
)
|
|
78
80
|
end
|
|
79
81
|
|
|
80
|
-
def lock_source
|
|
81
|
-
@section.site_scoped? ? maglev_site : current_maglev_page
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def source_lock_version
|
|
85
|
-
lock_source.lock_version || 0
|
|
86
|
-
end
|
|
87
|
-
|
|
88
82
|
def redirect_to_section_blocks_path(success: true)
|
|
89
83
|
flash = case success
|
|
90
84
|
when true then { notice: flash_t(:success) }
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Maglev
|
|
4
4
|
module Editor
|
|
5
5
|
class SectionsController < Maglev::Editor::BaseController
|
|
6
|
+
include Maglev::Editor::LockVersionConcern
|
|
7
|
+
|
|
6
8
|
helper Maglev::Editor::SettingsHelper
|
|
7
|
-
helper_method :source_lock_version
|
|
8
9
|
|
|
9
10
|
before_action :ensure_turbo_frame_request, only: [:new]
|
|
10
11
|
before_action :set_section, only: %i[edit update]
|
|
@@ -39,16 +40,17 @@ module Maglev
|
|
|
39
40
|
|
|
40
41
|
def update
|
|
41
42
|
update_section
|
|
43
|
+
reload_lock_source
|
|
42
44
|
flash.now[:notice] = flash_t(:success)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def sort
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
services.sort_sections.call(
|
|
49
|
+
page: current_maglev_page,
|
|
50
|
+
section_ids: params[:item_ids],
|
|
51
|
+
lock_version: params[:lock_version]
|
|
52
|
+
)
|
|
53
|
+
redirect_to_sections_path
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def destroy
|
|
@@ -95,14 +97,6 @@ module Maglev
|
|
|
95
97
|
headers['X-Section-Position'] = flash[:position]
|
|
96
98
|
end
|
|
97
99
|
|
|
98
|
-
def lock_source
|
|
99
|
-
@section.site_scoped? ? maglev_site : current_maglev_page
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def source_lock_version
|
|
103
|
-
lock_source.lock_version || 0
|
|
104
|
-
end
|
|
105
|
-
|
|
106
100
|
def redirect_to_sections_path
|
|
107
101
|
redirect_to editor_sections_path_with_context, notice: flash_t(:success), status: :see_other
|
|
108
102
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
module Studio
|
|
5
|
+
# Renders a single section from default theme content on an in-memory page (no DB page load).
|
|
6
|
+
# :slug is the theme section type (e.g. "showcase"). Disabled outside +Rails.env.local?+.
|
|
7
|
+
class SectionPreviewController < ApplicationController
|
|
8
|
+
include Maglev::RenderingConcern
|
|
9
|
+
include Maglev::ServicesConcern
|
|
10
|
+
include Maglev::ContentLocaleConcern
|
|
11
|
+
|
|
12
|
+
rescue_from ActiveRecord::RecordNotFound, with: :respond_not_found
|
|
13
|
+
rescue_from Maglev::Errors::UnknownSection, with: :respond_not_found
|
|
14
|
+
|
|
15
|
+
before_action :fetch_maglev_site
|
|
16
|
+
around_action :with_default_site_locale
|
|
17
|
+
|
|
18
|
+
def show
|
|
19
|
+
render_maglev_page
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def fetch_maglev_site
|
|
25
|
+
super.tap do |site|
|
|
26
|
+
raise ActiveRecord::RecordNotFound if site.nil?
|
|
27
|
+
|
|
28
|
+
maglev_services.context.site = site
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fetch_maglev_page
|
|
33
|
+
@fetch_maglev_page ||= build_preview_page
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_preview_page
|
|
37
|
+
theme = fetch_maglev_theme
|
|
38
|
+
section_content = build_section_content_for_preview(theme)
|
|
39
|
+
|
|
40
|
+
Maglev::Page.new(title: 'Section preview', path: 'index').tap do |page|
|
|
41
|
+
page.sections = [section_content]
|
|
42
|
+
page.prepare_sections(theme)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build_section_content_for_preview(theme)
|
|
47
|
+
definition = theme.sections.find(params[:slug].to_s)
|
|
48
|
+
raise Maglev::Errors::UnknownSection unless definition
|
|
49
|
+
|
|
50
|
+
definition.build_default_content.with_indifferent_access
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fetch_maglev_page_sections(*)
|
|
54
|
+
section_id = fetch_maglev_page.sections.first.fetch('id')
|
|
55
|
+
|
|
56
|
+
@fetch_maglev_page_sections ||= maglev_services.get_page_sections.call(
|
|
57
|
+
page: fetch_maglev_page,
|
|
58
|
+
section_id: section_id,
|
|
59
|
+
locale: content_locale
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def maglev_rendering_mode
|
|
64
|
+
:section
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def with_default_site_locale(&block)
|
|
68
|
+
Maglev::I18n.with_locale(maglev_site.default_locale_prefix, &block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def respond_not_found
|
|
72
|
+
head :not_found
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -19,6 +19,31 @@ module Maglev
|
|
|
19
19
|
maglev_importmap_tags(:client, 'maglev-client')
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Use this helper in your theme layout instead of calling javascript_importmap_tags
|
|
23
|
+
# and maglev_client_javascript_tags separately.
|
|
24
|
+
#
|
|
25
|
+
# Firefox (unlike Chrome) strictly follows the HTML spec which only allows a single
|
|
26
|
+
# <script type="importmap"> per document and ignores any subsequent ones. When both
|
|
27
|
+
# the theme and the Maglev client each emit their own importmap, Firefox silently
|
|
28
|
+
# discards the second one, causing `import "maglev-client"` to fail with:
|
|
29
|
+
# "The specifier 'maglev-client' was a bare specifier, but was not remapped to anything."
|
|
30
|
+
#
|
|
31
|
+
# This helper solves the problem by merging the Maglev client importmap entries into
|
|
32
|
+
# the theme's importmap so that only one <script type="importmap"> is ever emitted.
|
|
33
|
+
# Once browsers universally support multiple importmaps (Chrome already does),
|
|
34
|
+
# this helper can be simplified back to two separate calls.
|
|
35
|
+
def maglev_javascript_importmap_tags(entry_point = 'application', importmap: Rails.application.importmap)
|
|
36
|
+
return javascript_importmap_tags(entry_point, importmap: importmap) unless maglev_rendering_mode == :editor
|
|
37
|
+
|
|
38
|
+
safe_join [
|
|
39
|
+
javascript_inline_importmap_tag(maglev_merged_importmap_json(importmap)),
|
|
40
|
+
javascript_importmap_module_preload_tags(importmap, entry_point: entry_point),
|
|
41
|
+
javascript_importmap_module_preload_tags(Maglev::Engine.importmaps[:client], entry_point: 'maglev-client'),
|
|
42
|
+
javascript_import_module_tag(entry_point),
|
|
43
|
+
javascript_import_module_tag('maglev-client')
|
|
44
|
+
], "\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
22
47
|
def maglev_importmap_tags(namespace, entry_point)
|
|
23
48
|
safe_join [
|
|
24
49
|
javascript_inline_importmap_tag(Maglev::Engine.importmaps[namespace].to_json(resolver: self)),
|
|
@@ -176,6 +201,15 @@ module Maglev
|
|
|
176
201
|
# rubocop:enable Layout/LineLength
|
|
177
202
|
maglev_client_javascript_tags
|
|
178
203
|
end
|
|
204
|
+
|
|
205
|
+
private
|
|
206
|
+
|
|
207
|
+
def maglev_merged_importmap_json(theme_importmap)
|
|
208
|
+
theme_map = JSON.parse(theme_importmap.to_json(resolver: self))
|
|
209
|
+
client_map = JSON.parse(Maglev::Engine.importmaps[:client].to_json(resolver: self))
|
|
210
|
+
theme_map['imports'].merge!(client_map['imports'])
|
|
211
|
+
theme_map.to_json
|
|
212
|
+
end
|
|
179
213
|
end
|
|
180
214
|
end
|
|
181
215
|
# rubocop:enable Metrics/ModuleLength
|
|
@@ -33,12 +33,6 @@ module Maglev::SectionsConcern
|
|
|
33
33
|
sections&.find_all { |section| section['type'] == type } || []
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def reorder_sections(sorted_section_ids, lock_version)
|
|
37
|
-
self.lock_version = lock_version
|
|
38
|
-
sections_translations_will_change!
|
|
39
|
-
sections.sort! { |a, b| sorted_section_ids.index(a['id']) <=> sorted_section_ids.index(b['id']) }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
36
|
def delete_section(section_id)
|
|
43
37
|
sections_translations_will_change!
|
|
44
38
|
sections.delete_if { |section| section['id'] == section_id }
|
|
@@ -58,7 +58,7 @@ module Maglev
|
|
|
58
58
|
|
|
59
59
|
def update_setting_value(setting, current_content)
|
|
60
60
|
setting_content = current_content.find { |s| s['id'] == setting.id }
|
|
61
|
-
value = content[setting.id.to_sym]
|
|
61
|
+
value = setting.cast_value(content[setting.id.to_sym])
|
|
62
62
|
|
|
63
63
|
if setting_content.nil?
|
|
64
64
|
current_content.push({ id: setting.id, value: value })
|
|
@@ -57,6 +57,8 @@ module Maglev
|
|
|
57
57
|
depends_on: %i[fetch_site fetch_theme]
|
|
58
58
|
dependency :delete_section, class: Maglev::Content::DeleteSectionService,
|
|
59
59
|
depends_on: %i[fetch_site fetch_theme]
|
|
60
|
+
dependency :sort_sections, class: Maglev::Content::SortSectionsService,
|
|
61
|
+
depends_on: %i[fetch_site fetch_theme]
|
|
60
62
|
|
|
61
63
|
dependency :add_section_block, class: Maglev::Content::AddSectionBlockService,
|
|
62
64
|
depends_on: %i[fetch_site fetch_theme]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
module Content
|
|
5
|
+
class SortSectionsService
|
|
6
|
+
include Injectable
|
|
7
|
+
|
|
8
|
+
dependency :fetch_theme
|
|
9
|
+
dependency :fetch_site
|
|
10
|
+
|
|
11
|
+
argument :page
|
|
12
|
+
argument :section_ids
|
|
13
|
+
argument :lock_version, default: nil
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
check_lock_version!
|
|
17
|
+
sort_sections!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def sort_sections!
|
|
23
|
+
sorted_ids = normalized_section_ids
|
|
24
|
+
page.sections_translations_will_change!
|
|
25
|
+
page.sections.sort! { |a, b| sorted_ids.index(a['id']) <=> sorted_ids.index(b['id']) }
|
|
26
|
+
page.save!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_lock_version!
|
|
30
|
+
return if lock_version.blank?
|
|
31
|
+
|
|
32
|
+
current_version = page.lock_version.to_i
|
|
33
|
+
page.lock_version = lock_version.to_i
|
|
34
|
+
|
|
35
|
+
return if current_version == lock_version.to_i
|
|
36
|
+
|
|
37
|
+
raise ActiveRecord::StaleObjectError.new(page, 'sort_sections')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def normalized_section_ids
|
|
41
|
+
# For site-scoped sections, transform_if_site_scoped replaces the page-local ID
|
|
42
|
+
# with the site section's ID at render time, so the client sends site IDs.
|
|
43
|
+
# Legacy pages (created before IDs were unified) store a different page-local ID.
|
|
44
|
+
# Build a mapping: display_id (what the client sends) → page_id (what page.sections stores).
|
|
45
|
+
id_map = page.sections.each_with_object({}) do |page_section, map|
|
|
46
|
+
display_id = site_display_id_for(page_section) || page_section['id']
|
|
47
|
+
map[display_id] = page_section['id']
|
|
48
|
+
end
|
|
49
|
+
section_ids.map { |id| id_map[id] || id }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def site_display_id_for(page_section)
|
|
53
|
+
return unless theme.sections.find(page_section['type'])&.site_scoped?
|
|
54
|
+
|
|
55
|
+
site.find_section(page_section['type'])&.dig('id')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def theme
|
|
59
|
+
@theme ||= fetch_theme.call
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def site
|
|
63
|
+
@site ||= fetch_site.call
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -10,8 +10,18 @@ module Maglev
|
|
|
10
10
|
argument :absolute, default: false
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
|
-
path = "#{
|
|
13
|
+
path = "#{base_path}.#{extension}"
|
|
14
14
|
absolute ? Rails.root.join("public/#{path}").to_s : "/#{path}"
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def base_path
|
|
20
|
+
"#{fetch_sections_path.call(theme: theme)}/#{section.category}/#{section.id}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def extension
|
|
24
|
+
File.exist?(Rails.root.join("public/#{base_path}.webp")) ? 'webp' : 'jpg'
|
|
25
|
+
end
|
|
16
26
|
end
|
|
17
27
|
end
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
options: {
|
|
14
14
|
placeholder: t('maglev.editor.image_library.search_placeholder'),
|
|
15
15
|
data: { turbo_frame: 'asset-search-results' }
|
|
16
|
-
}) %>
|
|
16
|
+
}) do %>
|
|
17
|
+
<%= hidden_field_tag :picker, params[:picker] if params[:picker].present? %>
|
|
18
|
+
<%= hidden_field_tag :source, params[:source] if params[:source].present? %>
|
|
19
|
+
<% end %>
|
|
17
20
|
|
|
18
21
|
</div>
|
|
19
22
|
<div class="ml-auto">
|
data/config/routes.rb
CHANGED
data/lib/maglev/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: maglevcms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Didier Lafforgue
|
|
@@ -359,6 +359,7 @@ files:
|
|
|
359
359
|
- app/controllers/concerns/maglev/back_action_concern.rb
|
|
360
360
|
- app/controllers/concerns/maglev/content_locale_concern.rb
|
|
361
361
|
- app/controllers/concerns/maglev/editor/errors_concern.rb
|
|
362
|
+
- app/controllers/concerns/maglev/editor/lock_version_concern.rb
|
|
362
363
|
- app/controllers/concerns/maglev/editor/preview_urls_concern.rb
|
|
363
364
|
- app/controllers/concerns/maglev/editor/turbo_concern.rb
|
|
364
365
|
- app/controllers/concerns/maglev/errors_concern.rb
|
|
@@ -391,6 +392,7 @@ files:
|
|
|
391
392
|
- app/controllers/maglev/published_page_preview_controller.rb
|
|
392
393
|
- app/controllers/maglev/site_controller.rb
|
|
393
394
|
- app/controllers/maglev/sitemap_controller.rb
|
|
395
|
+
- app/controllers/maglev/studio/section_preview_controller.rb
|
|
394
396
|
- app/helpers/maglev/admin/sections/previews_helper.rb
|
|
395
397
|
- app/helpers/maglev/admin/themes_helper.rb
|
|
396
398
|
- app/helpers/maglev/application_helper.rb
|
|
@@ -399,6 +401,7 @@ files:
|
|
|
399
401
|
- app/helpers/maglev/editor/settings_helper.rb
|
|
400
402
|
- app/helpers/maglev/page_preview_helper.rb
|
|
401
403
|
- app/helpers/maglev/sitemap_helper.rb
|
|
404
|
+
- app/helpers/maglev/studio/section_preview_helper.rb
|
|
402
405
|
- app/jobs/maglev/application_job.rb
|
|
403
406
|
- app/lib/maglev/form/form_builder.rb
|
|
404
407
|
- app/lib/maglev/form/inputs/check_box.rb
|
|
@@ -462,6 +465,7 @@ files:
|
|
|
462
465
|
- app/services/maglev/content/delete_section_block_service.rb
|
|
463
466
|
- app/services/maglev/content/delete_section_service.rb
|
|
464
467
|
- app/services/maglev/content/sort_section_blocks_service.rb
|
|
468
|
+
- app/services/maglev/content/sort_sections_service.rb
|
|
465
469
|
- app/services/maglev/content/update_section_block_service.rb
|
|
466
470
|
- app/services/maglev/content/update_section_service.rb
|
|
467
471
|
- app/services/maglev/create_page_service.rb
|
|
@@ -579,6 +583,7 @@ files:
|
|
|
579
583
|
- config/routes/maglev/editor.rb
|
|
580
584
|
- config/routes/maglev/preview.rb
|
|
581
585
|
- config/routes/maglev/public_preview.rb
|
|
586
|
+
- config/routes/maglev/studio.rb
|
|
582
587
|
- db/migrate/20200824085207_create_maglev_sites.rb
|
|
583
588
|
- db/migrate/20200824104648_create_maglev_pages.rb
|
|
584
589
|
- db/migrate/20200831101942_create_maglev_section_content.rb
|