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
@@ -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,14 +9,22 @@ module Maglev
9
9
 
10
10
  def call
11
11
  ActiveRecord::Base.transaction do
12
- publish_container_sections!(site)
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
@@ -29,5 +37,10 @@ module Maglev
29
37
  def find_or_build_published_store(container)
30
38
  container.sections_content_stores.find_or_initialize_by(container: container, published: true)
31
39
  end
40
+
41
+ def publish_page_information!
42
+ page.update_published_payload
43
+ page.save!
44
+ end
32
45
  end
33
46
  end
@@ -2,6 +2,7 @@
2
2
  path: editor_pages_path(maglev_editing_route_context),
3
3
  icon: 'file_copy',
4
4
  active: controller_name == 'pages',
5
+ label: t('maglev.editor.sidebar_nav.list_pages_tooltip'),
5
6
  options: {
6
7
  data: { controller: 'prevent-same-path' }
7
8
  }
@@ -10,13 +11,15 @@
10
11
  <% sidebar.with_link(
11
12
  path: editor_sections_path(maglev_editing_route_context),
12
13
  icon: 'stack',
13
- active: controller_name == 'sections'
14
+ active: controller_name == 'sections',
15
+ label: t('maglev.editor.sidebar_nav.manage_page_sections_tooltip')
14
16
  ) %>
15
17
 
