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.
- checksums.yaml +4 -4
- data/README.md +37 -2
- data/Rakefile +2 -0
- data/app/builders/panda/cms/form_builder.rb +13 -5
- data/app/components/panda/cms/admin/heading_component.rb +5 -4
- data/app/components/panda/cms/admin/panel_component.rb +2 -2
- data/app/components/panda/cms/admin/statistics_component.rb +1 -2
- data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
- data/app/components/panda/cms/admin/user_activity_component.rb +1 -3
- data/app/components/panda/cms/code_component.rb +8 -4
- data/app/components/panda/cms/menu_component.rb +7 -6
- data/app/components/panda/cms/page_menu_component.rb +15 -17
- data/app/components/panda/cms/rich_text_component.rb +5 -6
- data/app/components/panda/cms/text_component.rb +6 -7
- data/app/constraints/panda/cms/admin_constraint.rb +4 -1
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +13 -9
- data/app/controllers/panda/cms/admin/forms_controller.rb +0 -2
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +2 -1
- data/app/controllers/panda/cms/admin/pages_controller.rb +2 -1
- data/app/controllers/panda/cms/admin/posts_controller.rb +1 -2
- data/app/controllers/panda/cms/admin/sessions_controller.rb +3 -5
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
- data/app/controllers/panda/cms/admin/settings_controller.rb +14 -10
- data/app/controllers/panda/cms/application_controller.rb +7 -2
- data/app/controllers/panda/cms/errors_controller.rb +5 -2
- data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
- data/app/controllers/panda/cms/pages_controller.rb +32 -29
- data/app/controllers/panda/cms/posts_controller.rb +2 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
- data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
- data/app/helpers/panda/cms/asset_helper.rb +182 -0
- data/app/helpers/panda/cms/pages_helper.rb +2 -0
- data/app/helpers/panda/cms/posts_helper.rb +2 -0
- data/app/helpers/panda/cms/theme_helper.rb +2 -0
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
- data/app/javascript/panda/cms/controllers/index.js +3 -9
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +16 -0
- data/app/javascript/panda/cms/stimulus-loading.js +39 -0
- data/app/javascript/panda_cms/stimulus-loading.js +39 -0
- data/app/jobs/panda/cms/application_job.rb +2 -0
- data/app/jobs/panda/cms/record_visit_job.rb +2 -0
- data/app/mailers/panda/cms/application_mailer.rb +2 -0
- data/app/mailers/panda/cms/form_mailer.rb +3 -1
- data/app/models/panda/cms/application_record.rb +2 -0
- data/app/models/panda/cms/block.rb +4 -1
- data/app/models/panda/cms/block_content.rb +2 -0
- data/app/models/panda/cms/breadcrumb.rb +2 -0
- data/app/models/panda/cms/current.rb +2 -0
- data/app/models/panda/cms/form.rb +2 -0
- data/app/models/panda/cms/form_submission.rb +2 -0
- data/app/models/panda/cms/menu.rb +12 -9
- data/app/models/panda/cms/menu_item.rb +10 -6
- data/app/models/panda/cms/page.rb +14 -12
- data/app/models/panda/cms/post.rb +9 -5
- data/app/models/panda/cms/redirect.rb +6 -3
- data/app/models/panda/cms/template.rb +12 -7
- data/app/models/panda/cms/user.rb +2 -0
- data/app/models/panda/cms/visit.rb +2 -0
- data/app/models/panda/social/instagram_post.rb +2 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +4 -2
- data/app/services/panda/social/instagram_feed_service.rb +3 -1
- data/app/views/layouts/different_page.html.erb +6 -0
- data/app/views/layouts/homepage.html.erb +37 -0
- data/app/views/layouts/page.html.erb +18 -0
- data/app/views/layouts/panda/cms/application.html.erb +1 -0
- data/app/views/panda/cms/admin/pages/new.html.erb +14 -8
- data/app/views/panda/cms/admin/settings/index.html.erb +1 -1
- data/app/views/panda/cms/shared/_header.html.erb +10 -2
- data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
- data/app/views/shared/_footer.html.erb +3 -0
- data/app/views/shared/_header.html.erb +11 -0
- data/config/importmap.rb +2 -0
- data/config/initializers/inflections.rb +2 -0
- data/config/initializers/panda/cms/form_errors.rb +20 -21
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
- data/config/initializers/panda/cms.rb +2 -0
- data/config/initializers/zeitwork.rb +2 -0
- data/config/puma/test.rb +3 -1
- data/config/routes.rb +8 -8
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +2 -0
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +2 -0
- data/db/seeds.rb +2 -0
- data/lib/generators/panda/cms/install_generator.rb +2 -0
- data/lib/panda/cms/asset_loader.rb +390 -0
- data/lib/panda/cms/bulk_editor.rb +7 -3
- data/lib/panda/cms/demo_site_generator.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/alert.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/base.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/header.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/image.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/list.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/quote.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/table.rb +3 -1
- data/lib/panda/cms/editor_js/renderer.rb +3 -0
- data/lib/panda/cms/editor_js.rb +2 -0
- data/lib/panda/cms/editor_js_content.rb +47 -41
- data/lib/panda/cms/engine.rb +29 -32
- data/lib/panda/cms/exceptions_app.rb +2 -0
- data/lib/panda/cms/railtie.rb +2 -0
- data/lib/panda/cms/slug.rb +3 -1
- data/lib/panda-cms/version.rb +3 -1
- data/lib/panda-cms.rb +4 -2
- data/lib/tasks/assets.rake +547 -0
- data/lib/tasks/panda/cms/install.rake +2 -0
- data/lib/tasks/panda/social/instagram.rake +2 -0
- data/lib/tasks/panda_cms.rake +3 -30
- data/public/panda-cms-assets/manifest.json +20 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
- metadata +168 -14
@@ -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: [
|
7
|
-
before_action :handle_redirects, only: [
|
8
|
-
after_action :record_visit, only: [
|
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
|
19
|
+
Panda::CMS::Page.find_by(path: @overrides[:page_path_match])
|
18
20
|
else
|
19
|
-
Panda::CMS::Page.find_by(path: "
|
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 = "
|
45
|
+
current_path = "/#{params[:path]}"
|
46
46
|
redirect = Panda::CMS::Redirect.find_by(origin_path: current_path)
|
47
47
|
|
48
|
-
|
49
|
-
redirect.increment!(:visits)
|
48
|
+
return unless redirect
|
50
49
|
|
51
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
@@ -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
|
@@ -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 {
|
3
|
+
import { application } from "@hotwired/stimulus-loading"
|
4
4
|
|
5
|
-
|
5
|
+
console.debug("[Panda CMS] Using shared Stimulus application...")
|
6
6
|
|
7
|
-
|
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,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"]
|
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,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",
|
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}
|