maglevcms 1.8.0 → 2.0.0.beta1

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.yarn/install-state.gz +0 -0
  3. data/README.md +4 -4
  4. data/Rakefile +2 -1
  5. data/app/components/maglev/block_component.rb +1 -1
  6. data/app/components/maglev/content/base.rb +1 -1
  7. data/app/components/maglev/content/link.rb +4 -0
  8. data/app/components/maglev/section_component.rb +2 -2
  9. data/app/controllers/concerns/maglev/fetchers_concern.rb +3 -1
  10. data/app/controllers/concerns/maglev/resource_id_concern.rb +10 -0
  11. data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
  12. data/app/controllers/concerns/maglev/{ui_locale_concern.rb → user_interface_locale_concern.rb} +1 -1
  13. data/app/controllers/maglev/api/collection_items_controller.rb +7 -0
  14. data/app/controllers/maglev/api/page_clones_controller.rb +1 -2
  15. data/app/controllers/maglev/api_controller.rb +1 -1
  16. data/app/controllers/maglev/application_controller.rb +1 -5
  17. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +17 -0
  18. data/app/controllers/maglev/assets/proxy_controller.rb +21 -0
  19. data/app/controllers/maglev/editor_controller.rb +1 -1
  20. data/app/controllers/maglev/page_preview_controller.rb +3 -3
  21. data/app/frontend/admin/controllers/screenshot_controller.js +29 -1
  22. data/app/frontend/editor/assets/remixicons/clipboard-line.svg +1 -0
  23. data/app/frontend/editor/assets/remixicons/ri-draggable.svg +1 -0
  24. data/app/frontend/editor/assets/remixicons/ri-folders-line.svg +1 -0
  25. data/app/frontend/editor/assets/remixicons/ri-more-fill.svg +1 -0
  26. data/app/frontend/editor/assets/remixicons/ri-shadow-line.svg +1 -0
  27. data/app/frontend/editor/components/errors/forbidden.vue +24 -0
  28. data/app/frontend/editor/components/errors/stale-record.vue +2 -2
  29. data/app/frontend/editor/components/header-nav/device-toggler.vue +6 -3
  30. data/app/frontend/editor/components/header-nav/index.vue +13 -42
  31. data/app/frontend/editor/components/header-nav/locale-toggler/index.vue +1 -1
  32. data/app/frontend/editor/components/header-nav/page-info.vue +41 -0
  33. data/app/frontend/editor/components/header-nav/preview-button.vue +6 -1
  34. data/app/frontend/editor/components/header-nav/separator.vue +1 -1
  35. data/app/frontend/editor/components/image-library/index.vue +26 -29
  36. data/app/frontend/editor/components/kit/collection-item-input.vue +9 -0
  37. data/app/frontend/editor/components/kit/copy-paste-button.vue +26 -0
  38. data/app/frontend/editor/components/kit/dropdown.vue +5 -0
  39. data/app/frontend/editor/components/kit/icon-button.vue +24 -0
  40. data/app/frontend/editor/components/kit/icon.vue +2 -3
  41. data/app/frontend/editor/components/kit/index.js +4 -2
  42. data/app/frontend/editor/components/kit/link-input.vue +1 -1
  43. data/app/frontend/editor/components/kit/modal.vue +1 -1
  44. data/app/frontend/editor/components/kit/page-icon.vue +1 -1
  45. data/app/frontend/editor/components/kit/rich-text-input/link-buttons.vue +1 -1
  46. data/app/frontend/editor/components/kit/select-input.vue +4 -2
  47. data/app/frontend/editor/components/link-picker/actions.vue +1 -1
  48. data/app/frontend/editor/components/link-picker/index.vue +1 -1
  49. data/app/frontend/editor/components/link-picker/page.vue +2 -0
  50. data/app/frontend/editor/components/page/form/main.vue +1 -1
  51. data/app/frontend/editor/components/page/list/actions-button.vue +159 -0
  52. data/app/frontend/editor/components/page/list/index.vue +15 -24
  53. data/app/frontend/editor/components/page/list/list-item.vue +10 -61
  54. data/app/frontend/editor/components/section-highlighter/index.vue +47 -46
  55. data/app/frontend/editor/components/section-list/add-button.vue +39 -0
  56. data/app/frontend/editor/components/section-list/index.vue +30 -15
  57. data/app/frontend/editor/components/section-list/list-item.vue +23 -8
  58. data/app/frontend/editor/components/section-pane/block-list/index.vue +1 -0
  59. data/app/frontend/editor/components/section-pane/block-list/list-item.vue +9 -6
  60. data/app/frontend/editor/components/section-pane/block-tree/new-nested-block-button.vue +2 -2
  61. data/app/frontend/editor/components/section-pane/block-tree/tree-node.vue +2 -2
  62. data/app/frontend/editor/components/sidebar-nav/index.vue +102 -0
  63. data/app/frontend/editor/components/sidebar-nav/link.vue +60 -0
  64. data/app/frontend/editor/components/theme-section-list/category.vue +47 -0
  65. data/app/frontend/editor/components/theme-section-list/index.vue +7 -31
  66. data/app/frontend/editor/components/theme-section-list/list-item.vue +10 -3
  67. data/app/frontend/editor/design/components/modal.scss +1 -1
  68. data/app/frontend/editor/design/components/tooltip.scss +6 -0
  69. data/app/frontend/editor/design/transitions.scss +1 -1
  70. data/app/frontend/editor/layouts/app.vue +1 -1
  71. data/app/frontend/editor/layouts/default.vue +2 -2
  72. data/app/frontend/editor/layouts/slide-pane.vue +1 -1
  73. data/app/frontend/editor/locales/editor.ar.json +265 -0
  74. data/app/frontend/editor/locales/editor.en.json +23 -15
  75. data/app/frontend/editor/locales/editor.es.json +18 -10
  76. data/app/frontend/editor/locales/editor.fr.json +28 -13
  77. data/app/frontend/editor/locales/editor.pt-BR.json +18 -10
  78. data/app/frontend/editor/locales/index.js +3 -0
  79. data/app/frontend/editor/mixins/error-modal.js +7 -2
  80. data/app/frontend/editor/mixins/preview-transformation.js +0 -1
  81. data/app/frontend/editor/plugins/i18n.js +2 -17
  82. data/app/frontend/editor/services/__tests__/section.spec.js +6 -0
  83. data/app/frontend/editor/services/collection-item.js +8 -0
  84. data/app/frontend/editor/services/page.js +1 -1
  85. data/app/frontend/editor/services/section.js +30 -0
  86. data/app/frontend/editor/services/site.js +4 -4
  87. data/app/frontend/editor/services/theme.js +1 -0
  88. data/app/frontend/editor/spec/__mocks__/page.js +3 -1
  89. data/app/frontend/editor/spec/__mocks__/services.js +1 -0
  90. data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
  91. data/app/frontend/editor/store/actions/index.js +1 -0
  92. data/app/frontend/editor/store/actions/site.js +8 -4
  93. data/app/frontend/editor/store/getters.js +11 -0
  94. data/app/frontend/editor/views/page-preview.vue +2 -1
  95. data/app/frontend/images/favicon.svg +11 -0
  96. data/app/frontend/images/logo.svg +14 -0
  97. data/app/frontend/live-preview-client/iframe-decorator.js +17 -15
  98. data/app/frontend/live-preview-client/rails.js +1 -1
  99. data/app/helpers/maglev/application_helper.rb +7 -2
  100. data/app/helpers/maglev/editor_helper.rb +12 -5
  101. data/app/models/concerns/maglev/sections_concern.rb +4 -0
  102. data/app/models/maglev/asset.rb +13 -14
  103. data/app/models/maglev/page/path_concern.rb +23 -8
  104. data/app/models/maglev/page.rb +17 -18
  105. data/app/models/maglev/page_path.rb +18 -18
  106. data/app/models/maglev/section/setting.rb +1 -1
  107. data/app/models/maglev/section.rb +18 -9
  108. data/app/models/maglev/setting_types/link.rb +1 -1
  109. data/app/models/maglev/site.rb +13 -24
  110. data/app/services/maglev/app_container.rb +2 -2
  111. data/app/services/maglev/extract_locale.rb +3 -2
  112. data/app/services/maglev/fetch_page.rb +2 -0
  113. data/app/services/maglev/fetch_theme_layout.rb +2 -0
  114. data/app/services/maglev/get_page_fullpath.rb +1 -2
  115. data/app/services/maglev/remove_section_type.rb +50 -0
  116. data/app/services/maglev/rename_section_type.rb +57 -0
  117. data/app/services/maglev/search_pages.rb +2 -1
  118. data/app/views/layouts/maglev/admin/application.html.erb +1 -1
  119. data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
  120. data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
  121. data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
  122. data/app/views/maglev/editor/show.html.erb +6 -5
  123. data/config/locales/activerecord.ar.yml +13 -0
  124. data/config/locales/activerecord.en.yml +8 -2
  125. data/config/locales/activerecord.es.yml +4 -0
  126. data/config/locales/activerecord.fr.yml +6 -2
  127. data/config/locales/activerecord.pt-BR.yml +13 -0
  128. data/config/routes.rb +3 -4
  129. data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
  130. data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
  131. data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
  132. data/db/migrate/20211013210954_translate_section_content.rb +16 -2
  133. data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
  134. data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
  135. data/lib/commands/maglev/change_site_locales_command.rb +61 -0
  136. data/lib/commands/maglev/create_site_command.rb +28 -0
  137. data/lib/commands/maglev/sections/remove_command.rb +48 -0
  138. data/lib/commands/maglev/sections/rename_command.rb +49 -0
  139. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
  140. data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
  141. data/lib/maglev/errors.rb +1 -0
  142. data/lib/maglev/preview_constraint.rb +5 -1
  143. data/lib/maglev/theme_filesystem_loader.rb +7 -0
  144. data/lib/maglev/version.rb +1 -1
  145. data/lib/maglev.rb +13 -3
  146. data/lib/tasks/maglev/icons.rake +123 -0
  147. data/lib/tasks/maglev/vite.rake +62 -0
  148. data/lib/tasks/maglev_tasks.rake +9 -107
  149. data/tailwind.config.js +1 -0
  150. metadata +48 -33
  151. data/app/controllers/maglev/assets_controller.rb +0 -10
  152. data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
  153. data/app/frontend/editor/components/sidebar-nav.vue +0 -125
  154. data/app/frontend/editor/plugins/maglev_dummy.js +0 -2
  155. data/app/frontend/images/favicon.png +0 -0
  156. data/app/frontend/images/logo.png +0 -0
