jekyll-pig 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/jekyll-pig.rb +262 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f4318788681e364b49c60f4f16347407579a5d08521b14a256acfe2da615f95f
|
4
|
+
data.tar.gz: 98d542da59f0bb43be00272838ea42b320a1ca8aa077139d50f49fbba5fc44df
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: efa93dac3c338d0721b00c42a6dbd28b2984ca3de5f5987e9548410f9b89622aee71a3d01c2c2aa63acc9401d4af046c542e00dd507b0a5b216be49795f6a948
|
7
|
+
data.tar.gz: 0a831bb70e85c46164d9d55804886746259cf3e3ebb6d1e5668760ab0582abd5760e81fb417aac65b296a068183b0371351acfc3461063204890e08bc7499b6c
|
data/lib/jekyll-pig.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'json'
|
4
|
+
require 'mini_magick'
|
5
|
+
|
6
|
+
module JekyllPig
|
7
|
+
|
8
|
+
class SourceGallery
|
9
|
+
def initialize(path, name)
|
10
|
+
@path = path
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
def to_s
|
14
|
+
"gallery #{@name} at #{@path}"
|
15
|
+
end
|
16
|
+
def path
|
17
|
+
@path
|
18
|
+
end
|
19
|
+
def name
|
20
|
+
@name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class JekyllPig < Jekyll::Generator
|
25
|
+
|
26
|
+
@@image_cache = {}
|
27
|
+
|
28
|
+
@@pig_min_js = '!function(t){"use strict";var i,e,s=(e=!(i=[]),{add:function(t){i.length||window.addEventListener("resize",n),i.push(t)},disable:function(){window.removeEventListener("resize",n)},reEnable:function(){window.addEventListener("resize",n)}});function n(){e||(e=!0,window.requestAnimationFrame?window.requestAnimationFrame(o):setTimeout(o,66))}function o(){i.forEach(function(t){t()}),e=!1}function a(t,i){return this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings={containerId:"pig",scroller:window,classPrefix:"pig",figureTagName:"figure",spaceBetweenImages:8,transitionSpeed:500,primaryImageBufferHeight:1e3,secondaryImageBufferHeight:300,thumbnailSize:20,urlForSize:function(t,i){return"/img/"+i+"/"+t},onClickHandler:function(t){},getMinAspectRatio:function(t){return t<=640?2:t<=1280?4:t<=1920?5:6},getImageSize:function(t){return t<=640?100:t<=1920?250:500}},function(t,i){for(var e in i)i.hasOwnProperty(e)&&(t[e]=i[e])}(this.settings,i||{}),this.container=document.getElementById(this.settings.containerId),this.container||console.error("Could not find element with ID "+this.settings.containerId),this.scroller=this.settings.scroller,this.images=this._parseImageData(t),function(t,i,e){var s="#"+t+" { position: relative;}."+i+"-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}."+i+"-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: "+e/1e3+"s ease opacity; -webkit-transition: "+e/1e3+"s ease opacity;}."+i+"-figure img."+i+"-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}."+i+"-figure img."+i+"-loaded { opacity: 1;}",n=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",o.styleSheet?o.styleSheet.cssText=s:o.appendChild(document.createTextNode(s)),n.appendChild(o)}(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}function r(t,i,e){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=i,this.pig=e,this.classNames={figure:e.settings.classPrefix+"-figure",thumbnail:e.settings.classPrefix+"-thumbnail",loaded:e.settings.classPrefix+"-loaded"},this}a.prototype._getTransitionTimeout=function(){return 1.5*this.settings.transitionSpeed},a.prototype._getTransitionString=function(){return this.isTransitioning?this.settings.transitionSpeed/1e3+"s transform ease":"none"},a.prototype._recomputeMinAspectRatio=function(){var t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),null!==t&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1},a.prototype._parseImageData=function(t){var s=[];return t.forEach(function(t,i){var e=new r(t,i,this);s.push(e)}.bind(this)),s},a.prototype._computeLayout=function(){var s=parseInt(this.container.clientWidth),n=[],o=0,a=0,r=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(function(){this.isTransitioning=!1},this._getTransitionTimeout()));var h=this._getTransitionString();[].forEach.call(this.images,function(t,i){if(r+=parseFloat(t.aspectRatio),n.push(t),r>=this.minAspectRatio||i+1===this.images.length){r=Math.max(r,this.minAspectRatio);var e=(s-this.settings.spaceBetweenImages*(n.length-1))/r;n.forEach(function(t){var i=e*t.aspectRatio;t.style={width:parseInt(i),height:parseInt(e),translateX:o,translateY:a,transition:h},o+=i+this.settings.spaceBetweenImages}.bind(this)),n=[],r=0,a+=parseInt(e)+this.settings.spaceBetweenImages,o=0}}.bind(this)),this.totalHeight=a-this.settings.spaceBetweenImages},a.prototype._doLayout=function(){this.container.style.height=this.totalHeight+"px";var t="up"===this.scrollDirection?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,i="down"===this.scrollDirection?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,e=function(t){for(var i=0;isNaN(t.offsetTop)||(i+=t.offsetTop),t=t.offsetParent;);return i}(this.container),s=this.scroller===window?window.innerHeight:this.scroller.offsetHeight,n=this.latestYOffset-e-t,o=this.latestYOffset-e+s+i;this.images.forEach(function(t){t.style.translateY+t.style.height<n||t.style.translateY>o?t.hide():t.load()}.bind(this))},a.prototype._getOnScroll=function(){var i=this;return function(){var t=i.scroller===window?window.pageYOffset:i.scroller.scrollTop;i.previousYOffset=i.latestYOffset||t,i.latestYOffset=t,i.scrollDirection=i.latestYOffset>i.previousYOffset?"down":"up",i.inRAF||(i.inRAF=!0,window.requestAnimationFrame(function(){i._doLayout(),i.inRAF=!1}))}},a.prototype.enable=function(){return this.onScroll=this._getOnScroll(),this.scroller.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),s.add(function(){this.lastWindowWidth=this.scroller===window?window.innerWidth:this.scroller.offsetWidth,this._computeLayout(),this._doLayout()}.bind(this)),this},a.prototype.disable=function(){return this.scroller.removeEventListener("scroll",this.onScroll),s.disable(),this},r.prototype.load=function(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(function(){this.existsOnPage&&(this.thumbnail||(this.thumbnail=new Image,this.thumbnail.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.thumbnailSize),this.thumbnail.className=this.classNames.thumbnail,this.thumbnail.onload=function(){this.thumbnail&&(this.thumbnail.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.thumbnail)),this.fullImage||(this.fullImage=new Image,this.fullImage.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth)),this.fullImage.onload=function(){this.fullImage&&(this.fullImage.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.fullImage)))}.bind(this),100)},r.prototype.hide=function(){this.getElement()&&(this.thumbnail&&(this.thumbnail.src="",this.getElement().removeChild(this.thumbnail),delete this.thumbnail),this.fullImage&&(this.fullImage.src="",this.getElement().removeChild(this.fullImage),delete this.fullImage)),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1},r.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this.element.addEventListener("click",function(){this.pig.settings.onClickHandler(this.filename)}.bind(this)),this._updateStyles()),this.element},r.prototype._updateStyles=function(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.transform="translate3d("+this.style.translateX+"px,"+this.style.translateY+"px, 0)"},"function"==typeof define&&define.amd?define([],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:t.Pig=a}("undefined"!=typeof window?window:this);'
|
29
|
+
|
30
|
+
def full_size_html(gallery_name, name, date, prev_url, next_url)
|
31
|
+
"---\n" \
|
32
|
+
"layout: post\n" \
|
33
|
+
"title: #{name}\n" \
|
34
|
+
"date: #{date.strftime("%Y-%m-%d %H:%M:%S")}\n" \
|
35
|
+
"permalink: /assets/html/#{gallery_name}/#{name}.html\n" \
|
36
|
+
"exclude: true\n" \
|
37
|
+
"---\n" \
|
38
|
+
"<div><a href=\"#{prev_url}\" style=\"display:inline;\">prev</a><a href=\"#{next_url}\" style=\"display:inline; float:right\">next</a></div>\n" \
|
39
|
+
"<img src=\"{{site.baseurl}}/assets/img/#{gallery_name}/1024/#{name}\"/>\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
def gallery_html(id, image_data)
|
43
|
+
"<div id='#{id}_pig'></div>\n" \
|
44
|
+
"<script src='{{site.baseurl}}/assets/js/pig.min.js'></script>\n" \
|
45
|
+
"<script>\n" \
|
46
|
+
"var #{id}_pig = new Pig(\n" \
|
47
|
+
" #{image_data.to_json()},\n" \
|
48
|
+
" {\n" \
|
49
|
+
" containerId: '#{id}_pig',\n" \
|
50
|
+
" classPrefix: '#{id}_pig',\n" \
|
51
|
+
" urlForSize: function(filename, size) {\n" \
|
52
|
+
" return '{{site.baseurl}}/assets/img/#{id}/' + size + '/' + filename;\n" \
|
53
|
+
" },\n" \
|
54
|
+
" onClickHandler: function(filename) {\n" \
|
55
|
+
" window.location.href = '{{site.baseurl}}/assets/html/#{id}/' + filename + '.html';\n" \
|
56
|
+
" }\n" \
|
57
|
+
" }\n" \
|
58
|
+
").enable();\n" \
|
59
|
+
"</script>"
|
60
|
+
end
|
61
|
+
|
62
|
+
def image_html_url(gallery_name, image_name)
|
63
|
+
"/assets/html/#{gallery_name}/#{image_name}.html"
|
64
|
+
end
|
65
|
+
|
66
|
+
#read the image data from the _includes folder
|
67
|
+
def get_image_data(gallery_name)
|
68
|
+
image_data = []
|
69
|
+
#read image_data if existing
|
70
|
+
if File.exists?(File.join(@data_path, "#{gallery_name}.json"))
|
71
|
+
File.open(File.join(@data_path, "#{gallery_name}.json"), 'r') { |file|
|
72
|
+
#get array of image data (drop 'var imageData = ' and ';')
|
73
|
+
image_data = JSON.parse(file.read)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
image_data
|
77
|
+
end
|
78
|
+
|
79
|
+
#read images that require processing from gallery
|
80
|
+
def get_images(gallery_path)
|
81
|
+
patterns = ['*.jpg', '*.jpeg', '*.png'].map { |ext| File.join(gallery_path, ext) }
|
82
|
+
Dir.glob(patterns).map { |path| File.basename(path) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_image(gallery_path, image_name)
|
86
|
+
image = @@image_cache[File.join(gallery_path, image_name)]
|
87
|
+
if image == nil
|
88
|
+
image = MiniMagick::Image.open(File.join(gallery_path, image_name))
|
89
|
+
@@image_cache[File.join(gallery_path, image_name)] = image
|
90
|
+
end
|
91
|
+
image
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_image_date(gallery_path, image_name)
|
95
|
+
image_date = nil
|
96
|
+
begin
|
97
|
+
image = get_image(gallery_path, image_name)
|
98
|
+
exif_date = image.exif['DateTimeOriginal']
|
99
|
+
if exif_date == nil
|
100
|
+
#no exif date, try to get from file name
|
101
|
+
image_date = Time.strptime(image_name, "%Y-%m-%d")
|
102
|
+
else
|
103
|
+
#try to get the image date from exif
|
104
|
+
image_date = Time.strptime(exif_date, "%Y:%m:%d %H:%M:%S")
|
105
|
+
end
|
106
|
+
rescue
|
107
|
+
#get the date from file if possible
|
108
|
+
image_date = File.mtime(File.join(gallery_path, image_name))
|
109
|
+
end
|
110
|
+
image_date
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_previous_url(image_data, gallery_name, image_name)
|
114
|
+
index = image_data.index { |data| data['filename'] == image_name }
|
115
|
+
index = index - 1
|
116
|
+
if index < 0
|
117
|
+
index = image_data.length - 1
|
118
|
+
end
|
119
|
+
image_html_url(gallery_name, image_data[index]['filename'])
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_next_url(image_data, gallery_name, image_name)
|
123
|
+
index = image_data.index { |data| data['filename'] == image_name }
|
124
|
+
index = index + 1
|
125
|
+
if index >= image_data.length
|
126
|
+
index = 0
|
127
|
+
end
|
128
|
+
image_html_url(gallery_name, image_data[index]['filename'])
|
129
|
+
end
|
130
|
+
|
131
|
+
#create thumbnails and fullsize image assets, and create full size html page for a given image
|
132
|
+
def process_image(image_data, gallery_id, gallery_path, image_name)
|
133
|
+
#puts "jekyll-pig: processing " << image_name
|
134
|
+
#create thumbs
|
135
|
+
[1024, 500, 250, 100, 20].each { |size|
|
136
|
+
size_out_path = File.join(@img_path, gallery_id, size.to_s)
|
137
|
+
resized_img_path = File.join(size_out_path, image_name)
|
138
|
+
if not File.exists? resized_img_path
|
139
|
+
image = get_image(gallery_path, image_name)
|
140
|
+
image.resize("x" + size.to_s)
|
141
|
+
FileUtils.mkdir_p size_out_path unless File.exists? size_out_path
|
142
|
+
image.write(resized_img_path)
|
143
|
+
end
|
144
|
+
}
|
145
|
+
full_size_html_path = File.join(@html_path, gallery_id, image_name + ".html")
|
146
|
+
#create full size html if it doesn't exist
|
147
|
+
if not File.exists? full_size_html_path
|
148
|
+
#get image date
|
149
|
+
image_date = get_image_date(gallery_path, image_name)
|
150
|
+
#create full size html text
|
151
|
+
full_size_html = full_size_html(gallery_id, image_name, image_date,
|
152
|
+
get_previous_url(image_data, gallery_id, image_name),
|
153
|
+
get_next_url(image_data, gallery_id, image_name))
|
154
|
+
File.open(full_size_html_path, 'w') { |file|
|
155
|
+
file.write(full_size_html)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_paths
|
161
|
+
@assets_path = File.join(@site.source, "assets")
|
162
|
+
@js_path = File.join(@assets_path, "js")
|
163
|
+
@data_path = File.join(@site.source, "_data")
|
164
|
+
@img_path = File.join(@assets_path, "img")
|
165
|
+
@html_path = File.join(@assets_path, "html")
|
166
|
+
@includes_path = File.join(@site.source, "_includes")
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_galleries
|
170
|
+
galleries = []
|
171
|
+
config_galleries = Jekyll.configuration({})['galleries']
|
172
|
+
if config_galleries != nil
|
173
|
+
config_galleries.each do |gallery|
|
174
|
+
full_path = File.join(@site.source, gallery['path'])
|
175
|
+
if File.directory?(full_path)
|
176
|
+
galleries << SourceGallery.new(full_path, gallery['name'])
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
default_gallery_path = File.join(@site.source, 'gallery')
|
181
|
+
if File.directory?(default_gallery_path)
|
182
|
+
galleries << SourceGallery.new(default_gallery_path, 'gallery')
|
183
|
+
end
|
184
|
+
end
|
185
|
+
galleries
|
186
|
+
end
|
187
|
+
|
188
|
+
def make_output_paths
|
189
|
+
FileUtils.mkdir_p @assets_path unless File.exists? @assets_path
|
190
|
+
FileUtils.mkdir_p @js_path unless File.exists? @js_path
|
191
|
+
FileUtils.mkdir_p @img_path unless File.exists? @img_path
|
192
|
+
FileUtils.mkdir_p @html_path unless File.exists? @html_path
|
193
|
+
FileUtils.mkdir_p @includes_path unless File.exists? @includes_path
|
194
|
+
FileUtils.mkdir_p @data_path unless File.exists? @data_path
|
195
|
+
end
|
196
|
+
|
197
|
+
def augment_image_data(gallery, image_data, images)
|
198
|
+
images.each do |image_name|
|
199
|
+
#append data to image_data array if it's not already there
|
200
|
+
if not image_data.any? { |data| data['filename'] == image_name }
|
201
|
+
#get image date
|
202
|
+
image_date = get_image_date(gallery.path, image_name)
|
203
|
+
image = get_image(gallery.path, image_name)
|
204
|
+
image_data <<
|
205
|
+
{
|
206
|
+
'datetime' => image_date.to_s,
|
207
|
+
'filename' => image_name,
|
208
|
+
'aspectRatio' => image.width.to_f / image.height
|
209
|
+
}
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def generate(site)
|
215
|
+
@site = site
|
216
|
+
get_paths()
|
217
|
+
make_output_paths()
|
218
|
+
galleries = get_galleries()
|
219
|
+
galleries.each do |gallery|
|
220
|
+
|
221
|
+
#make gallery specific html and image output paths
|
222
|
+
html_output_path = File.join(@html_path, gallery.name)
|
223
|
+
FileUtils.mkdir_p html_output_path unless File.exists? html_output_path
|
224
|
+
img_output_path = File.join(@img_path, gallery.name)
|
225
|
+
FileUtils.mkdir_p img_output_path unless File.exists? img_output_path
|
226
|
+
|
227
|
+
#write pig.min.js to js path
|
228
|
+
if not File.exists? File.join(@js_path, 'pig.min.js')
|
229
|
+
File.open(File.join(@js_path, 'pig.min.js'), 'w') { |file| file.write(@@pig_min_js) }
|
230
|
+
end
|
231
|
+
|
232
|
+
#get image data from _data
|
233
|
+
image_data = get_image_data(gallery.name)
|
234
|
+
|
235
|
+
#get images from gallery
|
236
|
+
images = get_images(gallery.path)
|
237
|
+
|
238
|
+
#add any additional images to image_data
|
239
|
+
augment_image_data(gallery, image_data, images)
|
240
|
+
|
241
|
+
#sort image data
|
242
|
+
image_data = image_data.sort_by { |data| data['datetime'] }
|
243
|
+
|
244
|
+
#process images
|
245
|
+
images.each do |image_name|
|
246
|
+
#create thumbs, full size, and html assets for each image
|
247
|
+
process_image(image_data, gallery.name, gallery.path, image_name)
|
248
|
+
end
|
249
|
+
|
250
|
+
#write image_data
|
251
|
+
File.open(File.join(@data_path, "#{gallery.name}.json"), 'w') { |file|
|
252
|
+
file.write(image_data.to_json)
|
253
|
+
}
|
254
|
+
|
255
|
+
#save this gallery's includable content
|
256
|
+
File.open(File.join(@includes_path, "#{gallery.name}.html"), 'w') { |file|
|
257
|
+
file.write(gallery_html(gallery.name, image_data))
|
258
|
+
}
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jekyll-pig
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin Holzman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mini_magick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.10'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.10'
|
27
|
+
description: Uses ImageMagick and pig.js to create progressive image galleries for
|
28
|
+
Jekyll pages
|
29
|
+
email: me@colinholzman.xyz
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/jekyll-pig.rb
|
35
|
+
homepage: https://github.com/clnhlzmn/jekyll-pig
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.7.6
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Jekyll image gallery generator
|
59
|
+
test_files: []
|