spree_easy_homepage 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|