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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormSubmissionsController < ApplicationController
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class PagesController < ApplicationController
4
6
  include ActionView::Helpers::TagHelper
5
7
 
6
- before_action :check_login_required, only: [:root, :show]
7
- before_action :handle_redirects, only: [:root, :show]
8
- after_action :record_visit, only: [:root, :show], unless: :ignore_visit?
8
+ before_action :check_login_required, only: %i[root show]
9
+ before_action :handle_redirects, only: %i[root show]
10
+ after_action :record_visit, only: %i[root show], unless: :ignore_visit?
9
11
 
10
12
  def root
11
13
  params[:path] = ""
@@ -14,15 +16,13 @@ module Panda
14
16
 
15
17
  def show
16
18
  page = if @overrides&.dig(:page_path_match)
17
- Panda::CMS::Page.find_by(path: @overrides.dig(:page_path_match))
19
+ Panda::CMS::Page.find_by(path: @overrides[:page_path_match])
18
20
  else
19
- Panda::CMS::Page.find_by(path: "/" + params[:path].to_s)
21
+ Panda::CMS::Page.find_by(path: "/#{params[:path]}")
20
22
  end
21
23
 
22
24
  Panda::CMS::Current.page = page || Panda::CMS::Page.find_by(path: "/404")
23
- if @overrides
24
- Panda::CMS::Current.page.title = @overrides&.dig(:title) || page.title
25
- end
25
+ Panda::CMS::Current.page.title = @overrides&.dig(:title) || page.title if @overrides
26
26
 
27
27
  layout = page&.template&.file_path
28
28
 
@@ -42,36 +42,39 @@ module Panda
42
42
  private
43
43
 
44
44
  def handle_redirects
45
- current_path = "/" + params[:path].to_s
45
+ current_path = "/#{params[:path]}"
46
46
  redirect = Panda::CMS::Redirect.find_by(origin_path: current_path)
47
47
 
48
- if redirect
49
- redirect.increment!(:visits)
48
+ return unless redirect
50
49
 
51
- # Check if the destination is also a redirect
52
- next_redirect = Panda::CMS::Redirect.find_by(origin_path: redirect.destination_path)
53
- if next_redirect
54
- next_redirect.increment!(:visits)
55
- redirect_to next_redirect.destination_path, status: redirect.status_code and return
56
- end
50
+ redirect.increment!(:visits)
57
51
 
58
- redirect_to redirect.destination_path, status: redirect.status_code and return
52
+ # Check if the destination is also a redirect
53
+ next_redirect = Panda::CMS::Redirect.find_by(origin_path: redirect.destination_path)
54
+ if next_redirect
55
+ next_redirect.increment!(:visits)
56
+ redirect_to next_redirect.destination_path, status: redirect.status_code and return
59
57
  end
58
+
59
+ redirect_to redirect.destination_path, status: redirect.status_code and return
60
60
  end
61
61
 
62
62
  def check_login_required
63
- if Panda::CMS.config.require_login_to_view && !user_signed_in?
64
- redirect_to panda_cms_maintenance_path and return
65
- end
63
+ return unless Panda::CMS.config.require_login_to_view && !user_signed_in?
64
+
65
+ redirect_to panda_cms_maintenance_path and return
66
66
  end
67
67
 
68
68
  def ignore_visit?
69
69
  # Ignore visits from bots (TODO: make this configurable)
70
70
  return true if /bot/i.match?(request.user_agent)
71
71
  # Ignore visits from Honeybadger
72
- return true if request.headers.to_h.key?("Honeybadger-Token") || request.user_agent == "Honeybadger Uptime Check"
72
+ if request.headers.to_h.key?("Honeybadger-Token") || request.user_agent == "Honeybadger Uptime Check"
73
+ return true
74
+ end
73
75
  # Ignore visits where we're asking for PHP files
74
76
  return true if request.path.ends_with?(".php")
77
+
75
78
  # Otherwise, record the visit
76
79
  false
77
80
  end
@@ -90,13 +93,13 @@ module Panda
90
93
  end
91
94
 
92
95
  def create_redirect_if_path_changed
93
- if path_changed? && path_was.present?
94
- Panda::CMS::Redirect.create!(
95
- origin_path: path_was,
96
- destination_path: path,
97
- status_code: 301
98
- )
99
- end
96
+ return unless path_changed? && path_was.present?
97
+
98
+ Panda::CMS::Redirect.create!(
99
+ origin_path: path_was,
100
+ destination_path: path,
101
+ status_code: 301
102
+ )
100
103
  end
101
104
  end
