app/models/panda/cms/post.rb in panda-cms-0.7.0 vs app/models/panda/cms/post.rb in panda-cms-0.7.2

- old
+ new

@@ -3,41 +3,70 @@ module Panda module CMS class Post < ApplicationRecord include ::Panda::CMS::EditorJsContent + after_commit :clear_menu_cache + before_validation :format_slug + self.table_name = "panda_cms_posts" has_paper_trail versions: { class_name: "Panda::CMS::PostVersion" } belongs_to :user, class_name: "Panda::CMS::User" + belongs_to :author, class_name: "Panda::CMS::User" validates :title, presence: true validates :slug, presence: true, uniqueness: true, format: { - with: /\A\/[a-z0-9-]+\z/, - message: "must start with a forward slash and contain only lowercase letters, numbers, and hyphens" + with: %r{\A(/\d{4}/\d{2}/[a-z0-9-]+|/[a-z0-9-]+)\z}, + message: "must be in format /YYYY/MM/slug or /slug with only lowercase letters, numbers, and hyphens" } scope :ordered, -> { order(published_at: :desc) } scope :with_user, -> { includes(:user) } + scope :with_author, -> { includes(:author) } enum :status, { active: "active", draft: "draft", hidden: "hidden", archived: "archived" } def to_param - slug.delete_prefix("/") + # For date-based URLs, return just the slug portion + parts = CGI.unescape(slug).delete_prefix("/").split("/") + if parts.length == 3 # year/month/slug format + parts.last + else + parts.first + end end + def year + return nil unless slug.match?(%r{\A/\d{4}/}) + slug.split("/")[1] + end + + def month + return nil unless slug.match?(%r{\A/\d{4}/\d{2}/}) + slug.split("/")[2] + end + + def route_params + if year && month + {year: year, month: month, slug: to_param} + else + {slug: to_param} + end + end + def admin_param id end def excerpt(length = 100, squish: true) @@ -52,9 +81,38 @@ content.to_s end text = text.squish if squish text.truncate(length).html_safe + end + + private + + def clear_menu_cache + Rails.cache.delete("panda_cms_news_months") + end + + def format_slug + return if slug.blank? + + # Remove any leading/trailing slashes and decode + self.slug = CGI.unescape(slug.strip.gsub(%r{^/+|/+$}, "")) + + # Handle the case where we already have a properly formatted slug + if slug.match?(%r{\A\d{4}/\d{2}/[^/]+\z}) + return self.slug = "/#{slug}" + end + + # Handle the case where we have a date-prefixed slug (from JS) + if (match = slug.match(%r{\A(\d{4})-(\d{2})-(.+)\z})) + year, month, base_slug = match[1], match[2], match[3] + return self.slug = "/#{year}/#{month}/#{base_slug}" + end + + # For new slugs without any date structure + base_slug = slug.downcase.gsub(/[^a-z0-9-]+/, "-").gsub(/^-+|-+$/, "") + date_prefix = published_at.present? ? published_at.strftime("%Y/%m") : Time.current.strftime("%Y/%m") + self.slug = "/#{date_prefix}/#{base_slug}" end end end end