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,13 +1,17 @@
1
- <%= render Maglev::Uikit::DropdownComponent.new(placement: placement) do |component| %>
1
+ <%= render Maglev::Uikit::DropdownComponent.new(placement: placement, wrapper_classes: wrapper_classes) do |component| %>
2
2
  <% component.with_trigger do %>
3
3
  <% if trigger.present? %>
4
4
  <%= trigger %>
5
+ <% elsif trigger_classes.present? && icon_name.present? %>
6
+ <%= button_tag class: trigger_classes, data: { action: "click->uikit-dropdown#toggle", 'uikit-dropdown-target': 'button' } do %>
7
+ <%= render Maglev::Uikit::IconComponent.new(name: icon_name) %>
8
+ <% end %>
5
9
  <% else %>
6
10
  <%= render Maglev::Uikit::IconButtonComponent.new(icon_name: icon_name, data: { action: "click->uikit-dropdown#toggle", 'uikit-dropdown-target': 'button' }) %>
7
11
  <% end %>
8
12
  <% end %>
9
13
 
10
- <div class="flex flex-col w-48 text-gray-800">
14
+ <div class="<%= list_item_classes %>">
11
15
  <% items.each do |item| %>
12
16
  <%= item %>
13
17
  <% end %>
@@ -3,30 +3,267 @@
3
3
  module Maglev
4
4
  module Uikit
5
5
  class MenuDropdownComponent < Maglev::Uikit::BaseComponent
6
+ module RenderPolymorphicItems
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ renders_many :items, types: {
11
+ button: lambda { |options = nil|
12
+ Maglev::Uikit::MenuDropdownComponent::ButtonItemComponent.new(options, parent: self)
13
+ },
14
+ link_to: lambda { |options = nil, html_options = nil|
15
+ Maglev::Uikit::MenuDropdownComponent::LinkToItemComponent.new(options, html_options, parent: self)
16
+ },
17
+ button_to: lambda { |options = nil, html_options = nil|
18
+ Maglev::Uikit::MenuDropdownComponent::ButtonToItemComponent.new(options, html_options, parent: self)
19
+ },
20
+ text: lambda { |options = nil|
21
+ Maglev::Uikit::MenuDropdownComponent::TextItemComponent.new(options, parent: self)
22
+ },
23
+ nested_menu: lambda { |placement:|
24
+ Maglev::Uikit::MenuDropdownComponent::NestedMenuComponent.new(placement: placement, parent: self)
25
+ },
26
+ wrapper: lambda { |options = nil|
27
+ Maglev::Uikit::MenuDropdownComponent::WrapperItemComponent.new(options, parent: self)
28
+ },
29
+ divider: -> { Maglev::Uikit::MenuDropdownComponent::DividerItemComponent.new(parent: self) }
30
+ }
31
+ end
32
+ end
33
+
6
34
  renders_one :trigger
7
- renders_many :items
35
+ include RenderPolymorphicItems
8
36
 
9
- attr_reader :icon_name, :placement
37
+ attr_reader :icon_name, :placement, :wrapper_classes, :trigger_classes
10
38
 
11
- def initialize(icon_name: nil, placement: 'bottom-start')
39
+ def initialize(icon_name: nil, placement: 'bottom-start', wrapper_classes: nil, trigger_classes: nil)
12
40
  @icon_name = icon_name
13
41
  @placement = placement
42
+ @wrapper_classes = wrapper_classes
43
+ @trigger_classes = trigger_classes
44
+ end
45
+
46
+ def list_item_classes
47
+ 'text-gray-800 grid grid-cols-[auto_1fr_auto] min-w-[12rem] my-1'
14
48
  end
15
49
 
50
+ # rubocop:disable Metrics/MethodLength
16
51
  def item_classes(...)
17
52
  class_variants(
18
53
  base: %(
19
- flex items-center px-4 py-4 hover:bg-gray-100 w-full
20
- transition-colors duration-200 focus:outline-none cursor-pointer flex-1
21
- )
54
+ col-span-3 grid grid-cols-subgrid
55
+ flex flex-1 items-center px-2 py-3 w-full mx-1 rounded-sm text-left
56
+ transition-colors duration-200 focus:outline-none cursor-pointer
57
+ ),
58
+ variants: {
59
+ danger: 'text-red-600 hover:bg-red-100',
60
+ '!danger': 'hover:bg-gray-100',
61
+ subdued: 'text-gray-400'
62
+ },
63
+ defaults: { danger: false, subdued: false }
22
64
  ).render(...)
