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
@@ -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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Panda
|
2
4
|
module CMS
|
3
5
|
module EditorJs
|
@@ -23,7 +25,7 @@ module Panda
|
|
23
25
|
index = 0
|
24
26
|
|
25
27
|
while index < content.length
|
26
|
-
rows << if index
|
28
|
+
rows << if index.zero? && with_headings
|
27
29
|
render_header_row(content[index])
|
28
30
|
else
|
29
31
|
render_data_row(content[index])
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sanitize"
|
2
4
|
|
3
5
|
module Panda
|
@@ -60,6 +62,7 @@ module Panda
|
|
60
62
|
# Only allow the exact valid content we expect
|
61
63
|
valid_content = '<figure class="text-left"><blockquote><p>Valid HTML</p></blockquote><figcaption>Valid caption</figcaption></figure>'
|
62
64
|
return html if html.strip == valid_content.strip
|
65
|
+
|
63
66
|
return ""
|
64
67
|
end
|
65
68
|
|
data/lib/panda/cms/editor_js.rb
CHANGED
@@ -1,55 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "json"
|
2
4
|
|
3
|
-
module Panda
|
4
|
-
|
5
|
+
module Panda
|
6
|
+
module CMS
|
7
|
+
module EditorJsContent
|
8
|
+
extend ActiveSupport::Concern
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
included do
|
11
|
+
include ActiveModel::Validations
|
12
|
+
include ActiveModel::Callbacks
|
9
13
|
|
10
|
-
|
11
|
-
|
14
|
+
before_save :generate_cached_content
|
15
|
+
end
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def content=(value)
|
18
|
+
if value.is_a?(Hash)
|
19
|
+
super(value.to_json)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def content
|
26
|
+
value = super
|
27
|
+
if value.is_a?(String)
|
28
|
+
begin
|
29
|
+
JSON.parse(value)
|
30
|
+
rescue JSON::ParserError
|
31
|
+
value
|
32
|
+
end
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
28
36
|
end
|
29
|
-
else
|
30
|
-
value
|
31
|
-
end
|
32
|
-
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def generate_cached_content
|
39
|
+
if content.is_a?(String)
|
40
|
+
begin
|
41
|
+
parsed_content = JSON.parse(content)
|
42
|
+
self.cached_content = if parsed_content.is_a?(Hash) && parsed_content["blocks"].present?
|
43
|
+
Panda::CMS::EditorJs::Renderer.new(parsed_content).render
|
44
|
+
else
|
45
|
+
content
|
46
|
+
end
|
47
|
+
rescue JSON::ParserError
|
48
|
+
# If it's not JSON, treat it as plain text
|
49
|
+
self.cached_content = content
|
50
|
+
end
|
51
|
+
elsif content.is_a?(Hash) && content["blocks"].present?
|
52
|
+
# Process EditorJS content
|
53
|
+
self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
|
40
54
|
else
|
41
|
-
|
55
|
+
# For any other case, store as is
|
56
|
+
self.cached_content = content.to_s
|
42
57
|
end
|
43
|
-
rescue JSON::ParserError
|
44
|
-
# If it's not JSON, treat it as plain text
|
45
|
-
self.cached_content = content
|
46
58
|
end
|
47
|
-
elsif content.is_a?(Hash) && content["blocks"].present?
|
48
|
-
# Process EditorJS content
|
49
|
-
self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
|
50
|
-
else
|
51
|
-
# For any other case, store as is
|
52
|
-
self.cached_content = content.to_s
|
53
59
|
end
|
54
60
|
end
|
55
61
|
end
|