panda-cms 0.10.0 → 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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -11
  3. data/app/components/panda/cms/code_component.rb +45 -8
  4. data/app/components/panda/cms/menu_component.rb +9 -3
  5. data/app/components/panda/cms/page_menu_component.rb +9 -1
  6. data/app/components/panda/cms/rich_text_component.rb +49 -17
  7. data/app/components/panda/cms/text_component.rb +46 -14
  8. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -2
  9. data/app/controllers/panda/cms/admin/pages_controller.rb +6 -2
  10. data/app/controllers/panda/cms/admin/posts_controller.rb +3 -1
  11. data/app/controllers/panda/cms/form_submissions_controller.rb +134 -11
  12. data/app/controllers/panda/cms/pages_controller.rb +7 -2
  13. data/app/controllers/panda/cms/posts_controller.rb +16 -0
  14. data/app/helpers/panda/cms/application_helper.rb +2 -3
  15. data/app/helpers/panda/cms/asset_helper.rb +14 -72
  16. data/app/helpers/panda/cms/forms_helper.rb +60 -0
  17. data/app/helpers/panda/cms/seo_helper.rb +85 -0
  18. data/app/javascript/panda/cms/{application_panda_cms.js → application.js} +4 -0
  19. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +31 -4
  20. data/app/javascript/panda/cms/controllers/file_upload_controller.js +165 -0
  21. data/app/javascript/panda/cms/controllers/index.js +6 -0
  22. data/app/javascript/panda/cms/controllers/menu_form_controller.js +14 -1
  23. data/app/javascript/panda/cms/controllers/page_form_controller.js +454 -0
  24. data/app/javascript/panda/cms/stimulus-loading.js +2 -1
  25. data/app/models/panda/cms/menu.rb +12 -0
  26. data/app/models/panda/cms/page.rb +106 -0
  27. data/app/models/panda/cms/post.rb +97 -0
  28. data/app/views/layouts/homepage.html.erb +1 -4
  29. data/app/views/layouts/page.html.erb +1 -4
  30. data/app/views/panda/cms/admin/dashboard/show.html.erb +1 -1
  31. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  32. data/app/views/panda/cms/admin/forms/show.html.erb +3 -3
  33. data/app/views/panda/cms/admin/menus/_menu_item_fields.html.erb +3 -3
  34. data/app/views/panda/cms/admin/menus/edit.html.erb +12 -14
  35. data/app/views/panda/cms/admin/menus/index.html.erb +1 -1
  36. data/app/views/panda/cms/admin/menus/new.html.erb +5 -7
  37. data/app/views/panda/cms/admin/pages/edit.html.erb +139 -20
  38. data/app/views/panda/cms/admin/pages/index.html.erb +6 -6
  39. data/app/views/panda/cms/admin/posts/_form.html.erb +41 -2
  40. data/app/views/panda/cms/admin/posts/edit.html.erb +1 -1
  41. data/app/views/panda/cms/admin/posts/index.html.erb +4 -4
  42. data/app/views/shared/_header.html.erb +1 -4
  43. data/config/brakeman.ignore +38 -0
  44. data/config/importmap.rb +8 -6
  45. data/config/locales/en.yml +41 -0
  46. data/config/routes.rb +1 -1
  47. data/db/migrate/20251109131150_add_seo_fields_to_pages.rb +32 -0
  48. data/db/migrate/20251109131205_add_seo_fields_to_posts.rb +27 -0
  49. data/db/migrate/20251110114258_add_spam_tracking_to_form_submissions.rb +7 -0
  50. data/db/migrate/20251110122812_add_performance_indexes_to_pages_and_redirects.rb +13 -0
  51. data/lib/panda/cms/asset_loader.rb +27 -77
  52. data/lib/panda/cms/bulk_editor.rb +288 -12
  53. data/lib/panda/cms/engine/asset_config.rb +49 -0
  54. data/lib/panda/cms/engine/autoload_config.rb +19 -0
  55. data/lib/panda/cms/engine/backtrace_config.rb +42 -0
  56. data/lib/panda/cms/engine/core_config.rb +106 -0
  57. data/lib/panda/cms/engine/helper_config.rb +20 -0
  58. data/lib/panda/cms/engine/route_config.rb +34 -0
  59. data/lib/panda/cms/engine/view_component_config.rb +31 -0
  60. data/lib/panda/cms/engine.rb +44 -221
  61. data/lib/panda/cms.rb +10 -0
  62. data/lib/panda-cms/version.rb +1 -1
  63. data/lib/panda-cms.rb +16 -2
  64. metadata +20 -22
  65. data/app/javascript/panda_cms/stimulus-loading.js +0 -39
  66. data/app/views/panda/cms/shared/_importmap.html.erb +0 -34
  67. data/config/initializers/inflections.rb +0 -5
  68. data/lib/tasks/assets.rake +0 -540