23
65
  end
66
+ # rubocop:enable Metrics/MethodLength
24
67
 
25
68
  def form_item_classes(...)
26
69
  class_variants(
27
- base: 'flex items-center focus:outline-none cursor-pointer'
70
+ base: %w[
71
+ col-span-3 grid grid-cols-subgrid
72
+ flex items-center focus:outline-none cursor-pointer
73
+ ]
28
74
  ).render(...)
29
75
  end
76
+
77
+ class ItemComponent < ViewComponent::Base
78
+ renders_one :icon
79
+ renders_one :label
80
+ renders_one :sub_label
81
+ renders_one :right_icon
82
+
83
+ def self.inner_content
84
+ <<-ERB
85
+ <%= render Maglev::Uikit::IconComponent.new(name: icon.to_s, size: '1.15rem', class_names: 'mr-2 shrink-0') if icon? %>
86
+ <span class="<%= 'col-start-2' unless icon? %> whitespace-nowrap whitespace-nowrap truncate overflow-hidden">
87
+ <%= label %>
88
+ </span>
89
+ <%= render Maglev::Uikit::IconComponent.new(name: 'arrow_right', size: '1.15rem', class_names: 'ml-2 shrink-0') if right_arrow? %>
90
+ <span class="col-start-2 whitespace-nowrap whitespace-nowrap truncate overflow-hidden text-xs text-gray-500">
91
+ <%= sub_label %>
92
+ </span>
93
+ ERB
94
+ end
95
+
96
+ def right_arrow?
97
+ false
98
+ end
99
+ end
100
+
101
+ class TextItemComponent < ItemComponent
102
+ attr_reader :options, :parent_component, :variant
103
+
104
+ def initialize(options = nil, parent: nil)
105
+ @parent_component = parent
106
+ @options = options || {}
107
+ @variant = @options.delete(:variant)
108
+
109
+ apply_parent_classes
110
+ end
111
+
112
+ erb_template <<-ERB
113
+ <%= tag.span(**options) do %>
114
+ #{inner_content}
115
+ <% end %>
116
+ ERB
117
+
118
+ private
119
+
120
+ def apply_parent_classes
121
+ options[:class] = parent_component.item_classes(subdued: variant == 'subdued', class: options[:class])
122
+ end
123
+ end
124
+
125
+ class LinkToItemComponent < ItemComponent
126
+ attr_reader :options, :html_options, :parent_component, :variant
127
+
128
+ def initialize(options = nil, html_options = nil, parent: nil)
129
+ @parent_component = parent
130
+ @options = options
131
+ @html_options = html_options || {}
132
+ @variant = @html_options.delete(:variant)
133
+
134
+ apply_parent_classes
135
+ end
136
+
137
+ erb_template <<-ERB
138
+ <%= link_to options, html_options do %>
139
+ #{inner_content}
140
+ <% end %>
141
+ ERB
142
+
143
+ private
144
+
145
+ def apply_parent_classes
146
+ html_options[:class] = parent_component.item_classes(danger: variant == 'danger', class: html_options[:class])
147
+ end
148
+ end
149
+
150
+ class ButtonItemComponent < ItemComponent
151
+ attr_reader :options, :parent_component
152
+
153
+ def initialize(options = nil, parent: nil)
154
+ @parent_component = parent
155
+ @options = (options || {}).merge(type: 'button')
156
+
157
+ apply_parent_classes
158
+ end
159
+
160
+ erb_template <<-ERB
161
+ <%= button_tag options do %>
162
+ #{inner_content}
163
+ <% end %>
164
+ ERB
165
+
166
+ private
167
+
168
+ def apply_parent_classes
169
+ options[:class] = parent_component.item_classes(class: options[:class])
170
+ end
171
+ end
172
+
173
+ class ButtonToItemComponent < LinkToItemComponent
174
+ erb_template <<-ERB
175
+ <%= button_to options, html_options do %>
176
+ #{inner_content}
177
+ <% end %>
178
+ ERB
179
+
180
+ private
181
+
182
+ def apply_parent_classes
183
+ super
184
+ html_options[:form_class] = parent_component.form_item_classes(class: html_options[:form_class])
185
+ end
186
+ end
187
+
188
+ class NestedMenuComponent < ItemComponent
189
+ include RenderPolymorphicItems
190
+
191
+ attr_reader :placement, :parent_component
192
+
193
+ delegate :list_item_classes, :item_classes, :form_item_classes, to: :parent_component
194
+
195
+ alias wrapper_classes form_item_classes
196
+
197
+ def initialize(placement:, parent:)
198
+ @placement = placement
199
+ @parent_component = parent
200
+ end
201
+
202
+ erb_template <<-ERB
203
+ <%= render Maglev::Uikit::DropdownComponent.new(placement: placement, wrapper_classes: wrapper_classes) do |dropdown| %>
204
+ <% dropdown.with_trigger do %>
205
+ <%= button_tag class: item_classes, data: { action: 'click->uikit-dropdown#toggle', 'uikit-dropdown-target': 'button' } do %>
206
+ #{inner_content}#{' '}
207
+ <% end %>
208
+ <% end %>
209
+
210
+ <div class="<%= list_item_classes %>">
211
+ <% items.each do |item| %>
212
+ <%= item %>
213
+ <% end %>
214
+ </div>
215
+ <% end %>
216
+ ERB
217
+
218
+ def right_arrow?
219
+ true
220
+ end
221
+ end
222
+
223
+ class WrapperItemComponent < Maglev::Uikit::BaseComponent
224
+ include RenderPolymorphicItems
225
+
226
+ attr_reader :options, :parent_component
227
+
228
+ delegate :item_classes, :form_item_classes, to: :parent_component
229
+
230
+ def initialize(options, parent: nil)
231
+ @parent_component = parent
232
+ @options = options || {}
233
+
234
+ apply_parent_classes
235
+ end
236
+
237
+ erb_template <<-ERB
238
+ <%= tag.div(**options) do %>
239
+ <% items.each do |item| %>
240
+ <%= item %>
241
+ <% end %>
242
+ <% end %>
243
+ ERB
244
+
245
+ private
246
+
247
+ def apply_parent_classes
248
+ options[:class] = parent_component.form_item_classes(class: options[:class])
249
+ end
250
+ end
251
+
252
+ class DividerItemComponent < ItemComponent
253
+ attr_reader :parent_component
254
+
255
+ def initialize(parent:)
256
+ @parent_component = parent
257
+ end
258
+
259
+ def item_classes(...)
260
+ parent_component.form_item_classes(class: 'border-t border-gray-200 my-1 px-0!')
261
+ end
262
+
263
+ erb_template <<-ERB
264
+ <%= tag.hr class: item_classes %>
265
+ ERB
266
+ end
30
267
  end
