panda-cms 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/Rakefile +2 -0
  4. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  5. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  6. data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
  7. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  8. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  9. data/app/components/panda/cms/code_component.rb +8 -4
  10. data/app/components/panda/cms/menu_component.rb +7 -6
  11. data/app/components/panda/cms/page_menu_component.rb +15 -17
  12. data/app/components/panda/cms/rich_text_component.rb +10 -11
  13. data/app/components/panda/cms/text_component.rb +6 -7
  14. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  15. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  16. data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
  17. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  18. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
  19. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  20. data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
  21. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
  22. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  23. data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
  24. data/app/controllers/panda/cms/application_controller.rb +19 -6
  25. data/app/controllers/panda/cms/errors_controller.rb +5 -2
  26. data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
  27. data/app/controllers/panda/cms/pages_controller.rb +34 -31
  28. data/app/controllers/panda/cms/posts_controller.rb +2 -0
  29. data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
  30. data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
  31. data/app/helpers/panda/cms/application_helper.rb +3 -3
  32. data/app/helpers/panda/cms/asset_helper.rb +195 -0
  33. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  34. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  35. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  36. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  37. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  38. data/app/javascript/panda/cms/controllers/index.js +8 -24
  39. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  40. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  41. data/app/jobs/panda/cms/application_job.rb +2 -0
  42. data/app/jobs/panda/cms/record_visit_job.rb +2 -0
  43. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  44. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  45. data/app/models/panda/cms/application_record.rb +2 -0
  46. data/app/models/panda/cms/block.rb +4 -1
  47. data/app/models/panda/cms/block_content.rb +3 -1
  48. data/app/models/panda/cms/current.rb +5 -12
  49. data/app/models/panda/cms/form.rb +2 -0
  50. data/app/models/panda/cms/form_submission.rb +2 -0
  51. data/app/models/panda/cms/menu.rb +12 -9
  52. data/app/models/panda/cms/menu_item.rb +10 -6
  53. data/app/models/panda/cms/page.rb +14 -12
  54. data/app/models/panda/cms/post.rb +12 -8
  55. data/app/models/panda/cms/redirect.rb +6 -3
  56. data/app/models/panda/cms/template.rb +12 -7
  57. data/app/models/panda/cms/visit.rb +3 -1
  58. data/app/models/panda/social/instagram_post.rb +2 -0
  59. data/app/services/panda/social/instagram_feed_service.rb +3 -1
  60. data/app/views/layouts/different_page.html.erb +6 -0
  61. data/app/views/layouts/homepage.html.erb +37 -0
  62. data/app/views/layouts/page.html.erb +18 -0
  63. data/app/views/layouts/panda/cms/application.html.erb +2 -1
  64. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  65. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  66. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  67. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  68. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  69. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  70. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  71. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  72. data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
  73. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  74. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  75. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  76. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  77. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  78. data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
  79. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  80. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  81. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  82. data/app/views/panda/cms/shared/_header.html.erb +10 -2
  83. data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
  84. data/app/views/shared/_footer.html.erb +3 -0
  85. data/app/views/shared/_header.html.erb +11 -0
  86. data/config/importmap.rb +2 -0
  87. data/config/initializers/inflections.rb +2 -0
  88. data/config/initializers/panda/cms/form_errors.rb +20 -21
  89. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  90. data/config/initializers/panda/cms.rb +8 -3
  91. data/config/initializers/zeitwork.rb +2 -0
  92. data/config/puma/test.rb +3 -1
  93. data/config/routes.rb +11 -19
  94. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  95. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  96. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  97. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  98. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  99. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  100. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  101. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  102. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  103. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  104. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  105. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  106. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  107. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  108. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  109. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  110. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  111. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  112. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  113. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  114. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  115. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  116. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  117. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  118. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  119. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  120. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  121. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  122. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  123. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  124. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  125. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  126. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  127. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  128. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  129. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  130. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  131. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  132. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  133. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
  134. data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
  135. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
  136. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  137. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  138. data/db/seeds.rb +2 -0
  139. data/lib/generators/panda/cms/install_generator.rb +2 -0
  140. data/lib/panda/cms/asset_loader.rb +390 -0
  141. data/lib/panda/cms/bulk_editor.rb +7 -3
  142. data/lib/panda/cms/demo_site_generator.rb +2 -0
  143. data/lib/panda/cms/engine.rb +57 -116
  144. data/lib/panda/cms/exceptions_app.rb +2 -0
  145. data/lib/panda/cms/railtie.rb +2 -0
  146. data/lib/panda/cms/slug.rb +3 -1
  147. data/lib/panda-cms/version.rb +3 -1
  148. data/lib/panda-cms.rb +54 -42
  149. data/lib/tasks/assets.rake +587 -0
  150. data/lib/tasks/panda/cms/install.rake +2 -0
  151. data/lib/tasks/panda/cms/migrations.rake +13 -0
  152. data/lib/tasks/panda/social/instagram.rake +2 -0
  153. data/lib/tasks/panda_cms.rake +3 -30
  154. data/public/panda-cms-assets/manifest.json +20 -0
  155. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  156. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  157. metadata +186 -49
  158. data/app/builders/panda/cms/form_builder.rb +0 -217
  159. data/app/components/panda/cms/admin/button_component.rb +0 -70
  160. data/app/components/panda/cms/admin/container_component.rb +0 -13
  161. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  162. data/app/components/panda/cms/admin/heading_component.rb +0 -45
  163. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  164. data/app/components/panda/cms/admin/table_component.rb +0 -46
  165. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  166. data/app/constraints/panda/cms/admin_constraint.rb +0 -18
  167. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
  168. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
  169. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
  170. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  171. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  172. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  173. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  174. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  175. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  176. data/app/models/panda/cms/breadcrumb.rb +0 -12
  177. data/app/models/panda/cms/user.rb +0 -31
  178. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
  179. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  180. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  181. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
  182. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
  183. data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
  184. data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
  185. data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
  186. data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
  187. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
  188. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
  189. data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
  190. data/lib/panda/cms/editor_js/renderer.rb +0 -124
  191. data/lib/panda/cms/editor_js.rb +0 -16
  192. data/lib/panda/cms/editor_js_content.rb +0 -55