102
105
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class PostsController < ApplicationController
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
- module Admin::FilesHelper
5
+ module Admin
6
+ module FilesHelper
7
+ end
4
8
  end
5
9
  end
6
10
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
- module Admin::PagesHelper
5
+ module Admin
6
+ module PagesHelper
7
+ end
4
8
  end
5
9
  end
6
10
  end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module CMS
5
+ module AssetHelper
6
+ # Include Panda CMS JavaScript and CSS assets
7
+ # Automatically chooses between GitHub-hosted assets (production)
8
+ # and local development assets
9
+ def panda_cms_assets
10
+ Panda::CMS::AssetLoader.asset_tags.html_safe
11
+ end
12
+
13
+ # Include only Panda CMS JavaScript
14
+ def panda_cms_javascript
15
+ js_url = Panda::CMS::AssetLoader.javascript_url
16
+ return "" unless js_url
17
+
18
+ if Panda::CMS::AssetLoader.use_github_assets?
19
+ # GitHub-hosted assets with integrity check
20
+ version = Panda::CMS::AssetLoader.send(:asset_version)
21
+ integrity = asset_integrity(version, "panda-cms-#{version}.js")
22
+
23
+ tag_options = {
24
+ src: js_url
25
+ }
26
+ # In CI environment, don't use defer to ensure immediate execution
27
+ tag_options[:defer] = true unless ENV["GITHUB_ACTIONS"] == "true"
28
+ # Standalone bundles should NOT use type: "module" - they're regular scripts
29
+ # Only use type: "module" for importmap/ES module assets
30
+ if !js_url.include?("panda-cms-assets")
31
+ tag_options[:type] = "module"
32
+ end
33
+ tag_options[:integrity] = integrity if integrity
34
+ tag_options[:crossorigin] = "anonymous" if integrity
35
+
36
+ content_tag(:script, "", tag_options)
37
+ elsif js_url.include?("panda-cms-assets")
38
+ # Development assets - check if it's a standalone bundle or importmap
39
+ defer_option = (ENV["GITHUB_ACTIONS"] == "true") ? {} : {defer: true}
40
+ javascript_include_tag(js_url, **defer_option)
41
+ # Standalone bundle - don't use type: "module"
42
+ else
43
+ # Importmap asset - use type: "module"
44
+ defer_option = (ENV["GITHUB_ACTIONS"] == "true") ? {} : {defer: true}
45
+ javascript_include_tag(js_url, type: "module", **defer_option)
46
+ end
47
+ end
48
+
49
+ # Include only Panda CMS CSS
50
+ def panda_cms_stylesheet
51
+ css_url = Panda::CMS::AssetLoader.css_url
52
+ return "" unless css_url
53
+
54
+ if Panda::CMS::AssetLoader.use_github_assets?
55
+ # GitHub-hosted assets with integrity check
56
+ version = Panda::CMS::VERSION
57
+ integrity = asset_integrity(version, "panda-cms-#{version}.css")
58
+
59
+ tag_options = {
60
+ rel: "stylesheet",
61
+ href: css_url
62
+ }
63
+ tag_options[:integrity] = integrity if integrity
64
+ tag_options[:crossorigin] = "anonymous" if integrity
65
+
66
+ tag(:link, tag_options)
67
+ else
68
+ # Development assets
69
+ stylesheet_link_tag(css_url)
70
+ end
71
+ end
72
+
73
+ # Get the current Panda CMS version
74
+ def panda_cms_version
75
+ Panda::CMS::VERSION
76
+ end
77
+
78
+ # Check if using GitHub-hosted assets
79
+ def using_github_assets?
80
+ Panda::CMS::AssetLoader.use_github_assets?
81
+ end
82
+
83
+ # Download and cache assets if needed
84
+ # Call this in an initializer or controller to pre-cache assets
85
+ def ensure_panda_cms_assets!
86
+ Panda::CMS::AssetLoader.ensure_assets_available!
87
+ end
88
+
89
+ # Debug information about asset loading
90
+ def panda_cms_asset_debug
91
+ return "" unless Rails.env.development? || Rails.env.test?
92
+
93
+ version = Panda::CMS::VERSION
94
+ js_url = Panda::CMS::AssetLoader.javascript_url
95
+ css_url = Panda::CMS::AssetLoader.css_url
96
+ using_github = Panda::CMS::AssetLoader.use_github_assets?
97
+ compiled_available = Panda::CMS::AssetLoader.send(:compiled_assets_available?)
98
+
99
+ # Additional CI debugging
100
+ asset_file_exists = js_url && File.exist?(Rails.root.join("public#{js_url}"))
101
+ ci_env = ENV["GITHUB_ACTIONS"] == "true"
102
+
103
+ # Check what script tag will be generated
104
+ script_tag_preview = if using_github
105
+ tag_options = {src: js_url}
106
+ tag_options[:defer] = true unless ci_env
107
+ if !js_url.include?("panda-cms-assets")
108
+ tag_options[:type] = "module"
109
+ end
110
+ "Script tag: <script#{tag_options.map { |k, v| (v == true) ? " #{k}" : " #{k}=\"#{v}\"" }.join}></script>"
111
+ else
112
+ "Using development assets"
113
+ end
114
+
115
+ debug_info = [
116
+ "<!-- Panda CMS Asset Debug Info -->",
117
+ "<!-- Version: #{version} -->",
118
+ "<!-- Using GitHub assets: #{using_github} -->",
119
+ "<!-- Compiled assets available: #{compiled_available} -->",
120
+ "<!-- JavaScript URL: #{js_url} -->",
121
+ "<!-- CSS URL: #{css_url || "none"} -->",
122
+ "<!-- Rails environment: #{Rails.env} -->",
123
+ "<!-- Asset file exists: #{asset_file_exists} -->",
124
+ "<!-- Rails root: #{Rails.root} -->",
125
+ "<!-- CI environment: #{ci_env} -->",
126
+ "<!-- #{script_tag_preview} -->",
127
+ "<!-- Params embed_id: #{params[:embed_id] if respond_to?(:params)} -->",
128
+ "<!-- Compiled at: #{Time.now.utc.iso8601} -->"
129
+ ]
130
+
131
+ debug_info.join("\n").html_safe
132
+ end
133
+
134
+ # Initialize Panda CMS Stimulus application
135
+ # Call this after the asset tags to ensure proper initialization
136
+ def panda_cms_stimulus_init
137
+ javascript_tag(<<~JS, type: "module")
138
+ // Initialize Panda CMS Stimulus application
139
+ document.addEventListener('DOMContentLoaded', function() {
140
+ if (window.pandaCmsStimulus) {
141
+ console.debug('[Panda CMS] Stimulus application initialized');
142
+
143
+ // Set debug mode based on Rails environment
144
+ const railsEnv = document.body?.dataset?.environment || 'production';
145
+ window.pandaCmsStimulus.debug = (railsEnv === 'development');
146
+
147
+ // Trigger a custom event to signal Panda CMS is ready
148
+ document.dispatchEvent(new CustomEvent('panda-cms:ready', {
149
+ detail: {
150
+ version: '#{Panda::CMS::VERSION}',
151
+ usingGitHubAssets: #{Panda::CMS::AssetLoader.use_github_assets?}
152
+ }
153
+ }));
154
+ } else {
155
+ console.warn('[Panda CMS] Stimulus application not found. Assets may not have loaded properly.');
156
+ }
157
+ });
158
+ JS
159
+ end
160
+
161
+ # Complete asset loading with initialization
162
+ # This is the recommended way to include all Panda CMS assets
163
+ def panda_cms_complete_assets
164
+ [
165
+ panda_cms_asset_debug,
166
+ panda_cms_assets,
167
+ panda_cms_stimulus_init,
168
+ # Add immediate JavaScript execution test for CI debugging
169
+ (Rails.env.test? ? javascript_tag("window.pandaCmsInlineTest = true; console.log('[Panda CMS] Inline script executed');") : "")
170
+ ].join("\n").html_safe
171
+ end
172
+
173
+ private
174
+
175
+ def asset_integrity(version, filename)
176
+ Panda::CMS::AssetLoader.send(:asset_integrity, version, filename)
177
+ rescue
178
+ nil
179
+ end
180
+ end
181
+ end
182
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  module PagesHelper
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  module PostsHelper
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  module ThemeHelper
@@ -10,6 +10,10 @@ export default class extends Controller {
10
10
 
11
11
  connect() {
12
12
  this.loadEditorResources();
13
+ // Enable submit button after a delay as fallback
14
+ setTimeout(() => {
15
+ this.enableSubmitButton();
16
+ }, 1000);
13
17
  }
14
18
 
15
19
  async loadEditorResources() {
@@ -77,6 +81,8 @@ export default class extends Controller {
77
81
  holderDiv.dataset.editorInitialized = "true";
78
82
  // Add a class to indicate the editor is ready
79
83
  holderDiv.classList.add("editor-ready");
84
+ // Enable the submit button
85
+ this.enableSubmitButton();
80
86
  // Dispatch an event when editor is ready
81
87
  this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
82
88
  },
@@ -119,12 +125,6 @@ export default class extends Controller {
119
125
  // Wait for editor to be ready
120
126
  await this.editor.isReady;
121
127
  console.debug("[Panda CMS] Editor initialized successfully");
122
- this.editorContainerTarget.dataset.editorInitialized = "true";
123
- holderDiv.dataset.editorInitialized = "true";
124
- // Add a class to indicate the editor is ready
125
- holderDiv.classList.add("editor-ready");
126
- // Dispatch an event when editor is ready
127
- this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
128
128
 
129
129
  } catch (error) {
130
130
  console.error("[Panda CMS] Editor setup failed:", error);
@@ -133,6 +133,8 @@ export default class extends Controller {
133
133
  holderDiv.dataset.editorInitialized = "false";
134
134
  holderDiv.classList.remove("editor-ready");
135
135
  }
136
+ // Still enable the submit button even if editor fails
137
+ this.enableSubmitButton();
136
138
  }
137
139
  }
138
140
 
@@ -190,6 +192,57 @@ export default class extends Controller {
190
192
  source: "editorJS",
191
193
  };
192
194
  }