31
268
  end
32
269
  end
@@ -1,56 +1,49 @@
1
- <%= render Maglev::Uikit::MenuDropdownComponent.new(icon_name: icon_name) do |dropdown| %>
2
- <% dropdown.with_item do %>
3
- <%= link_to paths[:edit], class: dropdown.item_classes, data: { turbo_frame: '_top' } do %>
4
- <%= render Maglev::Uikit::IconComponent.new(name: 'settings', class_names: 'shrink-0') %>
5
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden"><%= t('maglev.editor.pages.actions.edit') %></span>
6
- <% end %>
1
+ <%= render Maglev::Uikit::MenuDropdownComponent.new(icon_name: icon_name) do |menu_dropdown| %>
2
+ <% menu_dropdown.with_item_link_to(paths[:edit], data: { turbo_frame: '_top' }) do |menu_item| %>
3
+ <% menu_item.with_icon_content('settings') %>
4
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.edit')) %>
7
5
  <% end if allow?(:edit) %>
8
6
 
9
- <% dropdown.with_item do %>
10
- <%= render Maglev::Uikit::MenuDropdownComponent.new(placement: 'right-start') do |sub_menu| %>
11
- <% sub_menu.with_trigger do %>
12
- <%= button_tag class: sub_menu.item_classes, data: { action: 'click->uikit-dropdown#toggle', 'uikit-dropdown-target': 'button' } do %>
13
- <%= render Maglev::Uikit::IconComponent.new(name: 'preview', class_names: 'shrink-0') %>
14
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden"><%= t('maglev.editor.pages.actions.preview') %></span>
15
- <% end %>
16
- <% end %>
17
- <% sub_menu.with_item do %>
18
- <%= link_to t('maglev.editor.pages.actions.draft_preview'), paths[:preview], class: sub_menu.item_classes, target: '_blank' %>
19
- <% end %>
20
- <% sub_menu.with_item do %>
21
- <%= link_to t('maglev.editor.pages.actions.live_preview'), live_url, class: sub_menu.item_classes, target: '_blank' %>
22
- <% end %>
7
+ <% menu_dropdown.with_item_divider if allow?(:edit) %>
8
+
9
+ <% menu_dropdown.with_item_nested_menu(placement: 'right-start') do |menu_item| %>
10
+ <% menu_item.with_icon_content('preview') %>
11
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.preview')) %>
12
+
13
+ <% menu_item.with_item_link_to(paths[:preview], target: '_blank') do |sub_menu_item| %>
14
+ <% sub_menu_item.with_label_content(t('maglev.editor.pages.actions.draft_preview')) %>
23
15
  <% end %>
