panda-cms 0.7.4 → 0.7.5

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/Rakefile +2 -0
  4. data/app/builders/panda/cms/form_builder.rb +13 -5
  5. data/app/components/panda/cms/admin/heading_component.rb +5 -4
  6. data/app/components/panda/cms/admin/panel_component.rb +2 -2
  7. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  8. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  9. data/app/components/panda/cms/admin/user_activity_component.rb +1 -3
  10. data/app/components/panda/cms/code_component.rb +8 -4
  11. data/app/components/panda/cms/menu_component.rb +7 -6
  12. data/app/components/panda/cms/page_menu_component.rb +15 -17
  13. data/app/components/panda/cms/rich_text_component.rb +5 -6
  14. data/app/components/panda/cms/text_component.rb +6 -7
  15. data/app/constraints/panda/cms/admin_constraint.rb +4 -1
  16. data/app/controllers/panda/cms/admin/dashboard_controller.rb +13 -9
  17. data/app/controllers/panda/cms/admin/forms_controller.rb +0 -2
  18. data/app/controllers/panda/cms/admin/my_profile_controller.rb +2 -1
  19. data/app/controllers/panda/cms/admin/pages_controller.rb +2 -1
  20. data/app/controllers/panda/cms/admin/posts_controller.rb +1 -2
  21. data/app/controllers/panda/cms/admin/sessions_controller.rb +3 -5
  22. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  23. data/app/controllers/panda/cms/admin/settings_controller.rb +14 -10
  24. data/app/controllers/panda/cms/application_controller.rb +7 -2
  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 +32 -29
  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/asset_helper.rb +182 -0
  32. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  33. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  34. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  35. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  36. data/app/javascript/panda/cms/controllers/index.js +3 -9
  37. data/app/javascript/panda/cms/controllers/theme_form_controller.js +16 -0
  38. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  39. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  40. data/app/jobs/panda/cms/application_job.rb +2 -0
  41. data/app/jobs/panda/cms/record_visit_job.rb +2 -0
  42. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  43. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  44. data/app/models/panda/cms/application_record.rb +2 -0
  45. data/app/models/panda/cms/block.rb +4 -1
  46. data/app/models/panda/cms/block_content.rb +2 -0
  47. data/app/models/panda/cms/breadcrumb.rb +2 -0
  48. data/app/models/panda/cms/current.rb +2 -0
  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 +9 -5
  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/user.rb +2 -0
  58. data/app/models/panda/cms/visit.rb +2 -0
  59. data/app/models/panda/social/instagram_post.rb +2 -0
  60. data/app/services/panda/cms/html_to_editor_js_converter.rb +4 -2
  61. data/app/services/panda/social/instagram_feed_service.rb +3 -1
  62. data/app/views/layouts/different_page.html.erb +6 -0
  63. data/app/views/layouts/homepage.html.erb +37 -0
  64. data/app/views/layouts/page.html.erb +18 -0
  65. data/app/views/layouts/panda/cms/application.html.erb +1 -0
  66. data/app/views/panda/cms/admin/pages/new.html.erb +14 -8
  67. data/app/views/panda/cms/admin/settings/index.html.erb +1 -1
  68. data/app/views/panda/cms/shared/_header.html.erb +10 -2
  69. data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
  70. data/app/views/shared/_footer.html.erb +3 -0
  71. data/app/views/shared/_header.html.erb +11 -0
  72. data/config/importmap.rb +2 -0
  73. data/config/initializers/inflections.rb +2 -0
  74. data/config/initializers/panda/cms/form_errors.rb +20 -21
  75. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  76. data/config/initializers/panda/cms.rb +2 -0
  77. data/config/initializers/zeitwork.rb +2 -0
  78. data/config/puma/test.rb +3 -1
  79. data/config/routes.rb +8 -8
  80. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  81. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  82. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  83. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  84. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  85. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  86. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  87. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  88. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  89. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  90. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  91. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  92. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  93. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  94. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  95. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  96. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  97. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  98. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  99. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  100. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  101. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  102. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  103. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  104. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  105. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  106. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  107. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  108. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  109. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  110. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  111. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  112. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  113. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  114. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  115. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  116. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  117. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  118. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  119. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +2 -0
  120. data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
  121. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +2 -0
  122. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +2 -0
  123. data/db/seeds.rb +2 -0
  124. data/lib/generators/panda/cms/install_generator.rb +2 -0
  125. data/lib/panda/cms/asset_loader.rb +390 -0
  126. data/lib/panda/cms/bulk_editor.rb +7 -3
  127. data/lib/panda/cms/demo_site_generator.rb +2 -0
  128. data/lib/panda/cms/editor_js/blocks/alert.rb +2 -0
  129. data/lib/panda/cms/editor_js/blocks/base.rb +2 -0
  130. data/lib/panda/cms/editor_js/blocks/header.rb +2 -0
  131. data/lib/panda/cms/editor_js/blocks/image.rb +3 -0
  132. data/lib/panda/cms/editor_js/blocks/list.rb +2 -0
  133. data/lib/panda/cms/editor_js/blocks/paragraph.rb +3 -0
  134. data/lib/panda/cms/editor_js/blocks/quote.rb +3 -0
  135. data/lib/panda/cms/editor_js/blocks/table.rb +3 -1
  136. data/lib/panda/cms/editor_js/renderer.rb +3 -0
  137. data/lib/panda/cms/editor_js.rb +2 -0
  138. data/lib/panda/cms/editor_js_content.rb +47 -41
  139. data/lib/panda/cms/engine.rb +29 -32
  140. data/lib/panda/cms/exceptions_app.rb +2 -0
  141. data/lib/panda/cms/railtie.rb +2 -0
  142. data/lib/panda/cms/slug.rb +3 -1
  143. data/lib/panda-cms/version.rb +3 -1
  144. data/lib/panda-cms.rb +4 -2
  145. data/lib/tasks/assets.rake +547 -0
  146. data/lib/tasks/panda/cms/install.rake +2 -0
  147. data/lib/tasks/panda/social/instagram.rake +2 -0
  148. data/lib/tasks/panda_cms.rake +3 -30
  149. data/public/panda-cms-assets/manifest.json +20 -0
  150. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  151. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  152. metadata +168 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07d84fd0b6bd17f5e5f253a570bae55270227bec0196037960aa3358d09903bd