195
+
196
+ enableSubmitButton() {
197
+ // Find the submit button in the form and enable it
198
+ const form = this.element.closest('form');
199
+ if (form) {
200
+ const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
201
+ if (submitButton) {
202
+ submitButton.disabled = false;
203
+ }
204
+ }
205
+ }
206
+
207
+ async submit(event) {
208
+ // Prevent the default button click behavior temporarily
209
+ event.preventDefault();
210
+
211
+ const submitButton = event.target;
212
+ const form = submitButton.closest('form');
213
+
214
+ // Re-enable the button that was disabled by data-disable-with
215
+ submitButton.disabled = false;
216
+
217
+ // Ensure editor content is saved before form submission
218
+ if (this.editor) {
219
+ try {
220
+ const outputData = await this.editor.save();
221
+ outputData.source = "editorJS";
222
+ const jsonString = JSON.stringify(outputData);
223
+ this.hiddenFieldTarget.value = jsonString;
224
+ console.log("[Panda CMS] Editor content saved before submission");
225
+ } catch (error) {
226
+ console.error("[Panda CMS] Failed to save editor content:", error);
227
+ }
228
+ }
229
+
230
+ // Now trigger the normal form submission (this will let Rails/Turbo handle it properly)
231
+ if (form) {
232
+ // Remove our custom action to prevent infinite loop
233
+ submitButton.removeAttribute('data-action');
234
+
235
+ // Create a new click event that will trigger the normal form submission
236
+ const clickEvent = new MouseEvent('click', {
237
+ bubbles: true,
238
+ cancelable: true,
239
+ view: window
240
+ });
241
+
242
+ // Dispatch the click event, which will trigger normal Rails form submission
243
+ submitButton.dispatchEvent(clickEvent);
244
+ }
245
+ }
193
246
 