24
- <% end if allow?(:preview) %>
25
16
 
26
- <% dropdown.with_item do %>
27
- <%= button_to paths[:clone], class: dropdown.form_item_classes, form_class: dropdown.item_classes do %>
28
- <%= render Maglev::Uikit::IconComponent.new(name: 'clone', class_names: 'shrink-0') %>
29
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden"><%= t('maglev.editor.pages.actions.clone') %></span>
17
+ <% menu_item.with_item_link_to(live_url, target: '_blank') do |sub_menu_item| %>
18
+ <% sub_menu_item.with_label_content(t('maglev.editor.pages.actions.live_preview')) %>
19
+ <% end if live_url.present? %>
20
+
21
+ <% menu_item.with_item_text(variant: 'subdued') do |text_item| %>
22
+ <% text_item.with_label_content(t('maglev.editor.pages.actions.no_live_preview')) %>
23
+ <% end if live_url.blank? %>
24
+ <% end %>
25
+
26
+ <% menu_dropdown.with_item_wrapper(data: { controller: 'copy-to-clipboard', copy_to_clipboard_source_value: live_url }) do |wrapper_item| %>
27
+ <% wrapper_item.with_item_button(data: { copy_to_clipboard_target: 'text', action: 'click->copy-to-clipboard#copy:prevent' }) do |menu_item| %>
28
+ <% menu_item.with_icon_content('clipboard') %>
29
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.copy_url_to_clipboard')) %>
30
30
  <% end %>
31
- <% end if allow?(:clone) %>
32
31
 
33
- <% dropdown.with_item do %>
34
- <div data-controller="copy-to-clipboard" data-copy-to-clipboard-source-value="<%= live_url %>">
35
- <%= link_to '#', class: dropdown.item_classes, data: { copy_to_clipboard_target: 'text', action: 'click->copy-to-clipboard#copy:prevent' } do %>
36
- <%= render Maglev::Uikit::IconComponent.new(name: 'clipboard', class_names: 'shrink-0') %>
37
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden">
38
- <%= t('maglev.editor.pages.actions.copy_url_to_clipboard') %>
39
- </span>
40
- <% end %>
41
- <div class="<%= dropdown.item_classes %> hidden" data-copy-to-clipboard-target="success">
42
- <%= render Maglev::Uikit::IconComponent.new(name: 'checkbox_circle', class_names: 'text-green-500 shrink-0') %>
43
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden text-green-500">
44
- <%= t('maglev.editor.pages.actions.copy_url_to_clipboard_success') %>
45
- </span>
46
- </div>
47
- </div>
32
+ <% wrapper_item.with_item_button(class: 'hidden text-green-500', data: { copy_to_clipboard_target: 'success' }) do |menu_item| %>
33
+ <% menu_item.with_icon_content('checkbox_circle') %>
34
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.copy_url_to_clipboard_success')) %>
35
+ <% end %>
48
36
  <% end if allow?(:copy_url_to_clipboard) %>
49
37
 
