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
|
@@ -4,6 +4,7 @@ module Maglev
|
|
|
4
4
|
module Editor
|
|
5
5
|
class SectionBlocksController < Maglev::Editor::BaseController
|
|
6
6
|
helper Maglev::Editor::SettingsHelper
|
|
7
|
+
helper_method :source_lock_version
|
|
7
8
|
|
|
8
9
|
before_action :set_section
|
|
9
10
|
before_action :set_section_block, only: %i[edit update destroy]
|
|
@@ -31,7 +32,6 @@ module Maglev
|
|
|
31
32
|
|
|
32
33
|
def update
|
|
33
34
|
update_section_block
|
|
34
|
-
refresh_lock_version
|
|
35
35
|
flash.now[:notice] = flash_t(:success)
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -59,12 +59,12 @@ module Maglev
|
|
|
59
59
|
|
|
60
60
|
def set_section
|
|
61
61
|
@section = current_maglev_sections.find { |section| section.id == params[:section_id] }
|
|
62
|
-
|
|
62
|
+
redirect_to editor_sections_path_with_context unless @section
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def set_section_block
|
|
66
66
|
@section_block = @section.blocks.find(params[:id])
|
|
67
|
-
|
|
67
|
+
redirect_to editor_sections_path_with_context unless @section_block
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def update_section_block
|
|
@@ -77,12 +77,12 @@ module Maglev
|
|
|
77
77
|
)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
86
|
end
|
|
87
87
|
|
|
88
88
|
def redirect_to_section_blocks_path(success: true)
|
|
@@ -96,6 +96,10 @@ module Maglev
|
|
|
96
96
|
|
|
97
97
|
redirect_to path, status: :see_other, **flash
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
def editor_sections_path_with_context
|
|
101
|
+
editor_sections_path(maglev_editing_route_context)
|
|
102
|
+
end
|
|
99
103
|
end
|
|
100
104
|
end
|
|
101
105
|
end
|
|
@@ -4,7 +4,9 @@ module Maglev
|
|
|
4
4
|
module Editor
|
|
5
5
|
class SectionsController < Maglev::Editor::BaseController
|
|
6
6
|
helper Maglev::Editor::SettingsHelper
|
|
7
|
+
helper_method :source_lock_version
|
|
7
8
|
|
|
9
|
+
before_action :ensure_turbo_frame_request, only: [:new]
|
|
8
10
|
before_action :set_section, only: %i[edit update]
|
|
9
11
|
|
|
10
12
|
def show
|
|
@@ -12,8 +14,11 @@ module Maglev
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def new
|
|
15
|
-
|
|
17
|
+
set_query_and_category_id
|
|
16
18
|
@position = (params[:position] || -1).to_i
|
|
19
|
+
@theme_sections = maglev_theme.sections.filter(current_maglev_sections, keyword: @query,
|
|
20
|
+
category_id: @category_id)
|
|
21
|
+
render layout: false
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def create
|
|
@@ -34,7 +39,6 @@ module Maglev
|
|
|
34
39
|
|
|
35
40
|
def update
|
|
36
41
|
update_section
|
|
37
|
-
refresh_lock_version
|
|
38
42
|
flash.now[:notice] = flash_t(:success)
|
|
39
43
|
end
|
|
40
44
|
|
|
@@ -56,7 +60,7 @@ module Maglev
|
|
|
56
60
|
|
|
57
61
|
def set_section
|
|
58
62
|
@section = current_maglev_sections.find { |section| section.id == params[:id] }
|
|
59
|
-
|
|
63
|
+
redirect_to editor_sections_path_with_context unless @section
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
def update_section
|
|
@@ -73,6 +77,14 @@ module Maglev
|
|
|
73
77
|
render 'index', status: :unprocessable_content
|
|
74
78
|
end
|
|
75
79
|
|
|
80
|
+
def set_query_and_category_id
|
|
81
|
+
# we can't filter by both query and category_id in the same time
|
|
82
|
+
@query = params[:category_id].present? ? nil : params[:query]
|
|
83
|
+
# if no category_id is provided AND we don't have a query, we take the first category
|
|
84
|
+
@category_id = params[:category_id] || maglev_theme.section_categories.first.id
|
|
85
|
+
@category_id = nil if @query.present?
|
|
86
|
+
end
|
|
87
|
+
|
|
76
88
|
def newly_added_section_to_flash
|
|
77
89
|
# use flash because we can't pass directly the information to the redirect
|
|
78
90
|
{ section_id: @section[:id], position: current_maglev_page.position_of_section(@section[:id]) }
|
|
@@ -83,13 +95,20 @@ module Maglev
|
|
|
83
95
|
headers['X-Section-Position'] = flash[:position]
|
|
84
96
|
end
|
|
85
97
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
89
104
|
end
|
|
90
105
|
|
|
91
106
|
def redirect_to_sections_path
|
|
92
|
-
redirect_to
|
|
107
|
+
redirect_to editor_sections_path_with_context, notice: flash_t(:success), status: :see_other
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def editor_sections_path_with_context
|
|
111
|
+
editor_sections_path(maglev_editing_route_context)
|
|
93
112
|
end
|
|
94
113
|
end
|
|
95
114
|
end
|
|
@@ -13,6 +13,10 @@ module Maglev
|
|
|
13
13
|
helper Maglev::PagePreviewHelper
|
|
14
14
|
|
|
15
15
|
def index
|
|
16
|
+
# use the title + SEO informations from the published payload
|
|
17
|
+
# Warning: the page may not exist even in draft
|
|
18
|
+
maglev_page&.apply_published_payload
|
|
19
|
+
|
|
16
20
|
render_maglev_page
|
|
17
21
|
end
|
|
18
22
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
class SiteController < Maglev::ApplicationController
|
|
5
|
+
def create
|
|
6
|
+
redirect_to editor_root_path and return if Maglev::Site.exists? || !Rails.env.local?
|
|
7
|
+
|
|
8
|
+
Maglev::GenerateSite.call(
|
|
9
|
+
theme: Maglev.local_themes.first
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
redirect_to editor_root_path
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# rubocop:disable Metrics/ModuleLength
|
|
4
4
|
module Maglev
|
|
5
5
|
module ApplicationHelper
|
|
6
|
+
## "system" helpers
|
|
6
7
|
def turbo_stream
|
|
7
8
|
# we don't want to pollute the global Turbo::Streams::TagBuilder
|
|
8
9
|
Maglev::Turbo::Streams::TagBuilder.new(self)
|
|
@@ -26,6 +27,17 @@ module Maglev
|
|
|
26
27
|
], "\n"
|
|
27
28
|
end
|
|
28
29
|
|
|
30
|
+
def maglev_delayed_stream_tag
|
|
31
|
+
# since we set the turbo request id before the build of the fetch request, Turbo will set
|
|
32
|
+
# the X-TURBO-REQUEST-ID header to a comma-separated list of request ids
|
|
33
|
+
# we only want to use the first request id (ours), so we split the header value and take the first part
|
|
34
|
+
safe_join [
|
|
35
|
+
tag.meta(name: 'turbo-request-id', content: ::Turbo.current_request_id.split(',').first),
|
|
36
|
+
tag.meta(name: 'turbo-delayed-stream', content: 'true')
|
|
37
|
+
], "\n"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
## Editor helpers
|
|
29
41
|
def maglev_editor_title
|
|
30
42
|
case maglev_config.title
|
|
31
43
|
when nil
|
|
@@ -93,20 +105,20 @@ module Maglev
|
|
|
93
105
|
def maglev_button_classes(...)
|
|
94
106
|
ClassVariants.build(
|
|
95
107
|
base: %(
|
|
96
|
-
rounded-
|
|
97
|
-
group-[.is-success]/form:bg-green-500
|
|
108
|
+
rounded-sm transition-colors transition-background duration-200 text-center cursor-pointer
|
|
109
|
+
group-[.is-success]/form:bg-green-500 group-[.is-success]/form:hover:bg-green-500/90
|
|
98
110
|
group-[.is-success]/form:disabled:bg-green-500/75
|
|
99
|
-
group-[.is-error]/form:bg-red-500
|
|
111
|
+
group-[.is-error]/form:bg-red-500 group-[.is-error]/form:hover:bg-red-500/90
|
|
100
112
|
group-[.is-error]/form:disabled:bg-red-500/75
|
|
101
113
|
),
|
|
102
114
|
variants: {
|
|
103
115
|
color: {
|
|
104
|
-
primary: 'text-white bg-editor-primary
|
|
116
|
+
primary: 'text-white bg-editor-primary hover:bg-editor-primary/90 disabled:bg-editor-primary/75',
|
|
105
117
|
secondary: 'text-gray-800 hover:bg-gray-100'
|
|
106
118
|
},
|
|
107
119
|
size: {
|
|
108
|
-
big: 'flex items-center justify-center w-full px-6
|
|
109
|
-
medium: 'inline-flex items-center justify-center px-4
|
|
120
|
+
big: 'flex items-center justify-center w-full px-6 h-14',
|
|
121
|
+
medium: 'inline-flex items-center justify-center px-4 h-10'
|
|
110
122
|
}
|
|
111
123
|
},
|
|
112
124
|
defaults: {
|
|
@@ -139,9 +151,15 @@ module Maglev
|
|
|
139
151
|
disappear_after: 3.seconds).with_content(message)
|
|
140
152
|
end
|
|
141
153
|
|
|
142
|
-
def maglev_page_icon(page, size: '1.15rem')
|
|
154
|
+
def maglev_page_icon(page, size: '1.15rem', wrapper_class_names: nil)
|
|
143
155
|
icon_name = page.index? ? 'home' : 'file'
|
|
144
|
-
|
|
156
|
+
content_tag :span, class: class_names('shrink-0 relative', wrapper_class_names) do
|
|
157
|
+
if page.need_to_be_published?
|
|
158
|
+
concat(content_tag(:span, '',
|
|
159
|
+
class: 'absolute -bottom-0.25 right-0 bg-yellow-600 rounded-full w-1.5 h-1.5'))
|
|
160
|
+
end
|
|
161
|
+
concat render(Maglev::Uikit::IconComponent.new(name: icon_name, size: size))
|
|
162
|
+
end
|
|
145
163
|
end
|
|
146
164
|
|
|
147
165
|
def maglev_page_preview_reload_data
|
|
@@ -39,7 +39,7 @@ module Maglev
|
|
|
39
39
|
def add_section_block_no_dropdown_button(section, block_type)
|
|
40
40
|
button_to(
|
|
41
41
|
t('maglev.editor.section_blocks.index.add_button'),
|
|
42
|
-
editor_section_blocks_path(section, block_type: block_type, lock_version:
|
|
42
|
+
editor_section_blocks_path(section, block_type: block_type, lock_version: source_lock_version,
|
|
43
43
|
**maglev_editing_route_context),
|
|
44
44
|
class: maglev_button_classes(size: :big),
|
|
45
45
|
**section_block_button_common_attributes(section)
|
|
@@ -72,7 +72,7 @@ module Maglev
|
|
|
72
72
|
def add_child_section_block_no_dropdown_button(section, parent_id, block_type)
|
|
73
73
|
button_to(
|
|
74
74
|
editor_section_blocks_path(section, block_type: block_type, parent_id: parent_id,
|
|
75
|
-
lock_version:
|
|
75
|
+
lock_version: source_lock_version,
|
|
76
76
|
**maglev_editing_route_context),
|
|
77
77
|
class: maglev_icon_button_classes,
|
|
78
78
|
name: 'add_child_section_block',
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
|
4
|
+
module Maglev::Page::PublishableConcern
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
# force JSON column for MariaDB
|
|
9
|
+
attribute :published_payload, :json
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def published?
|
|
13
|
+
published_at.present?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def need_to_be_published?
|
|
17
|
+
!published? || updated_at.blank? || updated_at > published_at
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# opposite of #need_to_be_published?
|
|
21
|
+
def published_and_up_to_date?
|
|
22
|
+
published? && updated_at <= published_at
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def discard_draft?
|
|
26
|
+
published? && updated_at > published_at
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# called when a publishedpage is being previewed
|
|
30
|
+
def apply_published_payload
|
|
31
|
+
return if !published? || published_payload.blank?
|
|
32
|
+
|
|
33
|
+
published_payload_attributes.each do |attribute|
|
|
34
|
+
send("#{attribute}=", published_payload[attribute])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# called when a page is being published
|
|
39
|
+
def update_published_payload
|
|
40
|
+
# in MySQL, default values for json columns are not supported, so we need to set an empty hash if the value is nil
|
|
41
|
+
self.published_payload ||= {}
|
|
42
|
+
|
|
43
|
+
published_payload_attributes.each do |attribute|
|
|
44
|
+
value = send(attribute.to_sym)
|
|
45
|
+
|
|
46
|
+
# in MySQL, default values for json columns are not supported, so we need to set an empty hash if the value is nil
|
|
47
|
+
value = {} if attribute.ends_with?('_translations') && value.nil?
|
|
48
|
+
|
|
49
|
+
self.published_payload[attribute] = value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def published_payload_attributes
|
|
56
|
+
published_payload_core_attributes + published_payload_additional_attributes
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def published_payload_additional_attributes
|
|
60
|
+
# override this method to add additional attributes to the published payload
|
|
61
|
+
[]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def published_payload_core_attributes
|
|
65
|
+
%w[title_translations seo_title_translations meta_description_translations og_title_translations
|
|
66
|
+
og_description_translations og_image_url_translations]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
data/app/models/maglev/page.rb
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
# og_description_translations :jsonb
|
|
11
11
|
# og_image_url_translations :jsonb
|
|
12
12
|
# og_title_translations :jsonb
|
|
13
|
+
# published_at :datetime
|
|
14
|
+
# published_payload :jsonb
|
|
13
15
|
# sections_translations :jsonb
|
|
14
16
|
# seo_title_translations :jsonb
|
|
15
17
|
# title_translations :jsonb
|
|
@@ -24,6 +26,7 @@ module Maglev
|
|
|
24
26
|
include Maglev::SectionsConcern
|
|
25
27
|
include Maglev::Page::PathConcern
|
|
26
28
|
include Maglev::Page::SearchConcern
|
|
29
|
+
include Maglev::Page::PublishableConcern
|
|
27
30
|
|
|
28
31
|
## associations ##
|
|
29
32
|
has_many :sections_content_stores, as: :container, dependent: :destroy
|
|
@@ -85,13 +85,15 @@ module Maglev::Section::ContentConcern
|
|
|
85
85
|
return [] if blocks.blank?
|
|
86
86
|
|
|
87
87
|
blocks.map do |block|
|
|
88
|
+
next unless block.root?
|
|
89
|
+
|
|
88
90
|
3.times.to_a.map do
|
|
89
91
|
{
|
|
90
92
|
type: block.type,
|
|
91
93
|
settings: fallback_build_default_settings_content(block.settings)
|
|
92
94
|
}
|
|
93
95
|
end
|
|
94
|
-
end.flatten
|
|
96
|
+
end.compact.flatten
|
|
95
97
|
end
|
|
96
98
|
end
|
|
97
99
|
# rubocop:enable Style/ClassAndModuleChildren
|
|
@@ -78,6 +78,14 @@ module Maglev
|
|
|
78
78
|
"maglev.themes.#{theme.id}.sections.#{id}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
def empty?
|
|
82
|
+
settings.none? && blocks.none?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def local_screenshot?
|
|
86
|
+
screenshot_timestamp.present?
|
|
87
|
+
end
|
|
88
|
+
|
|
81
89
|
## class methods ##
|
|
82
90
|
|
|
83
91
|
def self.build(hash)
|
|
@@ -104,7 +112,7 @@ module Maglev
|
|
|
104
112
|
|
|
105
113
|
class Store
|
|
106
114
|
extend Forwardable
|
|
107
|
-
def_delegators :@array, :all, :first, :last, :count, :each, :each_with_index, :map, :group_by
|
|
115
|
+
def_delegators :@array, :all, :first, :last, :count, :each, :each_with_index, :map, :group_by, :any?
|
|
108
116
|
|
|
109
117
|
attr_reader :array
|
|
110
118
|
|
|
@@ -134,6 +142,18 @@ module Maglev
|
|
|
134
142
|
self.class.new(new_array)
|
|
135
143
|
end
|
|
136
144
|
|
|
145
|
+
def filter(sections_content, keyword: nil, category_id: nil)
|
|
146
|
+
new_array = if keyword.present?
|
|
147
|
+
@array.select { |section| section.name.downcase.include?(keyword.downcase) }
|
|
148
|
+
elsif category_id.present?
|
|
149
|
+
@array.select { |section| section.category == category_id }
|
|
150
|
+
else
|
|
151
|
+
@array
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
self.class.new(new_array).available_for(sections_content)
|
|
155
|
+
end
|
|
156
|
+
|
|
137
157
|
def as_json(**_options)
|
|
138
158
|
@array.as_json
|
|
139
159
|
end
|
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Indexes
|
|
16
16
|
#
|
|
17
|
-
#
|
|
18
|
-
# maglev_sections_content_stores_container (container_id,container_type)
|
|
19
|
-
# maglev_sections_content_stores_container_and_published (container_id,container_type,published)
|
|
17
|
+
# maglev_sections_content_stores_container_and_published (container_id,container_type,published) UNIQUE
|
|
20
18
|
#
|
|
21
19
|
module Maglev
|
|
22
20
|
class SectionsContentStore < ApplicationRecord
|
data/app/models/maglev/site.rb
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# locales :jsonb
|
|
9
9
|
# lock_version :integer
|
|
10
10
|
# name :string
|
|
11
|
+
# published_at :datetime
|
|
11
12
|
# sections_translations :jsonb
|
|
12
13
|
# style :jsonb
|
|
13
14
|
# created_at :datetime not null
|
|
@@ -35,6 +36,10 @@ module Maglev
|
|
|
35
36
|
|
|
36
37
|
## methods ##
|
|
37
38
|
|
|
39
|
+
def published?
|
|
40
|
+
published_at.present?
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
def api_attributes
|
|
39
44
|
%i[id name]
|
|
40
45
|
end
|
|
@@ -68,22 +68,19 @@ module Maglev
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def check_section_lock_version!(source)
|
|
71
|
-
check_lock_version!(source,
|
|
71
|
+
check_lock_version!(source, 'update_section')
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def check_block_lock_version!(source)
|
|
75
|
-
check_lock_version!(source,
|
|
75
|
+
check_lock_version!(source, 'update_block')
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def check_lock_version!(source,
|
|
78
|
+
def check_lock_version!(source, action_name)
|
|
79
79
|
return if lock_version.blank? # without a lock version, we disable the lock version check
|
|
80
80
|
|
|
81
|
-
current_lock_version =
|
|
81
|
+
current_lock_version = source.lock_version.to_i
|
|
82
|
+
source.lock_version = lock_version.to_i
|
|
82
83
|
|
|
83
|
-
# always increment the lock version
|
|
84
|
-
section_or_block['lock_version'] = lock_version.to_i + 1
|
|
85
|
-
|
|
86
|
-
# if the lock version is the same, we don't need to raise an error
|
|
87
84
|
return if current_lock_version == lock_version.to_i
|
|
88
85
|
|
|
89
86
|
raise ActiveRecord::StaleObjectError.new(source, action_name)
|
|
@@ -8,7 +8,7 @@ module Maglev
|
|
|
8
8
|
Maglev.config
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# hold the Rails request context
|
|
11
|
+
# hold the Rails HTTP request context
|
|
12
12
|
dependency :context
|
|
13
13
|
|
|
14
14
|
dependency :fetch_site, class: Maglev::FetchSite, depends_on: %i[config context]
|
|
@@ -19,7 +19,7 @@ module Maglev
|
|
|
19
19
|
dependency :fetch_section_screenshot_path, class: Maglev::FetchSectionScreenshotPath,
|
|
20
20
|
depends_on: :fetch_sections_path
|
|
21
21
|
dependency :fetch_section_screenshot_url, class: Maglev::FetchSectionScreenshotUrl,
|
|
22
|
-
depends_on:
|
|
22
|
+
depends_on: %i[fetch_section_screenshot_path context]
|
|
23
23
|
|
|
24
24
|
dependency :fetch_collection_items, class: Maglev::FetchCollectionItems, depends_on: %i[fetch_site config]
|
|
25
25
|
dependency :fetch_static_pages, class: Maglev::FetchStaticPages, depends_on: %i[config]
|
|
@@ -67,7 +67,9 @@ module Maglev
|
|
|
67
67
|
dependency :sort_section_blocks, class: Maglev::Content::SortSectionBlocksService,
|
|
68
68
|
depends_on: %i[fetch_site fetch_theme]
|
|
69
69
|
|
|
70
|
+
dependency :has_unpublished_changes, class: Maglev::HasUnpublishedChanges
|
|
70
71
|
dependency :publish, class: Maglev::PublishService
|
|
72
|
+
dependency :discard_page_draft, class: Maglev::DiscardPageDraftService
|
|
71
73
|
|
|
72
74
|
def call
|
|
73
75
|
self
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
class DiscardPageDraftService
|
|
5
|
+
include Injectable
|
|
6
|
+
|
|
7
|
+
argument :site
|
|
8
|
+
argument :page
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
ActiveRecord::Base.transaction do
|
|
12
|
+
revert_container_sections!(site)
|
|
13
|
+
revert_container_sections!(page)
|
|
14
|
+
revert_page_information!
|
|
15
|
+
end
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def revert_container_sections!(container)
|
|
22
|
+
store = find_published_store(container)
|
|
23
|
+
|
|
24
|
+
raise Maglev::Errors::UnpublishedPage if store.blank?
|
|
25
|
+
|
|
26
|
+
container.sections_translations_will_change!
|
|
27
|
+
container.sections_translations = store.sections_translations
|
|
28
|
+
container.save!
|
|
29
|
+
|
|
30
|
+
# Update updated_at to be before published_at to mark as up-to-date
|
|
31
|
+
# rubocop:disable Rails/SkipsModelValidations
|
|
32
|
+
container.update_column(:updated_at, container.published_at) if container.published_at.present?
|
|
33
|
+
# rubocop:enable Rails/SkipsModelValidations
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_published_store(container)
|
|
37
|
+
container.sections_content_stores.published.first
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def revert_page_information!
|
|
41
|
+
page.apply_published_payload
|
|
42
|
+
page.save!
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -5,9 +5,13 @@ module Maglev
|
|
|
5
5
|
include Injectable
|
|
6
6
|
|
|
7
7
|
dependency :fetch_section_screenshot_path
|
|
8
|
+
dependency :context
|
|
9
|
+
|
|
8
10
|
argument :section
|
|
9
11
|
|
|
10
12
|
def call
|
|
13
|
+
return nil unless section.local_screenshot?
|
|
14
|
+
|
|
11
15
|
screenshot_path = fetch_section_screenshot_path.call(section: section) + query_string
|
|
12
16
|
asset_host ? URI.join(asset_host, screenshot_path).to_s : screenshot_path
|
|
13
17
|
end
|
|
@@ -15,11 +19,18 @@ module Maglev
|
|
|
15
19
|
private
|
|
16
20
|
|
|
17
21
|
def asset_host
|
|
18
|
-
Rails.application.config.asset_host
|
|
22
|
+
host = Rails.application.config.asset_host
|
|
23
|
+
return nil if host.blank?
|
|
24
|
+
|
|
25
|
+
host.start_with?('http://', 'https://') ? host : "#{request_protocol}#{host}"
|
|
19
26
|
end
|
|
20
27
|
|
|
21
28
|
def query_string
|
|
22
29
|
"?#{section.screenshot_timestamp}"
|
|
23
30
|
end
|
|
31
|
+
|
|
32
|
+
def request_protocol
|
|
33
|
+
context.controller.request.protocol
|
|
34
|
+
end
|
|
24
35
|
end
|
|
25
36
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
class HasUnpublishedChanges
|
|
5
|
+
include Injectable
|
|
6
|
+
|
|
7
|
+
argument :site
|
|
8
|
+
argument :page
|
|
9
|
+
argument :theme
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
page.need_to_be_published? || site_need_to_be_published?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def site_need_to_be_published?
|
|
18
|
+
!site.published? || site.updated_at.blank? || site.updated_at > site.published_at
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -9,22 +9,38 @@ module Maglev
|
|
|
9
9
|
|
|
10
10
|
def call
|
|
11
11
|
ActiveRecord::Base.transaction do
|
|
12
|
-
|
|
13
|
-
publish_container_sections!(page)
|
|
12
|
+
unsafe_call
|
|
14
13
|
end
|
|
15
14
|
true
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
private
|
|
19
18
|
|
|
19
|
+
def unsafe_call
|
|
20
|
+
# copy content from the containers (site and page) to the published stores
|
|
21
|
+
publish_container_sections!(site)
|
|
22
|
+
publish_container_sections!(page)
|
|
23
|
+
|
|
24
|
+
# copy the page information to the page published payload
|
|
25
|
+
publish_page_information!
|
|
26
|
+
end
|
|
27
|
+
|
|
20
28
|
def publish_container_sections!(container)
|
|
21
29
|
store = find_or_build_published_store(container)
|
|
22
30
|
store.sections_translations = container.sections_translations
|
|
23
31
|
store.save!
|
|
32
|
+
# mark the container as published.
|
|
33
|
+
# We need to add a delay to ensure that published_at will be posterior to the native updated_at of the container.
|
|
34
|
+
container.update(published_at: Time.current + 0.2.seconds)
|
|
24
35
|
end
|
|
25
36
|
|
|
26
37
|
def find_or_build_published_store(container)
|
|
27
38
|
container.sections_content_stores.find_or_initialize_by(container: container, published: true)
|
|
28
39
|
end
|
|
40
|
+
|
|
41
|
+
def publish_page_information!
|
|
42
|
+
page.update_published_payload
|
|
43
|
+
page.save!
|
|
44
|
+
end
|
|
29
45
|
end
|
|
30
46
|
end
|