maglevcms 3.0.0.beta2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -53
- data/Rakefile +3 -1
- data/app/assets/builds/maglev/tailwind.css +681 -11670
- data/app/assets/config/maglev_manifest.js +3 -2
- data/app/assets/javascripts/maglev/client/dom-operations.js +5 -5
- data/app/assets/javascripts/maglev/client/iframe-decorator.js +24 -0
- data/app/assets/javascripts/maglev/client/incoming-messages.js +13 -3
- data/app/assets/javascripts/maglev/client/index.js +3 -2
- data/app/assets/javascripts/maglev/client/utils.js +22 -0
- data/app/assets/javascripts/maglev/editor/controllers/app/forms/section_form_controller.js +4 -3
- data/app/assets/javascripts/maglev/editor/controllers/app/forms/style_form_controller.js +2 -1
- data/app/assets/javascripts/maglev/editor/controllers/app/page_preview_controller.js +96 -5
- data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js +105 -23
- data/app/assets/javascripts/maglev/editor/controllers/app/setting_controller.js +3 -2
- data/app/assets/javascripts/maglev/editor/controllers/shared/copy_to_clipboard_controller.js +2 -1
- data/app/assets/javascripts/maglev/editor/controllers/shared/submit_button_controller.js +29 -5
- data/app/assets/javascripts/maglev/editor/controllers/utils.js +38 -0
- data/app/assets/javascripts/maglev/editor/index.js +5 -44
- data/app/assets/javascripts/maglev/editor/patches/page_renderer_patch.js +47 -0
- data/app/assets/javascripts/maglev/editor/patches/turbo_delayed_streams.js +35 -0
- data/app/assets/javascripts/maglev/editor/patches/turbo_stream_patch.js +50 -0
- data/app/assets/stylesheets/maglev/application.css +0 -2
- data/app/assets/stylesheets/maglev/tailwind.css.erb +11 -2
- data/app/components/maglev/content/link.rb +1 -1
- data/app/components/maglev/editor/settings/link/link_component.rb +7 -1
- data/app/components/maglev/section_component.rb +11 -1
- data/app/components/maglev/uikit/app_layout/sidebar/link_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/sidebar/link_component.rb +35 -5
- data/app/components/maglev/uikit/app_layout/sidebar_component.html.erb +3 -4
- data/app/components/maglev/uikit/app_layout/topbar/logo_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/topbar/page_info_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/topbar_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/topbar_component.rb +1 -1
- data/app/components/maglev/uikit/button_group_component/button_group_component.html.erb +5 -0
- data/app/components/maglev/uikit/button_group_component.rb +70 -0
- data/app/components/maglev/uikit/device_toggler_component.rb +10 -1
- data/app/components/maglev/uikit/dropdown_component/dropdown_component.html.erb +2 -2
- data/app/components/maglev/uikit/dropdown_component/dropdown_controller.js +6 -1
- data/app/components/maglev/uikit/dropdown_component.rb +6 -1
- data/app/components/maglev/uikit/form/color_field_component.html.erb +1 -1
- data/app/components/maglev/uikit/form/combobox_component.html.erb +1 -0
- data/app/components/maglev/uikit/form/link_component.rb +5 -1
- data/app/components/maglev/uikit/form/richtext_controller.js +5 -4
- data/app/components/maglev/uikit/form/search_form_component.html.erb +1 -0
- data/app/components/maglev/uikit/icon_component.rb +3 -0
- data/app/components/maglev/uikit/image_library/uploader_controller.js +3 -2
- data/app/components/maglev/uikit/list/list_item_component.html.erb +36 -19
- data/app/components/maglev/uikit/list/list_item_component.rb +19 -5
- data/app/components/maglev/uikit/locale_switcher_component/locale_switcher_component.html.erb +6 -10
- data/app/components/maglev/uikit/menu_dropdown_component/menu_dropdown_component.html.erb +6 -2
- data/app/components/maglev/uikit/menu_dropdown_component.rb +244 -7
- data/app/components/maglev/uikit/page_actions_dropdown_component/page_actions_dropdown_component.html.erb +39 -46
- data/app/components/maglev/uikit/pagination_component/pagination_component.html.erb +9 -12
- data/app/components/maglev/uikit/pagination_component.rb +6 -1
- data/app/components/maglev/uikit/section_toolbar/bottom_component.html.erb +1 -1
- data/app/components/maglev/uikit/tabs_component/tabs_component.html.erb +7 -4
- data/app/components/maglev/uikit/tabs_component.rb +23 -4
- data/app/components/maglev/uikit/well/simple_well_component.html.erb +15 -0
- data/app/components/maglev/uikit/well/simple_well_component.rb +13 -0
- data/app/controllers/concerns/maglev/editor/errors_concern.rb +9 -9
- data/app/controllers/concerns/maglev/editor/preview_urls_concern.rb +32 -0
- data/app/controllers/concerns/maglev/editor/turbo_concern.rb +29 -0
- data/app/controllers/concerns/maglev/errors_concern.rb +17 -0
- data/app/controllers/concerns/maglev/flash_i18n_concern.rb +1 -0
- data/app/controllers/maglev/application_controller.rb +2 -1
- data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +2 -1
- data/app/controllers/maglev/editor/assets_controller.rb +1 -1
- data/app/controllers/maglev/editor/base_controller.rb +6 -32
- data/app/controllers/maglev/editor/pages/clone_controller.rb +22 -0
- data/app/controllers/maglev/editor/pages/discard_draft_controller.rb +17 -0
- data/app/controllers/maglev/editor/pages_controller.rb +26 -7
- data/app/controllers/maglev/editor/section_blocks_controller.rb +13 -9
- data/app/controllers/maglev/editor/sections_controller.rb +26 -7
- data/app/controllers/maglev/published_page_preview_controller.rb +4 -0
- data/app/controllers/maglev/site_controller.rb +15 -0
- data/app/helpers/maglev/application_helper.rb +26 -8
- data/app/helpers/maglev/editor/section_blocks_helper.rb +2 -2
- data/app/models/maglev/page/publishable_concern.rb +69 -0
- data/app/models/maglev/page.rb +3 -0
- data/app/models/maglev/section/block.rb +4 -0
- data/app/models/maglev/section/content_concern.rb +3 -1
- data/app/models/maglev/section.rb +21 -1
- data/app/models/maglev/sections_content_store.rb +1 -3
- data/app/models/maglev/site.rb +5 -0
- data/app/services/concerns/maglev/content/helpers_concern.rb +5 -8
- data/app/services/maglev/app_container.rb +4 -2
- data/app/services/maglev/discard_page_draft_service.rb +45 -0
- data/app/services/maglev/fetch_section_screenshot_url.rb +12 -1
- data/app/services/maglev/fetch_site.rb +3 -1
- data/app/services/maglev/get_published_page_sections_service.rb +1 -1
- data/app/services/maglev/has_unpublished_changes.rb +21 -0
- data/app/services/maglev/publish_service.rb +18 -2
- data/app/views/layouts/maglev/editor/_sidebar.html.erb +8 -2
- data/app/views/layouts/maglev/editor/_topbar.html.erb +6 -23
- data/app/views/layouts/maglev/editor/application.html.erb +6 -0
- data/app/views/layouts/maglev/editor/topbar/_page_info.html.erb +11 -0
- data/app/views/layouts/maglev/editor/topbar/_publish_button.html.erb +35 -0
- data/app/views/maglev/editor/assets/index.html.erb +1 -0
- data/app/views/maglev/editor/home/index.html.erb +1 -1
- data/app/views/maglev/editor/links/edit/_email.html.erb +1 -1
- data/app/views/maglev/editor/links/edit/_url.html.erb +1 -1
- data/app/views/maglev/editor/pages/_list.html.erb +25 -21
- data/app/views/maglev/editor/pages/_preview.html.erb +6 -6
- data/app/views/maglev/editor/pages/_preview_empty_message.html.erb +13 -10
- data/app/views/maglev/editor/pages/discard_draft/create.turbo_stream.erb +16 -0
- data/app/views/maglev/editor/pages/index.html.erb +8 -1
- data/app/views/maglev/editor/publication/create.turbo_stream.erb +3 -1
- data/app/views/maglev/editor/section_blocks/_form.html.erb +5 -13
- data/app/views/maglev/editor/section_blocks/_form_with_tabs.html.erb +15 -0
- data/app/views/maglev/editor/section_blocks/_new.html.erb +1 -1
- data/app/views/maglev/editor/section_blocks/edit.html.erb +2 -2
- data/app/views/maglev/editor/section_blocks/index/_list.html.erb +2 -2
- data/app/views/maglev/editor/section_blocks/index/_tree.html.erb +1 -1
- data/app/views/maglev/editor/section_blocks/update.turbo_stream.erb +9 -1
- data/app/views/maglev/editor/sections/_form.html.erb +6 -20
- data/app/views/maglev/editor/sections/_form_with_tabs.html.erb +21 -0
- data/app/views/maglev/editor/sections/_list.html.erb +1 -1
- data/app/views/maglev/editor/sections/edit.html.erb +3 -3
- data/app/views/maglev/editor/sections/index.html.erb +1 -1
- data/app/views/maglev/editor/sections/new.html.erb +18 -1
- data/app/views/maglev/editor/sections/theme/_empty_list.html.erb +3 -0
- data/app/views/maglev/editor/sections/theme/_list.html.erb +35 -0
- data/app/views/maglev/editor/sections/theme/_screenshot_placeholder.html.erb +6 -0
- data/app/views/maglev/editor/sections/theme/_search.html.erb +22 -0
- data/app/views/maglev/editor/sections/update.turbo_stream.erb +9 -1
- data/app/views/maglev/editor/shared/_button_label.html.erb +4 -4
- data/app/views/maglev/editor/style/edit.html.erb +1 -0
- data/app/views/maglev/errors/site_not_found.html.erb +33 -0
- data/config/editor_importmap.rb +16 -13
- data/config/locales/editor.ar.yml +12 -4
- data/config/locales/editor.en.yml +31 -23
- data/config/locales/editor.es.yml +12 -4
- data/config/locales/editor.fr.yml +12 -4
- data/config/locales/editor.pt-BR.yml +12 -4
- data/config/routes/maglev/assets.rb +4 -0
- data/config/routes/maglev/editor.rb +38 -0
- data/config/routes/maglev/preview.rb +8 -0
- data/config/routes/maglev/public_preview.rb +6 -0
- data/config/routes.rb +8 -44
- data/db/migrate/20211013210954_translate_section_content.rb +1 -0
- data/db/migrate/20251116171603_add_published_at_to_sites_and_pages.rb +6 -0
- data/db/migrate/20260114112058_add_published_payload_to_pages.rb +14 -0
- data/exe/tailwind-cli +1 -1
- data/lib/generators/maglev/install_generator.rb +9 -7
- data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +10 -3
- data/lib/maglev/active_storage/serving_blob.rb +29 -0
- data/lib/maglev/active_storage.rb +2 -0
- data/lib/maglev/config.rb +22 -3
- data/lib/maglev/engine.rb +15 -10
- data/lib/maglev/errors.rb +1 -0
- data/lib/maglev/version.rb +1 -1
- data/lib/maglev.rb +18 -3
- data/lib/tasks/db_test_all.rake +290 -0
- data/lib/tasks/maglev/tailwindcss.rake +1 -0
- metadata +55 -19
- data/app/controllers/maglev/editor/page_clone_controller.rb +0 -20
- data/app/views/maglev/editor/sections/_theme_list.html.erb +0 -32
- /data/vendor/javascript/{@floating-ui--core.js → maglev/@floating-ui--core.js} +0 -0
- /data/vendor/javascript/{@floating-ui--dom.js → maglev/@floating-ui--dom.js} +0 -0
- /data/vendor/javascript/{@floating-ui--utils--dom.js → maglev/@floating-ui--utils--dom.js} +0 -0
- /data/vendor/javascript/{@floating-ui--utils.js → maglev/@floating-ui--utils.js} +0 -0
- /data/vendor/javascript/{@hotwired--stimulus.js → maglev/@hotwired--stimulus.js} +0 -0
- /data/vendor/javascript/{@hotwired--turbo-rails.js → maglev/@hotwired--turbo-rails.js} +0 -0
- /data/vendor/javascript/{@hotwired--turbo.js → maglev/@hotwired--turbo.js} +0 -0
- /data/vendor/javascript/{@rails--actioncable--src.js → maglev/@rails--actioncable--src.js} +0 -0
- /data/vendor/javascript/{@rails--request.js.js → maglev/@rails--request.js.js} +0 -0
- /data/vendor/javascript/{@shopify--draggable.js → maglev/@shopify--draggable.js} +0 -0
- /data/vendor/javascript/{el-transition.js → maglev/el-transition.js} +0 -0
- /data/vendor/javascript/{stimulus-use.js → maglev/stimulus-use.js} +0 -0
- /data/vendor/javascript/{tiptap.bundle.js → maglev/tiptap.bundle.js} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
//= link_directory ../
|
|
1
|
+
//= link_directory ../builds/maglev .css
|
|
2
|
+
//= link maglev/application.css
|
|
2
3
|
|
|
3
|
-
//= link_tree ../../../vendor/javascript .js
|
|
4
|
+
//= link_tree ../../../vendor/javascript/maglev .js
|
|
4
5
|
//= link_tree ../../components .js
|
|
5
6
|
//= link_tree ../javascripts/maglev .js
|
|
6
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { debounce, postMessageToEditor } from 'maglev-client/utils'
|
|
1
|
+
import { debounce, log, postMessageToEditor } from 'maglev-client/utils'
|
|
2
2
|
import runScripts from 'maglev-client/run-scripts'
|
|
3
3
|
|
|
4
4
|
const parentDocument = window.parent.document
|
|
@@ -79,7 +79,7 @@ const removeSection = (event) => {
|
|
|
79
79
|
|
|
80
80
|
const checkSectionLockVersion = (event) => {
|
|
81
81
|
const { sectionId, lockVersion } = event.detail
|
|
82
|
-
|
|
82
|
+
log('[DOM Operations] checkSectionLockVersion', event)
|
|
83
83
|
const section = previewDocument.querySelector(`[data-maglev-section-id='${sectionId}']`)
|
|
84
84
|
const localLockVersion = section?.getAttribute('data-maglev-section-lock-version')
|
|
85
85
|
if (lockVersion !== localLockVersion && lockVersion !== '') {
|
|
@@ -180,7 +180,7 @@ const updateStyle = async (event) => {
|
|
|
180
180
|
// }
|
|
181
181
|
|
|
182
182
|
const updateTextSetting = (sourceId, change) => {
|
|
183
|
-
|
|
183
|
+
log('[DOM Operations] updateTextSetting', sourceId, change)
|
|
184
184
|
const selector = `[data-maglev-id='${sourceId}.${change.settingId}']`
|
|
185
185
|
const settings = previewDocument.querySelectorAll(selector)
|
|
186
186
|
settings.forEach(($el) => ($el.innerHTML = change.value))
|
|
@@ -188,11 +188,11 @@ const updateTextSetting = (sourceId, change) => {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
const updateLinkSetting = (sourceId, change) => {
|
|
191
|
-
|
|
191
|
+
log('updateLinkSetting', sourceId, change)
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
const updatePreviewDocument = async ({ sectionId, insertAt = undefined, scrollToSection = false }) => {
|
|
195
|
-
|
|
195
|
+
log('[DOM Operations] updatePreviewDocument', sectionId, insertAt, scrollToSection)
|
|
196
196
|
|
|
197
197
|
const doc = await getUpdatedDoc({ section_id: sectionId })
|
|
198
198
|
|
|
@@ -3,6 +3,7 @@ import { isBlank, postMessageToEditor } from 'maglev-client/utils'
|
|
|
3
3
|
// keep track of the current hovered section
|
|
4
4
|
let listeners = []
|
|
5
5
|
let hoveredSectionId = null
|
|
6
|
+
let hoveredSettingKey = null
|
|
6
7
|
let lastCursorPosition = { x: 0, y: 0 }
|
|
7
8
|
|
|
8
9
|
export const start = (config) => {
|
|
@@ -172,6 +173,7 @@ const getMinTop = (previewDocument, currentSectionId, stickySectionIds) => {
|
|
|
172
173
|
const onSectionLeft = () => {
|
|
173
174
|
postMessageToEditor('section:leave')
|
|
174
175
|
hoveredSectionId = null
|
|
176
|
+
hoveredSettingKey = null
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
const onBlockHovered = (el) => {
|
|
@@ -200,10 +202,32 @@ const onSettingHovered = (el) => {
|
|
|
200
202
|
window.getComputedStyle(el).borderRadius === '0px'
|
|
201
203
|
)
|
|
202
204
|
el.style.borderRadius = '2px'
|
|
205
|
+
|
|
206
|
+
const fragments = el.dataset.maglevId.split('.')
|
|
207
|
+
const section = el.closest('[data-maglev-section-id]')
|
|
208
|
+
const sectionId = section?.dataset?.maglevSectionId
|
|
209
|
+
const sectionBlock = el.closest('[data-maglev-block-id]')
|
|
210
|
+
const sectionBlockId = sectionBlock?.dataset?.maglevBlockId
|
|
211
|
+
const settingId = fragments[1]
|
|
212
|
+
|
|
213
|
+
if (!sectionId || !settingId) return
|
|
214
|
+
|
|
215
|
+
const key = `${sectionId}:${sectionBlockId || 'none'}:${settingId}`
|
|
216
|
+
if (key === hoveredSettingKey) return
|
|
217
|
+
|
|
218
|
+
hoveredSettingKey = key
|
|
219
|
+
|
|
220
|
+
const prefix = sectionBlockId ? 'sectionBlock' : 'section'
|
|
221
|
+
postMessageToEditor(`${prefix}:setting:hovered`, {
|
|
222
|
+
sectionId,
|
|
223
|
+
sectionBlockId,
|
|
224
|
+
settingId,
|
|
225
|
+
})
|
|
203
226
|
}
|
|
204
227
|
|
|
205
228
|
const onSettingLeft = (el) => {
|
|
206
229
|
el.style.boxShadow = 'none'
|
|
230
|
+
hoveredSettingKey = null
|
|
207
231
|
}
|
|
208
232
|
|
|
209
233
|
const onSettingClicked = (el, event) => {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { start as decorateIframe } from 'maglev-client/iframe-decorator'
|
|
2
|
-
import { postMessageToEditor } from 'maglev-client/utils'
|
|
2
|
+
import { log, postMessageToEditor } from 'maglev-client/utils'
|
|
3
|
+
|
|
4
|
+
const PAGE_UPDATED_EVENTS = ['section:add', 'section:move', 'section:update', 'section:remove', 'block:add', 'block:move', 'block:remove', 'setting:update', 'style:update']
|
|
3
5
|
|
|
4
6
|
export const start = () => {
|
|
5
7
|
window.addEventListener('message', ({ data: { type, ...data } }) => {
|
|
6
8
|
// a message MUST have a type
|
|
7
9
|
if (!type) return
|
|
8
10
|
|
|
9
|
-
const internalType = type
|
|
11
|
+
const internalType = getInternalType(type)
|
|
10
12
|
|
|
11
13
|
switch (internalType) {
|
|
12
14
|
case 'config':
|
|
@@ -36,7 +38,7 @@ export const start = () => {
|
|
|
36
38
|
triggerEvent(type, data)
|
|
37
39
|
break
|
|
38
40
|
default:
|
|
39
|
-
|
|
41
|
+
log('[maglev][iframe] unknown message type', type)
|
|
40
42
|
break
|
|
41
43
|
}
|
|
42
44
|
})
|
|
@@ -46,4 +48,12 @@ export const start = () => {
|
|
|
46
48
|
const triggerEvent = (type, data) => {
|
|
47
49
|
const event = new CustomEvent(type, { detail: data })
|
|
48
50
|
window.dispatchEvent(event)
|
|
51
|
+
|
|
52
|
+
if (PAGE_UPDATED_EVENTS.includes(getInternalType(type))) {
|
|
53
|
+
window.dispatchEvent(new CustomEvent('maglev:page-updated', { detail: { type, data } }))
|
|
54
|
+
}
|
|
49
55
|
}
|
|
56
|
+
|
|
57
|
+
function getInternalType(type) {
|
|
58
|
+
return type.replace('maglev:', '')
|
|
59
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { start as startListeningEvents } from 'maglev-client/dom-operations'
|
|
2
2
|
import { start as startListeningMessages } from 'maglev-client/incoming-messages'
|
|
3
|
+
import { log } from 'maglev-client/utils'
|
|
3
4
|
|
|
4
5
|
const initializeClient = () => {
|
|
5
|
-
|
|
6
|
+
log('Maglev Client v3 🚆')
|
|
6
7
|
|
|
7
8
|
// no need to start the client when the site is being visited outside the editor
|
|
8
9
|
// (shouldn't happen, but just in case)
|
|
@@ -14,7 +15,7 @@ const initializeClient = () => {
|
|
|
14
15
|
// listen local events (converted from messages) and process them
|
|
15
16
|
startListeningEvents()
|
|
16
17
|
|
|
17
|
-
console.log('Maglev Client
|
|
18
|
+
console.log('Maglev Client v3 ✅')
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// Check if document is already ready
|
|
@@ -7,6 +7,28 @@ export const isBlank = (object) => {
|
|
|
7
7
|
)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const getParentEditorConfig = () => {
|
|
11
|
+
try {
|
|
12
|
+
return window.parent?.maglevEditorConfig
|
|
13
|
+
} catch (_) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getEditorConfig = () => {
|
|
19
|
+
return window.maglevEditorConfig || getParentEditorConfig() || {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const isEditorJsLogsEnabled = () => {
|
|
23
|
+
return getEditorConfig().jsLogsEnabled === true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const log = (...args) => {
|
|
27
|
+
if (isEditorJsLogsEnabled()) {
|
|
28
|
+
console.log(...args)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
export const debounce = (fn, time) => {
|
|
11
33
|
let timeoutId
|
|
12
34
|
function wrapper(...args) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
2
|
import { useDebounce } from 'stimulus-use'
|
|
3
|
+
import { log } from 'maglev-controllers/utils'
|
|
3
4
|
|
|
4
5
|
export default class extends Controller {
|
|
5
6
|
static targets = ['lockVersion']
|
|
@@ -20,7 +21,7 @@ export default class extends Controller {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
onSettingChange(event) {
|
|
23
|
-
|
|
24
|
+
log('[SectionForm::onSettingChange]', this.sourceId , event.detail)
|
|
24
25
|
const { detail: { settingType, settingId, value } } = event
|
|
25
26
|
this.dispatch('updateSetting', { detail: {
|
|
26
27
|
sourceId: this.sourceId,
|
|
@@ -30,12 +31,12 @@ export default class extends Controller {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
onPersist(event) {
|
|
33
|
-
|
|
34
|
+
log('[SectionForm::onPersist]', event.detail, typeof event.detail)
|
|
34
35
|
this.lockVersionTarget.value = event.detail.lockVersion
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
async afterSettingUpdate(event) {
|
|
38
|
-
|
|
39
|
+
log('[SectionForm::afterSettingUpdate] 🙌🙌', event.detail)
|
|
39
40
|
await this.element.requestSubmit()
|
|
40
41
|
|
|
41
42
|
if (!event.detail.updated) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import { log } from 'maglev-controllers/utils'
|
|
2
3
|
|
|
3
4
|
export default class extends Controller {
|
|
4
5
|
static values = { style: Array }
|
|
5
6
|
|
|
6
7
|
update(event) {
|
|
7
|
-
|
|
8
|
+
log('[StyleForm] update', event.detail, this.styleValue)
|
|
8
9
|
|
|
9
10
|
const newStyle = this.styleValue
|
|
10
11
|
newStyle.forEach(style => {
|
|
@@ -6,9 +6,12 @@ export default class extends Controller {
|
|
|
6
6
|
connect() {
|
|
7
7
|
this.setupTransformations()
|
|
8
8
|
this.numberOfSections = null
|
|
9
|
+
this.syncPreviewTimer = null
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
disconnect() {
|
|
13
|
+
clearTimeout(this.syncPreviewTimer)
|
|
14
|
+
this.syncPreviewTimer = null
|
|
12
15
|
this.teardownTransformations()
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -33,6 +36,14 @@ export default class extends Controller {
|
|
|
33
36
|
this.element.classList.remove('is-empty')
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
// force reload the iframe
|
|
40
|
+
reload() {
|
|
41
|
+
this.startLoading()
|
|
42
|
+
if (!this.reloadIframeInPlace()) {
|
|
43
|
+
this.iframeTarget.src = this.iframeTarget.src
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
// called when the Maglev client JS lib has been fully loaded on the iframe
|
|
37
48
|
clientReady(event) {
|
|
38
49
|
const { numberOfSections } = event.detail
|
|
@@ -47,20 +58,100 @@ export default class extends Controller {
|
|
|
47
58
|
this.updateEmptyMessageState()
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
//
|
|
61
|
+
// Preview markup lives outside the Turbo morph root (#root), so the iframe persists while <head>
|
|
62
|
+
// meta updates. Reading meta on turbo:load can race; we debounce. After Back, compare the live
|
|
63
|
+
// iframe document URL (same-origin) to meta, not only the src attribute.
|
|
51
64
|
detectUrlChange() {
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
clearTimeout(this.syncPreviewTimer)
|
|
66
|
+
this.syncPreviewTimer = setTimeout(() => {
|
|
67
|
+
this.syncPreviewTimer = null
|
|
68
|
+
this.syncPreview()
|
|
69
|
+
}, 0)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
syncPreview() {
|
|
73
|
+
const meta = document.querySelector('meta[name=page-preview-url]')
|
|
74
|
+
if (!meta?.content) return
|
|
75
|
+
|
|
76
|
+
let targetHref
|
|
77
|
+
try {
|
|
78
|
+
targetHref = new URL(meta.content, document.baseURI).href
|
|
79
|
+
} catch {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
54
82
|
|
|
55
|
-
|
|
83
|
+
const currentHref = this.livePreviewHref()
|
|
84
|
+
const targetKey = this.previewUrlKey(targetHref)
|
|
85
|
+
const currentKey = this.previewUrlKey(currentHref)
|
|
86
|
+
|
|
87
|
+
if (currentKey !== targetKey) {
|
|
56
88
|
this.startLoading()
|
|
57
|
-
this.
|
|
89
|
+
this.assignIframeSrcWithoutHistory(targetHref)
|
|
58
90
|
} else {
|
|
59
91
|
this.element.classList.add('is-loaded')
|
|
60
92
|
this.updateEmptyMessageState()
|
|
61
93
|
}
|
|
62
94
|
}
|
|
63
95
|
|
|
96
|
+
livePreviewHref() {
|
|
97
|
+
try {
|
|
98
|
+
const win = this.iframeTarget.contentWindow
|
|
99
|
+
const href = win?.location?.href
|
|
100
|
+
if (href && !href.startsWith("about:")) {
|
|
101
|
+
return new URL(href, document.baseURI).href
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// cross-origin preview document
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return new URL(this.iframeTarget.src, document.baseURI).href
|
|
108
|
+
} catch {
|
|
109
|
+
return this.iframeTarget.src
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
previewUrlKey(href) {
|
|
114
|
+
try {
|
|
115
|
+
const u = new URL(href, document.baseURI)
|
|
116
|
+
u.hash = ""
|
|
117
|
+
let path = u.pathname
|
|
118
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
119
|
+
path = path.slice(0, -1)
|
|
120
|
+
}
|
|
121
|
+
return `${u.origin}${path}${u.search}`
|
|
122
|
+
} catch {
|
|
123
|
+
return href
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Assigning iframe.src pushes a joint session-history entry in most browsers, so the first Back
|
|
128
|
+
// pops the iframe instead of the parent Turbo visit. replace/reload avoid that when same-origin.
|
|
129
|
+
assignIframeSrcWithoutHistory(resolvedHref) {
|
|
130
|
+
try {
|
|
131
|
+
const win = this.iframeTarget.contentWindow
|
|
132
|
+
if (win?.location?.replace) {
|
|
133
|
+
win.location.replace(resolvedHref)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// cross-origin iframe document: fall back to src
|
|
138
|
+
}
|
|
139
|
+
this.iframeTarget.src = resolvedHref
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
reloadIframeInPlace() {
|
|
143
|
+
try {
|
|
144
|
+
const win = this.iframeTarget.contentWindow
|
|
145
|
+
if (win?.location?.reload) {
|
|
146
|
+
win.location.reload()
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
// cross-origin: caller will fall back to reassigning src
|
|
151
|
+
}
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
|
|
64
155
|
updateEmptyMessageState() {
|
|
65
156
|
if (this.numberOfSections === 0) {
|
|
66
157
|
this.element.classList.add('is-empty')
|
data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
import { isSamePath } from"maglev-controllers/utils"
|
|
2
|
+
import { isSamePath, log } from "maglev-controllers/utils"
|
|
3
3
|
|
|
4
4
|
export default class extends Controller {
|
|
5
5
|
static targets = ["iframe"]
|
|
6
6
|
static values = {
|
|
7
7
|
sectionPath: String,
|
|
8
|
-
sectionBlockPath: String
|
|
8
|
+
sectionBlockPath: String,
|
|
9
|
+
primaryColor: String,
|
|
10
|
+
stickySectionIds: Array,
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
connect() {
|
|
12
14
|
this.isClientReady = false
|
|
13
|
-
this.clientReadyCallbacks = []
|
|
15
|
+
this.clientReadyCallbacks = []
|
|
16
|
+
this.prefetchedPaths = new Set()
|
|
17
|
+
this.lastConfiguredDocument = null
|
|
18
|
+
|
|
19
|
+
// The iframe `load` event can fire before Stimulus wires `load->sendConfig`.
|
|
20
|
+
// If that happens, no config is sent and the client stays undecorated.
|
|
21
|
+
this.sendConfigIfIframeAlreadyLoaded()
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
// called when the iframe DOM is loaded
|
|
17
|
-
sendConfig(
|
|
18
|
-
const {
|
|
25
|
+
sendConfig() {
|
|
26
|
+
const { primaryColorValue, stickySectionIdsValue } = this
|
|
27
|
+
const iframeDocument = this.iframeTarget?.contentDocument
|
|
28
|
+
if (!iframeDocument) return
|
|
29
|
+
|
|
30
|
+
// Prevent duplicate `config` for the same document (load + fallback path).
|
|
31
|
+
if (this.lastConfiguredDocument === iframeDocument) return
|
|
32
|
+
|
|
19
33
|
this.postMessage('config', {
|
|
20
|
-
primaryColor,
|
|
21
|
-
stickySectionIds,
|
|
34
|
+
primaryColor: primaryColorValue,
|
|
35
|
+
stickySectionIds: stickySectionIdsValue,
|
|
22
36
|
})
|
|
37
|
+
|
|
38
|
+
// Prevent duplicate `config` for the same document (load + fallback path).
|
|
39
|
+
this.lastConfiguredDocument = iframeDocument
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
// called when the Maglev client JS lib has been fully loaded on the iframe
|
|
@@ -30,61 +47,70 @@ export default class extends Controller {
|
|
|
30
47
|
|
|
31
48
|
// called by the iframe when the user clicks on a setting of a section or a section block
|
|
32
49
|
editSection(event) {
|
|
33
|
-
|
|
50
|
+
log('[PreviewNotificationCenter][editSection]', event.detail)
|
|
34
51
|
const { sectionId, sectionBlockId, settingId } = event.detail
|
|
35
|
-
const
|
|
36
|
-
const path = `${pathTemplate}#${settingId}`.replace(':section_id', sectionId).replace(':section_block_id', sectionBlockId)
|
|
52
|
+
const path = this.buildSettingPath({ sectionId, sectionBlockId, settingId })
|
|
37
53
|
|
|
38
54
|
if (isSamePath(path)) {
|
|
39
55
|
window.location.hash = settingId
|
|
40
56
|
} else {
|
|
41
|
-
|
|
57
|
+
this.visitPath(path)
|
|
42
58
|
}
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
editSectionBlock(event) {
|
|
46
|
-
|
|
62
|
+
log('[PreviewNotificationCenter][editSectionBlock]', event.detail)
|
|
47
63
|
const { sectionId, sectionBlockId } = event.detail
|
|
48
64
|
const pathTemplate = this.sectionBlockPathValue
|
|
49
65
|
const path = `${pathTemplate}#${sectionBlockId}`.replace(':section_id', sectionId).replace(':section_block_id', sectionBlockId)
|
|
50
|
-
|
|
66
|
+
this.visitPath(path)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
prefetchEditSectionOrBlock(event) {
|
|
70
|
+
const { sectionId, sectionBlockId, settingId } = event.detail
|
|
71
|
+
const path = this.buildSettingPath({ sectionId, sectionBlockId, settingId })
|
|
72
|
+
const prefetchPath = this.stripHash(path)
|
|
73
|
+
|
|
74
|
+
if (isSamePath(prefetchPath) || this.prefetchedPaths.has(prefetchPath)) return
|
|
75
|
+
|
|
76
|
+
this.enqueuePrefetch(prefetchPath)
|
|
51
77
|
}
|
|
52
78
|
|
|
53
79
|
// === SECTIONS ===
|
|
54
80
|
|
|
55
81
|
addSection(event) {
|
|
56
|
-
|
|
82
|
+
log('addSection', event.detail.fetchResponse.response.headers.get('X-Section-Id'), event.detail.fetchResponse.response.headers.get('X-Section-Position'))
|
|
57
83
|
const sectionId = event.detail.fetchResponse.response.headers.get('X-Section-Id')
|
|
58
84
|
const position = event.detail.fetchResponse.response.headers.get('X-Section-Position')
|
|
59
85
|
this.postMessage('section:add', { sectionId, insertAt: parseInt(position) })
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
deleteSection(event) {
|
|
63
|
-
|
|
89
|
+
log('deleteSection', event.params)
|
|
64
90
|
const { sectionId } = event.params
|
|
65
91
|
this.postMessage('section:remove', { sectionId })
|
|
66
92
|
}
|
|
67
93
|
|
|
68
94
|
moveSection(event) {
|
|
69
|
-
|
|
95
|
+
log('moveSection 💨💨💨', event.detail)
|
|
70
96
|
const { oldIndex, newIndex } = event.detail
|
|
71
97
|
this.postMessage('section:move', { oldIndex, newIndex })
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
updateSection(event) {
|
|
75
|
-
|
|
101
|
+
log('updateSection 🧼🧼🧼', event.detail)
|
|
76
102
|
const { sectionId } = event.detail
|
|
77
103
|
this.postMessage('section:update', { sectionId })
|
|
78
104
|
}
|
|
79
105
|
|
|
80
106
|
checkSectionLockVersion(event) {
|
|
81
|
-
|
|
107
|
+
log('checkSectionLockVersion 🕵🏻♂️🕵🏻♂️🕵🏻♂️', event)
|
|
82
108
|
const { sectionId, lockVersion } = event.detail
|
|
83
109
|
this.postMessage('section:checkLockVersion', { sectionId, lockVersion })
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
pingSection(event) {
|
|
87
|
-
|
|
113
|
+
log('pingSection 🏓🏓🏓', event.detail, this.isClientReady)
|
|
88
114
|
const { sectionId } = event.detail
|
|
89
115
|
this.postMessageWhenClientReady('section:ping', { sectionId })
|
|
90
116
|
}
|
|
@@ -92,26 +118,26 @@ export default class extends Controller {
|
|
|
92
118
|
// === SECTION BLOCKS ===
|
|
93
119
|
|
|
94
120
|
addSectionBlock(event) {
|
|
95
|
-
|
|
121
|
+
log('addSectionBlock ➕➕➕', event.params)
|
|
96
122
|
const { sectionId } = event.params
|
|
97
123
|
this.postMessage('block:add', { sectionId })
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
deleteSectionBlock(event) {
|
|
101
|
-
|
|
127
|
+
log('deleteSectionBlock 🗑️🗑️🗑️', event.params)
|
|
102
128
|
const { sectionId, sectionBlockId } = event.params
|
|
103
129
|
this.postMessage('block:remove', { sectionId, sectionBlockId })
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
moveSectionBlocks(event) {
|
|
107
|
-
|
|
133
|
+
log('moveSectionBlocks 💨💨💨', event.params)
|
|
108
134
|
const sectionId = event.params.sectionId
|
|
109
135
|
const { oldItemId: sectionBlockId, newItemId: targetSectionBlockId, direction } = event.detail
|
|
110
136
|
this.postMessage('block:move', { sectionId, sectionBlockId, targetSectionBlockId, direction })
|
|
111
137
|
}
|
|
112
138
|
|
|
113
139
|
pingSectionBlock(event) {
|
|
114
|
-
|
|
140
|
+
log('pingSectionBlock 🏓🏓🏓', event.detail)
|
|
115
141
|
const { sectionBlockId } = event.detail
|
|
116
142
|
this.postMessageWhenClientReady('block:ping', { sectionBlockId })
|
|
117
143
|
}
|
|
@@ -132,6 +158,62 @@ export default class extends Controller {
|
|
|
132
158
|
|
|
133
159
|
// === UTILS ===
|
|
134
160
|
|
|
161
|
+
buildSettingPath({ sectionId, sectionBlockId, settingId }) {
|
|
162
|
+
const pathTemplate = sectionBlockId ? this.sectionBlockPathValue : this.sectionPathValue
|
|
163
|
+
return `${pathTemplate}#${settingId}`.replace(':section_id', sectionId).replace(':section_block_id', sectionBlockId)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
stripHash(path) {
|
|
167
|
+
const url = new URL(path, window.location.origin)
|
|
168
|
+
url.hash = ''
|
|
169
|
+
return `${url.pathname}${url.search}`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
enqueuePrefetch(path) {
|
|
173
|
+
if (this.prefetchedPaths.has(path)) return
|
|
174
|
+
|
|
175
|
+
this.prefetchedPaths.add(path)
|
|
176
|
+
|
|
177
|
+
// Use Turbo's own prefetch pipeline so the visit cache is warmed,
|
|
178
|
+
// which avoids the full loading-bar behavior on subsequent click.
|
|
179
|
+
const link = document.createElement('a')
|
|
180
|
+
link.href = path
|
|
181
|
+
link.dataset.maglevPrefetch = 'true'
|
|
182
|
+
link.dataset.turboPrefetch = 'true'
|
|
183
|
+
link.hidden = true
|
|
184
|
+
document.body.appendChild(link)
|
|
185
|
+
|
|
186
|
+
link.dispatchEvent(new MouseEvent('mouseenter', {
|
|
187
|
+
bubbles: true,
|
|
188
|
+
cancelable: true,
|
|
189
|
+
view: window,
|
|
190
|
+
}))
|
|
191
|
+
|
|
192
|
+
setTimeout(() => link.remove(), 500)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
visitPath(path) {
|
|
196
|
+
const url = new URL(path, window.location.origin)
|
|
197
|
+
const visitPath = `${url.pathname}${url.search}`
|
|
198
|
+
const hash = url.hash
|
|
199
|
+
|
|
200
|
+
if (hash) this.applyHashAfterVisit(hash)
|
|
201
|
+
Turbo.visit(visitPath)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
applyHashAfterVisit(hash) {
|
|
205
|
+
window.addEventListener('turbo:load', () => {
|
|
206
|
+
window.location.hash = hash
|
|
207
|
+
}, { once: true })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
sendConfigIfIframeAlreadyLoaded() {
|
|
211
|
+
const iframeDocument = this.iframeTarget?.contentDocument
|
|
212
|
+
if (!iframeDocument || iframeDocument.readyState !== 'complete') return
|
|
213
|
+
|
|
214
|
+
this.sendConfig()
|
|
215
|
+
}
|
|
216
|
+
|
|
135
217
|
postMessage(type, data) {
|
|
136
218
|
this.iframeTarget.contentWindow.postMessage({ type: `maglev:${type}`, ...(data || {}) }, '*')
|
|
137
219
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import { log } from 'maglev-controllers/utils'
|
|
2
3
|
|
|
3
4
|
export default class extends Controller {
|
|
4
5
|
static values = {
|
|
@@ -8,7 +9,7 @@ export default class extends Controller {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
connect() {
|
|
11
|
-
|
|
12
|
+
log('[Editor::Setting] connect', this.settingIdValue)
|
|
12
13
|
|
|
13
14
|
if (window.location.hash) {
|
|
14
15
|
// dirty way to wait for the underlying controller to be connected
|
|
@@ -17,7 +18,7 @@ export default class extends Controller {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
change(event) {
|
|
20
|
-
|
|
21
|
+
log('[Editor::Setting] change', event.detail)
|
|
21
22
|
this.dispatch('change', {
|
|
22
23
|
detail: {
|
|
23
24
|
settingId: this.settingIdValue,
|
data/app/assets/javascripts/maglev/editor/controllers/shared/copy_to_clipboard_controller.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import { log } from 'maglev-controllers/utils'
|
|
2
3
|
|
|
3
4
|
export default class extends Controller {
|
|
4
5
|
static targets = ['text', 'success']
|
|
@@ -15,7 +16,7 @@ export default class extends Controller {
|
|
|
15
16
|
if (navigator.clipboard) {
|
|
16
17
|
navigator.clipboard.writeText(this.sourceValue)
|
|
17
18
|
} else {
|
|
18
|
-
|
|
19
|
+
log('Clipboard API not supported or unavailable.')
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
this.timeout = setTimeout(() => {
|