16
18
  <% sidebar.with_link(
17
19
  path: edit_editor_style_path(maglev_editing_route_context),
18
20
  icon: 'drop',
19
21
  active: controller_name == 'style',
22
+ label: t('maglev.editor.sidebar_nav.edit_style_tooltip'),
20
23
  options: {
21
24
  icon_size: '1.25rem',
22
25
  data: { controller: 'prevent-same-path' }
@@ -26,6 +29,7 @@
26
29
  <% sidebar.with_link(
27
30
  path: editor_assets_path,
28
31
  icon: 'image',
32
+ label: t('maglev.editor.sidebar_nav.open_image_library_tooltip'),
29
33
  options: {
30
34
  data: { turbo_frame: 'modal' }
31
35
  }
@@ -34,6 +38,7 @@
34
38
  <% sidebar.with_link(
35
39
  path: editor_leave_path,
36
40
  icon: 'logout',
41
+ label: t('maglev.editor.sidebar_nav.leave_editor_tooltip'),
37
42
  options: {
38
43
  position: :bottom
39
44
  }
@@ -39,6 +39,8 @@
39
39
  client:sectionBlock:clicked@window->editor-preview-notification-center#editSectionBlock
40
40
  client:section:setting:clicked@window->editor-preview-notification-center#editSection
41
41
  client:sectionBlock:setting:clicked@window->editor-preview-notification-center#editSection
42
+ client:section:setting:hovered@window->editor-preview-notification-center#prefetchEditSectionOrBlock
43
+ client:sectionBlock:setting:hovered@window->editor-preview-notification-center#prefetchEditSectionOrBlock
42
44
  "
43
45
  >
44
46
  <div id="root">
@@ -57,6 +59,9 @@
57
59
  <% end %>
58
60
 
59
61
  <%= render 'maglev/editor/sections/toolbar_list' %>
62
+
63
+ <%# Inside #root so Turbo morph refreshes the new-section URL; visibility uses editor-preview-empty variant (see tailwind.css.erb). %>
64
+ <%= render 'maglev/editor/pages/preview_empty_message' %>
60
65
  </div>
61
66
 
62
67
  <%= render 'maglev/editor/pages/preview' %>
@@ -3,7 +3,7 @@
3
3
  paths: {
4
4
  edit: edit_editor_page_path(current_maglev_page, maglev_editing_route_context),
5
5
  preview: current_maglev_page_urls[:preview],
6
- clone: editor_page_clone_path(current_maglev_page, maglev_editing_route_context),
6
+ clone: clone_editor_page_path(current_maglev_page, maglev_editing_route_context),
7
7
  delete: editor_page_path(current_maglev_page, maglev_editing_route_context)
8
8
  },
9
9
  live_page_url: current_maglev_page_urls[:live],
@@ -1,13 +1,35 @@
1
1
  <div class="flex items-center h-full px-4">
2
- <%= button_to editor_publication_path(maglev_editing_route_context),
3
- method: :post,
4
- class: maglev_button_classes(color: :primary, size: :medium),
5
- form_class: 'group/form is-default',
6
- disabled: current_maglev_page.published_and_up_to_date?,
7
- data: { controller: 'submit-button' } do %>
8
- <%= maglev_button_label(
9
- t('maglev.editor.header_nav.publish_button.default'),
10
- pending: t('maglev.editor.header_nav.publish_button.in_progress'),
11
- )%>
2
+ <%= render Maglev::Uikit::ButtonGroupComponent.new do |button_group| %>
3
+ <% button_group.with_button do %>
4
+ <%= button_to editor_publication_path(maglev_editing_route_context),
5
+ method: :post,
6
+ class: button_group.wrapped_button_classes,
7
+ form_class: button_group.wrapper_classes(class: 'group/form is-default'),
8
+ disabled: !unpublished_changes?,
9
+ data: {
10
+ controller: 'submit-button',
11
+ turbo_confirm: t('maglev.editor.header_nav.publish_button.publish_confirm')
12
+ } do %>
13
+ <%= maglev_button_label(t('maglev.editor.header_nav.publish_button.default'), pending: t('maglev.editor.header_nav.publish_button.in_progress'),) %>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <% button_group.with_button do %>
18
+ <%= render Maglev::Uikit::MenuDropdownComponent.new(
19
+ icon_name: 'arrow_down',
20
+ wrapper_classes: button_group.wrapper_classes,
21
+ trigger_classes: button_group.wrapped_button_classes(class: 'px-2!')
22
+ ) do |dropdown| %>
23
+ <% dropdown.with_item_button_to(discard_draft_editor_page_path(current_maglev_page.id, **maglev_editing_route_context),
24
+ method: :post,
25
+ data: {
26
+ turbo_confirm: t('maglev.editor.header_nav.publish_button.discard_draft_confirm')
27
+ }) do |menu_item| %>
28
+ <% menu_item.with_icon_content('history') %>
29
+ <% menu_item.with_label_content(t('maglev.editor.header_nav.publish_button.discard_draft')) %>
30
+ <% menu_item.with_sub_label_content(l(current_maglev_page.published_at, format: :long)) %>
31
+ <% end %>
32
+ <% end %>
33
+ <% end if current_maglev_page.discard_draft? %>
12
34
  <% end %>
13
35
  </div>
@@ -12,7 +12,8 @@
12
12
  height: asset.height,
13
13
  byte_size: asset.byte_size,
14
14
  },
15
- delete_path: editor_asset_path(asset, **maglev_editing_route_context, **query_params(pagination: true)),
15
+ # Numeric id: paths ending in .jpg/.png are Turbo "unvisitable", so forms skip hijacking (no confirm).
16
+ delete_path: editor_asset_path(asset.id, **maglev_editing_route_context, **query_params(pagination: true)),
16
17
  picker_attributes: { picker: params[:picker], source: params[:source] }.compact_blank
17
18
  ) %>
18
19
  <% end %>
@@ -25,5 +25,6 @@
25
25
  </div>
26
26
 
27
27
  <%= render 'list', assets: @assets, pagy: @pagy %>
28
+ </div>
28
29
  <% end %>
29
30
  <% end %>
@@ -1 +1 @@
1
- <p class="text-green-500">Hello world!!!!</p>
1
+ <!-- this is the home page for the editor -->
@@ -1,5 +1,5 @@
1
1
  <div class="mt-2 grid grid-cols-1 gap-4">
2
2
  <%= f.hidden_field :link_type, value: 'email' %>
3
3
 
4
- <%= f.text_field :email %>
4
+ <%= f.text_field :email, autocomplete: 'off' %>
5
5
  </div>
@@ -1,7 +1,7 @@
1
1
  <div class="mt-2 grid grid-cols-1 gap-4">
2
2
  <%= f.hidden_field :link_type, value: 'url' %>
3
3
 
4
- <%= f.text_field :url_href %>
4
+ <%= f.text_field :url_href, autocomplete: 'off' %>
5
5
 
6
6
  <%= f.check_box :open_new_window %>
7
7
  </div>
@@ -2,27 +2,31 @@
2
2
  <% if @pages.empty? %>
3
3
  <p class="text-center mt-8"><%= t('maglev.editor.pages.list.empty') %></p>
4
4
  <% else %>
5
- <% @pages.each do |page| %>
6
- <div class="flex items-center pl-4 pr-2 hover:bg-gray-50 transition-colors duration-200">
7
- <%= link_to editor_real_root_path(maglev_editing_route_context(page: page)), class: 'flex items-center text-gray-800 overflow-hidden w-full py-3.5 gap-2', data: { turbo_frame: '_top', **maglev_page_preview_reload_data } do %>
8
- <%= maglev_page_icon(page) %>
9
- <span class="truncate">
10
- <%= page.title.presence || page.default_title %>
11
- </span>
5
+ <%= render Maglev::Uikit::List::ListComponent.new do |component| %>
6
+ <% @pages.each do |page| %>
7
+ <%# Omit maglev_page_preview_reload_data: it runs before Turbo visits the row URL and reloads the iframe, then syncPreview loads the new preview again (double fetch). %>
8
+ <% component.with_item_content(id: page.id, link: { url: editor_real_root_path(maglev_editing_route_context(page: page)), data: { turbo_frame: '_top' } }, options: { variant: :ghost }) do |item| %>
9
+ <% item.with_icon do %>
10
+ <%= maglev_page_icon(page) %>
11
+ <% end %>
12
+
13
+ <% item.with_title_content(page.title.presence || page.default_title) %>
14
+ <% item.with_sub_title_content("/" + page.path) %>
15
+
16
+ <% item.with_action do %>
17
+ <%= render Maglev::Uikit::PageActionsDropdownComponent.new(
18
+ paths: {
19
+ edit: edit_editor_page_path(page.id, **query_params(from_list: true), **maglev_editing_route_context),
20
+ preview: maglev_page_preview_url(page),
21
+ clone: clone_editor_page_path(page.id, maglev_editing_route_context),
22
+ delete: editor_page_path(page.id, **query_params, **maglev_editing_route_context)
23
+ },
24
+ live_url: maglev_page_live_url(page),
25
+ without_actions: page.index? ? [:delete] : []
26
+ ) %>
27
+ <% end %>
12
28
  <% end %>
13
- <div class="ml-auto pr-2 relative">
14
- <%= render Maglev::Uikit::PageActionsDropdownComponent.new(
15
- paths: {
16
- edit: edit_editor_page_path(page.id, **query_params(from_list: true), **maglev_editing_route_context),
17
- preview: maglev_page_preview_url(page),
18
- clone: editor_page_clone_path(page.id, maglev_editing_route_context),
19
- delete: editor_page_path(page.id, **query_params, **maglev_editing_route_context)
20
- },
21
- live_url: maglev_page_live_url(page),
22
- without_actions: page.index? ? [:delete] : []
23
- ) %>
24
- </div>
25
- </div>
26
- <% end %>
29
+ <% end %>
30
+ <% end %>
27
31
  <% end %>
28
32
  </div>
@@ -1,12 +1,14 @@
1
1
  <div
2
- class="absolute top-16 left-16 right-0 bottom-0 overflow-hidden group"
2
+ class="absolute top-16 left-16 right-0 bottom-0 z-0 overflow-hidden group"
3
3
  data-controller="editor-page-preview"
4
4
  data-action="
5
5
  uikit-device-toggler:change@window->editor-page-preview#changeDevice
6
6
  client:ready@window->editor-page-preview#clientReady
7
7
  client:ready@window->editor-preview-notification-center#clientReady
8
8
  turbo:load@window->editor-page-preview#detectUrlChange
9
- dispatcher:page-preview:reload@window->editor-page-preview#startLoading
9
+ turbo:render@window->editor-page-preview#detectUrlChange
10
+ pageshow@window->editor-page-preview#detectUrlChange
11
+ dispatcher:page-preview:reload@window->editor-page-preview#reload
10
12
  dispatcher:page-preview:remove-empty-message@window->editor-page-preview#removeEmptyMessage
11
13
  "
12
14
  >
@@ -20,8 +22,8 @@
20
22
  class="bg-white w-full h-full mx-auto transition-discrete transition-all duration-100 ease-in-out group-data-[device=mobile]/device:w-[375px] group-data-[device=tablet]/device:w-[1024px]"
21
23
  data-editor-page-preview-target="iframe"
22
24
  data-editor-preview-notification-center-target="iframe"
23
- data-editor-preview-notification-center-primary-color-param="<%= maglev_primary_hex_color %>"
24
- data-editor-preview-notification-center-sticky-section-ids-param="<%=current_maglev_sections.select(&:sticky?).map(&:id).to_json %>"
25
+ data-editor-preview-notification-center-primary-color-value="<%= maglev_primary_hex_color %>"
26
+ data-editor-preview-notification-center-sticky-section-ids-value="<%= current_maglev_sections.select(&:sticky?).map(&:id).to_json %>"
25
27
  data-action="load->editor-preview-notification-center#sendConfig"></iframe>
26
28
  </div>
27
29
 
@@ -30,6 +32,4 @@
30
32
  <%= t('maglev.editor.page_preview.loading') %>
31
33
  </p>
32
34
  </div>
33
-
34
- <%= render 'maglev/editor/pages/preview_empty_message' %>
35
35
  </div>
@@ -1,14 +1,17 @@
1
- <div class="absolute inset-0 flex-col justify-center items-center hidden group-[.is-empty]:flex bg-gray-50">
2
- <h2 class="text-center text-2xl font-bold">
3
- <%= t('maglev.editor.page_preview.empty.title') %>
4
- </h2>
5
- <div class="mt-4 text-gray-600 max-w-md mx-auto leading-9">
6
- <% new_section_link = capture do %>
7
- <%= link_to new_editor_section_path(**maglev_editing_route_context), class: 'text-editor-primary inline-block relative top-1.5' do %>
8
- <%= render Maglev::Uikit::IconComponent.new(name: 'stack', size: '1.5rem') %>
1
+ <%# z-[5]: below PageLayoutComponent (z-10 / z-40), above preview shell (z-0). editor-preview-empty: tailwind.css.erb @custom-variant on [data-controller~="editor-page-preview"].is-empty %>
2
+ <div class="fixed top-16 left-16 right-0 bottom-0 z-[5] bg-gray-50 hidden editor-preview-empty:block">
3
+ <div class="flex min-h-full flex-col items-center justify-center px-4">
4
+ <h2 class="text-center text-2xl font-bold">
5
+ <%= t('maglev.editor.page_preview.empty.title') %>
6
+ </h2>
7
+ <div class="mt-4 max-w-md mx-auto leading-9 text-gray-600">
8
+ <% new_section_link = capture do %>
9
+ <%= link_to new_editor_section_path(**maglev_editing_route_context), class: 'text-editor-primary inline-block relative top-1.5', data: { turbo_frame: 'modal' } do %>
10
+ <%= render Maglev::Uikit::IconComponent.new(name: 'stack', size: '1.5rem') %>
11
+ <% end %>
9
12
  <% end %>
10
- <% end %>
11
13
 
12
- <%= t('maglev.editor.page_preview.empty.message', button: new_section_link).html_safe %>
14
+ <%= t('maglev.editor.page_preview.empty.message', button: new_section_link).html_safe %>
15
+ </div>
13
16
  </div>
14
17
  </div>
@@ -0,0 +1,16 @@
1
+ <%= turbo_stream.console_log 'Discard page draft' %>
2
+
3
+ <%= turbo_stream.dispatch_event "page-preview:reload", payload: {}.to_json %>
4
+
5
+ <%= turbo_stream.update dom_id(current_maglev_page, 'topbar-page-info'), method: 'morph' do %>
6
+ <%= render 'layouts/maglev/editor/topbar/page_info' %>
7
+ <% end %>
8
+
9
+ <%= turbo_stream.update dom_id(current_maglev_page, 'topbar-publish-button'), method: 'morph' do %>
10
+ <%= render 'layouts/maglev/editor/topbar/publish_button' %>
11
+ <% end %>
12
+
13
+ <%= turbo_stream.refresh %>
14
+
15
+ <%#= turbo_stream.refresh delayed: true %>
16
+
@@ -18,6 +18,13 @@
18
18
  </div>
19
19
 
20
20
  <% page_layout.with_footer do %>
21
- <%= link_to t('maglev.editor.pages.list.new_button'), new_editor_page_path(**maglev_editing_route_context, **query_params), class: maglev_button_classes(size: :big), data: { turbo_frame: '_top' } %>
21
+ <div>
22
+ <% if defined?(@pagy) && @pagy.present? %>
23
+ <div class="pb-2">
24
+ <%= render Maglev::Uikit::PaginationComponent.new(pagy: @pagy, hidden_if_single_page: false, show_info: false) %>
25
+ </div>
26
+ <% end %>
27
+ <%= link_to t('maglev.editor.pages.list.new_button'), new_editor_page_path(**maglev_editing_route_context, **query_params), class: maglev_button_classes(size: :big), data: { turbo_frame: '_top' } %>
28
+ </div>
22
29
  <% end %>
23
30
  <% end %>
@@ -1,15 +1,7 @@
1
- <%= hidden_field_tag 'lock_version', @section_block.lock_version.presence || 0, data: { editor_section_form_target: 'lockVersion' } %>
2
-
3
- <% if @section_block.definition.advanced_settings.blank? %>
4
- <%= section_block_form_settings(@section, @section_block) %>
5
- <% else %>
6
- <%= render Maglev::Uikit::TabsComponent.new(container_classes: 'w-full h-full flex flex-col') do |tabs| %>
7
- <% tabs.with_tab(label: t('maglev.editor.section_blocks.edit.tabs.main')) do %>
8
- <%= section_block_form_settings(@section, @section_block) %>
9
- <% end %>
10
-
11
- <% tabs.with_tab(label: t('maglev.editor.section_blocks.edit.tabs.advanced')) do %>
12
- <%= section_block_form_settings(@section, @section_block, advanced: true) %>
13
- <% end %>
1
+ <% if @section_block.definition.empty? %>
2
+ <%= render Maglev::Uikit::Well::SimpleWellComponent.new do |well| %>
3
+ <% well.with_title_content(t('maglev.editor.section_blocks.edit.no_settings')) %>
14
4
  <% end %>
5
+ <% else %>
6
+ <%= section_block_form_settings(@section, @section_block, advanced: defined?(advanced) && advanced) %>
15
7
  <% end %>
@@ -0,0 +1,15 @@
1
+ <%= hidden_field_tag 'lock_version', source_lock_version, data: { editor_section_form_target: 'lockVersion' } %>
2
+
3
+ <% if @section_block.definition.advanced_settings.blank? %>
4
+ <%= render 'maglev/editor/section_blocks/form' %>
5
+ <% else %>
6
+ <%= render Maglev::Uikit::TabsComponent.new(container_classes: 'w-full h-full flex flex-col') do |tabs| %>
7
+ <% tabs.with_tab(label: t('maglev.editor.section_blocks.edit.tabs.main')) do %>
8
+ <%= render 'maglev/editor/section_blocks/form' %>
9
+ <% end %>
10
+
11
+ <% tabs.with_tab(label: t('maglev.editor.section_blocks.edit.tabs.advanced')) do %>
12
+ <%= render 'maglev/editor/section_blocks/form', advanced: true %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -6,7 +6,7 @@
6
6
  'editor-preview-notification-center-section-id-param': @section.id,
7
7
  } do |f| %>
8
8
  <%= f.hidden_field :parent_id, value: parent_id if parent_id %>
9
- <%= f.hidden_field :lock_version, value: @section.lock_version %>
9
+ <%= f.hidden_field :lock_version, value: source_lock_version %>
10
10
 
11
11
  <div class="space-y-4">
12
12
  <% blocks.each do |block_definition| %>
@@ -12,7 +12,7 @@
12
12
  controller: 'editor-section-form',
13
13
  'editor-section-form-section-id-value': @section.id,
14
14
  'editor-section-form-section-block-id-value': @section_block.id,
15
- 'editor-section-form-section-lock-version-value': @section.lock_version,
15
+ 'editor-section-form-section-lock-version-value': source_lock_version,
16
16
  action: %(
17
17
  editor-setting:change->editor-section-form#onSettingChange
18
18
  editor-section-form:connected@window->editor-preview-notification-center#checkSectionLockVersion
@@ -23,6 +23,6 @@
23
23
  dispatcher:section-block-#{@section_block.id}:persisted@window->editor-section-form#onPersist
24
24
  )
25
25
  } do %>
