spree_easy_homepage 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.travis.yml +51 -0
- data/Appraisals +28 -0
- data/Gemfile +18 -0
- data/LICENSE +26 -0
- data/README.md +124 -0
- data/Rakefile +22 -0
- data/app/assets/javascripts/spree/backend/product_autosearch.js +33 -0
- data/app/assets/javascripts/spree/backend/spree_easy_homepage.js +42 -0
- data/app/assets/javascripts/spree/frontend/spree_easy_homepage.js +2 -0
- data/app/assets/stylesheets/spree/backend/spree_easy_homepage.scss +12 -0
- data/app/concerns/spree/product_ids_processable.rb +19 -0
- data/app/controllers/spree/admin/base_controller_decorator.rb +13 -0
- data/app/controllers/spree/admin/home_sections_controller.rb +6 -0
- data/app/helpers/spree/admin/home_section_helper.rb +28 -0
- data/app/helpers/spree/easy_homepage_helper.rb +29 -0
- data/app/models/spree/home_section.rb +44 -0
- data/app/models/spree/home_section_product.rb +8 -0
- data/app/models/spree/product_decorator.rb +29 -0
- data/app/overrides/spree/admin/shared/sub_menu/_configuration.rb +8 -0
- data/app/presenter/spree/easy_homepage/product_presenter.rb +40 -0
- data/app/views/spree/admin/home_section_products/_line_item.js.erb +27 -0
- data/app/views/spree/admin/home_sections/_form.html.erb +48 -0
- data/app/views/spree/admin/home_sections/edit.html.erb +11 -0
- data/app/views/spree/admin/home_sections/index.html.erb +52 -0
- data/app/views/spree/admin/home_sections/new.html.erb +11 -0
- data/app/views/spree/admin/products/_autosearch.js.erb +16 -0
- data/app/views/spree/shared/_home_sections.html.erb +16 -0
- data/bin/rails +8 -0
- data/config/locales/en.yml +16 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20191228110047_create_spree_home_section.rb +10 -0
- data/db/migrate/20191228112655_create_spree_home_section_product.rb +11 -0
- data/gemfiles/spree_3_5.gemfile +10 -0
- data/gemfiles/spree_3_7.gemfile +11 -0
- data/gemfiles/spree_4_0.gemfile +10 -0
- data/gemfiles/spree_master.gemfile +10 -0
- data/lib/generators/spree_easy_homepage/install/install_generator.rb +29 -0
- data/lib/spree_easy_homepage.rb +4 -0
- data/lib/spree_easy_homepage/engine.rb +20 -0
- data/lib/spree_easy_homepage/version.rb +17 -0
- data/spree_easy_homepage.gemspec +47 -0
- metadata +408 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Spree
|
2
|
+
module ProductIdsProcessable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def add_product(product_id:)
|
6
|
+
products << Spree::Product.find(product_id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_products(product_ids:)
|
10
|
+
clean_ids(product_ids).each { |id| add_product(product_id: id) }
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def clean_ids(ids)
|
16
|
+
ids.reject(&:blank?)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
module BaseControllerDecorator
|
4
|
+
Spree::Admin::BaseController.include(Spree::Admin::HomeSectionHelper)
|
5
|
+
|
6
|
+
def self.prepended(base)
|
7
|
+
base.helper_method :parse_home_sections
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Spree::Admin::BaseController.prepend Spree::Admin::BaseControllerDecorator
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
module HomeSectionHelper
|
4
|
+
def parse_home_sections(home_section)
|
5
|
+
return [] unless home_section.section_products?
|
6
|
+
|
7
|
+
parse_products(products: home_section.products_by_position)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_products(products:)
|
11
|
+
products.map { |product| product_presenter(product: product).as_json }
|
12
|
+
end
|
13
|
+
|
14
|
+
def product_presenter(product:)
|
15
|
+
presenter(product).parse
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def presenter(product)
|
21
|
+
Spree::EasyHomepage::ProductPresenter.new(
|
22
|
+
product: product,
|
23
|
+
main_app: main_app
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spree
|
2
|
+
module EasyHomepageHelper
|
3
|
+
def fill_with_sections(partial: home_section_partial, **partial_args)
|
4
|
+
get_homesection_partial(partial: partial, **partial_args)
|
5
|
+
end
|
6
|
+
|
7
|
+
def home_sections
|
8
|
+
Spree::HomeSection.all
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def get_homesection_partial(partial: home_section_partial, **partial_args)
|
14
|
+
render(partial: partial, **partial_args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def home_section_partial
|
18
|
+
'spree/shared/home_sections'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Author note:
|
24
|
+
# This helper and the homesection partial view, violates a few OOP principles
|
25
|
+
# I plan to change this when I have time to do so.
|
26
|
+
# Although I don't plan to change the public interface,
|
27
|
+
# the private interface will.
|
28
|
+
# for example, instead of calling render,
|
29
|
+
# a new Presenter or Service object would instead be called
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Spree
|
2
|
+
class HomeSection < Spree::Base
|
3
|
+
include Spree::ProductIdsProcessable
|
4
|
+
acts_as_list
|
5
|
+
|
6
|
+
has_many :home_section_products, dependent: :destroy
|
7
|
+
has_many :products, through: :home_section_products, source: :product
|
8
|
+
|
9
|
+
alias_attribute :sections, :home_section_products
|
10
|
+
|
11
|
+
after_save :reset_section_products
|
12
|
+
|
13
|
+
attr_accessor :product_ids, :force_delete
|
14
|
+
|
15
|
+
def delete_sections
|
16
|
+
home_section_products.delete_all
|
17
|
+
end
|
18
|
+
|
19
|
+
def product_ids?
|
20
|
+
product_ids.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def force_delete?
|
24
|
+
force_delete.present?
|
25
|
+
end
|
26
|
+
|
27
|
+
def products_by_position
|
28
|
+
products.order('spree_home_section_products.position ASC')
|
29
|
+
end
|
30
|
+
|
31
|
+
def section_products?
|
32
|
+
home_section_products.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def reset_section_products
|
38
|
+
delete_sections if force_delete?
|
39
|
+
return unless product_ids?
|
40
|
+
|
41
|
+
add_products(product_ids: product_ids)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spree
|
2
|
+
module ProductDecorator
|
3
|
+
def self.prepended(base)
|
4
|
+
base.has_many :home_section_products,
|
5
|
+
class_name: 'Spree::HomeSectionProduct'
|
6
|
+
|
7
|
+
base.has_many :home_sections,
|
8
|
+
through: :home_section_products,
|
9
|
+
class_name: 'Spree::HomeSection'
|
10
|
+
end
|
11
|
+
|
12
|
+
def image_url(style: :product)
|
13
|
+
image = images.first
|
14
|
+
return if image.blank?
|
15
|
+
|
16
|
+
image.url(style)
|
17
|
+
end
|
18
|
+
|
19
|
+
def presenter_attributes
|
20
|
+
{
|
21
|
+
id: id,
|
22
|
+
name: name,
|
23
|
+
image: image_url(style: :mini)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Spree::Product.prepend Spree::ProductDecorator
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Deface::Override.new(
|
2
|
+
virtual_path: 'spree/admin/shared/sub_menu/_configuration',
|
3
|
+
name: 'home_section_menu_item',
|
4
|
+
insert_after: "erb[loud]:contains('configurations_sidebar_menu_item(Spree.t(:general_settings), spree.edit_admin_general_settings_path) if can? :manage, Spree::Config')",
|
5
|
+
text: "<%= configurations_sidebar_menu_item(
|
6
|
+
Spree.t(:home_section_menu_name),
|
7
|
+
spree.admin_home_sections_path) if can? :manage, Spree::HomeSection %>"
|
8
|
+
)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Spree
|
2
|
+
module EasyHomepage
|
3
|
+
class ProductPresenter
|
4
|
+
include Rails.application.routes.url_helpers
|
5
|
+
include Spree::Core::Engine.routes.url_helpers
|
6
|
+
|
7
|
+
attr_accessor :product
|
8
|
+
attr_reader :main_app
|
9
|
+
|
10
|
+
def initialize(product:, main_app:)
|
11
|
+
@product = product
|
12
|
+
@main_app = main_app
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
product_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def image_link(image_url)
|
22
|
+
return if image_url.nil?
|
23
|
+
|
24
|
+
main_app.url_for(image_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
edit_admin_product_path(product)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def product_attributes
|
34
|
+
parser_attributes = product.presenter_attributes
|
35
|
+
parser_attributes[:image] = image_link(parser_attributes[:image])
|
36
|
+
parser_attributes.merge(path: path)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<script type='text/template' id='product-search-lineitem'>
|
2
|
+
<tr data-hook='home_sections_products_row'>
|
3
|
+
<input name="home_section[product_ids][]" type="hidden" value="{{product.id}}" />
|
4
|
+
<td class='move-handle handle grabbable'>
|
5
|
+
<span class='icon icon-move'></span>
|
6
|
+
</td>
|
7
|
+
<td>
|
8
|
+
{{#if product.image }}
|
9
|
+
<img src='{{product.image}}' class='thumbnail mb-0' />
|
10
|
+
{{ else }}
|
11
|
+
<img src='<%= image_path('noimage/mini.png') %>' class='thumbnail mb-0' />
|
12
|
+
{{/if}}
|
13
|
+
</td>
|
14
|
+
<td>
|
15
|
+
<strong></strong>
|
16
|
+
{{#if product.path }}
|
17
|
+
<a target='_blank' href='{{product.path}}'>{{product.name}}</a>
|
18
|
+
{{else}}
|
19
|
+
{{product.name}}
|
20
|
+
{{/if}}
|
21
|
+
</td>
|
22
|
+
<td class='actions actions-2 text-right'>
|
23
|
+
<%= link_to_delete('#', {no_text: true, url: '#', data: {}}) %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
26
|
+
</script>
|
27
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<%= render partial: 'spree/shared/error_messages', locals: { target: @home_section } %>
|
2
|
+
|
3
|
+
<%= render partial: 'spree/admin/home_section_products/line_item', formats: :js %>
|
4
|
+
<%= render partial: 'spree/admin/products/autosearch', formats: :js %>
|
5
|
+
|
6
|
+
<div class="form-group">
|
7
|
+
<%= f.field_container :title, class: ['form-group'] do %>
|
8
|
+
<%= f.label :title %>
|
9
|
+
<%= f.text_field :title, class: 'form-control title' %>
|
10
|
+
<%= f.error_message_on :title %>
|
11
|
+
<% end %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="form-group">
|
15
|
+
<table class="table">
|
16
|
+
<thead data-hook="home_sections_product_header">
|
17
|
+
<tr>
|
18
|
+
<th></th>
|
19
|
+
<th><%= Spree.t(:image) %></th>
|
20
|
+
<th><%= plural_resource_name(Spree::Product) %></th>
|
21
|
+
<th class="actions"></th>
|
22
|
+
</tr>
|
23
|
+
</thead>
|
24
|
+
<%= content_tag :tbody,
|
25
|
+
id: "section-product-list",
|
26
|
+
data: {
|
27
|
+
home_sections: parse_home_sections(@home_section)
|
28
|
+
} do %>
|
29
|
+
|
30
|
+
<% end %>
|
31
|
+
</table>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<%= hidden_field_tag 'home_section[force_delete]', true %>
|
35
|
+
|
36
|
+
<div class="card mb-3">
|
37
|
+
<div class="card-header">
|
38
|
+
<h1 class="card-title mb-0 h5"><%= Spree.t(:add_product) %></h1>
|
39
|
+
</div>
|
40
|
+
<div class="card-body">
|
41
|
+
<div class="form-group">
|
42
|
+
<%= label_tag :product_search, Spree.t(:name_or_sku) %>
|
43
|
+
<%= hidden_field_tag :product_search, "",
|
44
|
+
class: "product-autosearch d-block w-100"
|
45
|
+
%>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% content_for :page_title do %>
|
2
|
+
<%= link_to Spree.t(plural_resource_name(Spree::HomeSection)), spree.admin_home_sections_url %> /
|
3
|
+
<%= @home_section.title %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<%= form_for [:admin, @home_section] do |f| %>
|
7
|
+
<fieldset>
|
8
|
+
<%= render partial: 'form', locals: { f: f } %>
|
9
|
+
<%= render partial: 'spree/admin/shared/edit_resource_links' %>
|
10
|
+
</fieldset>
|
11
|
+
<% end %>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<% content_for :page_title do %>
|
2
|
+
<%= plural_resource_name(Spree::HomeSection) %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<% content_for :page_actions do %>
|
6
|
+
<%= button_link_to Spree.t(:new_home_section), new_object_url, { class: "btn-success", icon: 'add', id: 'admin_new_home_section' } %>
|
7
|
+
<% end if can?(:create, Spree::HomeSection) %>
|
8
|
+
|
9
|
+
<% if @home_sections.any? %>
|
10
|
+
<table class="table sortable" data-sortable-link="<%= update_positions_admin_home_sections_url %>">
|
11
|
+
<thead data-hook="home_sections_header">
|
12
|
+
<tr>
|
13
|
+
<th></th>
|
14
|
+
<th><%= I18n.t(:title, scope: 'activerecord.attributes.spree/home_section') %></th>
|
15
|
+
<th><%= plural_resource_name(Spree::Product) %></th>
|
16
|
+
<th class="actions"></th>
|
17
|
+
</tr>
|
18
|
+
</thead>
|
19
|
+
<tbody>
|
20
|
+
<% @home_sections.each do |home_section| %>
|
21
|
+
<tr id="<%= spree_dom_id home_section %>" data-hook="home_sections_row">
|
22
|
+
<td class="move-handle handle grabbable">
|
23
|
+
<% if can? :edit, home_section %>
|
24
|
+
<span class="icon icon-move"></span>
|
25
|
+
<% end %>
|
26
|
+
</td>
|
27
|
+
<td><%= home_section.title %></td>
|
28
|
+
<td>
|
29
|
+
<%= home_section.products.count %>
|
30
|
+
<%= I18n.t('activerecord.models.spree/product', count: home_section.products.count) %>
|
31
|
+
<%= '-' if home_section.products.any? %>
|
32
|
+
<% home_section.products_by_position.each_with_index do |product, index| %>
|
33
|
+
<%= link_to "\"#{product.try(:name)}\"", edit_admin_product_path(product), target: '_blank' %>
|
34
|
+
<%= ', ' if index < home_section.products.count - 1 %>
|
35
|
+
<% end %>
|
36
|
+
</td>
|
37
|
+
<td class="actions actions-2 text-right">
|
38
|
+
<%= link_to_edit(home_section, no_text: true) if can?(:edit, home_section) %>
|
39
|
+
<%= link_to_delete(home_section, no_text: true) if can?(:destroy, home_section) %>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
<% end %>
|
43
|
+
</tbody>
|
44
|
+
</table>
|
45
|
+
<% else %>
|
46
|
+
<div class="no-objects-found alert alert-info">
|
47
|
+
<%= Spree.t(:no_resource_found, resource: plural_resource_name(Spree::HomeSection)) %>
|
48
|
+
<% if can?(:create, Spree::HomeSection) %>
|
49
|
+
- <%= link_to(Spree.t(:add_one), spree.new_admin_home_section_path(@home_section)) %>!
|
50
|
+
<% end %>
|
51
|
+
</div>
|
52
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% content_for :page_title do %>
|
2
|
+
<%= link_to plural_resource_name(Spree::HomeSection), spree.admin_home_sections_url %> /
|
3
|
+
<%= Spree.t(:new_home_section) %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<%= form_for [:admin, @home_section] do |f| %>
|
7
|
+
<fieldset>
|
8
|
+
<%= render partial: 'form', locals: { f: f } %>
|
9
|
+
<%= render partial: 'spree/admin/shared/new_resource_links' %>
|
10
|
+
</fieldset>
|
11
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<script type='text/template' id='product-search-result'>
|
2
|
+
<div class='variant-autocomplete-item media align-items-center'>
|
3
|
+
<figure class='variant-image media-left mb-0 mr-3'>
|
4
|
+
{{#if product.image }}
|
5
|
+
<img src='{{product.image}}' class="thumbnail mb-0" />
|
6
|
+
{{ else }}
|
7
|
+
<img src='<%= image_path("noimage/mini.png") %>' class="thumbnail mb-0" />
|
8
|
+
{{/if}}
|
9
|
+
</figure>
|
10
|
+
|
11
|
+
<div class='variant-details media-body'>
|
12
|
+
<h5 class="variant-name media-heading h6 mb-0">{{product.name}}</h5>
|
13
|
+
<strong><%= Spree.t(:sku) %>:</strong> {{product.sku}}
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</script>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% product_partial ||= 'spree/products/product' %>
|
2
|
+
<% product_locals ||= {} %>
|
3
|
+
|
4
|
+
<% home_sections.each do |section| %>
|
5
|
+
<div class="row text-center">
|
6
|
+
<div class="col-sm-12">
|
7
|
+
<h2> <%= section.title %> </h2>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<div class="row">
|
11
|
+
<% section.products_by_position.each do |product| %>
|
12
|
+
<% locals = {product: product, taxon: @taxon || nil}.merge(product_locals) %>
|
13
|
+
<%= render partial: product_partial, locals: locals %>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
data/bin/rails
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" from the root of your extension
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/spree_easy_homepage/engine', __FILE__)
|
6
|
+
|
7
|
+
require 'rails/all'
|
8
|
+
require 'rails/engine/commands'
|