data/config/importmap.rb CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  # Base dependencies are now in panda-core (Stimulus, Turbo, Font Awesome, etc.)
4
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/
5
7
 
6
- pin "application_panda_cms", to: "panda/cms/application_panda_cms.js", preload: true
7
- pin "@hotwired/stimulus-loading", to: "panda/cms/stimulus-loading.js", preload: true
8
- 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
9
11
 
10
12
  # Pin the controllers directory
11
- pin "controllers", to: "panda/cms/controllers/index.js"
12
- pin_all_from Panda::CMS::Engine.root.join("app/javascript/panda/cms/controllers"), under: "controllers"
13
- 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"
@@ -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
@@ -10,7 +10,7 @@ Panda::CMS::Engine.routes.draw do
10
10
 
11
11
  namespace admin_path.delete_prefix("/").to_sym, path: "#{admin_path}/cms", as: :admin_cms, module: :admin do
12
12
  resources :files
13
- resources :forms, only: %i[index show]
13
+ resources :forms
14
14
  resources :menus
15
15
  resources :pages do
16
16
  resources :block_contents, only: %i[update]
@@ -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
@@ -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
- # Use GitHub assets in production or when explicitly enabled
39
- Rails.env.production? ||
40
- ENV["PANDA_CMS_USE_GITHUB_ASSETS"] == "true" ||
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
- # In test environment with CI, always use compiled assets
109
- if (Rails.env.test? || ENV["CI"].present?) && compiled_assets_available?
110
- # Use the same logic as GitHub assets but with local paths
111
- version = asset_version
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,25 +130,8 @@ module Panda
161
130
  end
162
131
 
163
132
  def development_javascript_url
164
- # In development, use importmap for live-reloading
165
- # Only use compiled assets in test/CI environments
166
- if Rails.env.development?
167
- "/panda/cms/application_panda_cms.js"
168
- else
169
- # Try cached assets for test environment
170
- version = asset_version
171
- root_path = "/panda-cms-assets/panda-cms-#{version}.js"
172
- versioned_path = "/panda-cms-assets/#{version}/panda-cms-#{version}.js"
173
-
174
- if cached_asset_exists?(root_path)
175
- root_path
176
- elsif cached_asset_exists?(versioned_path)
177
- versioned_path
178
- else
179
- # Fallback to importmap
180
- "/panda/cms/application_panda_cms.js"
181
- end
182
- end
133
+ # Always use importmap (no compiled bundles)
134
+ "/panda/cms/application.js"
183
135
  end
184
136
 
185
137
  def development_css_url
@@ -207,14 +159,7 @@ module Panda
207
159
  end
208
160
 
209
161
  def asset_version
210
- # In test environment, use VERSION constant for consistency with compiled assets
211
- # In other environments, use git SHA for dynamic versioning
212
- # Also check for test environment indicators since Rails.env might be development in specs
213
- if Rails.env.test? || ENV["CI"].present? || in_test_environment?
214
- Panda::CMS::VERSION
215
- else
216
- `git rev-parse --short HEAD`.strip
217
- end
162
+ Panda::CMS::VERSION
218
163
  end
219
164
 
220
165
  def in_test_environment?
@@ -222,14 +167,6 @@ module Panda
222
167
  defined?(RSpec) && RSpec.respond_to?(:configuration)
223
168
  end
224
169
 
225
- def compiled_assets_available?
226
- # Check if compiled assets exist in test location
227
- version = asset_version
228
- js_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.js")
229
- css_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.css")
230
- js_file.exist? && css_file.exist?
231
- end
232
-
233
170
  def development_assets_available?
234
171
  # Check if local development assets exist (importmap, etc.)
235
172
  importmap_available? || engine_assets_available?
@@ -239,12 +176,18 @@ module Panda
239
176
  return false unless defined?(Rails.application.importmap)
240
177
 
241
178
  begin
242
- # Rails 8+ uses a different API for accessing importmap entries
243
- if Rails.application.importmap.respond_to?(:to_json)
244
- importmap_json = JSON.parse(Rails.application.importmap.to_json)
245
- importmap_json.any? { |name, _| name.include?("panda") }
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") }
246
183
  elsif Rails.application.importmap.respond_to?(:entries)
184
+ # importmap-rails 1.x - check entries array
247
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
248
191
  else
249
192
  false
250
193
  end
@@ -260,6 +203,13 @@ module Panda
260
203
  File.exist?(engine_js_path)
261
204
  end
262
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
+
263
213
  def cached_assets_exist?(version)
264
214
  cache_dir = local_cache_directory.join(version)
265
215
  cache_dir.exist? && cache_dir.join("panda-cms-#{version}.js").exist?