Module: Abqari::RenderContext::PictureHelpers
- Included in:
- Abqari::RenderContext
- Defined in:
- lib/abqari/render_context/picture_helpers.rb
Overview
Image / icon / asset-path helpers. Lifted out of RenderContext so
the main class can stay focused on templating; the behaviour is
unchanged. Every method that emits markup returns already-safe
HTML — templates interpolate via <%==.
Constant Summary collapse
- HEROICON_VARIANTS =
{ outline: '24/outline', solid: '24/solid', mini: '20/solid', micro: '16/solid' }.freeze
Instance Method Summary collapse
- #asset_path(name) ⇒ Object
-
#bundle_asset_url(path) ⇒ Object
Resolve a relative bundle-asset filename to an absolute URL by prepending the current page's URL.
-
#bundle_asset_url_for(item, path) ⇒ Object
Like
bundle_asset_url, but resolves relative paths against the givenitem's URL rather than the currentpage. -
#bundle_picture_tag(page, image, alt:, class_: nil, loading: 'eager', fetchpriority: 'high') ⇒ Object
Bundle-aware
renderer. -
#heroicon(name, variant: :outline, **attrs) ⇒ Object
Inline a Heroicon SVG.
-
#icon(name, **attrs) ⇒ Object
Site-local SVG icon.
-
#optional_asset_path(name) ⇒ Object
Like
asset_path, but returns nil instead of raising when the asset doesn't exist. - #picture_tag(source, alt: '', sizes: nil, widths: nil) ⇒ Object
Instance Method Details
#asset_path(name) ⇒ Object
17 18 19 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 17 def asset_path(name) site.assets.path_for(name) end |
#bundle_asset_url(path) ⇒ Object
Resolve a relative bundle-asset filename to an absolute URL by
prepending the current page's URL. Passes through absolute
paths (/foo), full URLs (https://…), and anchors (#foo)
unchanged.
Used in layouts to render frontmatter image fields that point
at colocated assets — e.g. image: hero.jpg in a post or
workshop frontmatter. Markdown body images get auto-rewritten
by the build's post-processor, but frontmatter values bypass
that pass; this helper closes the gap.
Pair with sanitized_url for safe HTML output:
<img src="<%= sanitized_url(bundle_asset_url(hero)) %>" …>
90 91 92 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 90 def bundle_asset_url(path) bundle_asset_url_for(page, path) end |
#bundle_asset_url_for(item, path) ⇒ Object
Like bundle_asset_url, but resolves relative paths against
the given item's URL rather than the current page. Used by
listing partials (post list, related items, etc.) that render
image fields for many items while the active page is the
index.
99 100 101 102 103 104 105 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 99 def bundle_asset_url_for(item, path) s = path.to_s return s if s.empty? || s.start_with?('/', 'http://', 'https://', '#') base = item.respond_to?(:url) ? item.url : item.to_s "#{base}#{s}" end |
#bundle_picture_tag(page, image, alt:, class_: nil, loading: 'eager', fetchpriority: 'high') ⇒ Object
Bundle-aware
1. **Pipeline path (preferred).** When `images.optimise` is
on AND the bundle image is registered with `ImagePipeline`
(i.e. it's a raster format the pipeline handles), this
delegates to the pipeline's `picture_tag_for_url`, which
emits the same `<picture>` shape as auto-rewritten
markdown `<img>` tags get. Widths/quality/format come
from `images:` config — no per-call configuration
needed.
2. **Hand-rolled sibling fallback.** SVGs and any raster
image whose author manually pre-generated `<basename>.avif`
and `<basename>.webp` next to the source. Useful for
sites that haven't migrated to the pipeline yet, or for
cases where you want explicit control over a specific
hero image.
3. **Plain `<img>`.** Last resort when neither of the above
applies (e.g. SVGs, which don't benefit from format
negotiation).
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 128 def bundle_picture_tag(page, image, alt:, class_: nil, loading: 'eager', fetchpriority: 'high') img_url = sanitized_url(bundle_asset_url_for(page, image)) # Path 1 — pipeline. Skip for SVGs (vector, nothing to thumbnail). unless image.to_s.end_with?('.svg') extra = { class: class_, loading: loading } extra[:fetchpriority] = fetchpriority if fetchpriority pipeline_html = site.images.picture_tag_for_url( bundle_asset_url_for(page, image), alt: alt, attrs: extra.compact ) return pipeline_html if pipeline_html end # Path 2 + 3 — hand-rolled siblings or plain <img>. attrs = [] attrs << %(class="#{h(class_)}") if class_ attrs << %(src="#{h(img_url)}") attrs << %(alt="#{h(alt)}") attrs << %(loading="#{loading}") attrs << %(fetchpriority="#{fetchpriority}") if fetchpriority attrs << 'decoding="async"' plain_img = "<img #{attrs.join(' ')}>" return plain_img if image.to_s.end_with?('.svg') return plain_img unless page.respond_to?(:bundle_dir) && page.bundle_dir basename = File.basename(image.to_s, File.extname(image.to_s)) avif_path = File.join(page.bundle_dir, "#{basename}.avif") webp_path = File.join(page.bundle_dir, "#{basename}.webp") avif_exists = File.exist?(avif_path) webp_exists = File.exist?(webp_path) return plain_img unless avif_exists || webp_exists sources = [] if avif_exists avif_url = sanitized_url(bundle_asset_url_for(page, "#{basename}.avif")) sources << %(<source type="image/avif" srcset="#{h(avif_url)}">) end if webp_exists webp_url = sanitized_url(bundle_asset_url_for(page, "#{basename}.webp")) sources << %(<source type="image/webp" srcset="#{h(webp_url)}">) end "<picture>#{sources.join}#{plain_img}</picture>" end |
#heroicon(name, variant: :outline, **attrs) ⇒ Object
Inline a Heroicon SVG. Reads from vendor/heroicons/
Variants map to the Heroicons folder layout:
:outline → 24/outline (24×24 stroke, default)
:solid → 24/solid (24×24 filled)
:mini → 20/solid (20×20 filled, smaller UI)
:micro → 16/solid (16×16 filled, very small UI)
Returns '' (renders nothing) when the icon name isn't found, so a typo doesn't fail the build — just an empty span. List of icon names: https://heroicons.com/
64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 64 def heroicon(name, variant: :outline, **attrs) subdir = HEROICON_VARIANTS[variant.to_sym] or return '' # Heroicons are engine-bundled — they ship in the gem. path = File.join(site.engine_root, 'vendor', 'heroicons', subdir, "#{name}.svg") return '' unless File.exist?(path) svg = File.read(path, encoding: 'UTF-8') return svg if attrs.empty? attrs_str = attrs.map { |k, v| %(#{h(k.to_s)}="#{h(v.to_s)}") }.join(' ') svg.sub(/<svg([^>]*)>/) { "<svg#{::Regexp.last_match(1)} #{attrs_str}>" } end |
#icon(name, **attrs) ⇒ Object
Site-local SVG icon. Hybrid path — a site can drop
app/icons/<name>.svg to override an engine-bundled icon or
add its own.
39 40 41 42 43 44 45 46 47 48 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 39 def icon(name, **attrs) path = site.find_in_paths(File.join('app', 'icons', "#{name}.svg")) return '' unless File.exist?(path) svg = File.read(path, encoding: 'UTF-8') return svg if attrs.empty? attrs_str = attrs.map { |k, v| %(#{h(k.to_s)}="#{h(v.to_s)}") }.join(' ') svg.sub(/<svg([^>]*)>/) { "<svg#{::Regexp.last_match(1)} #{attrs_str}>" } end |
#optional_asset_path(name) ⇒ Object
Like asset_path, but returns nil instead of raising when the
asset doesn't exist. Used to conditionally include opt-in site-
local assets like app/assets/css/site.css — sites declare one
when they want to add overrides on top of theme + _common.css;
most sites don't, so the link should silently disappear.
26 27 28 29 30 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 26 def optional_asset_path(name) site.assets.path_for(name) rescue ArgumentError nil end |
#picture_tag(source, alt: '', sizes: nil, widths: nil) ⇒ Object
32 33 34 |
# File 'lib/abqari/render_context/picture_helpers.rb', line 32 def picture_tag(source, alt: '', sizes: nil, widths: nil) site.images.picture_tag(source, alt: alt, sizes: sizes, widths: widths) end |