panda-cms 0.10.0 → 0.10.3

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -11
  3. data/app/assets/tailwind/panda/cms/_application.css +1 -0
  4. data/app/components/panda/cms/admin/popular_pages_component.rb +62 -0
  5. data/app/components/panda/cms/code_component.rb +46 -9
  6. data/app/components/panda/cms/menu_component.rb +18 -5
  7. data/app/components/panda/cms/page_menu_component.rb +9 -1
  8. data/app/components/panda/cms/rich_text_component.rb +49 -17
  9. data/app/components/panda/cms/text_component.rb +46 -14
  10. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -2
  11. data/app/controllers/panda/cms/admin/pages_controller.rb +6 -2
  12. data/app/controllers/panda/cms/admin/posts_controller.rb +3 -1
  13. data/app/controllers/panda/cms/form_submissions_controller.rb +134 -11
  14. data/app/controllers/panda/cms/pages_controller.rb +7 -2
  15. data/app/controllers/panda/cms/posts_controller.rb +16 -0
  16. data/app/helpers/panda/cms/application_helper.rb +2 -3
  17. data/app/helpers/panda/cms/asset_helper.rb +14 -72
  18. data/app/helpers/panda/cms/forms_helper.rb +60 -0
  19. data/app/helpers/panda/cms/seo_helper.rb +85 -0
  20. data/app/javascript/panda/cms/{application_panda_cms.js → application.js} +4 -0
  21. data/app/javascript/panda/cms/controllers/editor_form_controller.js +3 -3
  22. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +35 -8
  23. data/app/javascript/panda/cms/controllers/file_upload_controller.js +165 -0
  24. data/app/javascript/panda/cms/controllers/index.js +6 -0
  25. data/app/javascript/panda/cms/controllers/menu_form_controller.js +14 -1
  26. data/app/javascript/panda/cms/controllers/page_form_controller.js +454 -0
  27. data/app/javascript/panda/cms/stimulus-loading.js +2 -1
  28. data/app/jobs/panda/cms/record_visit_job.rb +2 -1
  29. data/app/models/panda/cms/menu.rb +12 -0
  30. data/app/models/panda/cms/page.rb +106 -0
  31. data/app/models/panda/cms/post.rb +97 -0
  32. data/app/models/panda/cms/visit.rb +16 -1
  33. data/app/services/panda/social/instagram_feed_service.rb +54 -54
  34. data/app/views/layouts/homepage.html.erb +1 -4
  35. data/app/views/layouts/page.html.erb +1 -4
  36. data/app/views/panda/cms/admin/dashboard/show.html.erb +11 -4
  37. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  38. data/app/views/panda/cms/admin/forms/new.html.erb +1 -1
  39. data/app/views/panda/cms/admin/forms/show.html.erb +3 -3
  40. data/app/views/panda/cms/admin/menus/_menu_item_fields.html.erb +3 -3
  41. data/app/views/panda/cms/admin/menus/edit.html.erb +12 -14
  42. data/app/views/panda/cms/admin/menus/index.html.erb +1 -1
  43. data/app/views/panda/cms/admin/menus/new.html.erb +6 -8
  44. data/app/views/panda/cms/admin/pages/edit.html.erb +213 -20
  45. data/app/views/panda/cms/admin/pages/index.html.erb +6 -6
  46. data/app/views/panda/cms/admin/posts/_form.html.erb +47 -8
  47. data/app/views/panda/cms/admin/posts/edit.html.erb +1 -1
  48. data/app/views/panda/cms/admin/posts/index.html.erb +4 -4
  49. data/app/views/panda/cms/shared/_favicons.html.erb +7 -7
  50. data/app/views/shared/_header.html.erb +1 -4
  51. data/config/brakeman.ignore +38 -0
  52. data/config/importmap.rb +7 -6
  53. data/config/initializers/groupdate.rb +5 -0
  54. data/config/locales/en.yml +42 -2
  55. data/config/routes.rb +1 -1
  56. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +0 -10
  57. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +0 -6
  58. data/db/migrate/20240317230622_create_panda_cms_visits.rb +1 -1
  59. data/db/migrate/20240805121123_create_panda_cms_posts.rb +1 -1
  60. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +1 -1
  61. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +0 -6
  62. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +1 -3
  63. data/db/migrate/20251109131150_add_seo_fields_to_pages.rb +32 -0
  64. data/db/migrate/20251109131205_add_seo_fields_to_posts.rb +27 -0
  65. data/db/migrate/20251110114258_add_spam_tracking_to_form_submissions.rb +7 -0
  66. data/db/migrate/20251110122812_add_performance_indexes_to_pages_and_redirects.rb +13 -0
  67. data/db/migrate/20251117234530_add_index_to_visited_at_on_panda_cms_visits.rb +7 -0
  68. data/db/migrate/20251118015100_backfill_visited_at_for_existing_visits.rb +17 -0
  69. data/db/seeds.rb +5 -0
  70. data/lib/panda/cms/asset_loader.rb +42 -78
  71. data/lib/panda/cms/bulk_editor.rb +288 -12
  72. data/lib/panda/cms/engine/asset_config.rb +49 -0
  73. data/lib/panda/cms/engine/autoload_config.rb +37 -0
  74. data/lib/panda/cms/engine/backtrace_config.rb +42 -0
  75. data/lib/panda/cms/engine/core_config.rb +106 -0
  76. data/lib/panda/cms/engine/helper_config.rb +20 -0
  77. data/lib/panda/cms/engine/route_config.rb +33 -0
  78. data/lib/panda/cms/engine/view_component_config.rb +31 -0
  79. data/lib/panda/cms/engine.rb +32 -228
  80. data/lib/{panda-cms → panda/cms}/version.rb +1 -1
  81. data/lib/panda/cms.rb +12 -0
  82. data/lib/panda-cms.rb +24 -3
  83. data/lib/tasks/ci.rake +0 -0
  84. metadata +32 -67
  85. data/app/assets/builds/panda.cms.css +0 -2754
  86. data/app/assets/stylesheets/panda/cms/application.tailwind.css +0 -162
  87. data/app/assets/stylesheets/panda/cms/editor.css +0 -120
  88. data/app/assets/tailwind/application.css +0 -178
  89. data/app/assets/tailwind/tailwind.config.js +0 -15
  90. data/app/javascript/panda_cms/stimulus-loading.js +0 -39
  91. data/app/views/panda/cms/shared/_importmap.html.erb +0 -34
  92. data/config/initializers/inflections.rb +0 -5
  93. data/config/initializers/panda/cms/healthcheck_log_silencer.rb.disabled +0 -31
  94. data/db/migrate/20240317010532_create_panda_cms_users.rb +0 -14
  95. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +0 -61
  96. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +0 -7
  97. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +0 -24
  98. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +0 -30
  99. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +0 -10
  100. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +0 -37
  101. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +0 -113
  102. data/lib/generators/panda/cms/install_generator.rb +0 -28
  103. data/lib/tasks/assets.rake +0 -540
  104. data/public/panda-cms-assets/editor-js/core/editorjs.min.js +0 -83
  105. data/public/panda-cms-assets/editor-js/plugins/embed.min.js +0 -2
  106. data/public/panda-cms-assets/editor-js/plugins/header.min.js +0 -9
  107. data/public/panda-cms-assets/editor-js/plugins/nested-list.min.js +0 -2
  108. data/public/panda-cms-assets/editor-js/plugins/paragraph.min.js +0 -9
  109. data/public/panda-cms-assets/editor-js/plugins/quote.min.js +0 -2
  110. data/public/panda-cms-assets/editor-js/plugins/simple-image.min.js +0 -2
  111. data/public/panda-cms-assets/editor-js/plugins/table.min.js +0 -2
  112. data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
  113. data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
  114. data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
  115. data/public/panda-cms-assets/favicons/browserconfig.xml +0 -9
  116. data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
  117. data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
  118. data/public/panda-cms-assets/favicons/favicon.ico +0 -0
  119. data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
  120. data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +0 -61
  121. data/public/panda-cms-assets/favicons/site.webmanifest +0 -14
  122. data/public/panda-cms-assets/manifest.json +0 -20
  123. data/public/panda-cms-assets/panda-cms-0.7.4.css +0 -26
  124. data/public/panda-cms-assets/panda-cms-0.7.4.js +0 -150
  125. data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
  126. data/public/panda-cms-assets/panda-nav.png +0 -0
  127. data/public/panda-cms-assets/rich_text_editor.css +0 -568
  128. /data/db/migrate/{20251105000001_add_pending_review_status_to_pages_and_posts.panda_cms.rb → 20251105000001_add_pending_review_status_to_pages_and_posts.rb} +0 -0