26
- <%= render 'maglev/editor/section_blocks/form' %>
26
+ <%= render 'maglev/editor/section_blocks/form_with_tabs' %>
27
27
  <% end %>
28
28
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <% if @section.blocks.any? %>
2
2
  <%= render Maglev::Uikit::List::ListComponent.new(
3
3
  sort_form: {
4
- path: sort_editor_section_blocks_path(@section, lock_version: @section.lock_version, **maglev_editing_route_context),
4
+ path: sort_editor_section_blocks_path(@section, lock_version: source_lock_version, **maglev_editing_route_context),
5
5
  data: {
6
6
  turbo_frame: '_top',
7
7
  action: 'sortable:drag-stopped@window->editor-preview-notification-center#moveSectionBlocks',
@@ -35,7 +35,7 @@
35
35
  <% end %>
36
36
  <% end %>
37
37
  <% else %>
38
- <p class="text-center mt-8 text-gray-800">
38
+ <p class="text-center mt-8 text-gray-500">
39
39
  <%= t('maglev.editor.section_blocks.index.empty') %>
40
40
  </p>
41
41
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <%= render Maglev::Uikit::List::ListComponent.new(
2
2
  sort_form: {
3
- path: sort_editor_section_blocks_path(@section, parent_id: parent_id, lock_version: @section.lock_version, **maglev_editing_route_context),
3
+ path: sort_editor_section_blocks_path(@section, parent_id: parent_id, lock_version: source_lock_version, **maglev_editing_route_context),
4
4
  data: { turbo_frame: '_top' }
5
5
  },
6
6
  sortable_scope: parent_id || 'root',
@@ -1,4 +1,4 @@
1
- <%= turbo_stream.dispatch_event "section-block-#{@section_block.id}:persisted", payload: { lockVersion: @section_block.lock_version }.to_json %>
1
+ <%= turbo_stream.dispatch_event "section-block-#{@section_block.id}:persisted", payload: { lockVersion: source_lock_version }.to_json %>
2
2
 
3
3
  <%= turbo_stream.update 'page-layout-notification', method: 'morph' do %>
4
4
  <%= render Maglev::Uikit::BadgeComponent.new(color: :green, icon_name: 'checkbox_circle', disappear_after: 3.seconds).with_content(flash.now[:notice]) %>
@@ -1,21 +1,7 @@
1
- <%= hidden_field_tag 'lock_version', @section.lock_version.presence || 0, data: { editor_section_form_target: 'lockVersion' } %>
2
-
3
- <% if @section.definition.blocks.none? && !@section.definition.advanced_settings.any? %>
4
- <%= section_form_settings(@section) %>
5
- <% else %>
6
- <%= render Maglev::Uikit::TabsComponent.new(container_classes: 'w-full h-full flex flex-col') do |tabs| %>
7
- <% tabs.with_tab(label: t('maglev.editor.sections.edit.tabs.main')) do %>
8
- <%= section_form_settings(@section) %>
9
- <% end %>
10
-
11
- <% tabs.with_tab(label: t('maglev.editor.sections.edit.tabs.advanced')) do %>
12
- <%= section_form_settings(@section, advanced: true) %>
13
- <% end if @section.definition.advanced_settings.any? %>
14
-
15
- <% tabs.with_tab(
16
- label: @section.blocks_label,
17
- link: { url: editor_section_blocks_path(@section, maglev_editing_route_context), html_options: { data: { turbo_frame: '_top' } } }
18
- ) unless @section.definition.blocks.none?
19
- %>
1
+ <% if @section.definition.empty? %>
2
+ <%= render Maglev::Uikit::Well::SimpleWellComponent.new do |well| %>
3
+ <% well.with_title_content(t('maglev.editor.sections.edit.no_settings')) %>
20
4
  <% end %>
21
- <% end %>
5
+ <% else %>
6
+ <%= section_form_settings(@section, advanced: defined?(advanced) && advanced) %>
7
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <%= hidden_field_tag 'lock_version', source_lock_version, data: { editor_section_form_target: 'lockVersion' } %>
2
+
3
+ <% if @section.definition.blocks.none? && !@section.definition.advanced_settings.any? %>
4
+ <%= render 'maglev/editor/sections/form' %>
5
+ <% else %>
6
+ <%= render Maglev::Uikit::TabsComponent.new(container_classes: 'w-full h-full flex flex-col') do |tabs| %>
7
+ <% tabs.with_tab(label: t('maglev.editor.sections.edit.tabs.main')) do %>
8
+ <%= render 'maglev/editor/sections/form' %>
9
+ <% end %>
10
+
11
+ <% tabs.with_tab(label: t('maglev.editor.sections.edit.tabs.advanced')) do %>
12
+ <%= render 'maglev/editor/sections/form', advanced: true %>
13
+ <% end if @section.definition.advanced_settings.any? %>
14
+
15
+ <% tabs.with_tab(
16
+ label: @section.blocks_label,
17
+ link: { url: editor_section_blocks_path(@section, maglev_editing_route_context), html_options: { data: { turbo_frame: '_top' } } }
18
+ ) unless @section.definition.blocks.none?
19
+ %>
20
+ <% end %>
21
+ <% end %>
@@ -8,7 +8,7 @@
8
8
  action: 'sortable:drag-stopped@window->editor-preview-notification-center#moveSection',
9
9
  }
10
10
  }) do |component| %>
11
- <% component.with_item_insert_button(link: { url: new_editor_section_path(position: 0, **maglev_editing_route_context), data: { turbo_frame: '_top' } })%>
11
+ <% component.with_item_insert_button(link: { url: new_editor_section_path(position: 0, **maglev_editing_route_context), data: { turbo_frame: 'modal' } })%>
12
12
 
13
13
  <% current_maglev_sections.each_with_index do |section, index| %>
14
14
  <% component.with_item_content(id: section.id, link: { url: edit_editor_section_path(section.id, maglev_editing_route_context), data: { turbo_frame: '_top' } }) do |item| %>
@@ -39,11 +39,11 @@
39
39
  <% end %>
40
40
  <% end %>
41
41
 
42
- <% component.with_item_insert_button(link: { url: new_editor_section_path(position: index + 1, **maglev_editing_route_context), data: { turbo_frame: '_top' } }) %>
42
+ <% component.with_item_insert_button(link: { url: new_editor_section_path(position: index + 1, **maglev_editing_route_context), data: { turbo_frame: 'modal' } }) %>
43
43
  <% end %>
44
44
  <% end %>
45
45
  <% else %>
46
- <p class="text-center mt-8 text-gray-800">
46
+ <p class="text-center mt-8 text-gray-500">
47
47
  <%= t('maglev.editor.sections.index.empty') %>
48
48
  </p>
49
49
  <% end %>