maglevcms 3.0.0.beta3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) 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/index.html.erb +1 -0
  86. data/app/views/maglev/editor/home/index.html.erb +1 -1
  87. data/app/views/maglev/editor/links/edit/_email.html.erb +1 -1
  88. data/app/views/maglev/editor/links/edit/_url.html.erb +1 -1
  89. data/app/views/maglev/editor/pages/_list.html.erb +25 -21
  90. data/app/views/maglev/editor/pages/_preview.html.erb +6 -6
  91. data/app/views/maglev/editor/pages/_preview_empty_message.html.erb +13 -10
  92. data/app/views/maglev/editor/pages/discard_draft/create.turbo_stream.erb +16 -0
  93. data/app/views/maglev/editor/pages/index.html.erb +8 -1
  94. data/app/views/maglev/editor/section_blocks/_form.html.erb +5 -13
  95. data/app/views/maglev/editor/section_blocks/_form_with_tabs.html.erb +15 -0
  96. data/app/views/maglev/editor/section_blocks/_new.html.erb +1 -1
  97. data/app/views/maglev/editor/section_blocks/edit.html.erb +2 -2
  98. data/app/views/maglev/editor/section_blocks/index/_list.html.erb +2 -2
  99. data/app/views/maglev/editor/section_blocks/index/_tree.html.erb +1 -1
  100. data/app/views/maglev/editor/section_blocks/update.turbo_stream.erb +1 -1
  101. data/app/views/maglev/editor/sections/_form.html.erb +6 -20
  102. data/app/views/maglev/editor/sections/_form_with_tabs.html.erb +21 -0
  103. data/app/views/maglev/editor/sections/_list.html.erb +1 -1
  104. data/app/views/maglev/editor/sections/edit.html.erb +3 -3
  105. data/app/views/maglev/editor/sections/index.html.erb +1 -1
  106. data/app/views/maglev/editor/sections/new.html.erb +18 -1
  107. data/app/views/maglev/editor/sections/theme/_empty_list.html.erb +3 -0
  108. data/app/views/maglev/editor/sections/theme/_list.html.erb +35 -0
  109. data/app/views/maglev/editor/sections/theme/_screenshot_placeholder.html.erb +6 -0
  110. data/app/views/maglev/editor/sections/theme/_search.html.erb +22 -0
  111. data/app/views/maglev/editor/sections/update.turbo_stream.erb +1 -1
  112. data/app/views/maglev/editor/shared/_button_label.html.erb +4 -4
  113. data/app/views/maglev/editor/style/edit.html.erb +1 -0
  114. data/config/editor_importmap.rb +13 -13
  115. data/config/locales/editor.ar.yml +12 -4
  116. data/config/locales/editor.en.yml +31 -23
  117. data/config/locales/editor.es.yml +12 -4
  118. data/config/locales/editor.fr.yml +12 -4
  119. data/config/locales/editor.pt-BR.yml +12 -4
  120. data/config/routes/maglev/assets.rb +4 -0
  121. data/config/routes/maglev/editor.rb +38 -0
  122. data/config/routes/maglev/preview.rb +8 -0
  123. data/config/routes/maglev/public_preview.rb +6 -0
  124. data/config/routes.rb +8 -47
  125. data/db/migrate/20211013210954_translate_section_content.rb +1 -0
  126. data/db/migrate/20260114112058_add_published_payload_to_pages.rb +14 -0
  127. data/exe/tailwind-cli +1 -1
  128. data/lib/generators/maglev/install_generator.rb +9 -7
  129. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +10 -3
  130. data/lib/maglev/active_storage/serving_blob.rb +29 -0
  131. data/lib/maglev/active_storage.rb +2 -0
  132. data/lib/maglev/config.rb +22 -3
  133. data/lib/maglev/engine.rb +14 -10
  134. data/lib/maglev/version.rb +1 -1
  135. data/lib/maglev.rb +18 -3
  136. data/lib/tasks/db_test_all.rake +290 -0
  137. metadata +46 -19
  138. data/app/controllers/maglev/editor/page_clone_controller.rb +0 -20
  139. data/app/views/maglev/editor/sections/_theme_list.html.erb +0 -32
  140. /data/vendor/javascript/{@floating-ui--core.js → maglev/@floating-ui--core.js} +0 -0
  141. /data/vendor/javascript/{@floating-ui--dom.js → maglev/@floating-ui--dom.js} +0 -0
  142. /data/vendor/javascript/{@floating-ui--utils--dom.js → maglev/@floating-ui--utils--dom.js} +0 -0
  143. /data/vendor/javascript/{@floating-ui--utils.js → maglev/@floating-ui--utils.js} +0 -0
  144. /data/vendor/javascript/{@hotwired--stimulus.js → maglev/@hotwired--stimulus.js} +0 -0
  145. /data/vendor/javascript/{@hotwired--turbo-rails.js → maglev/@hotwired--turbo-rails.js} +0 -0
  146. /data/vendor/javascript/{@hotwired--turbo.js → maglev/@hotwired--turbo.js} +0 -0
  147. /data/vendor/javascript/{@rails--actioncable--src.js → maglev/@rails--actioncable--src.js} +0 -0
  148. /data/vendor/javascript/{@rails--request.js.js → maglev/@rails--request.js.js} +0 -0
  149. /data/vendor/javascript/{@shopify--draggable.js → maglev/@shopify--draggable.js} +0 -0
  150. /data/vendor/javascript/{el-transition.js → maglev/el-transition.js} +0 -0
  151. /data/vendor/javascript/{stimulus-use.js → maglev/stimulus-use.js} +0 -0
  152. /data/vendor/javascript/{tiptap.bundle.js → maglev/tiptap.bundle.js} +0 -0
