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 :author_image %> + <%= f.file_field :author_image, class: 'form-control' %> + <%= f.error_message_on :author_image %> + <% if resource.author_image.present? %> +
+ + + + +
+ <%= image_tag resource.author_image.url, size: "100x100" %> + <%= link_to_delete 'Delete', url: delete_image_admin_blog_path(resource, image_id: resource.author_image.id), method: :delete, data: { confirm: 'Are you sure?' } %> +
+
+ <% end %> +
+ +
+ <%= f.label :article_images %> + <%= f.file_field :article_images, multiple: true, class: 'form-control' %> + <%= f.error_message_on :article_images %> + <% if resource.article_images.present? %> +
+ + <% resource.article_images.each do |image| %> + + + + <% end %> +
+ <%= image_tag image.url, size: "100x100" %> + <%= link_to_delete 'Delete', url: delete_image_admin_blog_path(resource, image_id: image.id), method: :delete, data: { confirm: 'Are you sure?' } %> +
+
+ <% end %> +
+
+ +
+
+ <%= 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 :stores, plural_resource_name(Spree::Store) %> + <% Spree::Store.all.each do |store| %> +
+ <%= f.label store.name.downcase.to_sym, { class: 'form-check-label' } do %> + <%= check_box_tag 'blog[store_ids][]', store.id, @blog.stores.include?(store), { class: 'form-check-input' } %> + <%= store.name %> + <% end %> +
+ <% end %> +
+
+ +
+
+ <%= 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 %> + +<% 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? %> + + + + + + + + + + + + + + + + + + + + + + <% @blogs.each do |method| %> + + + + + + + + + <% end %> + +
    <%= 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) %>
    <%= 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 %> +
    +<% 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 %> + +<% end %> \ No newline at end of file