4
- data.tar.gz: c6bc6d0173f89d965a23132bc016bfd3a44e6bd0b2533960cca495fa28e645e3
3
+ metadata.gz: a11a8249369ac564953d025d4887020ced356b6357a5d8c823580c27e39394cb
4
+ data.tar.gz: 04d6f8dab1ae48f77414ea10b515739f36a54c0f37b79e82f7d9282305af03a1
5
5
  SHA512:
6
- metadata.gz: 63b30314a40563ab4cb8f99d516cd30edc3aed7ffe8d1841d4b8760ea6c80c8b253a983ca7cadd473caba240a7b18132cda903b23ed35da845833aef4880b2d4
7
- data.tar.gz: 820e1b606f088e8bacd7e03d997812f5a2255941e399e8fe863076a0a1196935c66c6a2aca34d50f9dd69563280cda9d795692ccba6df5af565aefd2b38439e2
6
+ metadata.gz: eb71f7f01ba585e142738e5d28510c5a0508b69843aab7c5b60fbf5cbaa7bb89419a916df923d0282cd2311a61614d836002635e67b9d6dfadd4bd6dba4e3f81
7
+ data.tar.gz: a795545501c2633b3e911b93ccd5c302adbb7d9c9f5a0ab6c363196ae4e0d18089a2b99cc3ada4b66e44902c8a5828212251b331e1b8b4d4762dfafef74dfe0f
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__)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormBuilder < ActionView::Helpers::FormBuilder
4
6
  include ActionView::Helpers::TagHelper
5
7
  include ActionView::Helpers::FormTagHelper
6
8
 
7
- def label(attribute, text = nil, options = {}, &block)
9
+ def label(attribute, text = nil, options = {})
8
10
  super(attribute, text, options.reverse_merge(class: label_styles))
