spree_page_builder 5.3.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +43 -0
- data/Rakefile +18 -0
- data/app/controllers/concerns/spree/admin/page_builder_concern.rb +45 -0
- data/app/controllers/concerns/spree/admin/storefront_breadcrumb_concern.rb +12 -0
- data/app/controllers/spree/admin/page_blocks_controller.rb +58 -0
- data/app/controllers/spree/admin/page_links_controller.rb +60 -0
- data/app/controllers/spree/admin/page_sections_controller.rb +82 -0
- data/app/controllers/spree/admin/pages_controller.rb +39 -0
- data/app/controllers/spree/admin/storefront_controller.rb +41 -0
- data/app/controllers/spree/admin/themes_controller.rb +87 -0
- data/app/helpers/spree/admin/page_builder_helper.rb +125 -0
- data/app/helpers/spree/admin/themes_helper.rb +9 -0
- data/app/jobs/spree/page_builder/products/touch_taxons_job_decorator.rb +15 -0
- data/app/jobs/spree/themes/duplicate_components_job.rb +59 -0
- data/app/jobs/spree/themes/screenshot_job.rb +81 -0
- data/app/models/concerns/spree/has_page_links.rb +53 -0
- data/app/models/spree/page.rb +188 -0
- data/app/models/spree/page_block.rb +73 -0
- data/app/models/spree/page_blocks/buttons.rb +29 -0
- data/app/models/spree/page_blocks/heading.rb +18 -0
- data/app/models/spree/page_blocks/image.rb +20 -0
- data/app/models/spree/page_blocks/link.rb +21 -0
- data/app/models/spree/page_blocks/mega_nav.rb +33 -0
- data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +32 -0
- data/app/models/spree/page_blocks/metafields.rb +18 -0
- data/app/models/spree/page_blocks/nav.rb +15 -0
- data/app/models/spree/page_blocks/newsletter_form.rb +18 -0
- data/app/models/spree/page_blocks/products/brand.rb +15 -0
- data/app/models/spree/page_blocks/products/buy_buttons.rb +24 -0
- data/app/models/spree/page_blocks/products/description.rb +18 -0
- data/app/models/spree/page_blocks/products/price.rb +18 -0
- data/app/models/spree/page_blocks/products/quantity_selector.rb +20 -0
- data/app/models/spree/page_blocks/products/share.rb +8 -0
- data/app/models/spree/page_blocks/products/title.rb +19 -0
- data/app/models/spree/page_blocks/products/variant_picker.rb +13 -0
- data/app/models/spree/page_blocks/subheading.rb +17 -0
- data/app/models/spree/page_blocks/text.rb +16 -0
- data/app/models/spree/page_builder/policy_decorator.rb +17 -0
- data/app/models/spree/page_builder/post_decorator.rb +17 -0
- data/app/models/spree/page_builder/product_decorator.rb +17 -0
- data/app/models/spree/page_builder/store_decorator.rb +46 -0
- data/app/models/spree/page_builder/taxon_decorator.rb +45 -0
- data/app/models/spree/page_link.rb +60 -0
- data/app/models/spree/page_section.rb +222 -0
- data/app/models/spree/page_sections/announcement_bar.rb +28 -0
- data/app/models/spree/page_sections/breadcrumbs.rb +12 -0
- data/app/models/spree/page_sections/collection_banner.rb +18 -0
- data/app/models/spree/page_sections/custom_code.rb +11 -0
- data/app/models/spree/page_sections/featured_posts.rb +45 -0
- data/app/models/spree/page_sections/featured_product.rb +50 -0
- data/app/models/spree/page_sections/featured_taxon.rb +90 -0
- data/app/models/spree/page_sections/featured_taxons.rb +45 -0
- data/app/models/spree/page_sections/footer.rb +101 -0
- data/app/models/spree/page_sections/header.rb +62 -0
- data/app/models/spree/page_sections/image_banner.rb +55 -0
- data/app/models/spree/page_sections/image_with_text.rb +65 -0
- data/app/models/spree/page_sections/main_password_footer.rb +18 -0
- data/app/models/spree/page_sections/main_password_header.rb +20 -0
- data/app/models/spree/page_sections/newsletter.rb +54 -0
- data/app/models/spree/page_sections/page_title.rb +19 -0
- data/app/models/spree/page_sections/post_details.rb +19 -0
- data/app/models/spree/page_sections/post_grid.rb +19 -0
- data/app/models/spree/page_sections/product_details.rb +53 -0
- data/app/models/spree/page_sections/product_grid.rb +13 -0
- data/app/models/spree/page_sections/related_products.rb +58 -0
- data/app/models/spree/page_sections/rich_text.rb +31 -0
- data/app/models/spree/page_sections/taxon_banner.rb +18 -0
- data/app/models/spree/page_sections/taxon_grid.rb +17 -0
- data/app/models/spree/page_sections/video.rb +107 -0
- data/app/models/spree/pages/account.rb +19 -0
- data/app/models/spree/pages/cart.rb +19 -0
- data/app/models/spree/pages/checkout.rb +15 -0
- data/app/models/spree/pages/custom.rb +38 -0
- data/app/models/spree/pages/homepage.rb +72 -0
- data/app/models/spree/pages/login.rb +19 -0
- data/app/models/spree/pages/password.rb +59 -0
- data/app/models/spree/pages/post.rb +27 -0
- data/app/models/spree/pages/post_list.rb +36 -0
- data/app/models/spree/pages/product_details.rb +30 -0
- data/app/models/spree/pages/search_results.rb +43 -0
- data/app/models/spree/pages/shop_all.rb +40 -0
- data/app/models/spree/pages/taxon.rb +29 -0
- data/app/models/spree/pages/taxon_list.rb +41 -0
- data/app/models/spree/pages/wishlist.rb +15 -0
- data/app/models/spree/theme.rb +233 -0
- data/app/models/spree/themes/default.rb +97 -0
- data/app/services/spree/taxons/touch_featured_sections.rb +21 -0
- data/app/views/layouts/spree/page_builder.html.erb +46 -0
- data/app/views/spree/admin/dashboard/_store_preview.html.erb +38 -0
- data/app/views/spree/admin/page_blocks/_form_tab_buttons.html.erb +5 -0
- data/app/views/spree/admin/page_blocks/create.turbo_stream.erb +4 -0
- data/app/views/spree/admin/page_blocks/destroy.turbo_stream.erb +9 -0
- data/app/views/spree/admin/page_blocks/edit.html.erb +43 -0
- data/app/views/spree/admin/page_blocks/forms/_brand.html.erb +15 -0
- data/app/views/spree/admin/page_blocks/forms/_brand_logo.html.erb +4 -0
- data/app/views/spree/admin/page_blocks/forms/_buttons.html.erb +13 -0
- data/app/views/spree/admin/page_blocks/forms/_heading.html.erb +28 -0
- data/app/views/spree/admin/page_blocks/forms/_image.html.erb +10 -0
- data/app/views/spree/admin/page_blocks/forms/_link.html.erb +19 -0
- data/app/views/spree/admin/page_blocks/forms/_mega_nav.html.erb +14 -0
- data/app/views/spree/admin/page_blocks/forms/_mega_nav_with_subcategories.html.erb +9 -0
- data/app/views/spree/admin/page_blocks/forms/_metafields.html.erb +35 -0
- data/app/views/spree/admin/page_blocks/forms/_nav.html.erb +6 -0
- data/app/views/spree/admin/page_blocks/forms/_newsletter_form.html.erb +20 -0
- data/app/views/spree/admin/page_blocks/forms/_price.html.erb +15 -0
- data/app/views/spree/admin/page_blocks/forms/_share.html.erb +5 -0
- data/app/views/spree/admin/page_blocks/forms/_subheading.html.erb +26 -0
- data/app/views/spree/admin/page_blocks/forms/_text.html.erb +21 -0
- data/app/views/spree/admin/page_blocks/forms/_title.html.erb +20 -0
- data/app/views/spree/admin/page_blocks/forms/_variant_picker.html.erb +10 -0
- data/app/views/spree/admin/page_blocks/forms/products/_buy_buttons.html.erb +10 -0
- data/app/views/spree/admin/page_blocks/forms/products/_description.html.erb +0 -0
- data/app/views/spree/admin/page_blocks/forms/products/_quantity_selector.html.erb +10 -0
- data/app/views/spree/admin/page_blocks/move_higher.turbo_stream.erb +4 -0
- data/app/views/spree/admin/page_blocks/move_lower.turbo_stream.erb +4 -0
- data/app/views/spree/admin/page_blocks/show.html.erb +1 -0
- data/app/views/spree/admin/page_blocks/update.turbo_stream.erb +6 -0
- data/app/views/spree/admin/page_builder/_add_block.html.erb +22 -0
- data/app/views/spree/admin/page_builder/_color_palette.html.erb +17 -0
- data/app/views/spree/admin/page_builder/_color_picker.html.erb +26 -0
- data/app/views/spree/admin/page_builder/_header.html.erb +113 -0
- data/app/views/spree/admin/page_builder/_labeled_range_input.html.erb +10 -0
- data/app/views/spree/admin/page_builder/_pages_dropdown.html.erb +17 -0
- data/app/views/spree/admin/page_builder/_range_input.html.erb +12 -0
- data/app/views/spree/admin/page_builder/_sidebar.html.erb +12 -0
- data/app/views/spree/admin/page_builder/_sidebar_block.html.erb +30 -0
- data/app/views/spree/admin/page_builder/_sidebar_colors.html.erb +86 -0
- data/app/views/spree/admin/page_builder/_sidebar_fonts.html.erb +85 -0
- data/app/views/spree/admin/page_builder/_sidebar_section.html.erb +44 -0
- data/app/views/spree/admin/page_builder/_sidebar_sections.html.erb +38 -0
- data/app/views/spree/admin/page_builder/_sidebar_sections_toolbar.html.erb +180 -0
- data/app/views/spree/admin/page_links/_form.html.erb +10 -0
- data/app/views/spree/admin/page_links/_linkable_type_dropdown.html.erb +15 -0
- data/app/views/spree/admin/page_links/_list.html.erb +36 -0
- data/app/views/spree/admin/page_links/_refresh_theme_preview.turbo_stream.erb +5 -0
- data/app/views/spree/admin/page_links/create.turbo_stream.erb +10 -0
- data/app/views/spree/admin/page_links/destroy.turbo_stream.erb +15 -0
- data/app/views/spree/admin/page_links/edit.html.erb +27 -0
- data/app/views/spree/admin/page_links/update.turbo_stream.erb +14 -0
- data/app/views/spree/admin/page_sections/_form_tab_buttons.html.erb +8 -0
- data/app/views/spree/admin/page_sections/create.turbo_stream.erb +11 -0
- data/app/views/spree/admin/page_sections/destroy.turbo_stream.erb +16 -0
- data/app/views/spree/admin/page_sections/edit.html.erb +39 -0
- data/app/views/spree/admin/page_sections/fields/_background_color.html.erb +6 -0
- data/app/views/spree/admin/page_sections/fields/_border_bottom_width.html.erb +5 -0
- data/app/views/spree/admin/page_sections/fields/_border_color.html.erb +7 -0
- data/app/views/spree/admin/page_sections/fields/_border_top_width.html.erb +6 -0
- data/app/views/spree/admin/page_sections/fields/_bottom_padding.html.erb +5 -0
- data/app/views/spree/admin/page_sections/fields/_button.html.erb +15 -0
- data/app/views/spree/admin/page_sections/fields/_header_layout.html.erb +26 -0
- data/app/views/spree/admin/page_sections/fields/_height.html.erb +6 -0
- data/app/views/spree/admin/page_sections/fields/_text_color.html.erb +7 -0
- data/app/views/spree/admin/page_sections/fields/_top_padding.html.erb +6 -0
- data/app/views/spree/admin/page_sections/forms/_announcement_bar.html.erb +5 -0
- data/app/views/spree/admin/page_sections/forms/_brand_story.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_breadcrumbs.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_collection_banner.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_custom_code.html.erb +4 -0
- data/app/views/spree/admin/page_sections/forms/_featured_posts.html.erb +33 -0
- data/app/views/spree/admin/page_sections/forms/_featured_product.html.erb +4 -0
- data/app/views/spree/admin/page_sections/forms/_featured_taxon.html.erb +59 -0
- data/app/views/spree/admin/page_sections/forms/_featured_taxons.html.erb +13 -0
- data/app/views/spree/admin/page_sections/forms/_footer.html.erb +20 -0
- data/app/views/spree/admin/page_sections/forms/_header.html.erb +12 -0
- data/app/views/spree/admin/page_sections/forms/_image_banner.html.erb +13 -0
- data/app/views/spree/admin/page_sections/forms/_image_with_text.html.erb +28 -0
- data/app/views/spree/admin/page_sections/forms/_main_password_footer.html.erb +9 -0
- data/app/views/spree/admin/page_sections/forms/_main_password_header.erb +13 -0
- data/app/views/spree/admin/page_sections/forms/_newsletter.html.erb +15 -0
- data/app/views/spree/admin/page_sections/forms/_page_title.html.erb +4 -0
- data/app/views/spree/admin/page_sections/forms/_post_details.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_post_grid.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_product_details.html.erb +1 -0
- data/app/views/spree/admin/page_sections/forms/_product_grid.html.erb +1 -0
- data/app/views/spree/admin/page_sections/forms/_related_products.html.erb +32 -0
- data/app/views/spree/admin/page_sections/forms/_rich_text.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_taxon_banner.html.erb +0 -0
- data/app/views/spree/admin/page_sections/forms/_taxon_grid.html.erb +5 -0
- data/app/views/spree/admin/page_sections/forms/_video.html.erb +15 -0
- data/app/views/spree/admin/page_sections/move_higher.turbo_stream.erb +7 -0
- data/app/views/spree/admin/page_sections/move_lower.turbo_stream.erb +7 -0
- data/app/views/spree/admin/page_sections/new.html.erb +26 -0
- data/app/views/spree/admin/page_sections/remove_attachment.turbo_stream.erb +4 -0
- data/app/views/spree/admin/page_sections/restore_design_settings_to_defaults.turbo_stream.erb +4 -0
- data/app/views/spree/admin/page_sections/show.html.erb +1 -0
- data/app/views/spree/admin/page_sections/update.turbo_stream.erb +10 -0
- data/app/views/spree/admin/pages/_form.html.erb +20 -0
- data/app/views/spree/admin/pages/_table_header.html.erb +6 -0
- data/app/views/spree/admin/pages/_table_row.html.erb +28 -0
- data/app/views/spree/admin/pages/edit.html.erb +13 -0
- data/app/views/spree/admin/pages/filters.html.erb +7 -0
- data/app/views/spree/admin/pages/index.html.erb +11 -0
- data/app/views/spree/admin/pages/new.html.erb +1 -0
- data/app/views/spree/admin/storefront/edit.html.erb +89 -0
- data/app/views/spree/admin/themes/_theme.html.erb +56 -0
- data/app/views/spree/admin/themes/_theme_preview_image.html.erb +17 -0
- data/app/views/spree/admin/themes/edit.html.erb +14 -0
- data/app/views/spree/admin/themes/index.html.erb +80 -0
- data/app/views/spree/admin/themes/update.turbo_stream.erb +1 -0
- data/config/initializers/spree_admin_navigation.rb +46 -0
- data/config/initializers/spree_admin_partials.rb +3 -0
- data/config/locales/en.yml +42 -0
- data/config/routes.rb +43 -0
- data/db/migrate/20250120094216_create_page_builder_models.rb +78 -0
- data/db/migrate/20250305121352_remove_page_builder_indices.rb +11 -0
- data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +7 -0
- data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +10 -0
- data/lib/generators/spree/page_builder/install/install_generator.rb +23 -0
- data/lib/spree/page_builder/engine.rb +185 -0
- data/lib/spree/page_builder/testing_support/factories/page_block_factory.rb +22 -0
- data/lib/spree/page_builder/testing_support/factories/page_factory.rb +33 -0
- data/lib/spree/page_builder/testing_support/factories/page_link_factory.rb +7 -0
- data/lib/spree/page_builder/testing_support/factories/page_section_factory.rb +27 -0
- data/lib/spree/page_builder/testing_support/factories/theme_factory.rb +14 -0
- data/lib/spree/page_builder/testing_support/factories.rb +3 -0
- data/lib/spree/page_builder.rb +4 -0
- data/lib/spree_page_builder.rb +1 -0
- metadata +288 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Themes
|
|
3
|
+
class DuplicateComponentsJob < Spree::BaseJob
|
|
4
|
+
queue_as Spree.queues.themes
|
|
5
|
+
|
|
6
|
+
# We need to duplicate:
|
|
7
|
+
# 1. Theme files + sections with pageable_type Spree::Theme.
|
|
8
|
+
# 2. Duplicate pages + sections.
|
|
9
|
+
# 3. Adjust linkable on page links for Spree::PageSection and Spree::PageBlock parents and Spree::Page linkable.
|
|
10
|
+
|
|
11
|
+
def perform(theme_id, duplicated_theme_id)
|
|
12
|
+
theme = Spree::Theme.find(theme_id)
|
|
13
|
+
duplicated_theme = Spree::Theme.find(duplicated_theme_id)
|
|
14
|
+
|
|
15
|
+
ApplicationRecord.transaction do
|
|
16
|
+
duplicated_pages = duplicate_pages(theme, duplicated_theme)
|
|
17
|
+
|
|
18
|
+
# Duplicated links on pages and blocks have references to pages from the previous theme
|
|
19
|
+
# We need to look for a duplicated page and update the link with it
|
|
20
|
+
duplicate_layout_sections(theme, duplicated_theme)
|
|
21
|
+
adjust_page_links(theme, duplicated_theme, duplicated_pages)
|
|
22
|
+
|
|
23
|
+
duplicated_theme.update!(ready: true)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def duplicate_layout_sections(theme, duplicated_theme)
|
|
30
|
+
theme.layout_sections.each { |section| section.deep_clone(duplicated_theme) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def duplicate_pages(theme, duplicated_theme)
|
|
34
|
+
theme.pages.map { |page| page.duplicate(duplicated_theme) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def adjust_page_links(theme, duplicated_theme, duplicated_pages)
|
|
38
|
+
page_sections = Spree::PageSection.where(pageable: duplicated_pages)
|
|
39
|
+
layout_sections = duplicated_theme.layout_sections
|
|
40
|
+
all_section_ids = page_sections.ids + layout_sections.ids
|
|
41
|
+
|
|
42
|
+
all_page_blocks = Spree::PageBlock.where(section_id: all_section_ids)
|
|
43
|
+
|
|
44
|
+
all_parent_ids = all_section_ids + all_page_blocks.ids
|
|
45
|
+
all_page_links = Spree::PageLink.
|
|
46
|
+
includes(:linkable, parent: :pageable).
|
|
47
|
+
where(linkable_type: 'Spree::Page').
|
|
48
|
+
where(parent_id: all_parent_ids, linkable_id: theme.pages)
|
|
49
|
+
|
|
50
|
+
all_page_links.each do |page_link|
|
|
51
|
+
linkable_type = page_link.linkable.type
|
|
52
|
+
duplicated_page = duplicated_theme.pages.find_by(type: linkable_type)
|
|
53
|
+
|
|
54
|
+
page_link.update!(linkable: duplicated_page)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
|
|
4
|
+
module Spree
|
|
5
|
+
module Themes
|
|
6
|
+
class ScreenshotJob < Spree::BaseJob
|
|
7
|
+
queue_as Spree.queues.themes
|
|
8
|
+
|
|
9
|
+
BASE_SCREENSHOT_API_URL = 'https://shot.screenshotapi.net/v3/screenshot'.freeze
|
|
10
|
+
SCREENSHOT_OUTPUT = 'image'.freeze
|
|
11
|
+
SCREENSHOT_FILE_TYPE = 'png'.freeze
|
|
12
|
+
|
|
13
|
+
def perform(theme_id)
|
|
14
|
+
theme = Spree::Theme.find(theme_id)
|
|
15
|
+
|
|
16
|
+
Rails.logger.warn('Screenshot API token is missing') and return if screenshot_api_token.blank?
|
|
17
|
+
|
|
18
|
+
return if theme.screenshot.attached?
|
|
19
|
+
|
|
20
|
+
url = URI.encode_www_form_component("#{theme.store.url_or_custom_domain}?theme_id=#{theme_id}")
|
|
21
|
+
query_params = {
|
|
22
|
+
token: screenshot_api_token,
|
|
23
|
+
url: url,
|
|
24
|
+
output: SCREENSHOT_OUTPUT,
|
|
25
|
+
file_type: SCREENSHOT_FILE_TYPE,
|
|
26
|
+
retina: true,
|
|
27
|
+
enable_caching: true
|
|
28
|
+
}
|
|
29
|
+
query = "#{BASE_SCREENSHOT_API_URL}?#{query_params.to_query}"
|
|
30
|
+
|
|
31
|
+
# Send a GET request to the API
|
|
32
|
+
uri = URI.parse(query)
|
|
33
|
+
response = Net::HTTP.get_response(uri)
|
|
34
|
+
|
|
35
|
+
case response.code
|
|
36
|
+
when '301', '302'
|
|
37
|
+
redirect_uri = response['location']
|
|
38
|
+
redirect_uri = URI.parse(redirect_uri)
|
|
39
|
+
response = Net::HTTP.get_response(redirect_uri)
|
|
40
|
+
save_screenshot(theme, response)
|
|
41
|
+
when '200'
|
|
42
|
+
save_screenshot(theme, response)
|
|
43
|
+
else
|
|
44
|
+
Rails.error.report('Screenshot API returned non-200 status code')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def screenshot_api_token
|
|
51
|
+
@screenshot_api_token ||= Spree.screenshot_api_token
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def save_screenshot(theme, response)
|
|
55
|
+
temp_file_path = "theme-screenshot-#{theme.id}.png"
|
|
56
|
+
|
|
57
|
+
# Create a temporary file
|
|
58
|
+
File.open(temp_file_path, "wb") do |file|
|
|
59
|
+
file.write(response.body)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Open the file for reading to attach it
|
|
63
|
+
file = File.open(temp_file_path, "rb")
|
|
64
|
+
|
|
65
|
+
# Attach the file
|
|
66
|
+
theme.screenshot.attach(
|
|
67
|
+
io: file,
|
|
68
|
+
filename: "theme-screenshot-#{theme.id}.#{SCREENSHOT_FILE_TYPE}",
|
|
69
|
+
content_type: "image/#{SCREENSHOT_FILE_TYPE}"
|
|
70
|
+
)
|
|
71
|
+
theme.save!
|
|
72
|
+
|
|
73
|
+
# Close the file before attempting to delete it
|
|
74
|
+
file.close
|
|
75
|
+
|
|
76
|
+
# Remove the temporary file
|
|
77
|
+
File.delete(temp_file_path) if File.exist?(temp_file_path)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module HasPageLinks
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
has_many :links, -> { order(:position).includes(:linkable) }, class_name: 'Spree::PageLink', as: :parent, dependent: :destroy, inverse_of: :parent
|
|
7
|
+
alias page_links links
|
|
8
|
+
|
|
9
|
+
after_create :create_default_links, unless: :do_not_create_links
|
|
10
|
+
|
|
11
|
+
attribute :do_not_create_links, :boolean, default: false
|
|
12
|
+
attr_accessor :default_links
|
|
13
|
+
|
|
14
|
+
def allowed_linkable_types
|
|
15
|
+
[
|
|
16
|
+
[Spree.t(:page), 'Spree::Page'],
|
|
17
|
+
[Spree.t(:product), 'Spree::Product'],
|
|
18
|
+
[Spree.t(:post), 'Spree::Post'],
|
|
19
|
+
[Spree.t(:taxon), 'Spree::Taxon'],
|
|
20
|
+
[Spree.t(:policy), 'Spree::Policy'],
|
|
21
|
+
[Spree.t(:url), nil]
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default_linkable_type
|
|
26
|
+
'Spree::Page'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def theme_or_parent
|
|
30
|
+
theme.preview? ? theme.parent : theme
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def default_linkable_resource
|
|
34
|
+
@default_linkable_resource ||= theme_or_parent.pages.find_by(type: 'Spree::Pages::Homepage')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def default_links
|
|
38
|
+
@default_links.presence || []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def links_available?
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_default_links
|
|
46
|
+
default_links.each do |link|
|
|
47
|
+
link.parent = self
|
|
48
|
+
link.save!
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class Page < Spree.base_class
|
|
3
|
+
include Spree::Previewable
|
|
4
|
+
include Spree::Linkable
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Magic methods
|
|
8
|
+
#
|
|
9
|
+
extend FriendlyId
|
|
10
|
+
friendly_id :slug_candidates, use: [:slugged, :scoped, :history], scope: :pageable
|
|
11
|
+
acts_as_paranoid
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Associations
|
|
15
|
+
#
|
|
16
|
+
belongs_to :pageable, polymorphic: true # this can be either Store or Theme
|
|
17
|
+
has_many :sections, -> { order(position: :asc) }, class_name: 'Spree::PageSection', dependent: :destroy_async, as: :pageable
|
|
18
|
+
alias page_sections sections
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Validations
|
|
22
|
+
#
|
|
23
|
+
validates :name, presence: true
|
|
24
|
+
validates :pageable, presence: true
|
|
25
|
+
validates :slug, uniqueness: { scope: :pageable, conditions: -> { where(deleted_at: nil).where.not(slug: nil) } }
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Callbacks
|
|
29
|
+
#
|
|
30
|
+
before_validation :set_name
|
|
31
|
+
after_create :create_default_sections, unless: :duplicating
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Scopes
|
|
35
|
+
#
|
|
36
|
+
scope :linkable, lambda {
|
|
37
|
+
where(type: [
|
|
38
|
+
'Spree::Pages::Homepage',
|
|
39
|
+
'Spree::Pages::ShopAll',
|
|
40
|
+
'Spree::Pages::PostList',
|
|
41
|
+
'Spree::Pages::Custom',
|
|
42
|
+
'Spree::Pages::Account',
|
|
43
|
+
'Spree::Pages::Login'
|
|
44
|
+
])
|
|
45
|
+
}
|
|
46
|
+
scope :product_details, -> { where(type: 'Spree::Pages::ProductDetails') }
|
|
47
|
+
scope :standard, -> { where.not(type: 'Spree::Pages::Custom') }
|
|
48
|
+
scope :custom, -> { where(type: 'Spree::Pages::Custom') }
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Virtual attributes
|
|
52
|
+
#
|
|
53
|
+
attribute :duplicating, :boolean, default: false
|
|
54
|
+
|
|
55
|
+
def icon_name
|
|
56
|
+
'file-earmark-richtext'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def store
|
|
60
|
+
if pageable.is_a?(Spree::Store)
|
|
61
|
+
pageable
|
|
62
|
+
else
|
|
63
|
+
pageable.store
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_preview
|
|
68
|
+
return self if preview? # no need to create a preview for the preview
|
|
69
|
+
|
|
70
|
+
ActiveRecord::Base.connected_to(role: :writing) do
|
|
71
|
+
ApplicationRecord.transaction do
|
|
72
|
+
new_preview = dup
|
|
73
|
+
new_preview.parent = self
|
|
74
|
+
new_preview.duplicating = true
|
|
75
|
+
new_preview.save!
|
|
76
|
+
|
|
77
|
+
sections.includes(:links, { asset_attachment: :blob }, { blocks: [:rich_text_text, :links] }).each do |section|
|
|
78
|
+
section.deep_clone(new_preview)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
new_preview
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Promotes the page preview to the main page
|
|
87
|
+
def promote
|
|
88
|
+
return unless preview?
|
|
89
|
+
|
|
90
|
+
ApplicationRecord.transaction do
|
|
91
|
+
old_page = parent
|
|
92
|
+
old_page.page_links.update_all(linkable_id: id)
|
|
93
|
+
update(parent: nil) # clear reference to the old page
|
|
94
|
+
Spree::Page.find(old_page.id).destroy # destroy the old page with their other previews, etc.
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def default_sections
|
|
99
|
+
[]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def customizable?
|
|
103
|
+
false
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def linkable?
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def layout_sections?
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def preview_url(theme_preview = nil, page_preview = nil)
|
|
115
|
+
return if page_builder_url.blank?
|
|
116
|
+
|
|
117
|
+
"#{page_builder_url}?#{url_query_params(theme_preview, page_preview).to_query}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def display_name
|
|
121
|
+
if custom?
|
|
122
|
+
name
|
|
123
|
+
else
|
|
124
|
+
name.titleize
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def custom?
|
|
129
|
+
type == 'Spree::Pages::Custom'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def duplicate(target_pageable)
|
|
133
|
+
duplicated_page = dup
|
|
134
|
+
duplicated_page.duplicating = true
|
|
135
|
+
duplicated_page.pageable = target_pageable
|
|
136
|
+
duplicated_page.save!
|
|
137
|
+
|
|
138
|
+
sections_scope = sections.includes(:links, :blocks, { asset_attachment: :blob },
|
|
139
|
+
{ blocks: [:rich_text_text, :links, { asset_attachment: :blob }] })
|
|
140
|
+
sections_scope.each { |section| section.deep_clone(duplicated_page) }
|
|
141
|
+
|
|
142
|
+
duplicated_page
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def theme
|
|
146
|
+
@theme ||= if pageable.is_a?(Spree::Theme)
|
|
147
|
+
pageable
|
|
148
|
+
else
|
|
149
|
+
pageable.default_theme
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def create_default_sections
|
|
156
|
+
default_sections.each do |section|
|
|
157
|
+
section.pageable = self
|
|
158
|
+
section.save!
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def url_query_params(theme_preview, page_preview)
|
|
163
|
+
theme_preview_cache = theme_preview.updated_at.to_i.to_s + rand(999_999).to_s if theme_preview
|
|
164
|
+
|
|
165
|
+
{
|
|
166
|
+
theme_id: theme.id,
|
|
167
|
+
page_preview_id: page_preview&.id,
|
|
168
|
+
theme_preview_id: theme_preview&.id,
|
|
169
|
+
theme_preview_cache: theme_preview_cache
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def set_name
|
|
174
|
+
return if custom?
|
|
175
|
+
return if name.present?
|
|
176
|
+
|
|
177
|
+
self.name = self.class.name.demodulize
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def should_generate_new_friendly_id?
|
|
181
|
+
name_changed? && custom?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def page_builder_url_exists?(path)
|
|
185
|
+
Spree::Core::Engine.routes.url_helpers.respond_to?(path)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class PageBlock < Spree.base_class
|
|
3
|
+
include Spree::HasPageLinks
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Magic methods
|
|
7
|
+
#
|
|
8
|
+
acts_as_paranoid
|
|
9
|
+
acts_as_list scope: :section
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Associations
|
|
13
|
+
#
|
|
14
|
+
belongs_to :section, class_name: 'Spree::PageSection', inverse_of: :blocks, touch: true
|
|
15
|
+
delegate :store, :theme, to: :section
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Rich Text
|
|
19
|
+
#
|
|
20
|
+
has_rich_text :text
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Attachments
|
|
24
|
+
#
|
|
25
|
+
has_one_attached :asset, service: Spree.public_storage_service_name
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Validations
|
|
29
|
+
#
|
|
30
|
+
validates :section, :type, :name, presence: true
|
|
31
|
+
validates :asset, content_type: Rails.application.config.active_storage.web_image_content_types
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Callbacks
|
|
35
|
+
#
|
|
36
|
+
before_validation :set_name_from_type, on: :create
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Preferences
|
|
40
|
+
#
|
|
41
|
+
TEXT_ALIGNMENT_DEFAULT = 'left'
|
|
42
|
+
CONTAINER_ALIGNMENT_DEFAULT = 'left'
|
|
43
|
+
SIZE_DEFAULT = 'medium'
|
|
44
|
+
WIDTH_DESKTOP_DEFAULT = 100
|
|
45
|
+
TOP_PADDING_DEFAULT = 0
|
|
46
|
+
BOTTOM_PADDING_DEFAULT = 0
|
|
47
|
+
|
|
48
|
+
preference :text_alignment, :string, default: -> { self.class::TEXT_ALIGNMENT_DEFAULT if defined?(self.class::TEXT_ALIGNMENT_DEFAULT) }
|
|
49
|
+
preference :container_alignment, :string, default: -> { self.class::CONTAINER_ALIGNMENT_DEFAULT if defined?(self.class::CONTAINER_ALIGNMENT_DEFAULT) }
|
|
50
|
+
preference :size, :string, default: -> { self.class::SIZE_DEFAULT if defined?(self.class::SIZE_DEFAULT) }
|
|
51
|
+
preference :width_desktop, :integer, default: -> { self.class::WIDTH_DESKTOP_DEFAULT if defined?(self.class::WIDTH_DESKTOP_DEFAULT) }
|
|
52
|
+
preference :top_padding, :integer, default: -> { self.class::TOP_PADDING_DEFAULT if defined?(self.class::TOP_PADDING_DEFAULT) }
|
|
53
|
+
preference :bottom_padding, :integer, default: -> { self.class::BOTTOM_PADDING_DEFAULT if defined?(self.class::BOTTOM_PADDING_DEFAULT) }
|
|
54
|
+
|
|
55
|
+
def display_name
|
|
56
|
+
name
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def form_partial_name
|
|
60
|
+
type.demodulize.underscore
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def icon_name
|
|
64
|
+
'fullscreen'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def set_name_from_type
|
|
70
|
+
self.name ||= type.demodulize.titleize
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class Buttons < Spree::PageBlock
|
|
4
|
+
include Spree::HasOneLink
|
|
5
|
+
|
|
6
|
+
TEXT_ALIGNMENT_DEFAULT = 'center'
|
|
7
|
+
|
|
8
|
+
preference :button_style_1, :string, default: 'primary'
|
|
9
|
+
preference :background_color, :string
|
|
10
|
+
preference :button_background_color, :string
|
|
11
|
+
preference :button_text_color, :string
|
|
12
|
+
|
|
13
|
+
# Without link there is no purpose of the button.
|
|
14
|
+
def link_destroyed(_link)
|
|
15
|
+
return unless page_links_count.zero?
|
|
16
|
+
|
|
17
|
+
destroy
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def display_name
|
|
21
|
+
Spree.t('page_blocks.buttons.display_name')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def icon_name
|
|
25
|
+
'click'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class Heading < Spree::PageBlock
|
|
4
|
+
SIZE_DEFAULT = 'large'
|
|
5
|
+
|
|
6
|
+
preference :text_color, :string
|
|
7
|
+
preference :background_color, :string
|
|
8
|
+
|
|
9
|
+
def icon_name
|
|
10
|
+
'heading'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def display_name
|
|
14
|
+
text.to_plain_text.truncate(30)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class Image < Spree::PageBlock
|
|
4
|
+
include Spree::HasImageAltText
|
|
5
|
+
|
|
6
|
+
alias image asset
|
|
7
|
+
|
|
8
|
+
TOP_PADDING_DEFAULT = 20
|
|
9
|
+
BOTTOM_PADDING_DEFAULT = 20
|
|
10
|
+
|
|
11
|
+
preference :height, :integer, default: 64
|
|
12
|
+
preference :mobile_height, :integer, default: 32
|
|
13
|
+
preference :image_alt, :string
|
|
14
|
+
|
|
15
|
+
def icon_name
|
|
16
|
+
'photo'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class Link < Spree::PageBlock
|
|
4
|
+
include Spree::HasOneLink
|
|
5
|
+
|
|
6
|
+
def icon_name
|
|
7
|
+
'menu-2'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def display_name
|
|
11
|
+
link&.label || Spree.t('page_blocks.link.display_name')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def link_destroyed(_link)
|
|
15
|
+
return unless page_links_count.zero?
|
|
16
|
+
|
|
17
|
+
destroy
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class MegaNav < Spree::PageBlock
|
|
4
|
+
include Spree::HasPageLinks
|
|
5
|
+
|
|
6
|
+
has_one :link, ->(ps) { ps.links.where(position: 1) }, class_name: 'Spree::PageLink', as: :parent, dependent: :destroy, inverse_of: :parent
|
|
7
|
+
accepts_nested_attributes_for :link
|
|
8
|
+
|
|
9
|
+
preference :featured_taxon_id, :string, default: ''
|
|
10
|
+
|
|
11
|
+
def featured_taxon
|
|
12
|
+
store.taxons.find_by(id: preferred_featured_taxon_id) if preferred_featured_taxon_id.present?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def icon_name
|
|
16
|
+
'layout-navbar-expand'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def display_name
|
|
20
|
+
link&.label || Spree.t(:mega_nav)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def default_links
|
|
24
|
+
@default_links.presence || [
|
|
25
|
+
Spree::PageLink.new(
|
|
26
|
+
label: Spree.t(:shop_all),
|
|
27
|
+
linkable: theme_or_parent.pages.find_by(type: 'Spree::Pages::ShopAll')
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class MegaNavWithSubcategories < Spree::PageBlock
|
|
4
|
+
preference :taxon_id, :string, default: ''
|
|
5
|
+
preference :featured_taxon_id, :string, default: ''
|
|
6
|
+
|
|
7
|
+
before_validation :make_taxon_id_valid
|
|
8
|
+
|
|
9
|
+
def taxon
|
|
10
|
+
store.taxons.find_by(id: preferred_taxon_id) if preferred_taxon_id.present?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def featured_taxon
|
|
14
|
+
store.taxons.find_by(id: preferred_featured_taxon_id) if preferred_featured_taxon_id.present?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def icon_name
|
|
18
|
+
'category'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def display_name
|
|
22
|
+
taxon&.name || Spree.t(:mega_nav_with_subcategories)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def make_taxon_id_valid
|
|
28
|
+
self.preferred_taxon_id = preferred_taxon_id.presence || store.taxons.where(depth: 1).first&.id
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class Metafields < Spree::PageBlock
|
|
4
|
+
RESOURCE_TYPE_DEFAULT = 'Spree::Product'
|
|
5
|
+
|
|
6
|
+
preference :resource_type, :string, default: -> { self.class::RESOURCE_TYPE_DEFAULT if defined?(self.class::RESOURCE_TYPE_DEFAULT) }
|
|
7
|
+
preference :metafield_definition_ids, :array, default: -> { available_metafield_definitions.pluck(:id) }
|
|
8
|
+
|
|
9
|
+
def icon_name
|
|
10
|
+
'list'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def available_metafield_definitions
|
|
14
|
+
@available_metafield_definitions ||= Spree::MetafieldDefinition.where(resource_type: preferred_resource_type).available_on_front_end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PageBlocks
|
|
3
|
+
class NewsletterForm < Spree::PageBlock
|
|
4
|
+
preference :button_text, :string, default: Spree.t('page_blocks.newsletter_form.button_text_default')
|
|
5
|
+
preference :button_text_color, :string
|
|
6
|
+
preference :button_background_color, :string
|
|
7
|
+
preference :button_style, :string, default: 'primary'
|
|
8
|
+
preference :placeholder, :string, default: Spree.t('page_blocks.newsletter_form.placeholder_default')
|
|
9
|
+
preference :justify, :string, default: 'center'
|
|
10
|
+
|
|
11
|
+
validates :preferred_button_style, inclusion: { in: ['primary', 'secondary'] }
|
|
12
|
+
|
|
13
|
+
def icon_name
|
|
14
|
+
'forms'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|