@@ -1,15 +1,25 @@
1
1
  class SwitchToLocalizedPageFields < ActiveRecord::Migration[6.1]
2
2
  def up
3
3
  remove_columns :maglev_pages, :title, :seo_title, :meta_description
4
- add_column :maglev_pages, :title_translations, :jsonb, default: {}
5
- add_column :maglev_pages, :seo_title_translations, :jsonb, default: {}
6
- add_column :maglev_pages, :meta_description_translations, :jsonb, default: {}
4
+
5
+ change_table :maglev_pages do |t|
6
+ if t.respond_to? :jsonb
7
+ t.jsonb :title_translations, default: {}
8
+ t.jsonb :seo_title_translations, default: {}
9
+ t.jsonb :meta_description_translations, default: {}
10
+ else
11
+ t.json :title_translations, default: {}
12
+ t.json :seo_title_translations, default: {}
13
+ t.json :meta_description_translations, default: {}
14
+ end
15
+ end
7
16
  end
8
17
 
9
18
  def down
10
19
  add_column :maglev_pages, :title, :string
11
20
  add_column :maglev_pages, :seo_title, :string
12
21
  add_column :maglev_pages, :meta_description, :string
22
+
13
23
  remove_column :maglev_pages, :title_translations
14
24
  remove_column :maglev_pages, :seo_title_translations
