spree_posts 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/LICENSE.md +9 -0
- data/README.md +59 -0
- data/Rakefile +23 -0
- data/app/controllers/spree/admin/post_categories_controller.rb +21 -0
- data/app/controllers/spree/admin/posts_controller.rb +41 -0
- data/app/finders/spree/posts/find.rb +137 -0
- data/app/helpers/spree/admin/posts_helper.rb +13 -0
- data/app/models/spree/post.rb +108 -0
- data/app/models/spree/post_category.rb +46 -0
- data/app/models/spree/store_decorator.rb +28 -0
- data/app/sorters/spree/posts/sort.rb +40 -0
- data/app/views/spree/admin/post_categories/_form.html.erb +6 -0
- data/app/views/spree/admin/post_categories/_table_header.html.erb +5 -0
- data/app/views/spree/admin/post_categories/_table_row.html.erb +9 -0
- data/app/views/spree/admin/post_categories/edit.html.erb +1 -0
- data/app/views/spree/admin/post_categories/index.html.erb +10 -0
- data/app/views/spree/admin/post_categories/new.html.erb +1 -0
- data/app/views/spree/admin/posts/_extra_actions.html.erb +1 -0
- data/app/views/spree/admin/posts/_form.html.erb +54 -0
- data/app/views/spree/admin/posts/_table_header.html.erb +7 -0
- data/app/views/spree/admin/posts/_table_row.html.erb +22 -0
- data/app/views/spree/admin/posts/edit.html.erb +5 -0
- data/app/views/spree/admin/posts/filters.html.erb +39 -0
- data/app/views/spree/admin/posts/index.html.erb +10 -0
- data/app/views/spree/admin/posts/new.html.erb +1 -0
- data/app/views/spree/admin/shared/_posts_tabs.html.erb +8 -0
- data/config/initializers/spree.rb +121 -0
- data/config/locales/en.yml +15 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +33 -0
- data/lib/generators/spree_posts/install/install_generator.rb +20 -0
- data/lib/spree_posts/configuration.rb +13 -0
- data/lib/spree_posts/engine.rb +24 -0
- data/lib/spree_posts/factories.rb +28 -0
- data/lib/spree_posts/version.rb +7 -0
- data/lib/spree_posts.rb +12 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c7201669bde6f132f7a639b35712d2cb84fef08d14d683cffb1475d080b710b3
|
|
4
|
+
data.tar.gz: 27b670664530c0f01c555cfe6f337492c114b1c2d043742907aa3c23ccfd5b31
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f94ad0b4715aa0d9bbbf2952d5e333a3483739f715afcdefbff12dc05ac7535eb15bd0a3ceeaea4dfc2a4f04d3fd2a17a1712f0b828cfb431b3deae98a8ddf45
|
|
7
|
+
data.tar.gz: 0a059bd209b396729481e427c275ed2cc95c81d527161970e824ae694d215de95709035e696e2e1c1cc1d93208b1aafc8d6a4ee33fb72287427f3853c4074ea9
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vendo Connect Inc., Vendo Sp. z o.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Spree Posts
|
|
2
|
+
|
|
3
|
+
This is a Posts extension for [Spree Commerce](https://spreecommerce.org)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
1. Add this extension to your Gemfile with this line:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
bundle add spree_posts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Run the install generator
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
bundle exec rails g spree_posts:install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Restart your server
|
|
20
|
+
|
|
21
|
+
If your server was running, restart it so that it can find the assets properly.
|
|
22
|
+
|
|
23
|
+
## Developing
|
|
24
|
+
|
|
25
|
+
1. Create a dummy app
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle update
|
|
29
|
+
bundle exec rake test_app
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. Add your new code
|
|
33
|
+
3. Run tests
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bundle exec rspec
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When testing your applications integration with this extension you may use it's factories.
|
|
40
|
+
Simply add this require statement to your spec_helper:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require 'spree_posts/factories'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Releasing a new version
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
bundle exec gem bump -p -t
|
|
50
|
+
bundle exec gem release
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For more options please see [gem-release README](https://github.com/svenfuchs/gem-release)
|
|
54
|
+
|
|
55
|
+
## Contributing
|
|
56
|
+
|
|
57
|
+
If you'd like to contribute, please take a look at the
|
|
58
|
+
[instructions](CONTRIBUTING.md) for installing dependencies and crafting a good
|
|
59
|
+
pull request.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler::GemHelper.install_tasks
|
|
3
|
+
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
require 'spree/testing_support/extension_rake'
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new
|
|
8
|
+
|
|
9
|
+
task :default do
|
|
10
|
+
if Dir['spec/dummy'].empty?
|
|
11
|
+
Rake::Task[:test_app].invoke
|
|
12
|
+
Dir.chdir('../../')
|
|
13
|
+
end
|
|
14
|
+
Rake::Task[:spec].invoke
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc 'Generates a dummy app for testing'
|
|
18
|
+
task :test_app do
|
|
19
|
+
ENV['LIB_NAME'] = 'spree_posts'
|
|
20
|
+
Rake::Task['extension:test_app'].execute(
|
|
21
|
+
install_admin: true
|
|
22
|
+
)
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class PostCategoriesController < ResourceController
|
|
4
|
+
include Spree::Admin::TableConcern
|
|
5
|
+
|
|
6
|
+
add_breadcrumb Spree.t(:posts), :admin_posts_path
|
|
7
|
+
add_breadcrumb Spree.t(:categories), :admin_post_categories_path
|
|
8
|
+
|
|
9
|
+
def select_options
|
|
10
|
+
post_categories = current_store.post_categories.accessible_by(current_ability)
|
|
11
|
+
render json: post_categories.pluck(:id, :title).map { |id, title| { id: id, name: title } }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def permitted_resource_params
|
|
17
|
+
params.require(:post_category).permit(:title, :slug, :description)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class PostsController < ResourceController
|
|
4
|
+
include Spree::Admin::TableConcern
|
|
5
|
+
|
|
6
|
+
before_action :load_post_categories
|
|
7
|
+
|
|
8
|
+
add_breadcrumb Spree.t(:posts), :admin_posts_path
|
|
9
|
+
|
|
10
|
+
before_action :add_breadcrumb_for_post, only: [:edit, :update]
|
|
11
|
+
|
|
12
|
+
def select_options
|
|
13
|
+
render json: current_store.posts.published.accessible_by(current_ability).to_tom_select_json
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def collection_includes
|
|
19
|
+
[:author, :post_category, :image_attachment]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load_post_categories
|
|
23
|
+
@post_categories = current_store.post_categories.accessible_by(current_ability).order(:title)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_breadcrumb_for_post
|
|
27
|
+
return unless @post.present?
|
|
28
|
+
|
|
29
|
+
add_breadcrumb @post.title, spree.edit_admin_post_path(@post)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def permitted_resource_params
|
|
33
|
+
params.require(:post).permit(
|
|
34
|
+
:title, :meta_title, :meta_description, :slug, :author_id,
|
|
35
|
+
:post_category_id, :published_at, :content, :excerpt, :image,
|
|
36
|
+
tag_list: []
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Posts
|
|
3
|
+
class Find < Spree::BaseFinder
|
|
4
|
+
def initialize(scope:, params:)
|
|
5
|
+
super(scope: scope, params: params)
|
|
6
|
+
@scope = scope
|
|
7
|
+
@query = params[:q].presence&.strip
|
|
8
|
+
@ids = String(params.dig(:filter, :ids)).split(',')
|
|
9
|
+
@slugs = String(params.dig(:filter, :slugs)).split(',')
|
|
10
|
+
@category_ids = String(params.dig(:filter, :category_ids)).split(',')
|
|
11
|
+
@author_ids = String(params.dig(:filter, :author_ids)).split(',')
|
|
12
|
+
@tags = params.dig(:filter, :tags).to_s.split(',').compact_blank
|
|
13
|
+
@sort_by = params[:sort_by]
|
|
14
|
+
@published = params.dig(:filter, :published)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute
|
|
18
|
+
posts = by_ids(scope)
|
|
19
|
+
posts = by_slugs(posts)
|
|
20
|
+
posts = by_query(posts)
|
|
21
|
+
posts = by_category_ids(posts)
|
|
22
|
+
posts = by_author_ids(posts)
|
|
23
|
+
posts = by_tags(posts)
|
|
24
|
+
posts = by_published(posts)
|
|
25
|
+
posts = ordered(posts)
|
|
26
|
+
|
|
27
|
+
posts.distinct
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :scope, :query, :ids, :slugs, :category_ids, :author_ids, :tags, :sort_by, :published
|
|
33
|
+
|
|
34
|
+
def query?
|
|
35
|
+
query.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ids?
|
|
39
|
+
ids.present?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def slugs?
|
|
43
|
+
slugs.present?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def category_ids?
|
|
47
|
+
category_ids.present?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def author_ids?
|
|
51
|
+
author_ids.present?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tags?
|
|
55
|
+
tags.present?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def published?
|
|
59
|
+
published.present?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sort_by?
|
|
63
|
+
sort_by.present?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def by_query(posts)
|
|
67
|
+
return posts unless query?
|
|
68
|
+
|
|
69
|
+
posts.search_by_title(query)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def by_ids(posts)
|
|
73
|
+
return posts unless ids?
|
|
74
|
+
|
|
75
|
+
posts.where(id: ids)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def by_slugs(posts)
|
|
79
|
+
return posts unless slugs?
|
|
80
|
+
|
|
81
|
+
posts.where(slug: slugs)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def by_category_ids(posts)
|
|
85
|
+
return posts unless category_ids?
|
|
86
|
+
|
|
87
|
+
posts.where(post_category_id: category_ids)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def by_author_ids(posts)
|
|
91
|
+
return posts unless author_ids?
|
|
92
|
+
|
|
93
|
+
posts.where(author_id: author_ids)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def by_tags(posts)
|
|
97
|
+
return posts if tags.empty?
|
|
98
|
+
|
|
99
|
+
posts.tagged_with(tags, any: true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def by_published(posts)
|
|
103
|
+
return posts unless published?
|
|
104
|
+
|
|
105
|
+
case published.to_s
|
|
106
|
+
when 'true'
|
|
107
|
+
posts.published
|
|
108
|
+
when 'false'
|
|
109
|
+
posts.where(published_at: nil)
|
|
110
|
+
else
|
|
111
|
+
posts
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def ordered(posts)
|
|
116
|
+
return posts unless sort_by?
|
|
117
|
+
|
|
118
|
+
case sort_by
|
|
119
|
+
when 'newest-first'
|
|
120
|
+
posts.by_newest
|
|
121
|
+
when 'oldest-first'
|
|
122
|
+
posts.order(created_at: :asc)
|
|
123
|
+
when 'published-newest'
|
|
124
|
+
posts.order(published_at: :desc)
|
|
125
|
+
when 'published-oldest'
|
|
126
|
+
posts.order(published_at: :asc)
|
|
127
|
+
when 'title-a-z'
|
|
128
|
+
posts.order(title: :asc)
|
|
129
|
+
when 'title-z-a'
|
|
130
|
+
posts.order(title: :desc)
|
|
131
|
+
else
|
|
132
|
+
posts
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module PostsHelper
|
|
4
|
+
def post_authors_select_options
|
|
5
|
+
current_store.users.map { |user| [user.name, user.id] }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def post_tags_json_array
|
|
9
|
+
Spree::Post.all_tags.map { |tag| { name: tag.name } }.to_json
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class Post < Spree.base_class
|
|
3
|
+
has_prefix_id :post
|
|
4
|
+
|
|
5
|
+
include Spree::SingleStoreResource
|
|
6
|
+
include Spree::Metafields
|
|
7
|
+
extend FriendlyId
|
|
8
|
+
|
|
9
|
+
publishes_lifecycle_events
|
|
10
|
+
|
|
11
|
+
friendly_id :slug_candidates, use: %i[slugged scoped history], scope: %i[store_id deleted?]
|
|
12
|
+
acts_as_paranoid
|
|
13
|
+
acts_as_taggable_on :tags
|
|
14
|
+
acts_as_taggable_tenant :store_id
|
|
15
|
+
|
|
16
|
+
if defined?(PgSearch)
|
|
17
|
+
include PgSearch::Model
|
|
18
|
+
pg_search_scope :search_by_title, against: :title
|
|
19
|
+
else
|
|
20
|
+
scope :search_by_title, ->(query) { where('title LIKE ?', "%#{query}%") }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# Ransack filtering
|
|
25
|
+
#
|
|
26
|
+
self.whitelisted_ransackable_attributes = %w[author_id post_category_id published_at]
|
|
27
|
+
self.whitelisted_ransackable_associations = %w[author post_category]
|
|
28
|
+
self.whitelisted_ransackable_scopes = %w[search_by_title]
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Attachments
|
|
32
|
+
#
|
|
33
|
+
has_one_attached :image, service: Spree.public_storage_service_name
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# Rich Text
|
|
37
|
+
#
|
|
38
|
+
has_rich_text :content
|
|
39
|
+
has_rich_text :excerpt
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Associations
|
|
43
|
+
#
|
|
44
|
+
belongs_to :author, class_name: Spree.admin_user_class.to_s, optional: true
|
|
45
|
+
belongs_to :store, class_name: 'Spree::Store', inverse_of: :posts
|
|
46
|
+
belongs_to :post_category, class_name: 'Spree::PostCategory', optional: true, touch: true, inverse_of: :posts
|
|
47
|
+
alias category post_category
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# Validations
|
|
51
|
+
#
|
|
52
|
+
validates :title, :store, presence: true
|
|
53
|
+
validates :slug, presence: true, uniqueness: { scope: :store_id, conditions: -> { where(deleted_at: nil) } }
|
|
54
|
+
validates :meta_title, length: { maximum: 160 }, allow_blank: true
|
|
55
|
+
validates :meta_description, length: { maximum: 320 }, allow_blank: true
|
|
56
|
+
validates :image, content_type: Rails.application.config.active_storage.web_image_content_types
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Scopes
|
|
60
|
+
#
|
|
61
|
+
scope :published, -> { where(published_at: [..Time.current]) }
|
|
62
|
+
scope :by_newest, -> { order(created_at: :desc) }
|
|
63
|
+
|
|
64
|
+
delegate :name, to: :author, prefix: true, allow_nil: true
|
|
65
|
+
delegate :title, to: :post_category, prefix: true, allow_nil: true
|
|
66
|
+
|
|
67
|
+
def should_generate_new_friendly_id?
|
|
68
|
+
slug.blank? || (persisted? && title_changed?)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def slug_candidates
|
|
72
|
+
[
|
|
73
|
+
:title,
|
|
74
|
+
[:title, :id]
|
|
75
|
+
]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def published?
|
|
79
|
+
published_at.present?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def publish(date = nil)
|
|
83
|
+
update(published_at: date || Time.current)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def unpublish
|
|
87
|
+
update(published_at: nil)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def description
|
|
91
|
+
excerpt.to_plain_text.presence || content.to_plain_text
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def shortened_description
|
|
95
|
+
desc = excerpt.to_plain_text.presence || content.to_plain_text
|
|
96
|
+
desc.length > 320 ? "#{desc[0...320]}..." : desc
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.to_tom_select_json
|
|
100
|
+
pluck(:id, :title).map do |id, title|
|
|
101
|
+
{
|
|
102
|
+
id: id,
|
|
103
|
+
name: title
|
|
104
|
+
}
|
|
105
|
+
end.as_json
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class PostCategory < Spree.base_class
|
|
3
|
+
has_prefix_id :pcat
|
|
4
|
+
|
|
5
|
+
include Spree::SingleStoreResource
|
|
6
|
+
include Spree::Metafields
|
|
7
|
+
extend FriendlyId
|
|
8
|
+
|
|
9
|
+
publishes_lifecycle_events
|
|
10
|
+
|
|
11
|
+
friendly_id :slug_candidates, use: %i[slugged scoped history], scope: %i[store_id]
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Associations
|
|
15
|
+
#
|
|
16
|
+
belongs_to :store, class_name: 'Spree::Store', inverse_of: :post_categories
|
|
17
|
+
has_many :posts, class_name: 'Spree::Post', dependent: :nullify, inverse_of: :post_category
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# Validations
|
|
21
|
+
#
|
|
22
|
+
validates :title, :store, presence: true
|
|
23
|
+
validates :slug, presence: true, uniqueness: { scope: :store_id }
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# ActionText
|
|
27
|
+
#
|
|
28
|
+
has_rich_text :description
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Ransack
|
|
32
|
+
#
|
|
33
|
+
self.whitelisted_ransackable_attributes = %w[title slug]
|
|
34
|
+
|
|
35
|
+
def should_generate_new_friendly_id?
|
|
36
|
+
slug.blank? || title_changed?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def slug_candidates
|
|
40
|
+
[
|
|
41
|
+
:title,
|
|
42
|
+
[:title, :id]
|
|
43
|
+
]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module StoreDecorator
|
|
3
|
+
def self.prepended(base)
|
|
4
|
+
base.has_many :posts, class_name: 'Spree::Post', dependent: :destroy, inverse_of: :store
|
|
5
|
+
base.has_many :post_categories, class_name: 'Spree::PostCategory', dependent: :destroy, inverse_of: :store
|
|
6
|
+
|
|
7
|
+
base.after_create :ensure_default_post_categories_are_created
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def ensure_default_post_categories_are_created
|
|
13
|
+
Spree::Events.disable do
|
|
14
|
+
[
|
|
15
|
+
I18n.t('spree.default_post_categories.resources', default: 'Resources'),
|
|
16
|
+
I18n.t('spree.default_post_categories.articles', default: 'Articles'),
|
|
17
|
+
I18n.t('spree.default_post_categories.news', default: 'News')
|
|
18
|
+
].each do |category_title|
|
|
19
|
+
next if post_categories.where(title: category_title).exists?
|
|
20
|
+
|
|
21
|
+
post_categories.create(title: category_title)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Spree::Store.prepend(Spree::StoreDecorator)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Posts
|
|
3
|
+
class Sort < ::Spree::BaseSorter
|
|
4
|
+
def initialize(scope, params = {}, allowed_sort_attributes = [])
|
|
5
|
+
super(scope, params, allowed_sort_attributes)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
posts = by_param_attributes(scope)
|
|
10
|
+
posts = select_translatable_fields(posts) if Spree.use_translations?
|
|
11
|
+
|
|
12
|
+
posts.distinct
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :sort, :scope, :allowed_sort_attributes
|
|
18
|
+
|
|
19
|
+
# Add translatable fields to SELECT statement to avoid InvalidColumnReference error (workaround for Mobility issue #596)
|
|
20
|
+
def select_translatable_fields(scope)
|
|
21
|
+
translatable_fields = translatable_sortable_fields
|
|
22
|
+
return scope if translatable_fields.empty?
|
|
23
|
+
|
|
24
|
+
scope.i18n.select("#{Spree::Post.table_name}.*").select(*translatable_fields)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def translatable_sortable_fields
|
|
28
|
+
fields = []
|
|
29
|
+
Spree::Post.translatable_fields.each do |field|
|
|
30
|
+
fields << field if sort_by?(field.to_s)
|
|
31
|
+
end
|
|
32
|
+
fields
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sort_by?(field)
|
|
36
|
+
sort.detect { |s| s[0] == field }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<tr data-controller="row-link" id="<%= dom_id(post_category) %>">
|
|
2
|
+
<td class="w-60 p-0">
|
|
3
|
+
<%= link_to post_category.title, spree.edit_admin_post_category_path(post_category), class: 'block w-full h-full px-4 py-2', data: { row_link_target: :link, turbo_frame: '_top' } %>
|
|
4
|
+
</td>
|
|
5
|
+
<td class="w-10 cursor-pointer" data-action="click->row-link#openLink"><%= post_category.posts.count %></td>
|
|
6
|
+
<td class="w-10 cursor-pointer text-right" data-action="click->row-link#openLink">
|
|
7
|
+
<%= link_to_edit(post_category, class: 'btn btn-light btn-sm', data: { turbo_frame: '_top' }) if can? :edit, post_category %>
|
|
8
|
+
</td>
|
|
9
|
+
</tr>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render 'spree/admin/shared/edit_resource' %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= render 'spree/admin/shared/posts_tabs' %>
|
|
2
|
+
|
|
3
|
+
<% content_for :page_actions do %>
|
|
4
|
+
<%= render_admin_partials(:post_categories_actions_partials) %>
|
|
5
|
+
<%= link_to_with_icon 'plus', Spree.t(:new_post_category), new_object_url, class: 'btn btn-primary' if can?(:create, Spree::PostCategory) %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<%= render_admin_partials(:post_categories_header_partials) %>
|
|
9
|
+
|
|
10
|
+
<%= render_table @collection, :post_categories %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render 'spree/admin/shared/new_resource' %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<div class="grid grid-cols-12 gap-6" data-controller="slug-form seo-form">
|
|
2
|
+
<div class="col-span-12 lg:col-span-8">
|
|
3
|
+
<div class="card mb-6">
|
|
4
|
+
<div class="card-body">
|
|
5
|
+
<%= f.spree_text_field :title,
|
|
6
|
+
required: true,
|
|
7
|
+
autofocus: true,
|
|
8
|
+
data: {
|
|
9
|
+
seo_form_target: 'sourceTitleInput',
|
|
10
|
+
slug_form_target: 'name',
|
|
11
|
+
action: 'slug-form#updateUrlFromName'
|
|
12
|
+
} %>
|
|
13
|
+
|
|
14
|
+
<%= f.spree_rich_text_area :content,
|
|
15
|
+
help_bubble: 'Add a summary of the post',
|
|
16
|
+
data: { seo_form_target: 'sourceDescriptionInput' } %>
|
|
17
|
+
|
|
18
|
+
<%= f.spree_rich_text_area :excerpt,
|
|
19
|
+
help_bubble: 'Add a summary of the post',
|
|
20
|
+
data: { seo_form_target: 'sourceExcerptInput' } %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="col-span-12 lg:col-span-4">
|
|
26
|
+
<div class="card mb-6">
|
|
27
|
+
<div class="card-body">
|
|
28
|
+
<%= f.spree_file_field :image, width: 1200, height: 600, crop: true, css: 'flex-col', label: Spree.t(:featured_image) %>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="card mb-6">
|
|
33
|
+
<div class="card-body">
|
|
34
|
+
<%= f.spree_select :author_id, options_for_select(post_authors_select_options, @post.author_id || try_spree_current_user.id), { label: Spree.t(:author), autocomplete: true } %>
|
|
35
|
+
<%= f.spree_select :post_category_id, options_for_select(@post_categories.pluck(:title, :id), @post.post_category_id), { label: Spree.t(:category), include_blank: true, autocomplete: true } %>
|
|
36
|
+
<%= f.spree_datetime_field :published_at, help_bubble: 'Marks when the post will be published, leave it blank to hide it' %>
|
|
37
|
+
|
|
38
|
+
<div class="form-group mb-0">
|
|
39
|
+
<%= f.label :tag_list, Spree.t(:tags) %>
|
|
40
|
+
<%= tom_select_tag 'post[tag_list]', multiple: true, class: 'w-full', options: post_tags_json_array, active_option: @post.tag_list, value_field: :name, create: true %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<%= render 'spree/admin/shared/seo',
|
|
46
|
+
f: f,
|
|
47
|
+
title: @post.title,
|
|
48
|
+
meta_title: @post.meta_title,
|
|
49
|
+
description: @post.content,
|
|
50
|
+
slug: @post.slug,
|
|
51
|
+
slug_path: 'posts',
|
|
52
|
+
placeholder: 'Add a title and content to see how this post might appear in a search engine listing' %>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<tr>
|
|
2
|
+
<th scope="col"><%= sort_link @search, :title, Spree.t(:title) %></th>
|
|
3
|
+
<th scope="col"><%= Spree.t(:category) %></th>
|
|
4
|
+
<th scope="col"><%= Spree.t(:author) %></th>
|
|
5
|
+
<th scope="col"><%= sort_link @search, :published_at, Spree.t(:published_at) %></th>
|
|
6
|
+
<th scope="col"></th>
|
|
7
|
+
</tr>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<tr data-controller="row-link" id="<%= dom_id(post) %>">
|
|
2
|
+
<td>
|
|
3
|
+
<%= link_to spree.edit_admin_post_path(post), class: 'no-underline flex items-center font-bold text-gray-900 gap-4', data: { row_link_target: :link, 'turbo-frame': '_top' } do %>
|
|
4
|
+
<% if post.image.attached? && post.image.variable? %>
|
|
5
|
+
<div class="admin-product-image-container">
|
|
6
|
+
<%= spree_image_tag(post.image, width: 100, height: 65, class: 'img-fluid', loading: :lazy) %>
|
|
7
|
+
</div>
|
|
8
|
+
<% else %>
|
|
9
|
+
<%= render 'spree/admin/shared/no_image', width: 100, height: 65 %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<%= post.title %>
|
|
12
|
+
<% end %>
|
|
13
|
+
</td>
|
|
14
|
+
|
|
15
|
+
<td class="cursor-pointer" data-action="click->row-link#openLink"><%= post.post_category_title || '-' %></td>
|
|
16
|
+
<td class="cursor-pointer" data-action="click->row-link#openLink"><%= post.author_name %></td>
|
|
17
|
+
<td class="cursor-pointer" data-action="click->row-link#openLink"><%= post.published_at.present? ? spree_date(post.published_at) : '-' %></td>
|
|
18
|
+
|
|
19
|
+
<td class="actions">
|
|
20
|
+
<%= link_to_edit(post, class: 'btn btn-light btn-sm', data: { 'turbo-frame': '_top' }) if can? :edit, post %>
|
|
21
|
+
</td>
|
|
22
|
+
</tr>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<%= search_form_for [:admin, @search], class: "filter-wrap", data: {controller: "filters dialog"} do |f| %>
|
|
2
|
+
<div class="flex flex-col lg:flex-row gap-2">
|
|
3
|
+
<%= render 'spree/admin/shared/filters_search_bar', param: :search_by_title, label: Spree.t(:title) %>
|
|
4
|
+
|
|
5
|
+
<%= button_tag type: 'button', class: 'btn btn-light flex items-center', data: { action: 'dialog#open' } do %>
|
|
6
|
+
<%= icon "adjustments", class: "mr-1" %>
|
|
7
|
+
<%= Spree.t("admin.filters") %>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<%= drawer(id: 'posts-filters-drawer', controller_name: 'dialog') do %>
|
|
12
|
+
<%= drawer_header(Spree.t(:filter), 'dialog') %>
|
|
13
|
+
<div class="drawer-body">
|
|
14
|
+
<div class="form-group">
|
|
15
|
+
<%= f.label :author_id_eq, Spree.t(:author) %>
|
|
16
|
+
<%= f.select :author_id_eq,
|
|
17
|
+
options_for_select(post_authors_select_options, params.dig(:q, :author_id_eq)),
|
|
18
|
+
{ include_blank: true },
|
|
19
|
+
{ data: { filters_target: :input, controller: 'autocomplete-select' } } %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="form-group">
|
|
22
|
+
<%= f.label :post_category_id_eq, Spree.t(:category) %>
|
|
23
|
+
<%= f.select :post_category_id_eq,
|
|
24
|
+
options_for_select(@post_categories.pluck(:title, :id), params.dig(:q, :post_category_id_eq)),
|
|
25
|
+
{ include_blank: true },
|
|
26
|
+
{ data: { filters_target: :input, controller: 'autocomplete-select' } } %>
|
|
27
|
+
</div>
|
|
28
|
+
<%= render_admin_partials(:posts_filters_partials, f: f) %>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="drawer-footer">
|
|
31
|
+
<%= drawer_discard_button('dialog') %>
|
|
32
|
+
<%= render 'spree/admin/shared/filter_submit' %>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
|
|
36
|
+
<%= render "spree/admin/shared/filter_badge_template" %>
|
|
37
|
+
|
|
38
|
+
<div data-filters-target="badgesContainer" class="filter-badges-container"></div>
|
|
39
|
+
<% end %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= render 'spree/admin/shared/posts_tabs' %>
|
|
2
|
+
|
|
3
|
+
<% content_for :page_actions do %>
|
|
4
|
+
<%= render_admin_partials(:posts_actions_partials) %>
|
|
5
|
+
<%= link_to_with_icon 'plus', Spree.t(:new_post), new_object_url, class: 'btn btn-primary' if can?(:create, Spree::Post) %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<%= render_admin_partials(:posts_header_partials) %>
|
|
9
|
+
|
|
10
|
+
<%= render_table @collection, :posts %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render 'spree/admin/shared/new_resource', col_class: 'col-span-12' %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= Spree.t(:posts) %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% content_for(:page_tabs) do %>
|
|
6
|
+
<%= nav_item(Spree.t(:posts), spree.admin_posts_path) if can?(:manage, Spree::Post) %>
|
|
7
|
+
<%= nav_item(Spree.t(:categories), spree.admin_post_categories_path) if can?(:manage, Spree::PostCategory) %>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Rails.application.config.after_initialize do
|
|
2
|
+
# Dependencies
|
|
3
|
+
Spree::Dependencies.posts_finder = 'Spree::Posts::Find'
|
|
4
|
+
Spree::Dependencies.posts_sorter = 'Spree::Posts::Sort'
|
|
5
|
+
|
|
6
|
+
if defined?(Spree::Api)
|
|
7
|
+
Spree::Api::Dependencies.storefront_posts_finder = 'Spree::Posts::Find'
|
|
8
|
+
Spree::Api::Dependencies.storefront_posts_sorter = 'Spree::Posts::Sort'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Metafields
|
|
12
|
+
Rails.application.config.spree.metafields.enabled_resources.push(Spree::Post, Spree::PostCategory)
|
|
13
|
+
|
|
14
|
+
# Admin navigation
|
|
15
|
+
if defined?(Spree::Admin) && Spree.respond_to?(:admin)
|
|
16
|
+
sidebar_nav = Spree.admin.navigation.sidebar
|
|
17
|
+
|
|
18
|
+
sidebar_nav.add :posts,
|
|
19
|
+
label: :posts,
|
|
20
|
+
url: :admin_posts_path,
|
|
21
|
+
icon: 'article',
|
|
22
|
+
position: 70,
|
|
23
|
+
active: -> { %w[posts post_categories].include?(controller_name) },
|
|
24
|
+
if: -> { can?(:manage, Spree::Post) }
|
|
25
|
+
|
|
26
|
+
# Admin tables
|
|
27
|
+
Spree.admin.tables.register(:posts, model_class: Spree::Post, search_param: :title_cont)
|
|
28
|
+
|
|
29
|
+
Spree.admin.tables.posts.add :title,
|
|
30
|
+
label: :title,
|
|
31
|
+
type: :link,
|
|
32
|
+
sortable: true,
|
|
33
|
+
filterable: true,
|
|
34
|
+
default: true,
|
|
35
|
+
position: 10
|
|
36
|
+
|
|
37
|
+
Spree.admin.tables.posts.add :post_category,
|
|
38
|
+
label: :category,
|
|
39
|
+
type: :string,
|
|
40
|
+
filter_type: :autocomplete,
|
|
41
|
+
sortable: false,
|
|
42
|
+
filterable: true,
|
|
43
|
+
default: true,
|
|
44
|
+
position: 20,
|
|
45
|
+
method: ->(post) { post.post_category_title },
|
|
46
|
+
ransack_attribute: 'post_category_id',
|
|
47
|
+
search_url: ->(view_context) { view_context.spree.select_options_admin_post_categories_path(format: :json) }
|
|
48
|
+
|
|
49
|
+
Spree.admin.tables.posts.add :author,
|
|
50
|
+
label: :author,
|
|
51
|
+
type: :string,
|
|
52
|
+
filter_type: :autocomplete,
|
|
53
|
+
sortable: false,
|
|
54
|
+
filterable: true,
|
|
55
|
+
default: true,
|
|
56
|
+
position: 30,
|
|
57
|
+
method: ->(post) { post.author_name },
|
|
58
|
+
ransack_attribute: 'author_id',
|
|
59
|
+
search_url: ->(view_context) { view_context.spree.select_options_admin_admin_users_path(format: :json) }
|
|
60
|
+
|
|
61
|
+
Spree.admin.tables.posts.add :published_at,
|
|
62
|
+
label: :published_at,
|
|
63
|
+
type: :date,
|
|
64
|
+
sortable: true,
|
|
65
|
+
filterable: true,
|
|
66
|
+
default: true,
|
|
67
|
+
position: 40
|
|
68
|
+
|
|
69
|
+
Spree.admin.tables.posts.add :created_at,
|
|
70
|
+
label: :created_at,
|
|
71
|
+
type: :datetime,
|
|
72
|
+
sortable: true,
|
|
73
|
+
filterable: true,
|
|
74
|
+
default: false,
|
|
75
|
+
position: 50
|
|
76
|
+
|
|
77
|
+
Spree.admin.tables.posts.add :updated_at,
|
|
78
|
+
label: :updated_at,
|
|
79
|
+
type: :datetime,
|
|
80
|
+
sortable: true,
|
|
81
|
+
filterable: true,
|
|
82
|
+
default: false,
|
|
83
|
+
position: 60
|
|
84
|
+
|
|
85
|
+
# Post Categories Table
|
|
86
|
+
Spree.admin.tables.register(:post_categories, model_class: Spree::PostCategory, search_param: :title_cont)
|
|
87
|
+
|
|
88
|
+
Spree.admin.tables.post_categories.add :title,
|
|
89
|
+
label: :title,
|
|
90
|
+
type: :link,
|
|
91
|
+
sortable: true,
|
|
92
|
+
filterable: true,
|
|
93
|
+
default: true,
|
|
94
|
+
position: 10
|
|
95
|
+
|
|
96
|
+
Spree.admin.tables.post_categories.add :posts_count,
|
|
97
|
+
label: :posts,
|
|
98
|
+
type: :number,
|
|
99
|
+
sortable: false,
|
|
100
|
+
filterable: false,
|
|
101
|
+
default: true,
|
|
102
|
+
position: 20,
|
|
103
|
+
method: ->(post_category) { post_category.posts.count }
|
|
104
|
+
|
|
105
|
+
Spree.admin.tables.post_categories.add :created_at,
|
|
106
|
+
label: :created_at,
|
|
107
|
+
type: :datetime,
|
|
108
|
+
sortable: true,
|
|
109
|
+
filterable: true,
|
|
110
|
+
default: false,
|
|
111
|
+
position: 30
|
|
112
|
+
|
|
113
|
+
Spree.admin.tables.post_categories.add :updated_at,
|
|
114
|
+
label: :updated_at,
|
|
115
|
+
type: :datetime,
|
|
116
|
+
sortable: true,
|
|
117
|
+
filterable: true,
|
|
118
|
+
default: false,
|
|
119
|
+
position: 40
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
spree:
|
|
4
|
+
all_posts: All posts
|
|
5
|
+
all_posts_with_tag: All posts with tag
|
|
6
|
+
blog: Blog
|
|
7
|
+
blogs_posts: Blogs posts
|
|
8
|
+
default_post_categories:
|
|
9
|
+
articles: Articles
|
|
10
|
+
news: News
|
|
11
|
+
resources: Resources
|
|
12
|
+
new_post: New Post
|
|
13
|
+
new_post_category: New Post Category
|
|
14
|
+
post_categories: Post categories
|
|
15
|
+
posts: Posts
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Spree::Core::Engine.add_routes do
|
|
2
|
+
namespace :admin do
|
|
3
|
+
resources :posts do
|
|
4
|
+
collection do
|
|
5
|
+
get :select_options, defaults: { format: :json }
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
resources :post_categories do
|
|
9
|
+
collection do
|
|
10
|
+
get :select_options, defaults: { format: :json }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class CreateSpreePostsAndSpreePostCategories < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
if !table_exists?(:spree_post_categories)
|
|
4
|
+
create_table :spree_post_categories do |t|
|
|
5
|
+
t.references :store, null: false, index: true
|
|
6
|
+
t.string :title, null: false
|
|
7
|
+
t.string :slug, null: false
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
|
|
11
|
+
t.index ['slug', 'store_id'], name: 'index_spree_post_categories_on_slug_and_store_id', unique: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if !table_exists?(:spree_posts)
|
|
16
|
+
create_table :spree_posts do |t|
|
|
17
|
+
t.references :author, index: true
|
|
18
|
+
t.datetime :published_at
|
|
19
|
+
t.string :title, null: false
|
|
20
|
+
t.string :slug, null: false
|
|
21
|
+
t.references :post_category, index: true
|
|
22
|
+
t.references :store, index: true
|
|
23
|
+
t.string :meta_title
|
|
24
|
+
t.string :meta_description
|
|
25
|
+
|
|
26
|
+
t.timestamps
|
|
27
|
+
t.datetime :deleted_at
|
|
28
|
+
|
|
29
|
+
t.index ['title'], name: 'index_spree_posts_on_title'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SpreePosts
|
|
2
|
+
module Generators
|
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
|
4
|
+
class_option :migrate, type: :boolean, default: true
|
|
5
|
+
|
|
6
|
+
def add_migrations
|
|
7
|
+
run 'bundle exec rake railties:install:migrations FROM=spree_posts'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run_migrations
|
|
11
|
+
run_migrations = options[:migrate] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
|
|
12
|
+
if run_migrations
|
|
13
|
+
run 'bin/rails db:migrate'
|
|
14
|
+
else
|
|
15
|
+
puts 'Skipping rails db:migrate, don\'t forget to run it!'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SpreePosts
|
|
2
|
+
class Configuration < Spree::Preferences::Configuration
|
|
3
|
+
|
|
4
|
+
# Some example preferences are shown below, for more information visit:
|
|
5
|
+
# https://docs.spreecommerce.org/developer/contributing/creating-an-extension
|
|
6
|
+
|
|
7
|
+
# preference :enabled, :boolean, default: true
|
|
8
|
+
# preference :dark_chocolate, :boolean, default: true
|
|
9
|
+
# preference :color, :string, default: 'Red'
|
|
10
|
+
# preference :favorite_number, :integer
|
|
11
|
+
# preference :supported_locales, :array, default: [:en]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SpreePosts
|
|
2
|
+
class Engine < Rails::Engine
|
|
3
|
+
require 'spree/core'
|
|
4
|
+
isolate_namespace Spree
|
|
5
|
+
engine_name 'spree_posts'
|
|
6
|
+
|
|
7
|
+
# use rspec for tests
|
|
8
|
+
config.generators do |g|
|
|
9
|
+
g.test_framework :rspec
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer 'spree_posts.environment', before: :load_config_initializers do |_app|
|
|
13
|
+
SpreePosts::Config = SpreePosts::Configuration.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.activate
|
|
17
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
|
|
18
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
config.to_prepare(&method(:activate).to_proc)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
FactoryBot.define do
|
|
2
|
+
factory :post, class: Spree::Post do
|
|
3
|
+
store { Spree::Store.default || create(:store) }
|
|
4
|
+
post_category { association(:post_category, store: store) }
|
|
5
|
+
title { FFaker::Lorem.sentence }
|
|
6
|
+
content { FFaker::Lorem.paragraph }
|
|
7
|
+
published_at { Time.current }
|
|
8
|
+
association :author, factory: :admin_user
|
|
9
|
+
|
|
10
|
+
trait :with_image do
|
|
11
|
+
image { Rack::Test::UploadedFile.new(Spree::Core::Engine.root.join('spec/fixtures/thinking-cat.jpg'), 'image/jpeg') }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
trait :published do
|
|
15
|
+
published_at { Time.current }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
trait :unpublished do
|
|
19
|
+
published_at { nil }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
factory :post_category, class: Spree::PostCategory do
|
|
24
|
+
sequence(:title) { |n| "Category ##{n + 1}" }
|
|
25
|
+
description { FFaker::Lorem.sentence }
|
|
26
|
+
store { Spree::Store.default || create(:store) }
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/spree_posts.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spree_posts
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vendo Connect Inc., Vendo Sp. z o.o.
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: spree
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 5.4.0.beta
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 5.4.0.beta
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: spree_admin
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 5.4.0.beta
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 5.4.0.beta
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: spree_dev_tools
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
email: hello@spreecommerce.org
|
|
55
|
+
executables: []
|
|
56
|
+
extensions: []
|
|
57
|
+
extra_rdoc_files: []
|
|
58
|
+
files:
|
|
59
|
+
- LICENSE.md
|
|
60
|
+
- README.md
|
|
61
|
+
- Rakefile
|
|
62
|
+
- app/controllers/spree/admin/post_categories_controller.rb
|
|
63
|
+
- app/controllers/spree/admin/posts_controller.rb
|
|
64
|
+
- app/finders/spree/posts/find.rb
|
|
65
|
+
- app/helpers/spree/admin/posts_helper.rb
|
|
66
|
+
- app/models/spree/post.rb
|
|
67
|
+
- app/models/spree/post_category.rb
|
|
68
|
+
- app/models/spree/store_decorator.rb
|
|
69
|
+
- app/sorters/spree/posts/sort.rb
|
|
70
|
+
- app/views/spree/admin/post_categories/_form.html.erb
|
|
71
|
+
- app/views/spree/admin/post_categories/_table_header.html.erb
|
|
72
|
+
- app/views/spree/admin/post_categories/_table_row.html.erb
|
|
73
|
+
- app/views/spree/admin/post_categories/edit.html.erb
|
|
74
|
+
- app/views/spree/admin/post_categories/index.html.erb
|
|
75
|
+
- app/views/spree/admin/post_categories/new.html.erb
|
|
76
|
+
- app/views/spree/admin/posts/_extra_actions.html.erb
|
|
77
|
+
- app/views/spree/admin/posts/_form.html.erb
|
|
78
|
+
- app/views/spree/admin/posts/_table_header.html.erb
|
|
79
|
+
- app/views/spree/admin/posts/_table_row.html.erb
|
|
80
|
+
- app/views/spree/admin/posts/edit.html.erb
|
|
81
|
+
- app/views/spree/admin/posts/filters.html.erb
|
|
82
|
+
- app/views/spree/admin/posts/index.html.erb
|
|
83
|
+
- app/views/spree/admin/posts/new.html.erb
|
|
84
|
+
- app/views/spree/admin/shared/_posts_tabs.html.erb
|
|
85
|
+
- config/initializers/spree.rb
|
|
86
|
+
- config/locales/en.yml
|
|
87
|
+
- config/routes.rb
|
|
88
|
+
- db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb
|
|
89
|
+
- lib/generators/spree_posts/install/install_generator.rb
|
|
90
|
+
- lib/spree_posts.rb
|
|
91
|
+
- lib/spree_posts/configuration.rb
|
|
92
|
+
- lib/spree_posts/engine.rb
|
|
93
|
+
- lib/spree_posts/factories.rb
|
|
94
|
+
- lib/spree_posts/version.rb
|
|
95
|
+
homepage: https://github.com/spree/spree-posts
|
|
96
|
+
licenses:
|
|
97
|
+
- MIT
|
|
98
|
+
metadata: {}
|
|
99
|
+
rdoc_options: []
|
|
100
|
+
require_paths:
|
|
101
|
+
- lib
|
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '3.2'
|
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
requirements:
|
|
113
|
+
- none
|
|
114
|
+
rubygems_version: 4.0.2
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: Spree Commerce Posts Extension
|
|
117
|
+
test_files: []
|