panda-cms 0.7.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 (233) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +73 -0
  3. data/Rakefile +7 -0
  4. data/app/assets/builds/panda.cms.css +2808 -0
  5. data/app/assets/config/panda_cms_manifest.js +4 -0
  6. data/app/assets/stylesheets/panda/cms/application.tailwind.css +162 -0
  7. data/app/assets/stylesheets/panda/cms/editor.css +120 -0
  8. data/app/builders/panda/cms/form_builder.rb +234 -0
  9. data/app/components/panda/cms/admin/button_component.rb +70 -0
  10. data/app/components/panda/cms/admin/container_component.html.erb +13 -0
  11. data/app/components/panda/cms/admin/container_component.rb +13 -0
  12. data/app/components/panda/cms/admin/flash_message_component.html.erb +31 -0
  13. data/app/components/panda/cms/admin/flash_message_component.rb +47 -0
  14. data/app/components/panda/cms/admin/heading_component.rb +45 -0
  15. data/app/components/panda/cms/admin/panel_component.html.erb +7 -0
  16. data/app/components/panda/cms/admin/panel_component.rb +13 -0
  17. data/app/components/panda/cms/admin/slideover_component.html.erb +9 -0
  18. data/app/components/panda/cms/admin/slideover_component.rb +15 -0
  19. data/app/components/panda/cms/admin/statistics_component.html.erb +4 -0
  20. data/app/components/panda/cms/admin/statistics_component.rb +17 -0
  21. data/app/components/panda/cms/admin/tab_bar_component.html.erb +35 -0
  22. data/app/components/panda/cms/admin/tab_bar_component.rb +15 -0
  23. data/app/components/panda/cms/admin/table_component.html.erb +29 -0
  24. data/app/components/panda/cms/admin/table_component.rb +46 -0
  25. data/app/components/panda/cms/admin/tag_component.rb +35 -0
  26. data/app/components/panda/cms/admin/user_activity_component.html.erb +5 -0
  27. data/app/components/panda/cms/admin/user_activity_component.rb +33 -0
  28. data/app/components/panda/cms/admin/user_display_component.html.erb +17 -0
  29. data/app/components/panda/cms/admin/user_display_component.rb +21 -0
  30. data/app/components/panda/cms/code_component.rb +64 -0
  31. data/app/components/panda/cms/grid_component.html.erb +6 -0
  32. data/app/components/panda/cms/grid_component.rb +15 -0
  33. data/app/components/panda/cms/menu_component.html.erb +6 -0
  34. data/app/components/panda/cms/menu_component.rb +58 -0
  35. data/app/components/panda/cms/page_menu_component.html.erb +21 -0
  36. data/app/components/panda/cms/page_menu_component.rb +38 -0
  37. data/app/components/panda/cms/rich_text_component.html.erb +6 -0
  38. data/app/components/panda/cms/rich_text_component.rb +84 -0
  39. data/app/components/panda/cms/text_component.rb +72 -0
  40. data/app/constraints/panda/cms/admin_constraint.rb +18 -0
  41. data/app/controllers/panda/cms/admin/block_contents_controller.rb +52 -0
  42. data/app/controllers/panda/cms/admin/dashboard_controller.rb +20 -0
  43. data/app/controllers/panda/cms/admin/files_controller.rb +21 -0
  44. data/app/controllers/panda/cms/admin/forms_controller.rb +53 -0
  45. data/app/controllers/panda/cms/admin/menus_controller.rb +30 -0
  46. data/app/controllers/panda/cms/admin/pages_controller.rb +91 -0
  47. data/app/controllers/panda/cms/admin/posts_controller.rb +146 -0
  48. data/app/controllers/panda/cms/admin/sessions_controller.rb +94 -0
  49. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +37 -0
  50. data/app/controllers/panda/cms/admin/settings_controller.rb +20 -0
  51. data/app/controllers/panda/cms/application_controller.rb +57 -0
  52. data/app/controllers/panda/cms/errors_controller.rb +33 -0
  53. data/app/controllers/panda/cms/form_submissions_controller.rb +23 -0
  54. data/app/controllers/panda/cms/pages_controller.rb +72 -0
  55. data/app/controllers/panda/cms/posts_controller.rb +13 -0
  56. data/app/helpers/panda/cms/admin/files_helper.rb +6 -0
  57. data/app/helpers/panda/cms/admin/pages_helper.rb +6 -0
  58. data/app/helpers/panda/cms/admin/posts_helper.rb +48 -0
  59. data/app/helpers/panda/cms/application_helper.rb +120 -0
  60. data/app/helpers/panda/cms/pages_helper.rb +6 -0
  61. data/app/helpers/panda/cms/theme_helper.rb +18 -0
  62. data/app/javascript/panda/cms/@editorjs--editorjs.js +2577 -0
  63. data/app/javascript/panda/cms/@hotwired--stimulus.js +4 -0
  64. data/app/javascript/panda/cms/@hotwired--turbo.js +160 -0
  65. data/app/javascript/panda/cms/@rails--actioncable--src.js +4 -0
  66. data/app/javascript/panda/cms/application_panda_cms.js +39 -0
  67. data/app/javascript/panda/cms/controllers/dashboard_controller.js +7 -0
  68. data/app/javascript/panda/cms/controllers/editor_form_controller.js +77 -0
  69. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +320 -0
  70. data/app/javascript/panda/cms/controllers/index.js +48 -0
  71. data/app/javascript/panda/cms/controllers/slug_controller.js +87 -0
  72. data/app/javascript/panda/cms/editor/css_extractor.js +80 -0
  73. data/app/javascript/panda/cms/editor/editor_js_config.js +177 -0
  74. data/app/javascript/panda/cms/editor/editor_js_initializer.js +285 -0
  75. data/app/javascript/panda/cms/editor/plain_text_editor.js +110 -0
  76. data/app/javascript/panda/cms/editor/resource_loader.js +115 -0
  77. data/app/javascript/panda/cms/tailwindcss-stimulus-components.js +4 -0
  78. data/app/jobs/panda/cms/application_job.rb +6 -0
  79. data/app/jobs/panda/cms/record_visit_job.rb +31 -0
  80. data/app/mailers/panda/cms/application_mailer.rb +8 -0
  81. data/app/mailers/panda/cms/form_mailer.rb +21 -0
  82. data/app/models/action_text/rich_text_version.rb +6 -0
  83. data/app/models/panda/cms/application_record.rb +7 -0
  84. data/app/models/panda/cms/block.rb +34 -0
  85. data/app/models/panda/cms/block_content.rb +18 -0
  86. data/app/models/panda/cms/block_content_version.rb +8 -0
  87. data/app/models/panda/cms/breadcrumb.rb +12 -0
  88. data/app/models/panda/cms/current.rb +17 -0
  89. data/app/models/panda/cms/form.rb +9 -0
  90. data/app/models/panda/cms/form_submission.rb +7 -0
  91. data/app/models/panda/cms/menu.rb +52 -0
  92. data/app/models/panda/cms/menu_item.rb +58 -0
  93. data/app/models/panda/cms/page.rb +96 -0
  94. data/app/models/panda/cms/page_version.rb +8 -0
  95. data/app/models/panda/cms/post.rb +60 -0
  96. data/app/models/panda/cms/post_version.rb +8 -0
  97. data/app/models/panda/cms/redirect.rb +11 -0
  98. data/app/models/panda/cms/template.rb +124 -0
  99. data/app/models/panda/cms/template_version.rb +8 -0
  100. data/app/models/panda/cms/user.rb +31 -0
  101. data/app/models/panda/cms/version.rb +8 -0
  102. data/app/models/panda/cms/visit.rb +9 -0
  103. data/app/services/panda/cms/html_to_editor_js_converter.rb +200 -0
  104. data/app/views/active_storage/blobs/blobs/_blob.html.erb +14 -0
  105. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  106. data/app/views/layouts/panda/cms/application.html.erb +41 -0
  107. data/app/views/layouts/panda/cms/public.html.erb +3 -0
  108. data/app/views/panda/cms/admin/dashboard/show.html.erb +12 -0
  109. data/app/views/panda/cms/admin/files/index.html.erb +124 -0
  110. data/app/views/panda/cms/admin/files/show.html.erb +2 -0
  111. data/app/views/panda/cms/admin/forms/edit.html.erb +0 -0
  112. data/app/views/panda/cms/admin/forms/index.html.erb +13 -0
  113. data/app/views/panda/cms/admin/forms/new.html.erb +15 -0
  114. data/app/views/panda/cms/admin/forms/show.html.erb +35 -0
  115. data/app/views/panda/cms/admin/menus/index.html.erb +8 -0
  116. data/app/views/panda/cms/admin/pages/edit.html.erb +36 -0
  117. data/app/views/panda/cms/admin/pages/index.html.erb +22 -0
  118. data/app/views/panda/cms/admin/pages/new.html.erb +15 -0
  119. data/app/views/panda/cms/admin/pages/show.html.erb +1 -0
  120. data/app/views/panda/cms/admin/posts/_form.html.erb +29 -0
  121. data/app/views/panda/cms/admin/posts/edit.html.erb +6 -0
  122. data/app/views/panda/cms/admin/posts/index.html.erb +18 -0
  123. data/app/views/panda/cms/admin/posts/new.html.erb +6 -0
  124. data/app/views/panda/cms/admin/sessions/new.html.erb +17 -0
  125. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +68 -0
  126. data/app/views/panda/cms/admin/settings/index.html.erb +21 -0
  127. data/app/views/panda/cms/admin/settings/insta.html +4 -0
  128. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +28 -0
  129. data/app/views/panda/cms/admin/shared/_flash.html.erb +5 -0
  130. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +41 -0
  131. data/app/views/panda/cms/form_mailer/notification_email.html.erb +11 -0
  132. data/app/views/panda/cms/shared/_editor.html.erb +0 -0
  133. data/app/views/panda/cms/shared/_favicons.html.erb +9 -0
  134. data/app/views/panda/cms/shared/_footer.html.erb +2 -0
  135. data/app/views/panda/cms/shared/_header.html.erb +15 -0
  136. data/app/views/panda/cms/shared/_importmap.html.erb +33 -0
  137. data/config/importmap.rb +13 -0
  138. data/config/initializers/inflections.rb +3 -0
  139. data/config/initializers/panda/cms/form_errors.rb +38 -0
  140. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +11 -0
  141. data/config/initializers/panda/cms/paper_trail.rb +7 -0
  142. data/config/initializers/panda/cms.rb +10 -0
  143. data/config/initializers/zeitwork.rb +3 -0
  144. data/config/locales/en.yml +49 -0
  145. data/config/puma/test.rb +9 -0
  146. data/config/routes.rb +48 -0
  147. data/config/tailwind.config.js +37 -0
  148. data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
  149. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
  150. data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
  151. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
  152. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
  153. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
  154. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
  155. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
  156. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
  157. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
  158. data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +82 -0
  159. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +9 -0
  160. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
  161. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
  162. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
  163. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
  164. data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
  165. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
  166. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
  167. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
  168. data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
  169. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
  170. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
  171. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  172. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
  173. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
  174. data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
  175. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
  176. data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
  177. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
  178. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
  179. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
  180. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
  181. data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +24 -0
  182. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +11 -0
  183. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +5 -0
  184. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +35 -0
  185. data/db/migrate/20241119214549_remove_action_text_from_posts.rb +9 -0
  186. data/db/migrate/20241120000419_remove_post_tag_references.rb +19 -0
  187. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +27 -0
  188. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +5 -0
  189. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +5 -0
  190. data/db/migrate/migrate +1 -0
  191. data/db/seeds.rb +5 -0
  192. data/lib/generators/panda/cms/install_generator.rb +29 -0
  193. data/lib/panda/cms/bulk_editor.rb +171 -0
  194. data/lib/panda/cms/demo_site_generator.rb +67 -0
  195. data/lib/panda/cms/editor_js/blocks/alert.rb +34 -0
  196. data/lib/panda/cms/editor_js/blocks/base.rb +33 -0
  197. data/lib/panda/cms/editor_js/blocks/header.rb +15 -0
  198. data/lib/panda/cms/editor_js/blocks/image.rb +36 -0
  199. data/lib/panda/cms/editor_js/blocks/list.rb +32 -0
  200. data/lib/panda/cms/editor_js/blocks/paragraph.rb +15 -0
  201. data/lib/panda/cms/editor_js/blocks/quote.rb +41 -0
  202. data/lib/panda/cms/editor_js/blocks/table.rb +50 -0
  203. data/lib/panda/cms/editor_js/renderer.rb +124 -0
  204. data/lib/panda/cms/editor_js.rb +16 -0
  205. data/lib/panda/cms/editor_js_content.rb +21 -0
  206. data/lib/panda/cms/engine.rb +257 -0
  207. data/lib/panda/cms/exceptions_app.rb +26 -0
  208. data/lib/panda/cms/railtie.rb +11 -0
  209. data/lib/panda/cms/slug.rb +24 -0
  210. data/lib/panda/cms.rb +0 -0
  211. data/lib/panda-cms/version.rb +5 -0
  212. data/lib/panda-cms.rb +81 -0
  213. data/lib/tasks/panda_cms.rake +54 -0
  214. data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
  215. data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
  216. data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
  217. data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
  218. data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
  219. data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
  220. data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
  221. data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
  222. data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
  223. data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
  224. data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
  225. data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
  226. data/public/panda-cms-assets/favicons/favicon.ico +0 -0
  227. data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
  228. data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
  229. data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
  230. data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
  231. data/public/panda-cms-assets/panda-nav.png +0 -0
  232. data/public/panda-cms-assets/rich_text_editor.css +568 -0
  233. metadata +654 -0
