diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 2bd1211..4ed561c 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2025-03-18 11:13:14 UTC using RuboCop version 1.74.0.
+# on 2025-03-20 11:37:51 UTC using RuboCop version 1.74.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -45,27 +45,36 @@ RSpec/NamedSubject:
- 'spec/controllers/spree/admin/pages_controller_spec.rb'
- 'spec/lib/solidus_static_content/route_matcher_spec.rb'
-# Offense count: 1
+# Offense count: 2
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasAndBelongsToMany:
Exclude:
+ - 'app/models/spree/blog.rb'
- 'app/models/spree/page.rb'
+# Offense count: 1
+# Configuration parameters: IgnoreScopes, Include.
+# Include: app/models/**/*.rb
+Rails/InverseOf:
+ Exclude:
+ - 'app/models/spree/blog.rb'
+
# Offense count: 1
Rails/OutputSafety:
Exclude:
- 'app/helpers/spree/pages_helper.rb'
-# Offense count: 4
+# Offense count: 7
# Configuration parameters: ForbiddenMethods, AllowedMethods.
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
Rails/SkipsModelValidations:
Exclude:
+ - 'app/models/spree/blog.rb'
- 'db/migrate/20090625125735_extend_pages.rb'
- 'db/migrate/20090814113100_add_visible_to_pages.rb'
-# Offense count: 1
+# Offense count: 4
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
# URISchemes: http, https
diff --git a/app/assets/javascripts/spree/backend/solidus_static_content.js b/app/assets/javascripts/spree/backend/solidus_static_content.js
new file mode 100644
index 0000000..f78321d
--- /dev/null
+++ b/app/assets/javascripts/spree/backend/solidus_static_content.js
@@ -0,0 +1,3 @@
+// Placeholder manifest file.
+// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
+//= require 'spree/backend/views/blogs/taxonblog_autocomplete'
diff --git a/app/assets/javascripts/spree/backend/views/blogs/taxonblog_autocomplete.js b/app/assets/javascripts/spree/backend/views/blogs/taxonblog_autocomplete.js
new file mode 100644
index 0000000..d4b1df6
--- /dev/null
+++ b/app/assets/javascripts/spree/backend/views/blogs/taxonblog_autocomplete.js
@@ -0,0 +1,87 @@
+$.fn.taxonblogAutocomplete = function (options) {
+ 'use strict';
+
+ var defaultOptions = {
+ multiple: true,
+ placeholder: Spree.translations.taxon_placeholder
+ };
+
+ options = $.extend({}, defaultOptions, options);
+
+ this.select2({
+ placeholder: options.placeholder,
+ multiple: options.multiple,
+ initSelection: function (element, callback) {
+ var ids = element.val();
+
+ if (options.multiple) {
+ var count = ids.split(",").length;
+
+ Spree.ajax({
+ type: "GET",
+ url: Spree.pathFor('api/taxons'),
+ data: {
+ ids: ids,
+ per_page: count,
+ without_children: true
+ },
+ success: function (data) {
+ callback(data['taxons']);
+ }
+ });
+ } else {
+
+ Spree.ajax({
+ type: "GET",
+ url: Spree.pathFor('api/taxons'),
+ data: {
+ ids: ids,
+ per_page: 1,
+ without_children: true
+ },
+ success: function (data) {
+ callback(data['taxons'][0]);
+ }
+ });
+ }
+ },
+ ajax: {
+ url: Spree.pathFor('api/taxons'),
+ datatype: 'json',
+ data: function (term, page) {
+ return {
+ per_page: 50,
+ page: page,
+ without_children: true,
+ q: {
+ name_cont: term
+ },
+ token: Spree.api_key
+ };
+ },
+ results: function (data, page) {
+ var more = page < data.pages;
+ return {
+ results: data['taxons'],
+ more: more
+ };
+ }
+ },
+ formatResult: function (taxon, container, query, escapeMarkup) {
+ return escapeMarkup(taxon.pretty_name);
+ },
+ formatSelection: function (taxon, container, escapeMarkup) {
+ return escapeMarkup(taxon.pretty_name);
+ }
+ });
+};
+
+Spree.ready(function () {
+ $('#blog_taxon_ids, .taxon_picker').taxonblogAutocomplete({
+ multiple: true,
+ });
+
+ $('#blog_primary_taxon_id').taxonblogAutocomplete({
+ multiple: false,
+ });
+});
\ No newline at end of file
diff --git a/app/models/spree/blog.rb b/app/models/spree/blog.rb
new file mode 100644
index 0000000..d602dea
--- /dev/null
+++ b/app/models/spree/blog.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Spree
+ class Blog < Spree::Base
+ AUTHOR_IMAGE_TYPE = 'AuthorImage'
+ ARTICLE_IMAGE_TYPE = 'ArticleImage'
+
+ extend FriendlyId
+ friendly_id :slug_candidates, use: :history
+
+ acts_as_list
+ has_and_belongs_to_many :stores, join_table: 'spree_blogs_stores'
+ has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: "Spree::Image"
+ has_many :tags, dependent: :delete_all, inverse_of: :blog
+ has_many :taxons, through: :tags, before_remove: :remove_taxon
+
+ belongs_to :primary_taxon, class_name: 'Spree::Taxon', optional: true
+
+ validates :title, :author, :content, :slug, presence: true
+ validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true }
+
+ before_validation :normalize_slug, on: :update
+ after_destroy :punch_slug
+ after_touch :touch_taxons
+
+ scope :published, -> { where(status: :publish).where('publishing_date <= ?', Time.current) }
+ scope :by_store, ->(store) { joins(:stores).where("spree_blogs_stores.store_id = ?", store) }
+ scope :ordered_by_position, -> { order(:position) }
+
+ enum :status, { draft: 0, publish: 1 }
+
+ def author_image
+ images.find_by(image_type: AUTHOR_IMAGE_TYPE)
+ end
+
+ def article_images
+ images.where(image_type: ARTICLE_IMAGE_TYPE)
+ end
+
+ def attach_author_image(image)
+ images.create!(attachment: image, image_type: AUTHOR_IMAGE_TYPE)
+ end
+
+ def attach_article_images(attach_images)
+ attach_images.each do |image|
+ images.create!(attachment: image, image_type: ARTICLE_IMAGE_TYPE)
+ end
+ end
+
+ private
+
+ def normalize_slug
+ self.slug = normalize_friendly_id(slug)
+ end
+
+ def punch_slug
+ # punch slug with date prefix to allow reuse of original
+ update_column :slug, "#{Time.current.to_i}_#{slug}" unless frozen?
+ end
+
+ # Try building a slug based on the following fields in increasing order of specificity.
+ def slug_candidates
+ [
+ :title,
+ [:title, :author]
+ ]
+ end
+
+ # Iterate through this product's taxons and taxonomies and touch their timestamps in a batch
+ def touch_taxons
+ taxons_to_touch = taxons.flat_map(&:self_and_ancestors).uniq
+ return if taxons_to_touch.empty?
+
+ Spree::Taxon.where(id: taxons_to_touch.map(&:id)).update_all(updated_at: Time.current)
+
+ taxonomy_ids_to_touch = taxons_to_touch.flat_map(&:taxonomy_id).uniq
+ Spree::Taxonomy.where(id: taxonomy_ids_to_touch).update_all(updated_at: Time.current)
+ end
+
+ def remove_taxon(taxon)
+ removed_tags = tags.where(taxon:)
+ removed_tags.each(&:remove_from_list)
+ end
+ end
+end
diff --git a/app/models/spree/tag.rb b/app/models/spree/tag.rb
new file mode 100644
index 0000000..ccd4920
--- /dev/null
+++ b/app/models/spree/tag.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Spree
+ class Tag < Spree::Base
+ self.table_name = 'spree_blogs_taxons'
+ acts_as_list scope: :taxon
+ belongs_to :blog, class_name: "Spree::Blog", inverse_of: :tags, touch: true, optional: true
+ belongs_to :taxon, class_name: "Spree::Taxon", inverse_of: :tags, touch: true, optional: true
+
+ # For https://github.com/spree/spree/issues/3494
+ validates :taxon_id, uniqueness: { scope: :blog_id, message: :already_linked }
+ end
+end
diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb
new file mode 100644
index 0000000..0b08b73
--- /dev/null
+++ b/app/models/spree/taxon_decorator.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Spree
+ module TaxonDecorator
+ def self.prepended(base)
+ base.has_many :tags, -> { order(:position) }, dependent: :delete_all, inverse_of: :taxon
+ end
+ end
+end
+
+Spree::Taxon.prepend(Spree::TaxonDecorator)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 5675611..62b3dae 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -23,16 +23,32 @@ en:
many: Pages
one: Page
other: Pages
+ spree/blog:
+ few: Blogs
+ many: Blogs
+ one: Blog
+ other: Blogs
spree:
admin:
tab:
pages: Pages
+ blogs: Blogs
+ contents: Contents
back_to_static_pages_list: "Back to static pages list"
page: Page
+ content: Content
+ contents: Contents
+ new_blog: New Blog
+ blogs: Blogs
pages: Pages
stores: Stores
static_pages: Static pages
- new_page: New page
+ new_page: New Page
editing_page: Editing page
link: Link
add_one: Create One
+ remove_image: Image Removed Successfully
+ edit_blog: Edit Blog
+ statuses:
+ draft: Draft
+ publish: Publish
diff --git a/config/routes.rb b/config/routes.rb
index 2ea81d7..a7a31c2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -7,6 +7,16 @@
post :update_positions
end
end
+
+ resources :blogs do
+ collection do
+ post :update_positions
+ end
+
+ member do
+ delete :delete_image
+ end
+ end
end
# Check if the layout is not the default layout to avoid conflicts with the starter_frontend routes
@@ -15,4 +25,10 @@
get '/(*path)', to: 'static_content#show', as: 'static'
end
end
+
+ if SolidusSupport.api_available?
+ namespace :api do
+ resources :blogs, only: [:index, :show, :create, :update, :destroy]
+ end
+ end
end
diff --git a/db/migrate/20250318123655_create_blogs.rb b/db/migrate/20250318123655_create_blogs.rb
new file mode 100644
index 0000000..a26d0a2
--- /dev/null
+++ b/db/migrate/20250318123655_create_blogs.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateBlogs < ActiveRecord::Migration[7.2]
+ def change
+ create_table :spree_blogs do |t|
+ t.integer :status, default: 0, null: false, comment: '0 is draft, 1 is publish'
+ t.string :slug
+ t.string :title
+ t.string :subtitle
+ t.string :author
+ t.datetime :publishing_date
+ t.text :content
+ t.integer :position, default: 0
+ t.references :primary_taxon, type: :integer, foreign_key: { to_table: :spree_taxons }
+
+ t.timestamps
+ end
+
+ add_index :spree_blogs, :slug, unique: true
+ add_index :spree_blogs, :position
+ end
+end
diff --git a/db/migrate/20250318125427_create_join_table_blogs_stores.rb b/db/migrate/20250318125427_create_join_table_blogs_stores.rb
new file mode 100644
index 0000000..0d1dea4
--- /dev/null
+++ b/db/migrate/20250318125427_create_join_table_blogs_stores.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateJoinTableBlogsStores < ActiveRecord::Migration[7.2]
+ def change
+ create_table :spree_blogs_stores, id: false do |t|
+ t.integer :blog_id, null: false
+ t.integer :store_id, null: false
+ t.timestamps
+ end
+
+ add_index :spree_blogs_stores, :blog_id
+ add_index :spree_blogs_stores, :store_id
+
+ add_foreign_key :spree_blogs_stores, :spree_blogs, column: :blog_id
+ add_foreign_key :spree_blogs_stores, :spree_stores, column: :store_id
+ end
+end
diff --git a/db/migrate/20250319092255_add_image_type_to_spree_assets.rb b/db/migrate/20250319092255_add_image_type_to_spree_assets.rb
new file mode 100644
index 0000000..3c19bbb
--- /dev/null
+++ b/db/migrate/20250319092255_add_image_type_to_spree_assets.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddImageTypeToSpreeAssets < ActiveRecord::Migration[7.2]
+ def change
+ add_column :spree_assets, :image_type, :string
+ end
+end
diff --git a/db/migrate/20250320102130_create_spree_blogs_taxons.rb b/db/migrate/20250320102130_create_spree_blogs_taxons.rb
new file mode 100644
index 0000000..7942b42
--- /dev/null
+++ b/db/migrate/20250320102130_create_spree_blogs_taxons.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateSpreeBlogsTaxons < ActiveRecord::Migration[7.2]
+ def change
+ create_table :spree_blogs_taxons do |t|
+ t.integer :blog_id
+ t.integer :taxon_id
+ t.integer :position
+ t.timestamps
+ end
+ add_index :spree_blogs_taxons, :blog_id
+ add_index :spree_blogs_taxons, :taxon_id
+ add_index :spree_blogs_taxons, :position
+ end
+end
diff --git a/lib/controllers/backend/spree/admin/blogs_controller.rb b/lib/controllers/backend/spree/admin/blogs_controller.rb
new file mode 100644
index 0000000..58d2ca3
--- /dev/null
+++ b/lib/controllers/backend/spree/admin/blogs_controller.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+module Spree
+ module Admin
+ class BlogsController < Spree::Admin::ResourceController
+ before_action :split_params, only: [:create, :update]
+
+ def delete_image
+ image = @blog.images.find(params[:image_id])
+ image.destroy!
+ redirect_to edit_admin_blog_path(@blog)
+ end
+
+ # Override the default create and update method to call attach_image on save/update
+ # This is necessary because the image is uploaded via a nested form
+ def update
+ invoke_callbacks(:update, :before)
+ if @object.update(permitted_resource_params)
+ invoke_callbacks(:update, :after)
+ attach_images
+ respond_with(@object) do |format|
+ format.html do
+ flash[:success] = flash_message_for(@object, :successfully_updated)
+ redirect_to location_after_save
+ end
+ format.js { render layout: false }
+ end
+ else
+ invoke_callbacks(:update, :fails)
+ respond_with(@object) do |format|
+ format.html do
+ flash.now[:error] = @object.errors.full_messages.join(", ")
+ render_after_update_error
+ end
+ format.js { render layout: false }
+ end
+ end
+ end
+
+ def create
+ invoke_callbacks(:create, :before)
+ @object.attributes = permitted_resource_params
+ if @object.save
+ invoke_callbacks(:create, :after)
+ attach_images
+ flash[:success] = flash_message_for(@object, :successfully_created)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_save }
+ format.js { render layout: false }
+ end
+ else
+ invoke_callbacks(:create, :fails)
+ respond_with(@object) do |format|
+ format.html do
+ flash.now[:error] = @object.errors.full_messages.join(", ")
+ render_after_create_error
+ end
+ format.js { render layout: false }
+ end
+ end
+ end
+
+ private
+
+ def find_resource
+ Spree::Blog.friendly.find(params[:id])
+ end
+
+ def split_params
+ return if params[:blog][:taxon_ids].blank?
+
+ params[:blog][:taxon_ids] = params[:blog][:taxon_ids].split(',')
+ end
+
+ # Set some default attributes
+ def build_resource
+ super.tap do |resource|
+ if resource.stores.blank?
+ resource.stores << Spree::Store.default
+ end
+ end
+ end
+
+ def collection
+ super.ordered_by_position
+ end
+
+ def attach_images
+ if params[:blog][:author_image].present?
+ @blog.images.where(image_type: 'AuthorImage').destroy_all
+ @blog.attach_author_image(params[:blog][:author_image])
+ end
+
+ return if params[:blog][:article_images].blank?
+
+ valid_images = params[:blog][:article_images].reject(&:blank?)
+ @blog.attach_article_images(valid_images)
+ end
+
+ # Use the normal permitted blog params without including `attachments`
+ def permitted_resource_params
+ params.require(:blog).permit(permitted_blog_params)
+ end
+
+ def permitted_blog_params
+ [:title, :subtitle, :slug, :content, :status, :author, :publishing_date, :primary_taxon_id, { taxon_ids: [] }, { store_ids: [] }]
+ end
+ end
+ end
+end
diff --git a/lib/controllers/spree/api/blogs_controller.rb b/lib/controllers/spree/api/blogs_controller.rb
new file mode 100644
index 0000000..3ce27d4
--- /dev/null
+++ b/lib/controllers/spree/api/blogs_controller.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Spree
+ module Api
+ class BlogsController < Spree::Api::BaseController
+ respond_to :json
+
+ before_action :find_blog, only: [:show, :update, :destroy]
+
+ def index
+ authorize! :read, Spree::Blog
+ @blogs = Spree::Blog.includes(:images, :tags)
+ respond_with @blogs, include: ['author_image', 'article_images', 'tags']
+ end
+
+ def show
+ authorize! :read, @blog
+ render json: @blog.as_json(methods: [:author_image, :article_images, :tags])
+ end
+
+ def create
+ authorize! :create, Spree::Blog
+ @blog = Spree::Blog.new(blog_params)
+ if @blog.save
+ render json: @blog.as_json(methods: [:author_image, :article_images, :tags]), status: :created
+ else
+ render json: @blog.errors, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ authorize! :update, @blog
+ if @blog.update(blog_params)
+ render json: @blog.as_json(methods: [:author_image, :article_images, :tags]), status: :ok
+ else
+ render json: @blog.errors, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ authorize! :destroy, @blog
+ if @blog.destroy
+ render json: { message: "Successfully Destroyed", object: @blog }, status: :ok
+ else
+ invalid_resource!(@blog)
+ end
+ end
+
+ private
+
+ def find_blog
+ @blog = Spree::Blog.friendly.find(params[:id])
+ end
+
+ def blog_params
+ params.require(:blog).permit(permitted_blog_params)
+ end
+
+ def permitted_blog_params
+ [:title, :subtitle, :slug, :content, :status, :author, :publishing_date, :primary_taxon_id, { taxon_ids: [] }, { store_ids: [] }]
+ end
+ end
+ end
+end
diff --git a/lib/generators/solidus_static_content/install/install_generator.rb b/lib/generators/solidus_static_content/install/install_generator.rb
index 2910320..64b71ef 100644
--- a/lib/generators/solidus_static_content/install/install_generator.rb
+++ b/lib/generators/solidus_static_content/install/install_generator.rb
@@ -9,7 +9,7 @@ class InstallGenerator < Rails::Generators::Base
class_option :frontend, type: :string, default: 'classic'
def add_stylesheets
- inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_static_content\n", before: %r{\*/}, verbose: true # rubocop:disable Metrics/LineLength
+ inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_static_content\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength
end
def add_migrations
@@ -17,6 +17,7 @@ def add_migrations
end
def add_javascripts
+ append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_static_content\n"
empty_directory 'app/assets/javascripts'
end
@@ -42,7 +43,7 @@ def copy_starter_frontend_files
end
def run_migrations
- run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Metrics/LineLength
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength
if run_migrations
run 'bin/rails db:migrate'
else
diff --git a/lib/solidus_static_content/engine.rb b/lib/solidus_static_content/engine.rb
index d4d6752..f21821c 100644
--- a/lib/solidus_static_content/engine.rb
+++ b/lib/solidus_static_content/engine.rb
@@ -16,18 +16,58 @@ class Engine < Rails::Engine
g.test_framework :rspec
end
- initializer 'solidus_subscriptions.configure_backend' do
+ if SolidusSupport.api_available?
+ paths["app/controllers"] << "lib/controllers"
+ end
+
+ initializer 'solidus_static_content.configure_backend' do
next unless ::Spree::Backend::Config.respond_to?(:menu_items)
::Spree::Backend::Config.configure do |config|
- config.menu_items << config.class::MenuItem.new(
- [:pages],
- 'file-text',
- url: :admin_pages_path,
- condition: -> { can?(:admin, Spree::Page) },
- match_path: '/pages'
+ content_menu_item = config.class::MenuItem.new(
+ label: :contents,
+ icon: 'file-text',
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_pages_path },
+ condition: -> { can?(:admin, Spree::Page) || can?(:admin, Spree::Blog) },
+ match_path: /contents/
)
+ config.menu_items << content_menu_item
+
+ config.menu_items = config.menu_items.map do |item|
+ if item.label.to_sym == :contents
+ if item.respond_to?(:children)
+ unless item.children.any? { |child| child.label == :pages }
+ item.children << config.class::MenuItem.new(
+ label: :pages,
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_pages_path },
+ condition: -> { can?(:admin, Spree::Page) },
+ match_path: /pages/
+ )
+ end
+ unless item.children.any? { |child| child.label == :blogs }
+ item.children << config.class::MenuItem.new(
+ label: :blogs,
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_blogs_path },
+ condition: -> { can?(:admin, Spree::Blog) },
+ match_path: /blogs/
+ )
+ end
+ else
+ item.sections << :pages
+ item.sections << :blogs
+ end
+ end
+ item
+ end
end
end
+
+ def self.activate
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')).sort.each do |c|
+ Rails.configuration.cache_classes ? require(c) : load(c)
+ end
+ end
+
+ config.to_prepare(&method(:activate).to_proc)
end
end
diff --git a/lib/views/backend/spree/admin/blogs/_form.html.erb b/lib/views/backend/spree/admin/blogs/_form.html.erb
new file mode 100644
index 0000000..b3fbd97
--- /dev/null
+++ b/lib/views/backend/spree/admin/blogs/_form.html.erb
@@ -0,0 +1,118 @@
+
+
+
+ <%= f.label :title %> *
+ <%= f.text_field :title, class: 'form-control' %>
+ <%= f.error_message_on :title %>
+
+
+
+ <%= f.label :subtitle %> *
+ <%= f.text_field :subtitle, class: 'form-control' %>
+ <%= f.error_message_on :subtitle %>
+
+
+
+ <%= f.label :slug, class: ('required' if !f.object.new_record?) %>
+ <%= f.text_field :slug, class: 'fullwidth title', required: !f.object.new_record?, disabled: f.object.new_record? %>
+ <%= f.error_message_on :slug %>
+
+
+
+ <%= f.label :content %> *
+ <%= f.text_area :content, rows: 12, class: 'form-control' %>
+ <%= f.error_message_on :content %>
+
+
+
+
+
+
+
+
+
+ <%= f.label :status %>
+ <%= f.select :status,
+ Spree::Blog.statuses.keys.map { |k| [t("spree.statuses.#{k}"), k] },
+ { include_blank: false },
+ class: 'form-control' %>
+
+
+
+
+
+
+
+ <%= f.label :author %>
+ <%= f.text_field :author, class: 'form-control' %>
+
+
+
+ <%= f.label :publishing_date %>
+ <%= render "spree/admin/shared/datepicker", f: f, date_attr: :publishing_date %>
+ <%= f.error_message_on :publishing_date %>
+
+
+
+
+
+
+
+ <%= f.label :taxon_ids, plural_resource_name(Spree::Taxon) %>
+ <%= f.hidden_field :taxon_ids, value: @blog.taxon_ids.join(','), class: 'fullwidth' %>
+
+
+
+ <%= f.label :primary_taxon_id %>
+ <%= f.hidden_field :primary_taxon_id, value: @blog.primary_taxon_id, class: 'fullwidth' %>
+
+
+
+
+
diff --git a/lib/views/backend/spree/admin/blogs/edit.html.erb b/lib/views/backend/spree/admin/blogs/edit.html.erb
new file mode 100644
index 0000000..eec1b0e
--- /dev/null
+++ b/lib/views/backend/spree/admin/blogs/edit.html.erb
@@ -0,0 +1,25 @@
+<%= render 'spree/admin/shared/blogs_tabs' %>
+
+<% admin_breadcrumb(t('spree.contents')) %>
+<% admin_breadcrumb(link_to plural_resource_name(Spree::Blog), spree.admin_blogs_path) %>
+<% admin_breadcrumb(@blog.title) %>
+
+<% content_for :page_title do %>
+ <%= t("spree.edit_blog") %>
+<% end %>
+
+<%= render "spree/shared/error_messages", :target => @blog %>
+
+
+ <%= form_for [:admin, @blog] do |f| %>
+
+ <%= render partial: 'form', locals: { f: f, resource: @blog } %>
+
+ <% if can?(:update, Spree::Blog) %>
+
+ <%= render partial: 'spree/admin/shared/edit_resource_links' %>
+
+ <% end %>
+
+ <% end %>
+
diff --git a/lib/views/backend/spree/admin/blogs/index.html.erb b/lib/views/backend/spree/admin/blogs/index.html.erb
new file mode 100644
index 0000000..7e0af8d
--- /dev/null
+++ b/lib/views/backend/spree/admin/blogs/index.html.erb
@@ -0,0 +1,69 @@
+<% content_for :tabs do %>
+
+
+ <% if can?(:show, Spree::Blog) %>
+ <%= settings_tab_item plural_resource_name(Spree::Blog), spree.admin_blogs_path %>
+ <% end %>
+
+
+<% end %>
+
+<% admin_breadcrumb(t('spree.contents')) %>
+<% admin_breadcrumb(plural_resource_name(Spree::Blog)) %>
+
+<% content_for :page_actions do %>
+ <% if can?(:create, Spree::Blog) %>
+
+ <%= link_to t('spree.new_blog'), new_object_url, id: 'admin_new_blogs_link', class: 'btn btn-primary' %>
+
+ <% end %>
+<% end %>
+
+<% if @blogs.any? %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= Spree::Blog.human_attribute_name(:status) %>
+ <%= Spree::Blog.human_attribute_name(:title) %>
+ <%= Spree::Blog.human_attribute_name(:author) %>
+ <%= Spree::Blog.human_attribute_name(:publishing_date) %>
+
+
+
+
+ <% @blogs.each do |method| %>
+
+
+ <%= t("spree.statuses.#{method.status}") %>
+ <%= method.title %>
+ <%= method.author %>
+ <%= method.publishing_date %>
+
+ <% if can?(:update, method.becomes(Spree::Blog)) %>
+ <%= link_to_edit method.becomes(Spree::Blog), no_text: true %>
+ <% end %>
+ <% if can?(:destroy, method.becomes(Spree::Blog)) %>
+ <%= link_to_delete method.becomes(Spree::Blog), no_text: true %>
+ <% end %>
+
+
+ <% end %>
+
+
+<% else %>
+
+ <%= render 'spree/admin/shared/no_objects_found',
+ resource: Spree::Blog,
+ new_resource_url: new_object_url %>
+
+<% end %>
diff --git a/lib/views/backend/spree/admin/blogs/new.html.erb b/lib/views/backend/spree/admin/blogs/new.html.erb
new file mode 100644
index 0000000..464f261
--- /dev/null
+++ b/lib/views/backend/spree/admin/blogs/new.html.erb
@@ -0,0 +1,24 @@
+<%= render 'spree/admin/shared/blogs_tabs' %>
+
+<% admin_breadcrumb(t('spree.contents')) %>
+<% admin_breadcrumb(link_to plural_resource_name(Spree::Blog), spree.admin_blogs_path) %>
+
+<% content_for :page_title do %>
+ <%= t("spree.new_blog") %>
+<% end %>
+
+<%= render "spree/shared/error_messages", :target => @blog %>
+
+
+ <%= form_for [:admin, @blog] do |f| %>
+
+ <%= render partial: 'form', locals: { f: f, resource: @blog } %>
+
+ <% if can?(:create, Spree::Blog) %>
+
+ <%= render partial: 'spree/admin/shared/new_resource_links' %>
+
+ <% end %>
+
+ <% end %>
+
\ No newline at end of file
diff --git a/lib/views/backend/spree/admin/pages/index.html.erb b/lib/views/backend/spree/admin/pages/index.html.erb
index 05c19f1..ce88f18 100644
--- a/lib/views/backend/spree/admin/pages/index.html.erb
+++ b/lib/views/backend/spree/admin/pages/index.html.erb
@@ -3,7 +3,7 @@
<% content_for :page_actions do %>
<% if can?(:create, Spree::Page) %>
- <%= link_to t("spree.new_page"), new_object_url, icon: 'plus' %>
+ <%= link_to t("spree.new_page"), new_object_url, class: 'btn btn-primary' %>
<% end %>
<% end %>
diff --git a/lib/views/backend/spree/admin/shared/_blogs_tabs.html.erb b/lib/views/backend/spree/admin/shared/_blogs_tabs.html.erb
new file mode 100644
index 0000000..d8ebdb8
--- /dev/null
+++ b/lib/views/backend/spree/admin/shared/_blogs_tabs.html.erb
@@ -0,0 +1,9 @@
+<% content_for :tabs do %>
+
+
+ <% if can?(:display, Spree::Blog) %>
+ <%= settings_tab_item plural_resource_name(Spree::Blog), spree.admin_blogs_path %>
+ <% end %>
+
+
+<% end %>
\ No newline at end of file