@@ -1,18 +1,18 @@
1
1
 
2
2
  import { StreamActions } from '@hotwired/turbo'
3
3
  import TurboDelayedStreams from 'maglev-patches/turbo_delayed_streams'
4
- import { generateRequestId } from 'maglev-controllers/utils'
4
+ import { generateRequestId, log } from 'maglev-controllers/utils'
5
5
 
6
6
  // Custom stream actions
7
7
  StreamActions.console_log = function() {
8
8
  const message = this.getAttribute("message")
9
- console.log(message)
9
+ log(message)
10
10
  }
11
11
 
12
12
  StreamActions.dispatch_event = function() {
13
13
  const type = this.getAttribute("type")
14
14
  const payload = this.getAttribute("payload")
15
- console.log('dispatchEvent', type, payload, `dispatcher:${type}`)
15
+ log('dispatchEvent', type, payload, `dispatcher:${type}`)
16
16
  const event = new CustomEvent(`dispatcher:${type}`, { detail: JSON.parse(payload) })
17
17
  window.dispatchEvent(event)
18
18
  }
@@ -8,8 +8,12 @@
8
8
  --color-editor-primary: var(--editor-color-primary);
9
9
  }
10
10
 
11
+ /* Empty-state overlay in #root: visible when the preview Stimulus shell has .is-empty */
12
+ @custom-variant editor-preview-empty (body:has([data-controller~="editor-page-preview"].is-empty) &);
13
+
11
14
  @source "<%= Maglev::Engine.root.join('app') %>";
12
15
  @source "<%= Maglev::Engine.root.join('lib') %>";
16
+ @source "<%= Maglev::Engine.root.join('spec/components/previews/maglev/uikit') %>";
13
17
 
14
18
  <% Maglev.config.tailwindcss_folders.each do |folder| %>
15
19
  @source "<%= folder %>";
@@ -28,6 +32,10 @@
28
32
  }
29
33
 