@@ -1,50 +0,0 @@
1
- module Panda
2
- module CMS
3
- module EditorJs
4
- module Blocks
5
- class Table < Base
6
- def render
7
- content = data["content"]
8
- with_headings = data["withHeadings"]
9
-
10
- html_safe(<<~HTML)
11
- <div class="overflow-x-auto">
12
- <table class="min-w-full">
13
- #{render_rows(content, with_headings)}
14
- </table>
15
- </div>
16
- HTML
17
- end
18
-
19
- private
20
-
21
- def render_rows(content, with_headings)
22
- rows = []
23
- index = 0
24
-
25
- while index < content.length
26
- rows << if index == 0 && with_headings
27
- render_header_row(content[index])
28
- else
29
- render_data_row(content[index])
30
- end
31
- index += 1
32
- end
33
-
34
- rows.join("\n")
35
- end
36
-
37
- def render_header_row(row)
38
- cells = row.map { |cell| "<th>#{sanitize(cell)}</th>" }
39
- "<tr>#{cells.join}</tr>"
40
- end
41
-
42
- def render_data_row(row)
43
- cells = row.map { |cell| "<td>#{sanitize(cell)}</td>" }
44
- "<tr>#{cells.join}</tr>"
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,124 +0,0 @@
1
- require "sanitize"
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- class Renderer
7
- attr_reader :content, :options, :custom_renderers, :cache_store
8
-
9
- def initialize(content, options = {})
10
- @content = content
11
- @options = options
12
- @custom_renderers = options.delete(:custom_renderers) || {}
13
- @cache_store = options.delete(:cache_store) || Rails.cache
14
- @validate_html = options.delete(:validate_html) || false
15
- end
16
-
17
- def render
18
- return "" if content.nil? || content == {}
19
- return content.to_s unless content.is_a?(Hash) && content["blocks"].is_a?(Array)
20
-
21
- rendered = content["blocks"].map do |block|
22
- render_block(block)
23
- end.join("\n")
24
-
25
- rendered = @validate_html ? validate_html(rendered) : rendered
26
- rendered.presence || ""
27
- end
28
-
29
- def section(blocks)
30
- return "" if blocks.nil? || blocks.empty?
31
-
32
- content = {"blocks" => blocks}
33
- rendered = self.class.new(content, options).render
34
-
35
- "<section class=\"content-section\">#{rendered}</section>"
36
- end
37
-
38
- def article(blocks, title: nil)
39
- return "" if blocks.nil? || blocks.empty?
40
-
41
- content = {"blocks" => blocks}
42
- rendered = self.class.new(content, options).render
43
-
44
- [
45
- "<article>",
46
- (title ? "<h1>#{title}</h1>" : ""),
47
- rendered,
48
- "</article>"
49
- ].join("\n")
50
- end
51
-
52
- private
53
-
54
- def validate_html(html)
55
- return "" if html.blank?
56
-
57
- begin
58
- # For quote blocks, only allow specific content
59
- if html.include?('<figure class="text-left">')
60
- # Only allow the exact valid content we expect
61
- valid_content = '<figure class="text-left"><blockquote><p>Valid HTML</p></blockquote><figcaption>Valid caption</figcaption></figure>'
62
- return html if html.strip == valid_content.strip
63
- return ""
64
- end
65
-
66
- # For other HTML, use sanitize
67
- config = Sanitize::Config::RELAXED.dup
68
- config[:elements] += %w[figure figcaption blockquote pre code mention math]
69
- config[:attributes].merge!({
70
- "figure" => ["class"],
71
- "blockquote" => ["class"],
72
- "p" => ["class"],
73
- "figcaption" => ["class"]
74
- })
75
-
76
- sanitized = Sanitize.fragment(html, config)
77
- (sanitized == html) ? html : ""
78
- rescue => e
79
- Rails.logger.error("HTML validation error: #{e.message}")
80
- ""
81
- end
82
- end
83
-
84
- def render_block_with_cache(block)
85
- cache_key = "editor_js_block/#{block["type"]}/#{Digest::MD5.hexdigest(block["data"].to_json)}"
86
-
87
- cache_store.fetch(cache_key) do
88
- renderer = renderer_for(block)
89
- renderer.render
90
- end
91
- end
92
-
93
- def renderer_for(block)
94
- if custom_renderers[block["type"]]
95
- custom_renderers[block["type"]].new(block["data"], options)
96
- else
97
- default_renderer_for(block)
98
- end
99
- end
100
-
101
- def default_renderer_for(block)
102
- renderer_class = "Panda::CMS::EditorJs::Blocks::#{block["type"].classify}".constantize
103
- renderer_class.new(block["data"], options)
104
- rescue NameError
105
- Panda::CMS::EditorJs::Blocks::Base.new(block["data"], options)
106
- end
107
-
108
- def remove_empty_paragraphs(blocks)
109
- blocks.reject do |block|
110
- block["type"] == "paragraph" && block["data"]["text"].blank?
111
- end
112
- end
113
-
114
- def empty_paragraph?(block)
115
- block["type"] == "paragraph" && block["data"]["text"].blank?
116
- end
117
-
118
- def render_block(block)
119
- render_block_with_cache(block)
120
- end
121
- end
122
- end
123
- end
124
- end
@@ -1,16 +0,0 @@
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
@@ -1,55 +0,0 @@
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 content=(value)
14
- if value.is_a?(Hash)
15
- super(value.to_json)
16
- else
17
- super
18
- end
19
- end
20
-
21
- def content
22
- value = super
23
- if value.is_a?(String)
24
- begin
25
- JSON.parse(value)
26
- rescue JSON::ParserError
27
- value
28
- end
29
- else
30
- value
31
- end
32
- end
33
-
34
- def generate_cached_content
35
- if content.is_a?(String)
36
- begin
37
- parsed_content = JSON.parse(content)
38
- self.cached_content = if parsed_content.is_a?(Hash) && parsed_content["blocks"].present?
39
- Panda::CMS::EditorJs::Renderer.new(parsed_content).render
40
- else
41
- content
42
- end
43
- rescue JSON::ParserError
44
- # If it's not JSON, treat it as plain text
45
- self.cached_content = content
46
- end
47
- elsif content.is_a?(Hash) && content["blocks"].present?
48
- # Process EditorJS content
49
- self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
50
- else
51
- # For any other case, store as is
52
- self.cached_content = content.to_s
53
- end
54
- end
55
- end