inkcite 1.15.0 → 1.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/inkcite.gemspec +0 -3
- data/lib/inkcite.rb +0 -1
- data/lib/inkcite/cli/base.rb +12 -8
- data/lib/inkcite/cli/preview.rb +0 -10
- data/lib/inkcite/cli/server.rb +9 -1
- data/lib/inkcite/email.rb +5 -10
- data/lib/inkcite/facade/animation.rb +4 -1
- data/lib/inkcite/image_minifier.rb +96 -0
- data/lib/inkcite/mailer.rb +2 -2
- data/lib/inkcite/minifier.rb +1 -1
- data/lib/inkcite/renderer.rb +10 -0
- data/lib/inkcite/renderer/base.rb +47 -2
- data/lib/inkcite/renderer/button.rb +15 -5
- data/lib/inkcite/renderer/container_base.rb +15 -1
- data/lib/inkcite/renderer/image.rb +1 -1
- data/lib/inkcite/renderer/image_base.rb +8 -0
- data/lib/inkcite/renderer/list.rb +104 -0
- data/lib/inkcite/renderer/mobile_image.rb +3 -0
- data/lib/inkcite/renderer/responsive.rb +2 -0
- data/lib/inkcite/renderer/slant.rb +207 -0
- data/lib/inkcite/renderer/snow.rb +15 -1
- data/lib/inkcite/renderer/special_effect.rb +7 -0
- data/lib/inkcite/renderer/sup.rb +18 -4
- data/lib/inkcite/renderer/table.rb +9 -1
- data/lib/inkcite/renderer/table_base.rb +20 -2
- data/lib/inkcite/renderer/td.rb +7 -2
- data/lib/inkcite/renderer/video_preview.rb +1 -1
- data/lib/inkcite/uploader.rb +40 -27
- data/lib/inkcite/version.rb +1 -1
- data/lib/inkcite/view.rb +133 -13
- data/lib/inkcite/view/context.rb +6 -1
- data/lib/inkcite/view/developer_scripts.rb +56 -0
- data/test/renderer/background_spec.rb +4 -4
- data/test/renderer/button_spec.rb +14 -8
- data/test/renderer/div_spec.rb +26 -3
- data/test/renderer/image_spec.rb +9 -4
- data/test/renderer/list_spec.rb +36 -0
- data/test/renderer/mobile_image_spec.rb +5 -0
- data/test/renderer/slant_spec.rb +47 -0
- data/test/renderer/span_spec.rb +12 -1
- data/test/renderer/table_spec.rb +1 -1
- data/test/renderer/td_spec.rb +24 -8
- data/test/renderer/trademark_spec.rb +6 -6
- metadata +11 -50
- data/lib/inkcite/image/base.rb +0 -38
- data/lib/inkcite/image/guetzli_minifier.rb +0 -62
- data/lib/inkcite/image/image_minifier.rb +0 -143
- data/lib/inkcite/image/image_optim_minifier.rb +0 -90
- data/lib/inkcite/image/mozjpeg_minifier.rb +0 -92
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inkcite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeffrey D. Hoffman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -122,34 +122,6 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: image_optim
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :runtime
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: image_optim_pack
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :runtime
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
125
|
- !ruby/object:Gem::Dependency
|
154
126
|
name: listen
|
155
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,20 +178,6 @@ dependencies:
|
|
206
178
|
- - ">="
|
207
179
|
- !ruby/object:Gem::Version
|
208
180
|
version: '0'
|
209
|
-
- !ruby/object:Gem::Dependency
|
210
|
-
name: mozjpeg
|
211
|
-
requirement: !ruby/object:Gem::Requirement
|
212
|
-
requirements:
|
213
|
-
- - ">="
|
214
|
-
- !ruby/object:Gem::Version
|
215
|
-
version: '0'
|
216
|
-
type: :runtime
|
217
|
-
prerelease: false
|
218
|
-
version_requirements: !ruby/object:Gem::Requirement
|
219
|
-
requirements:
|
220
|
-
- - ">="
|
221
|
-
- !ruby/object:Gem::Version
|
222
|
-
version: '0'
|
223
181
|
- !ruby/object:Gem::Dependency
|
224
182
|
name: net-sftp
|
225
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -389,11 +347,7 @@ files:
|
|
389
347
|
- lib/inkcite/facade/element.rb
|
390
348
|
- lib/inkcite/facade/keyframe.rb
|
391
349
|
- lib/inkcite/facade/style.rb
|
392
|
-
- lib/inkcite/
|
393
|
-
- lib/inkcite/image/guetzli_minifier.rb
|
394
|
-
- lib/inkcite/image/image_minifier.rb
|
395
|
-
- lib/inkcite/image/image_optim_minifier.rb
|
396
|
-
- lib/inkcite/image/mozjpeg_minifier.rb
|
350
|
+
- lib/inkcite/image_minifier.rb
|
397
351
|
- lib/inkcite/mailer.rb
|
398
352
|
- lib/inkcite/minifier.rb
|
399
353
|
- lib/inkcite/parser.rb
|
@@ -414,6 +368,7 @@ files:
|
|
414
368
|
- lib/inkcite/renderer/increment.rb
|
415
369
|
- lib/inkcite/renderer/like.rb
|
416
370
|
- lib/inkcite/renderer/link.rb
|
371
|
+
- lib/inkcite/renderer/list.rb
|
417
372
|
- lib/inkcite/renderer/litmus_analytics.rb
|
418
373
|
- lib/inkcite/renderer/lorem.rb
|
419
374
|
- lib/inkcite/renderer/mobile_image.rb
|
@@ -425,6 +380,7 @@ files:
|
|
425
380
|
- lib/inkcite/renderer/property.rb
|
426
381
|
- lib/inkcite/renderer/redacted.rb
|
427
382
|
- lib/inkcite/renderer/responsive.rb
|
383
|
+
- lib/inkcite/renderer/slant.rb
|
428
384
|
- lib/inkcite/renderer/snow.rb
|
429
385
|
- lib/inkcite/renderer/social.rb
|
430
386
|
- lib/inkcite/renderer/span.rb
|
@@ -442,6 +398,7 @@ files:
|
|
442
398
|
- lib/inkcite/version.rb
|
443
399
|
- lib/inkcite/view.rb
|
444
400
|
- lib/inkcite/view/context.rb
|
401
|
+
- lib/inkcite/view/developer_scripts.rb
|
445
402
|
- lib/inkcite/view/media_query.rb
|
446
403
|
- lib/inkcite/view/tag_stack.rb
|
447
404
|
- test/animation_spec.rb
|
@@ -460,11 +417,13 @@ files:
|
|
460
417
|
- test/renderer/footnote_spec.rb
|
461
418
|
- test/renderer/image_spec.rb
|
462
419
|
- test/renderer/link_spec.rb
|
420
|
+
- test/renderer/list_spec.rb
|
463
421
|
- test/renderer/lorem_spec.rb
|
464
422
|
- test/renderer/mobile_image_spec.rb
|
465
423
|
- test/renderer/mobile_only_spec.rb
|
466
424
|
- test/renderer/mobile_style_spec.rb
|
467
425
|
- test/renderer/redacted_spec.rb
|
426
|
+
- test/renderer/slant_spec.rb
|
468
427
|
- test/renderer/social_spec.rb
|
469
428
|
- test/renderer/span_spec.rb
|
470
429
|
- test/renderer/table_spec.rb
|
@@ -496,7 +455,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
496
455
|
version: '0'
|
497
456
|
requirements: []
|
498
457
|
rubyforge_project:
|
499
|
-
rubygems_version: 2.6.
|
458
|
+
rubygems_version: 2.6.14
|
500
459
|
signing_key:
|
501
460
|
specification_version: 4
|
502
461
|
summary: Simplifying email development
|
@@ -517,11 +476,13 @@ test_files:
|
|
517
476
|
- test/renderer/footnote_spec.rb
|
518
477
|
- test/renderer/image_spec.rb
|
519
478
|
- test/renderer/link_spec.rb
|
479
|
+
- test/renderer/list_spec.rb
|
520
480
|
- test/renderer/lorem_spec.rb
|
521
481
|
- test/renderer/mobile_image_spec.rb
|
522
482
|
- test/renderer/mobile_only_spec.rb
|
523
483
|
- test/renderer/mobile_style_spec.rb
|
524
484
|
- test/renderer/redacted_spec.rb
|
485
|
+
- test/renderer/slant_spec.rb
|
525
486
|
- test/renderer/social_spec.rb
|
526
487
|
- test/renderer/span_spec.rb
|
527
488
|
- test/renderer/table_spec.rb
|
data/lib/inkcite/image/base.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Inkcite
|
2
|
-
module Image
|
3
|
-
class ImageMinifier
|
4
|
-
|
5
|
-
# Base class for all image minifiers in the optimization pipeline
|
6
|
-
class Base
|
7
|
-
|
8
|
-
attr_reader :name
|
9
|
-
|
10
|
-
def initialize name
|
11
|
-
@name = name
|
12
|
-
end
|
13
|
-
|
14
|
-
def minify! email, source_img, cache_img
|
15
|
-
raise 'The extending class must implement this method'
|
16
|
-
end
|
17
|
-
|
18
|
-
protected
|
19
|
-
|
20
|
-
# Common configuration names
|
21
|
-
JPG_QUALITY = :'jpg-quality'
|
22
|
-
|
23
|
-
# JPG quality bounds
|
24
|
-
MIN_QUALITY = 0
|
25
|
-
MAX_QUALITY = 100
|
26
|
-
|
27
|
-
def get_jpg_quality config, override_key, default
|
28
|
-
quality = (config[override_key] || config[JPG_QUALITY] || default).to_i
|
29
|
-
quality = MIN_QUALITY if quality < MIN_QUALITY
|
30
|
-
quality = MAX_QUALITY if quality > MAX_QUALITY
|
31
|
-
quality
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module Inkcite
|
2
|
-
module Image
|
3
|
-
class GuetzliMinifier < ImageMinifier::Base
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
super('Guetzli')
|
7
|
-
end
|
8
|
-
|
9
|
-
def minify! email, source_img, cache_img
|
10
|
-
|
11
|
-
# Grab the full path to the guetzli binary
|
12
|
-
guetzli_path = `which guetzli`.delete("\n")
|
13
|
-
unless guetzli_path.blank?
|
14
|
-
|
15
|
-
cmd = []
|
16
|
-
cmd << guetzli_path
|
17
|
-
|
18
|
-
config = email.config
|
19
|
-
|
20
|
-
quality = get_jpg_quality(config, GUETZLI_QUALITY, DEFAULT_QUALITY)
|
21
|
-
if quality > 0 && quality < MAX_QUALITY
|
22
|
-
|
23
|
-
# Per the Guetzli documentation, a value less than 84 isn't useful.
|
24
|
-
quality = MIN_GUETZLI_QUALITY if quality < MIN_GUETZLI_QUALITY
|
25
|
-
cmd << "--quality #{quality}"
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
cmd << %Q("#{source_img}")
|
30
|
-
cmd << %Q("#{cache_img}")
|
31
|
-
|
32
|
-
Util::exec(cmd)
|
33
|
-
|
34
|
-
true
|
35
|
-
|
36
|
-
else
|
37
|
-
|
38
|
-
# No guetzli, so simply move the source image into the destination
|
39
|
-
# position without compression.
|
40
|
-
FileUtils.copy(source_img, cache_img)
|
41
|
-
|
42
|
-
false
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
# Default quality is zero - which lets Guetzli decide how best to
|
51
|
-
# optimize the image.
|
52
|
-
DEFAULT_QUALITY = 0
|
53
|
-
|
54
|
-
# The minimum quality setting per the Guetzli runtime.
|
55
|
-
MIN_GUETZLI_QUALITY = 84
|
56
|
-
|
57
|
-
# Configuration field names
|
58
|
-
GUETZLI_QUALITY = :'guetzli-quality'
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,143 +0,0 @@
|
|
1
|
-
require_relative 'base'
|
2
|
-
require_relative 'guetzli_minifier'
|
3
|
-
require_relative 'image_optim_minifier'
|
4
|
-
require_relative 'mozjpeg_minifier'
|
5
|
-
|
6
|
-
module Inkcite
|
7
|
-
module Image
|
8
|
-
class ImageMinifier
|
9
|
-
|
10
|
-
# Directory of optimized images
|
11
|
-
IMAGE_CACHE = 'images-optim'
|
12
|
-
|
13
|
-
# Common extensions
|
14
|
-
GIF = 'gif'
|
15
|
-
JPG = 'jpg'
|
16
|
-
PNG = 'png'
|
17
|
-
|
18
|
-
def self.minify email, img_name, force=false
|
19
|
-
|
20
|
-
# Original, unoptimized source image
|
21
|
-
source_img = File.join(email.image_dir, img_name)
|
22
|
-
|
23
|
-
# Cached, optimized path for this image.
|
24
|
-
cache_path = email.project_file(IMAGE_CACHE)
|
25
|
-
cached_img = File.join(cache_path, File.basename(img_name))
|
26
|
-
|
27
|
-
# Grab the first file that exists for this project.
|
28
|
-
config_path = email.config_file
|
29
|
-
|
30
|
-
unless force
|
31
|
-
|
32
|
-
# Get the last-modified date of the image optimization config
|
33
|
-
# file - if that file is newer than the image, re-optimization
|
34
|
-
# is necessary because the settings have changed.
|
35
|
-
config_last_modified = Util.last_modified(config_path)
|
36
|
-
|
37
|
-
# Get the last-modified date of the actual image. If the source
|
38
|
-
# image is newer than the cached version, we'll need to run it
|
39
|
-
# through optimization again, too.
|
40
|
-
cache_last_modified = Util.last_modified(cached_img)
|
41
|
-
source_last_modified = Util.last_modified(source_img)
|
42
|
-
|
43
|
-
# Nothing to do unless the image in the cache is older than the
|
44
|
-
# source or the config file.
|
45
|
-
return unless config_last_modified > cache_last_modified || source_last_modified > cache_last_modified
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
# Make sure the image cache directory exists
|
50
|
-
FileUtils.mkpath(cache_path)
|
51
|
-
|
52
|
-
# Copy the original image to the cache where it can be processed.
|
53
|
-
FileUtils.copy(source_img, cached_img)
|
54
|
-
|
55
|
-
# Get the file format (e.g. gif) of the file being optimized.
|
56
|
-
source_ext = Util::file_extension(source_img)
|
57
|
-
|
58
|
-
# This will hold the list of minifiers that will be applied to the
|
59
|
-
# image, based on its extension.
|
60
|
-
pipeline = []
|
61
|
-
|
62
|
-
# True if ImageOptim is allowed to make lossy optimizations to the
|
63
|
-
# images. When false, even if the quality settings allow it, ImageOptim
|
64
|
-
# won't make lossy optimizations.
|
65
|
-
allow_lossy = true
|
66
|
-
|
67
|
-
if source_ext == JPG
|
68
|
-
pipeline << MozjpegMinifier.new
|
69
|
-
pipeline << GuetzliMinifier.new
|
70
|
-
allow_lossy = false
|
71
|
-
end
|
72
|
-
|
73
|
-
# Always optimize with ImageOptim although for JPGs additional
|
74
|
-
# lossy compression is force disabled.
|
75
|
-
pipeline << ImageOptimMinifier.new(allow_lossy)
|
76
|
-
|
77
|
-
original_size = File.size(source_img)
|
78
|
-
|
79
|
-
msg = "Compressing #{img_name} #{Util.pretty_file_size(original_size)}"
|
80
|
-
|
81
|
-
# Process the image
|
82
|
-
pipeline.each do |p|
|
83
|
-
|
84
|
-
# Minifiers don't work well when the source and destination images are the
|
85
|
-
# same files - so move the image to a temporary file so the minifier can
|
86
|
-
# optimize it back into place.
|
87
|
-
temp_img = "#{cached_img}.tmp"
|
88
|
-
FileUtils.move(cached_img, temp_img)
|
89
|
-
|
90
|
-
temp_size = File.size(temp_img)
|
91
|
-
|
92
|
-
# Let the processor compress the image
|
93
|
-
p.minify!(email, temp_img, cached_img)
|
94
|
-
|
95
|
-
compressed_size = File.size(cached_img)
|
96
|
-
if compressed_size < temp_size
|
97
|
-
msg << " >> #{p.name} #{Util.pretty_file_size(compressed_size)}"
|
98
|
-
|
99
|
-
else
|
100
|
-
|
101
|
-
# Occassionally the compressor does the wrong thing and
|
102
|
-
# makes the image bigger (particularly ImageOptim after
|
103
|
-
# Guetzli) so in that case, revert the image to its
|
104
|
-
# smaller pre-optimization form.
|
105
|
-
FileUtils.copy(temp_img, cached_img)
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
# Now remove the temp file.
|
110
|
-
FileUtils.remove(temp_img) if File.exists?(temp_img)
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
# Get the final compressed size of the image so we can print the
|
115
|
-
# resulting compression ratio.
|
116
|
-
compressed_size = File.size(cached_img)
|
117
|
-
msg << " (#{self.compressed_percent(original_size, compressed_size)}%)"
|
118
|
-
|
119
|
-
Util.log msg
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
# Minifies all of the images in the provided email's project directory.
|
124
|
-
def self.minify_all email, force=false
|
125
|
-
|
126
|
-
images_path = File.join(email.image_dir, '*.*')
|
127
|
-
|
128
|
-
# Iterate through all of the images in the project and optimize them
|
129
|
-
# if necessary.
|
130
|
-
Dir.glob(images_path).each do |img|
|
131
|
-
self.minify(email, File.basename(img), force)
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.compressed_percent original_size, compressed_size
|
137
|
-
((1.0 - (compressed_size / original_size.to_f)) * 100).round(1)
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
@@ -1,90 +0,0 @@
|
|
1
|
-
module Inkcite
|
2
|
-
module Image
|
3
|
-
class ImageOptimMinifier < ImageMinifier::Base
|
4
|
-
|
5
|
-
def initialize allow_lossy=true
|
6
|
-
super('ImageOptim')
|
7
|
-
@allow_lossy = allow_lossy
|
8
|
-
end
|
9
|
-
|
10
|
-
def minify! email, source_img, cache_img
|
11
|
-
|
12
|
-
config = email.config
|
13
|
-
|
14
|
-
img_type = Util::file_extension(cache_img)
|
15
|
-
|
16
|
-
# This will hold the settings that control how imageoptim
|
17
|
-
# compresses the image, based on extension.
|
18
|
-
optim_opt = {
|
19
|
-
:verbose => false,
|
20
|
-
:svgo => false
|
21
|
-
}
|
22
|
-
|
23
|
-
FileUtils.copy(source_img, cache_img)
|
24
|
-
|
25
|
-
case img_type
|
26
|
-
when ImageMinifier::GIF
|
27
|
-
mix_gif_options email, config, optim_opt
|
28
|
-
when ImageMinifier::JPG
|
29
|
-
mix_jpg_options email, config, optim_opt
|
30
|
-
when ImageMinifier::PNG
|
31
|
-
mix_png_options email, config, optim_opt
|
32
|
-
else
|
33
|
-
# Don't know how to compress this type of image so
|
34
|
-
# just leave it alone
|
35
|
-
return
|
36
|
-
end
|
37
|
-
|
38
|
-
ImageOptim.new(optim_opt).optimize_image!(cache_img)
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
# Name of the configuration field that controls ImageOptim's JPG quality
|
45
|
-
IMAGEOPTIM_JPG_QUALITY = :'imageopt-jpg-quality'
|
46
|
-
|
47
|
-
# Default JPG image quality
|
48
|
-
DEFAULT_JPG_QUALITY = 85
|
49
|
-
|
50
|
-
# Quality constraints
|
51
|
-
MIN_QUALITY = 0
|
52
|
-
MAX_QUALITY = 100
|
53
|
-
|
54
|
-
def mix_gif_options email, config, opts
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def mix_jpg_options email, config, opts
|
59
|
-
|
60
|
-
max_quality = get_jpg_quality(config, IMAGEOPTIM_JPG_QUALITY, DEFAULT_JPG_QUALITY)
|
61
|
-
|
62
|
-
# Anything less than 100% quality means lossy is enabled.
|
63
|
-
lossy = @allow_lossy && max_quality < 100
|
64
|
-
opts[:allow_lossy] = lossy
|
65
|
-
|
66
|
-
# Additional configuration necessary only if lossy compression
|
67
|
-
# is enabled. Otherwise, the defaults are acceptable.
|
68
|
-
if lossy
|
69
|
-
opts[:jpegoptim] = {
|
70
|
-
:allow_lossy => lossy,
|
71
|
-
:max_quality => max_quality
|
72
|
-
}
|
73
|
-
|
74
|
-
opts[:jpegrecompress] = {
|
75
|
-
:allow_lossy => lossy,
|
76
|
-
:quality => (max_quality / 100.0 * 3).round(0)
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
def mix_png_options email, config, opts
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|