194
247
  disconnect() {
195
248
  if (this.editor) {
@@ -1,16 +1,10 @@
1
1
  console.debug("[Panda CMS] Importing Panda CMS Stimulus Controller...")
2
2
 
3
- import { Application as PandaCMSApplication } from "@hotwired/stimulus"
3
+ import { application } from "@hotwired/stimulus-loading"
4
4
 
5
- const pandaCmsApplication = PandaCMSApplication.start()
5
+ console.debug("[Panda CMS] Using shared Stimulus application...")
6
6
 
7
- console.debug("[Panda CMS] Application started...")
8
-
9
- // Configure Stimulus development experience
10
- const railsEnv = document.body?.dataset?.environment || "production";
11
- pandaCmsApplication.debug = railsEnv === "development";
12
-
13
- console.debug("[Panda CMS] window.pandaCmsStimulus available...")
7
+ const pandaCmsApplication = application
14
8
 
15
9
  console.debug("[Panda CMS] Registering controllers...")
16
10
 
@@ -2,8 +2,24 @@ import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  // Connects to data-controller="theme-form"
4
4
  export default class extends Controller {
5
+ connect() {
6
+ // Ensure submit button is enabled on connect
7
+ this.enableSubmitButton();
8
+ }
9
+
5
10
  updateTheme(event) {
6
11
  const newTheme = event.target.value;
7
12
  document.documentElement.dataset.theme = newTheme;
8
13
  }
14
+
15
+ enableSubmitButton() {
16
+ // Find the submit button in the form and ensure it's enabled
17
+ const form = this.element;
18
+ if (form) {
19
+ const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
20
+ if (submitButton) {
21
+ submitButton.disabled = false;
22
+ }
23
+ }
24
+ }
9
25
  }
@@ -0,0 +1,39 @@
1
+ // Stimulus loading utilities for Panda CMS
2
+ // This provides the loading functionality that would normally come from stimulus-rails
3
+
4
+ import { Application } from "@hotwired/stimulus"
5
+
6
+ const application = Application.start()
7
+
8
+ // Configure debug mode based on environment
9
+ const railsEnv = document.body?.dataset?.environment || "production";
10
+ application.debug = railsEnv === "development"
11
+ window.Stimulus = application
12
+
13
+ // Auto-registration functionality
14
+ function eagerLoadControllersFrom(context) {
15
+ const definitions = []
16
+ for (const path of context.keys()) {
17
+ const module = context(path)
18
+ const controller = module.default
19
+ if (controller && path.match(/[_-]controller\.(js|ts)$/)) {
20
+ const name = path
21
+ .replace(/^.*\//, "")
22
+ .replace(/[_-]controller\.(js|ts)$/, "")
23
+ .replace(/_/g, "-")
24
+ definitions.push({ name, module: controller, filename: path })
25
+ }
26
+ }
27
+ return definitions
28
+ }
29
+
30
+ function lazyLoadControllersFrom(context) {
31
+ return eagerLoadControllersFrom(context)
32
+ }
33
+
34
+ // Export the functions that stimulus-loading typically provides
35
+ export {
36
+ application,
37
+ eagerLoadControllersFrom,
38
+ lazyLoadControllersFrom
39
+ }
@@ -0,0 +1,39 @@
1
+ // Stimulus loading utilities for Panda CMS
2
+ // This provides the loading functionality that would normally come from stimulus-rails
3
+
4
+ import { Application } from "@hotwired/stimulus"
5
+
6
+ const application = Application.start()
7
+
8
+ // Configure debug mode based on environment
9
+ const railsEnv = document.body?.dataset?.environment || "production";
10
+ application.debug = railsEnv === "development"
11
+ window.Stimulus = application
12
+
13
+ // Auto-registration functionality
14
+ function eagerLoadControllersFrom(context) {
15
+ const definitions = []
16
+ for (const path of context.keys()) {
17
+ const module = context(path)
18
+ const controller = module.default
19
+ if (controller && path.match(/[_-]controller\.(js|ts)$/)) {
20
+ const name = path
21
+ .replace(/^.*\//, "")
22
+ .replace(/[_-]controller\.(js|ts)$/, "")
23
+ .replace(/_/g, "-")
24
+ definitions.push({ name, module: controller, filename: path })
25
+ }
26
+ }
27
+ return definitions
28
+ }
29
+
30
+ function lazyLoadControllersFrom(context) {
31
+ return eagerLoadControllersFrom(context)
32
+ }
33
+
34
+ // Export the functions that stimulus-loading typically provides
35
+ export {
36
+ application,
37
+ eagerLoadControllersFrom,
38
+ lazyLoadControllersFrom
39
+ }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationJob < ActiveJob::Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class RecordVisitJob < ApplicationJob
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationMailer < ActionMailer::Base
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormMailer < Panda::CMS::ApplicationMailer
4
6
  def notification_email(form:, form_submission:)
5
7
  # TODO: Handle fields named just "name", and "email" better
6
8
  @submission_data = form_submission.data
7
- @sender_name = @submission_data["first_name"].to_s + " " + @submission_data["last_name"].to_s
9
+ @sender_name = "#{@submission_data["first_name"]} #{@submission_data["last_name"]}"
8
10
  @sender_email = @submission_data["email"].to_s
9
11
 
10
12
  mail(
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationRecord < ActiveRecord::Base
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Block < ApplicationRecord
4
6
  self.table_name = "panda_cms_blocks"
5
7
 
6
8
  belongs_to :template, foreign_key: :panda_cms_template_id, class_name: "Panda::CMS::Template"
7
- has_many :block_contents, foreign_key: :panda_cms_block_id, class_name: "Panda::CMS::BlockContent", dependent: :destroy
9
+ has_many :block_contents, foreign_key: :panda_cms_block_id, class_name: "Panda::CMS::BlockContent",
10
+ dependent: :destroy
8
11
 
9
12
  validates :name, presence: true
10
13
  validates :key, presence: true, uniqueness: {scope: :panda_cms_template_id, case_sensitive: false}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class BlockContent < ApplicationRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Breadcrumb
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Current < ActiveSupport::CurrentAttributes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Form < ApplicationRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormSubmission < ApplicationRecord