15
25
  remove_column :maglev_pages, :meta_description_translations
@@ -1,5 +1,11 @@
1
1
  class AddLocalesToSites < ActiveRecord::Migration[6.0]
2
2
  def change
3
- add_column :maglev_sites, :locales, :jsonb, default: []
3
+ change_table :maglev_sites do |t|
4
+ if t.respond_to? :jsonb
5
+ t.jsonb :locales, default: []
6
+ else
7
+ t.json :locales, default: []
8
+ end
9
+ end
4
10
  end
5
11
  end
@@ -1,8 +1,22 @@
1
1
  class TranslateSectionContent < ActiveRecord::Migration[6.0]
2
2
  def change
3
3
  remove_column :maglev_sites, :sections, :jsonb, default: []
4
- add_column :maglev_sites, :sections_translations, :jsonb, default: {}
5
4
  remove_column :maglev_pages, :sections, :jsonb, default: []
6
- add_column :maglev_pages, :sections_translations, :jsonb, default: {}
5
+
6
+ change_table :maglev_sites do |t|
7
+ if t.respond_to? :jsonb
8
+ t.jsonb :sections_translations, default: {}
9
+ else
10
+ t.json :sections_translations, default: {}
11
+ end
12
+ end
13
+
14
+ change_table :maglev_pages do |t|
15
+ if t.respond_to? :jsonb
16
+ t.jsonb :sections_translations, default: {}
17
+ else
18
+ t.json :sections_translations, default: {}
19
+ end
20
+ end
7
21
  end