9
11
  end
10
12
 
@@ -13,8 +15,11 @@ module Panda
13
15
  content_tag :div, class: container_styles do
14
16
  label(attribute) + meta_text(options) +
15
17
  content_tag(:div, class: "flex flex-grow") do
16
- content_tag(:span, class: "inline-flex items-center px-3 text-base border border-r-none rounded-s-md whitespace-nowrap break-keep") { options.dig(:data, :prefix) } +
17
- super(attribute, options.reverse_merge(class: input_styles_prefix + " input-prefix rounded-l-none border-l-none"))
18
+ content_tag(:span,
19
+ class: "inline-flex items-center px-3 text-base border border-r-none rounded-s-md whitespace-nowrap break-keep") do
20
+ options.dig(:data, :prefix)
21
+ end +
22
+ super(attribute, options.reverse_merge(class: "#{input_styles_prefix} input-prefix rounded-l-none border-l-none"))
18
23
  end + error_message(attribute)
19
24
  end
20
25
  else
@@ -48,7 +53,7 @@ module Panda
48
53
  end
49
54
  end
50
55
 
51
- def select(method, choices = nil, options = {}, html_options = {}, &block)
56
+ def select(method, choices = nil, options = {}, html_options = {})
52
57
  content_tag :div, class: container_styles do
53
58
  label(method) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg + error_message(method)
54
59
  end
@@ -147,6 +152,7 @@ module Panda
147
152
 
148
153
  def meta_text(options)
149
154
  return unless options[:meta]
155
+
150
156
  @template.content_tag(:p, options[:meta], class: "block text-black/60 text-sm mb-2")
151
157
  end
152
158
 
@@ -173,7 +179,8 @@ module Panda
173
179
  end
174
180
 
175
181
  def select_svg
