panda-cms 0.7.4 → 0.8.0

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/Rakefile +2 -0
  4. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  5. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  6. data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
  7. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  8. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  9. data/app/components/panda/cms/code_component.rb +8 -4
  10. data/app/components/panda/cms/menu_component.rb +7 -6
  11. data/app/components/panda/cms/page_menu_component.rb +15 -17
  12. data/app/components/panda/cms/rich_text_component.rb +10 -11
  13. data/app/components/panda/cms/text_component.rb +6 -7
  14. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  15. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  16. data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
  17. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  18. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
  19. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  20. data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
  21. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
  22. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  23. data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
  24. data/app/controllers/panda/cms/application_controller.rb +19 -6
  25. data/app/controllers/panda/cms/errors_controller.rb +5 -2
  26. data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
  27. data/app/controllers/panda/cms/pages_controller.rb +34 -31
  28. data/app/controllers/panda/cms/posts_controller.rb +2 -0
  29. data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
  30. data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
  31. data/app/helpers/panda/cms/application_helper.rb +3 -3
  32. data/app/helpers/panda/cms/asset_helper.rb +195 -0
  33. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  34. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  35. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  36. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  37. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  38. data/app/javascript/panda/cms/controllers/index.js +8 -24
  39. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  40. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  41. data/app/jobs/panda/cms/application_job.rb +2 -0
  42. data/app/jobs/panda/cms/record_visit_job.rb +2 -0
  43. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  44. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  45. data/app/models/panda/cms/application_record.rb +2 -0
  46. data/app/models/panda/cms/block.rb +4 -1
  47. data/app/models/panda/cms/block_content.rb +3 -1
  48. data/app/models/panda/cms/current.rb +5 -12
  49. data/app/models/panda/cms/form.rb +2 -0
  50. data/app/models/panda/cms/form_submission.rb +2 -0
  51. data/app/models/panda/cms/menu.rb +12 -9
  52. data/app/models/panda/cms/menu_item.rb +10 -6
  53. data/app/models/panda/cms/page.rb +14 -12
  54. data/app/models/panda/cms/post.rb +12 -8
  55. data/app/models/panda/cms/redirect.rb +6 -3
  56. data/app/models/panda/cms/template.rb +12 -7
  57. data/app/models/panda/cms/visit.rb +3 -1
  58. data/app/models/panda/social/instagram_post.rb +2 -0
  59. data/app/services/panda/social/instagram_feed_service.rb +3 -1
  60. data/app/views/layouts/different_page.html.erb +6 -0
  61. data/app/views/layouts/homepage.html.erb +37 -0
  62. data/app/views/layouts/page.html.erb +18 -0
  63. data/app/views/layouts/panda/cms/application.html.erb +2 -1
  64. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  65. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  66. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  67. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  68. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  69. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  70. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  71. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  72. data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
  73. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  74. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  75. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  76. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  77. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  78. data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
  79. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  80. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  81. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  82. data/app/views/panda/cms/shared/_header.html.erb +10 -2
  83. data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
  84. data/app/views/shared/_footer.html.erb +3 -0
  85. data/app/views/shared/_header.html.erb +11 -0
  86. data/config/importmap.rb +2 -0
  87. data/config/initializers/inflections.rb +2 -0
  88. data/config/initializers/panda/cms/form_errors.rb +20 -21
  89. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  90. data/config/initializers/panda/cms.rb +8 -3
  91. data/config/initializers/zeitwork.rb +2 -0
  92. data/config/puma/test.rb +3 -1
  93. data/config/routes.rb +11 -19
  94. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  95. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  96. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  97. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  98. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  99. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  100. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  101. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  102. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  103. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  104. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  105. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  106. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  107. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  108. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  109. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  110. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  111. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  112. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  113. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  114. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  115. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  116. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  117. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  118. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  119. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  120. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  121. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  122. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  123. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  124. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  125. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  126. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  127. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  128. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  129. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  130. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  131. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  132. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  133. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
  134. data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
  135. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
  136. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  137. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  138. data/db/seeds.rb +2 -0
  139. data/lib/generators/panda/cms/install_generator.rb +2 -0
  140. data/lib/panda/cms/asset_loader.rb +390 -0
  141. data/lib/panda/cms/bulk_editor.rb +7 -3
  142. data/lib/panda/cms/demo_site_generator.rb +2 -0
  143. data/lib/panda/cms/engine.rb +57 -116
  144. data/lib/panda/cms/exceptions_app.rb +2 -0
  145. data/lib/panda/cms/railtie.rb +2 -0
  146. data/lib/panda/cms/slug.rb +3 -1
  147. data/lib/panda-cms/version.rb +3 -1
  148. data/lib/panda-cms.rb +54 -42
  149. data/lib/tasks/assets.rake +587 -0
  150. data/lib/tasks/panda/cms/install.rake +2 -0
  151. data/lib/tasks/panda/cms/migrations.rake +13 -0
  152. data/lib/tasks/panda/social/instagram.rake +2 -0
  153. data/lib/tasks/panda_cms.rake +3 -30
  154. data/public/panda-cms-assets/manifest.json +20 -0
  155. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  156. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  157. metadata +186 -49
  158. data/app/builders/panda/cms/form_builder.rb +0 -217
  159. data/app/components/panda/cms/admin/button_component.rb +0 -70
  160. data/app/components/panda/cms/admin/container_component.rb +0 -13
  161. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  162. data/app/components/panda/cms/admin/heading_component.rb +0 -45
  163. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  164. data/app/components/panda/cms/admin/table_component.rb +0 -46
  165. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  166. data/app/constraints/panda/cms/admin_constraint.rb +0 -18
  167. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
  168. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
  169. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
  170. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  171. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  172. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  173. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  174. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  175. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  176. data/app/models/panda/cms/breadcrumb.rb +0 -12
  177. data/app/models/panda/cms/user.rb +0 -31
  178. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
  179. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  180. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  181. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
  182. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
  183. data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
  184. data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
  185. data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
  186. data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
  187. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
  188. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
  189. data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
  190. data/lib/panda/cms/editor_js/renderer.rb +0 -124
  191. data/lib/panda/cms/editor_js.rb +0 -16
  192. data/lib/panda/cms/editor_js_content.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07d84fd0b6bd17f5e5f253a570bae55270227bec0196037960aa3358d09903bd
