distorted-jekyll 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/distorted-jekyll.rb +0 -4
- data/lib/distorted-jekyll/{template/13th-style.css → 13th-style.css} +0 -0
- data/lib/distorted-jekyll/13th-style.rb +1 -1
- data/lib/distorted-jekyll/_config_default.yml +3 -21
- data/lib/distorted-jekyll/invoker.rb +194 -219
- data/lib/distorted-jekyll/liquid_liquid.rb +255 -0
- data/lib/distorted-jekyll/liquid_liquid/anchor.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/anchor_inline.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/embed.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/img.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/object.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/picture.liquid +15 -0
- data/lib/distorted-jekyll/liquid_liquid/picture.rb +48 -0
- data/lib/distorted-jekyll/liquid_liquid/picture_source.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/root.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/video.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/video_source.liquid +1 -0
- data/lib/distorted-jekyll/md_injection.rb +30 -25
- data/lib/distorted-jekyll/media_molecule.rb +20 -0
- data/lib/distorted-jekyll/media_molecule/font.rb +21 -0
- data/lib/distorted-jekyll/media_molecule/image.rb +15 -0
- data/lib/distorted-jekyll/media_molecule/never_let_you_down.rb +28 -0
- data/lib/distorted-jekyll/media_molecule/pdf.rb +108 -0
- data/lib/distorted-jekyll/media_molecule/svg.rb +20 -0
- data/lib/distorted-jekyll/media_molecule/text.rb +23 -0
- data/lib/distorted-jekyll/media_molecule/video.rb +45 -0
- data/lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb +68 -1
- data/lib/distorted-jekyll/static_state.rb +19 -60
- data/lib/distorted-jekyll/the_setting_sun.rb +179 -0
- metadata +26 -21
- data/lib/distorted-jekyll/floor.rb +0 -266
- data/lib/distorted-jekyll/molecule/font.rb +0 -62
- data/lib/distorted-jekyll/molecule/image.rb +0 -94
- data/lib/distorted-jekyll/molecule/lastresort.rb +0 -51
- data/lib/distorted-jekyll/molecule/pdf.rb +0 -79
- data/lib/distorted-jekyll/molecule/svg.rb +0 -47
- data/lib/distorted-jekyll/molecule/text.rb +0 -62
- data/lib/distorted-jekyll/molecule/video.rb +0 -85
- data/lib/distorted-jekyll/template/error_code.liquid +0 -3
- data/lib/distorted-jekyll/template/font.liquid +0 -32
- data/lib/distorted-jekyll/template/image.liquid +0 -32
- data/lib/distorted-jekyll/template/lastresort.liquid +0 -20
- data/lib/distorted-jekyll/template/pdf.liquid +0 -14
- data/lib/distorted-jekyll/template/svg.liquid +0 -32
- data/lib/distorted-jekyll/template/text.liquid +0 -32
- data/lib/distorted-jekyll/template/video.liquid +0 -11
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'liquid/drop'
|
2
|
+
require 'liquid/template'
|
3
|
+
|
4
|
+
|
5
|
+
module Cooltrainer
|
6
|
+
|
7
|
+
# DistorteD Liquid::Template-caching Hash.
|
8
|
+
# Jekyll has its own Liquid cache enabled by default as of 4.0,
|
9
|
+
# but the Jekyll::LiquidRenderer::File has a different interface
|
10
|
+
# than Liquid::Template (e.g. no :assigns accessor).
|
11
|
+
@@watering rescue begin
|
12
|
+
@@watering = Hash[]
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# Entry-point for MediaMolecules to render HTML (and maybe eventually other formats!).
|
17
|
+
#
|
18
|
+
# Liquid is arguably a poor choice for this use case since it is designed
|
19
|
+
# to handle arbitrary user-supplied templates in a safe way,
|
20
|
+
# versus e.g. ERB which allows in-template execution of arbitrary Ruby code,
|
21
|
+
# but our templates are bundled here in the Gem (ostensibly) should be trustworthy.
|
22
|
+
#
|
23
|
+
# I considered using Nokogiri's DocumentFragment Builder instead:
|
24
|
+
# fragment = Nokogiri::HTML::DocumentFragment.parse(''.freeze)
|
25
|
+
# Nokogiri::HTML::Builder.with(fragment) do |doc|
|
26
|
+
# doc.picture {
|
27
|
+
# changes.each { |change| doc.source(:srcset=change.name) }
|
28
|
+
# }
|
29
|
+
# end
|
30
|
+
# fragment.to_html
|
31
|
+
#
|
32
|
+
# But the way DistorteD works (with MIME::Type#distorted_template_method)
|
33
|
+
# means we would need a way to collate child elements anyway, since each call
|
34
|
+
# to a :distorted_template_method should generate only one variation,
|
35
|
+
# meaning we'd end up with a bunch of <source> tag Strings but would
|
36
|
+
# still need to collect them under a parent <picture> with a sibling <img>.
|
37
|
+
#
|
38
|
+
# Liquid is already heavily used by Jekyll, and of course DistorteD-Jekyll
|
39
|
+
# itself is a Liquid::Tag, so we may as well use it.
|
40
|
+
# Nokogiri, on the other hand, is not an explicit dependency of Jekyll.
|
41
|
+
# It will most likely be available, and DD itself pulls it in via SVGO
|
42
|
+
# and others, but Liquid will also allow us the flexibility to render
|
43
|
+
# formats other than just HTML/XML, e.g. BBCode or even Markdown.
|
44
|
+
#
|
45
|
+
# I might revisit this design decision once I experience working with more
|
46
|
+
# media formats and in more page contexts :)
|
47
|
+
ElementalCreation = Struct.new(:element, :name, :parents, :children, :template, :assigns, :change, keyword_init: true) do
|
48
|
+
|
49
|
+
def initialize(element, change = nil, parents: nil, children: nil, template: nil, assigns: Hash[])
|
50
|
+
super(
|
51
|
+
# Symbol Template name, corresponding to a Liquid template file name,
|
52
|
+
# not necessarily corresponding to the exact name of the element we return.
|
53
|
+
element: element,
|
54
|
+
change: change,
|
55
|
+
name: change&.name || element,
|
56
|
+
# Symbol name or Enumerable[Symbol] names of parent Element(s) we should be under,
|
57
|
+
# in order from outermost to innermost nesting.
|
58
|
+
# This is used to collate Change templates under a required parent Element,
|
59
|
+
# e.g. <source> tags must be under a <picture> or a <video> tag.
|
60
|
+
parents: parents.nil? ? nil : (parents.is_a?(Enumerable) ? parents.to_a : Array[parents]),
|
61
|
+
# Set up a Hash to store any children of this element in an Array-like way
|
62
|
+
# using auto-incrementing Integer keys. Its `&default_proc` responds to Symbol Element names,
|
63
|
+
# uses :detect to search for and return the first instance of that Symbol iff one exists,
|
64
|
+
# and if not it instantiates an Element Struct for that symbol and stores it.
|
65
|
+
children: Hash.new { |children_hash, element| children_hash.values.detect(
|
66
|
+
# Enumerable#detect will call this `ifnone` Proc if :detect's block returns nil.
|
67
|
+
# This Proc will instantiate a new Element with a copy of our :change, then store and return it.
|
68
|
+
# Can remove the destructured empty Hash once Ruby 2.7 is gone.
|
69
|
+
->{ children_hash.store(children_hash.length, self.class.new(element, change, **{}))}
|
70
|
+
) { |child| child.element == element } if element.is_a?(Symbol) }.tap { |children_hash|
|
71
|
+
# Merge the children-given-to-initialize() into our Hash.
|
72
|
+
case children
|
73
|
+
when Array then children.map.with_index.with_object(children_hash) { |(v, i), h| h.store(i, v) }
|
74
|
+
when Hash then ch.merge(children)
|
75
|
+
end
|
76
|
+
},
|
77
|
+
# Our associated Liquid::Template, based on our element name.
|
78
|
+
template: template || self.WATERING(element),
|
79
|
+
# Container of variables we want to render in Liquid besides what's covered by the basics.
|
80
|
+
assigns: assigns,
|
81
|
+
)
|
82
|
+
|
83
|
+
# Go ahead and define accessors for any assigns we already know about.
|
84
|
+
# Others are supported via the :method_missing below.
|
85
|
+
assigns.each_key { |assign|
|
86
|
+
define_method(assign) do; self[:assigns].fetch(assign, nil); end
|
87
|
+
define_method("#{assign.to_s}=") do |value|; self[:assigns].store(assign, value); end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
# Hash[String] => Integer containing weights for MIME::Type#sub_type sorting weights.
|
92
|
+
# Weights are assigned in auto-incrementing Array order and will be called from :<=>.
|
93
|
+
# Sub-types near the beginning will sort before sub-types near the bottom.
|
94
|
+
# This is important for things like <picture> tags where the first supported <source> child
|
95
|
+
# encountered is the one that will be used, so we want vector types (SVG) to come first,
|
96
|
+
# then modern annoying formats like AVIF/WebP, then old standby types like PNG.
|
97
|
+
# TODO: Sort things other than Images
|
98
|
+
SORT_WEIGHTS = [
|
99
|
+
'svg+xml'.freeze,
|
100
|
+
'avif'.freeze,
|
101
|
+
'webp'.freeze,
|
102
|
+
'png'.freeze,
|
103
|
+
'jpeg'.freeze,
|
104
|
+
'gif'.freeze,
|
105
|
+
].map.with_index.to_h
|
106
|
+
# Return a static 0 weight for unknown sub_types.
|
107
|
+
SORT_WEIGHTS.default_proc = Proc.new { 0 }
|
108
|
+
|
109
|
+
# Elements should sort themselves under their parent when rendering.
|
110
|
+
# Use predefined weights, e.g.:
|
111
|
+
# irb> SORT_WEIGHTS['avif']
|
112
|
+
# => 1
|
113
|
+
# irb> SORT_WEIGHTS['png']
|
114
|
+
# => 3
|
115
|
+
# irb> SORT_WEIGHTS['avif'] <=> SORT_WEIGHTS['png']
|
116
|
+
# => -1
|
117
|
+
def <=>(otra)
|
118
|
+
SORT_WEIGHTS[self.change&.type&.sub_type] <=> SORT_WEIGHTS[otra&.change&.type&.sub_type]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Take a child Element and store it.
|
122
|
+
# If it requests no parents, store it with us.
|
123
|
+
# If it requests a parent, forward it there to the same method.
|
124
|
+
def mad_child(moon_child)
|
125
|
+
parent = moon_child.parents&.shift
|
126
|
+
if parent.nil? # When shifting/popping an empty :parents Array
|
127
|
+
# Store the child with an incrementing Integer key as if
|
128
|
+
# self[Lchildren] were an Array
|
129
|
+
self[:children].store(self[:children].length, moon_child)
|
130
|
+
else
|
131
|
+
# Forward the child to the next level of ElementalCreation.
|
132
|
+
# The Struct will be instantiated by the :children Hash's &default_proc
|
133
|
+
self[:children][parent].mad_child(moon_child)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Generic Liquid template loader
|
138
|
+
# Jekyll's site-wide Liquid renderer caches in 4.0+ and is usable via
|
139
|
+
# `site.liquid_renderer.file(cache_key).parse(liquid_string)`,
|
140
|
+
# but the Jekyll::LiquidRenderer::File you get back doesn't let us
|
141
|
+
# play with the `assigns` directly, so I stopped using the site renderer
|
142
|
+
# in favor of our own cache.
|
143
|
+
def WATERING(template_filename)
|
144
|
+
begin
|
145
|
+
# Memoize parsed Templates to this Struct subclass.
|
146
|
+
@@watering[template_filename] ||= begin
|
147
|
+
template = File.join(__dir__, 'liquid_liquid'.freeze, "#{template_filename}.liquid".freeze)
|
148
|
+
Jekyll.logger.debug('DistorteD::WATERING', template)
|
149
|
+
Liquid::Template.parse(File.read(template))
|
150
|
+
end
|
151
|
+
rescue Liquid::SyntaxError => l
|
152
|
+
# This shouldn't ever happen unless a new version of Liquid
|
153
|
+
# breaks syntax compatibility with our templates somehow.
|
154
|
+
l.message
|
155
|
+
end
|
156
|
+
end #WATERING
|
157
|
+
|
158
|
+
# Returns the rendered String contents of this and all child Elements.
|
159
|
+
def render
|
160
|
+
self[:template].render(
|
161
|
+
self[:assigns].merge(Hash[
|
162
|
+
# Most Elements will be associated with a Change Struct
|
163
|
+
# encapsulating a single wanted variation on the source file.
|
164
|
+
:change => self[:change].nil? ? nil : Cooltrainer::ChangeDrop.new(self[:change]),
|
165
|
+
# Create Elements for every wanted child if they were only given
|
166
|
+
# to us as Symbols, then render them all to Strings to include
|
167
|
+
# in our own output.
|
168
|
+
:children => self[:children]&.values.map { |child|
|
169
|
+
child.is_a?(Symbol) ? self.class.new(child, self[:change], parents: self[:name], assigns: self[:assigns]) : child
|
170
|
+
}.sort.map(&:render), # :sort will use ElementalCreation's :<=> method.
|
171
|
+
]).transform_keys(&:to_s).transform_values { |value|
|
172
|
+
# Liquid wants String keys and values, not Symbols.
|
173
|
+
value.is_a?(Symbol) ? value.to_s : value
|
174
|
+
}
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Calling :flatten on an Array[self] will fail unless we override :to_ary to stop
|
179
|
+
# implicitly looking like an Array. Or we could implement :flatten, but this probably
|
180
|
+
# fixes other situations too.
|
181
|
+
# https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
|
182
|
+
def to_ary; nil; end
|
183
|
+
|
184
|
+
# Expose Liquid's 'assigns' accessors for any keys we weren't given upfront.
|
185
|
+
def method_missing(meth, *a)
|
186
|
+
# Grab the key from the method name, minus the trailing '=' for writers.
|
187
|
+
meth.to_s.end_with?('='.freeze) ? self[:assigns].store(meth.to_s.chomp('='.freeze).to_sym, a.first) : self[:assigns].fetch(meth, nil)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Destination filename, or the element name.
|
191
|
+
def to_s; (self[:name] || self[:element]).to_s; end
|
192
|
+
|
193
|
+
# e.g. <ElementalCreation::source name=IIDX-turntable-full.svg, parents=[:anchor, :picture]>
|
194
|
+
def inspect; "<#{self.class.name.split('::'.freeze).last}::#{self[:element]} #{
|
195
|
+
[:name, :parents, :children].map { |k| (self[k].nil? || self[k]&.empty?) ? nil : " #{k.to_s}=#{self[k]}" }.compact.join(','.freeze)
|
196
|
+
}>"; end
|
197
|
+
|
198
|
+
end # Struct ElementalCreation
|
199
|
+
|
200
|
+
|
201
|
+
# Wrap a Change in a Drop that emits Element attribute keys as well as the value.
|
202
|
+
# An underlying ChangeDrop will instantiate us as an instance variable
|
203
|
+
# and forward its Change to us, then return that instance from its own :attr method.
|
204
|
+
#
|
205
|
+
# Our templates can use this to avoid emitting empty attributes
|
206
|
+
# for corresponding empty values by calling {{ change.attr.whatever }}
|
207
|
+
# instead of invoking the regular ChangeDrop via {{ change.whatever }}.
|
208
|
+
#
|
209
|
+
# For example, if a <source>'s Change defines a media-query,
|
210
|
+
# {{ change.media }} will emit the plain value (e.g. `min-width: 800px`)
|
211
|
+
# and would typically be used in a template inside an explicit attribute key,
|
212
|
+
# e.g. `<source … media="{{ change.media }}"\>`.
|
213
|
+
#
|
214
|
+
# A template could instead call this drop via e.g. <source … {{ attr_media }}\>`
|
215
|
+
# to emit the same thing if a media-query is set but emit nothing if one isn't!
|
216
|
+
class ChangeAttrDrop < Liquid::Drop
|
217
|
+
def initialize(change)
|
218
|
+
@change = change
|
219
|
+
end
|
220
|
+
def liquid_method_missing(method)
|
221
|
+
# The underlying ChangeDrop is what responds to :attr, so we only
|
222
|
+
# need to respond to the Change keys in the same way ChangeDrop does.
|
223
|
+
value = @change&.send(method.to_sym)
|
224
|
+
# Return an empty String if there is no value, otherwise return `attr="value"`.
|
225
|
+
# Intentional leading-space in output so Liquid tags can abut in templates.
|
226
|
+
value.nil? ? ''.freeze : " #{method.to_str}=\"#{value.to_s}\""
|
227
|
+
end
|
228
|
+
end # Struct ChangeAttrDrop
|
229
|
+
|
230
|
+
|
231
|
+
# Wrap a Change in a Drop that our Element Liquid::Templates can use
|
232
|
+
# to emit either values-alone or keys-and-values for any attribute
|
233
|
+
# of any one variation ot a media file.
|
234
|
+
class ChangeDrop < Liquid::Drop
|
235
|
+
def initialize(change)
|
236
|
+
@change = change
|
237
|
+
end
|
238
|
+
def initialism
|
239
|
+
@initialism ||= @change.type&.sub_type&.to_s.split(MIME::Type::SUB_TYPE_SEPARATORS)[0].upcase
|
240
|
+
end
|
241
|
+
def attr
|
242
|
+
# Use a ChangeAttrDrop to avoid emitting keys for empty values.
|
243
|
+
# It will respond to its own Change-key method just like we do.
|
244
|
+
@attr ||= Cooltrainer::ChangeAttrDrop.new(@change)
|
245
|
+
end
|
246
|
+
def liquid_method_missing(method)
|
247
|
+
# Liquid will send us String keys only, so translate them
|
248
|
+
# to Symbols when looking up in the Change Struct.
|
249
|
+
# Don't explicitly call :to_s before returning,
|
250
|
+
# because we might be returning an Array.
|
251
|
+
@change&.send(method.to_sym) || super
|
252
|
+
end
|
253
|
+
end # Struct ChangeDrop
|
254
|
+
|
255
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<a href="{{ change.dir }}{{ change.name }}{{ fragment }}">{{ change.name }} [{{ change.initialism }}]</a>
|
@@ -0,0 +1 @@
|
|
1
|
+
<embed src="{{ change.dir }}{{ change.name }}{{ fragment }}"{{ change.attr.type }}{{ change.attr.width }}{{ change.attr.height }}/>
|
@@ -0,0 +1 @@
|
|
1
|
+
<img src="{{ change.dir }}{{ change.name }}"{{ change.attr.alt }}{{ change.attr.title }}{{ change.attr.loading }}/>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{% assign img_child = nil -%}
|
2
|
+
<picture>
|
3
|
+
{% for child in children %}
|
4
|
+
{%- assign test_img = child | slice: 1, 3 -%}
|
5
|
+
{%- if test_img == 'img' -%}
|
6
|
+
{%- assign img_child = child -%}
|
7
|
+
{%- else -%}
|
8
|
+
{{ child }}
|
9
|
+
{%- endif -%}
|
10
|
+
{%- endfor -%}
|
11
|
+
{%- unless img_child -%}
|
12
|
+
{%- assign img_child = '<img/>' -%}
|
13
|
+
{%- endunless -%}
|
14
|
+
{{ img_child }}
|
15
|
+
</picture>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'distorted-jekyll/liquid_liquid'
|
2
|
+
require 'distorted/modular_technology/vips/save'
|
3
|
+
require 'distorted/media_molecule'
|
4
|
+
|
5
|
+
module Jekyll; end
|
6
|
+
module Jekyll::DistorteD; end
|
7
|
+
module Jekyll::DistorteD::LiquidLiquid; end
|
8
|
+
module Jekyll::DistorteD::LiquidLiquid::Picture
|
9
|
+
|
10
|
+
|
11
|
+
# Returns a CSS media query String for a full-size Image outer_limit allowing btowsers,
|
12
|
+
# to properly consider its <source> alongside any generated resized versions of the same Image.
|
13
|
+
# https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries
|
14
|
+
def self.full_size_media_query(change, vips_image)
|
15
|
+
# This is kinda hacky, but use the Setting loader to look for `:outer_limits`
|
16
|
+
# of this Type that are larger than this `vips_image` since we won't have visibility of
|
17
|
+
# other `changes` from this Class instance method.
|
18
|
+
larger_than_us = Jekyll::DistorteD::the_setting_sun(:outer_limits, *(change.type.settings_paths))
|
19
|
+
.map { |l| l.fetch(:width, nil)} # Look for a :width key in every outer limit.
|
20
|
+
.compact # Discard any limits that don't define :width.
|
21
|
+
.keep_if { |w| w > vips_image.width } # Discard any limits whose :width is smaller than us.
|
22
|
+
# Add an additional `max` constraint to the media query if this `vips_image`
|
23
|
+
# is not the largest one in the <picture>.
|
24
|
+
# This effectively makes this <source> useless since so few user-agents will ever
|
25
|
+
# have a viewport the exact pixel width of this image, but for our use case
|
26
|
+
# it's better to have a useless media query than no media query since it will
|
27
|
+
# make browsers pick a better variation than this one.
|
28
|
+
return "(min-width: #{vips_image.width}px)#{" and (max-width: #{vips_image.width}px)" unless larger_than_us.empty?}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an anonymous method that generates a <source> tag for Image output Types.
|
32
|
+
def self.render_picture_source
|
33
|
+
@@render_picture_source = lambda { |change|
|
34
|
+
# Fill in missing CSS media queries for any original-size (tag == null) Change that lacks one.
|
35
|
+
if change.width.nil? and not change.type.sub_type.include?('svg'.freeze)
|
36
|
+
change.width = to_vips_image.width
|
37
|
+
end
|
38
|
+
Cooltrainer::ElementalCreation.new(:picture_source, change, parents: Array[:anchor, :picture])
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Lots of MediaMolecules will want to render image representations of various media_types,
|
43
|
+
# so define a render method once for every Vips-supported Type that can be included/shared.
|
44
|
+
Cooltrainer::DistorteD::IMPLANTATION(:OUTER_LIMITS, Cooltrainer::DistorteD::Technology::Vips::Save).each_key { |type|
|
45
|
+
define_method(type.distorted_template_method, Jekyll::DistorteD::LiquidLiquid::Picture::render_picture_source)
|
46
|
+
}
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<source srcset="{{ change.dir }}{{ change.basename }}{{ change.extname }}{% if change.width %} {{ change.width }}w{% endif %}{% for break_width in change.breaks %}, {{ change.dir }}{{ change.basename }}-{{ break_width }}{{ change.extname }} {{ break_width }}w{% endfor %}"{{ change.attr.type }}{{ change.attr.media }}/>
|
@@ -0,0 +1 @@
|
|
1
|
+
<source src="{{ change.dir }}{{ change.name }}"{{ change.attr.type }}{{ change.attr.media }}/>
|
@@ -215,13 +215,13 @@ module Kramdown
|
|
215
215
|
matched
|
216
216
|
end
|
217
217
|
|
218
|
-
def
|
219
|
-
matched =
|
218
|
+
def flatten_attributes(el, type = :img)
|
219
|
+
matched = {}
|
220
220
|
|
221
221
|
if el.is_a? Enumerable
|
222
222
|
# Support an Array of elements...
|
223
223
|
el.each {
|
224
|
-
|child| matched.
|
224
|
+
|child| matched.merge!(flatten_attributes(child, type))
|
225
225
|
}
|
226
226
|
else
|
227
227
|
# ...or a tree of elements.
|
@@ -231,32 +231,36 @@ module Kramdown
|
|
231
231
|
# will be duplicated in `class` or some other `:attr` anyway.
|
232
232
|
# Those things should be added here if this is ever used in a
|
233
233
|
# more generic context than just parsing the image tags.
|
234
|
-
matched
|
234
|
+
matched.merge!(el.attr) unless el.attr.empty?
|
235
235
|
end
|
236
236
|
unless el.children.empty?
|
237
237
|
# Keep looking even if this element was one we are looking for.
|
238
238
|
el.children.each {
|
239
|
-
|child| matched.
|
239
|
+
|child| matched.merge!(flatten_attributes(child, type))
|
240
240
|
}
|
241
241
|
end
|
242
242
|
end
|
243
243
|
matched
|
244
244
|
end
|
245
245
|
|
246
|
-
#
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
246
|
+
# Geenerates a DistorteD Liquid tag String given a Hash of element attributes.
|
247
|
+
# Examples:
|
248
|
+
# {% distorted rpg-ra11.nfo alt="HellMarch INTENSIFIES" title="C&C RA2:YR NoCD NFO" encoding="IBM437" crop="none" %}
|
249
|
+
# {% distorted DistorteD.png alt="DistorteD logo" title="This is so cool" crop="none" loading="lazy" %}
|
250
|
+
# The :additional_defaults Hash contains attribute values to set
|
251
|
+
# iff those attributes have no user-given value.
|
252
|
+
def distorted(attributes, additional_defaults: {})
|
253
|
+
"{% distorted #{additional_defaults.transform_keys{ |k|
|
254
|
+
# We will end up with a Hash of String-keys and String-values,
|
255
|
+
# so make sure override-defaults are String-keyed too.
|
256
|
+
k.to_s
|
257
|
+
}.merge(attributes).select{ |k, v|
|
258
|
+
# Filter out empty values, e.g. from an unfilled title/alt field
|
259
|
+
# in a Markdown image.
|
260
|
+
not v.empty?
|
261
|
+
}.map{ |k, v|
|
262
|
+
k.to_s + '="'.freeze + v.to_s + '"'.freeze
|
263
|
+
}.join(' '.freeze)} %}"
|
260
264
|
end
|
261
265
|
|
262
266
|
# Kramdown entry point
|
@@ -278,11 +282,10 @@ module Kramdown
|
|
278
282
|
when 0..1
|
279
283
|
# Render one (1) image/video/whatever. This behavior is the same
|
280
284
|
# regardless if the image is in a single-item list or just by itself.
|
281
|
-
distorted(
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
}))
|
285
|
+
distorted(
|
286
|
+
flatten_attributes(imgs.first),
|
287
|
+
additional_defaults: {:crop => 'none'.freeze},
|
288
|
+
)
|
286
289
|
else
|
287
290
|
# Render a conceptual group (DD::BLOCKS)
|
288
291
|
|
@@ -296,7 +299,9 @@ module Kramdown
|
|
296
299
|
raise "MD->img regex returned an unequal number of listed and unlisted tags."
|
297
300
|
end
|
298
301
|
|
299
|
-
"{% distort -%}\n#{list_imgs.map{|img|
|
302
|
+
"{% distort -%}\n#{list_imgs.map{ |img|
|
303
|
+
distorted(flatten_attributes(img))
|
304
|
+
}.join("\n")}\n{% enddistort %}"
|
300
305
|
end
|
301
306
|
end
|
302
307
|
|