30
34
  @layer base {
35
+ .turbo-progress-bar {
36
+ background-color: var(--editor-color-primary) !important;
37
+ }
38
+
31
39
  /* Tiptap / ProseMirror */
32
40
  .ProseMirror {
33
41
  @apply block py-2 px-3 rounded bg-gray-100 text-gray-800 focus:outline-none focus:ring focus:ring-inset focus:ring-2 focus:ring-editor-primary/50 placeholder-gray-500;
@@ -143,7 +151,6 @@
143
151
  td,
144
152
  th {
145
153
  min-width: 1em;
146
- // border: 2px solid $color-grey;
147
154
  @apply border-2;
148
155
  @apply border-solid;
149
156
  padding: 3px 5px;
@@ -4,7 +4,7 @@ module Maglev
4
4
  module Content
5
5
  class Link < Base
6
6
  def href
7
- link[:href]
7
+ link[:href] || '#'
8
8
  end
9
9
 
10
10
  def text
@@ -6,12 +6,18 @@ module Maglev
6
6
  module Link
7
7
  class LinkComponent < Maglev::Editor::Settings::BaseComponent
8
8
  def edit_link_path
9
- fetch_path(:edit_link_path, { value: value, input_name: input_name })
9
+ fetch_path(:edit_link_path, { value: value || default_value, input_name: input_name })
10
10
  end
11
11
 
12
12
  def after_initialize
13
13
  @value = value&.with_indifferent_access
14
14
  end
15
+
16
+ private
17
+
18
+ def default_value
19
+ { link_type: 'url' }
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # NOTE: this class is too long (but not too complex),
4
+ # we need to move it along with the PageComponent and BlockComponent
5
+ # to a separate folder (content?)
6
+
7
+ # rubocop:disable Metrics/ClassLength
3
8
  module Maglev
4
9
  class SectionComponent < BaseComponent
5
10
  include TagHelper
@@ -31,7 +36,7 @@ module Maglev
31
36
  end
32
37
 
33
38
  def lock_version
34
- @lock_version ||= attributes[:lock_version] || '0'
39
+ @lock_version ||= lock_source&.lock_version || '0'
35
40
  end
36
41
 
37
42
  def dom_data
@@ -63,6 +68,10 @@ module Maglev
63
68
 
64
69
  private
65
70
 
71
+ def lock_source
72
+ definition&.site_scoped? ? site : page
73
+ end
74
+
66
75
  def build_block_list
67
76
  build_blocks(attributes[:blocks])
68
77
  end
@@ -131,3 +140,4 @@ module Maglev
131
140
  end
132
141
  end
133
142
  end
143
+ # rubocop:enable Metrics/ClassLength
@@ -1,5 +1,5 @@
1
1
  <li>
2
- <%= link_to path, class: link_classes(active: active?), data: data do %>
2
+ <%= link_to path, link_html_options do %>
3
3
  <%= render Maglev::Uikit::IconComponent.new(name: icon, size: icon_size) %>
4
4
  <% end %>
5
5
  </li>
@@ -5,12 +5,24 @@ module Maglev
5
5
  module AppLayout
6
6
  module Sidebar
7
7
  class LinkComponent < Maglev::Uikit::BaseComponent
8
- attr_reader :path, :icon, :icon_size, :active, :data
8
+ LINK_BASE_CLASSES = [
9
+ 'relative flex w-full min-h-11 items-center justify-center rounded py-3',
10
+ 'outline-none transition-colors duration-200',
11
+ 'focus-visible:ring-2 focus-visible:ring-editor-primary/50',
12
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-white'
13
+ ].join(' ')
9
14
 
10
- def initialize(path:, icon:, active: false, options: {})
15
+ LINK_ACTIVE_CLASSES = 'bg-gray-100 text-editor-primary hover:bg-gray-100'
16
+
17
+ LINK_INACTIVE_CLASSES = 'text-black hover:bg-gray-100'
18
+
19
+ attr_reader :path, :icon, :icon_size, :active, :data, :label
20
+
21
+ def initialize(path:, icon:, active: false, label: nil, options: {})
11
22
  @path = path
12
23
  @active = active
13
24
  @icon = icon
25
+ @label = label
14
26
  @icon_size = options[:icon_size] || '1.5rem'
15
27
  @position = options[:position] || :top
16
28
  @data = options[:data]
@@ -24,15 +36,33 @@ module Maglev
24
36
  @position == :top
25
37
  end
26
38
 
39
+ def link_html_options
40
+ {
41
+ class: link_classes(active: active?),
42
+ data: data,
43
+ title: label.presence,
44
+ aria: link_aria_attributes
45
+ }.compact
46
+ end
47
+
27
48
  def link_classes(...)
28
49
  class_variants(
29
- base: 'flex justify-center py-5 -ml-4 -mr-4 hover:bg-gray-100 transition-colors duration-200',
50
+ base: LINK_BASE_CLASSES,
30
51
  variants: {
31
- active: 'bg-gray-100',
32
- '!active': 'bg-white'
52
+ active: LINK_ACTIVE_CLASSES,
53
+ '!active': LINK_INACTIVE_CLASSES
33
54
  }
34
55
  ).render(...)
35
56
  end
57
+
58
+ private
59
+
60
+ def link_aria_attributes
61
+ aria = {}
62
+ aria[:label] = label.presence
63
+ aria[:current] = 'page' if active?
64
+ aria.compact.presence
65
+ end
36
66
  end
37
67
  end
38
68
  end
@@ -2,15 +2,14 @@
2
2
  id="app-layout-sidebar"
3
3
  class="w-16 flex-shrink-0 flex content-center h-full border-r border-gray-200 relative z-20 bg-white"
4
4
  >
5
- <nav class="w-16 flex flex-col justify-between">
6
- <ol class="divide-y divide-gray-300 px-4">
5
+ <nav class="flex h-full w-16 flex-col justify-between py-2">
6
+ <ol class="flex flex-col gap-1 px-2">
7
7
  <% links.each do |link| %>
8
8
  <%= link if link.top? %>
9
9
  <% end %>
10
10
  </ol>
11
11
 
12
- <ol class="divide-y divide-gray-300 px-4">
13
- <li></li>
12
+ <ol class="flex flex-col gap-1 px-2">
14
13
  <% links.each do |link| %>
15
14
  <%= link unless link.top? %>
16
15
  <% end %>
@@ -9,7 +9,7 @@
9
9
  <%= render Maglev::Uikit::DeviceTogglerComponent.new %>
10
10
  </div>
11
11
 
12
- <div class="col-span-2 flex justify-end h-full">
12
+ <div class="col-span-2 flex justify-end h-full items-center">
13
13
  <%= actions %>
14
14
  </div>
15
15
  </div>
@@ -5,7 +5,7 @@ module Maglev
5
5
  module AppLayout
6
6
  class TopbarComponent < ViewComponent::Base
7
7
  renders_one :logo, 'Maglev::Uikit::AppLayout::Topbar::LogoComponent'
8
- # -> { Maglev::Uikit::AppLayout::Topbar::LogoComponent.new(root_path: root_path, logo_url: logo_url) }
8
+
9
9
  renders_one :page_info
10
10
  renders_one :actions
11
11
 
@@ -0,0 +1,5 @@
1
+ <div class="[&>*:first-child]:rounded-l-sm [&>*:last-child]:rounded-r-sm inline-flex items-center text-white divide-x divide-white/15">
2
+ <% buttons.each do |button| %>
3
+ <%= button %>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Uikit
5
+ class ButtonGroupComponent < Maglev::Uikit::BaseComponent
6
+ renders_many :buttons
7
+
8
+ attr_reader :color, :size
9
+
10
+ def initialize(color: nil, size: nil)
11
+ @color = color || :primary
12
+ @size = size || :medium
13
+ end
14
+
15
+ # rubocop:disable Metrics/MethodLength
16
+ def button_classes
17
+ class_variants(
18
+ base: %(
19
+ inline-flex items-center justify-center cursor-pointer
20
+ group-[.is-success]/form:bg-green-500/95 group-[.is-success]/form:hover:bg-green-500/100
21
+ group-[.is-success]/form:disabled:bg-green-500/75
22
+ group-[.is-error]/form:bg-red-500/95 group-[.is-error]/form:hover:bg-red-500/100
23
+ group-[.is-error]/form:disabled:bg-red-500/75
24
+ ),
25
+ variants: {
26
+ size: {
27
+ medium: 'inline-flex items-center justify-center px-4 h-10'
28
+ },
29
+ color: {
30
+ primary: 'text-white bg-editor-primary hover:bg-editor-primary/90 disabled:bg-editor-primary/75',
31
+ secondary: 'text-gray-800 hover:bg-gray-100'
32
+ }
33
+ }
34
+ ).render(color: color, size: size)
35
+ end
36
+ # rubocop:enable Metrics/MethodLength
37
+
38
+ # rubocop:disable Metrics/MethodLength
39
+ def wrapper_classes(**args)
40
+ class_variants(
41
+ base: %(
42
+ inline-flex items-center transition-colors duration-200 h-full
43
+ [.is-success]:bg-green-500/95 [.is-success]:hover:bg-green-500/100
44
+ [.is-success]:disabled:bg-green-500/75
45
+ [.is-error]:bg-red-500/95 [.is-error]:hover:bg-red-500/100
46
+ [.is-error]:disabled:bg-red-500/75
47
+ ),
48
+ variants: {
49
+ color: {
50
+ primary: 'text-white bg-editor-primary hover:bg-editor-primary/90 has-[:disabled]:bg-editor-primary/75',
51
+ secondary: 'text-gray-800 hover:bg-gray-100'
52
+ }
53
+ }
54
+ ).render(color: color, **args)
55
+ end
56
+ # rubocop:enable Metrics/MethodLength
57
+
58
+ def wrapped_button_classes(**args)
59
+ class_variants(
60
+ base: 'cursor-pointer',
61
+ variants: {
62
+ size: {
63
+ medium: 'inline-flex items-center justify-center px-4 h-10'
64
+ }
65
+ }
66
+ ).render(size: size, **args)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -5,13 +5,22 @@ module Maglev
5
5
  class DeviceTogglerComponent < Maglev::Uikit::BaseComponent
6
6
  def toggler_classes(...)
7
7
  class_variants(
8
- base: 'cursor-pointer hover:bg-gray-100 h-10 w-10 flex items-center justify-center',
8
+ base: base_classes,
9
9
  variants: {
10
10
  active: active_classes
11
11
  }
12
12
  ).render(...)
13
13
  end
14
14
 
15
+ def base_classes
16
+ [
17
+ 'cursor-pointer hover:bg-gray-100 h-10 w-10 flex items-center justify-center rounded',
18
+ 'outline-none transition-colors duration-200',
19
+ 'focus-visible:ring-2 focus-visible:ring-editor-primary/50',
20
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-white'
21
+ ].join(' ').freeze
22
+ end
23
+
15
24
  def active_classes
16
25
  'bg-gray-100'
17
26
  end
@@ -1,5 +1,5 @@
1
1
  <%= tag.div(
2
- class: 'relative',
2
+ class: wrapper_classes,
3
3
  data: {
4
4
  controller: 'uikit-dropdown',
5
5
  'uikit-dropdown-placement-value': placement
@@ -8,7 +8,7 @@
8
8
  <%= trigger %>
9
9
 
10
10
  <div
11
- class="hidden z-50 opacity-0 scale-95 shadow-lg rounded p-1 text-sm bg-white w-max fixed"
11
+ class="hidden z-50 opacity-0 scale-95 shadow-lg rounded text-sm bg-white w-max fixed"
12
12
  data-aria-orientation="vertical"
13
13
  data-aria-labelledby="dropdown-button"
14
14
  data-uikit-dropdown-target="content"
@@ -7,9 +7,14 @@ module Maglev
7
7
 
8
8
  attr_reader :placement
9
9
 
10
- def initialize(placement: 'bottom-start')
10
+ def initialize(placement: 'bottom-start', wrapper_classes: nil)
11
+ @wrapper_classes = wrapper_classes
11
12
  @placement = placement
12
13
  end
14
+
15
+ def wrapper_classes
16
+ helpers.class_names('relative', @wrapper_classes)
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -50,7 +50,7 @@
50
50
  ) %>
51
51
  <% end %>
52
52
 
53
- <div class="flex flex-row flex-wrap gap-2">
53
+ <div class="flex flex-row flex-wrap gap-2 p-2">
54
54
  <% presets.each do |preset| %>
55
55
  <%= tag.button(
56
56
  type: 'button',
@@ -33,6 +33,7 @@
33
33
  placeholder: placeholder || t('maglev.uikit.form.combobox.placeholder'),
34
34
  value: selected_label,
35
35
  aria: { controls: 'options', expanded: 'false' },
36
+ autocomplete: 'off',
36
37
  data: {
37
38
  'uikit-form-combobox-target' => 'input',
38
39
  'uikit-dropdown-target' => 'button',
@@ -59,7 +59,7 @@ module Maglev
59
59
 
60
60
  def self.link_type_component(input_name:, value:, path:)
61
61
  # Default to url if the link type is not found (shouldn't happen)
62
- klass = link_type_klasses_map.fetch(value[:link_type].to_sym, 'url')
62
+ klass = link_type_klasses_map.fetch(value[:link_type]&.to_sym, default_link_type_klass)
63
63
 
64
64
  return unless klass
65
65
 
@@ -74,6 +74,10 @@ module Maglev
74
74
  static_page: Maglev::Uikit::Form::Link::StaticPageLinkComponent
75
75
  }
76
76
  end
77
+
78
+ def self.default_link_type_klass
79
+ Maglev::Uikit::Form::Link::UrlLinkComponent
80
+ end
77
81
  end
78
82
  end
79
83
  end
@@ -1,5 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import { Editor, Document, Text, Paragraph, Heading, Blockquote, CodeBlock, History, Bold, Italic, Underline, Strike, Superscript, HardBreak, Link, ListItem, BulletList, OrderedList } from 'tiptap'
3
+ import { log } from 'maglev-controllers/utils'
3
4
 
4
5
  const MaglevLink = Link.extend({
5
6
  addAttributes() {
@@ -205,11 +206,11 @@ export default class extends Controller {
205
206
  }
206
207
 
207
208
  openLinkModal(event) {
208
- console.log('toggleLink', event)
209
+ log('toggleLink', event)
209
210
  const url = new URL(this.editLinkPathValue, window.location.origin)
210
211
  const link = this.editor.getAttributes('link')
211
212
 
212
- console.log('toggleLink,build link from', link)
213
+ log('toggleLink,build link from', link)
213
214
 
214
215
  url.searchParams.set('input_name', this.inputNameValue)
215
216
  url.searchParams.set('link[href]', link.href ?? '')
@@ -230,8 +231,8 @@ export default class extends Controller {
230
231
  }
231
232
 
232
233
  setLink(event) {
233
- const link = JSON.parse(event.detail)
234
- console.log('setLink, link=', link)
234
+ const link = typeof event.detail === 'string' ? JSON.parse(event.detail) : event.detail
235
+ log('setLink, link=', link)
235
236
  this.editor.commands.setLink({
236
237
  href: link.href,
237
238
  target: link.open_new_window ? '_blank' : '',
@@ -14,6 +14,7 @@
14
14
  value: value,
15
15
  placeholder: placeholder,
16
16
  aria_label: placeholder,
17
+ autocomplete: 'off',
17
18
  class: 'block w-full border-0 bg-transparent focus:outline-none placeholder-gray-500',
18
19
  data: { uikit_search_form_target: 'input' }
19
20
  ) %>
@@ -68,6 +68,7 @@ module Maglev
68
68
  arrow_down: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="fill-current"><path fill="none" d="M0 0h24v24H0z"></path><path d="m12 13.172 4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"></path></svg>',
69
69
  arrow_left: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"></path></svg>',
70
70
  arrow_right: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"></path></svg>',
71
+ arrow_right_long: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M1.99974 13.0001L1.9996 11.0002L18.1715 11.0002L14.2218 7.05044L15.636 5.63623L22 12.0002L15.636 18.3642L14.2218 16.9499L18.1716 13.0002L1.99974 13.0001Z"></path></svg>',
71
72
 
72
73
  image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" fill="#000"/></svg>',
73
74
 
@@ -131,6 +132,8 @@ module Maglev
131
132
 
132
133
  logout: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 22a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v3h-2V4H6v16h12v-2h2v3a1 1 0 0 1-1 1H5zm13-6v-3h-7v-2h7V8l5 4-5 4z"/></svg>',
133
134
 
135
+ history: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12H4C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C9.25022 4 6.82447 5.38734 5.38451 7.50024L8 7.5V9.5H2V3.5H4L3.99989 5.99918C5.82434 3.57075 8.72873 2 12 2ZM13 7L12.9998 11.585L16.2426 14.8284L14.8284 16.2426L10.9998 12.413L11 7H13Z"></path></svg>',
136
+
134
137
  # Spinners
135
138
  spinner: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.364 5.63604L16.9497 7.05025C15.683 5.7835 13.933 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12H21C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C14.4853 3 16.7353 4.00736 18.364 5.63604Z"></path></svg>'
136
139
  }.with_indifferent_access.freeze
@@ -1,5 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import { FetchRequest } from '@rails/request.js'
3
+ import { log } from 'maglev-controllers/utils'
3
4
 
4
5
  export default class extends Controller {
5
6
  static targets = ['button', 'fileInput']
@@ -11,7 +12,7 @@ export default class extends Controller {
11
12
  }
12
13
 
13
14
  connect() {
14
- console.log('UploaderController connected')
15
+ log('UploaderController connected')
15
16
  }
16
17
 
17
18
  openFileDialog() {
@@ -64,7 +65,7 @@ export default class extends Controller {
64
65
  }
65
66
 
66
67
  reloadFrameTag() {
67
- console.log('reloadFrameTag', this.refreshPathValue)
68
+ log('reloadFrameTag', this.refreshPathValue)
68
69
  const frame = document.querySelector(`turbo-frame#modal-dialog`)
69
70
  if (frame) {
70
71
  frame.src = this.refreshPathValue
@@ -1,29 +1,48 @@
1
1
  <%= tag.div(data: { sortable_target: sortable_target, item_id: id, item_index: index }) do %>
2
- <%= tag.div(class: wrapper_classes) do %>
2
+ <%= content_tag(wrapper_tag_name.to_sym, class: wrapper_classes) do %>
3
+ <%= content if content? %>
3
4
 
4
5
  <%= tag.div(class: 'flex items-center cursor-move', data: { sortable_target: sortable_handle }) do %>
5
6
  <%= render Maglev::Uikit::IconComponent.new(name: "draggable") %>
6
7
  <% end if handle? %>
7
8
 
8
9
  <% item_body = capture do %>
9
- <% if image? %>
10
- <div class="h-8 w-8 bg-gray-400">
11
- <%= image %>
10
+ <% if icon? %>
11
+ <div class="grid grid-cols-[auto_1fr_auto] flex flex-1 items-center">
12
+ <div class="mr-2">
13
+ <%= icon %>
14
+ </div>
15
+ <div class="text-gray-800 truncate"><%= title %></div>
16
+ <% if sub_title? %>
17
+ <div class="col-start-2 text-gray-500 text-xs"><%= sub_title %></div>
18
+ <% end %>
12
19
  </div>
13
- <% end %>
14
-
15
- <% if big_image? %>
16
- <%= big_image %>
17
- <% end %>
20
+ <% else %>
21
+ <% if image? %>
22
+ <div class="h-8 w-8 bg-gray-400">
23
+ <%= image %>
24
+ </div>
25
+ <% end %>
18
26
 
19
- <div class="flex flex-col overflow-hidden">
20
- <% if pre_title? %>
21
- <div class="text-gray-500 text-xs"><%= pre_title %></div>
22
- <div class="text-gray-800 truncate"><%= title %></div>
23
- <% else %>
24
- <div class="truncate"><%= title %></div>
27
+ <% if big_image? %>
28
+ <%= big_image %>
25
29
  <% end %>
26
- </div>
30
+
31
+ <div class="flex flex-col overflow-hidden">
32
+ <% if pre_title? %>
33
+ <div class="text-gray-500 text-xs"><%= pre_title %></div>
34
+ <div class="text-gray-800 truncate"><%= title %></div>
35
+ <% if sub_title? %>
36
+ <div class="text-gray-500 text-xs"><%= sub_title %></div>
37
+ <% end %>
38
+ <% else %>
39
+ <div class="truncate"><%= title %></div>
40
+ <% if sub_title? %>
41
+ <div class="text-gray-500 text-xs"><%= sub_title %></div>
42
+ <% end %>
43
+ <% end %>
44
+ </div>
45
+ <% end %>
27
46
  <% end %>
28
47
 
29
48
  <% if link? %>
@@ -41,7 +60,5 @@
41
60
  <%= action %>
42
61
  </div>
43
62
  <% end %>
44
- <% end %>
45
-
46
- <%= content if content? %>
63
+ <% end %>
47
64
  <% end %>
@@ -7,16 +7,20 @@ module Maglev
7
7
  renders_one :handle
8
8
  renders_one :image
9
9
  renders_one :big_image
10
+ renders_one :icon
10
11
  renders_one :pre_title
12
+ renders_one :sub_title
11
13
  renders_one :title
12
14
  renders_one :action
13
15
 
14
- attr_reader :link, :index, :options
16
+ attr_reader :link, :index, :options, :variant, :wrapper_tag_name
15
17
 
16
18
  def initialize(id: nil, link: nil, options: {})
17
19
  @id = id
18
20
  @link = link
21
+ @variant = options.fetch(:variant, :filled).to_sym
19
22
  @custom_wrapper_classes = options[:wrapper_classes]
23
+ @wrapper_tag_name = options[:wrapper_tag] || :div
20
24
  @index = options[:index]
21
25
  @options = options
22
26
  end
@@ -37,18 +41,28 @@ module Maglev
37
41
  link[:data] || {}
38
42
  end
39
43
 
44
+ # rubocop:disable Metrics/MethodLength
40
45
  def wrapper_classes
41
46
  class_variants(
42
- base: 'bg-gray-100 rounded-md px-2 flex text-gray-800'
43
- ).render(class: @custom_wrapper_classes)
47
+ base: 'rounded-md flex text-gray-800',
48
+ variants: {
49
+ variant: {
50
+ filled: 'bg-gray-100',
51
+ ghost: 'hover:bg-gray-50 transition-colors duration-200'
52
+ },
53
+ padding: { default: 'p-2', medium: 'p-3' }
54
+ },
55
+ default: { variant: :filled, padding: :default }
56
+ ).render(variant: variant, padding: big_image? ? :medium : :default, class: @custom_wrapper_classes)
44
57
  end
58
+ # rubocop:enable Metrics/MethodLength
45
59
 
46
60
  def content_classes
47
61
  class_variants(
48
- base: 'flex flex-1 py-3 gap-3 overflow-hidden px-2',
62
+ base: 'flex flex-1 gap-3 overflow-hidden',
49
63
  variants: {
50
64
  disposition: {
51
- row: 'flex-row items-center',
65
+ row: 'flex-row items-center px-2',
52
66
  col: 'flex-col'
53
67
  }
54
68
  },
@@ -1,22 +1,18 @@
1
1
  <%= render(Maglev::Uikit::MenuDropdownComponent.new) do |dropdown| %>
2
2
  <% dropdown.with_trigger do %>
3
- <button
4
- class="flex-1 h-full w-full px-6 hover:bg-gray-100 transition-colors duration-200 flex items-center focus:outline-none focus:none cursor-pointer space-x-2"
5
- data-action="click->uikit-dropdown#toggle"
6
- data-uikit-dropdown-target="button">
3
+ <%= tag.button class: helpers.maglev_button_classes(color: :secondary, class: 'space-x-2'), data: { action: 'click->uikit-dropdown#toggle', 'uikit-dropdown-target': 'button' } do %>
7
4
  <%= render Maglev::Uikit::IconComponent.new(name: 'global') %>
8
5
  <span class="whitespace-nowrap">
9
6
  <%= current_locale_label %>
10
7
  </span>
11
8
  <%= render Maglev::Uikit::IconComponent.new(name: 'arrow_down') %>
12
- </button>
9
+ <% end %>
13
10
  <% end %>
14
11
 
15
12
  <% locales.each do |locale| %>
16
- <% dropdown.with_item do %>
17
- <%= link_to locale[:path], class: dropdown.item_classes, data: locale[:data_attributes] do %>
18
- <span class="<%= 'font-bold' if locale[:value] == current_locale %>"><%= locale[:label] %></span>
19
- <% end %>
20
- <% end %>
13
+ <% dropdown.with_item_link_to(locale[:path], data: locale[:data_attributes]) do |menu_item| %>
14
+ <% menu_item.with_label_content(locale[:label]) %>
15
+ <% menu_item.with_icon_content('check') if locale[:value] == current_locale %>
16
+ <% end %>
21
17
  <% end %>
22
18
  <% end %>