panda-cms 0.7.3 → 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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -5
  3. data/Rakefile +2 -0
  4. data/app/assets/builds/panda.cms.css +2 -6
  5. data/app/assets/tailwind/application.css +178 -0
  6. data/app/assets/tailwind/tailwind.config.js +15 -0
  7. data/app/builders/panda/cms/form_builder.rb +27 -36
  8. data/app/components/panda/cms/admin/flash_message_component.html.erb +2 -2
  9. data/app/components/panda/cms/admin/heading_component.rb +5 -4
  10. data/app/components/panda/cms/admin/panel_component.rb +2 -2
  11. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  12. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  13. data/app/components/panda/cms/admin/user_activity_component.rb +8 -21
  14. data/app/components/panda/cms/code_component.rb +8 -4
  15. data/app/components/panda/cms/menu_component.rb +7 -6
  16. data/app/components/panda/cms/page_menu_component.rb +15 -17
  17. data/app/components/panda/cms/rich_text_component.rb +5 -6
  18. data/app/components/panda/cms/text_component.rb +6 -7
  19. data/app/constraints/panda/cms/admin_constraint.rb +4 -1
  20. data/app/controllers/panda/cms/admin/block_contents_controller.rb +0 -1
  21. data/app/controllers/panda/cms/admin/dashboard_controller.rb +13 -9
  22. data/app/controllers/panda/cms/admin/forms_controller.rb +0 -3
  23. data/app/controllers/panda/cms/admin/my_profile_controller.rb +44 -0
  24. data/app/controllers/panda/cms/admin/pages_controller.rb +15 -4
  25. data/app/controllers/panda/cms/admin/posts_controller.rb +6 -22
  26. data/app/controllers/panda/cms/admin/sessions_controller.rb +3 -5
  27. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  28. data/app/controllers/panda/cms/admin/settings_controller.rb +14 -10
  29. data/app/controllers/panda/cms/application_controller.rb +7 -2
  30. data/app/controllers/panda/cms/errors_controller.rb +5 -2
  31. data/app/controllers/panda/cms/form_submissions_controller.rb +4 -0
  32. data/app/controllers/panda/cms/pages_controller.rb +40 -35
  33. data/app/controllers/panda/cms/posts_controller.rb +2 -0
  34. data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
  35. data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
  36. data/app/helpers/panda/cms/asset_helper.rb +182 -0
  37. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  38. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  39. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  40. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  41. data/app/javascript/panda/cms/controllers/index.js +5 -9
  42. data/app/javascript/panda/cms/controllers/slug_controller.js +64 -31
  43. data/app/javascript/panda/cms/controllers/theme_form_controller.js +25 -0
  44. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  45. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  46. data/app/jobs/panda/cms/application_job.rb +2 -0
  47. data/app/jobs/panda/cms/record_visit_job.rb +14 -14
  48. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  49. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  50. data/app/models/panda/cms/application_record.rb +3 -0
  51. data/app/models/panda/cms/block.rb +12 -17
  52. data/app/models/panda/cms/block_content.rb +7 -6
  53. data/app/models/panda/cms/breadcrumb.rb +2 -0
  54. data/app/models/panda/cms/current.rb +2 -0
  55. data/app/models/panda/cms/form.rb +2 -0
  56. data/app/models/panda/cms/form_submission.rb +2 -0
  57. data/app/models/panda/cms/menu.rb +12 -9
  58. data/app/models/panda/cms/menu_item.rb +10 -6
  59. data/app/models/panda/cms/page.rb +31 -16
  60. data/app/models/panda/cms/post.rb +12 -10
  61. data/app/models/panda/cms/redirect.rb +9 -1
  62. data/app/models/panda/cms/template.rb +17 -13
  63. data/app/models/panda/cms/user.rb +2 -0
  64. data/app/models/panda/cms/visit.rb +3 -1
  65. data/app/models/panda/social/instagram_post.rb +17 -0
  66. data/app/services/panda/cms/html_to_editor_js_converter.rb +10 -15
  67. data/app/services/panda/social/instagram_feed_service.rb +63 -0
  68. data/app/views/layouts/different_page.html.erb +6 -0
  69. data/app/views/layouts/homepage.html.erb +37 -0
  70. data/app/views/layouts/page.html.erb +18 -0
  71. data/app/views/layouts/panda/cms/application.html.erb +1 -0
  72. data/app/views/panda/cms/admin/my_profile/edit.html.erb +35 -0
  73. data/app/views/panda/cms/admin/pages/index.html.erb +1 -1
  74. data/app/views/panda/cms/admin/pages/new.html.erb +14 -8
  75. data/app/views/panda/cms/admin/posts/_form.html.erb +10 -0
  76. data/app/views/panda/cms/admin/posts/edit.html.erb +3 -2
  77. data/app/views/panda/cms/admin/posts/index.html.erb +1 -1
  78. data/app/views/panda/cms/admin/settings/index.html.erb +3 -1
  79. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +1 -1
  80. data/app/views/panda/cms/shared/_header.html.erb +14 -4
  81. data/app/views/panda/cms/shared/_importmap.html.erb +2 -1
  82. data/app/views/shared/_footer.html.erb +3 -0
  83. data/app/views/shared/_header.html.erb +11 -0
  84. data/config/importmap.rb +2 -0
  85. data/config/initializers/inflections.rb +2 -0
  86. data/config/initializers/panda/cms/form_errors.rb +20 -21
  87. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  88. data/config/initializers/panda/cms.rb +2 -0
  89. data/config/initializers/zeitwork.rb +2 -0
  90. data/config/locales/en.yml +5 -0
  91. data/config/puma/test.rb +3 -1
  92. data/config/routes.rb +11 -8
  93. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  94. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  95. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  96. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  97. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  98. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  99. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  100. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  101. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  102. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  103. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  104. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  105. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  106. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  107. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  108. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  109. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  110. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  111. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  112. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  113. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  114. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  115. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  116. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  117. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  118. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  119. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  120. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  121. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  122. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  123. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  124. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  125. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  126. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  127. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  128. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  129. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  130. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  131. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  132. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +2 -0
  133. data/db/migrate/20250120235542_remove_paper_trail.rb +56 -0
  134. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +16 -0
  135. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +7 -0
  136. data/db/seeds.rb +2 -0
  137. data/lib/generators/panda/cms/install_generator.rb +2 -0
  138. data/lib/panda/cms/asset_loader.rb +390 -0
  139. data/lib/panda/cms/bulk_editor.rb +7 -3
  140. data/lib/panda/cms/demo_site_generator.rb +27 -4
  141. data/lib/panda/cms/editor_js/blocks/alert.rb +2 -0
  142. data/lib/panda/cms/editor_js/blocks/base.rb +2 -0
  143. data/lib/panda/cms/editor_js/blocks/header.rb +2 -0
  144. data/lib/panda/cms/editor_js/blocks/image.rb +3 -0
  145. data/lib/panda/cms/editor_js/blocks/list.rb +2 -0
  146. data/lib/panda/cms/editor_js/blocks/paragraph.rb +3 -0
  147. data/lib/panda/cms/editor_js/blocks/quote.rb +3 -0
  148. data/lib/panda/cms/editor_js/blocks/table.rb +3 -1
  149. data/lib/panda/cms/editor_js/renderer.rb +3 -0
  150. data/lib/panda/cms/editor_js.rb +2 -0
  151. data/lib/panda/cms/editor_js_content.rb +50 -23
  152. data/lib/panda/cms/engine.rb +36 -37
  153. data/lib/panda/cms/exceptions_app.rb +2 -0
  154. data/lib/panda/cms/railtie.rb +2 -0
  155. data/lib/panda/cms/slug.rb +3 -1
  156. data/lib/panda-cms/version.rb +3 -1
  157. data/lib/panda-cms.rb +17 -2
  158. data/lib/tasks/assets.rake +547 -0
  159. data/lib/tasks/panda/cms/install.rake +25 -0
  160. data/lib/tasks/panda/social/instagram.rake +20 -0
  161. data/lib/tasks/panda_cms.rake +3 -30
  162. data/public/panda-cms-assets/editor-js/core/editorjs.min.js +83 -0
  163. data/public/panda-cms-assets/editor-js/plugins/embed.min.js +2 -0
  164. data/public/panda-cms-assets/editor-js/plugins/header.min.js +9 -0
  165. data/public/panda-cms-assets/editor-js/plugins/nested-list.min.js +2 -0
  166. data/public/panda-cms-assets/editor-js/plugins/paragraph.min.js +9 -0
  167. data/public/panda-cms-assets/editor-js/plugins/quote.min.js +2 -0
  168. data/public/panda-cms-assets/editor-js/plugins/simple-image.min.js +2 -0
  169. data/public/panda-cms-assets/editor-js/plugins/table.min.js +2 -0
  170. data/public/panda-cms-assets/manifest.json +20 -0
  171. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  172. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  173. metadata +71 -438
  174. data/app/models/action_text/rich_text_version.rb +0 -6
  175. data/app/models/panda/cms/block_content_version.rb +0 -8
  176. data/app/models/panda/cms/page_version.rb +0 -8
  177. data/app/models/panda/cms/post_version.rb +0 -8
  178. data/app/models/panda/cms/template_version.rb +0 -8
  179. data/app/models/panda/cms/version.rb +0 -8
  180. data/config/initializers/panda/cms/paper_trail.rb +0 -7
  181. data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +0 -24
  182. data/db/migrate/20241119214549_remove_action_text_from_posts.rb +0 -9
