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.
- checksums.yaml +4 -4
- data/.yarn/install-state.gz +0 -0
- data/README.md +4 -4
- data/Rakefile +2 -1
- data/app/components/maglev/block_component.rb +1 -1
- data/app/components/maglev/content/base.rb +1 -1
- data/app/components/maglev/content/link.rb +4 -0
- data/app/components/maglev/section_component.rb +2 -2
- data/app/controllers/concerns/maglev/fetchers_concern.rb +3 -1
- data/app/controllers/concerns/maglev/resource_id_concern.rb +10 -0
- data/app/controllers/concerns/maglev/standalone_sections_concern.rb +6 -0
- data/app/controllers/concerns/maglev/{ui_locale_concern.rb → user_interface_locale_concern.rb} +1 -1
- data/app/controllers/maglev/api/collection_items_controller.rb +7 -0
- data/app/controllers/maglev/api/page_clones_controller.rb +1 -2
- data/app/controllers/maglev/api_controller.rb +1 -1
- data/app/controllers/maglev/application_controller.rb +1 -5
- data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +17 -0
- data/app/controllers/maglev/assets/proxy_controller.rb +21 -0
- data/app/controllers/maglev/editor_controller.rb +1 -1
- data/app/controllers/maglev/page_preview_controller.rb +3 -3
- data/app/frontend/admin/controllers/screenshot_controller.js +29 -1
- data/app/frontend/editor/assets/remixicons/clipboard-line.svg +1 -0
- data/app/frontend/editor/assets/remixicons/ri-draggable.svg +1 -0
- data/app/frontend/editor/assets/remixicons/ri-folders-line.svg +1 -0
- data/app/frontend/editor/assets/remixicons/ri-more-fill.svg +1 -0
- data/app/frontend/editor/assets/remixicons/ri-shadow-line.svg +1 -0
- data/app/frontend/editor/components/errors/forbidden.vue +24 -0
- data/app/frontend/editor/components/errors/stale-record.vue +2 -2
- data/app/frontend/editor/components/header-nav/device-toggler.vue +6 -3
- data/app/frontend/editor/components/header-nav/index.vue +13 -42
- data/app/frontend/editor/components/header-nav/locale-toggler/index.vue +1 -1
- data/app/frontend/editor/components/header-nav/page-info.vue +41 -0
- data/app/frontend/editor/components/header-nav/preview-button.vue +6 -1
- data/app/frontend/editor/components/header-nav/separator.vue +1 -1
- data/app/frontend/editor/components/image-library/index.vue +26 -29
- data/app/frontend/editor/components/kit/collection-item-input.vue +9 -0
- data/app/frontend/editor/components/kit/copy-paste-button.vue +26 -0
- data/app/frontend/editor/components/kit/dropdown.vue +5 -0
- data/app/frontend/editor/components/kit/icon-button.vue +24 -0
- data/app/frontend/editor/components/kit/icon.vue +2 -3
- data/app/frontend/editor/components/kit/index.js +4 -2
- data/app/frontend/editor/components/kit/link-input.vue +1 -1
- data/app/frontend/editor/components/kit/modal.vue +1 -1
- data/app/frontend/editor/components/kit/page-icon.vue +1 -1
- data/app/frontend/editor/components/kit/rich-text-input/link-buttons.vue +1 -1
- data/app/frontend/editor/components/kit/select-input.vue +4 -2
- data/app/frontend/editor/components/link-picker/actions.vue +1 -1
- data/app/frontend/editor/components/link-picker/index.vue +1 -1
- data/app/frontend/editor/components/link-picker/page.vue +2 -0
- data/app/frontend/editor/components/page/form/main.vue +1 -1
- data/app/frontend/editor/components/page/list/actions-button.vue +159 -0
- data/app/frontend/editor/components/page/list/index.vue +15 -24
- data/app/frontend/editor/components/page/list/list-item.vue +10 -61
- data/app/frontend/editor/components/section-highlighter/index.vue +47 -46
- data/app/frontend/editor/components/section-list/add-button.vue +39 -0
- data/app/frontend/editor/components/section-list/index.vue +30 -15
- data/app/frontend/editor/components/section-list/list-item.vue +23 -8
- data/app/frontend/editor/components/section-pane/block-list/index.vue +1 -0
- data/app/frontend/editor/components/section-pane/block-list/list-item.vue +9 -6
- data/app/frontend/editor/components/section-pane/block-tree/new-nested-block-button.vue +2 -2
- data/app/frontend/editor/components/section-pane/block-tree/tree-node.vue +2 -2
- data/app/frontend/editor/components/sidebar-nav/index.vue +102 -0
- data/app/frontend/editor/components/sidebar-nav/link.vue +60 -0
- data/app/frontend/editor/components/theme-section-list/category.vue +47 -0
- data/app/frontend/editor/components/theme-section-list/index.vue +7 -31
- data/app/frontend/editor/components/theme-section-list/list-item.vue +10 -3
- data/app/frontend/editor/design/components/modal.scss +1 -1
- data/app/frontend/editor/design/components/tooltip.scss +6 -0
- data/app/frontend/editor/design/transitions.scss +1 -1
- data/app/frontend/editor/layouts/app.vue +1 -1
- data/app/frontend/editor/layouts/default.vue +2 -2
- data/app/frontend/editor/layouts/slide-pane.vue +1 -1
- data/app/frontend/editor/locales/editor.ar.json +265 -0
- data/app/frontend/editor/locales/editor.en.json +23 -15
- data/app/frontend/editor/locales/editor.es.json +18 -10
- data/app/frontend/editor/locales/editor.fr.json +28 -13
- data/app/frontend/editor/locales/editor.pt-BR.json +18 -10
- data/app/frontend/editor/locales/index.js +3 -0
- data/app/frontend/editor/mixins/error-modal.js +7 -2
- data/app/frontend/editor/mixins/preview-transformation.js +0 -1
- data/app/frontend/editor/plugins/i18n.js +2 -17
- data/app/frontend/editor/services/__tests__/section.spec.js +6 -0
- data/app/frontend/editor/services/collection-item.js +8 -0
- data/app/frontend/editor/services/page.js +1 -1
- data/app/frontend/editor/services/section.js +30 -0
- data/app/frontend/editor/services/site.js +4 -4
- data/app/frontend/editor/services/theme.js +1 -0
- data/app/frontend/editor/spec/__mocks__/page.js +3 -1
- data/app/frontend/editor/spec/__mocks__/services.js +1 -0
- data/app/frontend/editor/store/__tests__/getters.spec.js +56 -1
- data/app/frontend/editor/store/actions/index.js +1 -0
- data/app/frontend/editor/store/actions/site.js +8 -4
- data/app/frontend/editor/store/getters.js +11 -0
- data/app/frontend/editor/views/page-preview.vue +2 -1
- data/app/frontend/images/favicon.svg +11 -0
- data/app/frontend/images/logo.svg +14 -0
- data/app/frontend/live-preview-client/iframe-decorator.js +17 -15
- data/app/frontend/live-preview-client/rails.js +1 -1
- data/app/helpers/maglev/application_helper.rb +7 -2
- data/app/helpers/maglev/editor_helper.rb +12 -5
- data/app/models/concerns/maglev/sections_concern.rb +4 -0
- data/app/models/maglev/asset.rb +13 -14
- data/app/models/maglev/page/path_concern.rb +23 -8
- data/app/models/maglev/page.rb +17 -18
- data/app/models/maglev/page_path.rb +18 -18
- data/app/models/maglev/section/setting.rb +1 -1
- data/app/models/maglev/section.rb +18 -9
- data/app/models/maglev/setting_types/link.rb +1 -1
- data/app/models/maglev/site.rb +13 -24
- data/app/services/maglev/app_container.rb +2 -2
- data/app/services/maglev/extract_locale.rb +3 -2
- data/app/services/maglev/fetch_page.rb +2 -0
- data/app/services/maglev/fetch_theme_layout.rb +2 -0
- data/app/services/maglev/get_page_fullpath.rb +1 -2
- data/app/services/maglev/remove_section_type.rb +50 -0
- data/app/services/maglev/rename_section_type.rb +57 -0
- data/app/services/maglev/search_pages.rb +2 -1
- data/app/views/layouts/maglev/admin/application.html.erb +1 -1
- data/app/views/maglev/admin/sections/previews/show.html.erb +16 -1
- data/app/views/maglev/api/collection_items/show.json.jbuilder +7 -0
- data/app/views/maglev/api/page_clones/create.json.jbuilder +3 -0
- data/app/views/maglev/editor/show.html.erb +6 -5
- data/config/locales/activerecord.ar.yml +13 -0
- data/config/locales/activerecord.en.yml +8 -2
- data/config/locales/activerecord.es.yml +4 -0
- data/config/locales/activerecord.fr.yml +6 -2
- data/config/locales/activerecord.pt-BR.yml +13 -0
- data/config/routes.rb +3 -4
- data/db/migrate/20200831101942_create_maglev_section_content.rb +10 -2
- data/db/migrate/20210819092740_switch_to_localized_page_fields.rb +13 -3
- data/db/migrate/20211008064437_add_locales_to_sites.rb +7 -1
- data/db/migrate/20211013210954_translate_section_content.rb +16 -2
- data/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb +9 -3
- data/db/migrate/20220612092235_add_style_to_sites.rb +5 -1
- data/lib/commands/maglev/change_site_locales_command.rb +61 -0
- data/lib/commands/maglev/create_site_command.rb +28 -0
- data/lib/commands/maglev/sections/remove_command.rb +48 -0
- data/lib/commands/maglev/sections/rename_command.rb +49 -0
- data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +4 -1
- data/lib/generators/maglev/templates/theme/app/views/theme/layout.html.erb.tt +8 -2
- data/lib/maglev/errors.rb +1 -0
- data/lib/maglev/preview_constraint.rb +5 -1
- data/lib/maglev/theme_filesystem_loader.rb +7 -0
- data/lib/maglev/version.rb +1 -1
- data/lib/maglev.rb +13 -3
- data/lib/tasks/maglev/icons.rake +123 -0
- data/lib/tasks/maglev/vite.rake +62 -0
- data/lib/tasks/maglev_tasks.rake +9 -107
- data/tailwind.config.js +1 -0
- metadata +48 -33
- data/app/controllers/maglev/assets_controller.rb +0 -10
- data/app/frontend/editor/components/kit/list-item-button.vue +0 -16
- data/app/frontend/editor/components/sidebar-nav.vue +0 -125
- data/app/frontend/editor/plugins/maglev_dummy.js +0 -2
- data/app/frontend/images/favicon.png +0 -0
- 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
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
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
|
-
|
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.
|
5
|
-
|
6
|
-
|
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
|
@@ -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
|
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
|
-
|
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
@@ -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
|
data/lib/maglev/version.rb
CHANGED
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 =
|
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
|
-
|
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
|