4
- data.tar.gz: c6bc6d0173f89d965a23132bc016bfd3a44e6bd0b2533960cca495fa28e645e3
3
+ metadata.gz: 9a439141e4b2fd9c4167ea0bd872096a648fe1a9fcfc237e6a1c20e16d2a2562
4
+ data.tar.gz: fa28c3d1d698b9725140618603505a0120c0e58430ff4c923ccd986f3e27daa5
5
5
  SHA512:
6
- metadata.gz: 63b30314a40563ab4cb8f99d516cd30edc3aed7ffe8d1841d4b8760ea6c80c8b253a983ca7cadd473caba240a7b18132cda903b23ed35da845833aef4880b2d4
7
- data.tar.gz: 820e1b606f088e8bacd7e03d997812f5a2255941e399e8fe863076a0a1196935c66c6a2aca34d50f9dd69563280cda9d795692ccba6df5af565aefd2b38439e2
6
+ metadata.gz: 1923aea63bd5a266487a4a25e852df4cc96e0cba408fd9cb9351a5a302a20ad7e07ef22254dbe01ef7751ca3b35d0a9cdfb941a821e56730455fc73f0223083b
7
+ data.tar.gz: 54291d3de7b95047412a2c6c9e50b94f187457803bfd1609f0f9a947348de612c2ca6480f33e8e5df5f0305c131d638e025f1d6e04cc30c2997ce4ccb2a9c56e
data/README.md CHANGED
@@ -12,7 +12,7 @@ Better websites, on Rails.
12
12
  🐼 is grown from our work at [Otaina](https://www.otaina.co.uk), a small group of freelancers. We needed something that could handle websites large and small – but where we could expand it too. We sent our first websites live in March 2024.
13
13
 
14
14
  ![Gem Version](https://img.shields.io/gem/v/panda-cms) ![Build Status](https://img.shields.io/github/actions/workflow/status/tastybamboo/panda-cms/ci.yml)
15
- ![GitHub Last Commit](https://img.shields.io/github/last-commit/tasty-bamboo/panda-cms) [![Ruby Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
15
+ ![GitHub Last Commit](https://img.shields.io/github/last-commit/tastybamboo/panda-cms) [![Ruby Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
16
16
 
17
17
  ## Usage
18
18
 
@@ -66,8 +66,43 @@ We welcome contributions.
66
66
 
67
67
  See our [Contributing Guidelines](https://docs.pandacms.io/developers/contributing/)
68
68
 
69
+ ### Testing
70
+
71
+ Panda CMS uses RSpec for testing.
72
+
73
+ #### Using Fixtures
74
+
75
+ We encourage using fixtures for tests instead of factories for consistent test data:
76
+
77
+ 1. Create fixture files in `spec/fixtures` named after the model's table (e.g., `panda_cms_pages.yml`)
78
+ 2. Define records with unique names and their attributes
79
+ 3. Use helper methods to create test templates with mocked file validation
80
+
81
+ Example fixture format:
82
+
83
+ ```yaml
84
+ # spec/fixtures/panda_cms_pages.yml
85
+ home_page:
86
+ title: "Home"
87
+ path: "/"
88
+ panda_cms_template_id: <%= ActiveRecord::FixtureSet.identify(:page_template) %>
89
+ status: "active"
90
+ created_at: <%= Time.current %>
91
+ updated_at: <%= Time.current %>
92
+ ```
93
+
94
+ Example test using fixtures:
95
+
96
+ ```ruby
97
+ # Access fixture using table name and record name
98
+ page = panda_cms_pages(:home_page)
99
+ expect(page.title).to eq("Home")
100
+ ```
101
+
102
+ When testing models with file validations or complex callbacks, use the helper methods in `spec/models/panda/cms/page_spec.rb` as a reference.
103
+
69
104
  ## License
70
105
 
71
106
  The gem is available as open source under the terms of the [BSD-3-Clause License](https://opensource.org/licenses/bsd-3-clause).
72
107
 
73
- Copyright © 2024 - 2025, Panda Software Limited.
108
+ Copyright © 2024 - 2025, Otaina Limited.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/setup"
2
4
 
3
5
  APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
@@ -4,8 +4,7 @@ module Panda
4
4
  module CMS
5
5
  module Admin
6
6
  class StatisticsComponent < ViewComponent::Base
7
- attr_reader :metric
8
- attr_reader :value
7
+ attr_reader :metric, :value
9
8
 
10
9
  def initialize(metric:, value:)
11
10
  @metric = metric
@@ -1,5 +1,7 @@
1
- <% if user.is_a?(Panda::CMS::User) %>
1
+ <% if user.is_a?(Panda::Core::User) && time %>
2
2
  <%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "#{time_ago_in_words(time)} ago") %>
3
+ <% elsif user.is_a?(Panda::Core::User) %>
4
+ <%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "Not published") %>
3
5
  <% elsif time %>
4
6
  <div class="text-black/60"><%= time_ago_in_words(time) %> ago</div>
5
7
  <% end %>
@@ -4,16 +4,14 @@ module Panda
4
4
  module CMS
5
5
  module Admin
6
6
  class UserActivityComponent < ViewComponent::Base
7
- attr_accessor :model
8
- attr_accessor :time
9
- attr_accessor :user
7
+ attr_accessor :model, :time, :user
10
8
 
11
9
  # @param model [ActiveRecord::Base] Model instance to which the user activity is related
12
10
  # @param at [ActiveSupport::TimeWithZone] Time of the activity
13
- # @param user [Panda::CMS::User] User who performed the activity
11
+ # @param user [Panda::Core::User] User who performed the activity
14
12
  def initialize(model: nil, at: nil, user: nil)
15
13
  @model = model
16
- @user = user if user.is_a?(::Panda::CMS::User)
14
+ @user = user if user.is_a?(::Panda::Core::User)
17
15
  @time = at if at.is_a?(::ActiveSupport::TimeWithZone)
18
16
  end
19
17
  end
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <% end %>
12
12
  <div class="ml-3">
13
- <p class="text-sm text-black"><%= user.firstname %> <%= user.lastname %></p>
13
+ <p class="text-sm text-black"><%= user.name %></p>
14
14
  <% if metadata %><p class="text-sm text-black/60"><%= metadata %></p><% end %>
15
15
  </div>
16
16
  </div>
@@ -7,8 +7,8 @@ module Panda
7
7
  attr_accessor :user_id, :user, :metadata
8
8
 
9
9
  def initialize(user_id: nil, user: nil, metadata: "")
10
- @user = if user.nil? && user_id.present? && Panda::CMS::User.find(user_id)
11
- Panda::CMS::User.find(user_id)
10
+ @user = if user.nil? && user_id.present? && Panda::Core::User.find(user_id)
11
+ Panda::Core::User.find(user_id)
12
12
  else
13
13
  user
14
14
  end
@@ -17,15 +17,19 @@ module Panda
17
17
  @options[:id] ||= "code-#{key.to_s.dasherize}"
18
18
  @editable = editable
19
19
 
20
- raise BlockError.new("Key 'code' is not allowed for CodeComponent") if key == :code
20
+ raise BlockError, "Key 'code' is not allowed for CodeComponent" if key == :code
21
21
  end
22
22
 
23
23
  def call
24
24
  # TODO: For the non-editable version, grab this from a cache or similar?
25
- block = Panda::CMS::Block.find_by(kind: KIND, key: @key, panda_cms_template_id: Current.page.panda_cms_template_id)
25
+ block = Panda::CMS::Block.find_by(kind: KIND, key: @key,
26
+ panda_cms_template_id: Current.page.panda_cms_template_id)
26
27
 
27
28
  if block.nil?
28
- raise Panda::CMS::MissingBlockError.new("Block with key #{@key} not found for page #{Current.page.title}") unless Rails.env.production?
29
+ unless Rails.env.production?
30
+ raise Panda::CMS::MissingBlockError, "Block with key #{@key} not found for page #{Current.page.title}"
31
+ end
32
+
29
33
  return false
30
34
  end
31
35
 
@@ -58,7 +62,7 @@ module Panda
58
62
 
59
63
  def is_embedded?
60
64
  # TODO: Check security on this - embed_id should match something?
61
- request.params.dig(:embed_id).present?
65
+ request.params[:embed_id].present?
62
66
  end
63
67
  end
64
68
  end
@@ -23,24 +23,25 @@ module Panda
23
23
 
24
24
  @menu_items = @menu_items.order(:lft).map do |menu_item|
25
25
  if is_active?(menu_item)
26
- menu_item.define_singleton_method(:css_classes) { styles[:default] + " " + styles[:active] }
26
+ menu_item.define_singleton_method(:css_classes) { "#{styles[:default]} #{styles[:active]}" }
27
27
  else
28
- menu_item.define_singleton_method(:css_classes) { styles[:default] + " " + styles[:inactive] }
28
+ menu_item.define_singleton_method(:css_classes) { "#{styles[:default]} #{styles[:inactive]}" }
29
29
  end
30
30
 
31
31
  menu_item
32
32
  end
33
33
 
34
34
  # TODO: Surely don't need this but Current.page isn't working in the component
35
- if @render_page_menu
36
- @current_page = Panda::CMS::Page.find_by(path: @current_path)
37
- @page_menu_styles = page_menu_styles
38
- end
35
+ return unless @render_page_menu
36
+
37
+ @current_page = Panda::CMS::Page.find_by(path: @current_path)
38
+ @page_menu_styles = page_menu_styles
39
39
  end
40
40
 
41
41
  def is_active?(menu_item)
42
42
  return true if @current_path == "/" && active_link?(menu_item.page.path, match: :exact)
43
43
  return true if menu_item.page.path != "/" && active_link?(menu_item.page.path, match: :starts_with)
44
+
44
45
  false
45
46
  end
46
47
 
@@ -3,31 +3,29 @@
3
3
  module Panda
4
4
  module CMS
5
5
  class PageMenuComponent < ViewComponent::Base
6
- attr_accessor :page
7
- attr_accessor :menu_item
8
- attr_accessor :styles
6
+ attr_accessor :page, :menu_item, :styles
9
7
 
10
8
  def initialize(page:, start_depth:, styles: {}, show_heading: true)
11
9
  @page = page
12
10
 
13
- unless @page.nil?
14
- start_page = if @page.depth == start_depth
15
- @page
16
- else
17
- @page.ancestors.find { |anc| anc.depth == start_depth }
18
- end
11
+ return if @page.nil?
19
12
 
20
- menu = start_page&.page_menu
21
- return if menu.nil?
13
+ start_page = if @page.depth == start_depth
14
+ @page
15
+ else
16
+ @page.ancestors.find { |anc| anc.depth == start_depth }
17
+ end
22
18
 
23
- @menu_item = menu.menu_items.order(:lft)&.first
19
+ menu = start_page&.page_menu
20
+ return if menu.nil?
24
21
 
25
- @show_heading = show_heading
22
+ @menu_item = menu.menu_items.order(:lft)&.first
26
23
 
27
- # Set some default styles for sanity
28
- @styles = styles
29
- @styles[:indent_with] ||= "pl-2"
30
- end
24
+ @show_heading = show_heading
25
+
26
+ # Set some default styles for sanity
27
+ @styles = styles
28
+ @styles[:indent_with] ||= "pl-2"
31
29
  end
32
30
 
33
31
  def render?
@@ -12,9 +12,7 @@ module Panda
12
12
 
13
13
  KIND = "rich_text"
14
14
 
15
- attr_accessor :editable
16
- attr_accessor :content
17
- attr_accessor :options
15
+ attr_accessor :editable, :content, :options
18
16
 
19
17
  def initialize(key: :text_component, text: "Lorem ipsum...", editable: true, **options)
20
18
  @key = key
@@ -27,7 +25,8 @@ module Panda
27
25
  def before_render
28
26
  @editable &&= params[:embed_id].present? && params[:embed_id] == Current.page.id && Current.user.admin?
29
27
 
30
- block = Panda::CMS::Block.find_by(kind: "rich_text", key: @key, panda_cms_template_id: Current.page.panda_cms_template_id)
28
+ block = Panda::CMS::Block.find_by(kind: "rich_text", key: @key,
29
+ panda_cms_template_id: Current.page.panda_cms_template_id)
31
30
  raise ComponentError, "Block not found for key: #{@key}" unless block
32
31
 
33
32
  block_content = block.block_contents.find_by(panda_cms_page_id: Current.page.id)
@@ -65,7 +64,7 @@ module Panda
65
64
  # Ensure the content is properly structured
66
65
  {
67
66
  "time" => parsed["time"] || Time.current.to_i * 1000,
68
- "blocks" => parsed["blocks"].map { |block|
67
+ "blocks" => parsed["blocks"].map do |block|
69
68
  {
70
69
  "type" => block["type"],
71
70
  "data" => block["data"].merge(
@@ -73,19 +72,19 @@ module Panda
73
72
  ),
74
73
  "tunes" => block["tunes"]
75
74
  }.compact
76
- },
75
+ end,
77
76
  "version" => parsed["version"] || "2.28.2"
78
77
  }
79
78
  else
80
79
  # If not valid EditorJS, try to convert from HTML
81
80
  begin
82
- editor_content = Panda::CMS::HtmlToEditorJsConverter.convert(@content)
81
+ editor_content = Panda::Editor::HtmlToEditorJsConverter.convert(@content)
83
82
  if valid_editor_js_content?(editor_content)
84
83
  editor_content
85
84
  else
86
85
  empty_editor_js_content
87
86
  end
88
- rescue Panda::CMS::HtmlToEditorJsConverter::ConversionError => e
87
+ rescue Panda::Editor::HtmlToEditorJsConverter::ConversionError => e
89
88
  Rails.logger.error("HTML conversion error: #{e.message}")
90
89
  empty_editor_js_content
91
90
  end
@@ -94,13 +93,13 @@ module Panda
94
93
  Rails.logger.error("JSON parse error: #{e.message}")
95
94
  # Try to convert from HTML
96
95
  begin
97
- editor_content = Panda::CMS::HtmlToEditorJsConverter.convert(@content)
96
+ editor_content = Panda::Editor::HtmlToEditorJsConverter.convert(@content)
98
97
  if valid_editor_js_content?(editor_content)
99
98
  editor_content
100
99
  else
101
100
  empty_editor_js_content
102
101
  end
103
- rescue Panda::CMS::HtmlToEditorJsConverter::ConversionError => e
102
+ rescue Panda::Editor::HtmlToEditorJsConverter::ConversionError => e
104
103
  Rails.logger.error("HTML conversion error: #{e.message}")
105
104
  empty_editor_js_content
106
105
  end
@@ -130,7 +129,7 @@ module Panda
130
129
  parsed_content["blocks"][0]["data"]["text"].blank?
131
130
  "<p></p>".html_safe
132
131
  else
133
- renderer = Panda::CMS::EditorJs::Renderer.new(parsed_content)
132
+ renderer = Panda::Editor::Renderer.new(parsed_content)
134
133
  rendered = renderer.render
135
134
  rendered.presence&.html_safe || "<p></p>".html_safe
136
135
  end
@@ -25,10 +25,10 @@ module Panda
25
25
  content_tag(:span, @content, @options, false) # Don't escape the content
26
26
  rescue
27
27
  if !Rails.env.production? || is_defined?(Sentry)
28
- raise Panda::CMS::MissingBlockError.new("Block with key #{@key} not found for page #{Current.page.title}")
29
- else
30
- false
28
+ raise Panda::CMS::MissingBlockError, "Block with key #{@key} not found for page #{Current.page.title}"
31
29
  end
30
+
31
+ false
32
32
  end
33
33
 
34
34
  #
@@ -45,11 +45,10 @@ module Panda
45
45
  def before_render
46
46
  @editable &&= params[:embed_id].present? && params[:embed_id] == Current.page.id
47
47
 
48
- block = Panda::CMS::Block.find_by(kind: KIND, key: @key, panda_cms_template_id: Current.page.panda_cms_template_id)
48
+ block = Panda::CMS::Block.find_by(kind: KIND, key: @key,
49
+ panda_cms_template_id: Current.page.panda_cms_template_id)
49
50
 
50
- if block.nil?
51
- return false
52
- end
51
+ return false if block.nil?
53
52
 
54
53
  block_content = block.block_contents.find_by(panda_cms_page_id: Current.page.id)
55
54
  plain_text = block_content&.content.to_s
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module CMS
5
+ module Admin
6
+ # Base controller for all CMS admin controllers
7
+ # Inherits from Core AdminController for authentication
8
+ # Adds CMS-specific helpers and functionality
9
+ class BaseController < ::Panda::Core::AdminController
10
+ # Include CMS helpers so views have access to panda_cms_form_with, etc.
11
+ helper Panda::CMS::ApplicationHelper
12
+
13
+ # Include the helper methods in the controller as well
14
+ include Panda::CMS::ApplicationHelper
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,10 +3,9 @@
3
3
  module Panda
4
4
  module CMS
5
5
  module Admin
6
- class BlockContentsController < ApplicationController
6
+ class BlockContentsController < ::Panda::CMS::Admin::BaseController
7
7
  before_action :set_page, only: %i[update]
8
8
  before_action :set_block_content, only: %i[update]
9
- before_action :authenticate_admin_user!
10
9
 
11
10
  # @type PATCH/PUT
12
11
  # @return
@@ -1,19 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "groupdate"
2
4
 
3
5
  module Panda
4
6
  module CMS
5
- class Admin::DashboardController < ApplicationController
6
- before_action :set_initial_breadcrumb, only: %i[show]
7
- before_action :authenticate_admin_user!
7
+ module Admin
8
+ class DashboardController < ::Panda::Core::Admin::DashboardController
9
+ before_action :set_initial_breadcrumb, only: %i[show]
8
10
 
9
- # GET /admin
10
- def show
11
- end
11
+ # Override the panda-core dashboard with CMS-specific dashboard
12
+ def show
13
+ # Render the CMS dashboard view
14
+ render "panda/cms/admin/dashboard/show"
15
+ end
12
16
 
13
- private
17
+ private
14
18
 
15
- def set_initial_breadcrumb
16
- add_breadcrumb "Dashboard", Panda::CMS.route_namespace
19
+ def set_initial_breadcrumb
20
+ add_breadcrumb "Dashboard", admin_cms_dashboard_path
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -3,9 +3,7 @@
3
3
  module Panda
4
4
  module CMS
5
5
  module Admin
6
- class FilesController < ApplicationController
7
- before_action :authenticate_admin_user!
8
-
6
+ class FilesController < ::Panda::CMS::Admin::BaseController
9
7
  def create
10
8
  file = params[:image]
11
9
  return render json: {success: 0} unless file
@@ -3,9 +3,8 @@
3
3
  module Panda
4
4
  module CMS
5
5
  module Admin
6
- class FormsController < ApplicationController
6
+ class FormsController < ::Panda::CMS::Admin::BaseController
7
7
  before_action :set_initial_breadcrumb, only: %i[index show]
8
- before_action :authenticate_admin_user!
9
8
 
10
9
  # Lists all forms
11
10
  # @type GET
@@ -18,7 +17,7 @@ module Panda
18
17
  def show
19
18
  form = Panda::CMS::Form.find(params[:id])
20
19
 
21
- add_breadcrumb form.name, admin_form_path(form)
20
+ add_breadcrumb form.name, admin_cms_form_path(form)
22
21
  submissions = form.form_submissions.order(created_at: :desc)
23
22
  # TODO: Set a whitelist of fields we allow to be submitted for the form, shown in this view
24
23
  # and a formatting array of how to display them... eventually?
@@ -35,11 +34,9 @@ module Panda
35
34
  private
36
35
 
37
36
  def set_initial_breadcrumb
38
- add_breadcrumb "Forms", admin_forms_path
37
+ add_breadcrumb "Forms", admin_cms_forms_path
39
38
  end
40
39
 
41
- private
42
-
43
40
  # Only allow a list of trusted parameters through
44
41
  # @type private
45
42
  # @return ActionController::StrongParameters
@@ -3,9 +3,8 @@
3
3
  module Panda
4
4
  module CMS
5
5
  module Admin
6
- class MenusController < ApplicationController
6
+ class MenusController < ::Panda::CMS::Admin::BaseController
7
7
  before_action :set_initial_breadcrumb, only: %i[index]
8
- before_action :authenticate_admin_user!
9
8
 
10
9
  # Lists all menus which can be managed by the administrator
11
10
  # @type GET
@@ -22,7 +21,7 @@ module Panda
22
21
  end
23
22
 
24
23
  def set_initial_breadcrumb
25
- add_breadcrumb "Menus", admin_menus_path
24
+ add_breadcrumb "Menus", admin_cms_menus_path
26
25
  end
27
26
  end
28
27
  end
@@ -3,9 +3,9 @@
3
3
  module Panda
4
4
  module CMS
5
5
  module Admin
6
- class PagesController < ApplicationController
6
+ class PagesController < ::Panda::CMS::Admin::BaseController
7
7
  before_action :set_initial_breadcrumb, only: %i[index edit new create update]
8
- before_action :authenticate_admin_user!
8
+ # Authentication is automatically enforced by AdminController
9
9
 
10
10
  # Lists all pages which can be managed by the administrator
11
11
  # @type GET
@@ -25,7 +25,7 @@ module Panda
25
25
  # Loads the page editor
26
26
  # @type GET
27
27
  def edit
28
- add_breadcrumb page.title, edit_admin_page_path(page)
28
+ add_breadcrumb page.title, edit_admin_cms_page_path(page)
29
29
 
30
30
  render :edit, locals: {page: page, template: page.template}
31
31
  end
@@ -38,12 +38,13 @@ module Panda
38
38
  page.path = nil if page.path.blank?
39
39
 
40
40
  # Set the full path before validation if we have a parent
41
- if page.parent && page.parent.path != "/" && page.path.present?
41
+ if page.parent && page.parent.path != "/" && page.path.present? && !page.path.start_with?(page.parent.path)
42
+ # Only prepend parent path if it's not already included
42
43
  page.path = page.parent.path + page.path
43
44
  end
44
45
 
45
46
  if page.save
46
- redirect_to edit_admin_page_path(page), notice: "The page was successfully created."
47
+ redirect_to edit_admin_cms_page_path(page), notice: "The page was successfully created."
47
48
  else
48
49
  flash.now[:error] = page.errors.full_messages.to_sentence
49
50
  locals = setup_new_page_form(page: page)
@@ -55,7 +56,7 @@ module Panda
55
56
  # @return
56
57
  def update
57
58
  if page.update(page_params)
58
- redirect_to edit_admin_page_path(page),
59
+ redirect_to edit_admin_cms_page_path(page),
59
60
  status: :see_other,
60
61
  flash: {success: "This page was successfully updated!"}
61
62
  else
@@ -78,11 +79,11 @@ module Panda
78
79
  end
79
80
 
80
81
  def set_initial_breadcrumb
81
- add_breadcrumb "Pages", admin_pages_path
82
+ add_breadcrumb "Pages", admin_cms_pages_path
82
83
  end
83
84
 
84
85
  def setup_new_page_form(page:)
85
- add_breadcrumb "Add Page", new_admin_page_path
86
+ add_breadcrumb "Add Page", new_admin_cms_page_path
86
87
  {
87
88
  page: page,
88
89
  available_templates: Panda::CMS::Template.available
@@ -5,9 +5,8 @@ require "json"
5
5
  module Panda
6
6
  module CMS
7
7
  module Admin
8
- class PostsController < ApplicationController
8
+ class PostsController < ::Panda::CMS::Admin::BaseController
9
9
  before_action :set_initial_breadcrumb, only: %i[index edit new create update]
10
- before_action :authenticate_admin_user!
11
10
 
12
11
  # Get all posts
13
12
  # @type GET
@@ -27,7 +26,7 @@ module Panda
27
26
  # Loads the post editor
28
27
  # @type GET
29
28
  def edit
30
- add_breadcrumb post.title, edit_admin_post_path(post.admin_param)
29
+ add_breadcrumb post.title, edit_admin_cms_post_path(post.admin_param)
31
30
  render :edit, locals: {post: post}
32
31
  end
33
32
 
@@ -39,8 +38,7 @@ module Panda
39
38
 
40
39
  if @post.save
41
40
  Rails.logger.debug "Post saved successfully"
42
- flash[:success] = "The post was successfully created!"
43
- redirect_to edit_admin_post_path(@post.admin_param), status: :see_other
41
+ redirect_to edit_admin_cms_post_path(@post.admin_param), notice: "The post was successfully created!"
44
42
  else
45
43
  Rails.logger.debug "Post save failed: #{@post.errors.full_messages.inspect}"
46
44
  flash.now[:error] = @post.errors.full_messages.join(", ")
@@ -61,13 +59,13 @@ module Panda
61
59
  update_params[:user_id] = current_user.id
62
60
  if post.update(update_params)
63
61
  Rails.logger.debug "Post updated successfully"
64
- add_breadcrumb post.title, edit_admin_post_path(post.admin_param)
62
+ add_breadcrumb post.title, edit_admin_cms_post_path(post.admin_param)
65
63
  flash[:success] = "The post was successfully updated"
66
- redirect_to edit_admin_post_path(post.admin_param), status: :see_other
64
+ redirect_to edit_admin_cms_post_path(post.admin_param), status: :see_other
67
65
  else
68
66
  Rails.logger.debug "Post update failed: #{post.errors.full_messages.inspect}"
69
67
  Rails.logger.debug "Preserving content: #{post_params[:content].inspect}"
70
- add_breadcrumb post.title.presence || "Edit Post", edit_admin_post_path(post.admin_param)
68
+ add_breadcrumb post.title.presence || "Edit Post", edit_admin_cms_post_path(post.admin_param)
71
69
  flash.now[:error] = post.errors.full_messages.join(", ")
72
70
  render :edit, locals: {
73
71
  post: post,
@@ -93,11 +91,11 @@ module Panda
93
91
  end
94
92
 
95
93
  def set_initial_breadcrumb
96
- add_breadcrumb "Posts", admin_posts_path
94
+ add_breadcrumb "Posts", admin_cms_posts_path
97
95
  end
98
96
 
99
97
  def setup_new_post_form(post: nil, preserved_content: nil)
100
- add_breadcrumb "Add Post", new_admin_post_path
98
+ add_breadcrumb "Add Post", new_admin_cms_post_path
101
99
 
102
100
  post ||= Panda::CMS::Post.new(
103
101
  status: "active",
@@ -106,7 +104,7 @@ module Panda
106
104
 
107
105
  {
108
106
  post: post,
109
- url: admin_posts_path,
107
+ url: admin_cms_posts_path,
110
108
  preserved_content: preserved_content
111
109
  }
112
110
  end