maglevcms 3.0.0.beta3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -53
  3. data/Rakefile +3 -1
  4. data/app/assets/builds/maglev/tailwind.css +424 -84
  5. data/app/assets/config/maglev_manifest.js +3 -2
  6. data/app/assets/javascripts/maglev/client/dom-operations.js +5 -5
  7. data/app/assets/javascripts/maglev/client/iframe-decorator.js +24 -0
  8. data/app/assets/javascripts/maglev/client/incoming-messages.js +13 -3
  9. data/app/assets/javascripts/maglev/client/index.js +3 -2
  10. data/app/assets/javascripts/maglev/client/utils.js +22 -0
  11. data/app/assets/javascripts/maglev/editor/controllers/app/forms/section_form_controller.js +4 -3
  12. data/app/assets/javascripts/maglev/editor/controllers/app/forms/style_form_controller.js +2 -1
  13. data/app/assets/javascripts/maglev/editor/controllers/app/page_preview_controller.js +96 -5
  14. data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js +105 -23
  15. data/app/assets/javascripts/maglev/editor/controllers/app/setting_controller.js +3 -2
  16. data/app/assets/javascripts/maglev/editor/controllers/shared/copy_to_clipboard_controller.js +2 -1
  17. data/app/assets/javascripts/maglev/editor/controllers/utils.js +22 -0
  18. data/app/assets/javascripts/maglev/editor/index.js +2 -1
  19. data/app/assets/javascripts/maglev/editor/patches/turbo_stream_patch.js +3 -3
  20. data/app/assets/stylesheets/maglev/tailwind.css.erb +8 -1
  21. data/app/components/maglev/content/link.rb +1 -1
  22. data/app/components/maglev/editor/settings/link/link_component.rb +7 -1
  23. data/app/components/maglev/section_component.rb +11 -1
  24. data/app/components/maglev/uikit/app_layout/sidebar/link_component.html.erb +1 -1
  25. data/app/components/maglev/uikit/app_layout/sidebar/link_component.rb +35 -5
  26. data/app/components/maglev/uikit/app_layout/sidebar_component.html.erb +3 -4
  27. data/app/components/maglev/uikit/app_layout/topbar_component.html.erb +1 -1
  28. data/app/components/maglev/uikit/app_layout/topbar_component.rb +1 -1
  29. data/app/components/maglev/uikit/button_group_component/button_group_component.html.erb +5 -0
  30. data/app/components/maglev/uikit/button_group_component.rb +70 -0
  31. data/app/components/maglev/uikit/device_toggler_component.rb +10 -1
  32. data/app/components/maglev/uikit/dropdown_component/dropdown_component.html.erb +2 -2
  33. data/app/components/maglev/uikit/dropdown_component.rb +6 -1
  34. data/app/components/maglev/uikit/form/color_field_component.html.erb +1 -1
  35. data/app/components/maglev/uikit/form/combobox_component.html.erb +1 -0
  36. data/app/components/maglev/uikit/form/link_component.rb +5 -1
  37. data/app/components/maglev/uikit/form/richtext_controller.js +5 -4
  38. data/app/components/maglev/uikit/form/search_form_component.html.erb +1 -0
  39. data/app/components/maglev/uikit/icon_component.rb +3 -0
  40. data/app/components/maglev/uikit/image_library/uploader_controller.js +3 -2
  41. data/app/components/maglev/uikit/list/list_item_component.html.erb +36 -19
  42. data/app/components/maglev/uikit/list/list_item_component.rb +19 -5
  43. data/app/components/maglev/uikit/locale_switcher_component/locale_switcher_component.html.erb +6 -10
  44. data/app/components/maglev/uikit/menu_dropdown_component/menu_dropdown_component.html.erb +6 -2
  45. data/app/components/maglev/uikit/menu_dropdown_component.rb +244 -7
  46. data/app/components/maglev/uikit/page_actions_dropdown_component/page_actions_dropdown_component.html.erb +39 -46
  47. data/app/components/maglev/uikit/pagination_component/pagination_component.html.erb +9 -12
  48. data/app/components/maglev/uikit/pagination_component.rb +6 -1
  49. data/app/components/maglev/uikit/section_toolbar/bottom_component.html.erb +1 -1
  50. data/app/components/maglev/uikit/tabs_component/tabs_component.html.erb +7 -4
  51. data/app/components/maglev/uikit/tabs_component.rb +23 -4
  52. data/app/components/maglev/uikit/well/simple_well_component.html.erb +15 -0
  53. data/app/components/maglev/uikit/well/simple_well_component.rb +13 -0
  54. data/app/controllers/concerns/maglev/editor/preview_urls_concern.rb +32 -0
  55. data/app/controllers/concerns/maglev/editor/turbo_concern.rb +29 -0
  56. data/app/controllers/concerns/maglev/flash_i18n_concern.rb +1 -0
  57. data/app/controllers/maglev/application_controller.rb +1 -1
  58. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +2 -1
  59. data/app/controllers/maglev/editor/assets_controller.rb +1 -1
  60. data/app/controllers/maglev/editor/base_controller.rb +6 -32
  61. data/app/controllers/maglev/editor/pages/clone_controller.rb +22 -0
  62. data/app/controllers/maglev/editor/pages/discard_draft_controller.rb +17 -0
  63. data/app/controllers/maglev/editor/pages_controller.rb +26 -7
  64. data/app/controllers/maglev/editor/section_blocks_controller.rb +13 -9
  65. data/app/controllers/maglev/editor/sections_controller.rb +26 -7
  66. data/app/controllers/maglev/published_page_preview_controller.rb +4 -0
  67. data/app/helpers/maglev/application_helper.rb +6 -6
  68. data/app/helpers/maglev/editor/section_blocks_helper.rb +2 -2
  69. data/app/models/maglev/page/publishable_concern.rb +69 -0
  70. data/app/models/maglev/page.rb +2 -13
  71. data/app/models/maglev/section/block.rb +4 -0
  72. data/app/models/maglev/section/content_concern.rb +3 -1
  73. data/app/models/maglev/section.rb +21 -1
  74. data/app/models/maglev/sections_content_store.rb +1 -3
  75. data/app/services/concerns/maglev/content/helpers_concern.rb +5 -8
  76. data/app/services/maglev/app_container.rb +4 -2
  77. data/app/services/maglev/discard_page_draft_service.rb +45 -0
  78. data/app/services/maglev/fetch_section_screenshot_url.rb +12 -1
  79. data/app/services/maglev/has_unpublished_changes.rb +21 -0
  80. data/app/services/maglev/publish_service.rb +15 -2
  81. data/app/views/layouts/maglev/editor/_sidebar.html.erb +6 -1
  82. data/app/views/layouts/maglev/editor/application.html.erb +5 -0
  83. data/app/views/layouts/maglev/editor/topbar/_page_info.html.erb +1 -1
  84. data/app/views/layouts/maglev/editor/topbar/_publish_button.html.erb +32 -10
  85. data/app/views/maglev/editor/assets/_list.html.erb +2 -1
  86. data/app/views/maglev/editor/assets/index.html.erb +1 -0
  87. data/app/views/maglev/editor/home/index.html.erb +1 -1
  88. data/app/views/maglev/editor/links/edit/_email.html.erb +1 -1
  89. data/app/views/maglev/editor/links/edit/_url.html.erb +1 -1
  90. data/app/views/maglev/editor/pages/_list.html.erb +25 -21
  91. data/app/views/maglev/editor/pages/_preview.html.erb +6 -6
  92. data/app/views/maglev/editor/pages/_preview_empty_message.html.erb +13 -10
  93. data/app/views/maglev/editor/pages/discard_draft/create.turbo_stream.erb +16 -0
  94. data/app/views/maglev/editor/pages/index.html.erb +8 -1
  95. data/app/views/maglev/editor/section_blocks/_form.html.erb +5 -13
  96. data/app/views/maglev/editor/section_blocks/_form_with_tabs.html.erb +15 -0
  97. data/app/views/maglev/editor/section_blocks/_new.html.erb +1 -1
  98. data/app/views/maglev/editor/section_blocks/edit.html.erb +2 -2
  99. data/app/views/maglev/editor/section_blocks/index/_list.html.erb +2 -2
  100. data/app/views/maglev/editor/section_blocks/index/_tree.html.erb +1 -1
  101. data/app/views/maglev/editor/section_blocks/update.turbo_stream.erb +1 -1
  102. data/app/views/maglev/editor/sections/_form.html.erb +6 -20
  103. data/app/views/maglev/editor/sections/_form_with_tabs.html.erb +21 -0
  104. data/app/views/maglev/editor/sections/_list.html.erb +3 -3
  105. data/app/views/maglev/editor/sections/edit.html.erb +4 -4
  106. data/app/views/maglev/editor/sections/index.html.erb +1 -1
  107. data/app/views/maglev/editor/sections/new.html.erb +19 -2
  108. data/app/views/maglev/editor/sections/theme/_empty_list.html.erb +3 -0
  109. data/app/views/maglev/editor/sections/theme/_list.html.erb +35 -0
  110. data/app/views/maglev/editor/sections/theme/_screenshot_placeholder.html.erb +6 -0
  111. data/app/views/maglev/editor/sections/theme/_search.html.erb +22 -0
  112. data/app/views/maglev/editor/sections/update.turbo_stream.erb +1 -1
  113. data/app/views/maglev/editor/shared/_button_label.html.erb +4 -4
  114. data/app/views/maglev/editor/style/edit.html.erb +1 -0
  115. data/config/editor_importmap.rb +13 -13
  116. data/config/locales/editor.ar.yml +12 -4
  117. data/config/locales/editor.en.yml +31 -23
  118. data/config/locales/editor.es.yml +12 -4
  119. data/config/locales/editor.fr.yml +12 -4
  120. data/config/locales/editor.pt-BR.yml +12 -4
  121. data/config/routes/maglev/assets.rb +4 -0
  122. data/config/routes/maglev/editor.rb +38 -0
  123. data/config/routes/maglev/preview.rb +8 -0
  124. data/config/routes/maglev/public_preview.rb +6 -0
  125. data/config/routes.rb +8 -47
  126. data/db/migrate/20211013210954_translate_section_content.rb +1 -0
  127. data/db/migrate/20260114112058_add_published_payload_to_pages.rb +14 -0
  128. data/exe/tailwind-cli +1 -1
  129. data/lib/generators/maglev/install_generator.rb +9 -7
  130. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +10 -3
  131. data/lib/maglev/active_storage/serving_blob.rb +29 -0
  132. data/lib/maglev/active_storage.rb +2 -0
  133. data/lib/maglev/config.rb +22 -3
  134. data/lib/maglev/engine.rb +14 -10
  135. data/lib/maglev/version.rb +1 -1
  136. data/lib/maglev.rb +18 -3
  137. data/lib/tasks/db_test_all.rake +290 -0
  138. metadata +46 -19
  139. data/app/controllers/maglev/editor/page_clone_controller.rb +0 -20
  140. data/app/views/maglev/editor/sections/_theme_list.html.erb +0 -32
  141. /data/vendor/javascript/{@floating-ui--core.js → maglev/@floating-ui--core.js} +0 -0
  142. /data/vendor/javascript/{@floating-ui--dom.js → maglev/@floating-ui--dom.js} +0 -0
  143. /data/vendor/javascript/{@floating-ui--utils--dom.js → maglev/@floating-ui--utils--dom.js} +0 -0
  144. /data/vendor/javascript/{@floating-ui--utils.js → maglev/@floating-ui--utils.js} +0 -0
  145. /data/vendor/javascript/{@hotwired--stimulus.js → maglev/@hotwired--stimulus.js} +0 -0
  146. /data/vendor/javascript/{@hotwired--turbo-rails.js → maglev/@hotwired--turbo-rails.js} +0 -0
  147. /data/vendor/javascript/{@hotwired--turbo.js → maglev/@hotwired--turbo.js} +0 -0
  148. /data/vendor/javascript/{@rails--actioncable--src.js → maglev/@rails--actioncable--src.js} +0 -0
  149. /data/vendor/javascript/{@rails--request.js.js → maglev/@rails--request.js.js} +0 -0
  150. /data/vendor/javascript/{@shopify--draggable.js → maglev/@shopify--draggable.js} +0 -0
  151. /data/vendor/javascript/{el-transition.js → maglev/el-transition.js} +0 -0
  152. /data/vendor/javascript/{stimulus-use.js → maglev/stimulus-use.js} +0 -0
  153. /data/vendor/javascript/{tiptap.bundle.js → maglev/tiptap.bundle.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maglev
4
- class ApplicationController < ::ApplicationController
4
+ class ApplicationController < Maglev.parent_controller_class
5
5
  include Maglev::ServicesConcern
6
6
  include Maglev::ResourceIdConcern
7
7
  include Maglev::ErrorsConcern
@@ -8,7 +8,8 @@ module Maglev
8
8
  private
9
9
 
10
10
  def set_blob
11
- @blob = Maglev::Asset.find(resource_id).file
11
+ blob = Maglev::Asset.find(resource_id).file.blob
12
+ @blob = Maglev::ActiveStorage::ServingBlob.new(blob)
12
13
  rescue ActiveRecord::RecordNotFound
13
14
  head :not_found
14
15
  end
@@ -36,7 +36,7 @@ module Maglev
36
36
  end
37
37
 
38
38
  def per_page
39
- 16 # TODO: make this configurable
39
+ maglev_config.per_page(:assets)
40
40
  end
41
41
 
42
42
  def resources
@@ -13,6 +13,8 @@ module Maglev
13
13
  include Maglev::ServicesConcern
14
14
  include Maglev::FlashI18nConcern
15
15
  include Maglev::Editor::ErrorsConcern
16
+ include Maglev::Editor::TurboConcern
17
+ include Maglev::Editor::PreviewUrlsConcern
16
18
 
17
19
  before_action :fetch_maglev_site
18
20
  before_action :fetch_maglev_page
@@ -20,9 +22,8 @@ module Maglev
20
22
 
21
23
  helper Maglev::ApplicationHelper
22
24
  helper_method :maglev_site, :maglev_theme,
23
- :current_maglev_page, :current_maglev_sections, :current_maglev_page_urls,
24
- :maglev_editing_route_context, :maglev_disable_turbo_cache?,
25
- :maglev_page_live_url, :maglev_page_preview_url
25
+ :current_maglev_page, :current_maglev_sections,
26
+ :maglev_editing_route_context, :unpublished_changes?
26
27
 
27
28
  private
28
29
 
@@ -61,21 +62,8 @@ module Maglev
61
62
  )