176
- @template.content_tag(:svg, class: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", aria_hidden: true) do
182
+ @template.content_tag(:svg,
183
+ class: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", aria_hidden: true) do
177
184
  @template.content_tag(:path, d: "M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z")
178
185
  end
179
186
  end
@@ -208,6 +215,7 @@ module Panda
208
215
 
209
216
  def error_message(attribute)
210
217
  return unless object.respond_to?(:errors) && object.errors[attribute]&.any?
218
+
211
219
  content_tag(:p, class: "mt-2 text-sm text-red-600") do
212
220
  object.errors[attribute].join(", ")
213
221
  end
@@ -29,13 +29,14 @@ module Panda
29
29
  output = output.html_safe
30
30
  base_heading_styles = "flex pt-1 text-black mb-5 -mt-1"
31
31
 
32
- if level == 1
32
+ case level
33
+ when 1
33
34
  content_tag(:h1, output, class: [base_heading_styles, "text-2xl font-medium", @additional_styles])
34
- elsif level == 2
35
+ when 2
35
36
  content_tag(:h2, output, class: [base_heading_styles, "text-xl font-medium", @additional_styles])
36
- elsif level == 3
37
+ when 3
37
38
  content_tag(:h3, output, class: [base_heading_styles, "text-xl", "font-light", @additional_styles])
38
- elsif level == :panel
39
+ when :panel
39
40
  content_tag(:h3, output, class: ["text-base font-medium p-4 text-white"])
40
41
  end
41
42
  end
@@ -4,9 +4,9 @@ module Panda
4
4
  module CMS
5
5
  module Admin
6
6
  class PanelComponent < ViewComponent::Base
7
- renders_one :heading, ->(text:, icon: "", level: :panel, additional_styles: "") do
7
+ renders_one :heading, lambda { |text:, icon: "", level: :panel, additional_styles: ""|
8
8
  Panda::CMS::Admin::HeadingComponent.new(text: text, icon: icon, level: level, additional_styles: additional_styles)
9
- end
9
+ }
10
10
  end
11
11
  end
12
12
  end
@@ -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::CMS::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::CMS::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,9 +4,7 @@ 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
@@ -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,7 +72,7 @@ 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
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class AdminConstraint
@@ -11,7 +13,8 @@ module Panda
11
13
  end
12
14
 
13
15
  def current_user(request)
14
- User.find_by(id: request.session[:user_id])
16
+ user_id = request.session[:user_id]
17
+ User.find_by(id: user_id)
15
18
  end
16
19
  end
17
20
  end
@@ -1,19 +1,23 @@
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 < ApplicationController
9
+ before_action :set_initial_breadcrumb, only: %i[show]
10
+ before_action :authenticate_admin_user!
8
11
 
9
- # GET /admin
10
- def show
11
- end
12
+ # GET /admin
13
+ def show
14
+ end
12
15
 
13
- private
16
+ private
14
17
 
15
- def set_initial_breadcrumb
16
- add_breadcrumb "Dashboard", Panda::CMS.route_namespace
18
+ def set_initial_breadcrumb
19
+ add_breadcrumb "Dashboard", Panda::CMS.route_namespace
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -38,8 +38,6 @@ module Panda
38
38
  add_breadcrumb "Forms", admin_forms_path
39
39
  end
40
40
 
41
- private
42
-
43
41
  # Only allow a list of trusted parameters through
44
42
  # @type private
45
43
  # @return ActionController::StrongParameters
@@ -19,7 +19,8 @@ module Panda
19
19
  # @return void
20
20
  def update
21
21
  if current_user.update(user_params)
22
- redirect_to edit_admin_my_profile_path, flash: {success: "Your profile has been updated successfully."}
22
+ flash[:success] = "Your profile has been updated successfully."
23
+ redirect_to edit_admin_my_profile_path
23
24
  else
24
25
  render :edit, locals: {user: current_user}, status: :unprocessable_entity
25
26
  end
@@ -38,7 +38,8 @@ 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
 
@@ -39,8 +39,7 @@ module Panda
39
39
 
40
40
  if @post.save
41
41
  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
42
+ redirect_to edit_admin_post_path(@post.admin_param), notice: "The post was successfully created!"
44
43
  else
45
44
  Rails.logger.debug "Post save failed: #{@post.errors.full_messages.inspect}"
46
45
  flash.now[:error] = @post.errors.full_messages.join(", ")
@@ -27,9 +27,7 @@ module Panda
27
27
 
28
28
  # Always create the first user as admin, regardless of what our settings look like
29
29
  # else we can't ever really login. :)
30
- if !create_as_admin
31
- create_as_admin = true if !create_as_admin && Panda::CMS::User.count.zero?
32
- end
30
+ create_as_admin = true if !create_as_admin && Panda::CMS::User.count.zero?
33
31
 
34
32
  if user_info["first_name"] && user_info["last_name"]
35
33
  firstname = user_info["first_name"]
@@ -55,9 +53,9 @@ module Panda
55
53
  return
56
54
  end
57
55
 
58
- if !user.admin?
56
+ unless user.admin?
59
57
  # User can't be found with this email address or can't login
60
- Rails.logger.error "User ID #{user.id} attempted admin login, is not admin." if user && !user.admin
58
+ Rails.logger.error "User ID #{user.id} attempted admin login, is not admin."
61
59
  redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
62
60
  return
63
61
  end
@@ -1,36 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
- class Admin::Settings::BulkEditorController < ApplicationController
4
- before_action :set_initial_breadcrumb, only: %i[new]
5
+ module Admin
6
+ module Settings
7
+ class BulkEditorController < ApplicationController
8
+ before_action :set_initial_breadcrumb, only: %i[new]
5
9
 
6
- def new
7
- @json_data = BulkEditor.export
8
- end
10
+ def new
11
+ @json_data = BulkEditor.export
12
+ end
9
13
 
10
- def create
11
- begin
12
- debug_output = BulkEditor.import(params[:site_content])
13
- rescue JSON::ParserError
14
- redirect_to admin_settings_bulk_editor_path, flash: {error: "Error parsing content; are you sure this update is valid? Reverting..."}
15
- return
16
- end
14
+ def create
15
+ begin
16
+ debug_output = BulkEditor.import(params[:site_content])
17
+ rescue JSON::ParserError
18
+ redirect_to admin_settings_bulk_editor_path,
19
+ flash: {error: "Error parsing content; are you sure this update is valid? Reverting..."}
20
+ return
21
+ end
17
22
 
18
- # Grab the latest content back so it's all formatted properly
19
- @json_data = BulkEditor.export
23
+ # Grab the latest content back so it's all formatted properly
24
+ @json_data = BulkEditor.export
20
25
 
21
- if debug_output[:error].empty? && debug_output[:warning].empty? && debug_output[:success].empty?
22
- redirect_to admin_settings_bulk_editor_path, flash: {success: "No changes were found!"}
23
- else
24
- @debug = debug_output
25
- render :new, flash: {warning: "Please review the output below for more information."}
26
- end
27
- end
26
+ if debug_output[:error].empty? && debug_output[:warning].empty? && debug_output[:success].empty?
27
+ redirect_to admin_settings_bulk_editor_path, flash: {success: "No changes were found!"}
28
+ else
29
+ @debug = debug_output
30
+ render :new, flash: {warning: "Please review the output below for more information."}
31
+ end
32
+ end
28
33
 
29
- private
34
+ private
30
35
 
31
- def set_initial_breadcrumb
32
- add_breadcrumb "Settings", admin_settings_path
33
- add_breadcrumb "Bulk Editor", "#"
36
+ def set_initial_breadcrumb
37
+ add_breadcrumb "Settings", admin_settings_path
38
+ add_breadcrumb "Bulk Editor", "#"
39
+ end
40
+ end
34
41
  end
35
42
  end
36
43
  end
@@ -1,19 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
- class Admin::SettingsController < ApplicationController
4
- before_action :set_initial_breadcrumb, only: %i[index show]
5
- before_action :authenticate_admin_user!
5
+ module Admin
6
+ class SettingsController < ApplicationController
7
+ before_action :set_initial_breadcrumb, only: %i[index show]
8
+ before_action :authenticate_admin_user!
6
9
 
7
- def index
8
- end
10
+ def index
11
+ end
9
12
 
10
- def show
11
- end
13
+ def show
14
+ end
12
15
 
13
- private
16
+ private
14
17
 
15
- def set_initial_breadcrumb
16
- add_breadcrumb "Settings", admin_settings_path
18
+ def set_initial_breadcrumb
19
+ add_breadcrumb "Settings", admin_settings_path
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationController < ::ActionController::Base
@@ -37,11 +39,14 @@ module Panda
37
39
  end
38
40
 
39
41
  def authenticate_user!
40
- redirect_to root_path, flash: {error: "Please login to view this!"} unless user_signed_in?
42
+ redirect_to main_app.root_path, flash: {error: "Please login to view this!"} unless user_signed_in?
41
43
  end
42
44
 
43
45
  def authenticate_admin_user!
44
- redirect_to root_path, flash: {error: "Please login to view this!"} unless user_signed_in? && current_user.admin?
46
+ return if user_signed_in? && current_user.admin?
47
+
48
+ redirect_to admin_login_path,
49
+ flash: {error: "Please login to view this!"}
45
50
  end
46
51
 
47
52
  # Required for paper_trail and seems as good as convention these days
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ErrorsController < ApplicationController
@@ -5,7 +7,8 @@ module Panda
5
7
 
6
8
  def show
7
9
  exception = request.env["action_dispatch.exception"]
8
- status_code = exception.try(:status_code) || ActionDispatch::ExceptionWrapper.new(request.env, exception).status_code
10
+ status_code = exception.try(:status_code) || ActionDispatch::ExceptionWrapper.new(request.env,
11
+ exception).status_code
9
12
 
10
13
  render view_for_code(status_code), status: status_code
11
14
  end
@@ -17,7 +20,7 @@ module Panda
17
20
  private
18
21
 
19
22
  def view_for_code(code)
20
- supported_error_codes.fetch(code) { "404" }
23
+ supported_error_codes.fetch(code, "404")
21
24
  end
22
25
 
23
26
  def supported_error_codes