panda-cms 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +37 -2
- data/Rakefile +2 -0
- 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 +3 -5
- data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
- data/app/components/panda/cms/admin/user_display_component.rb +2 -2
- 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 +10 -11
- data/app/components/panda/cms/text_component.rb +6 -7
- data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
- data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
- data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
- data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
- data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
- data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
- data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
- data/app/controllers/panda/cms/application_controller.rb +19 -6
- 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 +34 -31
- 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/application_helper.rb +3 -3
- data/app/helpers/panda/cms/asset_helper.rb +195 -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/application_panda_cms.js +2 -34
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
- data/app/javascript/panda/cms/controllers/index.js +8 -24
- 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 +3 -1
- data/app/models/panda/cms/current.rb +5 -12
- 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 +12 -8
- data/app/models/panda/cms/redirect.rb +6 -3
- data/app/models/panda/cms/template.rb +12 -7
- data/app/models/panda/cms/visit.rb +3 -1
- data/app/models/panda/social/instagram_post.rb +2 -0
- 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 +2 -1
- data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
- data/app/views/panda/cms/admin/files/index.html.erb +1 -1
- data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
- data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
- data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
- data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
- data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
- data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
- data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
- data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
- data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
- data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
- data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
- 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 +8 -3
- data/config/initializers/zeitwork.rb +2 -0
- data/config/puma/test.rb +3 -1
- data/config/routes.rb +11 -19
- 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 +5 -1
- data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
- data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
- data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -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/engine.rb +57 -116
- 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 +54 -42
- data/lib/tasks/assets.rake +587 -0
- data/lib/tasks/panda/cms/install.rake +2 -0
- data/lib/tasks/panda/cms/migrations.rake +13 -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 +186 -49
- data/app/builders/panda/cms/form_builder.rb +0 -217
- data/app/components/panda/cms/admin/button_component.rb +0 -70
- data/app/components/panda/cms/admin/container_component.rb +0 -13
- data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
- data/app/components/panda/cms/admin/heading_component.rb +0 -45
- data/app/components/panda/cms/admin/panel_component.rb +0 -13
- data/app/components/panda/cms/admin/table_component.rb +0 -46
- data/app/components/panda/cms/admin/tag_component.rb +0 -35
- data/app/constraints/panda/cms/admin_constraint.rb +0 -18
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
- data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
- data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
- data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
- data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
- data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
- data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
- data/app/models/panda/cms/breadcrumb.rb +0 -12
- data/app/models/panda/cms/user.rb +0 -31
- data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
- data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
- data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
- data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
- data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
- data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
- data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
- data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
- data/lib/panda/cms/editor_js/renderer.rb +0 -124
- data/lib/panda/cms/editor_js.rb +0 -16
- data/lib/panda/cms/editor_js_content.rb +0 -55
@@ -0,0 +1,390 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
# AssetLoader handles loading compiled assets from GitHub releases
|
6
|
+
# Falls back to local development assets when GitHub assets unavailable
|
7
|
+
class AssetLoader
|
8
|
+
class << self
|
9
|
+
# Generate HTML tags for loading Panda CMS assets
|
10
|
+
def asset_tags(options = {})
|
11
|
+
if use_github_assets?
|
12
|
+
github_asset_tags(options)
|
13
|
+
else
|
14
|
+
development_asset_tags(options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the JavaScript asset URL
|
19
|
+
def javascript_url
|
20
|
+
if use_github_assets?
|
21
|
+
github_javascript_url
|
22
|
+
else
|
23
|
+
development_javascript_url
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the CSS asset URL (if exists)
|
28
|
+
def css_url
|
29
|
+
if use_github_assets?
|
30
|
+
github_css_url
|
31
|
+
else
|
32
|
+
development_css_url
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check if GitHub-hosted assets should be used
|
37
|
+
def use_github_assets?
|
38
|
+
# Use GitHub assets in production or when explicitly enabled
|
39
|
+
Rails.env.production? ||
|
40
|
+
ENV["PANDA_CMS_USE_GITHUB_ASSETS"] == "true" ||
|
41
|
+
!development_assets_available? ||
|
42
|
+
((Rails.env.test? || in_test_environment?) && compiled_assets_available?)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Download assets from GitHub to local cache
|
46
|
+
def ensure_assets_available!
|
47
|
+
return if development_assets_available? && !use_github_assets?
|
48
|
+
|
49
|
+
cache_dir = local_cache_directory
|
50
|
+
version = `git rev-parse --short HEAD`.strip
|
51
|
+
|
52
|
+
# Check if we already have cached assets for this version
|
53
|
+
if cached_assets_exist?(version)
|
54
|
+
Rails.logger.info "[Panda CMS] Using cached assets #{version}"
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
Rails.logger.info "[Panda CMS] Downloading assets #{version} from GitHub..."
|
59
|
+
download_github_assets(version, cache_dir)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def github_asset_tags(options = {})
|
65
|
+
version = asset_version
|
66
|
+
base_url = github_base_url(version)
|
67
|
+
|
68
|
+
tags = []
|
69
|
+
|
70
|
+
# JavaScript tag with integrity check
|
71
|
+
js_url = "#{base_url}panda-cms-#{version}.js"
|
72
|
+
integrity = asset_integrity(version, "panda-cms-#{version}.js")
|
73
|
+
|
74
|
+
js_attrs = {
|
75
|
+
src: js_url
|
76
|
+
}
|
77
|
+
# In CI environment, don't use defer to ensure immediate execution
|
78
|
+
js_attrs[:defer] = true unless ENV["GITHUB_ACTIONS"] == "true"
|
79
|
+
# Standalone bundles should NOT use type="module" - they're regular scripts
|
80
|
+
# Only use type="module" for importmap/ES module assets
|
81
|
+
if !js_url.include?("panda-cms-assets")
|
82
|
+
js_attrs[:type] = "module"
|
83
|
+
end
|
84
|
+
js_attrs[:integrity] = integrity if integrity
|
85
|
+
js_attrs[:crossorigin] = "anonymous" if integrity
|
86
|
+
|
87
|
+
tags << content_tag(:script, "", js_attrs)
|
88
|
+
|
89
|
+
# CSS tag if CSS bundle exists
|
90
|
+
css_url = "#{base_url}panda-cms-#{version}.css"
|
91
|
+
if github_asset_exists?(version, "panda-cms-#{version}.css")
|
92
|
+
css_integrity = asset_integrity(version, "panda-cms-#{version}.css")
|
93
|
+
|
94
|
+
css_attrs = {
|
95
|
+
rel: "stylesheet",
|
96
|
+
href: css_url
|
97
|
+
}
|
98
|
+
css_attrs[:integrity] = css_integrity if css_integrity
|
99
|
+
css_attrs[:crossorigin] = "anonymous" if css_integrity
|
100
|
+
|
101
|
+
tags << tag(:link, css_attrs)
|
102
|
+
end
|
103
|
+
|
104
|
+
tags.join("\n").html_safe
|
105
|
+
end
|
106
|
+
|
107
|
+
def development_asset_tags(options = {})
|
108
|
+
# In test environment with CI, always use compiled assets
|
109
|
+
if (Rails.env.test? || ENV["CI"].present?) && compiled_assets_available?
|
110
|
+
# Use the same logic as GitHub assets but with local paths
|
111
|
+
version = asset_version
|
112
|
+
js_url = "/panda-cms-assets/panda-cms-#{version}.js"
|
113
|
+
css_url = "/panda-cms-assets/panda-cms-#{version}.css"
|
114
|
+
|
115
|
+
tags = []
|
116
|
+
|
117
|
+
# JavaScript tag
|
118
|
+
tags << content_tag(:script, "", {
|
119
|
+
src: js_url,
|
120
|
+
defer: true
|
121
|
+
})
|
122
|
+
|
123
|
+
# CSS tag if exists
|
124
|
+
if cached_asset_exists?(css_url)
|
125
|
+
tags << tag(:link, {
|
126
|
+
rel: "stylesheet",
|
127
|
+
href: css_url
|
128
|
+
})
|
129
|
+
end
|
130
|
+
|
131
|
+
tags.join("\n").html_safe
|
132
|
+
else
|
133
|
+
# In development, just use a simple script tag
|
134
|
+
# The view will handle importmap tags separately
|
135
|
+
content_tag(:script, "", {
|
136
|
+
src: development_javascript_url,
|
137
|
+
type: "module",
|
138
|
+
defer: true
|
139
|
+
})
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def github_javascript_url
|
144
|
+
version = asset_version
|
145
|
+
# In test environment with local compiled assets, use local URL
|
146
|
+
if Rails.env.test? && compiled_assets_available?
|
147
|
+
"/panda-cms-assets/panda-cms-#{version}.js"
|
148
|
+
else
|
149
|
+
"#{github_base_url(version)}panda-cms-#{version}.js"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def github_css_url
|
154
|
+
version = asset_version
|
155
|
+
# In test environment with local compiled assets, use local URL
|
156
|
+
if Rails.env.test? && compiled_assets_available?
|
157
|
+
"/panda-cms-assets/panda-cms-#{version}.css"
|
158
|
+
else
|
159
|
+
"#{github_base_url(version)}panda-cms-#{version}.css"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def development_javascript_url
|
164
|
+
# Try cached assets first, then importmap
|
165
|
+
version = asset_version
|
166
|
+
# Try root level first (standalone bundle), then versioned directory
|
167
|
+
root_path = "/panda-cms-assets/panda-cms-#{version}.js"
|
168
|
+
versioned_path = "/panda-cms-assets/#{version}/panda-cms-#{version}.js"
|
169
|
+
|
170
|
+
if cached_asset_exists?(root_path)
|
171
|
+
root_path
|
172
|
+
elsif cached_asset_exists?(versioned_path)
|
173
|
+
versioned_path
|
174
|
+
else
|
175
|
+
# Fallback to importmap or engine asset
|
176
|
+
"/assets/panda/cms/controllers/index.js"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def development_css_url
|
181
|
+
version = asset_version
|
182
|
+
# Try versioned directory first, then root level
|
183
|
+
versioned_path = "/panda-cms-assets/#{version}/panda-cms-#{version}.css"
|
184
|
+
root_path = "/panda-cms-assets/panda-cms-#{version}.css"
|
185
|
+
|
186
|
+
if cached_asset_exists?(versioned_path)
|
187
|
+
versioned_path
|
188
|
+
elsif cached_asset_exists?(root_path)
|
189
|
+
root_path
|
190
|
+
else
|
191
|
+
nil # No CSS in development mode typically
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def github_base_url(version)
|
196
|
+
# In test environment with compiled assets, use local URLs
|
197
|
+
if (Rails.env.test? || in_test_environment?) && compiled_assets_available?
|
198
|
+
"/panda-cms-assets/"
|
199
|
+
else
|
200
|
+
"https://github.com/tastybamboo/panda-cms/releases/download/#{version}/"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def asset_version
|
205
|
+
# In test environment, use VERSION constant for consistency with compiled assets
|
206
|
+
# In other environments, use git SHA for dynamic versioning
|
207
|
+
# Also check for test environment indicators since Rails.env might be development in specs
|
208
|
+
if Rails.env.test? || ENV["CI"].present? || in_test_environment?
|
209
|
+
Panda::CMS::VERSION
|
210
|
+
else
|
211
|
+
`git rev-parse --short HEAD`.strip
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def in_test_environment?
|
216
|
+
# Check if we're running specs even if Rails.env is development
|
217
|
+
defined?(RSpec) && RSpec.respond_to?(:configuration)
|
218
|
+
end
|
219
|
+
|
220
|
+
def compiled_assets_available?
|
221
|
+
# Check if compiled assets exist in test location
|
222
|
+
version = asset_version
|
223
|
+
js_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.js")
|
224
|
+
css_file = Rails.public_path.join("panda-cms-assets", "panda-cms-#{version}.css")
|
225
|
+
js_file.exist? && css_file.exist?
|
226
|
+
end
|
227
|
+
|
228
|
+
def development_assets_available?
|
229
|
+
# Check if local development assets exist (importmap, etc.)
|
230
|
+
importmap_available? || engine_assets_available?
|
231
|
+
end
|
232
|
+
|
233
|
+
def importmap_available?
|
234
|
+
return false unless defined?(Rails.application.importmap)
|
235
|
+
|
236
|
+
begin
|
237
|
+
# Rails 8+ uses a different API for accessing importmap entries
|
238
|
+
if Rails.application.importmap.respond_to?(:to_json)
|
239
|
+
importmap_json = JSON.parse(Rails.application.importmap.to_json)
|
240
|
+
importmap_json.any? { |name, _| name.include?("panda") }
|
241
|
+
elsif Rails.application.importmap.respond_to?(:entries)
|
242
|
+
Rails.application.importmap.entries.any? { |entry| entry.name.include?("panda") }
|
243
|
+
else
|
244
|
+
false
|
245
|
+
end
|
246
|
+
rescue => e
|
247
|
+
Rails.logger.debug "[Panda CMS] Could not check importmap: #{e.message}"
|
248
|
+
false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def engine_assets_available?
|
253
|
+
# Check if engine's JavaScript files are available
|
254
|
+
engine_js_path = Rails.root.join("..", "..", "app", "javascript", "panda", "cms", "controllers", "index.js")
|
255
|
+
File.exist?(engine_js_path)
|
256
|
+
end
|
257
|
+
|
258
|
+
def cached_assets_exist?(version)
|
259
|
+
cache_dir = local_cache_directory.join(version)
|
260
|
+
cache_dir.exist? && cache_dir.join("panda-cms-#{version}.js").exist?
|
261
|
+
end
|
262
|
+
|
263
|
+
def cached_asset_exists?(path)
|
264
|
+
Rails.public_path.join(path.delete_prefix("/")).exist?
|
265
|
+
end
|
266
|
+
|
267
|
+
def local_cache_directory
|
268
|
+
Rails.public_path.join("panda-cms-assets")
|
269
|
+
end
|
270
|
+
|
271
|
+
def download_github_assets(version, cache_dir)
|
272
|
+
require "net/http"
|
273
|
+
require "uri"
|
274
|
+
require "json"
|
275
|
+
|
276
|
+
version_dir = cache_dir.join(version)
|
277
|
+
FileUtils.mkdir_p(version_dir)
|
278
|
+
|
279
|
+
# Download manifest first
|
280
|
+
manifest_url = "#{github_base_url(version)}manifest.json"
|
281
|
+
|
282
|
+
begin
|
283
|
+
manifest_response = fetch_url(manifest_url)
|
284
|
+
if manifest_response.success?
|
285
|
+
manifest = JSON.parse(manifest_response.body)
|
286
|
+
|
287
|
+
# Download each file
|
288
|
+
manifest["files"].each do |file_info|
|
289
|
+
filename = file_info["filename"]
|
290
|
+
file_url = "#{github_base_url(version)}#{filename}"
|
291
|
+
|
292
|
+
Rails.logger.debug "[Panda CMS] Downloading #{filename}..."
|
293
|
+
|
294
|
+
file_response = fetch_url(file_url)
|
295
|
+
if file_response.success?
|
296
|
+
File.write(version_dir.join(filename), file_response.body)
|
297
|
+
Rails.logger.debug "[Panda CMS] Downloaded #{filename}"
|
298
|
+
else
|
299
|
+
Rails.logger.warn "[Panda CMS] Failed to download #{filename}: #{file_response.code}"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Save manifest
|
304
|
+
File.write(version_dir.join("manifest.json"), manifest_response.body)
|
305
|
+
Rails.logger.info "[Panda CMS] Assets cached locally"
|
306
|
+
else
|
307
|
+
Rails.logger.warn "[Panda CMS] Failed to download manifest: #{manifest_response.code}"
|
308
|
+
end
|
309
|
+
rescue => e
|
310
|
+
Rails.logger.error "[Panda CMS] Error downloading assets: #{e.message}"
|
311
|
+
# Fall back to development mode
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def fetch_url(url)
|
316
|
+
uri = URI(url)
|
317
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
318
|
+
http.use_ssl = (uri.scheme == "https")
|
319
|
+
http.open_timeout = 10
|
320
|
+
http.read_timeout = 30
|
321
|
+
|
322
|
+
request = Net::HTTP::Get.new(uri)
|
323
|
+
request["User-Agent"] = "Panda-CMS/#{`git rev-parse --short HEAD`}"
|
324
|
+
|
325
|
+
response = http.request(request)
|
326
|
+
|
327
|
+
OpenStruct.new(
|
328
|
+
success?: response.code.to_i == 200,
|
329
|
+
code: response.code,
|
330
|
+
body: response.body
|
331
|
+
)
|
332
|
+
rescue => e
|
333
|
+
Rails.logger.error "[Panda CMS] Network error: #{e.message}"
|
334
|
+
OpenStruct.new(success?: false, code: "error", body: nil)
|
335
|
+
end
|
336
|
+
|
337
|
+
def asset_integrity(version, filename)
|
338
|
+
# Try to get integrity from cached manifest
|
339
|
+
manifest_path = local_cache_directory.join(version, "manifest.json")
|
340
|
+
return nil unless manifest_path.exist?
|
341
|
+
|
342
|
+
begin
|
343
|
+
manifest = JSON.parse(File.read(manifest_path))
|
344
|
+
file_info = manifest["files"].find { |f| f["filename"] == filename }
|
345
|
+
return nil unless file_info
|
346
|
+
|
347
|
+
"sha256-#{Base64.strict_encode64([file_info["sha256"]].pack("H*"))}"
|
348
|
+
rescue => e
|
349
|
+
Rails.logger.warn "[Panda CMS] Error reading asset integrity: #{e.message}"
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def github_asset_exists?(version, filename)
|
355
|
+
# Quick check - assume it exists for now
|
356
|
+
# Could be enhanced to do HEAD request
|
357
|
+
true
|
358
|
+
end
|
359
|
+
|
360
|
+
def content_tag(name, content, options = {})
|
361
|
+
if defined?(ActionView::Helpers::TagHelper)
|
362
|
+
# Create a view context to render the tag
|
363
|
+
view_context = ActionView::Base.new(ActionView::LookupContext.new([]), {}, nil)
|
364
|
+
view_context.content_tag(name, content, options)
|
365
|
+
else
|
366
|
+
# Fallback implementation
|
367
|
+
attrs = options.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
368
|
+
if content.present?
|
369
|
+
"<#{name} #{attrs}>#{content}</#{name}>"
|
370
|
+
else
|
371
|
+
"<#{name} #{attrs}></#{name}>"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def tag(name, options = {})
|
377
|
+
if defined?(ActionView::Helpers::TagHelper)
|
378
|
+
# Create a view context to render the tag
|
379
|
+
view_context = ActionView::Base.new(ActionView::LookupContext.new([]), {}, nil)
|
380
|
+
view_context.tag(name, options)
|
381
|
+
else
|
382
|
+
# Fallback implementation
|
383
|
+
attrs = options.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
384
|
+
"<#{name} #{attrs}>"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "json"
|
3
4
|
|
4
5
|
module Panda
|
@@ -77,11 +78,12 @@ module Panda
|
|
77
78
|
|
78
79
|
if current_data.dig("pages", path, "contents", key).nil?
|
79
80
|
raise "Unknown page 1" if page.nil?
|
81
|
+
|
80
82
|
block = Panda::CMS::Block.find_or_create_by(key: key, template: page.template) do |block_meta|
|
81
83
|
block_meta.name = key.titleize
|
82
84
|
end
|
83
85
|
|
84
|
-
|
86
|
+
unless block
|
85
87
|
debug[:error] << "Error creating block '#{key.titleize}' on page '#{page.title}'"
|
86
88
|
next
|
87
89
|
end
|
@@ -104,6 +106,7 @@ module Panda
|
|
104
106
|
elsif content != current_data["pages"][path]["contents"][key]["content"]
|
105
107
|
# Content has changed
|
106
108
|
raise "Unknown page 2" if page.nil?
|
109
|
+
|
107
110
|
block = Panda::CMS::Block.find_by(key: key, template: page.template)
|
108
111
|
if Panda::CMS::BlockContent.find_by(page: page, block: block)&.update(content: content)
|
109
112
|
debug[:success] << "Updated '#{key.titleize}' content on page '#{page.title}'"
|
@@ -143,7 +146,8 @@ module Panda
|
|
143
146
|
end
|
144
147
|
|
145
148
|
# TODO: Eventually set the position of the block in the template, and then order from there rather than the name?
|
146
|
-
Panda::CMS::BlockContent.includes(:block,
|
149
|
+
Panda::CMS::BlockContent.includes(:block,
|
150
|
+
page: [:template]).order("panda_cms_pages.lft ASC, panda_cms_blocks.key ASC").each do |block_content|
|
147
151
|
item = data["pages"][block_content.page.path] ||= {}
|
148
152
|
item["title"] = block_content.page.title
|
149
153
|
item["template"] = block_content.page.template.name
|
data/lib/panda/cms/engine.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rubygems"
|
2
4
|
require "panda/core"
|
3
5
|
require "panda/core/engine"
|
6
|
+
require "panda/editor"
|
7
|
+
require "panda/editor/engine"
|
4
8
|
require "panda/cms/railtie"
|
5
9
|
|
6
10
|
require "invisible_captcha"
|
@@ -16,10 +20,8 @@ module Panda
|
|
16
20
|
]
|
17
21
|
|
18
22
|
# Basic session setup only
|
19
|
-
initializer "
|
20
|
-
if app.config.middleware.frozen?
|
21
|
-
app.config.middleware = app.config.middleware.dup
|
22
|
-
end
|
23
|
+
initializer "panda.cms.session", before: :load_config_initializers do |app|
|
24
|
+
app.config.middleware = app.config.middleware.dup if app.config.middleware.frozen?
|
23
25
|
|
24
26
|
app.config.session_store :cookie_store, key: "_panda_cms_session"
|
25
27
|
app.config.middleware.use ActionDispatch::Cookies
|
@@ -28,15 +30,16 @@ module Panda
|
|
28
30
|
|
29
31
|
config.to_prepare do
|
30
32
|
ApplicationController.helper(::ApplicationHelper)
|
33
|
+
ApplicationController.helper(Panda::CMS::AssetHelper)
|
31
34
|
end
|
32
35
|
|
33
36
|
# Set our generators
|
34
37
|
config.generators do |g|
|
35
38
|
g.orm :active_record, primary_key_type: :uuid
|
36
39
|
g.test_framework :rspec, fixture: true
|
37
|
-
g.fixture_replacement
|
40
|
+
g.fixture_replacement nil
|
38
41
|
g.view_specs false
|
39
|
-
g.templates.unshift File.expand_path("
|
42
|
+
g.templates.unshift File.expand_path("../templates", __dir__)
|
40
43
|
end
|
41
44
|
|
42
45
|
# Make files in public available to the main app (e.g. /panda_cms-assets/favicon.ico)
|
@@ -49,7 +52,7 @@ module Panda
|
|
49
52
|
# Custom error handling
|
50
53
|
# config.exceptions_app = Panda::CMS::ExceptionsApp.new(exceptions_app: routes)
|
51
54
|
|
52
|
-
initializer "
|
55
|
+
initializer "panda.cms.assets" do |app|
|
53
56
|
if Rails.configuration.respond_to?(:assets)
|
54
57
|
# Add JavaScript paths
|
55
58
|
app.config.assets.paths << root.join("app/javascript")
|
@@ -67,12 +70,10 @@ module Panda
|
|
67
70
|
end
|
68
71
|
|
69
72
|
# Add importmap paths from the engine
|
70
|
-
initializer "
|
73
|
+
initializer "panda.cms.importmap", before: "importmap" do |app|
|
71
74
|
if app.config.respond_to?(:importmap)
|
72
75
|
# Create a new array if frozen
|
73
|
-
if app.config.importmap.paths.frozen?
|
74
|
-
app.config.importmap.paths = app.config.importmap.paths.dup
|
75
|
-
end
|
76
|
+
app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
|
76
77
|
|
77
78
|
# Add our paths
|
78
79
|
app.config.importmap.paths << root.join("config/importmap.rb")
|
@@ -96,13 +97,13 @@ module Panda
|
|
96
97
|
end
|
97
98
|
end
|
98
99
|
|
99
|
-
initializer "#{engine_name}.backtrace_cleaner" do |
|
100
|
+
initializer "#{engine_name}.backtrace_cleaner" do |_app|
|
100
101
|
engine_root_regex = Regexp.escape(root.to_s + File::SEPARATOR)
|
101
102
|
|
102
103
|
# Clean those ERB lines, we don't need the internal autogenerated
|
103
104
|
# ERB method, what we do need (line number in ERB file) is already there
|
104
105
|
Rails.backtrace_cleaner.add_filter do |line|
|
105
|
-
line.sub(/(\.erb:\d+):in `__.*$/,
|
106
|
+
line.sub(/(\.erb:\d+):in `__.*$/, '\\1')
|
106
107
|
end
|
107
108
|
|
108
109
|
# Remove our own engine's path prefix, even if it's
|
@@ -124,118 +125,58 @@ module Panda
|
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
|
-
# Set up ViewComponent
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# config.view_component.preview_paths << Panda::CMS::Engine.root.join("lib/component_previews").to_s
|
135
|
-
# config.view_component.generate.preview_path = "lib/component_previews"
|
136
|
-
|
137
|
-
# Set up authentication
|
138
|
-
initializer "panda_cms.omniauth", before: "omniauth" do |app|
|
139
|
-
app.config.session_store :cookie_store, key: "_panda_cms_session"
|
140
|
-
app.config.middleware.use ActionDispatch::Cookies
|
141
|
-
app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
|
128
|
+
# Set up ViewComponent
|
129
|
+
initializer "panda.cms.view_component" do |app|
|
130
|
+
app.config.view_component.preview_paths ||= []
|
131
|
+
app.config.view_component.preview_paths << root.join("spec/components/previews")
|
132
|
+
app.config.view_component.generate.sidecar = true
|
133
|
+
app.config.view_component.generate.preview = true
|
134
|
+
end
|
142
135
|
|
143
|
-
|
144
|
-
|
145
|
-
# TODO: Move this to somewhere more sensible?
|
146
|
-
# Define the mapping of our provider "names" to the OmniAuth strategies and configuration
|
147
|
-
auth_path = "#{Panda::CMS.route_namespace}/auth"
|
148
|
-
callback_path = "/callback"
|
149
|
-
available_providers = {
|
150
|
-
microsoft: {
|
151
|
-
strategy: :microsoft_graph,
|
152
|
-
defaults: {
|
153
|
-
name: "microsoft",
|
154
|
-
# Setup at the following URL:
|
155
|
-
# https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
|
156
|
-
client_id: Rails.application.credentials.dig(:microsoft, :client_id),
|
157
|
-
client_secret: Rails.application.credentials.dig(:microsoft, :client_secret),
|
158
|
-
# Don't change this or the sky will fall on your head
|
159
|
-
# https://github.com/synth/omniauth-microsoft_graph/tree/main?tab=readme-ov-file#domain-verification
|
160
|
-
skip_domain_verification: false,
|
161
|
-
# If your application is single-tenanted, replace "common" with your tenant (directory) ID
|
162
|
-
# from https://portal.azure.com/#settings/directory, otherwise you'll likely want to leave
|
163
|
-
# these settings unchanged
|
164
|
-
client_options: {
|
165
|
-
site: "https://login.microsoftonline.com/",
|
166
|
-
token_url: "common/oauth2/v2.0/token",
|
167
|
-
authorize_url: "common/oauth2/v2.0/authorize"
|
168
|
-
},
|
169
|
-
# If you assign specific users or groups, you will likely want to set this to
|
170
|
-
# true to enable auto-provisioning
|
171
|
-
create_account_on_first_login: false,
|
172
|
-
create_admin_account_on_first_login: false,
|
173
|
-
# Don't hide this provider from the login page
|
174
|
-
hidden: false
|
175
|
-
}
|
176
|
-
},
|
177
|
-
google: {
|
178
|
-
strategy: :google_oauth2,
|
179
|
-
defaults: {
|
180
|
-
name: "google",
|
181
|
-
# Setup at the following URL: https://console.developers.google.com/
|
182
|
-
client_id: Rails.application.credentials.dig(:google, :client_id),
|
183
|
-
client_secret: Rails.application.credentials.dig(:google, :client_secret),
|
184
|
-
# If you assign specific users or groups, you will likely want to set this to
|
185
|
-
# true to enable auto-provisioning
|
186
|
-
create_account_on_first_login: false,
|
187
|
-
create_admin_account_on_first_login: false,
|
188
|
-
# Options we need
|
189
|
-
scope: "email, profile",
|
190
|
-
image_aspect_ratio: "square",
|
191
|
-
image_size: 150,
|
192
|
-
# Worth setting select_account as default, as many people have multiple Google accounts now:
|
193
|
-
prompt: "select_account",
|
194
|
-
# You should probably also set the 'hd' option, huh?,
|
195
|
-
# Don't hide this provider from the login page
|
196
|
-
hidden: false
|
197
|
-
}
|
198
|
-
},
|
199
|
-
github: {
|
200
|
-
strategy: :github,
|
201
|
-
defaults: {
|
202
|
-
name: "github",
|
203
|
-
# Setup at the following URL: https://github.com/settings/applications/new
|
204
|
-
# with a callback of
|
205
|
-
# In the meantime, as long as you're set to /admin as your login path, and on
|
206
|
-
# http://localhost:3000, you can use these for a first login :)
|
207
|
-
client_id: Rails.application.credentials.dig(:github, :client_id),
|
208
|
-
client_secret: Rails.application.credentials.dig(:github, :client_secret),
|
209
|
-
scope: "user:email,read:user",
|
210
|
-
create_account_on_first_login: false,
|
211
|
-
create_admin_account_on_first_login: false,
|
212
|
-
# Don't hide this provider from the login page
|
213
|
-
hidden: false
|
214
|
-
}
|
215
|
-
}
|
216
|
-
}
|
136
|
+
# Authentication is now handled by Panda::Core::Engine
|
217
137
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
138
|
+
# Configure Core for CMS
|
139
|
+
initializer "panda.cms.configure_core" do |app|
|
140
|
+
Panda::Core.configure do |config|
|
141
|
+
# Customize login page
|
142
|
+
config.login_logo_path = "/panda-cms-assets/panda-nav.png"
|
143
|
+
config.login_page_title = "Panda CMS Admin"
|
222
144
|
|
223
|
-
|
224
|
-
|
225
|
-
else
|
226
|
-
"#{Panda::CMS.config.url}#{auth_path}/#{provider}#{callback_path}"
|
227
|
-
end
|
145
|
+
# Set dashboard redirect path to CMS dashboard (using Core's admin_path)
|
146
|
+
config.dashboard_redirect_path = "#{Panda::Core.configuration.admin_path}/cms"
|
228
147
|
|
229
|
-
|
148
|
+
# Customize initial breadcrumb
|
149
|
+
config.initial_admin_breadcrumb = ->(controller) {
|
150
|
+
# Use CMS dashboard path - just use the string path
|
151
|
+
["Admin", "#{Panda::Core.configuration.admin_path}/cms"]
|
152
|
+
}
|
230
153
|
|
231
|
-
|
232
|
-
|
154
|
+
# Dashboard widgets
|
155
|
+
config.admin_dashboard_widgets = ->(user) {
|
156
|
+
widgets = []
|
157
|
+
|
158
|
+
# Add CMS statistics widgets if CMS is available
|
159
|
+
if defined?(Panda::CMS)
|
160
|
+
widgets << Panda::CMS::Admin::StatisticsComponent.new(
|
161
|
+
metric: "Views Today",
|
162
|
+
value: Panda::CMS::Visit.group_by_day(:visited_at, last: 1).count.values.first || 0
|
163
|
+
)
|
164
|
+
widgets << Panda::CMS::Admin::StatisticsComponent.new(
|
165
|
+
metric: "Views Last Week",
|
166
|
+
value: Panda::CMS::Visit.group_by_week(:visited_at, last: 1).count.values.first || 0
|
167
|
+
)
|
168
|
+
widgets << Panda::CMS::Admin::StatisticsComponent.new(
|
169
|
+
metric: "Views Last Month",
|
170
|
+
value: Panda::CMS::Visit.group_by_month(:visited_at, last: 1).count.values.first || 0
|
171
|
+
)
|
233
172
|
end
|
234
|
-
|
173
|
+
|
174
|
+
widgets
|
175
|
+
}
|
235
176
|
end
|
236
177
|
end
|
237
178
|
|
238
|
-
config.before_initialize do |
|
179
|
+
config.before_initialize do |_app|
|
239
180
|
# Default configuration
|
240
181
|
Panda::CMS.configure do |config|
|
241
182
|
# Array of additional EditorJS tools to load
|