Preserve chronological order in gapless gallery rows
This commit is contained in:
@@ -1,6 +1,53 @@
|
|||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
|
||||||
module GalleryItems
|
module GalleryItems
|
||||||
|
JPEG_START_OF_FRAME = [0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7,
|
||||||
|
0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF].freeze
|
||||||
|
|
||||||
|
# Read just the dimensions from a local JPEG. The rendered gallery needs
|
||||||
|
# aspect ratios to size each chronological pair without cropping either photo.
|
||||||
|
def jpeg_dimensions(path)
|
||||||
|
File.open(path, 'rb') do |file|
|
||||||
|
return nil unless file.read(2) == "\xFF\xD8".b
|
||||||
|
|
||||||
|
while (prefix = file.read(1))
|
||||||
|
next unless prefix.getbyte(0) == 0xFF
|
||||||
|
|
||||||
|
marker = file.read(1)&.getbyte(0)
|
||||||
|
break unless marker
|
||||||
|
next if marker == 0xFF || marker == 0x01 || (0xD0..0xD7).cover?(marker)
|
||||||
|
break if marker == 0xD9 || marker == 0xDA
|
||||||
|
|
||||||
|
size = file.read(2)&.unpack1('n')
|
||||||
|
break unless size && size >= 2
|
||||||
|
if JPEG_START_OF_FRAME.include?(marker)
|
||||||
|
dimensions = file.read(5)
|
||||||
|
return nil unless dimensions&.length == 5
|
||||||
|
|
||||||
|
height, width = dimensions.byteslice(1, 4).unpack('n2')
|
||||||
|
return [width, height] if width.positive? && height.positive?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
file.seek(size - 2, IO::SEEK_CUR)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
rescue Errno::ENOENT, EOFError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def gallery_image_ratio(content)
|
||||||
|
image = Nokogiri::HTML::DocumentFragment.parse(content).at_css('img')
|
||||||
|
source = image&.[]('src')
|
||||||
|
return 1.5 unless source&.start_with?('/img/arts/')
|
||||||
|
|
||||||
|
path = File.join(File.expand_path('..', __dir__), source.delete_prefix('/'))
|
||||||
|
width, height = jpeg_dimensions(path)
|
||||||
|
return 1.5 unless width && height
|
||||||
|
|
||||||
|
(width.to_f / height).round(4)
|
||||||
|
end
|
||||||
|
|
||||||
# Uploaded batches share one source document, but each photo is a gallery item.
|
# Uploaded batches share one source document, but each photo is a gallery item.
|
||||||
# Keeping this at render time also fixes batches that have already been published.
|
# Keeping this at render time also fixes batches that have already been published.
|
||||||
def gallery_items(content)
|
def gallery_items(content)
|
||||||
|
|||||||
+22
-10
@@ -227,16 +227,22 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.art .post-list {
|
.art .post-list {
|
||||||
column-count: 2;
|
.gallery-row {
|
||||||
column-gap: $spacing-unit;
|
display: flex;
|
||||||
|
gap: $spacing-unit;
|
||||||
|
align-items: flex-start;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 $spacing-unit;
|
||||||
|
|
||||||
> li {
|
> li {
|
||||||
display: inline-block;
|
flex: var(--photo-ratio) 1 0;
|
||||||
width: 100%;
|
min-width: 0;
|
||||||
margin-top: 0;
|
margin: 0;
|
||||||
margin-bottom: $spacing-unit;
|
}
|
||||||
break-inside: avoid;
|
|
||||||
-webkit-column-break-inside: avoid;
|
> li:only-child {
|
||||||
|
flex: 0 0 calc((100% - #{$spacing-unit}) / 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-content > p:last-child {
|
.post-content > p:last-child {
|
||||||
@@ -249,11 +255,17 @@ hr {
|
|||||||
height: auto;
|
height: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
// Reserve space before lazy images load, then use their natural proportions.
|
// Reserve space before lazy images load, then use their natural proportions.
|
||||||
aspect-ratio: auto 3 / 2;
|
aspect-ratio: auto var(--photo-ratio, 3 / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-query($on-palm) {
|
@include media-query($on-palm) {
|
||||||
column-count: 1;
|
.gallery-row {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,14 +28,17 @@ wide_gallery: true
|
|||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="post-list">
|
<div class="post-list">
|
||||||
|
{% assign gallery_index = 0 %}
|
||||||
{% for post in site.art reversed %}
|
{% for post in site.art reversed %}
|
||||||
{% if post.published == false %}
|
{% if post.published == false %}
|
||||||
{% continue %}
|
{% continue %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% assign items = post.content | markdownify | lazy_images | gallery_items %}
|
{% assign items = post.content | markdownify | lazy_images | gallery_items %}
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<li class='lang-{% if post.lang %}{{ post.lang }}{% else %}en{% endif %}'>
|
{% assign row_position = gallery_index | modulo: 2 %}
|
||||||
|
{% if row_position == 0 %}<ul class="gallery-row">{% endif %}
|
||||||
|
<li class='lang-{% if post.lang %}{{ post.lang }}{% else %}en{% endif %}' style='--photo-ratio: {{ item | gallery_image_ratio }}'>
|
||||||
<article class='post-content'>
|
<article class='post-content'>
|
||||||
{% if post.anchor and forloop.first %}
|
{% if post.anchor and forloop.first %}
|
||||||
<span id='{{ post.title }}'></span>
|
<span id='{{ post.title }}'></span>
|
||||||
@@ -43,9 +46,13 @@ wide_gallery: true
|
|||||||
{{ item }}
|
{{ item }}
|
||||||
</article>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
|
{% assign gallery_index = gallery_index | plus: 1 %}
|
||||||
|
{% assign row_position = gallery_index | modulo: 2 %}
|
||||||
|
{% if row_position == 0 %}</ul>{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
{% if row_position == 1 %}</ul>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="rss-subscribe">subscribe <a href="{{ "/feed.xml" | prepend: site.baseurl }}">via rss</a></p>
|
<p class="rss-subscribe">subscribe <a href="{{ "/feed.xml" | prepend: site.baseurl }}">via rss</a></p>
|
||||||
|
|
||||||
|
|||||||
@@ -27,4 +27,10 @@ single = '<p><img src="/img/arts/uploads/batch/00.jpg"></p>'
|
|||||||
raise 'Single photo changed' unless renderer.gallery_items(single) == [single]
|
raise 'Single photo changed' unless renderer.gallery_items(single) == [single]
|
||||||
legacy = '<p><img src="/img/old.jpg"></p><p>Existing artwork content</p>'
|
legacy = '<p><img src="/img/old.jpg"></p><p>Existing artwork content</p>'
|
||||||
raise 'Legacy artwork changed' unless renderer.gallery_items(legacy) == [legacy]
|
raise 'Legacy artwork changed' unless renderer.gallery_items(legacy) == [legacy]
|
||||||
|
|
||||||
|
portrait = '<img src="/img/arts/dresden-prague-iran-2024/Prague_000512360030.jpg">'
|
||||||
|
landscape = '<img src="/img/arts/uploads/5e085e7d10edc54172e7b5c47d22fe532edec19c879a8d566667d3c3c1bb3d96/00.jpg">'
|
||||||
|
raise 'Portrait ratio incorrect' unless renderer.gallery_image_ratio(portrait) < 1
|
||||||
|
raise 'Landscape ratio incorrect' unless renderer.gallery_image_ratio(landscape) > 1
|
||||||
|
raise 'Video fallback ratio incorrect' unless renderer.gallery_image_ratio('<video></video>') == 1.5
|
||||||
puts 'Gallery item regression checks passed'
|
puts 'Gallery item regression checks passed'
|
||||||
|
|||||||
Reference in New Issue
Block a user