50
- <% dropdown.with_item do %>
51
- <%= button_to paths[:delete], method: :delete, class: dropdown.item_classes, form_class: dropdown.form_item_classes, data: { turbo_confirm: t('maglev.editor.pages.actions.confirm_delete'), turbo_frame: '_top' } do %>
52
- <%= render Maglev::Uikit::IconComponent.new(name: 'delete_bin', class_names: 'shrink-0') %>
53
- <span class="ml-2 whitespace-nowrap truncate overflow-hidden"><%= t('maglev.editor.pages.actions.delete') %></span>
54
- <% end %>
38
+ <% menu_dropdown.with_item_divider if allow?(:clone) || allow?(:delete) %>
39
+
40
+ <% menu_dropdown.with_item_button_to(paths[:clone], data: { turbo_frame: '_top' }) do |menu_item| %>
41
+ <% menu_item.with_icon_content('clone') %>
42
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.clone')) %>
43
+ <% end if allow?(:clone) %>
44
+
45
+ <% menu_dropdown.with_item_button_to(paths[:delete], method: :delete, variant: 'danger', data: { turbo_confirm: t('maglev.editor.pages.actions.confirm_delete'), turbo_frame: '_top' }) do |menu_item| %>
46
+ <% menu_item.with_icon_content('delete_bin') %>
47
+ <% menu_item.with_label_content(t('maglev.editor.pages.actions.delete')) %>
55
48
  <% end if allow?(:delete) %>
56
49
  <% end %>
@@ -27,20 +27,17 @@
27
27
  <% end %>
28
28
  </div>
29
29
 
30
- <div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
31
- <div>
32
- <p class="text-sm">
30
+ <div class="hidden sm:flex sm:flex-1 sm:items-center <%= show_info? ? 'sm:justify-between' : 'sm:justify-center' %>">
31
+ <p class="text-sm <%= 'hidden' unless show_info? %>">
33
32
  <%== pagy_info(pagy, item_name: item_name) %>
34
- </p>
35
- </div>
33
+ </p>
36
34
 
37
- <div class="<%= 'hidden' if pagy.series.size == 1 %>">
38
35
  <nav class="isolate inline-flex gap-1" aria-label="Pages">
39
36
  <%# Previous page link %>
40
37
  <% if pagy.prev %>
