/app/views/theme/sections/#{section.id}.html.erb"
20 | end
21 |
22 | def section_screenshot_path(section)
23 | services.fetch_section_screenshot_path.call(section: section)
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/helpers/maglev/sitemap_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | module SitemapHelper
5 | def sitemap_url(host, page, locale = nil)
6 | path = maglev_services.get_page_fullpath.call(page: page, locale: locale)
7 |
8 | return path if path =~ %r{^https?://}
9 |
10 | [host, path].join
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/jobs/maglev/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class ApplicationJob < ActiveJob::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/mailers/maglev/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class ApplicationMailer < ActionMailer::Base
5 | default from: 'from@example.com'
6 | layout 'mailer'
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/models/maglev/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class ApplicationRecord < ActiveRecord::Base
5 | self.abstract_class = true
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | value
7 | end
8 | end
9 |
10 | # rubocop:enable Style/ClassAndModuleChildren
11 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/checkbox.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Checkbox < Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | ActiveModel::Type::Boolean.new.cast(value) || false
7 | end
8 | end
9 | # rubocop:enable Style/ClassAndModuleChildren
10 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/collection_item.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::CollectionItem < Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | if value.is_a?(String)
7 | { id: value }
8 | else
9 | value
10 | end
11 | end
12 | end
13 | # rubocop:enable Style/ClassAndModuleChildren
14 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/color.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Color < Maglev::SettingTypes::Base
5 | end
6 | # rubocop:enable Style/ClassAndModuleChildren
7 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/divider.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Divider < Maglev::SettingTypes::Base
5 | def cast_value(_value)
6 | nil
7 | end
8 | end
9 | # rubocop:enable Style/ClassAndModuleChildren
10 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/hint.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Hint < Maglev::SettingTypes::Base
5 | def cast_value(_value)
6 | nil
7 | end
8 | end
9 | # rubocop:enable Style/ClassAndModuleChildren
10 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/icon.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Icon < Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | value
7 | end
8 | end
9 | # rubocop:enable Style/ClassAndModuleChildren
10 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/image.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Image < Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | if value.is_a?(String)
7 | { url: value }
8 | else
9 | value || {}
10 | end
11 | end
12 | end
13 | # rubocop:enable Style/ClassAndModuleChildren
14 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/link.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Link < Maglev::SettingTypes::Base
5 | def cast_value(value)
6 | if value.is_a?(String)
7 | { text: 'Link', link_type: 'url', href: value }
8 | elsif value
9 | { text: 'Link', link_type: 'url', href: '#' }.merge(value.symbolize_keys)
10 | end
11 | end
12 | end
13 | # rubocop:enable Style/ClassAndModuleChildren
14 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/select.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Select < Maglev::SettingTypes::Base
5 | end
6 | # rubocop:enable Style/ClassAndModuleChildren
7 |
--------------------------------------------------------------------------------
/app/models/maglev/setting_types/text.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::SettingTypes::Text < Maglev::SettingTypes::Base
5 | end
6 | # rubocop:enable Style/ClassAndModuleChildren
7 |
--------------------------------------------------------------------------------
/app/models/maglev/site/locale.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::Site::Locale
5 | ## concerns ##
6 | include ActiveModel::Model
7 |
8 | ## attributes ##
9 | attr_accessor :label, :prefix
10 |
11 | ## validations ##
12 | validates :label, :prefix, 'maglev/presence': true
13 |
14 | ## methods ##
15 | def as_json(_options = nil)
16 | { label: label, prefix: prefix }
17 | end
18 | end
19 | # rubocop:enable Style/ClassAndModuleChildren
20 |
--------------------------------------------------------------------------------
/app/models/maglev/theme/section_category.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Style/ClassAndModuleChildren
4 | class Maglev::Theme::SectionCategory
5 | ## concerns ##
6 | include ActiveModel::Model
7 |
8 | ## attributes ##
9 | attr_accessor :name, :id
10 |
11 | ## class methods ##
12 |
13 | def self.build(hash)
14 | attributes = hash.slice('name', 'id')
15 | attributes['id'] ||= attributes['name'].parameterize(separator: '_')
16 | new(attributes)
17 | end
18 |
19 | def self.build_many(list)
20 | (list || []).map { |hash| build(hash) }
21 | end
22 | end
23 | # rubocop:enable Style/ClassAndModuleChildren
24 |
--------------------------------------------------------------------------------
/app/services/maglev/extract_locale.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Extract the locale from the current HTTP request
5 | # and set the Translatable.current_locale accordingly
6 | class ExtractLocale
7 | include Injectable
8 |
9 | argument :params
10 | argument :locales
11 |
12 | def call
13 | locale, path = extract_locale
14 |
15 | Maglev::I18n.current_locale = locale
16 |
17 | params[:path] = path
18 |
19 | [path, locale]
20 | end
21 |
22 | protected
23 |
24 | def extract_locale
25 | path = params[:path] || 'index'
26 | segments = path.split('/')
27 |
28 | return [default_locale, path] unless locales.include?(segments[0]&.to_sym)
29 |
30 | [segments.shift, segments.empty? ? 'index' : segments.join('/')]
31 | end
32 |
33 | def default_locale
34 | locales.first
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_page.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Retrieve a page from a path and a locale
5 | # (previously extracted by the ExtractLocale service)
6 | class FetchPage
7 | include Injectable
8 |
9 | dependency :context
10 |
11 | argument :path
12 | argument :locale
13 | argument :default_locale
14 | argument :fallback_to_default_locale, default: false
15 | argument :only_visible, default: false
16 |
17 | def call
18 | page = fetch_page(path, locale)
19 | page = fetch_page(path, default_locale) if !page && fallback_to_default_locale
20 | page
21 | end
22 |
23 | protected
24 |
25 | def fetch_page(path, locale)
26 | page = pages.by_path(path || 'index', locale).first
27 | !only_visible || (page&.visible? && only_visible) ? page : nil
28 | end
29 |
30 | def pages
31 | Maglev::Page
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_section_screenshot_path.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class FetchSectionScreenshotPath
5 | include Injectable
6 |
7 | dependency :fetch_sections_path
8 | argument :theme, default: nil
9 | argument :section
10 | argument :absolute, default: false
11 |
12 | def call
13 | path = "#{fetch_sections_path.call(theme: theme)}/#{section.category}/#{section.id}.jpg"
14 | absolute ? Rails.root.join("public/#{path}").to_s : "/#{path}"
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_section_screenshot_url.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class FetchSectionScreenshotUrl
5 | include Injectable
6 |
7 | dependency :fetch_section_screenshot_path
8 | argument :section
9 |
10 | def call
11 | fetch_section_screenshot_path.call(section: section) + "?#{section.screenshot_timestamp}"
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_sections_path.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class FetchSectionsPath
5 | include Injectable
6 |
7 | argument :theme, default: nil
8 |
9 | def call
10 | 'theme'
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_site.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Fetch the site and set up the Translatable available locales
5 | class FetchSite
6 | include Injectable
7 |
8 | def call
9 | site.tap do |site|
10 | change_default_locales(site)
11 | end
12 | end
13 |
14 | private
15 |
16 | def site
17 | @site ||= Maglev::Site.first
18 | end
19 |
20 | def change_default_locales(site)
21 | Maglev::I18n.available_locales = site.locale_prefixes
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_theme.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Fetch the current theme
5 | class FetchTheme
6 | include Injectable
7 |
8 | def call
9 | Maglev.local_themes.first
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/services/maglev/fetch_theme_layout.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Return the path of the layout folder
5 | class FetchThemeLayout
6 | include Injectable
7 |
8 | dependency :fetch_theme
9 |
10 | argument :page
11 |
12 | def call
13 | 'theme/layout'
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/services/maglev/generate_site.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Generate site and its pages in the locales defined by the config file.
5 | class GenerateSite
6 | include Injectable
7 |
8 | dependency(:config) { Maglev.config }
9 | dependency :setup_pages, class: Maglev::SetupPages
10 |
11 | argument :theme
12 |
13 | def call
14 | raise 'A Maglev Site already exists' if Maglev::Site.first
15 |
16 | Maglev::Site.transaction do
17 | Maglev::Site.create(name: 'Default', locales: config.default_site_locales).tap do |site|
18 | Maglev::I18n.available_locales = site.locale_prefixes
19 | Maglev::I18n.with_locale(site.default_locale_prefix) do
20 | setup_pages.call(site: site, theme: theme) if site.errors.empty?
21 | end
22 | end
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/services/maglev/get_base_url.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Get the url used to build the URLs of the pages.
5 | # In the preview mode, we need the maglev root path.
6 | class GetBaseUrl
7 | include Injectable
8 |
9 | dependency :context
10 | dependency :fetch_site
11 |
12 | argument :preview_mode, default: nil
13 |
14 | def call
15 | preview_mode? ? site_preview_path : live_url
16 | end
17 |
18 | private
19 |
20 | def preview_mode?
21 | preview_mode.nil? ? context.rendering_mode == :editor : preview_mode
22 | end
23 |
24 | def site_preview_path
25 | context.controller.site_preview_path
26 | end
27 |
28 | def live_url
29 | nil
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/services/maglev/get_page_section_names.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | # Get an array of maps including the id and name of each page section.
5 | class GetPageSectionNames
6 | include Injectable
7 |
8 | dependency :fetch_theme
9 |
10 | argument :page
11 |
12 | def call
13 | (page.sections || []).map do |section|
14 | definition = theme.sections.find(section['type'])
15 | { id: section['id'], name: definition.name }
16 | end
17 | end
18 |
19 | protected
20 |
21 | def theme
22 | fetch_theme.call
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/validators/maglev/collection_validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class CollectionValidator < ActiveModel::EachValidator
5 | def validate_each(record, attribute, value)
6 | prefix = attribute.to_s.singularize.humanize
7 |
8 | value.each_with_index do |item, index|
9 | next if item.valid?
10 |
11 | record.errors.add(
12 | "#{prefix} ##{index}",
13 | "is invalid, reason(s): #{clean_item_errors(item)}"
14 | )
15 | end
16 | end
17 |
18 | private
19 |
20 | def clean_item_errors(item)
21 | item.errors.full_messages.join(', ')
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/validators/maglev/presence_validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class PresenceValidator < ActiveModel::Validations::PresenceValidator
5 | def validate_each(record, attribute, value)
6 | value = 'false' if value == false # trick the validator
7 | super
8 | end
9 |
10 | private
11 |
12 | def clean_item_errors(item)
13 | item.errors.full_messages.join(', ')
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/views/layouts/maglev/admin/_header_actions.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to base_editor_path, class: 'text-white px-3 py-2 bg-purple-400 rounded hover:bg-purple-500 transition-colors' do %>
3 | Open editor
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/app/views/layouts/maglev/admin/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Maglev - Admin
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= vite_javascript_tag 'admin' %>
10 | <%= vite_stylesheet_tag 'admin.scss' %>
11 |
12 | <%= favicon_link_tag vite_asset_path 'images/favicon.png' %>
13 |
14 |
15 |
16 |
17 |
18 |
<%= link_to 'Maglev', admin_root_path %>
19 | Administration interface
20 |
21 | <%= render 'layouts/maglev/admin/header_actions' %>
22 |
23 | <%= yield %>
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/views/maglev/admin/themes/_section_categories.html.erb:
--------------------------------------------------------------------------------
1 | <% theme.section_categories.each_with_index do |category, index| %>
2 | <%- selected = section_category_id == category.id %>
3 | <%= link_to section_category_path(category), class: ['rounded-full text-sm px-4 py-1 mr-3 flex items-center transition-colors whitespace-nowrap', selected ? 'bg-purple-600 bg-opacity-25 text-purple-600' : 'bg-gray-300 text-gray-800 hover:bg-gray-400 hover:text-gray-900'].compact.join(' ') do %>
4 | <%= category.name.humanize %>
5 | <%= tag.span class: ['ml-2 font-bold text-xs text-white rounded-full w-4 h-4 flex items-center justify-center', selected ? 'bg-purple-600' : 'bg-gray-600'].compact.join(' ') do %>
6 | <%= theme.sections.grouped_by_category[category.id]&.count || 0 %>
7 | <% end %>
8 | <% end %>
9 | <% end %>
--------------------------------------------------------------------------------
/app/views/maglev/admin/themes/show.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= @theme.name %>
4 | ‐
5 | <%= @theme.description %>
6 |
7 |
8 |
9 | <% if @theme.sections.count == 0 %>
10 |
11 | <%= render 'empty' %>
12 |
13 | <% else %>
14 |
15 | <%= render 'section_categories', theme: @theme %>
16 |
17 |
18 |
19 | <%= render 'sections', sections: @theme.sections.grouped_by_category[section_category_id] || [] %>
20 |
21 | <% end %>
--------------------------------------------------------------------------------
/app/views/maglev/api/_pagination.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.next url_for(page: records.next_page) unless records.last_page?
4 | json.prev url_for(page: records.prev_page) unless records.first_page?
5 | json.total_pages records.total_pages
6 | json.total_items records.total_count
7 |
--------------------------------------------------------------------------------
/app/views/maglev/api/assets/_show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.id asset.id
4 | json.filename asset.filename
5 | json.byte_size asset.byte_size
6 | json.width asset.width
7 | json.height asset.height
8 | json.url public_asset_url(asset, host: maglev_config.asset_host, only_path: maglev_config.asset_host.blank?)
9 |
--------------------------------------------------------------------------------
/app/views/maglev/api/assets/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.data do
4 | json.array! @assets do |asset|
5 | json.partial!('show', asset: asset)
6 | end
7 | end
8 |
9 | json.partial!('pagination', records: @assets)
10 |
--------------------------------------------------------------------------------
/app/views/maglev/api/assets/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.partial!('show', asset: @asset)
4 |
--------------------------------------------------------------------------------
/app/views/maglev/api/collection_items/_show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.id item.id
4 | json.label item.label
5 | json.image_url item.image_url
6 |
--------------------------------------------------------------------------------
/app/views/maglev/api/collection_items/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.array! @items do |item|
4 | json.partial!('show', item: item)
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/maglev/api/collection_items/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | if @item
4 | json.partial!('show', item: @item)
5 | else
6 | json.nil!
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/maglev/api/pages/_show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.key_format! camelize: :lower
4 | json.deep_format_keys!
5 |
6 | json.id page.id
7 | json.title page.title || page.default_title
8 | json.path page.path || page.default_path
9 | json.path_hash page.path_hash
10 | json.visible page.visible
11 |
12 | json.seo_title page.seo_title
13 | json.meta_description page.meta_description
14 | json.og_title page.og_title
15 | json.og_description page.og_description
16 | json.og_image_url page.og_image_url
17 |
18 | json.preview_url services.get_page_fullpath.call(page: page, preview_mode: true, locale: content_locale)
19 | json.live_url services.get_page_fullpath.call(page: page, preview_mode: false, locale: content_locale)
20 | json.section_names services.get_page_section_names.call(page: page)
21 | json.sections services.get_page_sections.call(page: page)
22 | json.lock_version page.lock_version
23 | json.translated page.path.present?
24 |
--------------------------------------------------------------------------------
/app/views/maglev/api/pages/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.array! @pages do |page|
4 | json.id page.id
5 | json.title page.title || page.default_title
6 | json.path page.path || page.default_path
7 | json.visible page.visible
8 | json.static page.static?
9 |
10 | json.seo_title page.seo_title
11 | json.meta_description page.meta_description
12 | json.og_title page.og_title
13 | json.og_description page.og_description
14 | json.og_image_url page.og_image_url
15 |
16 | json.preview_url services.get_page_fullpath.call(page: page, preview_mode: true, locale: content_locale)
17 | json.section_names services.get_page_section_names.call(page: page)
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/maglev/api/pages/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.partial!('show', page: @page)
4 |
--------------------------------------------------------------------------------
/app/views/maglev/api/sites/_show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.key_format! camelize: :lower
4 | json.deep_format_keys!
5 |
6 | json.sections site.sections || []
7 |
8 | json.style services.fetch_style.call(site: site, theme: maglev_theme).as_json
9 |
10 | json.locales site.locales
11 |
12 | json.call(site, *site.api_attributes)
13 | json.home_page_id home_page_id
14 |
15 | json.lock_version site.lock_version
16 |
--------------------------------------------------------------------------------
/app/views/maglev/api/sites/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.partial!('show', site: @site, home_page_id: @home_page_id)
4 |
--------------------------------------------------------------------------------
/app/views/maglev/api/themes/_show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.key_format! camelize: :lower
4 | json.deep_format_keys!
5 | json.call(theme, :id, :name, :description)
6 | json.sections theme.sections do |section|
7 | json.call(section, :id, :name, :category, :site_scoped, :singleton, :viewport_fixed_position,
8 | :insert_button, :insert_at, :max_width_pane,
9 | :blocks_label, :blocks_presentation, :sample)
10 | json.settings section.settings.as_json
11 | json.blocks section.blocks.as_json
12 | json.theme_id theme.id
13 | json.screenshot_path services.fetch_section_screenshot_url.call(section: section)
14 | end
15 | json.section_categories theme.section_categories.as_json
16 | json.icons theme.icons || []
17 | json.style_settings theme.style_settings.as_json
18 |
--------------------------------------------------------------------------------
/app/views/maglev/editor/_header.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/maglev/settings/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | http://localhost:3000<%= sources_from_manifest_entrypoints(%w(live-preview-client), type: :javascript).first %>
6 |
7 | <%= live_preview_client_js_url %>
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/views/maglev/sitemap/index.xml.builder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | xml.instruct!
4 |
5 | xml.urlset xmlns: 'http://www.google.com/schemas/sitemap/0.9', "xmlns:xhtml": 'http://www.w3.org/1999/xhtml' do
6 | @pages.each do |page|
7 | xml.url do
8 | xml.loc sitemap_url(@host, page, maglev_site.default_locale_prefix)
9 | xml.lastmod page.updated_at.strftime('%Y-%m-%d')
10 |
11 | if maglev_site.locales.size > 1
12 | maglev_site.locales.each do |locale|
13 | xml.xhtml :link,
14 | rel: 'alternate',
15 | hreflang: locale.prefix,
16 | href: sitemap_url(@host, page, locale.prefix)
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path('..', __dir__)
6 | ENGINE_PATH = File.expand_path('../lib/maglev/engine', __dir__)
7 |
8 | # Set up gems listed in the Gemfile.
9 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
11 |
12 | require 'rails/version'
13 | DUMMY_NAME = Rails::VERSION::MAJOR >= 8 ? 'dummy' : 'legacy_dummy'
14 | APP_PATH = File.expand_path("../spec/#{DUMMY_NAME}/config/application", __dir__)
15 |
16 | require "rails/all"
17 | require "rails/engine/commands"
18 |
--------------------------------------------------------------------------------
/bin/vite:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'vite' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("vite_ruby", "vite")
30 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameters_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += %i[base64_image]
7 |
--------------------------------------------------------------------------------
/config/initializers/kaminari.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'kaminari'
4 |
5 | Kaminari.configure do |config|
6 | config.default_per_page = 25
7 | config.max_per_page = 25
8 | # config.window = 4
9 | # config.outer_window = 0
10 | # config.left = 0
11 | # config.right = 0
12 | # config.page_method_name = :page
13 | # config.param_name = :page
14 | # config.max_pages = nil
15 | # config.params_on_first_page = false
16 | end
17 |
--------------------------------------------------------------------------------
/config/initializers/zeitwerk.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.autoloaders.each do |autoloader|
4 | autoloader.ignore("#{__dir__}/../../app/frontend")
5 | end
6 |
--------------------------------------------------------------------------------
/config/locales/activerecord.ar.yml:
--------------------------------------------------------------------------------
1 | ar:
2 | activerecord:
3 | attributes:
4 | 'maglev/page':
5 | cloned_title: "%{title} نسخة"
6 | 'maglev/page/paths':
7 | value: المسار
8 | errors:
9 | models:
10 | maglev/page_path:
11 | attributes:
12 | value:
13 | invalid_path: "ليس مسارًا صالحًا"
14 |
--------------------------------------------------------------------------------
/config/locales/activerecord.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | activerecord:
3 | attributes:
4 | 'maglev/page':
5 | cloned_title: "%{title} COPY"
6 | 'maglev/page/paths':
7 | value: Path
8 | errors:
9 | models:
10 | maglev/page_path:
11 | attributes:
12 | value:
13 | invalid_path: "is not a valid path"
14 |
--------------------------------------------------------------------------------
/config/locales/activerecord.es.yml:
--------------------------------------------------------------------------------
1 | es:
2 | activerecord:
3 | models:
4 | 'maglev/page': Página
5 |
6 | attributes:
7 | 'maglev/page':
8 | title: Título
9 | cloned_title: "%{title} COPIA"
10 | seo_title: Título SEO
11 | meta_description: Meta descripción
12 | og_title: "og:title"
13 | og_description: "og:description"
14 | og_image_url: "og:image"
15 | 'maglev/page/paths':
16 | value: Ruta
17 |
18 | errors:
19 | models:
20 | maglev/page:
21 | attributes:
22 | title:
23 | blank: no puede estar en blanco
24 | path:
25 | taken: ya está en uso por otra página
26 | exclusion: ya está en uso por otra página
27 | maglev/page_path:
28 | attributes:
29 | value:
30 | invalid_path: "no es una ruta válida"
31 |
--------------------------------------------------------------------------------
/config/locales/activerecord.fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | activerecord:
3 | models:
4 | 'maglev/page': Page
5 |
6 | attributes:
7 | 'maglev/page':
8 | title: Titre
9 | cloned_title: "%{title} COPIE"
10 | seo_title: Titre SEO
11 | meta_description: Meta description
12 | og_title: "og:title"
13 | og_description: "og:description"
14 | og_image_url: "og:image"
15 | 'maglev/page/paths':
16 | value: Chemin
17 |
18 | errors:
19 | models:
20 | maglev/page:
21 | attributes:
22 | title:
23 | blank: doît être rempli
24 | path:
25 | taken: est déjà utilisé par une autre page
26 | exclusion: est déjà utilisé par une autre page
27 | maglev/page_path:
28 | attributes:
29 | value:
30 | invalid_path: "n'est pas un chemin valide"
31 |
--------------------------------------------------------------------------------
/config/locales/activerecord.pt-BR.yml:
--------------------------------------------------------------------------------
1 | 'pt-BR':
2 | activerecord:
3 | attributes:
4 | 'maglev/page':
5 | cloned_title: "%{title} CÓPIA"
6 | 'maglev/page/paths':
7 | value: Caminho
8 | errors:
9 | models:
10 | maglev/page_path:
11 | attributes:
12 | value:
13 | invalid_path: "não é um caminho válido"
--------------------------------------------------------------------------------
/config/vite.json:
--------------------------------------------------------------------------------
1 | {
2 | "all": {
3 | "sourceCodeDir": "app/frontend",
4 | "watchAdditionalPaths": [],
5 | "publicOutputDir": "maglev-assets"
6 | },
7 | "development": {
8 | "autoBuild": true,
9 | "publicOutputDir": "maglev-assets-dev",
10 | "port": 3136
11 | },
12 | "test": {
13 | "autoBuild": true,
14 | "publicOutputDir": "maglev-assets-test",
15 | "port": 3137
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/db/migrate/20200824085207_create_maglev_sites.rb:
--------------------------------------------------------------------------------
1 | class CreateMaglevSites < ActiveRecord::Migration[6.0]
2 | include Maglev::Migration
3 | def change
4 | create_table :maglev_sites, id: primary_key_type do |t|
5 | t.string :name
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20200824104648_create_maglev_pages.rb:
--------------------------------------------------------------------------------
1 | class CreateMaglevPages < ActiveRecord::Migration[6.0]
2 | include Maglev::Migration
3 | def change
4 | create_table :maglev_pages, id: primary_key_type do |t|
5 | t.string :title
6 | t.string :path
7 | t.string :seo_title
8 | t.string :meta_description
9 | t.boolean :visible, default: true
10 |
11 | t.timestamps
12 | end
13 |
14 | up_only do
15 | add_index :maglev_pages, :path, unique: true
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/db/migrate/20200831101942_create_maglev_section_content.rb:
--------------------------------------------------------------------------------
1 | class CreateMaglevSectionContent < ActiveRecord::Migration[6.0]
2 | def change
3 | change_table :maglev_sites do |t|
4 | if t.respond_to? :jsonb
5 | t.jsonb :sections, default: []
6 | else
7 | t.json :sections, default: []
8 | end
9 | end
10 |
11 | change_table :maglev_pages do |t|
12 | if t.respond_to? :jsonb
13 | t.jsonb :sections, default: []
14 | else
15 | t.json :sections, default: []
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/db/migrate/20201206172020_create_maglev_assets.rb:
--------------------------------------------------------------------------------
1 | class CreateMaglevAssets < ActiveRecord::Migration[6.0]
2 | include Maglev::Migration
3 | def change
4 | create_table :maglev_assets, id: primary_key_type do |t|
5 | t.string :filename
6 | t.string :content_type
7 | t.integer :width
8 | t.integer :height
9 | t.integer :byte_size
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20210830085101_create_maglev_page_paths.rb:
--------------------------------------------------------------------------------
1 | class CreateMaglevPagePaths < ActiveRecord::Migration[6.0]
2 | include Maglev::Migration
3 | def change
4 | create_table :maglev_page_paths, id: primary_key_type do |t|
5 | t.references :maglev_page, type: foreign_key_type
6 | t.string :locale, null: false
7 | t.string :value, null: false
8 | end
9 |
10 | add_index :maglev_page_paths, [:value, :locale], unique: true
11 |
12 | remove_column :maglev_pages, :path, :string
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20210906102712_add_canonical_to_pages.rb:
--------------------------------------------------------------------------------
1 | class AddCanonicalToPages < ActiveRecord::Migration[6.0]
2 | def change
3 | add_column :maglev_page_paths, :canonical, :boolean, null: true, default: true
4 | add_index :maglev_page_paths, %i[canonical maglev_page_id locale], unique: true, name: 'canonical_uniqueness'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211008064437_add_locales_to_sites.rb:
--------------------------------------------------------------------------------
1 | class AddLocalesToSites < ActiveRecord::Migration[6.0]
2 | def change
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
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20211013210954_translate_section_content.rb:
--------------------------------------------------------------------------------
1 | class TranslateSectionContent < ActiveRecord::Migration[6.0]
2 | def change
3 | remove_column :maglev_sites, :sections, :jsonb, default: []
4 | remove_column :maglev_pages, :sections, :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
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20211101205001_add_lock_version_to_maglev_pages.rb:
--------------------------------------------------------------------------------
1 | class AddLockVersionToMaglevPages < ActiveRecord::Migration[6.0]
2 | def change
3 | add_column :maglev_sites, :lock_version, :integer
4 | add_column :maglev_pages, :lock_version, :integer
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211116161121_better_page_path_canonical_indices.rb:
--------------------------------------------------------------------------------
1 | class BetterPagePathCanonicalIndices < ActiveRecord::Migration[6.0]
2 | def change
3 | remove_index :maglev_page_paths, [:value, :locale], unique: true
4 | remove_index :maglev_page_paths, %i[canonical maglev_page_id locale], unique: true, name: 'canonical_uniqueness'
5 | add_index :maglev_page_paths, %i[canonical locale value], name: 'canonical_speed'
6 | add_index :maglev_page_paths, %i[canonical maglev_page_id locale value], unique: true, name: 'canonical_uniqueness'
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20211124101005_fix_page_path_indices.rb:
--------------------------------------------------------------------------------
1 | class FixPagePathIndices < ActiveRecord::Migration[6.0]
2 | def change
3 | remove_index :maglev_page_paths, %i[canonical maglev_page_id locale value], unique: true, name: 'canonical_uniqueness'
4 | add_index :maglev_page_paths, %i[canonical maglev_page_id locale], name: 'scoped_canonical_speed'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211203224112_add_open_graph_tags_to_pages.rb:
--------------------------------------------------------------------------------
1 | class AddOpenGraphTagsToPages < ActiveRecord::Migration[6.0]
2 | def change
3 | change_table :maglev_pages do |t|
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
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20220612092235_add_style_to_sites.rb:
--------------------------------------------------------------------------------
1 | class AddStyleToSites < ActiveRecord::Migration[6.0]
2 | def change
3 | change_table :maglev_sites do |t|
4 | if t.respond_to? :jsonb
5 | t.jsonb :style, default: []
6 | else
7 | t.json :style, default: []
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/commands/maglev/create_site_command.rb:
--------------------------------------------------------------------------------
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
29 |
--------------------------------------------------------------------------------
/lib/generators/maglev/templates/install/public/theme/image-placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/lib/generators/maglev/templates/install/public/theme/image-placeholder.jpg
--------------------------------------------------------------------------------
/lib/generators/maglev/templates/theme/app/theme/theme.yml.tt:
--------------------------------------------------------------------------------
1 | # Please, do not change the id of the theme
2 | id: "theme"
3 |
4 | name: "theme"
5 |
6 | description: "A brief description of the theme"
7 |
8 | section_categories:
9 | - name: content
10 |
11 | # Properties of your theme such as the primary color, font name, ...etc.
12 | style_settings: []
13 |
14 | pages:
15 | - title: "Home page"
16 | path: "/index"
17 |
18 | # List of CSS class names used by your library of icons (font awesome, remixicons, ...etc)
19 | icons: []
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/generators/maglev/theme_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class ThemeGenerator < Rails::Generators::Base
5 | source_root File.expand_path('templates/theme', __dir__)
6 | hook_for :maglev_theme
7 |
8 | def create_theme_files
9 | directory 'app'
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/maglev/active_storage.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | module ActiveStorage
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | has_one_attached :file
9 |
10 | after_commit :save_metadata_now, on: :create, prepend: true
11 |
12 | delegate :url, :download, to: :file
13 | end
14 |
15 | private
16 |
17 | # rubocop:disable Metrics/AbcSize
18 | def save_metadata_now
19 | file.analyze if file.attached?
20 | update(
21 | filename: file.filename.to_s,
22 | content_type: file.content_type,
23 | byte_size: file.byte_size,
24 | height: file.metadata['height'],
25 | width: file.metadata['width']
26 | )
27 | end
28 | # rubocop:enable Metrics/AbcSize
29 |
30 | module ClassMethods
31 | def optimized
32 | all.with_attached_file
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/maglev/config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | Config = Struct.new(:primary_color, :title, :favicon, :logo, :back_action,
5 | :site_publishable, :uploader, :preview_host, :asset_host, :services,
6 | :collections, :is_authenticated, :ui_locale, :default_site_locales,
7 | :static_pages, :reserved_paths,
8 | :admin_username, :admin_password)
9 | end
10 |
--------------------------------------------------------------------------------
/lib/maglev/errors.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | module Errors
5 | class NotAuthorized < StandardError; end
6 | class UnknownSection < StandardError; end
7 | class DuplicateSectionDefinition < StandardError; end
8 |
9 | class UnknownSetting < StandardError
10 | def initialize(section_id, block_id, setting_id)
11 | key = [section_id, block_id].compact.join('.')
12 | super("[#{key}] The #{setting_id} setting is undeclared OR its type is unknown.")
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/maglev/migration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | module Migration
5 | private
6 |
7 | def primary_key_type
8 | primary_key_type_setting || :primary_key
9 | end
10 |
11 | def foreign_key_type
12 | primary_key_type_setting || :bigint
13 | end
14 |
15 | def primary_key_type_setting
16 | config = Rails.configuration.generators
17 | config.options[config.orm][:primary_key_type]
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/maglev/reserved_paths.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | class ReservedPaths < ::Array
5 | def include?(value)
6 | each do |path|
7 | return true if path == value ||
8 | (path.include?('*') && value && File.fnmatch(path, value))
9 | end
10 | false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/maglev/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | VERSION = '1.8.0'
5 | end
6 |
--------------------------------------------------------------------------------
/lib/maglevcms.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # maglevcms is the official gem name but we prefer to use maglev inside the project
4 | require 'maglev'
5 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/spec/components/maglev/content/checkbox_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::Content::Checkbox do
6 | let(:section_component) { double('Maglev::SectionComponent') }
7 | let(:content) { true }
8 | let(:setting) do
9 | double('Maglev::Section::Checkbox', default: false, id: 'display_title', label: 'Display title?', type: 'checkbox',
10 | options: {})
11 | end
12 | let(:checkbox) { described_class.new(section_component, content, setting) }
13 |
14 | describe 'to_s method' do
15 | it { expect(checkbox.to_s).to eq(true) }
16 | end
17 |
18 | describe 'Given the content is the "true" string' do
19 | let(:content) { 'true' }
20 | it { expect(checkbox.true?).to eq(true) }
21 | end
22 |
23 | describe 'Given the content is the "false" string' do
24 | let(:content) { 'false' }
25 | it { expect(checkbox.true?).to eq(false) }
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/controllers/maglev/assets/active_storage_proxy_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe Maglev::Assets::ActiveStorageProxyController do
6 | routes { Maglev::Engine.routes }
7 |
8 | describe 'GET show' do
9 | describe 'Given we use uuids as primary keys in the DB' do
10 | before do
11 | allow(Maglev).to receive(:uuid_as_primary_key?).and_return(true)
12 | end
13 |
14 | it 'serves the file' do
15 | asset = create(:asset)
16 | expect(Maglev::Asset).to receive(:find).with('9565604d-be66-4a23-98da-ed1639804103').and_return(asset)
17 | get :show, params: { id: '9565604d-be66-4a23-98da-ed1639804103-myasset' }
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require_relative 'config/application'
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/new-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/app/assets/images/new-logo.png
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/themes/sections/jumbotron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/app/assets/images/themes/sections/jumbotron.png
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
5 | allow_browser versions: :modern
6 |
7 | rescue_from Maglev::Errors::NotAuthorized, with: :unauthorized_maglev
8 |
9 | private
10 |
11 | def current_account
12 | Account.first
13 | end
14 |
15 | protected
16 |
17 | def exotic_locale
18 | 'fr'
19 | end
20 |
21 | def unauthorized_maglev
22 | flash[:error] = "You're not authorized to access the Maglev editor!"
23 | redirect_to main_app.nocoffee_path
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/products_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ProductsController < ApplicationController
4 | include Maglev::StandaloneSectionsConcern
5 |
6 | def show
7 | fetch_maglev_site_scoped_sections
8 | @product = Product.find(params[:id])
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | # Automatically retry jobs that encountered a deadlock
5 | # retry_on ActiveRecord::Deadlocked
6 |
7 | # Most jobs are safe to ignore if the underlying records are no longer available
8 | # discard_on ActiveJob::DeserializationError
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: 'from@example.com'
5 | layout 'mailer'
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/account.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Account < ApplicationRecord
4 | has_secure_password
5 | end
6 |
7 | # == Schema Information
8 | #
9 | # Table name: accounts
10 | #
11 | # id :bigint not null, primary key
12 | # email :string
13 | # password_digest :string
14 | # created_at :datetime not null
15 | # updated_at :datetime not null
16 | #
17 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | primary_abstract_class
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/theme/.keep:
--------------------------------------------------------------------------------
1 | .keep
--------------------------------------------------------------------------------
/spec/dummy/app/theme/sections/features/featured_product.yml:
--------------------------------------------------------------------------------
1 | name: Featured product
2 |
3 | category: features
4 |
5 | settings:
6 | - label: "Title"
7 | id: title
8 | type: text
9 |
10 | - label: "Product"
11 | id: product
12 | type: collection_item
13 | collection_id: products
14 | default: any
15 |
16 | - label: "Description"
17 | id: description
18 | type: text
19 | html: true
20 | nb_rows: 6
21 | # line_break: true
22 | default: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eros nisi, feugiat non molestie ac, pretium sodales eros. Nullam faucibus malesuada orci non dignissim. "
23 |
24 | blocks: []
--------------------------------------------------------------------------------
/spec/dummy/app/theme/sections/headers/jumbotron.yml:
--------------------------------------------------------------------------------
1 | # Name of the section displayed in the editor UI
2 | name: Jumbotron
3 |
4 | category: headers
5 |
6 | # Definition of the settings:
7 | # A setting type can be one of the following values: text, image, checkbox, link and color.
8 | # Please visit: https://docs.maglev.dev/concepts/setting for more explanation.
9 | settings:
10 | - label: "Title"
11 | id: title
12 | type: text
13 | default: "Title"
14 | # html: true
15 | # line_break: true
16 |
17 | - label: "Body"
18 | id: body
19 | type: text
20 | html: true
21 | line_break: true
22 | default: "Body"
23 |
24 | # Definition of the blocks.
25 | # You can define as many types of blocks as you want.
26 | blocks: []
27 |
28 | # By default, in the editor UI, blocks will be listed below the "Content" title.
29 | # The title can be changed with the following property:
30 | # blocks_label: "My list of items"
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/maglev/editor/_header.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/products/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= render_maglev_section :navbar %>
2 | <% content_for(:title) { @product.name } %>
3 | <%= @product.name %>
4 | Price: <%= number_to_currency @product.price %>
--------------------------------------------------------------------------------
/spec/dummy/app/views/pwa/manifest.json.erb:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Dummy",
3 | "icons": [
4 | {
5 | "src": "/icon.png",
6 | "type": "image/png",
7 | "sizes": "512x512"
8 | },
9 | {
10 | "src": "/icon.png",
11 | "type": "image/png",
12 | "sizes": "512x512",
13 | "purpose": "maskable"
14 | }
15 | ],
16 | "start_url": "/",
17 | "display": "standalone",
18 | "scope": "/",
19 | "description": "Dummy.",
20 | "theme_color": "red",
21 | "background_color": "red"
22 | }
23 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/theme/sections/features/featured_product.html.erb:
--------------------------------------------------------------------------------
1 | <%= maglev_section.wrapper_tag class: 'featured-product' do %>
2 | <%= maglev_section.setting_tag :product, class: 'featured-product' do |product| %>
3 | <%= product.name %>
4 | <%= number_to_currency product.price %>
5 | <% end %>
6 | <% end %>
--------------------------------------------------------------------------------
/spec/dummy/app/views/theme/sections/features/showcase.html.erb:
--------------------------------------------------------------------------------
1 | <%= tag.section class: 'showcase', data: section.tag_data do %>
2 |
3 |
<%= section.setting_tag :icon, html_tag: 'i' %>
4 |
5 | <%= tag.h2 class: 'display-4', data: section.settings.title.tag_data do %>
6 | <%= raw section.settings.title %>
7 | <% end %>
8 |
9 |
10 | <% section.blocks.each do |block| %>
11 | <%= tag.li data: block.tag_data do %>
12 | <%= tag.h3 data: block.settings.title.tag_data do %>
13 | <%= block.settings.title %>
14 | <% end %>
15 | <%= image_tag block.settings.image.url, alt: block.settings.image.alt_text, data: block.settings.image.tag_data %>
16 | <% end %>
17 | <% end %>
18 |
19 |
20 | <% end %>
21 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/theme/sections/headers/jumbotron.html.erb:
--------------------------------------------------------------------------------
1 | class="jumbotron">
2 |
3 |
class="display-3"><%= raw section.settings.title %>
4 |
><%= raw section.settings.body %>
5 |
6 |
--------------------------------------------------------------------------------
/spec/dummy/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | exec './bin/rails', 'server', *ARGV
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_PATH = File.expand_path('../config/application', __dir__)
5 | require_relative '../config/boot'
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require_relative '../config/boot'
5 | require 'rake'
6 | Rake.application.run
7 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative 'config/environment'
6 |
7 | run Rails.application
8 | Rails.application.load_server
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
5 |
6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative 'application'
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = '1.0'
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
6 | # Use this to limit dissemination of sensitive information.
7 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
8 | Rails.application.config.filter_parameters += %i[
9 | passw email secret token _key crypt salt certificate otp ssn cvv cvc
10 | ]
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Add new inflection rules using the following format. Inflections
6 | # are locale specific, and you may define rules for as many different
7 | # locales as you wish. All of these examples are active by default:
8 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
9 | # inflect.plural /^(ox)$/i, "\\1en"
10 | # inflect.singular /^(ox)en/i, "\\1"
11 | # inflect.irregular "person", "people"
12 | # inflect.uncountable %w( fish sheep )
13 | # end
14 |
15 | # These inflection rules are supported but not enabled by default:
16 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
17 | # inflect.acronym "RESTful"
18 | # end
19 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/maglev.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Maglev.configure do |config|
4 | # config.logo = 'logo.svg'
5 |
6 | config.uploader = :active_storage
7 |
8 | config.collections = {
9 | products: {
10 | model: 'Product',
11 | fields: {
12 | label: :name,
13 | image: :thumbnail_url
14 | }
15 | }
16 | }
17 |
18 | config.default_site_locales = [{ label: 'English', prefix: 'en' }, { label: 'French', prefix: 'fr' }]
19 |
20 | config.static_pages = [
21 | {
22 | title: { en: 'Products', fr: 'Produits' },
23 | path: { en: 'products', fr: 'fr/produits' }
24 | },
25 | {
26 | title: { en: 'Authentication', fr: 'Authentification' },
27 | path: { en: 'sign-in', fr: 'fr/se-connecter' }
28 | }
29 | ]
30 |
31 | config.reserved_paths = %w[products sign-in search posts/*]
32 | end
33 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | hello: "Bonjour le monde"
3 | lang:
4 | en: "English"
5 | fr: "Français"
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20200824091231_create_accounts.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateAccounts < ActiveRecord::Migration[6.0]
4 | def change
5 | create_table :accounts do |t|
6 | t.string :email
7 | t.string :password_digest
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20210129103814_create_active_storage_variant_records.active_storage.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This migration comes from active_storage (originally 20191206030411)
4 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
5 | def change
6 | create_table :active_storage_variant_records do |t|
7 | t.belongs_to :blob, null: false, index: false
8 | t.string :variation_digest, null: false
9 |
10 | t.index %i[blob_id variation_digest], name: 'index_active_storage_variant_records_uniqueness', unique: true
11 | t.foreign_key :active_storage_blobs, column: :blob_id
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20210819081156_create_products.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateProducts < ActiveRecord::Migration[6.1]
4 | def change
5 | create_table :products do |t|
6 | t.string :name
7 | t.string :sku, unique: true
8 | t.float :price
9 | t.boolean :sold_out, default: false
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/lib/tasks/annotate_rb.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This rake task was added by annotate_rb gem.
4 |
5 | # Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
6 | if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
7 | require 'annotate_rb'
8 |
9 | AnnotateRb::Core.load_rake_tasks
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/dummy/public/icon.png
--------------------------------------------------------------------------------
/spec/dummy/public/icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/spec/factories/maglev/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :asset, class: 'Maglev::Asset' do
5 | file { Rack::Test::UploadedFile.new('spec/fixtures/files/asset.jpg', 'image/jpeg') }
6 | filename { 'asset.jpg' }
7 | content_type { 'image/jpeg' }
8 | end
9 | end
10 |
11 | # == Schema Information
12 | #
13 | # Table name: maglev_assets
14 | #
15 | # id :bigint not null, primary key
16 | # byte_size :integer
17 | # content_type :string
18 | # filename :string
19 | # height :integer
20 | # width :integer
21 | # created_at :datetime not null
22 | # updated_at :datetime not null
23 | #
24 |
--------------------------------------------------------------------------------
/spec/factories/maglev/section_blocks.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :section_block, class: 'Maglev::Section::Block' do
5 | name { 'Slide' }
6 | type { 'slide' }
7 | settings { [] }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/factories/maglev/section_settings.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :section_setting, class: 'Maglev::Section::Setting' do
5 | id { 'title' }
6 | label { 'Title' }
7 | type { 'text' }
8 | default { 'Hello world' }
9 | options { { html: true } }
10 |
11 | trait :link do
12 | id { 'link' }
13 | label { 'Link' }
14 | type { 'link' }
15 | options { { with_text: true } }
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/factories/maglev/static_pages.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :static_page, class: 'Maglev::StaticPage' do
5 | id { 'static-page-1' }
6 | title_translations { { en: 'Products', fr: 'Produits' }.stringify_keys }
7 | path_translations { { en: 'products', fr: 'fr/produits' }.stringify_keys }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/factories/products.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :product, class: 'Product' do
5 | sequence(:name) { |n| "Product ##{n.to_s.rjust(2, '0')}" }
6 | sequence(:sku) { |n| "sku-#{n}" }
7 | price { 42.0 }
8 | thumbnail { Rack::Test::UploadedFile.new('spec/fixtures/files/asset.jpg', 'image/jpeg') }
9 |
10 | trait :without_thumbnail do
11 | thumbnail { nil }
12 | end
13 |
14 | trait :sold_out do
15 | sold_out { true }
16 | end
17 | end
18 | end
19 |
20 | # == Schema Information
21 | #
22 | # Table name: products
23 | #
24 | # id :bigint not null, primary key
25 | # name :string
26 | # price :float
27 | # sku :string
28 | # sold_out :boolean default(FALSE)
29 | # created_at :datetime not null
30 | # updated_at :datetime not null
31 | #
32 |
--------------------------------------------------------------------------------
/spec/fixtures/files/asset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/fixtures/files/asset.jpg
--------------------------------------------------------------------------------
/spec/fixtures/files/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/fixtures/files/logo.png
--------------------------------------------------------------------------------
/spec/fixtures/themes/theme_with_sections_with_same_id/sections/category_1/section_a.yml:
--------------------------------------------------------------------------------
1 | # Name of the section displayed in the editor UI
2 | name: Section A
3 |
4 | category: category_1
5 |
6 | # Definition of the settings:
7 | # A setting type can be one of the following values: text, image, checkbox, link and color.
8 | # Please visit: https://docs.maglev.dev/concepts/setting for more explanation.
9 | settings:
10 | - label: "Title"
11 | id: title
12 | type: text
13 | default: "Title"
14 | # html: true
15 | # line_break: true
16 |
17 | - label: "Body"
18 | id: body
19 | type: text
20 | html: true
21 | line_break: true
22 | default: "Body"
23 |
24 | # Definition of the blocks.
25 | # You can define as many types of blocks as you want.
26 | blocks: []
27 |
28 | # By default, in the editor UI, blocks will be listed below the "Content" title.
29 | # The title can be changed with the following property:
30 | # blocks_label: "My list of items"
--------------------------------------------------------------------------------
/spec/fixtures/themes/theme_with_sections_with_same_id/sections/category_2/section_a.yml:
--------------------------------------------------------------------------------
1 | # Name of the section displayed in the editor UI
2 | name: Section A
3 |
4 | category: category_2
5 |
6 | # Definition of the settings:
7 | # A setting type can be one of the following values: text, image, checkbox, link and color.
8 | # Please visit: https://docs.maglev.dev/concepts/setting for more explanation.
9 | settings:
10 | - label: "Title"
11 | id: title
12 | type: text
13 | default: "Title"
14 | # html: true
15 | # line_break: true
16 |
17 | # Definition of the blocks.
18 | # You can define as many types of blocks as you want.
19 | blocks: []
20 |
21 | # By default, in the editor UI, blocks will be listed below the "Content" title.
22 | # The title can be changed with the following property:
23 | # blocks_label: "My list of items"
--------------------------------------------------------------------------------
/spec/fixtures/themes/theme_with_sections_with_same_id/theme.yml:
--------------------------------------------------------------------------------
1 | id: default
2 | name: Default
3 |
4 | section_categories:
5 | - id: category_1
6 | name: "Category #1"
7 | - id: category_2
8 | name: "Category #2"
9 |
10 | style_settings: []
--------------------------------------------------------------------------------
/spec/helpers/json_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module JsonResponse
4 | def json_response
5 | JSON.parse(response.body)
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/helpers/pretty_html.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module PrettyHTML
4 | HTML_XSL = <<~XSL
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | XSL
13 |
14 | def pretty_html(html)
15 | doc = Nokogiri::XML(html)
16 | Nokogiri::XSLT(HTML_XSL)
17 | .transform(doc)
18 | .to_s
19 | .gsub('', '')
20 | .strip
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults
2 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.1.4
2 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require_relative 'config/application'
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link maglev_manifest.js
4 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/assets/images/new-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/app/assets/images/new-logo.png
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/assets/images/themes/sections/jumbotron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/app/assets/images/themes/sections/jumbotron.png
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | rescue_from Maglev::Errors::NotAuthorized, with: :unauthorized_maglev
5 |
6 | private
7 |
8 | def current_account
9 | Account.first
10 | end
11 |
12 | protected
13 |
14 | def exotic_locale
15 | 'fr'
16 | end
17 |
18 | def unauthorized_maglev
19 | flash[:error] = "You're not authorized to access the Maglev editor!"
20 | redirect_to main_app.nocoffee_path
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/controllers/products_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ProductsController < ApplicationController
4 | include Maglev::StandaloneSectionsConcern
5 |
6 | def show
7 | fetch_maglev_site_scoped_sections
8 | @product = Product.find(params[:id])
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/javascript/packs/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require_tree .
16 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | # Automatically retry jobs that encountered a deadlock
5 | # retry_on ActiveRecord::Deadlocked
6 |
7 | # Most jobs are safe to ignore if the underlying records are no longer available
8 | # discard_on ActiveJob::DeserializationError
9 | end
10 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: 'from@example.com'
5 | layout 'mailer'
6 | end
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/models/account.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Account < ApplicationRecord
4 | has_secure_password
5 | end
6 |
7 | # == Schema Information
8 | #
9 | # Table name: accounts
10 | #
11 | # id :bigint not null, primary key
12 | # email :string
13 | # password_digest :string
14 | # created_at :datetime not null
15 | # updated_at :datetime not null
16 | #
17 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/theme/.keep:
--------------------------------------------------------------------------------
1 | .keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/theme/sections/features/featured_product.yml:
--------------------------------------------------------------------------------
1 | name: Featured product
2 |
3 | category: features
4 |
5 | settings:
6 | - label: "Title"
7 | id: title
8 | type: text
9 |
10 | - label: "Product"
11 | id: product
12 | type: collection_item
13 | collection_id: products
14 |
15 | blocks: []
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/theme/sections/headers/jumbotron.yml:
--------------------------------------------------------------------------------
1 | # Name of the section displayed in the editor UI
2 | name: Jumbotron
3 |
4 | category: headers
5 |
6 | # Definition of the settings:
7 | # A setting type can be one of the following values: text, image, checkbox, link and color.
8 | # Please visit: https://docs.maglev.dev/concepts/setting for more explanation.
9 | settings:
10 | - label: "Title"
11 | id: title
12 | type: text
13 | # html: true
14 | # line_break: true
15 |
16 | - label: "Body"
17 | id: body
18 | type: text
19 | html: true
20 | line_break: true
21 |
22 | # Definition of the blocks.
23 | # You can define as many types of blocks as you want.
24 | blocks: []
25 |
26 | # By default, in the editor UI, blocks will be listed below the "Content" title.
27 | # The title can be changed with the following property:
28 | # blocks_label: "My list of items"
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/theme/theme.yml:
--------------------------------------------------------------------------------
1 | id: "simple"
2 | name: "My simple theme"
3 |
4 | section_categories:
5 | - name: Headers
6 | id: headers
7 | - name: Features
8 | - name: Call to Actions
9 |
10 | style_settings:
11 | - label: "Primary color"
12 | id: primary_color
13 | type: color
14 | presets: ["#F87171", "#FBBF24", "#34D399"]
15 | default: "#F87171"
16 | - label: "Font name"
17 | id: font_name
18 | type: text
19 |
20 | pages:
21 | - title: "Home page"
22 | path: "index"
23 | sections:
24 | - type: "jumbotron"
25 | settings:
26 | title: "Let's create the product
your clients
will love."
27 | body: "NoCoffee, passionated developers,
creators of web applications, mobiles apps and
fancy R&D projects.
"
28 | blocks: []
29 | - type: "showcase"
30 | settings:
31 | title: "Our projects"
32 | blocks: []
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= content_for?(:page_title) ? yield(:page_title) : 'Dummy' %>
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 |
9 | <%= yield %>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/products/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= render_maglev_section :navbar %>
2 | <% content_for(:page_title) { @product.name } %>
3 | <%= @product.name %>
4 | Price: <%= number_to_currency @product.price %>
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/theme/layout.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= maglev_site.name %> - <%= maglev_page.title %>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 | <%= render_maglev_alternate_links %>
19 | <%= maglev_live_preview_client_javascript_tag %>
20 |
21 |
22 |
23 | <%= render_maglev_sections %>
24 |
25 |
26 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/theme/sections/features/showcase.html.erb:
--------------------------------------------------------------------------------
1 | <%= tag.section class: 'showcase', data: section.tag_data do %>
2 |
3 | <%= tag.h2 class: 'display-4', data: section.settings.title.tag_data do %>
4 | <%= raw section.settings.title %>
5 | <% end %>
6 |
7 | <% section.blocks.each do |block| %>
8 | <%= tag.li data: block.tag_data do %>
9 | <%= tag.h3 data: block.settings.title.tag_data do %>
10 | <%= block.settings.title %>
11 | <% end %>
12 | <%= image_tag block.settings.image.url, alt: block.settings.image.alt_text, data: block.settings.image.tag_data %>
13 | <% end %>
14 | <% end %>
15 |
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/app/views/theme/sections/headers/jumbotron.html.erb:
--------------------------------------------------------------------------------
1 | class="jumbotron">
2 |
3 |
class="display-3"><%= raw section.settings.title %>
4 |
><%= raw section.settings.body %>
5 |
6 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_PATH = File.expand_path('../config/application', __dir__)
5 | require_relative '../config/boot'
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require_relative '../config/boot'
5 | require 'rake'
6 | Rake.application.run
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative 'config/environment'
6 |
7 | run Rails.application
8 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
5 |
6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
8 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative 'application'
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # ActiveSupport::Reloader.to_prepare do
6 | # ApplicationController.renderer.defaults.merge!(
7 | # http_host: 'example.org',
8 | # https: false
9 | # )
10 | # end
11 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = '1.0'
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
9 | # Rails.backtrace_cleaner.remove_silencers!
10 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Specify a serializer for the signed and encrypted cookie jars.
6 | # Valid options are :json, :marshal, and :hybrid.
7 | Rails.application.config.action_dispatch.cookies_serializer = :json
8 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += [:password]
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Add new inflection rules using the following format. Inflections
6 | # are locale specific, and you may define rules for as many different
7 | # locales as you wish. All of these examples are active by default:
8 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
9 | # inflect.plural /^(ox)$/i, '\1en'
10 | # inflect.singular /^(ox)en/i, '\1'
11 | # inflect.irregular 'person', 'people'
12 | # inflect.uncountable %w( fish sheep )
13 | # end
14 |
15 | # These inflection rules are supported but not enabled by default:
16 | ActiveSupport::Inflector.inflections(:en) do |inflect|
17 | # inflect.acronym 'RESTful'
18 | end
19 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/maglev.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Maglev.configure do |config|
4 | config.uploader = :active_storage
5 |
6 | config.collections = {
7 | products: {
8 | model: 'Product',
9 | fields: {
10 | label: :name,
11 | image: :thumbnail_url
12 | }
13 | }
14 | }
15 |
16 | config.default_site_locales = [{ label: 'English', prefix: 'en' }, { label: 'French', prefix: 'fr' }]
17 |
18 | config.static_pages = [
19 | {
20 | title: { en: 'Products', fr: 'Produits' },
21 | path: { en: 'products', fr: 'fr/produits' }
22 | },
23 | {
24 | title: { en: 'Authentication', fr: 'Authentification' },
25 | path: { en: 'sign-in', fr: 'fr/se-connecter' }
26 | }
27 | ]
28 |
29 | config.reserved_paths = %w[products sign-in search posts/*]
30 | end
31 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Add new mime types for use in respond_to blocks:
6 | # Mime::Type.register "text/richtext", :rtf
7 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # This file contains settings for ActionController::ParamsWrapper which
6 | # is enabled by default.
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9 | ActiveSupport.on_load(:action_controller) do
10 | wrap_parameters format: [:json]
11 | end
12 |
13 | # To enable root element in JSON for ActiveRecord objects.
14 | # ActiveSupport.on_load(:active_record) do
15 | # self.include_root_in_json = true
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | hello: "Bonjour le monde"
3 | lang:
4 | en: "English"
5 | fr: "Français"
6 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/config/spring.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Spring.watch(
4 | '.ruby-version',
5 | '.rbenv-vars',
6 | 'tmp/restart.txt',
7 | 'tmp/caching-dev.txt'
8 | )
9 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/db/migrate/20200824091231_create_accounts.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateAccounts < ActiveRecord::Migration[6.0]
4 | def change
5 | create_table :accounts do |t|
6 | t.string :email
7 | t.string :password_digest
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/db/migrate/20210129103814_create_active_storage_variant_records.active_storage.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This migration comes from active_storage (originally 20191206030411)
4 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
5 | def change
6 | create_table :active_storage_variant_records do |t|
7 | t.belongs_to :blob, null: false, index: false
8 | t.string :variation_digest, null: false
9 |
10 | t.index %i[blob_id variation_digest], name: 'index_active_storage_variant_records_uniqueness', unique: true
11 | t.foreign_key :active_storage_blobs, column: :blob_id
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/db/migrate/20210819081156_create_products.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateProducts < ActiveRecord::Migration[6.1]
4 | def change
5 | create_table :products do |t|
6 | t.string :name
7 | t.string :sku, unique: true
8 | t.float :price
9 | t.boolean :sold_out, default: false
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/log/.keep
--------------------------------------------------------------------------------
/spec/legacy_dummy/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-import'),
4 | require('postcss-flexbugs-fixes'),
5 | require('postcss-preset-env')({
6 | autoprefixer: {
7 | flexbox: 'no-2009'
8 | },
9 | stage: 3
10 | })
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/spec/legacy_dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/spec/legacy_dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/spec/legacy_dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maglevhq/maglev-core/f892a5f466029cc75a6d18f8f0119a789d4f0d50/spec/legacy_dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/lib/generators/maglev/theme_generator_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 | require 'generator_spec'
5 | require 'generators/maglev/theme_generator'
6 |
7 | describe Maglev::ThemeGenerator, type: :generator do
8 | destination File.expand_path('../tmp', __dir__)
9 |
10 | before(:all) do
11 | prepare_destination
12 | run_generator
13 | end
14 |
15 | it 'creates the YAML file to describe the theme and its default HTML/ERB layout' do
16 | assert_file 'app/theme/theme.yml', /id: "theme"/
17 | assert_file 'app/views/theme/layout.html.erb'
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/lib/i18n_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe Maglev::I18n do
6 | it 'has a default locale' do
7 | expect(described_class.current_locale).to eq(:en)
8 | end
9 |
10 | describe 'with_locale' do
11 | it 'temporarily changes locale' do
12 | expect(described_class.current_locale).to eq(:en)
13 | described_class.with_locale(:es) do
14 | expect(described_class.current_locale).to eq(:es)
15 | end
16 | expect(described_class.current_locale).to eq(:en)
17 | end
18 | end
19 |
20 | it 'disallows setting an unavailable locale' do
21 | expect { described_class.current_locale = :fr }.to raise_error Maglev::I18n::UnavailableLocaleError
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/lib/maglev/theme_filesystem_loader_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::ThemeFilesystemLoader do
6 | describe '#call' do
7 | # rubocop:disable Lint/UnusedBlockArgument
8 | let(:fetch_section_screenshot_path) { ->(theme:, section:, absolute:) { path } }
9 | # rubocop:enable Lint/UnusedBlockArgument
10 |
11 | subject { described_class.new(fetch_section_screenshot_path).call(Pathname.new(path)) }
12 |
13 | context 'There are 2 sections with the same id in different categories' do
14 | let(:path) { File.expand_path('./spec/fixtures/themes/theme_with_sections_with_same_id') }
15 |
16 | it 'raises a DuplicateSectionDefinition error' do
17 | expect { subject }.to raise_error(Maglev::Errors::DuplicateSectionDefinition)
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/models/maglev/page/search_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe Maglev::Page, type: :model do
6 | describe 'search' do
7 | before do
8 | create(:page, title: 'Chicago', path: 'bands/chicago')
9 | create(:page, title: 'Boston', path: 'bands/boston')
10 | create(:page, title: 'San Francisco', path: 'cities/sf')
11 | end
12 |
13 | subject { described_class.search(q, 'en') }
14 |
15 | describe 'Given we search from the title' do
16 | let(:q) { 'boston' }
17 |
18 | it { expect(subject.all.map(&:title).sort).to eq ['Boston'] }
19 | end
20 |
21 | describe 'Given we search from the path' do
22 | let(:q) { 'bands' }
23 |
24 | it { expect(subject.all.map(&:title).sort).to eq %w[Boston Chicago] }
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/models/maglev/section/block_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::Section::Block do
6 | describe 'validation' do
7 | subject { block.valid? }
8 |
9 | let(:block) { build(:section_block) }
10 |
11 | it { is_expected.to eq true }
12 |
13 | context 'name is required' do
14 | let(:block) { build(:section_block, name: nil) }
15 |
16 | it { is_expected.to eq false }
17 | end
18 |
19 | context 'type is required' do
20 | let(:block) { build(:section_block, type: nil) }
21 |
22 | it { is_expected.to eq false }
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/maglev/theme_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::Theme do
6 | describe 'validation' do
7 | subject { theme.valid? }
8 |
9 | let(:theme) { build(:theme) }
10 |
11 | it { is_expected.to eq true }
12 |
13 | context 'id is required' do
14 | let(:theme) { build(:theme, id: nil) }
15 |
16 | it { is_expected.to eq false }
17 | end
18 |
19 | context 'name is required' do
20 | let(:theme) { build(:theme, name: nil) }
21 |
22 | it { is_expected.to eq false }
23 | end
24 | end
25 |
26 | describe 'settings' do
27 | subject { theme.style_settings }
28 |
29 | let(:theme) { build(:theme) }
30 |
31 | it 'returns the settings (definition) of the theme' do
32 | expect(subject.map(&:id)).to eq(%w[primary_color font_name])
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/requests/maglev/admin/sections/screenshots_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'Maglev::Admin::Sections::ScreenshotsController', type: :request do
6 | let(:service) { double('PersistSectionScreenshot') }
7 |
8 | before do
9 | allow(Maglev).to receive(:services).and_return(double('AppContainer', persist_section_screenshot: service))
10 | end
11 |
12 | it 'calls the take_section_screenshot service' do
13 | expect(service).to receive(:call).with(
14 | section_id: 'jumbotron',
15 | base64_image: 'data:image/png;base64,bodyofthepngfile'
16 | )
17 | post '/maglev/admin/sections/jumbotron/screenshots',
18 | params: { screenshot: { base64_image: 'data:image/png;base64,bodyofthepngfile' } }
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/requests/maglev/admin/themes_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'Maglev::Admin::ThemesController', type: :request do
6 | it 'prints the information about the local theme + lists the sections of the first category' do
7 | get '/maglev/admin/theme'
8 | expect(response.body).to include('My simple theme')
9 | expect(response.body).to include('Jumbotron')
10 | expect(response.body).not_to include('Showcase')
11 | end
12 |
13 | it 'lists the sections of another category' do
14 | get '/maglev/admin/theme?category_id=features'
15 | expect(response.body).to include('My simple theme')
16 | expect(response.body).not_to include('Jumbotron')
17 | expect(response.body).to include('Showcase')
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/requests/maglev/assets/active_storage_proxy_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'Maglev::Assets::ActiveStorageProxyController' do
6 | let(:asset) { create(:asset) }
7 |
8 | it 'allows retrieval by id' do
9 | get "/maglev/assets/#{asset.id}/gibberish"
10 | expect(response.body).to eq(IO.binread(file_fixture('asset.jpg')))
11 | expect(response.headers['cache-control']).to match('max-age=3155695200, public')
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/requests/maglev/assets/proxy_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'Maglev::Assets::ActiveStorageProxyController' do
6 | let(:asset) { create(:asset) }
7 |
8 | it 'allows retrieval by id' do
9 | get "/simple-assets/#{asset.id}"
10 | expect(response.body).to eq(IO.binread(file_fixture('asset.jpg')))
11 | expect(response.headers['cache-control']).to eq('max-age=3600, public')
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/requests/maglev/dashboard_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'Maglev::DashboardController', type: :request do
6 | it 'redirects to the theme page instead' do
7 | get '/maglev'
8 | expect(response).to redirect_to('/maglev/admin')
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/requests/maglev/live_preview_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | RSpec.describe 'live-preview-client.js', type: :request do
6 | describe 'GET /maglev/live-preview-client.js' do
7 | it 'serves the JS file' do
8 | get '/maglev/live-preview-client.js'
9 | expect(response.headers['Location']).to include('http://www.example.com/maglev-assets-test/assets/live-preview-rails-client')
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/services/maglev/app_container_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::AppContainer do
6 | let(:controller) { double('ApplicationController', site_preview_path: '/maglev/preview') }
7 | let(:context) { Maglev::ServiceContext.new(controller: controller, rendering_mode: :editor) }
8 | let(:container) { Maglev.services(context: context, config: 'Hello world') }
9 |
10 | it 'returns the base url' do
11 | expect(container.get_base_url.call).to eq '/maglev/preview'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/services/maglev/fetch_section_screenshot_url_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::FetchSectionScreenshotUrl do
6 | subject { service.call(section: section) }
7 |
8 | let(:screenshot_path) { '/theme/jumbotron.png' }
9 | let(:fetch_section_screenshot_path) { instance_double('FetchSectionScreenshotPath', call: screenshot_path) }
10 | let(:service) { described_class.new(fetch_section_screenshot_path: fetch_section_screenshot_path) }
11 | let(:section) { instance_double('Section', id: 'jumbotron', screenshot_timestamp: 42) }
12 |
13 | it 'returns the url to the screenshot of the section' do
14 | expect(subject).to eq '/theme/jumbotron.png?42'
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/services/maglev/fetch_sections_path_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::FetchSectionsPath do
6 | subject { service.call }
7 |
8 | let(:service) { described_class.new }
9 |
10 | it 'returns the local theme' do
11 | expect(subject).to eq 'theme'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/services/maglev/fetch_site_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::FetchSite do
6 | subject { service.call }
7 |
8 | let!(:site) { create(:site) }
9 | let(:service) { described_class.new }
10 |
11 | it 'returns the first site' do
12 | expect(subject.name).to eq 'My awesome site'
13 | end
14 |
15 | it 'sets up the available locales' do
16 | subject
17 | expect(Maglev::I18n.available_locales).to eq(%i[en fr])
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/services/maglev/fetch_style_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::FetchStyle do
6 | subject { service.call(site: site, theme: theme) }
7 |
8 | let!(:site) { create(:site) }
9 | let!(:theme) { build(:theme) }
10 | let(:service) { described_class.new }
11 |
12 | context 'the site has no style settings' do
13 | it 'uses the default values from the theme' do
14 | expect(subject.primary_color).to eq('#f00')
15 | expect(subject.font_name).to eq('comic sans')
16 | end
17 | end
18 |
19 | context 'the site has style settings filled by the editor' do
20 | let!(:site) { create(:site, :with_style) }
21 |
22 | it 'uses the values from the site model' do
23 | expect(subject.primary_color).to eq('#ff00ff')
24 | expect(subject.font_name).to eq('roboto')
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/services/maglev/fetch_theme_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::FetchTheme do
6 | subject { service.call }
7 |
8 | let(:service) { described_class.new }
9 |
10 | it 'returns the local theme' do
11 | expect(subject.name).to eq 'My simple theme'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/services/maglev/get_page_section_names_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | describe Maglev::GetPageSectionNames do
6 | subject { service.call(page: page) }
7 |
8 | let(:theme) { build(:theme) }
9 | let(:service) { described_class.new(fetch_theme: double('FetchTheme', call: theme)) }
10 |
11 | context 'the page has no sections' do
12 | let(:page) { build(:page, sections: []) }
13 |
14 | it 'returns an empty array' do
15 | expect(subject).to eq([])
16 | end
17 | end
18 |
19 | context 'the page has a couple of sections' do
20 | let(:page) { build(:page).tap { |page| page.prepare_sections(theme) } }
21 |
22 | it 'returns an array of hashes containing the id and name of a page section' do
23 | expect(subject).to match([a_hash_including(name: 'Jumbotron'), a_hash_including(name: 'Showcase')])
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/support/api_authentication.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Maglev
4 | module SpecHelpers
5 | module ApiAuthentication
6 | def api_sign_in
7 | allow(Maglev.config).to receive(:is_authenticated).and_return(->(_site) { true })
8 | get '/maglev/editor'
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/template.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | if Rails.version.start_with?('7.') && RUBY_VERSION.start_with?('2.')
4 | puts "🚨 We're sorry but Maglev works with Rails 7 and Ruby 3.x.\nPlease install Ruby 3."
5 | exit
6 | end
7 |
8 | # Puma 5.x can't serve files generated by Vite 🤷🏻
9 | gsub_file 'Gemfile', /gem "puma", "~> 5\.\d"/, 'gem "puma", "~> 6.4"'
10 |
11 | gem 'maglevcms', '~> 1.8.0'
12 | gem 'maglevcms-hyperui-kit', '~> 1.2.0'
13 | gem 'image_processing', '~> 1.2'
14 |
15 | after_bundle do
16 | rails_command 'db:create'
17 | rails_command 'active_storage:install'
18 |
19 | generate 'maglev:install'
20 | generate 'maglev:hyperui:install', '--force'
21 |
22 | rails_command 'maglev:create_site'
23 | end
24 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue2'
3 | import RubyPlugin from 'vite-plugin-ruby'
4 | import { createSvgPlugin } from 'vite-plugin-vue2-svg'
5 | import * as path from 'path'
6 |
7 | export default defineConfig({
8 | plugins: [
9 | RubyPlugin(),
10 | vue(),
11 | createSvgPlugin()
12 | ],
13 | resolve: {
14 | alias: {
15 | '@': path.resolve(__dirname, './app/frontend/editor')
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config'
2 | import * as path from 'path'
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | environment: 'jsdom'
8 | },
9 | resolve: {
10 | alias: {
11 | '@': path.resolve(__dirname, './app/frontend/editor'),
12 | },
13 | },
14 | })
--------------------------------------------------------------------------------