@@ -0,0 +1,37 @@
1
+ module Panda
2
+ module CMS
3
+ class Admin::Settings::BulkEditorController < ApplicationController
4
+ before_action :set_initial_breadcrumb, only: %i[new]
5
+
6
+ def new
7
+ @json_data = BulkEditor.export
8
+ end
9
+
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
17
+
18
+ # Grab the latest content back so it's all formatted properly
19
+ @json_data = BulkEditor.export
20
+
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
28
+
29
+ private
30
+
31
+ def set_initial_breadcrumb
32
+ add_breadcrumb "Settings", admin_settings_path
33
+ add_breadcrumb "Bulk Editor", "#"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ module Panda
2
+ module CMS
3
+ class Admin::SettingsController < ApplicationController
4
+ before_action :set_initial_breadcrumb, only: %i[index show]
5
+ before_action :authenticate_admin_user!
6
+
7
+ def index
8
+ end
9
+
10
+ def show
11
+ end
12
+
13
+ private
14
+
15
+ def set_initial_breadcrumb
16
+ add_breadcrumb "Settings", admin_settings_path
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ module Panda
2
+ module CMS
3
+ class ApplicationController < ::ActionController::Base
4
+ include ApplicationHelper
5
+ include ::ApplicationHelper
6
+
7
+ protect_from_forgery with: :exception
8
+
9
+ # Add flash types for improved alert support with Tailwind
10
+ add_flash_types :success, :warning, :error, :info
11
+
12
+ before_action :set_current_request_details
13
+
14
+ helper_method :breadcrumbs
15
+ helper_method :current_user
16
+ helper_method :user_signed_in?
17
+
18
+ def breadcrumbs
19
+ @breadcrumbs ||= []
20
+ end
21
+
22
+ def add_breadcrumb(name, path = nil)
23
+ breadcrumbs << Breadcrumb.new(name, path)
24
+ end
25
+
26
+ # Set the current request details
27
+ # @return [void]
28
+ def set_current_request_details
29
+ Panda::CMS::Current.request_id = request.uuid
30
+ Panda::CMS::Current.user_agent = request.user_agent
31
+ Panda::CMS::Current.ip_address = request.ip
32
+ Panda::CMS::Current.root = request.base_url
33
+ Panda::CMS::Current.page = nil
34
+ Panda::CMS::Current.user ||= User.find_by(id: session[:user_id]) if session[:user_id]
35
+
36
+ Panda::CMS.config.url ||= Panda::CMS::Current.root
37
+ end
38
+
39
+ def authenticate_user!
40
+ redirect_to root_path, flash: {error: "Please login to view this!"} unless user_signed_in?
41
+ end
42
+
43
+ def authenticate_admin_user!
44
+ redirect_to root_path, flash: {error: "Please login to view this!"} unless user_signed_in? && current_user.admin?
45
+ end
46
+
47
+ # Required for paper_trail and seems as good as convention these days
48
+ def current_user
49
+ Panda::CMS::Current.user
50
+ end
51
+
52
+ def user_signed_in?
53
+ !!Panda::CMS::Current.user
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ module Panda
2
+ module CMS
3
+ class ErrorsController < ApplicationController
4
+ layout "error"
5
+
6
+ def show
7
+ exception = request.env["action_dispatch.exception"]
8
+ status_code = exception.try(:status_code) || ActionDispatch::ExceptionWrapper.new(request.env, exception).status_code
9
+
10
+ render view_for_code(status_code), status: status_code
11
+ end
12
+
13
+ def error_503
14
+ render view_for_code(503), status: 503
15
+ end
16
+
17
+ private
18
+
19
+ def view_for_code(code)
20
+ supported_error_codes.fetch(code) { "404" }
21
+ end
22
+
23
+ def supported_error_codes
24
+ {
25
+ 403 => "403",
26
+ 404 => "404",
27
+ 500 => "500",
28
+ 503 => "503"
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module Panda
2
+ module CMS
3
+ class FormSubmissionsController < ApplicationController
4
+ def create
5
+ vars = params.except(:authenticity_token, :controller, :action, :id)
6
+
7
+ form = Panda::CMS::Form.find(params[:id])
8
+ form_submission = Panda::CMS::FormSubmission.create(form_id: params[:id], data: vars.to_unsafe_h)
9
+ form.update(submission_count: form.submission_count + 1)
10
+
11
+ Panda::CMS::FormMailer.notification_email(form: form, form_submission: form_submission).deliver_now
12
+
13
+ if (completion_path = form&.completion_path)
14
+ redirect_to completion_path
15
+ else
16
+ # TODO: This isn't a great fallback, we should do something nice here...
17
+ # Perhaps a simple JS alert when sent?
18
+ redirect_to "/"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ module Panda
2
+ module CMS
3
+ class PagesController < ApplicationController
4
+ include ActionView::Helpers::TagHelper
5
+
6
+ before_action :check_login_required, only: [:root, :show]
7
+ after_action :record_visit, only: [:root, :show], unless: :ignore_visit?
8
+
9
+ def root
10
+ params[:path] = ""
11
+ show
12
+ end
13
+
14
+ def show
15
+ page = if @overrides&.dig(:page_path_match)
16
+ Panda::CMS::Page.find_by(path: @overrides.dig(:page_path_match))
17
+ else
18
+ Panda::CMS::Page.find_by(path: "/" + params[:path].to_s)
19
+ end
20
+
21
+ Panda::CMS::Current.page = page || Panda::CMS::Page.find_by(path: "/404")
22
+ if @overrides
23
+ Panda::CMS::Current.page.title = @overrides&.dig(:title) || page.title
24
+ end
25
+
26
+ layout = page&.template&.file_path
27
+
28
+ if page.nil? || page.status == "archived" || layout.nil?
29
+ # This works for now, but we may want to override in future (e.g. custom 404s)
30
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found and return
31
+ end
32
+
33
+ template_vars = {
34
+ page: page,
35
+ title: Panda::CMS::Current.page&.title || Panda::CMS.config.title
36
+ }
37
+
38
+ render inline: "", assigns: template_vars, status: :ok, layout: layout
39
+ end
40
+
41
+ private
42
+
43
+ def check_login_required
44
+ if Panda::CMS.config.require_login_to_view && !user_signed_in?
45
+ redirect_to panda_cms_maintenance_path and return
46
+ end
47
+ end
48
+
49
+ def ignore_visit?
50
+ # Ignore visits from bots (TODO: make this configurable)
51
+ return true if /bot/i.match?(request.user_agent)
52
+ # Ignore visits from Honeybadger
53
+ return true if request.headers.to_h.key? "Honeybadger-Token"
54
+
55
+ false
56
+ end
57
+
58
+ def record_visit
59
+ RecordVisitJob.perform_later(
60
+ url: request.url,
61
+ user_agent: request.user_agent,
62
+ referrer: request.referrer,
63
+ ip_address: request.remote_ip,
64
+ page_id: Panda::CMS::Current.page&.id,
65
+ current_user_id: current_user&.id,
66
+ params: params.to_unsafe_h.except(:controller, :action, :path),
67
+ visited_at: Time.zone.now
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,13 @@
1
+ module Panda
2
+ module CMS
3
+ class PostsController < ApplicationController
4
+ def show
5
+ @posts_index_page = Panda::CMS::Page.find_by(path: "/#{Panda::CMS.config.posts[:prefix]}")
6
+ @post = Panda::CMS::Post.find_by!(slug: "/#{params[:slug]}")
7
+ @title = @post.title
8
+
9
+ render inline: "", status: :ok, layout: "layouts/post"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Panda
2
+ module CMS
3
+ module Admin::FilesHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Panda
2
+ module CMS
3
+ module Admin::PagesHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,48 @@
1
+ module Panda
2
+ module CMS
3
+ module Admin
4
+ module PostsHelper
5
+ def editor_content_for(post, preserved_content = nil)
6
+ Rails.logger.debug "Editor content for post: #{post.inspect}"
7
+ Rails.logger.debug "Preserved content: #{preserved_content.inspect}"
8
+ Rails.logger.debug "Post content: #{post.content.inspect}"
9
+
10
+ content = if preserved_content.present?
11
+ # If preserved_content is a string (JSON), parse it
12
+ begin
13
+ preserved_content.is_a?(String) ? JSON.parse(preserved_content) : preserved_content
14
+ rescue JSON::ParserError => e
15
+ Rails.logger.error "Failed to parse preserved content: #{e.message}"
16
+ post.content
17
+ end
18
+ else
19
+ post.content
20
+ end
21
+
22
+ Rails.logger.debug "Using content: #{content.inspect}"
23
+
24
+ # Ensure we have a valid editor content structure
25
+ content = if content.blank? || !content.is_a?(Hash) || !content.key?("blocks")
26
+ Rails.logger.debug "Creating new editor content structure"
27
+ {
28
+ time: Time.now.to_i * 1000,
29
+ blocks: [],
30
+ version: "2.28.2"
31
+ }
32
+ else
33
+ # Ensure we have all required fields
34
+ Rails.logger.debug "Ensuring required fields are present"
35
+ content["time"] ||= Time.now.to_i * 1000
36
+ content["version"] ||= "2.28.2"
37
+ content["blocks"] ||= []
38
+ content
39
+ end
40
+
41
+ json = content.to_json
42
+ Rails.logger.debug "Returning JSON: #{json}"
43
+ json
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,120 @@
1
+ module Panda
2
+ module CMS
3
+ module ApplicationHelper
4
+ #
5
+ # Helper method to render a ViewComponent
6
+ # @see ViewComponent::Rendering#render
7
+ # @usage <%= component "example", title: "Hello World!" %>
8
+ #
9
+ def component(name, *, **, &)
10
+ component = name.to_s.camelize.constantize::Component
11
+ render(component.new(*, **), &)
12
+ end
13
+
14
+ # TODO: Expose this elsewhere later, or you have to include helpers
15
+ # in the calling application
16
+ def title_tag
17
+ if @breadcrumbs.present?
18
+ "#{@breadcrumbs.last&.name} &middot; #{Panda::CMS.config.title}".html_safe
19
+ elsif @title.present?
20
+ "#{@title} &middot; #{Panda::CMS.config.title}".html_safe
21
+ else
22
+ Panda::CMS.config.title
23
+ end
24
+ end
25
+
26
+ def panda_cms_editor
27
+ if Current.user&.admin
28
+ content_tag(:a, "🐼", href: edit_admin_page_url(Current.page), class: "text-3xl inline absolute right-2 top-2")
29
+ end
30
+ end
31
+
32
+ def active_link?(path, match: :starts_with)
33
+ if match == :starts_with
34
+ return request.path.starts_with?(path)
35
+ elsif match == :exact
36
+ return (request.path == path)
37
+ end
38
+
39
+ false
40
+ end
41
+
42
+ def block_link_to(name = nil, options = nil, html_options = {}, &)
43
+ html_options[:class] = "block-link"
44
+ link_to(name, options, html_options, &)
45
+ end
46
+
47
+ def panda_cms_form_with(**options, &)
48
+ options[:builder] = Panda::CMS::FormBuilder
49
+ options[:class] = ["block visible p-6 bg-mid/5 rounded-lg border-mid border", options[:class]].compact.join(" ")
50
+ form_with(**options, &)
51
+ end
52
+
53
+ def nav_class(mode)
54
+ if mode == "mobile"
55
+ "-mx-3 block rounded-lg px-3 py-2 font-semibold leading-6 text-white hover:text-white hover:underline focus:underline"
56
+ else
57
+ "font-semibold leading-6 text-white hover:text-white hover:underline focus:underline"
58
+ end
59
+ end
60
+
61
+ def selected_nav_highlight_colour_classes(request)
62
+ "bg-mid text-white relative flex transition-all py-3 px-2 mb-2 rounded-md group flex gap-x-3 rounded-md text-base leading-6 font-normal "
63
+ end
64
+
65
+ def nav_highlight_colour_classes(request)
66
+ "text-white hover:bg-mid/60 transition-all group flex gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal "
67
+ end
68
+
69
+ def level_indent(level)
70
+ case level
71
+ when 0
72
+ "ml-0"
73
+ when 1
74
+ "ml-4"
75
+ when 2
76
+ "ml-8"
77
+ when 3
78
+ "ml-12"
79
+ when 4
80
+ "ml-16"
81
+ when 5
82
+ "ml-20"
83
+ when 6
84
+ "ml-24"
85
+ when 7
86
+ "ml-28"
87
+ when 8
88
+ "ml-32"
89
+ when 9
90
+ "ml-36"
91
+ when 10
92
+ "ml-40" # We can go to 72...
93
+ else
94
+ "ml-48"
95
+ end
96
+ end
97
+
98
+ def table_indent(item_with_level_attribute)
99
+ level_indent(item_with_level_attribute.level)
100
+ end
101
+
102
+ def menu_indent(item_with_level_attribute, indent_with: "pl-")
103
+ case item_with_level_attribute.level
104
+ when 0
105
+ "#{indent_with}0"
106
+ when 1
107
+ "#{indent_with}4"
108
+ when 2
109
+ "#{indent_with}8"
110
+ when 3
111
+ "#{indent_with}12"
112
+ when 4
113
+ "#{indent_with}16"
114
+ else
115
+ "#{indent_with}20"
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,6 @@
1
+ module Panda
2
+ module CMS
3
+ module PagesHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module Panda
2
+ module CMS
3
+ module ThemeHelper
4
+ # TODO: Move these into one method?
5
+ def h1(text, icon: "", additional_styles: "")
6
+ render HeadingComponent.new(text: text, level: 1, icon: icon, additional_styles: additional_styles)
7
+ end
8
+
9
+ def h2(text, icon: "", additional_styles: "")
10
+ render HeadingComponent.new(text: text, level: 2, icon: icon, additional_styles: additional_styles)
11
+ end
12
+
13
+ def h3(text, icon: "", additional_styles: "")
14
+ render HeadingComponent.new(text: text, level: 3, icon: icon, additional_styles: additional_styles)
15
+ end
16
+ end
17
+ end
18
+ end