62
63
  end
63
64
 
64
- def current_maglev_page_urls
65
- {
66
- path: maglev_services.get_page_fullpath.call(page: current_maglev_page, preview_mode: false,
67
- locale: content_locale),
68
- preview: maglev_page_preview_url(current_maglev_page),
69
- live: maglev_page_live_url(current_maglev_page)
70
- }
71
- end
72
-
73
- def maglev_page_live_url(page)
74
- maglev_services.get_page_fullpath.call(page: page, preview_mode: false, locale: content_locale)
75
- end
76
-
77
- def maglev_page_preview_url(page)
78
- maglev_services.get_page_fullpath.call(page: page, preview_mode: true, locale: content_locale)
65
+ def unpublished_changes?
66
+ maglev_services.has_unpublished_changes.call(site: maglev_site, page: current_maglev_page, theme: maglev_theme)
79
67
  end
80
68
 
81
69
  def maglev_theme
@@ -89,20 +77,6 @@ module Maglev
89
77
  }.compact_blank
90
78
  end
91
79
 
92
- def maglev_disable_turbo_cache
93
- @maglev_disable_turbo_cache = true
94
- end
95
-
96
- def maglev_disable_turbo_cache?
97
- !!@maglev_disable_turbo_cache
98
- end
99
-
100
- def ensure_turbo_frame_request
101
- return if turbo_frame_request?
102
-
103
- redirect_to editor_root_path
104
- end
105
-
106
80
  def redirect_to_real_root
