jekyll_picture_tag 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +24 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/_config.yml +4 -0
- data/examples/_data/picture.yml +85 -0
- data/examples/post.md +18 -0
- data/jekyll_picture_tag.gemspec +39 -0
- data/lib/jekyll_picture_tag.rb +67 -0
- data/lib/jekyll_picture_tag/defaults/global.yml +10 -0
- data/lib/jekyll_picture_tag/defaults/presets.yml +7 -0
- data/lib/jekyll_picture_tag/generated_image.rb +66 -0
- data/lib/jekyll_picture_tag/instructions.rb +105 -0
- data/lib/jekyll_picture_tag/instructions/configuration.rb +82 -0
- data/lib/jekyll_picture_tag/instructions/html_attributes.rb +59 -0
- data/lib/jekyll_picture_tag/instructions/preset.rb +61 -0
- data/lib/jekyll_picture_tag/instructions/tag_parser.rb +48 -0
- data/lib/jekyll_picture_tag/output_formats.rb +9 -0
- data/lib/jekyll_picture_tag/output_formats/auto.rb +15 -0
- data/lib/jekyll_picture_tag/output_formats/basics.rb +105 -0
- data/lib/jekyll_picture_tag/output_formats/data_attributes.rb +44 -0
- data/lib/jekyll_picture_tag/output_formats/data_auto.rb +14 -0
- data/lib/jekyll_picture_tag/output_formats/data_img.rb +8 -0
- data/lib/jekyll_picture_tag/output_formats/data_picture.rb +8 -0
- data/lib/jekyll_picture_tag/output_formats/direct_url.rb +13 -0
- data/lib/jekyll_picture_tag/output_formats/img.rb +24 -0
- data/lib/jekyll_picture_tag/output_formats/picture.rb +66 -0
- data/lib/jekyll_picture_tag/source_image.rb +62 -0
- data/lib/jekyll_picture_tag/srcsets.rb +3 -0
- data/lib/jekyll_picture_tag/srcsets/basics.rb +88 -0
- data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +32 -0
- data/lib/jekyll_picture_tag/srcsets/width.rb +45 -0
- data/lib/jekyll_picture_tag/utils.rb +67 -0
- data/lib/jekyll_picture_tag/version.rb +3 -0
- data/migration.md +178 -0
- data/readme.md +787 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b7fc61d571c945d859aac14c2a85a9b80752c3642a314456c89516ae7c9632d4
|
4
|
+
data.tar.gz: 27f70f0b0a73101606ef098a4aceaf8183356607b9d3145c5bb514c9ea9c92cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b008e590e962f46f83de4506b9c0a1da77f887b89987442ef9af9756987dd1bbaaa9964703ed2da3245496c02ca0fa274014a788bfd623aa719e6d098a665d4b
|
7
|
+
data.tar.gz: 795bf04f0b5aa155f6386bb41f33d12643699ff0065cc88f80273589179234d1ba26b801aa12da6028b03283365309ea4449038386820eaa3b1f0a634d023dac
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.3
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2013, Robert Wierzbowski
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the <organization> nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'jekyll-picture-tag'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Example picture presets:
|
2
|
+
# TODO: change to EMs, because safari sucks.
|
3
|
+
|
4
|
+
# Media presets are used in several places:
|
5
|
+
# - To specify alternate source images (for art direction)
|
6
|
+
# - To build the 'sizes' attribute
|
7
|
+
# - When given alternate source images, specify which sizes to generate.
|
8
|
+
media_presets:
|
9
|
+
wide_desktop: 'min-width: 1801px'
|
10
|
+
desktop: 'max-width: 1800px'
|
11
|
+
wide_tablet: 'max-width: 1200px'
|
12
|
+
tablet: 'max-width: 900px'
|
13
|
+
mobile: 'max-width: 600px'
|
14
|
+
|
15
|
+
# Markup presets allow you to group settings together, and select one of them by name in your jekyll
|
16
|
+
# tag. All settings are optional.
|
17
|
+
markup_presets:
|
18
|
+
default:
|
19
|
+
|
20
|
+
# Optionally specify a markup type. Your current options are 'picture', 'img', or 'auto'
|
21
|
+
# (default).
|
22
|
+
markup: auto
|
23
|
+
|
24
|
+
# Must be an array, in order of decreasing preference. Defaults to just 'original'.
|
25
|
+
formats: [webp, original]
|
26
|
+
|
27
|
+
# Must be an array: which image sizes (width in pixels) to generate (unless directed otherwise
|
28
|
+
# below). If not specified, will use sensible default values.
|
29
|
+
widths: [200, 400, 800, 1600]
|
30
|
+
|
31
|
+
# Alternate source images (for art direction) are associated with media query presets. Here, you
|
32
|
+
# can optionally specify a different set of sizes to generate for those alternate source images.
|
33
|
+
# This is how you avoid generating a 1800 pixel wide image that's only shown on narrow screens.
|
34
|
+
# Must be arrays.
|
35
|
+
media_widths:
|
36
|
+
mobile: [200, 400, 600]
|
37
|
+
tablet: [400, 600, 800]
|
38
|
+
|
39
|
+
# Specifies an optional, unconditional size attribute. Can be given alone, or if specified in
|
40
|
+
# combination with 'sizes' below, will be given last (when no media queries apply).
|
41
|
+
size: 800px
|
42
|
+
|
43
|
+
# For building the 'sizes' attribute on img and source tags. specifies how wide the image will
|
44
|
+
# be when a given media query is true. Note that every source in a given <picture> tag will have
|
45
|
+
# the same associated sizes attribute.
|
46
|
+
sizes:
|
47
|
+
mobile: 100vw
|
48
|
+
desktop: 60vw
|
49
|
+
|
50
|
+
# Specify the properties of the fallback image. If not specified, will return a 900 pixel
|
51
|
+
# wide image in the original format.
|
52
|
+
fallback_width: 800
|
53
|
+
fallback_format: original
|
54
|
+
|
55
|
+
# Attributes can be added to each HTML element, by type:
|
56
|
+
attributes:
|
57
|
+
picture: 'class="awesome" data-volume="11"'
|
58
|
+
img: 'class="some-other-class"'
|
59
|
+
|
60
|
+
# This is an example of how you would create a 'multiplier' based srcset; useful when an image
|
61
|
+
# will always be the same size on all screens, but you'd like to supply higher resolution images
|
62
|
+
# to devices with higher pixel ratios.
|
63
|
+
icon:
|
64
|
+
base_width: 20
|
65
|
+
pixel_ratios: [1, 1.5, 2]
|
66
|
+
fallback_width: 20
|
67
|
+
attributes:
|
68
|
+
img: 'class="icon"'
|
69
|
+
|
70
|
+
# Here's an example of how you'd configure jekyll-picture-tag to work with something like
|
71
|
+
# lazyload:
|
72
|
+
# https://github.com/verlok/lazyload
|
73
|
+
lazy:
|
74
|
+
markup: data_auto
|
75
|
+
formats: [webp, original]
|
76
|
+
widths: [200, 400, 600, 800]
|
77
|
+
noscript: true # Default: false
|
78
|
+
attributes:
|
79
|
+
img: class="lazy"
|
80
|
+
|
81
|
+
# This is an example of how you'd get generated image and a URL, and nothing else.
|
82
|
+
direct:
|
83
|
+
markup: direct_url
|
84
|
+
fallback_format: webp # Default original
|
85
|
+
fallback_width: 600 # Default 800
|
data/examples/post.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
layout: post
|
3
|
+
title: Tag examples
|
4
|
+
---
|
5
|
+
|
6
|
+
{% picture portrait.jpg --alt An unsual picture %}
|
7
|
+
|
8
|
+
What was the narrative that this representation was meant to embellish and complete? As I regarded
|
9
|
+
the work, I slowly sensed that the underlying tale was the picture itself. The painting wasn’t the
|
10
|
+
extension of a story at all, it was something in its own right.
|
11
|
+
|
12
|
+
### Variations
|
13
|
+
|
14
|
+
With a preset specified:
|
15
|
+
{% picture gallery portrait.jpg --alt An unsual picture --picture data-downloadable="true" %}
|
16
|
+
|
17
|
+
With an alternate source images:
|
18
|
+
{% picture half portrait.jpg tablet: dream-midrange.jpg desktop: dream-fullpage.jpg --alt An unsual picture --picture data-downloadable="true" %}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'jekyll_picture_tag/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'jekyll_picture_tag'
|
7
|
+
spec.version = PictureTag::VERSION
|
8
|
+
spec.authors = ['Robert Wierzbowski', 'Brendan Tobolaski',
|
9
|
+
'Robert Buchberger']
|
10
|
+
spec.email = ['hello@robwierzbowski.com', 'brendan@tobolaski.com',
|
11
|
+
'robert@buchberger.cc']
|
12
|
+
|
13
|
+
spec.summary = 'Easy responsive images for Jekyll.'
|
14
|
+
spec.description = <<-HEREDOC
|
15
|
+
Jekyll Picture Tag is a liquid tag that adds responsive images to your Jekyll static site. It follows the picture
|
16
|
+
element pattern, and polyfills older browsers with Picturefill. Jekyll Picture Tag automatically creates resized
|
17
|
+
source images, is fully configurable, and covers all use cases — including art direction and resolution switching —
|
18
|
+
with a little YAML configuration and a simple template tag.
|
19
|
+
HEREDOC
|
20
|
+
spec.homepage = 'https://github.com/robwierzbowski/jekyll-picture-tag'
|
21
|
+
spec.license = 'BSD-3-Clause'
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
f.match(%r{^(test|spec|features)/})
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.required_ruby_version = ['>= 2.5', '< 3']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
31
|
+
spec.add_development_dependency 'pry'
|
32
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
33
|
+
|
34
|
+
spec.add_dependency 'fastimage', '~> 2'
|
35
|
+
spec.add_dependency 'mini_magick', '~> 4'
|
36
|
+
spec.add_dependency 'objective_elements', '~> 1.1'
|
37
|
+
|
38
|
+
spec.add_runtime_dependency 'jekyll', '< 4'
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'objective_elements'
|
2
|
+
|
3
|
+
require_relative 'jekyll_picture_tag/generated_image'
|
4
|
+
require_relative 'jekyll_picture_tag/source_image'
|
5
|
+
require_relative 'jekyll_picture_tag/instructions'
|
6
|
+
require_relative 'jekyll_picture_tag/output_formats'
|
7
|
+
require_relative 'jekyll_picture_tag/srcsets'
|
8
|
+
require_relative 'jekyll_picture_tag/utils'
|
9
|
+
require_relative 'jekyll_picture_tag/version'
|
10
|
+
|
11
|
+
module PictureTag
|
12
|
+
ROOT_PATH = __dir__
|
13
|
+
# Title: Jekyll Picture Tag
|
14
|
+
# Authors: Rob Wierzbowski : @robwierzbowski
|
15
|
+
# Justin Reese : @justinxreese
|
16
|
+
# Welch Canavan : @xiwcx
|
17
|
+
# Robert Buchberger : @celeritas_5k
|
18
|
+
#
|
19
|
+
# Description: Easy responsive images for Jekyll.
|
20
|
+
#
|
21
|
+
# Download: https://github.com/robwierzbowski/jekyll-picture-tag
|
22
|
+
# Documentation: https://github.com/robwierzbowski/jekyll-picture-tag/readme.md
|
23
|
+
# Issues: https://github.com/robwierzbowski/jekyll-picture-tag/issues
|
24
|
+
#
|
25
|
+
# Syntax:
|
26
|
+
# {% picture [preset] img.jpg [media_query: alt-img.jpg] [attr="value"] %}
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
# {% picture poster.jpg --alt The strange case of responsive images %}
|
30
|
+
# {% picture gallery poster.jpg source_small: poster_closeus.jpg
|
31
|
+
# alt="The strange case of responsive images" class="gal-img" %}
|
32
|
+
#
|
33
|
+
# See the documentation for full configuration and usage instructions.
|
34
|
+
class Picture < Liquid::Tag
|
35
|
+
def initialize(tag_name, raw_params, tokens)
|
36
|
+
@raw_params = raw_params
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(context)
|
41
|
+
# We can't initialize the tag until we have a context.
|
42
|
+
PictureTag.init(@raw_params, context)
|
43
|
+
|
44
|
+
# Return a string:
|
45
|
+
build_markup.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Super clever metaprogramming. It's the dynamic version of MyClass.new;
|
51
|
+
# instantiate the class defined in our config.
|
52
|
+
def build_markup
|
53
|
+
Object.const_get(output_class).new
|
54
|
+
end
|
55
|
+
|
56
|
+
# This is the class name of whichever output format we are selecting:
|
57
|
+
def output_class
|
58
|
+
'PictureTag::OutputFormats::' + titleize(PictureTag.preset['markup'])
|
59
|
+
end
|
60
|
+
|
61
|
+
def titleize(input)
|
62
|
+
input.split('_').map(&:capitalize).join
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Liquid::Template.register_tag('picture', PictureTag::Picture)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Generated Image
|
2
|
+
# Represents a generated source file.
|
3
|
+
class GeneratedImage
|
4
|
+
require 'mini_magick'
|
5
|
+
require 'fastimage'
|
6
|
+
|
7
|
+
def initialize(source_file:, width:, format:)
|
8
|
+
@source = source_file
|
9
|
+
@format = format
|
10
|
+
|
11
|
+
@size = build_size(width)
|
12
|
+
|
13
|
+
generate_image unless File.exist? absolute_filename
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
name = @source.base_name
|
18
|
+
name << "-#{@size[:width]}by#{@size[:height]}-"
|
19
|
+
name << @source.digest
|
20
|
+
name << '.' + @format
|
21
|
+
end
|
22
|
+
|
23
|
+
def absolute_filename
|
24
|
+
@absolute_filename ||= File.join(PictureTag.config.dest_dir, name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def width
|
28
|
+
@size[:width]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_size(width)
|
34
|
+
if width < @source.width
|
35
|
+
{
|
36
|
+
width: width,
|
37
|
+
height: (width / @source.aspect_ratio).round
|
38
|
+
}
|
39
|
+
else
|
40
|
+
@source.size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_image
|
45
|
+
puts 'Generating new image file: ' + name
|
46
|
+
image = MiniMagick::Image.open(@source.name)
|
47
|
+
# Scale and crop
|
48
|
+
image.combine_options do |i|
|
49
|
+
i.resize "#{@size[:width]}x#{@size[:height]}^"
|
50
|
+
i.auto_orient
|
51
|
+
i.strip
|
52
|
+
end
|
53
|
+
|
54
|
+
image.format @format
|
55
|
+
|
56
|
+
check_dest_dir
|
57
|
+
|
58
|
+
image.write absolute_filename
|
59
|
+
end
|
60
|
+
|
61
|
+
# Make sure destination directory exists
|
62
|
+
def check_dest_dir
|
63
|
+
dir = File.dirname absolute_filename
|
64
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative './instructions/configuration'
|
2
|
+
require_relative './instructions/html_attributes'
|
3
|
+
require_relative './instructions/preset'
|
4
|
+
require_relative './instructions/tag_parser'
|
5
|
+
|
6
|
+
# Allows us to access settings as methods on PictureTag itself.
|
7
|
+
module PictureTag
|
8
|
+
class << self
|
9
|
+
attr_reader :context, :config, :params, :preset, :html_attributes
|
10
|
+
|
11
|
+
def init(raw_tag_params, context)
|
12
|
+
@context = context
|
13
|
+
|
14
|
+
# Create global config (big picture). Config class loads jekyll
|
15
|
+
# data/config files, and the j-p-t defaults from included yml files.
|
16
|
+
@config = Instructions::Configuration.new
|
17
|
+
|
18
|
+
# Parse tag params. We must do this before setting the preset, because
|
19
|
+
# it's one of the params.
|
20
|
+
@params = Instructions::TagParser.new(raw_tag_params)
|
21
|
+
|
22
|
+
# Create preset. Takes preset name from params, merges associated settings
|
23
|
+
# with default values.
|
24
|
+
@preset = Instructions::Preset.new
|
25
|
+
|
26
|
+
# Create HTML attributes. Depends on both the preset and tag params, so
|
27
|
+
# we must do this after creating both.
|
28
|
+
@html_attributes = Instructions::HTMLAttributeSet.new(
|
29
|
+
@params.html_attributes_raw
|
30
|
+
)
|
31
|
+
|
32
|
+
# Keep our generated files
|
33
|
+
Utils.keep_files
|
34
|
+
end
|
35
|
+
|
36
|
+
# Global site data
|
37
|
+
def site
|
38
|
+
@context.registers[:site]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Page which tag is called from
|
42
|
+
def page
|
43
|
+
@context.registers[:page]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Media query presets. It's really just a hash, and there are no default
|
47
|
+
# values, so extracting it to its own class is overkill.
|
48
|
+
def media_presets
|
49
|
+
site.data.dig('picture', 'media_presets') || {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# The rest of the application doesn't care where the instruction logic
|
53
|
+
# resides. For example, I don't want to use PictureTag.config.source_dir and
|
54
|
+
# PictureTag.params.source_images, I just want to use PictureTag.source_dir
|
55
|
+
# and PictureTag.source_images. The following method definitions accomplish
|
56
|
+
# that.
|
57
|
+
|
58
|
+
# At first I thought I'd do some sweet dynamic metaprogramming here, but it
|
59
|
+
# ended up more complicated and clever than convenient. This way is not
|
60
|
+
# strictly DRY, but it's understandable and readable.
|
61
|
+
|
62
|
+
# Config Forwarding
|
63
|
+
def source_dir
|
64
|
+
@config.source_dir
|
65
|
+
end
|
66
|
+
|
67
|
+
def dest_dir
|
68
|
+
@config.dest_dir
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_url(filename)
|
72
|
+
@config.build_url(filename)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_source_url(filename)
|
76
|
+
@config.build_source_url(filename)
|
77
|
+
end
|
78
|
+
|
79
|
+
def nomarkdown?
|
80
|
+
@config.nomarkdown?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Preset forwarding
|
84
|
+
def widths(media)
|
85
|
+
@preset.widths(media)
|
86
|
+
end
|
87
|
+
|
88
|
+
def fallback_format
|
89
|
+
@preset.fallback_format
|
90
|
+
end
|
91
|
+
|
92
|
+
def fallback_width
|
93
|
+
@preset.fallback_width
|
94
|
+
end
|
95
|
+
|
96
|
+
# Params forwarding
|
97
|
+
def preset_name
|
98
|
+
@params.preset_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def source_images
|
102
|
+
@params.source_images
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|