Skip to content

Commit 5d92efd

Browse files
committed
Implement smart responsive image detection and content reprocessing task
- Enhance make_media_responsive to re-process existing picture tags - Add context-aware size detection (e.g. .hero-section triggers :hero sizes) - Add pwb:content:reprocess_responsive rake task to update stored HTML - Update documentation with maintenance instructions - Add spec for the new rake task
1 parent a1b303c commit 5d92efd

File tree

4 files changed

+190
-6
lines changed

4 files changed

+190
-6
lines changed

app/helpers/pwb/images_helper.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -522,16 +522,27 @@ def make_media_responsive(html_content, options = {})
522522
sizes = ResponsiveVariants.sizes_for(options[:sizes] || :content)
523523

524524
doc.css('img').each do |img_node|
525-
# Skip if already inside a picture element
526-
next if img_node.ancestors('picture').any?
527-
525+
# Handle images already inside picture tags (re-process them)
526+
picture_ancestor = img_node.ancestors('picture').first
527+
528528
src = img_node['src']
529529
next if src.blank?
530530

531531
# Ensure lazy loading
532532
img_node['loading'] ||= 'lazy'
533533
img_node['decoding'] ||= 'async'
534534

535+
# Determine sizes attribute based on context
536+
current_sizes = sizes
537+
538+
# Smart detection for hero sections if using default content sizes
539+
if options[:sizes].blank? || options[:sizes] == :content
540+
if img_node.ancestors('.hero-section').any? || img_node.ancestors('.pwb-hero').any?
541+
current_sizes = ResponsiveVariants.sizes_for(:hero)
542+
end
543+
end
544+
545+
535546
# Check if we can upgrade to picture element with WebP support
536547
if trusted_webp_source?(src) && src.match?(/\.jpe?g$/i)
537548
# Create options for external_image_picture
@@ -543,16 +554,26 @@ def make_media_responsive(html_content, options = {})
543554
style: img_node['style'],
544555
width: img_node['width'],
545556
height: img_node['height'],
546-
sizes: sizes,
557+
sizes: current_sizes,
547558
responsive: true,
548559
data: img_node.keys.select { |k| k.start_with?('data-') }.each_with_object({}) { |k, h| h[k] = img_node[k] }
549560
}
550561

551562
# Generates the picture tag string with WebP source
552563
picture_html = external_image_picture(src, pic_options)
553564

554-
# Replace the original img node with the new picture node
555-
img_node.replace(picture_html)
565+
# Replace identifying node (the picture tag if it existed, otherwise the img tag)
566+
if picture_ancestor
567+
picture_ancestor.replace(picture_html)
568+
else
569+
img_node.replace(picture_html)
570+
end
571+
else
572+
# Even if not upgrading to picture, ensure sizes attribute is set
573+
# Only set if it was calculated/changed from default
574+
if current_sizes.present?
575+
img_node['sizes'] = current_sizes
576+
end
556577
end
557578
end
558579

docs/RESPONSIVE_IMAGES_IMPLEMENTATION.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,21 @@ Import external images to local storage:
307307
```bash
308308
bundle exec rake images:import_external
309309
```
310+
311+
## Maintenance Tasks
312+
313+
### Reprocessing Content
314+
315+
If responsive image logic changes (e.g., new breakpoints or logic updates), you can reprocess all existing content blocks in the database using the provided rake task:
316+
317+
```bash
318+
bundle exec rake pwb:content:reprocess_responsive
319+
```
320+
321+
This task:
322+
1. Iterates through all `Pwb::Content` records.
323+
2. Checks all localized `raw_*` fields (e.g., `raw_en`, `raw_es`).
324+
3. Runs the content through `make_media_responsive`.
325+
4. Updates the record only if the HTML has changed (e.g., new `srcset`, updated `sizes`).
326+
5. Handles `Mobility` translations correctly.
327+