41
- <%== a.(pagy.prev, left_arrow, aria_label: 'Previous', classes: 'relative inline-flex items-center rounded-sm px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
38
+ <%== a.(pagy.prev, left_arrow, aria_label: 'Previous', classes: 'relative inline-flex items-center rounded-xs px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
42
39
  <% else %>
43
- <a role="link" aria-disabled="true" aria-label="Previous" class="relative inline-flex items-center rounded-sm px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0">
40
+ <a role="link" aria-disabled="true" aria-label="Previous" class="relative inline-flex items-center rounded-xs px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0">
44
41
  <%= left_arrow %>
45
42
  </a>
46
43
  <% end %>
@@ -48,9 +45,9 @@
48
45
  <%# Page links (series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]) %>
49
46
  <% pagy.series.each do |item| %>
50
47
  <% if item.is_a?(Integer) %>
51
- <%== a.(item, classes: 'relative inline-flex items-center px-2.5 py-1 text-sm font-semibold text-gray-900 hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
48
+ <%== a.(item, classes: 'relative inline-flex items-center px-2.5 py-1 text-sm font-semibold text-gray-900 rounded-xs hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
52
49
  <% elsif item.is_a?(String) %>
53
- <a role="link" aria-disabled="true" aria-current="page" class="relative z-10 inline-flex items-center bg-editor-primary px-2.5 py-1 text-sm font-semibold text-white focus:z-20"><%= item %></a>
50
+ <a role="link" aria-disabled="true" aria-current="page" class="relative z-10 inline-flex items-center rounded-xs bg-editor-primary px-2.5 py-1 text-sm font-semibold text-white focus:z-20"><%= item %></a>
54
51
  <% elsif item == :gap %>
55
52
  <a role="link" aria-disabled="true" class="relative inline-flex items-center px-2.5 py-1 text-sm font-semibold text-gray-700 focus:outline-offset-0">&hellip;</a>
56
53
  <% end %>
@@ -58,9 +55,9 @@
58
55
 
59
56
  <%# Next page link %>
60
57
  <% if pagy.next %>
61
- <%== a.(pagy.next, right_arrow, aria_label: 'Next', classes: 'relative inline-flex items-center rounded-sm px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
58
+ <%== a.(pagy.next, right_arrow, aria_label: 'Next', classes: 'relative inline-flex items-center rounded-xs px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0') %>
62
59
  <% else %>
63
- <a role="link" aria-disabled="true" aria-label="Next" class="relative inline-flex items-center rounded-sm px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0">
60
+ <a role="link" aria-disabled="true" aria-label="Next" class="relative inline-flex items-center rounded-xs px-1 py-1 text-gray-400 hover:bg-gray-100 focus:z-20 focus:outline-offset-0">
64
61
  <%= right_arrow %>
65
62
  </a>
66
63
  <% end %>
@@ -7,10 +7,11 @@ module Maglev
7
7
 
8
8
  attr_reader :pagy, :hidden_if_single_page
9
9
 
10
- def initialize(pagy:, item_name: 'item', hidden_if_single_page: true)
10
+ def initialize(pagy:, item_name: 'item', hidden_if_single_page: true, show_info: true)
11
11
  @pagy = pagy
12
12
  @hidden_if_single_page = hidden_if_single_page
13
13
  @item_name = item_name
14
+ @show_info = show_info
14
15
  end
15
16
 
16
17
  def item_name
@@ -20,6 +21,10 @@ module Maglev
20
21
  def render?
21
22
  !(hidden_if_single_page && pagy.pages < 2)
22
23
  end
24
+
25
+ def show_info?
26
+ @show_info
27
+ end
23
28
  end
24
29
  end
25
30
  end
@@ -7,7 +7,7 @@
7
7
  data-transition-leave-start="translate-y-0 opacity-100"
8
8
  data-transition-leave-end="translate-y-[50%] opacity-0"
9
9
  >
10
- <%= link_to paths[:add], class: button_classes, data: { turbo_frame: '_top' } do %>
10
+ <%= link_to paths[:add], class: button_classes, data: { turbo_frame: 'modal' } do %>
11
11
  <%= render Maglev::Uikit::IconComponent.new(name: 'add') %>
12
12
  <% end if insert_button? %>
13
13
  </div>
@@ -8,9 +8,9 @@
8
8
  <input type="hidden" name="active_tab" value="<%= active_tab_index %>" data-uikit-tabs-target="hiddenInput" />
9
9
 
10
10
  <nav>
11
- <ul class="flex flex-col sm:flex-row">
11
+ <ul class="flex flex-row">
12
12
  <% tabs.each_with_index do |tab, index| %>
13
- <li>
13
+ <li class="shrink-0">
14
14
  <% if tab.link? %>
15
15
  <%= link_to tab.label, tab.link[:url], class: label_classes(active: tab.active?), **tab.link_html_options %>
16
16
  <% else %>
@@ -25,11 +25,14 @@
25
25
  </ul>
26
26
  </nav>
27
27
 
28
- <div class="relative -mt-0.5"><div class="w-full border-gray-200 border-t-2 h-0"></div></div>
28
+ <% if variant == :default %>
29
+ <div class="relative -mt-0.5"><div class="w-full border-gray-200 border-t-2 h-0"></div></div>
30
+ <% end %>
29
31
 
30
32
  <% tabs.each_with_index do |tab, index| %>
33
+ <% next unless tab.block? %>
31
34
  <div data-uikit-tabs-target="tabContent" class="flex-1 mt-4 pt-2 pb-4 overflow-y-auto overflow-x-visible <%= 'hidden' unless tab.active? %>">
32
- <%= capture(&tab.block) if tab.block? %>
35
+ <%= capture(&tab.block) %>
33
36
  </div>
34
37
  <% end %>
35
38
  </div>
@@ -7,16 +7,21 @@ module Maglev
7
7
  Tab.new(self, label: label, active: active, link: link, block: block)
8
8
  }
9
9
 
10
- attr_reader :container_classes, :active_tab, :active_tab_index, :disable_inputs
10
+ attr_reader :container_classes, :active_tab, :active_tab_index, :disable_inputs, :variant
11
11
 
12
12
  # disable_inputs: true | false (default: false) if true, the inputs of hidden tabs will not be submitted
13
- def initialize(container_classes: nil, active_tab_index: nil, disable_inputs: false)
13
+ def initialize(container_classes: nil, active_tab_index: nil, disable_inputs: false, variant: :default)
14
14
  @container_classes = container_classes
15
15
  @active_tab_index = active_tab_index
16
16
  @disable_inputs = disable_inputs
17
+ @variant = variant
17
18
  end
18
19
 
19
20
  def label_classes(...)
21
+ variant == :pills ? pills_label_classes(...) : default_label_classes(...)
22
+ end
23
+
24
+ def default_label_classes(...)
20
25
  class_variants(
21
26
  base: %(
22
27
  relative py-1 pb-0 px-4 block focus:outline-none border-b-2
@@ -29,9 +34,23 @@ module Maglev
29
34
  ).render(...)
30
35
  end
31
36
 
37
+ def pills_label_classes(...)
38
+ class_variants(
39
+ base: %(rounded-md px-3 py-2 text-sm),
40
+ variants: {
41
+ active: 'rounded-md bg-editor-primary/10 font-semibold text-editor-primary',
42
+ '!active': 'text-gray-500 hover:text-gray-700 font-medium'
43
+ }
44
+ ).render(...)
45
+ end
46
+
32
47
  def before_render
33
- @active_tab = tabs.find(&:active?) || tabs[active_tab_index.to_i]
34
- @active_tab.active = true
48
+ @active_tab = tabs.find(&:active?)
49
+
50
+ return unless !@active_tab && active_tab_index != -1
51
+
52
+ @active_tab = tabs[active_tab_index.to_i]
53
+ @active_tab&.active = true
35
54
  end
36
55
 
37
56
  def disable_inputs?
@@ -0,0 +1,15 @@
1
+ <div class="bg-gray-50 sm:rounded-lg">
2
+ <div class="px-4 py-5 sm:p-6">
3
+ <h3 class="text-base font-semibold text-gray-900">
4
+ <%= title %>
5
+ </h3>
6
+ <div class="mt-2 max-w-xl text-sm text-gray-500 space-y-2">
7
+ <%= description %>
8
+ </div>
9
+ <% if action? %>
10
+ <div class="mt-5">
11
+ <%= action %>
12
+ </div>
13
+ <% end %>
14
+ </div>
15
+ </div>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Uikit
5
+ module Well
6
+ class SimpleWellComponent < ViewComponent::Base
7
+ renders_one :title
8
+ renders_one :description
9
+ renders_one :action
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Editor
5
+ module PreviewUrlsConcern
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :current_maglev_page_urls, :maglev_page_live_url, :maglev_page_preview_url
10
+ end
11
+
12
+ private
13
+
14
+ def current_maglev_page_urls
15
+ {
16
+ path: maglev_services.get_page_fullpath.call(page: current_maglev_page, preview_mode: false,
17
+ locale: content_locale),
18
+ preview: maglev_page_preview_url(current_maglev_page),
19
+ live: maglev_page_live_url(current_maglev_page)
20
+ }
21
+ end
22
+
23
+ def maglev_page_live_url(page)
24
+ maglev_services.get_page_fullpath.call(page: page, preview_mode: false, locale: content_locale)
25
+ end
26
+
27
+ def maglev_page_preview_url(page)
28
+ maglev_services.get_page_fullpath.call(page: page, preview_mode: true, locale: content_locale)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Editor
5
+ module TurboConcern
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :maglev_disable_turbo_cache?
10
+ end
11
+
12
+ private
13
+
14
+ def maglev_disable_turbo_cache
15
+ @maglev_disable_turbo_cache = true
16
+ end
17
+
18
+ def maglev_disable_turbo_cache?
19
+ !!@maglev_disable_turbo_cache
20
+ end
21
+
22
+ def ensure_turbo_frame_request
23
+ return if turbo_frame_request?
24
+
25
+ redirect_to editor_root_path
26
+ end
27
+ end
28
+ end
29
+ end
@@ -5,6 +5,7 @@ module Maglev
5
5
  private
6
6
 
7
7
  def flash_t(type, **opts)
8
+ controller_name = controller_path.gsub('maglev/editor/', '').gsub('/', '.')
8
9
  t("maglev.editor.flash.#{controller_name}.#{action_name}.#{type}", **opts)
9
10
  end
10
11
  end