@@ -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
- pandaCmsApplication.debug = false
11
- window.pandaCmsStimulus = pandaCmsApplication
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
 
@@ -28,6 +22,8 @@ import EditorIframeController from "panda/cms/controllers/editor_iframe_controll
28
22
  pandaCmsApplication.register("editor-iframe", EditorIframeController)
29
23
 
30
24
  console.debug("[Panda CMS] Registering components...")
25
+ import ThemeFormController from "panda/cms/controllers/theme_form_controller";
26
+ pandaCmsApplication.register("theme-form", ThemeFormController);
31
27
 
32
28
  // Import and register all TailwindCSS Components or just the ones you need
33
29
  import { Alert, Autosave, ColorPreview, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
@@ -14,57 +14,85 @@ export default class extends Controller {
14
14
 
15
15
  connect() {
16
16
  console.debug("[Panda CMS] Slug handler connected...");
17
- // Generate path on initial load if title exists
18
- if (this.input_textTarget.value) {
19
- this.generatePath();
20
- }
17
+ // Don't auto-generate on connect anymore
21
18
  }
22
19
 
23
- generatePath() {
24
- const title = this.input_textTarget.value;
25
- if (!title) return;
20
+ generatePath(event) {
21
+ // Prevent event object from being used as input
22
+ const title = this.input_textTarget.value.trim();
23
+ console.debug("[Panda CMS] Generating path from title:", title);
26
24
 
27
- // Convert title to slug format
28
- const slug = title
29
- .toLowerCase()
30
- .replace(/[^a-z0-9]+/g, "-")
31
- .replace(/^-+|-+$/g, "");
25
+ if (!title) {
26
+ this.output_textTarget.value = "";
27
+ return;
28
+ }
29
+
30
+ // Only generate path if output is empty OR user has not manually edited it
31
+ if (!this.output_textTarget.value || !this.output_textTarget.dataset.userEdited) {
32
+ // Convert title to slug format
33
+ const slug = this.createSlug(title);
34
+ console.debug("[Panda CMS] Generated slug:", slug);
32
35
 
33
- // Only add year/month prefix for posts
34
- if (this.addDatePrefixValue) {
35
- // Get current date for year/month
36
- const now = new Date();
37
- const year = now.getFullYear();
38
- const month = String(now.getMonth() + 1).padStart(2, "0");
39
-
40
- // Add leading slash and use date format
41
- this.output_textTarget.value = `/${year}/${month}/${slug}`;
42
- } else {
43
- // Add leading slash for regular pages
44
- this.output_textTarget.value = `/${slug}`;
36
+ // Only add year/month prefix for posts
37
+ if (this.addDatePrefixValue) {
38
+ // Get current date for year/month
39
+ const now = new Date();
40
+ const year = now.getFullYear();
41
+ const month = String(now.getMonth() + 1).padStart(2, "0");
42
+
43
+ // Add leading slash and use date format
44
+ this.output_textTarget.value = `/${year}/${month}/${slug}`;
45
+ } else {
46
+ // If we have a parent selected, let setPrePath handle it
47
+ if (this.input_selectTarget.value) {
48
+ this.setPrePath(slug);
49
+ } else {
50
+ // Add leading slash for regular pages
51
+ this.output_textTarget.value = `/${slug}`;
52
+ }
53
+ }
54
+ console.debug("[Panda CMS] Final path value:", this.output_textTarget.value);
45
55
  }
46
56
  }
47
57
 
48
- setPrePath() {
58
+ setPrePath(slug = null) {
49
59
  try {
60
+ // Don't do anything if we're passed the event object
61
+ if (slug && typeof slug === 'object') {
62
+ slug = null;
63
+ }
64
+
50
65
  const match = this.input_selectTarget.options[this.input_selectTarget.selectedIndex].text.match(/.*\((.*)\)$/);
51
66
  if (match) {
52
- this.parent_slugs = match[1];
53
- const prePath = (this.existing_rootTarget.value + this.parent_slugs).replace(/\/$/, "");
54
- // Ensure we don't double up slashes
55
- const currentPath = this.output_textTarget.value.replace(/^\//, "");
56
- this.output_textTarget.value = `${prePath}/${currentPath}`;
67
+ const parentPath = match[1].replace(/\/$/, ""); // Remove trailing slash if present
68
+
69
+ // If we have a specific slug passed in, use it
70
+ // Otherwise only use the title-based slug if we have a title
71
+ const currentSlug = slug ||
72
+ (this.input_textTarget.value.trim() ? this.createSlug(this.input_textTarget.value.trim()) : "");
73
+
74
+ // Set the full path including parent path
75
+ this.output_textTarget.value = currentSlug
76
+ ? `${parentPath}/${currentSlug}`
77
+ : `${parentPath}/`;
78
+
79
+ console.debug("[Panda CMS] Set path with parent:", this.output_textTarget.value);
57
80
  }
58
81
  } catch (e) {
59
82
  console.error("[Panda CMS] Error setting pre-path:", e);
83
+ // Clear the output on error
84
+ this.output_textTarget.value = "";
60
85
  }
61
86
  }
62
87
 
63
88
  createSlug(input) {
64
- return input
89
+ if (!input || typeof input !== 'string') return "";
90
+ const slug = input
65
91
  .toLowerCase()
92
+ .trim()
66
93
  .replace(/[^a-z0-9]+/g, "-")
67
94
  .replace(/^-+|-+$/g, "");
95
+ return slug;
68
96
  }
69
97
 
70
98
  trimStartEnd(str, ch) {
@@ -74,4 +102,9 @@ export default class extends Controller {
74
102
  while (end > start && str[end - 1] === ch) --end;
75
103
  return start > 0 || end < str.length ? str.substring(start, end) : str;
76
104
  }
105
+
106
+ // Add handler for manual path edits
107
+ handlePathInput() {
108
+ this.output_textTarget.dataset.userEdited = "true";
109
+ }
77
110
  }
@@ -0,0 +1,25 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="theme-form"
4
+ export default class extends Controller {
5
+ connect() {
6
+ // Ensure submit button is enabled on connect
7
+ this.enableSubmitButton();
8
+ }
9
+
10
+ updateTheme(event) {
11
+ const newTheme = event.target.value;
12
+ document.documentElement.dataset.theme = newTheme;
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
+ }
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,29 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class RecordVisitJob < ApplicationJob
4
6
  queue_as :default
5
7
 
6
8
  def perform(
7
- url: nil,
9
+ path:,
10
+ user_id: nil,
11
+ redirect_id: nil,
12
+ page_id: nil,
8
13
  user_agent: nil,
9
- referrer: nil,
10
14
  ip_address: nil,
11
- page_id: nil,
12
- current_user_id: nil,
13
- params: [],
14
- visited_at: nil,
15
- redirect_id: nil
15
+ referer: nil,
16
+ params: []
16
17
  )
17
18
  Panda::CMS::Visit.create!(
18
- url: url,
19
+ url: path,
20
+ user_id: user_id,
21
+ redirect_id: redirect_id,
22
+ page_id: page_id,
19
23
  user_agent: user_agent,
20
- referrer: referrer,
21
24
  ip_address: ip_address,
22
- page_id: page_id,
23
- redirect_id: redirect_id,
24
- user_id: current_user_id,
25
- params: params,
26
- visited_at: visited_at
25
+ referrer: referer, # TODO: Fix the naming of this column
26
+ params: params
27
27
  )
28
28
  end
29
29
  end
@@ -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,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationRecord < ActiveRecord::Base
4
6
  self.abstract_class = true
7
+ self.implicit_order_column = "created_at"
5
8
  end
6
9
  end
7
10
  end