distorted-jekyll 0.6.0 → 0.7.0
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 +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
|
|