lib/tasks/content_responsive.rake

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
namespace :pwb do
2+
namespace :content do
3+
desc "Reprocess all content blocks to ensure responsive images are correctly generated"
4+
task reprocess_responsive: :environment do
5+
require 'pwb/responsive_variants'
6+
7+
# Helper class to mixin the helper methods
8+
class ContentProcessor
9+
include ActionView::Helpers::TagHelper
10+
include ActionView::Helpers::AssetTagHelper
11+
include ActionView::Helpers::UrlHelper
12+
include ActionView::Context
13+
include Pwb::ImagesHelper
14+
15+
# Mock request/controller for helpers that need it
16+
def request
17+
@request ||= ActionDispatch::TestRequest.create
18+
end
19+
20+
def controller
21+
@controller ||= ActionDispatch::TestRequest.create
22+
end
23+
end
24+
25+
view = ContentProcessor.new
26+
processed_count = 0
27+
updated_count = 0
28+
29+
puts "Reprocessing Pwb::Content for responsive images..."
30+
puts "============================================================"
31+
32+
Pwb::Content.find_each do |content|
33+
processed_count += 1
34+
changed = false
35+
36+
# Iterate through available locales to check translated fields
37+
# Pwb::Content uses Mobility with JSONB backend
38+
I18n.available_locales.each do |locale|
39+
col = "raw_#{locale}"
40+
41+
# Skip if the model doesn't respond to this locale method
42+
next unless content.respond_to?(col)
43+
44+
html = content.send(col)
45+
next if html.blank?
46+
47+
# make_media_responsive is in Pwb::ImagesHelper
48+
# It detects .hero-section classes and applies :hero sizes
49+
# It also upgrades img tags to picture tags for detailed responsive behavior
50+
new_html = view.make_media_responsive(html)
51+
52+
if new_html != html
53+
content.send("#{col}=", new_html)
54+
changed = true
55+
puts " Fixed #{col} for Content ##{content.id} (#{content.page_part_key})"
56+
end
57+
end
58+
59+
if changed
60+
if content.save
61+
updated_count += 1
62+
else
63+
puts " FAILED to save Content ##{content.id}: #{content.errors.full_messages.join(', ')}"
64+
end
65+
end
66+
end
67+
68+
puts "============================================================"
69+
puts "Done!"
70+
puts "Processed: #{processed_count}"
71+
puts "Updated: #{updated_count}"
72+
end
73+
end
74+
end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
require 'rake'
5+
6+
RSpec.describe 'pwb:content:reprocess_responsive', type: :task do
7+
before(:all) do
8+
Rails.application.load_tasks if Rake::Task.tasks.empty?
9+
end
10+
11+
let(:task) { Rake::Task['pwb:content:reprocess_responsive'] }
12+
13+
before do
14+
task.reenable
15+
end
16+
17+
# Create a content record that needs updating
18+
let!(:hero_content) do
19+
Pwb::Content.create!(
20+
key: 'heroes/hero_centered',
21+
page_part_key: 'heroes/hero_centered',
22+
raw_en: '<div class="hero-section"><img src="https://example.com/image.jpg" /></div>'
23+
)
24+
end
25+
26+
let!(:standard_content) do
27+
Pwb::Content.create!(
28+
key: 'about_us_content',
29+
page_part_key: 'about_us_content',
30+
raw_en: '<div class="content"><img src="https://example.com/image.jpg" /></div>'
31+
)
32+
end
33+
34+
it 'updates hero content with hero sizes' do
35+
# Verify initial state
36+
expect(hero_content.raw_en).not_to include('sizes=')
37+
38+
# Capture stdout to avoid cluttering test output
39+
expect { task.invoke }.to output(/Processed: 2/).to_stdout
40+
41+
hero_content.reload
42+
43+
# Should have sizes attribute for hero
44+
# exact string depends on Pwb::ResponsiveVariants::SIZE_PRESETS[:hero]
45+
expect(hero_content.raw_en).to include('sizes="(min-width: 1280px) 1280px, 100vw"')
46+
47+
# Should be upgraded to picture tag (mocked helper behavior)
48+
# Note: make_media_responsive uses trusted_webp_source? which defaults to false for example.com
49+
# UNLESS we stub it or use a trusted domain.
50+
# But wait, make_media_responsive ONLY upgrades to <picture> if trusted.
51+
# However, it ALWAYS adds `sizes` to the <img> tag if it's not there.
52+
end
53+
54+
context 'with trusted image source' do
55+
let!(:trusted_content) do
56+
Pwb::Content.create!(
57+
key: 'trusted_hero',
58+
page_part_key: 'trusted_hero',
59+
raw_en: '<div class="hero-section"><img src="https://seed-assets.propertywebbuilder.com/image.jpg" /></div>'
60+
)
61+
end
62+
63+
it 'upgrades trusted images to picture tags' do
64+
expect { task.invoke }.to output.to_stdout
65+
66+
trusted_content.reload
67+
expect(trusted_content.raw_en).to include('<picture>')
68+
expect(trusted_content.raw_en).to include('type="image/webp"')
69+
end
70+
end
71+
end

0 commit comments

Comments
 (0)