8
22
  end
@@ -1,9 +1,15 @@
1
1
  class AddOpenGraphTagsToPages < ActiveRecord::Migration[6.0]
2
2
  def change
3
3
  change_table :maglev_pages do |t|
4
- t.jsonb :og_title_translations, default: {}
5
- t.jsonb :og_description_translations, default: {}
6
- t.jsonb :og_image_url_translations, default: {}
4
+ if t.respond_to? :jsonb
5
+ t.jsonb :og_title_translations, default: {}
6
+ t.jsonb :og_description_translations, default: {}
7
+ t.jsonb :og_image_url_translations, default: {}
8
+ else
9
+ t.json :og_title_translations, default: {}
10
+ t.json :og_description_translations, default: {}
11
+ t.json :og_image_url_translations, default: {}
12
+ end
7
13
  end
8
14
  end
9
15
  end
@@ -1,7 +1,11 @@
1
1
  class AddStyleToSites < ActiveRecord::Migration[6.0]
2
2
  def change
3
3
  change_table :maglev_sites do |t|
4
- t.jsonb :style, default: []
4
+ if t.respond_to? :jsonb
5
+ t.jsonb :style, default: []
6
+ else
7
+ t.json :style, default: []
8
+ end
5
9
  end
6
10
  end
7
11
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Maglev
6
+ class ChangeSiteLocalesCommand < Rails::Command::Base
7
+ desc 'change_site_locales [LOCALES]', 'Change site locales (format: label:prefix)'
8
+
9
+ def self.banner
10
+ 'bin/rails maglev:change_site_locales [label:prefix label2:prefix2 ...]'
11
+ end
12
+
13
+ def perform(*locale_args)
14
+ require File.expand_path('config/environment', Rails.root)
15
+
16
+ site = fetch_site
17
+ return if site.blank?
18
+
19
+ locales = build_locales(locale_args)
20
+ return if locales.empty?
21
+
22
+ update_site_locales(site, locales)
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_site
28
+ Maglev::Site.first.tap do |site|
29
+ say("[Error] You don't seem to have an existing site. 🤔", :red) unless site
30
+ end
31
+ end
32
+
33
+ def build_locales(locale_args)
34
+ build_custom_locales(locale_args).tap do |locales|
35
+ validate_locales(locales)
36
+ end
37
+ end
38
+
39
+ def build_custom_locales(locale_args)
40
+ (locale_args || []).map do |arg|
41
+ label, prefix = arg.split(':')
42
+ Maglev::Site::Locale.new(label: label, prefix: prefix)
43
+ end
44
+ end
45
+
46
+ def validate_locales(locales)
47
+ return true if !locales.empty? && locales.all?(&:valid?)
48
+
49
+ say("[Error] make sure your locales follow the 'label:prefix' pattern. 🤓", :red)
50
+ false
51
+ end
52
+
53
+ def update_site_locales(site, locales)
54
+ Maglev::ChangeSiteLocales.call(
55
+ site: site,
56
+ locales: locales
57
+ )
58
+ say('Success! 🎉🎉🎉', :green)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Maglev
6
+ class CreateSiteCommand < Rails::Command::Base
7
+ desc 'create_site', 'Create your site'
8
+
9
+ def self.banner
10
+ 'bin/rails maglev:create_site'
11
+ end
12
+
13
+ def perform
14
+ require File.expand_path('config/environment')
15
+
16
+ if Maglev::Site.exists?
17
+ say '🤔 You already have a site. 🤔', :yellow
18
+ return
19
+ end
20
+
21
+ Maglev::GenerateSite.call(
22
+ theme: Maglev.local_themes.first
23
+ )
24
+
25
+ say '🎉 Your site has been created with success!'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Maglev
6
+ module Sections
7
+ class RemoveCommand < Rails::Command::Base
8
+ desc 'remove TYPE', 'Remove a section type across the site and its pages'
9
+
10
+ def self.banner
11
+ 'bin/rails maglev:sections:remove TYPE'
12
+ end
13
+
14
+ def perform(type)
15
+ require File.expand_path('config/environment', Rails.root)
16
+
17
+ site = fetch_site
18
+
19
+ return if site.blank?
20
+
21
+ removed_count = Maglev::RemoveSectionType.call(
22
+ site: site,
23
+ type: type
24
+ )
25
+
26
+ display_final_message(removed_count, type)
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_site
32
+ Maglev::Site.first.tap do |site|
33
+ say("[Error] You don't seem to have an existing site. 🤔", :red) unless site
34
+ end
35
+ end
36
+
37
+ def display_final_message(removed_count, type)
38
+ if removed_count.zero?
39
+ say "No section of type '#{type}' found 🤔", :yellow
40
+ return
41
+ end
42
+
43
+ say "Successfully removed #{removed_count} #{'section'.pluralize(removed_count)} of type '#{type}' 🎉",
44
+ :green
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module Sections
5
+ class RenameCommand < Rails::Command::Base
6
+ desc 'rename OLD_TYPE NEW_TYPE', 'Rename a section type across the site and its pages'
7
+
8
+ def self.banner
9
+ 'bin/rails maglev:sections:rename OLD_TYPE NEW_TYPE'
10
+ end
11
+
12
+ def perform(old_type, new_type)
13
+ require File.expand_path('config/environment', Rails.root)
14
+
15
+ site = fetch_site
16
+ theme = fetch_theme
17
+
18
+ return if site.blank? || theme.blank?
19
+
20
+ rename_sections(site, theme, old_type, new_type)
21
+
22
+ say "Successfully renamed all '#{old_type}' sections to '#{new_type}' 🎉", :green
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_site
28
+ Maglev::Site.first.tap do |site|
29
+ say("[Error] You don't seem to have an existing site. 🤔", :red) unless site
30
+ end
31
+ end
32
+
33
+ def fetch_theme
34
+ Maglev.local_themes&.first.tap do |theme|
35
+ say('[Error] No theme found. 🤔', :red) unless theme
36
+ end
37
+ end
38
+
39
+ def rename_sections(site, theme, old_type, new_type)
40
+ Maglev::RenameSectionType.call(
41
+ site: site,
42
+ theme: theme,
43
+ old_type: old_type,
44
+ new_type: new_type
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,6 +3,7 @@
3
3
  Maglev.configure do |config|
4
4
  # Title of the Editor window
5
5
  # config.title = 'Maglev - Editor'
6
+ # config.title = ->(site) { "#{site.name} 👋" }
6
7
 
7
8
  # Logo of the Editor (top left corner).
8
9
  # Put your custom logo in the app/assets/images folder of your Rails application.
@@ -50,7 +51,9 @@ Maglev.configure do |config|
50
51
  config.admin_username = Rails.env.production? ? ENV.fetch('MAGLEV_ADMIN_USERNAME') : nil
51
52
  config.admin_password = Rails.env.production? ? ENV.fetch('MAGLEV_ADMIN_PASSWORD') : nil
52
53
 
53
- # Uploader engine (:active_storage is only supported for now)
54
+ # Uploader engine (:active_storage is the default one)
55
+ # Checkout https://github.com/MarsBased/maglevcms-shrine for an example of a custom uploader
56
+ # config.uploader = 'Maglev::Shrine::Uploader'
54
57
  config.uploader = :active_storage
55
58
 
56
59
  # Collections mapping (https://docs.maglev.dev/guides/setup-collections)
@@ -4,7 +4,7 @@
4
4
  <title><%%= maglev_page.seo_title.presence || maglev_site.name %></title>
5
5
  <meta content="width=device-width, initial-scale=1" name="viewport">
6
6
 
7
- <meta name="description" content="<%%= maglev_page.meta_description %>" />
7
+ <meta name="description" content="<%%= maglev_page.meta_description %>" />
8
8
  <meta property="og:title" content="<%%= maglev_page.og_title %>" />
9
9
  <meta property="og:description" content="<%%= maglev_page.og_description %>" />
10
10
  <meta property="og:image" content="<%%= maglev_page.og_image_url %>" />
@@ -13,7 +13,13 @@
13
13
 
14
14
  <%%= maglev_live_preview_client_javascript_tag %>
15
15
 
16
- <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
16
+ <%%=
17
+ javascript_include_tag(
18
+ "https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio",
19
+ nonce: true,
20
+ id: "tailwind-cdn"
21
+ )
22
+ %>
17
23
  </head>
18
24
  <body class="bg-white">
19
25
  <main data-maglev-dropzone>
data/lib/maglev/errors.rb CHANGED
@@ -4,6 +4,7 @@ module Maglev
4
4
  module Errors
5
5
  class NotAuthorized < StandardError; end
6
6
  class UnknownSection < StandardError; end
7
+ class DuplicateSectionDefinition < StandardError; end
7
8
 
8
9
  class UnknownSetting < StandardError
9
10
  def initialize(section_id, block_id, setting_id)
@@ -13,7 +13,7 @@ module Maglev
13
13
  end
14
14
 
15
15
  def matches?(request)
16
- (accepted_format?(request) || crawler?(request)) && match_host?(request)
16
+ !websocket?(request) && (accepted_format?(request) || crawler?(request)) && match_host?(request)
17
17
  end
18
18
 
19
19
  protected
@@ -32,6 +32,10 @@ module Maglev
32
32
  request.format.symbol.nil? && CRAWLER_USER_AGENTS.match?(request.user_agent)
33
33
  end
34
34
 
35
+ def websocket?(request)
36
+ request.headers['HTTP_UPGRADE'] == 'websocket'
37
+ end
38
+
35
39
  def match_host?(request)
36
40
  !preview_host || preview_host == request.host
37
41
  end
@@ -11,6 +11,7 @@ module Maglev
11
11
  def call(path)
12
12
  theme = add(YAML.safe_load(File.read(path.join('theme.yml')), aliases: true))
13
13
  sections = load_sections(theme, Pathname.new(path).join('sections/**/*.yml'))
14
+ detect_duplicate_sections(sections)
14
15
  theme.sections = Maglev::Section::Store.new(sections)
15
16
  theme
16
17
  rescue Errno::ENOENT
@@ -52,6 +53,12 @@ module Maglev
52
53
  File.exist?(path) ? File.mtime(path).to_i : nil
53
54
  end
54
55
 
56
+ def detect_duplicate_sections(sections)
57
+ sections.group_by(&:id).each do |id, list|
58
+ raise Maglev::Errors::DuplicateSectionDefinition, "Duplicate section definition: #{id}" if list.size > 1
59
+ end
60
+ end
61
+
55
62
  def log_missing_theme_file(path)
56
63
  # don't log the error if the ruby code is not executed inside
57
64
  # the Rails console or when the Rails server is running
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maglev
4
- VERSION = '1.8.0'
4
+ VERSION = '2.0.0.beta1'
5
5
  end
data/lib/maglev.rb CHANGED
@@ -26,7 +26,7 @@ module Maglev
26
26
  c.favicon = nil
27
27
  c.logo = nil
28
28
  c.primary_color = '#040712'
29
- c.uploader = 'active_storage'
29
+ c.uploader = :active_storage
30
30
  c.site_publishable = false
31
31
  c.preview_host = nil
32
32
  c.asset_host = Rails.application.config.action_controller.asset_host
@@ -47,12 +47,22 @@ module Maglev
47
47
  config.tap do
48
48
  yield(config)
49
49
  config.reserved_paths = Maglev::ReservedPaths.new(config.reserved_paths)
50
+ require_relative 'maglev/active_storage' if config.uploader == :active_storage
50
51
  end
51
52
  end
52
53
 
53
54
  def uploader
54
- require_relative "maglev/#{config.uploader}"
55
- const_get("::Maglev::#{config.uploader.to_s.classify}")
55
+ case config.uploader
56
+ when Symbol then const_get("::Maglev::#{config.uploader.to_s.classify}")
57
+ when String then const_get(config.uploader)
58
+ when Class then config.uploader
59
+ else
60
+ raise "Invalid or not set uploader: #{config.uploader}"
61
+ end
62
+ end
63
+
64
+ def uploader_proxy_controller_name
65
+ config.uploader == :active_storage ? 'assets/active_storage_proxy' : 'assets/proxy'
56
66
  end
57
67
 
58
68
  def services(overrides = {})
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :maglev do
4
+ namespace :icons do
5
+ desc 'Generate FontAwesome icon configuration for Maglev'
6
+ task fontawesome: :environment do
7
+ require 'net/http'
8
+ require 'uri'
9
+
10
+ puts 'Fetching FontAwesome icons from CDN...'
11
+
12
+ # Fetch the all.css file from FontAwesome CDN
13
+ uri = URI('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.css')
14
+ response = Net::HTTP.get(uri)
15
+
16
+ # Extract icon classes using regex
17
+ icon_classes = []
18
+
19
+ # Match .fa-* classes that define content
20
+ response.scan(/\.(fa-[^:]+):before/).each do |match|
21
+ class_name = match[0]
22
+ next if class_name.include?('{') || class_name.include?('}') # Skip utility classes
23
+
24
+ # Add each style variant
25
+ %w[fa-solid fa-regular fa-light fa-thin fa-brands].each do |style|
26
+ # Extract the icon name without fa- prefix
27
+ icon_name = class_name.sub('fa-', '')
28
+ icon_classes << "#{style} fa-#{icon_name}"
29
+ end
30
+ end
31
+
32
+ # Remove duplicates and sort
33
+ icon_classes.uniq!
34
+ icon_classes.sort!
35
+
36
+ puts "Found #{icon_classes.size} icon variations"
37
+
38
+ # Generate the icons YAML content in a single line
39
+ icons_yaml = ['', '', '# FontAwesome icons', "icons: [#{icon_classes.map { |i| %("#{i}") }.join(', ')}]"]
40
+ icons_yaml = icons_yaml.join("\n")
41
+
42
+ # Append to theme.yml
43
+ theme_file = Rails.root.join('app/theme/theme.yml')
44
+
45
+ if File.exist?(theme_file)
46
+ # Read the current content
47
+ content = File.read(theme_file)
48
+
49
+ # Check if icons are already defined
50
+ if content.match?(/^icons:/)
51
+ puts "Warning: 'icons' section already exists in #{theme_file}"
52
+ puts "Please remove the existing 'icons' section first"
53
+ puts 'You can add the following content to your theme.yml file:'
54
+ puts icons_yaml
55
+ else
56
+ # Append the icons section
57
+ File.write(theme_file, [content.rstrip, icons_yaml, "\n"].join)
58
+ puts "Updated #{theme_file} with FontAwesome icons"
59
+ end
60
+ else
61
+ puts "Warning: #{theme_file} not found. Please create a theme first."
62
+ puts 'You can add the following content to your theme.yml file:'
63
+ puts icons_yaml
64
+ end
65
+ end
66
+
67
+ desc 'Generate Remixicon icon configuration for Maglev'
68
+ task remixicon: :environment do
69
+ require 'net/http'
70
+ require 'uri'
71
+
72
+ puts 'Fetching Remixicon icons from CDN...'
73
+
74
+ # Fetch the remixicon.css file from CDN
75
+ uri = URI('https://cdn.jsdelivr.net/npm/remixicon@3.7.0/fonts/remixicon.css')
76
+ response = Net::HTTP.get(uri)
77
+
78
+ # Extract icon classes using regex
79
+ icon_classes = []
80
+
81
+ # Match .ri-* classes that define content
82
+ response.scan(/\.(ri-[^:]+):before/).each do |match|
83
+ class_name = match[0]
84
+ next if class_name.include?('{') || class_name.include?('}') # Skip utility classes
85
+
86
+ icon_classes << class_name
87
+ end
88
+
89
+ icon_classes.uniq!
90
+ icon_classes.sort!
91
+
92
+ puts "Found #{icon_classes.size} Remixicon icons"
93
+
94
+ # Generate the icons YAML content in a single line
95
+ icons_yaml = ['', '', '# Remixicon icons', "icons: [#{icon_classes.map { |i| %("#{i}") }.join(', ')}]"]
96
+ icons_yaml = icons_yaml.join("\n")
97
+
98
+ # Append to theme.yml
99
+ theme_file = Rails.root.join('app/theme/theme.yml')
100
+
101
+ if File.exist?(theme_file)
102
+ # Read the current content
103
+ content = File.read(theme_file)
104
+
105
+ # Check if icons are already defined
106
+ if content.match?(/^icons:/)
107
+ puts "Warning: 'icons' section already exists in #{theme_file}"
108
+ puts "Please remove the existing 'icons' section first"
109
+ puts 'You can add the following content to your theme.yml file:'
110
+ puts icons_yaml
111
+ else
112
+ # Append the icons section
113
+ File.write(theme_file, [content.rstrip, icons_yaml, "\n"].join)
114
+ puts "Updated #{theme_file} with Remixicon icons"
115
+ end
116
+ else
117
+ puts "Warning: #{theme_file} not found. Please create a theme first."
118
+ puts 'You can add the following content to your theme.yml file:'
119
+ puts icons_yaml
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :maglev do
4
+ namespace :vite do
5
+ desc 'Bundle frontend entrypoints using ViteRuby'
6
+ task build: :'vite:verify_install' do
7
+ within_engine_folder do
8
+ Maglev::Engine.vite_ruby.commands.build_from_task
9
+ end
10
+ end
11
+
12
+ desc 'Bundle entrypoints using Vite Ruby (SSR only if enabled)'
13
+ task build_all: :'vite:verify_install' do
14
+ within_engine_folder do
15
+ Maglev::Engine.vite_ruby.commands.build_from_task
16
+ Maglev::Engine.vite_ruby.commands.build_from_task('--ssr') if ViteRuby.config.ssr_build_enabled
17
+ end
18
+ end
19
+
20
+ desc 'Ensure build dependencies like Vite are installed before bundling'
21
+ task install_dependencies: :environment do
22
+ within_engine_folder do
23
+ install_dev_dependencies = ENV['VITE_RUBY_SKIP_INSTALL_DEV_DEPENDENCIES'] == 'true'
24
+ # rubocop:disable Style/StringHashKeys
25
+ install_env_args = install_dev_dependencies ? {} : { 'NODE_ENV' => 'development' }
26
+ # rubocop:enable Style/StringHashKeys
27
+ cmd = Maglev::Engine.vite_ruby.commands.legacy_npm_version? ? 'npx ci --yes' : 'npx --yes ci'
28
+ result = system(install_env_args, cmd)
29
+ # Fallback to `yarn` if `npx` is not available.
30
+ system(install_env_args, 'yarn install --frozen-lockfile') if result.nil?
31
+ end
32
+ end
33
+
34
+ desc 'Verify if ViteRuby is properly installed in the app'
35
+ task verify_install: :environment do
36
+ within_engine_folder do
37
+ Maglev::Engine.vite_ruby.commands.verify_install
38
+ end
39
+ end
40
+
41
+ desc 'Remove old bundles created by ViteRuby'
42
+ task :clean, %i[keep age] => :'vite:verify_install' do |_, args|
43
+ within_engine_folder do
44
+ Maglev::Engine.vite_ruby.commands.clean_from_task(args)
45
+ end
46
+ end
47
+
48
+ desc 'Remove the build output directory for ViteRuby'
49
+ task clobber: :'vite:verify_install' do
50
+ within_engine_folder do
51
+ Maglev::Engine.vite_ruby.commands.clobber
52
+ end
53
+ end
54
+
55
+ desc "Provide information on ViteRuby's environment"
56
+ task info: :environment do
57
+ within_engine_folder do
58
+ Maglev::Engine.vite_ruby.commands.print_info
59
+ end
60
+ end
61
+ end
62
+ end