@@ -3,11 +3,5 @@
3
3
  class AddDepthToPandaCMSMenus < ActiveRecord::Migration[7.2]
4
4
  def change
5
5
  add_column :panda_cms_menus, :depth, :integer, null: true, default: nil
6
-
7
- homepage = Panda::CMS::Page.find_by(path: "/")
8
- return unless homepage
9
-
10
- main_menu = Panda::CMS::Menu.find_by(start_page_id: homepage.id, kind: :auto)
11
- main_menu&.update(depth: 2)
12
6
  end
13
7
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  class AddAuthorIdToPandaCMSPosts < ActiveRecord::Migration[8.0]
4
4
  def change
5
- unless column_exists?(:panda_cms_posts, :author_id)
6
- add_reference :panda_cms_posts, :author, type: :uuid, foreign_key: {to_table: :panda_core_users}
7
- end
5
+ add_reference :panda_cms_posts, :author, type: :uuid, foreign_key: {to_table: :panda_core_users}
8
6
  end
9
7
  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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexToVisitedAtOnPandaCMSVisits < ActiveRecord::Migration[7.1]
4
+ def change
5
+ add_index :panda_cms_visits, :visited_at
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BackfillVisitedAtForExistingVisits < ActiveRecord::Migration[7.1]
4
+ def up
5
+ # Backfill visited_at from created_at for all existing visits where visited_at is NULL
6
+ # This ensures historical visit data shows up in dashboard statistics
7
+ execute <<-SQL
8
+ UPDATE panda_cms_visits
9
+ SET visited_at = created_at
10
+ WHERE visited_at IS NULL
11
+ SQL
12
+ end
13
+
14
+ def down
15
+ # No need to reverse - visited_at values are still valid
16
+ end
17
+ end
data/db/seeds.rb CHANGED
@@ -5,3 +5,8 @@ generator.create_templates
5
5
  generator.create_pages
