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,16 @@
1
+ require_relative "editor_js/blocks/base"
2
+ require_relative "editor_js/blocks/alert"
3
+ require_relative "editor_js/blocks/header"
4
+ require_relative "editor_js/blocks/image"
5
+ require_relative "editor_js/blocks/list"
6
+ require_relative "editor_js/blocks/paragraph"
7
+ require_relative "editor_js/blocks/quote"
8
+ require_relative "editor_js/blocks/table"
9
+ require_relative "editor_js/renderer"
10
+
11
+ module Panda
12
+ module CMS
13
+ module EditorJs
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ module Panda::CMS::EditorJsContent
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Validations
8
+ include ActiveModel::Callbacks
9
+
10
+ before_save :generate_cached_content
11
+ end
12
+
13
+ def generate_cached_content
14
+ if content.is_a?(String)
15
+ self.cached_content = content
16
+ elsif content.is_a?(Hash) && content["blocks"].present?
17
+ # Process EditorJS content
18
+ self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,257 @@
1
+ require "importmap-rails"
2
+ require "turbo-rails"
3
+ require "stimulus-rails"
4
+ require "view_component"
5
+
6
+ require "panda/cms/railtie"
7
+
8
+ module Panda
9
+ module CMS
10
+ class Engine < ::Rails::Engine
11
+ isolate_namespace Panda::CMS
12
+ engine_name "panda_cms"
13
+
14
+ # Add services directory to autoload paths
15
+ config.autoload_paths += %W[
16
+ #{root}/app/services
17
+ ]
18
+
19
+ # Basic session setup only
20
+ initializer "panda.cms.session", before: :load_config_initializers do |app|
21
+ if app.config.middleware.frozen?
22
+ app.config.middleware = app.config.middleware.dup
23
+ end
24
+
25
+ app.config.session_store :cookie_store, key: "_panda_cms_session"
26
+ app.config.middleware.use ActionDispatch::Cookies
27
+ app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
28
+ end
29
+
30
+ config.to_prepare do
31
+ ApplicationController.helper(::ApplicationHelper)
32
+ end
33
+
34
+ # Set our generators
35
+ config.generators do |g|
36
+ g.orm :active_record, primary_key_type: :uuid
37
+ g.test_framework :rspec, fixture: true
38
+ g.fixture_replacement :factory_bot, dir: "spec/factories"
39
+ g.view_specs false
40
+ g.templates.unshift File.expand_path("../../templates", __FILE__)
41
+ end
42
+
43
+ # Make files in public available to the main app (e.g. /panda-cms-assets/favicon.ico)
44
+ config.app_middleware.use(
45
+ Rack::Static,
46
+ urls: ["/panda-cms-assets"],
47
+ root: Panda::CMS::Engine.root.join("public")
48
+ )
49
+
50
+ # Custom error handling
51
+ # config.exceptions_app = Panda::CMS::ExceptionsApp.new(exceptions_app: routes)
52
+
53
+ initializer "panda.cms.assets" do |app|
54
+ if Rails.configuration.respond_to?(:assets)
55
+ # Add JavaScript paths
56
+ app.config.assets.paths << root.join("app/javascript")
57
+ app.config.assets.paths << root.join("app/javascript/panda")
58
+ app.config.assets.paths << root.join("app/javascript/panda/cms")
59
+ app.config.assets.paths << root.join("app/javascript/panda/cms/controllers")
60
+
61
+ # Make sure these files are precompiled
62
+ app.config.assets.precompile += %w[
63
+ panda_cms_manifest.js
64
+ panda/cms/controllers/**/*.js
65
+ panda/cms/application_panda_cms.js
66
+ ]
67
+ end
68
+ end
69
+
70
+ # Add importmap paths from the engine
71
+ initializer "panda.cms.importmap", before: "importmap" do |app|
72
+ if app.config.respond_to?(:importmap)
73
+ # Create a new array if frozen
74
+ if app.config.importmap.paths.frozen?
75
+ app.config.importmap.paths = app.config.importmap.paths.dup
76
+ end
77
+
78
+ # Add our paths
79
+ app.config.importmap.paths << root.join("config/importmap.rb")
80
+
81
+ # Handle cache sweepers similarly
82
+ if app.config.importmap.cache_sweepers.frozen?
83
+ app.config.importmap.cache_sweepers = app.config.importmap.cache_sweepers.dup
84
+ end
85
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
86
+ end
87
+ end
88
+
89
+ config.after_initialize do |app|
90
+ # Append routes to the routes file
91
+ app.routes.append do
92
+ mount Panda::CMS::Engine => "/", :as => "panda_cms"
93
+ post "/_forms/:id", to: "panda/cms/form_submissions#create", as: :panda_cms_form_submit
94
+ get "/_maintenance", to: "panda/cms/errors#error_503", as: :panda_cms_maintenance
95
+ get "/*path", to: "panda/cms/pages#show", as: :panda_cms_page
96
+ root to: "panda/cms/pages#root"
97
+ end
98
+ end
99
+
100
+ initializer "#{engine_name}.backtrace_cleaner" do |app|
101
+ engine_root_regex = Regexp.escape(root.to_s + File::SEPARATOR)
102
+
103
+ # Clean those ERB lines, we don't need the internal autogenerated
104
+ # ERB method, what we do need (line number in ERB file) is already there
105
+ Rails.backtrace_cleaner.add_filter do |line|
106
+ line.sub(/(\.erb:\d+):in `__.*$/, "\\1")
107
+ end
108
+
109
+ # Remove our own engine's path prefix, even if it's
110
+ # being used from a local path rather than the gem directory.
111
+ Rails.backtrace_cleaner.add_filter do |line|
112
+ line.sub(/^#{engine_root_regex}/, "#{engine_name} ")
113
+ end
114
+
115
+ # Keep Umlaut's own stacktrace in the backtrace -- we have to remove Rails
116
+ # silencers and re-add them how we want.
117
+ Rails.backtrace_cleaner.remove_silencers!
118
+
119
+ # Silence what Rails silenced, UNLESS it looks like
120
+ # it's from Umlaut engine
121
+ Rails.backtrace_cleaner.add_silencer do |line|
122
+ (line !~ Rails::BacktraceCleaner::APP_DIRS_PATTERN) &&
123
+ (line !~ /^#{engine_root_regex}/) &&
124
+ (line !~ /^#{engine_name} /)
125
+ end
126
+ end
127
+
128
+ # Set up ViewComponent and Lookbook
129
+ # config.view_component.component_parent_class = "Panda::CMS::BaseComponent"
130
+ # config.view_component.view_component_path = Panda::CMS::Engine.root.join("lib/components").to_s
131
+ # config.eager_load_paths << Panda::CMS::Engine.root.join("lib/components").to_s
132
+ # config.view_component.generate.sidecar = true
133
+ # config.view_component.generate.preview = truexw
134
+ # config.view_component.preview_paths ||= []
135
+ # config.view_component.preview_paths << Panda::CMS::Engine.root.join("lib/component_previews").to_s
136
+ # config.view_component.generate.preview_path = "lib/component_previews"
137
+
138
+ # Set up authentication
139
+ initializer "panda_cms.omniauth", before: "omniauth" do |app|
140
+ app.config.session_store :cookie_store, key: "_panda_cms_session"
141
+ app.config.middleware.use ActionDispatch::Cookies
142
+ app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
143
+
144
+ OmniAuth.config.logger = Rails.logger
145
+
146
+ # TODO: Move this to somewhere more sensible?
147
+ # Define the mapping of our provider "names" to the OmniAuth strategies and configuration
148
+ auth_path = "#{Panda::CMS.route_namespace}/auth"
149
+ callback_path = "/callback"
150
+ available_providers = {
151
+ microsoft: {
152
+ strategy: :microsoft_graph,
153
+ defaults: {
154
+ name: "microsoft",
155
+ # Setup at the following URL:
156
+ # https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
157
+ client_id: Rails.application.credentials.dig(:microsoft, :client_id),
158
+ client_secret: Rails.application.credentials.dig(:microsoft, :client_secret),
159
+ # Don't change this or the sky will fall on your head
160
+ # https://github.com/synth/omniauth-microsoft_graph/tree/main?tab=readme-ov-file#domain-verification
161
+ skip_domain_verification: false,
162
+ # If your application is single-tenanted, replace "common" with your tenant (directory) ID
163
+ # from https://portal.azure.com/#settings/directory, otherwise you'll likely want to leave
164
+ # these settings unchanged
165
+ client_options: {
166
+ site: "https://login.microsoftonline.com/",
167
+ token_url: "common/oauth2/v2.0/token",
168
+ authorize_url: "common/oauth2/v2.0/authorize"
169
+ },
170
+ # If you assign specific users or groups, you will likely want to set this to
171
+ # true to enable auto-provisioning
172
+ create_account_on_first_login: false,
173
+ create_admin_account_on_first_login: false,
174
+ # Don't hide this provider from the login page
175
+ hidden: false
176
+ }
177
+ },
178
+ google: {
179
+ strategy: :google_oauth2,
180
+ defaults: {
181
+ name: "google",
182
+ # Setup at the following URL: https://console.developers.google.com/
183
+ client_id: Rails.application.credentials.dig(:google, :client_id),
184
+ client_secret: Rails.application.credentials.dig(:google, :client_secret),
185
+ # If you assign specific users or groups, you will likely want to set this to
186
+ # true to enable auto-provisioning
187
+ create_account_on_first_login: false,
188
+ create_admin_account_on_first_login: false,
189
+ # Options we need
190
+ scope: "email, profile",
191
+ image_aspect_ratio: "square",
192
+ image_size: 150,
193
+ # Worth setting select_account as default, as many people have multiple Google accounts now:
194
+ prompt: "select_account",
195
+ # You should probably also set the 'hd' option, huh?,
196
+ # Don't hide this provider from the login page
197
+ hidden: false
198
+ }
199
+ },
200
+ github: {
201
+ strategy: :github,
202
+ defaults: {
203
+ name: "github",
204
+ # Setup at the following URL: https://github.com/settings/applications/new
205
+ # with a callback of
206
+ # In the meantime, as long as you're set to /admin as your login path, and on
207
+ # http://localhost:3000, you can use these for a first login :)
208
+ client_id: Rails.application.credentials.dig(:github, :client_id),
209
+ client_secret: Rails.application.credentials.dig(:github, :client_secret),
210
+ scope: "user:email,read:user",
211
+ create_account_on_first_login: false,
212
+ create_admin_account_on_first_login: false,
213
+ # Don't hide this provider from the login page
214
+ hidden: false
215
+ }
216
+ }
217
+ }
218
+
219
+ available_providers.each do |provider, options|
220
+ if Panda::CMS.config.authentication.dig(provider, :enabled)
221
+ auth_path = auth_path.starts_with?("/") ? auth_path : "/#{auth_path}"
222
+ options[:defaults][:path_prefix] = auth_path
223
+
224
+ options[:defaults][:redirect_uri] = if Rails.env.test?
225
+ "#{Capybara.app_host}#{auth_path}/#{provider}#{callback_path}"
226
+ else
227
+ "#{Panda::CMS.config.url}#{auth_path}/#{provider}#{callback_path}"
228
+ end
229
+
230
+ provider_config = options[:defaults].merge(Panda::CMS.config.authentication[provider])
231
+
232
+ app.config.middleware.use OmniAuth::Builder do
233
+ provider options[:strategy], provider_config
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ config.before_initialize do |app|
240
+ # Default configuration
241
+ Panda::CMS.configure do |config|
242
+ # Array of additional EditorJS tools to load
243
+ # Example: [{ url: "https://cdn.jsdelivr.net/npm/@editorjs/image@latest" }]
244
+ config.editor_js_tools ||= []
245
+
246
+ # Hash of EditorJS tool configurations
247
+ # Example: { image: { class: 'ImageTool', config: { ... } } }
248
+ config.editor_js_tool_config ||= {}
249
+ end
250
+ end
251
+ end
252
+
253
+ class MissingBlockError < StandardError; end
254
+
255
+ class BlockError < StandardError; end
256
+ end
257
+ end
@@ -0,0 +1,26 @@
1
+ # https://guides.rubyonrails.org/configuring.html#config-exceptions-app
2
+ module Panda
3
+ module CMS
4
+ class ExceptionsApp
5
+ def initialize(exceptions_app:)
6
+ @exceptions_app = exceptions_app
7
+ end
8
+
9
+ def call(env)
10
+ request = ActionDispatch::Request.new(env)
11
+
12
+ fallback_to_html_format_if_invalid_mime_type(request)
13
+
14
+ @exceptions_app.call(env)
15
+ end
16
+
17
+ private
18
+
19
+ def fallback_to_html_format_if_invalid_mime_type(request)
20
+ request.formats
21
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
22
+ request.set_header "CONTENT_TYPE", "text/html"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Panda
2
+ module CMS
3
+ class Railtie < Rails::Railtie
4
+ config.before_initialize do
5
+ Rails.autoloaders.main.inflector.inflect(
6
+ "cms" => "CMS"
7
+ )
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Panda
2
+ module CMS
3
+ class Slug
4
+ #
5
+ # Generates a slug from a provided string
6
+ #
7
+ # @param string [String] The provided string to turn into a slug
8
+ # @return string Generated slug
9
+ # @see slug_controller.js should also implement this logic
10
+ def self.generate(string)
11
+ # Trim whitespace and downcase the string
12
+ string = string.to_s.strip.downcase
13
+ # Replace & with "and"
14
+ string = string.gsub("&", "and")
15
+ # Remove special characters
16
+ string = string.gsub(/[\!\@\£\$\%\^\&\*\(\)\+\=\{\}\[\]\:\;\"\'\|\\\`\<\>\?\,\.\/]+/, "")
17
+ # Replace any whitespace character with -
18
+ string = string.gsub(/[^\w\s-]/, "-")
19
+ # Replace multiple occurences of _ and - with -
20
+ string.gsub(/[\s_-]+/, "-")
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/panda/cms.rb ADDED
File without changes
@@ -0,0 +1,5 @@
1
+ module Panda
2
+ module CMS
3
+ VERSION = "0.7.0"
4
+ end
5
+ end
data/lib/panda-cms.rb ADDED
@@ -0,0 +1,81 @@
1
+ require "dry-configurable"
2
+ require "importmap-rails"
3
+ require "lookbook"
4
+ require "omniauth"
5
+ require "omniauth/rails_csrf_protection"
6
+ require "omniauth/strategies/microsoft_graph"
7
+ require "omniauth/strategies/google_oauth2"
8
+ require "omniauth/strategies/github"
9
+ require "paper_trail"
10
+ require "view_component"
11
+ require "zeitwerk"
12
+
13
+ module Panda
14
+ module CMS
15
+ extend Dry::Configurable
16
+
17
+ setting :title, default: "Demo Site"
18
+ setting :admin_path, default: "/admin"
19
+ setting :require_login_to_view, default: false
20
+ setting :authentication, default: {}
21
+ setting :posts, default: {enabled: true, prefix: "blog"}
22
+ setting :route_namespace, default: "/admin"
23
+ setting :url
24
+ setting :editor_js_tools, default: []
25
+ setting :editor_js_tool_config, default: {}
26
+
27
+ def self.root_path
28
+ config.admin_path
29
+ end
30
+
31
+ class << self
32
+ attr_accessor :loader
33
+
34
+ def route_namespace
35
+ config.admin_path
36
+ end
37
+
38
+ def configure
39
+ yield config if block_given?
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Set up autoloading for the gem's internals
46
+ Panda::CMS.loader = Zeitwerk::Loader.new
47
+ Panda::CMS.loader.tag = "panda-cms"
48
+
49
+ # Ignore the panda-cms directory
50
+ Panda::CMS.loader.ignore("#{__dir__}/panda-cms")
51
+
52
+ # Only autoload the panda/cms directory
53
+ Panda::CMS.loader.push_dir(File.expand_path("panda/cms", __dir__), namespace: Panda::CMS)
54
+
55
+ # Ignore both lib and lib/panda directories to prevent Rails autoloader conflicts
56
+ Panda::CMS.loader.ignore(__dir__.to_s)
57
+ Panda::CMS.loader.ignore(File.expand_path("panda", __dir__))
58
+
59
+ # Configure Zeitwerk inflections
60
+ Panda::CMS.loader.inflector.inflect(
61
+ "cms" => "CMS"
62
+ )
63
+
64
+ # Manually require files from panda-cms directory
65
+ require_relative "panda-cms/version"
66
+ require_relative "panda/cms/exceptions_app"
67
+ require_relative "panda/cms/engine"
68
+ require_relative "panda/cms/demo_site_generator"
69
+ require_relative "panda/cms/editor_js"
70
+ require_relative "panda/cms/editor_js_content"
71
+ require_relative "panda/cms/editor_js/renderer"
72
+ require_relative "panda/cms/editor_js/blocks/alert"
73
+ require_relative "panda/cms/editor_js/blocks/header"
74
+ require_relative "panda/cms/editor_js/blocks/image"
75
+ require_relative "panda/cms/editor_js/blocks/list"
76
+ require_relative "panda/cms/editor_js/blocks/paragraph"
77
+ require_relative "panda/cms/editor_js/blocks/quote"
78
+ require_relative "panda/cms/editor_js/blocks/table"
79
+ require_relative "panda/cms/slug"
80
+
81
+ Panda::CMS.loader.setup
@@ -0,0 +1,54 @@
1
+ require "tailwindcss-rails"
2
+ require "tailwindcss/ruby"
3
+ require "shellwords"
4
+
5
+ require "panda/cms/engine"
6
+
7
+ ENV["TAILWIND_PATH"] ||= Tailwindcss::Engine.root.join("exe/tailwindcss").to_s
8
+
9
+ namespace :panda do
10
+ namespace :cms do
11
+ desc "Watch admin assets for Panda CMS"
12
+ # We only care about this in development
13
+ task :watch_admin do
14
+ run_tailwind(
15
+ root_path: Panda::CMS::Engine.root,
16
+ input_path: "app/assets/stylesheets/panda/cms/application.tailwind.css",
17
+ output_path: "app/assets/builds/panda.cms.css",
18
+ watch: true,
19
+ minify: false
20
+ )
21
+ end
22
+
23
+ desc "Generate missing blocks from template files"
24
+ task generate_missing_blocks: [:environment] do
25
+ Panda::CMS::Template.generate_missing_blocks
26
+ end
27
+
28
+ namespace :export do
29
+ desc "Generate a .json export and output to stdout"
30
+ task json: [:environment] do
31
+ puts Panda::CMS::BulkEditor.export
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ task default: [:spec, :panda, :cms]
38
+
39
+ def run_tailwind(root_path:, input_path: nil, output_path: nil, config_path: nil, watch: false, minify: true)
40
+ config_path ||= root_path.join("config/tailwind.config.js")
41
+
42
+ command = [
43
+ Tailwindcss::Ruby.executable,
44
+ "-i #{root_path.join(input_path)}",
45
+ "-o #{root_path.join(output_path)}",
46
+ "-c #{root_path.join(config_path)}"
47
+ ]
48
+
49
+ command << "-w" if watch
50
+ command << "-m" if minify
51
+
52
+ command = command.join(" ")
53
+ system command
54
+ end
@@ -0,0 +1,43 @@
1
+ <%%= form_with(model: <%= model_resource_name %>, class: "contents") do |form| %>
2
+ <%% if <%= singular_table_name %>.errors.any? %>
3
+ <div id="error_explanation" class="py-2 px-3 mt-3 font-medium text-red-500 bg-red-50 rounded-lg">
4
+ <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
5
+
6
+ <ul>
7
+ <%% <%= singular_table_name %>.errors.each do |error| %>
8
+ <li><%%= error.full_message %></li>
9
+ <%% end %>
10
+ </ul>
11
+ </div>
12
+ <%% end %>
13
+
14
+ <% attributes.each do |attribute| -%>
15
+ <div class="my-5">
16
+ <% if attribute.password_digest? -%>
17
+ <%%= form.label :password %>
18
+ <%%= form.password_field :password, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
19
+ </div>
20
+
21
+ <div class="my-5">
22
+ <%%= form.label :password_confirmation %>
23
+ <%%= form.password_field :password_confirmation, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
24
+ <% elsif attribute.attachments? -%>
25
+ <%%= form.label :<%= attribute.column_name %> %>
26
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
27
+ <% else -%>
28
+ <%%= form.label :<%= attribute.column_name %> %>
29
+ <% if attribute.field_type == :text_area -%>
30
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, rows: 4, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
31
+ <% elsif attribute.field_type == :check_box -%>
32
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: "block mt-2 h-5 w-5" %>
33
+ <% else -%>
34
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
35
+ <% end -%>
36
+ <% end -%>
37
+ </div>
38
+
39
+ <% end -%>
40
+ <div class="inline">
41
+ <%%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
42
+ </div>
43
+ <%% end %>
@@ -0,0 +1,8 @@
1
+ <div class="mx-auto w-full md:w-2/3">
2
+ <h1 class="text-4xl font-bold">Editing <%= human_name.downcase %></h1>
3
+
4
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
5
+
6
+ <%%= link_to "Show this <%= human_name.downcase %>", @<%= singular_table_name %>, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
7
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
8
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="w-full">
2
+ <%% if notice.present? %>
3
+ <p class="inline-block py-2 px-3 mb-5 font-medium text-green-500 bg-green-50 rounded-lg" id="notice"><%%= notice %></p>
4
+ <%% end %>
5
+
6
+ <div class="flex justify-between items-center">
7
+ <h1 class="text-4xl font-bold"><%= human_name.pluralize %></h1>
8
+ <%%= link_to "New <%= human_name.downcase %>", new_<%= singular_route_name %>_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
9
+ </div>
10
+
11
+ <div id="<%= plural_table_name %>" class="min-w-full">
12
+ <%%= render @<%= plural_table_name %> %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,7 @@
1
+ <div class="mx-auto w-full md:w-2/3">
2
+ <h1 class="text-4xl font-bold">New <%= human_name.downcase %></h1>
3
+
4
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
5
+
6
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
7
+ </div>
@@ -0,0 +1,22 @@
1
+ <div id="<%%= dom_id <%= singular_name %> %>">
2
+ <% attributes.reject(&:password_digest?).each do |attribute| -%>
3
+ <p class="my-5">
4
+ <strong class="block mb-1 font-medium"><%= attribute.human_name %>:</strong>
5
+ <% if attribute.attachment? -%>
6
+ <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %> if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
7
+ <% elsif attribute.attachments? -%>
8
+ <%% <%= singular_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
9
+ <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
10
+ <%% end %>
11
+ <% else -%>
12
+ <%%= <%= singular_name %>.<%= attribute.column_name %> %>
13
+ <% end -%>
14
+ </p>
15
+
16
+ <% end -%>
17
+ <%% if action_name != "show" %>
18
+ <%%= link_to "Show this <%= human_name.downcase %>", <%= singular_name %>, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
19
+ <%%= link_to "Edit this <%= human_name.downcase %>", edit_<%= singular_name %>_path(<%= singular_name %>), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
20
+ <hr class="mt-6">
21
+ <%% end %>
22
+ </div>
@@ -0,0 +1,15 @@
1
+ <div class="flex mx-auto w-full md:w-2/3">
2
+ <div class="mx-auto">
3
+ <%% if notice.present? %>
4
+ <p class="inline-block py-2 px-3 mb-5 font-medium text-green-500 bg-green-50 rounded-lg" id="notice"><%%= notice %></p>
5
+ <%% end %>
6
+
7
+ <%%= render @<%= singular_table_name %> %>
8
+
9
+ <%%= link_to "Edit this <%= singular_table_name %>", edit_<%= singular_table_name %>_path(@<%= singular_table_name %>), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
10
+ <div class="inline-block ml-2">
11
+ <%%= button_to "Destroy this <%= singular_table_name %>", <%= singular_table_name %>_path(@<%= singular_table_name %>), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
12
+ </div>
13
+ <%%= link_to "Back to <%= plural_table_name %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
14
+ </div>
15
+ </div>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig>
3
+ <msapplication>
4
+ <tile>
5
+ <square150x150logo src="/panda-cms-assets/mstile-150x150.png"/>
6
+ <TileColor>#b91d47</TileColor>
7
+ </tile>
8
+ </msapplication>
9
+ </browserconfig>