107
81
  redirect_to editor_real_root_path(maglev_editing_route_context)
108
82
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Editor
5
+ module Pages
6
+ class CloneController < Maglev::Editor::BaseController
7
+ def create
8
+ @source_page = maglev_page_resources.find(params[:id])
9
+ @page = clone_page(@source_page)
10
+ edit_cloned_page_path = edit_editor_page_path(@page, maglev_editing_route_context(page: @page))
11
+ redirect_to edit_cloned_page_path, notice: flash_t(:success), status: :see_other
12
+ end
13
+
14
+ private
15
+
16
+ def clone_page(page)
17
+ services.clone_page.call(page: page)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Editor
5
+ module Pages
6
+ class DiscardDraftController < Maglev::Editor::BaseController
7
+ def create
8
+ page = maglev_page_resources.find(params[:id])
9
+ services.discard_page_draft.call(
10
+ site: maglev_site,
11
+ page: page
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,16 +3,16 @@
3
3
  module Maglev
4
4
  module Editor
5
5
  class PagesController < Maglev::Editor::BaseController
6
+ include ::Pagy::Backend
7
+
6
8
  before_action :set_page, only: %i[edit update destroy]
7
9
  before_action :maglev_disable_turbo_cache, only: %i[edit update new create]
8
10
 
9
11
  helper_method :query_params
10
12
 
11
13
  def index
12
- @pages = services.search_pages.call(q: params[:q], content_locale: content_locale,
13
- default_locale: default_content_locale,
14
- with_static_pages: false,
15
- index_first: true)
14
+ @pages = fetch_pages
15
+ @pagy, @pages = pagy(@pages, limit: per_page, page_param: 'offset') if pagination_enabled?
16
16
  end
17
17
 
18
18
  def new
@@ -58,6 +58,10 @@ module Maglev
58
58
  @page = maglev_page_resources.find(params[:id])
59
59
  end
60
60
 
61
+ def build_page_resource
62
+ maglev_page_resources.build(page_params)
63
+ end
64
+
61
65
  def page_params
62
66
  params.require(:page).permit(:title, :path,
63
67
  :seo_title, :meta_description,
@@ -66,11 +70,26 @@ module Maglev
66
70
  end
67
71
 
68
72
  def query_params(from_list: false)
69
- { q: params[:q], from_list: params[:from_list] || from_list }.compact_blank
73
+ base = { q: params[:q], from_list: params[:from_list] || from_list }
74
+ (pagination_enabled? ? base.merge(offset: params[:offset]) : base).compact_blank
70
75
  end
71
76
 
72
- def build_page_resource
73
- maglev_page_resources.build(page_params)
77
+ def fetch_pages
78
+ services.search_pages.call(
79
+ q: params[:q],
80
+ content_locale: content_locale,
81
+ default_locale: default_content_locale,
82
+ with_static_pages: false,
83
+ index_first: true
84
+ )
85
+ end
86
+
87
+ def pagination_enabled?
88
+ maglev_config.pagination?(:pages)
89
+ end
90
+
91
+ def per_page
92
+ maglev_config.per_page(:pages)
74
93
  end
75
94
  end
76
95
  end
@@ -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
- raise ActiveRecord::StaleObjectError unless @section
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
- raise ActiveRecord::StaleObjectError unless @section_block
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 refresh_lock_version
81
- source = @section.site_scoped? ? maglev_site : current_maglev_page
82
- @section_block.lock_version = source.find_section_block_by_id(
83
- @section.id,
84
- @section_block.id
85
- )['lock_version']
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
- @grouped_sections = maglev_theme.sections.available_for(current_maglev_sections).grouped_by_category
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
- raise ActiveRecord::StaleObjectError unless @section
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 refresh_lock_version
87
- source = @section.site_scoped? ? maglev_site : current_maglev_page
88
- @section.lock_version = source.find_section_by_id(@section.id)['lock_version']
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 editor_sections_path(maglev_editing_route_context), notice: flash_t(:success), status: :see_other
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
 
@@ -105,20 +105,20 @@ module Maglev
105
105
  def maglev_button_classes(...)
106
106
  ClassVariants.build(
107
107
  base: %(
108
- rounded-xs transition-colors transition-background duration-200 text-center cursor-pointer
109
- group-[.is-success]/form:bg-green-500/95 group-[.is-success]/form:hover:bg-green-500/100
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
110
110
  group-[.is-success]/form:disabled:bg-green-500/75
111
- group-[.is-error]/form:bg-red-500/95 group-[.is-error]/form:hover:bg-red-500/100
111
+ group-[.is-error]/form:bg-red-500 group-[.is-error]/form:hover:bg-red-500/90
112
112
  group-[.is-error]/form:disabled:bg-red-500/75
113
113
  ),
114
114
  variants: {
115
115
  color: {
116
- primary: 'text-white bg-editor-primary/95 hover:bg-editor-primary/100 disabled:bg-editor-primary/75',
116
+ primary: 'text-white bg-editor-primary hover:bg-editor-primary/90 disabled:bg-editor-primary/75',
117
117
  secondary: 'text-gray-800 hover:bg-gray-100'
118
118
  },
119
119
  size: {
120
- big: 'flex items-center justify-center w-full px-6 py-4',
121
- medium: 'inline-flex items-center justify-center px-4 py-2'
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'
122
122
  }
123
123
  },
124
124
  defaults: {
@@ -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: section.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: section.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
@@ -11,6 +11,7 @@
11
11
  # og_image_url_translations :jsonb
12
12
  # og_title_translations :jsonb
13
13
  # published_at :datetime
14
+ # published_payload :jsonb
14
15
  # sections_translations :jsonb
15
16
  # seo_title_translations :jsonb
16
17
  # title_translations :jsonb
@@ -25,6 +26,7 @@ module Maglev
25
26
  include Maglev::SectionsConcern
26
27
  include Maglev::Page::PathConcern
27
28
  include Maglev::Page::SearchConcern
29
+ include Maglev::Page::PublishableConcern
28
30
 
29
31
  ## associations ##
30
32
  has_many :sections_content_stores, as: :container, dependent: :destroy
@@ -56,19 +58,6 @@ module Maglev
56
58
  false
57
59
  end
58
60
 
59
- def published?
60
- published_at.present?
61
- end
62
-
63
- def need_to_be_published?
64
- !published? || updated_at.blank? || updated_at > published_at
65
- end
66
-
67
- # opposite of #need_to_be_published?
68
- def published_and_up_to_date?
69
- published? && updated_at <= published_at
70
- end
71
-
72
61
  def translate_in(locale, source_locale)
73
62
  %i[title sections seo_title meta_description og_title og_description og_image_url].each do |attr|
74
63
  translate_attr_in(attr, locale, source_locale)
@@ -30,6 +30,10 @@ class Maglev::Section::Block
30
30
  settings.select(&:advanced?)
31
31
  end
32
32
 
33
+ def empty?
34
+ settings.none?
35
+ end
36
+
33
37
  def as_json
34
38
  super(only: %i[name type settings root accept])
35
39
  end
@@ -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
- # index_maglev_sections_content_stores_on_published (published)
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
@@ -68,22 +68,19 @@ module Maglev
68
68
  end
69
69
 
70
70
  def check_section_lock_version!(source)
71
- check_lock_version!(source, find_section(source), 'update_section')
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, find_block(source), 'update_block')
75
+ check_lock_version!(source, 'update_block')
76
76
  end
77
77
 
78
- def check_lock_version!(source, section_or_block, action_name)
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 = section_or_block['lock_version'].to_i
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: :fetch_section_screenshot_path
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