6
6
  generator.create_menus
7
7
  Panda::CMS::Template.generate_missing_blocks
8
+
9
+ if (homepage = Panda::CMS::Page.find_by(path: "/"))
10
+ main_menu = Panda::CMS::Menu.find_by(start_page_id: homepage.id, kind: :auto)
11
+ main_menu&.update(depth: 2)
12
+ end
@@ -35,11 +35,22 @@ 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
+ # In test, never use GitHub assets
39
+ return false if Rails.env.test? || in_test_environment?
40
+
41
+ # In production, prefer local assets over GitHub
42
+ # CMS uses importmap for JS and gets CSS from panda-core
43
+ # Only use GitHub as fallback if explicitly enabled
44
+ if Rails.env.production?
45
+ # Check if compiled assets exist locally (though CMS doesn't bundle)
46
+ return false if compiled_assets_available?
47
+
48
+ # Only use GitHub as fallback if explicitly enabled
49
+ return ENV["PANDA_CMS_USE_GITHUB_ASSETS"] == "true"
50
+ end
51
+
52
+ # In development, use GitHub assets only when explicitly enabled or development assets unavailable
53
+ ENV["PANDA_CMS_USE_GITHUB_ASSETS"] == "true" || !development_assets_available?
43
54
  end
44
55
 
45
56
  # Download assets from GitHub to local cache
@@ -105,39 +116,10 @@ module Panda
105
116
  end
106
117
 
107
118
  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
119
+ # Panda CMS uses importmaps for JavaScript (no compiled bundles)
120
+ # The view will handle importmap tags via <%= javascript_importmap_tags %>
121
+ # We don't need to return anything here - CSS comes from panda-core
122
+ "".html_safe
141
123
  end
142
124
 
143
125
  def github_javascript_url
@@ -161,25 +143,8 @@ module Panda
161
143
  end
162
144
 
163
145
  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
146
+ # Always use importmap (no compiled bundles)
147
+ "/panda/cms/application.js"
183
148
  end
184
149
 
185
150
  def development_css_url
@@ -202,19 +167,13 @@ module Panda
202
167
  if (Rails.env.test? || in_test_environment?) && compiled_assets_available?
203
168
  "/panda-cms-assets/"
204
169
  else
205
- "https://github.com/tastybamboo/panda-cms/releases/download/#{version}/"
170
+ # Use raw.githubusercontent.com instead of releases
171
+ "https://raw.githubusercontent.com/tastybamboo/panda-cms/main/public/panda-cms-assets/"
206
172
  end
207
173
  end
208
174
 
209
175
  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
176
+ Panda::CMS::VERSION
218
177
  end
219
178
 
220
179
  def in_test_environment?
@@ -222,14 +181,6 @@ module Panda
222
181
  defined?(RSpec) && RSpec.respond_to?(:configuration)
223
182
  end
224
183
 
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
184
  def development_assets_available?
234
185
  # Check if local development assets exist (importmap, etc.)
235
186
  importmap_available? || engine_assets_available?
@@ -239,12 +190,18 @@ module Panda
239
190
  return false unless defined?(Rails.application.importmap)
240
191
 
241
192
  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") }
193
+ # Try different API methods depending on importmap-rails version
194
+ if Rails.application.importmap.respond_to?(:packages)
195
+ # importmap-rails 2.x - check packages hash
196
+ Rails.application.importmap.packages.keys.any? { |name| name.to_s.include?("panda") }
246
197
  elsif Rails.application.importmap.respond_to?(:entries)
198
+ # importmap-rails 1.x - check entries array
247
199
  Rails.application.importmap.entries.any? { |entry| entry.name.include?("panda") }
200
+ elsif Rails.application.importmap.respond_to?(:to_json)
201
+ # Fallback with proper resolver
202
+ # Note: to_json requires a resolver in newer versions
203
+ # We'll just return false if we can't access packages/entries
204
+ false
248
205
  else
249
206
  false
250
207
  end
@@ -260,6 +217,13 @@ module Panda
260
217
  File.exist?(engine_js_path)
261
218
  end
262
219
 
220
+ def compiled_assets_available?
221
+ # Check if compiled JavaScript bundle exists
222
+ version = asset_version
223
+ js_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.js")
224
+ js_file.exist?
225
+ end
226
+
263
227
  def cached_assets_exist?(version)
264
228
  cache_dir = local_cache_directory.join(version)
265
229
  cache_dir.exist? && cache_dir.join("panda-cms-#{version}.js").exist?