panda-cms 0.8.2 → 0.10.2
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 +4 -4
- data/README.md +75 -5
- data/app/components/panda/cms/code_component.rb +154 -39
- data/app/components/panda/cms/grid_component.rb +26 -6
- data/app/components/panda/cms/menu_component.rb +72 -34
- data/app/components/panda/cms/page_menu_component.rb +102 -13
- data/app/components/panda/cms/rich_text_component.rb +229 -139
- data/app/components/panda/cms/text_component.rb +107 -42
- data/app/controllers/panda/cms/admin/base_controller.rb +19 -3
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +3 -3
- data/app/controllers/panda/cms/admin/files_controller.rb +7 -0
- data/app/controllers/panda/cms/admin/menus_controller.rb +47 -3
- data/app/controllers/panda/cms/admin/pages_controller.rb +11 -2
- data/app/controllers/panda/cms/admin/posts_controller.rb +3 -1
- data/app/controllers/panda/cms/form_submissions_controller.rb +134 -11
- data/app/controllers/panda/cms/pages_controller.rb +7 -2
- data/app/controllers/panda/cms/posts_controller.rb +16 -0
- data/app/helpers/panda/cms/application_helper.rb +17 -4
- data/app/helpers/panda/cms/asset_helper.rb +14 -61
- data/app/helpers/panda/cms/forms_helper.rb +60 -0
- data/app/helpers/panda/cms/seo_helper.rb +85 -0
- data/app/javascript/panda/cms/{application_panda_cms.js → application.js} +5 -1
- data/app/javascript/panda/cms/controllers/code_editor_controller.js +95 -0
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +31 -4
- data/app/javascript/panda/cms/controllers/file_gallery_controller.js +128 -0
- data/app/javascript/panda/cms/controllers/file_upload_controller.js +165 -0
- data/app/javascript/panda/cms/controllers/index.js +54 -13
- data/app/javascript/panda/cms/controllers/inline_code_editor_controller.js +96 -0
- data/app/javascript/panda/cms/controllers/menu_form_controller.js +53 -0
- data/app/javascript/panda/cms/controllers/nested_form_controller.js +35 -0
- data/app/javascript/panda/cms/controllers/page_form_controller.js +454 -0
- data/app/javascript/panda/cms/controllers/tree_controller.js +214 -0
- data/app/javascript/panda/cms/stimulus-loading.js +6 -7
- data/app/models/panda/cms/block_content.rb +9 -0
- data/app/models/panda/cms/menu.rb +12 -0
- data/app/models/panda/cms/page.rb +147 -0
- data/app/models/panda/cms/post.rb +98 -0
- data/app/views/layouts/homepage.html.erb +1 -4
- data/app/views/layouts/page.html.erb +1 -4
- data/app/views/panda/cms/admin/dashboard/show.html.erb +5 -5
- data/app/views/panda/cms/admin/files/_file_details.html.erb +45 -0
- data/app/views/panda/cms/admin/files/index.html.erb +11 -118
- data/app/views/panda/cms/admin/forms/index.html.erb +2 -2
- data/app/views/panda/cms/admin/forms/new.html.erb +1 -2
- data/app/views/panda/cms/admin/forms/show.html.erb +15 -30
- data/app/views/panda/cms/admin/menus/_menu_item_fields.html.erb +11 -0
- data/app/views/panda/cms/admin/menus/edit.html.erb +62 -0
- data/app/views/panda/cms/admin/menus/index.html.erb +3 -2
- data/app/views/panda/cms/admin/menus/new.html.erb +38 -0
- data/app/views/panda/cms/admin/pages/edit.html.erb +147 -22
- data/app/views/panda/cms/admin/pages/index.html.erb +49 -11
- data/app/views/panda/cms/admin/pages/new.html.erb +3 -11
- data/app/views/panda/cms/admin/posts/_form.html.erb +44 -15
- data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +6 -6
- data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +3 -3
- data/app/views/shared/_header.html.erb +1 -4
- data/config/brakeman.ignore +38 -0
- data/config/importmap.rb +10 -10
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb.disabled +31 -0
- data/config/initializers/panda/cms.rb +52 -10
- data/config/locales/en.yml +41 -0
- data/config/routes.rb +5 -3
- data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +2 -2
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +6 -1
- data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +23 -21
- data/db/migrate/20251104150640_add_cached_last_updated_at_to_panda_cms_pages.rb +22 -0
- data/db/migrate/20251104172242_add_page_type_to_panda_cms_pages.rb +6 -0
- data/db/migrate/20251104172638_set_page_types_for_existing_pages.rb +27 -0
- data/db/migrate/20251105000001_add_pending_review_status_to_pages_and_posts.panda_cms.rb +21 -0
- data/db/migrate/20251109131150_add_seo_fields_to_pages.rb +32 -0
- data/db/migrate/20251109131205_add_seo_fields_to_posts.rb +27 -0
- data/db/migrate/20251110114258_add_spam_tracking_to_form_submissions.rb +7 -0
- data/db/migrate/20251110122812_add_performance_indexes_to_pages_and_redirects.rb +13 -0
- data/lib/generators/panda/cms/install_generator.rb +2 -5
- data/lib/panda/cms/asset_loader.rb +46 -76
- data/lib/panda/cms/bulk_editor.rb +288 -12
- data/lib/panda/cms/debug.rb +29 -0
- data/lib/panda/cms/engine/asset_config.rb +49 -0
- data/lib/panda/cms/engine/autoload_config.rb +19 -0
- data/lib/panda/cms/engine/backtrace_config.rb +42 -0
- data/lib/panda/cms/engine/core_config.rb +106 -0
- data/lib/panda/cms/engine/helper_config.rb +20 -0
- data/lib/panda/cms/engine/route_config.rb +34 -0
- data/lib/panda/cms/engine/view_component_config.rb +31 -0
- data/lib/panda/cms/engine.rb +44 -162
- data/lib/panda/cms/features.rb +52 -0
- data/lib/panda/cms.rb +10 -0
- data/lib/panda-cms/version.rb +1 -1
- data/lib/panda-cms.rb +20 -7
- data/lib/tasks/panda_cms_tasks.rake +16 -0
- metadata +41 -50
- data/app/components/panda/cms/admin/container_component.html.erb +0 -13
- data/app/components/panda/cms/admin/flash_message_component.html.erb +0 -31
- data/app/components/panda/cms/admin/panel_component.html.erb +0 -7
- data/app/components/panda/cms/admin/slideover_component.html.erb +0 -9
- data/app/components/panda/cms/admin/slideover_component.rb +0 -15
- data/app/components/panda/cms/admin/statistics_component.html.erb +0 -4
- data/app/components/panda/cms/admin/statistics_component.rb +0 -16
- data/app/components/panda/cms/admin/tab_bar_component.html.erb +0 -35
- data/app/components/panda/cms/admin/tab_bar_component.rb +0 -15
- data/app/components/panda/cms/admin/table_component.html.erb +0 -29
- data/app/components/panda/cms/admin/user_activity_component.html.erb +0 -7
- data/app/components/panda/cms/admin/user_activity_component.rb +0 -20
- data/app/components/panda/cms/admin/user_display_component.html.erb +0 -17
- data/app/components/panda/cms/admin/user_display_component.rb +0 -21
- data/app/components/panda/cms/grid_component.html.erb +0 -6
- data/app/components/panda/cms/menu_component.html.erb +0 -6
- data/app/components/panda/cms/page_menu_component.html.erb +0 -21
- data/app/components/panda/cms/rich_text_component.html.erb +0 -90
- data/app/javascript/panda_cms/stimulus-loading.js +0 -39
- data/app/views/layouts/panda/cms/application.html.erb +0 -42
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +0 -28
- data/app/views/panda/cms/admin/shared/_flash.html.erb +0 -5
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +0 -41
- data/app/views/panda/cms/shared/_footer.html.erb +0 -2
- data/app/views/panda/cms/shared/_header.html.erb +0 -25
- data/app/views/panda/cms/shared/_importmap.html.erb +0 -34
- data/config/initializers/inflections.rb +0 -5
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +0 -13
- data/lib/tasks/assets.rake +0 -587
data/config/importmap.rb
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Base dependencies are now in panda-core (Stimulus, Turbo, Font Awesome, etc.)
|
|
4
|
+
# This file only contains CMS-specific pins
|
|
5
|
+
# NOTE: Paths must be absolute (starting with /) because Rack::Static serves
|
|
6
|
+
# from /panda/cms/, not from asset pipeline /assets/
|
|
4
7
|
|
|
5
|
-
pin "
|
|
6
|
-
pin "@
|
|
7
|
-
pin "@
|
|
8
|
-
pin "@hotwired/stimulus-loading", to: "panda_cms/stimulus-loading.js", preload: true
|
|
9
|
-
pin "tailwindcss-stimulus-components" # @6.1.2
|
|
10
|
-
pin "@editorjs/editorjs", to: "panda/cms/editor/editorjs.js" # @2.30.6
|
|
8
|
+
pin "panda/cms/application", to: "/panda/cms/application.js", preload: true
|
|
9
|
+
pin "@hotwired/stimulus-loading", to: "/panda/cms/stimulus-loading.js", preload: true
|
|
10
|
+
pin "@editorjs/editorjs", to: "/panda/cms/editor/editorjs.js" # @2.30.6
|
|
11
11
|
|
|
12
12
|
# Pin the controllers directory
|
|
13
|
-
pin "controllers", to: "panda/cms/controllers/index.js"
|
|
14
|
-
pin_all_from Panda::CMS::Engine.root.join("app/javascript/panda/cms/controllers"), under: "controllers"
|
|
15
|
-
pin_all_from Panda::CMS::Engine.root.join("app/javascript/panda/cms/editor"), under: "editor"
|
|
13
|
+
pin "panda/cms/controllers/index", to: "/panda/cms/controllers/index.js"
|
|
14
|
+
pin_all_from Panda::CMS::Engine.root.join("app/javascript/panda/cms/controllers"), under: "controllers", to: "/panda/cms/controllers"
|
|
15
|
+
pin_all_from Panda::CMS::Engine.root.join("app/javascript/panda/cms/editor"), under: "editor", to: "/panda/cms/editor"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "silencer/rails/logger"
|
|
4
|
+
|
|
5
|
+
# Don't log requests to the healthcheck endpoint
|
|
6
|
+
Rails.application.configure do
|
|
7
|
+
# Rails 8 renamed Rails::Rack::Logger to ActionDispatch::Request::Logger
|
|
8
|
+
# Determine which logger middleware class to use
|
|
9
|
+
logger_class = if Object.const_defined?("ActionDispatch::Request::Logger")
|
|
10
|
+
ActionDispatch::Request::Logger
|
|
11
|
+
elsif Object.const_defined?("Rails::Rack::Logger")
|
|
12
|
+
Rails::Rack::Logger
|
|
13
|
+
else
|
|
14
|
+
# If neither exists, skip the middleware swap
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if logger_class
|
|
19
|
+
begin
|
|
20
|
+
config.middleware.swap(
|
|
21
|
+
logger_class,
|
|
22
|
+
Silencer::Logger,
|
|
23
|
+
config.log_tags,
|
|
24
|
+
silence: ["/up"]
|
|
25
|
+
)
|
|
26
|
+
rescue RuntimeError => e
|
|
27
|
+
# Silently fail if middleware doesn't exist in stack
|
|
28
|
+
raise unless e.message.include?("No such middleware")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Panda
|
|
4
|
-
|
|
5
|
-
config.title = "Demo Site"
|
|
6
|
-
|
|
7
|
-
# Site access control
|
|
8
|
-
config.require_login_to_view = false
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Admin path is now configured via Panda::Core
|
|
3
|
+
# This file is an example of Panda configuration.
|
|
4
|
+
# In your application, this should be at config/initializers/panda.rb
|
|
12
5
|
Panda::Core.configure do |config|
|
|
13
|
-
# The path to the administration panel
|
|
14
6
|
config.admin_path = "/admin"
|
|
7
|
+
|
|
8
|
+
config.login_page_title = "Panda Admin"
|
|
9
|
+
|
|
10
|
+
# Configure authentication providers
|
|
11
|
+
# Uncomment and configure the providers you want to use
|
|
12
|
+
# Don't forget to add the corresponding gems (e.g., omniauth-google-oauth2)
|
|
13
|
+
#
|
|
14
|
+
# config.authentication_providers = {
|
|
15
|
+
# google_oauth2: {
|
|
16
|
+
# enabled: true,
|
|
17
|
+
# name: "Google", # Display name for the button
|
|
18
|
+
# client_id: Rails.application.credentials.dig(:google, :client_id),
|
|
19
|
+
# client_secret: Rails.application.credentials.dig(:google, :client_secret),
|
|
20
|
+
# options: {
|
|
21
|
+
# scope: "email,profile",
|
|
22
|
+
# prompt: "select_account",
|
|
23
|
+
# hd: "yourdomain.com" # Specify your domain here if you want to restrict admin logins
|
|
24
|
+
# }
|
|
25
|
+
# }
|
|
26
|
+
# }
|
|
27
|
+
|
|
28
|
+
# Configure the session token cookie name
|
|
29
|
+
config.session_token_cookie = :panda_session
|
|
30
|
+
|
|
31
|
+
# Configure the user class for the application
|
|
32
|
+
config.user_class = "Panda::Core::User"
|
|
33
|
+
|
|
34
|
+
# Configure the user identity class for the application
|
|
35
|
+
config.user_identity_class = "Panda::Core::UserIdentity"
|
|
36
|
+
|
|
37
|
+
# Configure the storage provider (default: :active_storage)
|
|
38
|
+
# config.storage_provider = :active_storage
|
|
39
|
+
|
|
40
|
+
# Configure the cache store (default: :memory_store)
|
|
41
|
+
# config.cache_store = :memory_store
|
|
15
42
|
end
|
|
43
|
+
|
|
44
|
+
# Optional CMS-specific configuration
|
|
45
|
+
# Panda::CMS.configure do |config|
|
|
46
|
+
# # Site access control
|
|
47
|
+
# config.require_login_to_view = false
|
|
48
|
+
# end
|
|
49
|
+
|
|
50
|
+
# Optional EditorJS configuration
|
|
51
|
+
# Panda::Editor.configure do |config|
|
|
52
|
+
# # Additional EditorJS tools to load
|
|
53
|
+
# # config.editor_js_tools = []
|
|
54
|
+
#
|
|
55
|
+
# # EditorJS tool configurations
|
|
56
|
+
# # config.editor_js_tool_config = {}
|
|
57
|
+
# end
|
data/config/locales/en.yml
CHANGED
|
@@ -29,6 +29,17 @@ en:
|
|
|
29
29
|
title: Title
|
|
30
30
|
path: URL
|
|
31
31
|
panda_cms_template_id: Template
|
|
32
|
+
page_type: Page Type
|
|
33
|
+
seo_title: SEO Title
|
|
34
|
+
seo_description: SEO Description
|
|
35
|
+
seo_keywords: SEO Keywords
|
|
36
|
+
seo_index_mode: Search Engine Visibility
|
|
37
|
+
canonical_url: Canonical URL
|
|
38
|
+
og_title: OpenGraph Title
|
|
39
|
+
og_description: OpenGraph Description
|
|
40
|
+
og_type: OpenGraph Type
|
|
41
|
+
og_image: OpenGraph Image
|
|
42
|
+
inherit_seo: Inherit Settings
|
|
32
43
|
panda/cms/post:
|
|
33
44
|
title: Title
|
|
34
45
|
slug: URL
|
|
@@ -36,6 +47,15 @@ en:
|
|
|
36
47
|
user_id: Author
|
|
37
48
|
published_at: Published At
|
|
38
49
|
post_content: Content
|
|
50
|
+
seo_title: SEO Title
|
|
51
|
+
seo_description: SEO Description
|
|
52
|
+
seo_keywords: SEO Keywords
|
|
53
|
+
seo_index_mode: Search Engine Visibility
|
|
54
|
+
canonical_url: Canonical URL
|
|
55
|
+
og_title: OpenGraph Title
|
|
56
|
+
og_description: OpenGraph Description
|
|
57
|
+
og_type: OpenGraph Type
|
|
58
|
+
og_image: OpenGraph Image
|
|
39
59
|
statuses:
|
|
40
60
|
active: Active
|
|
41
61
|
draft: Draft
|
|
@@ -52,3 +72,24 @@ en:
|
|
|
52
72
|
lastname: Last Name
|
|
53
73
|
email: Email Address
|
|
54
74
|
current_theme: Theme
|
|
75
|
+
enums:
|
|
76
|
+
panda/cms/page:
|
|
77
|
+
seo_index_mode:
|
|
78
|
+
visible: Visible to search engines
|
|
79
|
+
invisible: Hidden from search engines
|
|
80
|
+
og_type:
|
|
81
|
+
website: Website
|
|
82
|
+
article: Article
|
|
83
|
+
profile: Profile
|
|
84
|
+
video: Video
|
|
85
|
+
book: Book
|
|
86
|
+
panda/cms/post:
|
|
87
|
+
seo_index_mode:
|
|
88
|
+
visible: Visible to search engines
|
|
89
|
+
invisible: Hidden from search engines
|
|
90
|
+
og_type:
|
|
91
|
+
website: Website
|
|
92
|
+
article: Article
|
|
93
|
+
profile: Profile
|
|
94
|
+
video: Video
|
|
95
|
+
book: Book
|
data/config/routes.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Panda::CMS::Engine.routes.draw do
|
|
4
|
-
|
|
4
|
+
# Test authentication endpoint moved to panda-core at /admin/test_login/:user_id
|
|
5
|
+
|
|
6
|
+
constraints Panda::Core::AdminConstraint.new do
|
|
5
7
|
# CMS-specific dashboard (using Core's admin_path)
|
|
6
|
-
admin_path = Panda::Core.
|
|
8
|
+
admin_path = Panda::Core.config.admin_path
|
|
7
9
|
get "#{admin_path}/cms", to: "admin/dashboard#show", as: :admin_cms_dashboard
|
|
8
10
|
|
|
9
11
|
namespace admin_path.delete_prefix("/").to_sym, path: "#{admin_path}/cms", as: :admin_cms, module: :admin do
|
|
10
12
|
resources :files
|
|
11
|
-
resources :forms
|
|
13
|
+
resources :forms
|
|
12
14
|
resources :menus
|
|
13
15
|
resources :pages do
|
|
14
16
|
resources :block_contents, only: %i[update]
|
|
@@ -4,12 +4,12 @@ class ConvertHtmlContentToEditorJs < ActiveRecord::Migration[7.1]
|
|
|
4
4
|
def up
|
|
5
5
|
# First, let's check if the converter service exists
|
|
6
6
|
converter_path = Panda::CMS::Engine.root.join("app/services/panda/cms/html_to_editor_js_converter.rb")
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
unless File.exist?(converter_path)
|
|
9
9
|
Rails.logger.info "HtmlToEditorJsConverter service not found. Skipping HTML to EditorJS conversion."
|
|
10
10
|
return
|
|
11
11
|
end
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
require converter_path
|
|
14
14
|
|
|
15
15
|
# Check if we have any existing valid EditorJS content
|
|
@@ -8,7 +8,12 @@ class AddNestedSetsToPandaCMSPages < ActiveRecord::Migration[7.1]
|
|
|
8
8
|
|
|
9
9
|
# This is necessary to update :lft and :rgt columns
|
|
10
10
|
Panda::CMS::Page.reset_column_information
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
# Only rebuild if there are existing pages
|
|
13
|
+
# On fresh installs, there won't be any pages yet
|
|
14
|
+
if Panda::CMS::Page.any?
|
|
15
|
+
Panda::CMS::Page.rebuild!
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def self.down
|
|
@@ -7,27 +7,29 @@ class MigrateUsersToPandaCore < ActiveRecord::Migration[8.0]
|
|
|
7
7
|
if table_exists?(:panda_cms_users) && table_exists?(:panda_core_users)
|
|
8
8
|
# Check if there's any data to migrate
|
|
9
9
|
cms_user_count = ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM panda_cms_users")
|
|
10
|
-
return if cms_user_count == 0
|
|
11
|
-
# Copy all user data
|
|
12
|
-
execute <<-SQL
|
|
13
|
-
INSERT INTO panda_core_users (
|
|
14
|
-
id, name, email, image_url, is_admin, created_at, updated_at
|
|
15
|
-
)
|
|
16
|
-
SELECT
|
|
17
|
-
id,
|
|
18
|
-
COALESCE(name, CONCAT(firstname, ' ', lastname), 'Unknown User'),
|
|
19
|
-
email,
|
|
20
|
-
image_url,
|
|
21
|
-
COALESCE(admin, false),
|
|
22
|
-
created_at,
|
|
23
|
-
updated_at
|
|
24
|
-
FROM panda_cms_users
|
|
25
|
-
WHERE NOT EXISTS (
|
|
26
|
-
SELECT 1 FROM panda_core_users WHERE panda_core_users.id = panda_cms_users.id
|
|
27
|
-
)
|
|
28
|
-
SQL
|
|
29
10
|
|
|
30
|
-
|
|
11
|
+
if cms_user_count > 0
|
|
12
|
+
# Copy all user data
|
|
13
|
+
execute <<-SQL
|
|
14
|
+
INSERT INTO panda_core_users (
|
|
15
|
+
id, name, email, image_url, is_admin, created_at, updated_at
|
|
16
|
+
)
|
|
17
|
+
SELECT
|
|
18
|
+
id,
|
|
19
|
+
COALESCE(name, CONCAT(firstname, ' ', lastname), 'Unknown User'),
|
|
20
|
+
email,
|
|
21
|
+
image_url,
|
|
22
|
+
COALESCE(admin, false),
|
|
23
|
+
created_at,
|
|
24
|
+
updated_at
|
|
25
|
+
FROM panda_cms_users
|
|
26
|
+
WHERE NOT EXISTS (
|
|
27
|
+
SELECT 1 FROM panda_core_users WHERE panda_core_users.id = panda_cms_users.id
|
|
28
|
+
)
|
|
29
|
+
SQL
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Update foreign key references in other tables (always do this, even if no data)
|
|
31
33
|
|
|
32
34
|
# Posts author_id
|
|
33
35
|
if column_exists?(:panda_cms_posts, :author_id)
|
|
@@ -47,7 +49,7 @@ class MigrateUsersToPandaCore < ActiveRecord::Migration[8.0]
|
|
|
47
49
|
add_foreign_key :panda_cms_visits, :panda_core_users, column: :user_id, primary_key: :id
|
|
48
50
|
end
|
|
49
51
|
|
|
50
|
-
#
|
|
52
|
+
# Always drop the old table if it exists (even if it was empty)
|
|
51
53
|
drop_table :panda_cms_users
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class AddCachedLastUpdatedAtToPandaCMSPages < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :panda_cms_pages, :cached_last_updated_at, :datetime
|
|
4
|
+
add_index :panda_cms_pages, :cached_last_updated_at
|
|
5
|
+
|
|
6
|
+
# Backfill existing pages
|
|
7
|
+
reversible do |dir|
|
|
8
|
+
dir.up do
|
|
9
|
+
execute <<-SQL
|
|
10
|
+
UPDATE panda_cms_pages
|
|
11
|
+
SET cached_last_updated_at = GREATEST(
|
|
12
|
+
updated_at,
|
|
13
|
+
COALESCE(
|
|
14
|
+
(SELECT MAX(updated_at) FROM panda_cms_block_contents WHERE panda_cms_page_id = panda_cms_pages.id),
|
|
15
|
+
updated_at
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
SQL
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class SetPageTypesForExistingPages < ActiveRecord::Migration[8.0]
|
|
2
|
+
def up
|
|
3
|
+
# Set system type for error pages
|
|
4
|
+
execute <<-SQL
|
|
5
|
+
UPDATE panda_cms_pages
|
|
6
|
+
SET page_type = 'system'
|
|
7
|
+
WHERE path IN ('/404', '/500')
|
|
8
|
+
SQL
|
|
9
|
+
|
|
10
|
+
# Set posts type for news/blog pages
|
|
11
|
+
execute <<-SQL
|
|
12
|
+
UPDATE panda_cms_pages
|
|
13
|
+
SET page_type = 'posts'
|
|
14
|
+
WHERE path LIKE '%news%' OR path LIKE '%blog%' OR path LIKE '%updates%'
|
|
15
|
+
SQL
|
|
16
|
+
|
|
17
|
+
# All other pages remain as 'standard' (the default)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def down
|
|
21
|
+
# Reset all to standard
|
|
22
|
+
execute <<-SQL
|
|
23
|
+
UPDATE panda_cms_pages
|
|
24
|
+
SET page_type = 'standard'
|
|
25
|
+
SQL
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddPendingReviewStatusToPagesAndPosts < ActiveRecord::Migration[8.0]
|
|
4
|
+
def up
|
|
5
|
+
# Add pending_review status to pages and posts for content approval workflow
|
|
6
|
+
# This allows content creators to submit drafts for editorial review
|
|
7
|
+
|
|
8
|
+
# Note: We're not modifying the enum directly in the database
|
|
9
|
+
# Rails enums are string-based, so we just document the new allowed value
|
|
10
|
+
# The application code will handle the new status value
|
|
11
|
+
|
|
12
|
+
# Update any existing pages/posts that might benefit from this status
|
|
13
|
+
# (In a real migration, you'd add logic here if needed)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def down
|
|
17
|
+
# Convert any pending_review items back to draft
|
|
18
|
+
Panda::CMS::Page.where(status: "pending_review").update_all(status: "draft")
|
|
19
|
+
Panda::CMS::Post.where(status: "pending_review").update_all(status: "draft")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class AddSEOFieldsToPages < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
# Create enum types for SEO fields
|
|
4
|
+
create_enum :panda_cms_seo_index_mode, ["visible", "invisible"]
|
|
5
|
+
create_enum :panda_cms_og_type, ["website", "article", "profile", "video", "book"]
|
|
6
|
+
|
|
7
|
+
# Add SEO basic fields
|
|
8
|
+
add_column :panda_cms_pages, :seo_title, :string
|
|
9
|
+
add_column :panda_cms_pages, :seo_description, :text
|
|
10
|
+
add_column :panda_cms_pages, :seo_keywords, :string
|
|
11
|
+
|
|
12
|
+
# Robots control (visible/invisible in base CMS)
|
|
13
|
+
add_column :panda_cms_pages, :seo_index_mode, :enum,
|
|
14
|
+
enum_type: "panda_cms_seo_index_mode",
|
|
15
|
+
default: "visible",
|
|
16
|
+
null: false
|
|
17
|
+
|
|
18
|
+
# Canonical URL
|
|
19
|
+
add_column :panda_cms_pages, :canonical_url, :string
|
|
20
|
+
|
|
21
|
+
# OpenGraph / Social Sharing
|
|
22
|
+
add_column :panda_cms_pages, :og_title, :string
|
|
23
|
+
add_column :panda_cms_pages, :og_description, :text
|
|
24
|
+
add_column :panda_cms_pages, :og_type, :enum,
|
|
25
|
+
enum_type: "panda_cms_og_type",
|
|
26
|
+
default: "website",
|
|
27
|
+
null: false
|
|
28
|
+
|
|
29
|
+
# Inheritance (pages only - they have hierarchy via nested sets)
|
|
30
|
+
add_column :panda_cms_pages, :inherit_seo, :boolean, default: true, null: false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class AddSEOFieldsToPosts < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
# Add SEO basic fields
|
|
4
|
+
add_column :panda_cms_posts, :seo_title, :string
|
|
5
|
+
add_column :panda_cms_posts, :seo_description, :text
|
|
6
|
+
add_column :panda_cms_posts, :seo_keywords, :string
|
|
7
|
+
|
|
8
|
+
# Robots control (visible/invisible in base CMS)
|
|
9
|
+
add_column :panda_cms_posts, :seo_index_mode, :enum,
|
|
10
|
+
enum_type: "panda_cms_seo_index_mode",
|
|
11
|
+
default: "visible",
|
|
12
|
+
null: false
|
|
13
|
+
|
|
14
|
+
# Canonical URL
|
|
15
|
+
add_column :panda_cms_posts, :canonical_url, :string
|
|
16
|
+
|
|
17
|
+
# OpenGraph / Social Sharing
|
|
18
|
+
add_column :panda_cms_posts, :og_title, :string
|
|
19
|
+
add_column :panda_cms_posts, :og_description, :text
|
|
20
|
+
add_column :panda_cms_posts, :og_type, :enum,
|
|
21
|
+
enum_type: "panda_cms_og_type",
|
|
22
|
+
default: "article", # Posts default to 'article' type
|
|
23
|
+
null: false
|
|
24
|
+
|
|
25
|
+
# Note: No inherit_seo for posts - they don't have hierarchy
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class AddSpamTrackingToFormSubmissions < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :panda_cms_form_submissions, :ip_address, :string
|
|
4
|
+
add_column :panda_cms_form_submissions, :user_agent, :text
|
|
5
|
+
add_index :panda_cms_form_submissions, :ip_address
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddPerformanceIndexesToPagesAndRedirects < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
# Add index to pages.path for fast page lookups
|
|
6
|
+
# This is the most critical query path in the CMS
|
|
7
|
+
add_index :panda_cms_pages, :path, name: "index_panda_cms_pages_on_path"
|
|
8
|
+
|
|
9
|
+
# Add index to redirects.origin_path for fast redirect lookups
|
|
10
|
+
# Checked on every request in PagesController#handle_redirects
|
|
11
|
+
add_index :panda_cms_redirects, :origin_path, name: "index_panda_cms_redirects_on_origin_path"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -10,11 +10,8 @@ module Generators
|
|
|
10
10
|
desc "Adds the basic configuration for Panda CMS to your Rails app."
|
|
11
11
|
|
|
12
12
|
def create_initializer_file
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
unless File.exist?("#{::Rails.root}/#{initializer_path}")
|
|
16
|
-
FileUtils.cp "#{::Panda::CMS::Engine.root}/#{initializer_path}", "#{::Rails.root}/#{initializer_path}"
|
|
17
|
-
end
|
|
13
|
+
# Skip creating initializer - Panda::Core already creates config/initializers/panda.rb
|
|
14
|
+
# See config/initializers/panda/cms.rb in the gem for an example configuration
|
|
18
15
|
|
|
19
16
|
# Add the seed loader to the seeds.rb file
|
|
20
17
|
unless File.read("#{::Rails.root}/db/seeds.rb")&.include?("Panda::CMS::Engine.load_seed")
|
|
@@ -35,11 +35,9 @@ module Panda
|
|
|
35
35
|
|
|
36
36
|
# Check if GitHub-hosted assets should be used
|
|
37
37
|
def use_github_assets?
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
!development_assets_available? ||
|
|
42
|
-
((Rails.env.test? || in_test_environment?) && compiled_assets_available?)
|
|
38
|
+
# Panda CMS uses importmaps for JavaScript (no compilation needed)
|
|
39
|
+
# Only use GitHub assets in production or when explicitly enabled
|
|
40
|
+
false # Always use importmaps like panda-core
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
# Download assets from GitHub to local cache
|
|
@@ -105,39 +103,10 @@ module Panda
|
|
|
105
103
|
end
|
|
106
104
|
|
|
107
105
|
def development_asset_tags(options = {})
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
js_url = "/panda-cms-assets/panda-cms-#{version}.js"
|
|
113
|
-
css_url = "/panda-cms-assets/panda-cms-#{version}.css"
|
|
114
|
-
|
|
115
|
-
tags = []
|
|
116
|
-
|
|
117
|
-
# JavaScript tag
|
|
118
|
-
tags << content_tag(:script, "", {
|
|
119
|
-
src: js_url,
|
|
120
|
-
defer: true
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
# CSS tag if exists
|
|
124
|
-
if cached_asset_exists?(css_url)
|
|
125
|
-
tags << tag(:link, {
|
|
126
|
-
rel: "stylesheet",
|
|
127
|
-
href: css_url
|
|
128
|
-
})
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
tags.join("\n").html_safe
|
|
132
|
-
else
|
|
133
|
-
# In development, just use a simple script tag
|
|
134
|
-
# The view will handle importmap tags separately
|
|
135
|
-
content_tag(:script, "", {
|
|
136
|
-
src: development_javascript_url,
|
|
137
|
-
type: "module",
|
|
138
|
-
defer: true
|
|
139
|
-
})
|
|
140
|
-
end
|
|
106
|
+
# Panda CMS uses importmaps for JavaScript (no compiled bundles)
|
|
107
|
+
# The view will handle importmap tags via <%= javascript_importmap_tags %>
|
|
108
|
+
# We don't need to return anything here - CSS comes from panda-core
|
|
109
|
+
"".html_safe
|
|
141
110
|
end
|
|
142
111
|
|
|
143
112
|
def github_javascript_url
|
|
@@ -161,20 +130,8 @@ module Panda
|
|
|
161
130
|
end
|
|
162
131
|
|
|
163
132
|
def development_javascript_url
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
# Try root level first (standalone bundle), then versioned directory
|
|
167
|
-
root_path = "/panda-cms-assets/panda-cms-#{version}.js"
|
|
168
|
-
versioned_path = "/panda-cms-assets/#{version}/panda-cms-#{version}.js"
|
|
169
|
-
|
|
170
|
-
if cached_asset_exists?(root_path)
|
|
171
|
-
root_path
|
|
172
|
-
elsif cached_asset_exists?(versioned_path)
|
|
173
|
-
versioned_path
|
|
174
|
-
else
|
|
175
|
-
# Fallback to importmap or engine asset
|
|
176
|
-
"/assets/panda/cms/controllers/index.js"
|
|
177
|
-
end
|
|
133
|
+
# Always use importmap (no compiled bundles)
|
|
134
|
+
"/panda/cms/application.js"
|
|
178
135
|
end
|
|
179
136
|
|
|
180
137
|
def development_css_url
|
|
@@ -202,14 +159,7 @@ module Panda
|
|
|
202
159
|
end
|
|
203
160
|
|
|
204
161
|
def asset_version
|
|
205
|
-
|
|
206
|
-
# In other environments, use git SHA for dynamic versioning
|
|
207
|
-
# Also check for test environment indicators since Rails.env might be development in specs
|
|
208
|
-
if Rails.env.test? || ENV["CI"].present? || in_test_environment?
|
|
209
|
-
Panda::CMS::VERSION
|
|
210
|
-
else
|
|
211
|
-
`git rev-parse --short HEAD`.strip
|
|
212
|
-
end
|
|
162
|
+
Panda::CMS::VERSION
|
|
213
163
|
end
|
|
214
164
|
|
|
215
165
|
def in_test_environment?
|
|
@@ -217,14 +167,6 @@ module Panda
|
|
|
217
167
|
defined?(RSpec) && RSpec.respond_to?(:configuration)
|
|
218
168
|
end
|
|
219
169
|
|
|
220
|
-
def compiled_assets_available?
|
|
221
|
-
# Check if compiled assets exist in test location
|
|
222
|
-
version = asset_version
|
|
223
|
-
js_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.js")
|
|
224
|
-
css_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.css")
|
|
225
|
-
js_file.exist? && css_file.exist?
|
|
226
|
-
end
|
|
227
|
-
|
|
228
170
|
def development_assets_available?
|
|
229
171
|
# Check if local development assets exist (importmap, etc.)
|
|
230
172
|
importmap_available? || engine_assets_available?
|
|
@@ -234,12 +176,18 @@ module Panda
|
|
|
234
176
|
return false unless defined?(Rails.application.importmap)
|
|
235
177
|
|
|
236
178
|
begin
|
|
237
|
-
#
|
|
238
|
-
if Rails.application.importmap.respond_to?(:
|
|
239
|
-
|
|
240
|
-
|
|
179
|
+
# Try different API methods depending on importmap-rails version
|
|
180
|
+
if Rails.application.importmap.respond_to?(:packages)
|
|
181
|
+
# importmap-rails 2.x - check packages hash
|
|
182
|
+
Rails.application.importmap.packages.keys.any? { |name| name.to_s.include?("panda") }
|
|
241
183
|
elsif Rails.application.importmap.respond_to?(:entries)
|
|
184
|
+
# importmap-rails 1.x - check entries array
|
|
242
185
|
Rails.application.importmap.entries.any? { |entry| entry.name.include?("panda") }
|
|
186
|
+
elsif Rails.application.importmap.respond_to?(:to_json)
|
|
187
|
+
# Fallback with proper resolver
|
|
188
|
+
# Note: to_json requires a resolver in newer versions
|
|
189
|
+
# We'll just return false if we can't access packages/entries
|
|
190
|
+
false
|
|
243
191
|
else
|
|
244
192
|
false
|
|
245
193
|
end
|
|
@@ -251,10 +199,17 @@ module Panda
|
|
|
251
199
|
|
|
252
200
|
def engine_assets_available?
|
|
253
201
|
# Check if engine's JavaScript files are available
|
|
254
|
-
engine_js_path =
|
|
202
|
+
engine_js_path = Panda::CMS::Engine.root.join("app", "javascript", "panda", "cms", "controllers", "index.js")
|
|
255
203
|
File.exist?(engine_js_path)
|
|
256
204
|
end
|
|
257
205
|
|
|
206
|
+
def compiled_assets_available?
|
|
207
|
+
# Check if compiled JavaScript bundle exists
|
|
208
|
+
version = asset_version
|
|
209
|
+
js_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.js")
|
|
210
|
+
js_file.exist?
|
|
211
|
+
end
|
|
212
|
+
|
|
258
213
|
def cached_assets_exist?(version)
|
|
259
214
|
cache_dir = local_cache_directory.join(version)
|
|
260
215
|
cache_dir.exist? && cache_dir.join("panda-cms-#{version}.js").exist?
|
|
@@ -312,6 +267,21 @@ module Panda
|
|
|
312
267
|
end
|
|
313
268
|
end
|
|
314
269
|
|
|
270
|
+
# Simple response object for HTTP requests
|
|
271
|
+
class Response
|
|
272
|
+
attr_reader :code, :body
|
|
273
|
+
|
|
274
|
+
def initialize(success:, code:, body:)
|
|
275
|
+
@success = success
|
|
276
|
+
@code = code
|
|
277
|
+
@body = body
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def success?
|
|
281
|
+
@success
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
315
285
|
def fetch_url(url)
|
|
316
286
|
uri = URI(url)
|
|
317
287
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -324,14 +294,14 @@ module Panda
|
|
|
324
294
|
|
|
325
295
|
response = http.request(request)
|
|
326
296
|
|
|
327
|
-
|
|
328
|
-
success
|
|
297
|
+
Response.new(
|
|
298
|
+
success: response.code.to_i == 200,
|
|
329
299
|
code: response.code,
|
|
330
300
|
body: response.body
|
|
331
301
|
)
|
|
332
302
|
rescue => e
|
|
333
303
|
Rails.logger.error "[Panda CMS] Network error: #{e.message}"
|
|
334
|
-
|
|
304
|
+
Response.new(success: false, code: "error", body: nil)
|
|
335
305
|
end
|
|
336
306
|
|
|
337
307
|
def asset_integrity(version, filename)
|