From 4c0bdca4b48e51333b39c8450a187afcd97f59eb Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 17:45:51 +0530 Subject: [PATCH 01/10] Create migrations for blogs and related tables Create migrations to set up the database schema for blogs, including the `spree_blogs`, `spree_blogs_stores`, and `spree_blogs_taxons` tables. This establishes the necessary database structure for managing blogs. --- db/migrate/20250318123655_create_blogs.rb | 22 +++++++++++++++++++ ...18125427_create_join_table_blogs_stores.rb | 17 ++++++++++++++ ...19092255_add_image_type_to_spree_assets.rb | 7 ++++++ ...0250320102130_create_spree_blogs_taxons.rb | 15 +++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 db/migrate/20250318123655_create_blogs.rb create mode 100644 db/migrate/20250318125427_create_join_table_blogs_stores.rb create mode 100644 db/migrate/20250319092255_add_image_type_to_spree_assets.rb create mode 100644 db/migrate/20250320102130_create_spree_blogs_taxons.rb 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 From 4385bf2a485404f337c17dc8996afb6d24bda263 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 17:49:31 +0530 Subject: [PATCH 02/10] Add models and decorators for blog functionality Create `Spree::Blog` model with necessary associations and validations. This establishes the core structure for managing blogs within the application. Create `Spree::Tag` model to manage the relationship between blogs and taxons. This allows blogs to be associated with multiple taxons for better categorization. Decorated the `Spree::Taxon` model to include tags. This modification ensures that taxons can be associated with blogs through tags. --- app/models/spree/blog.rb | 85 +++++++++++++++++++++++++++++ app/models/spree/tag.rb | 13 +++++ app/models/spree/taxon_decorator.rb | 11 ++++ 3 files changed, 109 insertions(+) create mode 100644 app/models/spree/blog.rb create mode 100644 app/models/spree/tag.rb create mode 100644 app/models/spree/taxon_decorator.rb 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) From f122a2e368bc444b7f81a68309d6f79be40d79d1 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 17:55:19 +0530 Subject: [PATCH 03/10] Add backend controller for managing blogs Add backend controller for managing blogs, including actions for creating, updating, and deleting blogs and their associated images. This provides the necessary logic for blog management within the admin interface. --- .../backend/spree/admin/blogs_controller.rb | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 lib/controllers/backend/spree/admin/blogs_controller.rb 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 From b1c2382499676ee31a06002e4c3246d6d453e5d5 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 17:57:22 +0530 Subject: [PATCH 04/10] Add API controller for managing blogs Add controller for managing blogs, including actions for CRUD operations. This enables external systems to interact with the blog data through the API. --- lib/controllers/spree/api/blogs_controller.rb | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lib/controllers/spree/api/blogs_controller.rb 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 From aad42bd50aee314444dcbe5ebf642447a9199f3a Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 17:58:34 +0530 Subject: [PATCH 05/10] Add routes for managing blogs Add routes for blogs, including CRUD operations and image deletion. This enables the necessary endpoints for blog management within the admin interface and API. --- config/routes.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 From bb4119ecec1ba2b9fb92986e0abaa438be517f6e Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 18:00:06 +0530 Subject: [PATCH 06/10] Add views for managing blogs in the admin interface Add views for blogs in the admin interface, including forms for creating and editing blogs and a list view for displaying existing blogs. This provides the necessary UI components for blog management. --- .../backend/spree/admin/blogs/_form.html.erb | 118 ++++++++++++++++++ .../backend/spree/admin/blogs/edit.html.erb | 25 ++++ .../backend/spree/admin/blogs/index.html.erb | 69 ++++++++++ .../backend/spree/admin/blogs/new.html.erb | 24 ++++ .../backend/spree/admin/pages/index.html.erb | 2 +- .../spree/admin/shared/_blogs_tabs.html.erb | 9 ++ 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 lib/views/backend/spree/admin/blogs/_form.html.erb create mode 100644 lib/views/backend/spree/admin/blogs/edit.html.erb create mode 100644 lib/views/backend/spree/admin/blogs/index.html.erb create mode 100644 lib/views/backend/spree/admin/blogs/new.html.erb create mode 100644 lib/views/backend/spree/admin/shared/_blogs_tabs.html.erb 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 From 61df613217c1dc0807a6e3701407602fe41e3379 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 18:03:08 +0530 Subject: [PATCH 07/10] Add JavaScript file for blog taxon autocomplete Add JavaScript file for taxon autocomplete for blogs. This enhances the user experience by providing a convenient way to select taxons when creating or editing blogs. --- .../spree/backend/solidus_static_content.js | 3 + .../views/blogs/taxonblog_autocomplete.js | 87 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 app/assets/javascripts/spree/backend/solidus_static_content.js create mode 100644 app/assets/javascripts/spree/backend/views/blogs/taxonblog_autocomplete.js 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 From 6057ad2bd50fb08e83a3871d5ac831d62d4fde38 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 18:04:08 +0530 Subject: [PATCH 08/10] Update locales to include blog-related translations --- config/locales/en.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 From c300773918b3a687deee0ed7a6ad058cdb42a5c6 Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 18:04:37 +0530 Subject: [PATCH 09/10] Update Solidus Static Content engine to include blog management Updated the Solidus Static Content engine to include blog management, including menu items and activation logic. This integrates blog management into the existing Solidus Static Content functionality. It also adds javascript using generator. --- .../install/install_generator.rb | 5 +- lib/solidus_static_content/engine.rb | 54 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) 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 From a31373219bca9e1999c6286d61671f3d9061d99c Mon Sep 17 00:00:00 2001 From: Mayur Shah Date: Thu, 20 Mar 2025 18:05:38 +0530 Subject: [PATCH 10/10] Update .rubocop_todo.yml to include new offenses --- .rubocop_todo.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) 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