jekyll-collection-pages 0.1.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 +7 -0
- data/.devcontainer/devcontainer.json +27 -0
- data/.devcontainer/post-create.sh +17 -0
- data/.github/build.sh +4 -0
- data/.github/bump.sh +28 -0
- data/.github/dependabot.yml +12 -0
- data/.github/pr-labeler.yml +7 -0
- data/.github/release-drafter.yml +40 -0
- data/.github/release.sh +69 -0
- data/.github/test.sh +4 -0
- data/.github/workflows/ci.yml +92 -0
- data/.github/workflows/pr_labeler.yml +16 -0
- data/.github/workflows/release.yml +23 -0
- data/.github/workflows/release_draft.yml +61 -0
- data/.github/workflows/site.yml +91 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +43 -0
- data/.rubocop_todo.yml +33 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +5 -0
- data/.vscode/tasks.json +58 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +41 -0
- data/Gemfile +39 -0
- data/LICENSE +21 -0
- data/README.md +152 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/demo/CODE_OF_CONDUCT.md +1 -0
- data/demo/_articles/jekyll-collection-pages-comparison.md +90 -0
- data/demo/_articles/jekyll-collection-pages-indices.md +173 -0
- data/demo/_articles/jekyll-for-documentation.md +399 -0
- data/demo/_articles/linking-obsidian-and-jekyll.md +89 -0
- data/demo/_config.yml +83 -0
- data/demo/_docs/configuration-guide.md +113 -0
- data/demo/_docs/examples.md +159 -0
- data/demo/_docs/generated-data.md +76 -0
- data/demo/_docs/layout-recipes.md +122 -0
- data/demo/_docs/quick-start.md +1 -0
- data/demo/_docs/troubleshooting.md +68 -0
- data/demo/_layouts/collection_layout.html +27 -0
- data/demo/_layouts/tags.html +31 -0
- data/demo/articles.md +18 -0
- data/demo/assets/img/articles.png +0 -0
- data/demo/assets/img/docs.png +0 -0
- data/demo/assets/img/jekyll-collection-pages-preview.png +0 -0
- data/demo/assets/img/jekyll-collection-pages.png +0 -0
- data/demo/assets/img/post-img-1.png +0 -0
- data/demo/assets/img/post-img-2.png +0 -0
- data/demo/assets/img/post-img-3.png +0 -0
- data/demo/assets/img/post-img-4.png +0 -0
- data/demo/contributing.md +1 -0
- data/demo/directory.md +36 -0
- data/demo/docs.md +24 -0
- data/demo/gallery.md +14 -0
- data/demo/index.md +63 -0
- data/demo/tags.md +15 -0
- data/jekyll-collection-pages.gemspec +28 -0
- data/lib/jekyll/collection_pages.rb +383 -0
- data/lib/jekyll-collection-pages.rb +4 -0
- metadata +126 -0
data/demo/tags.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tags
|
|
3
|
+
layout: page
|
|
4
|
+
permalink: /tags/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
{%- assign tags_info = site.data.collection_pages.articles.tags %}
|
|
8
|
+
{%- assign tag_permalink = site.data.collection_pages.articles.tags.permalink %}
|
|
9
|
+
|
|
10
|
+
{% include post-index.html
|
|
11
|
+
collection=tags_info.pages
|
|
12
|
+
collection_permalink=tag_permalink
|
|
13
|
+
replace_value=":field"
|
|
14
|
+
per_section=3
|
|
15
|
+
%}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'jekyll-collection-pages'
|
|
5
|
+
spec.version = File.read(File.expand_path('VERSION', __dir__)).strip
|
|
6
|
+
spec.authors = ['allison@allisonthackston.com']
|
|
7
|
+
|
|
8
|
+
spec.summary = 'A Jekyll plugin for generating tag pages for multiple collections'
|
|
9
|
+
spec.description = 'This Jekyll plugin allows you to generate tag pages for multiple collections, with support for pagination.'
|
|
10
|
+
spec.homepage = 'https://github.com/PrimerPages/jekyll-collection-pages'
|
|
11
|
+
spec.license = 'MIT'
|
|
12
|
+
spec.metadata = {
|
|
13
|
+
'source_code_uri' => spec.homepage,
|
|
14
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
15
|
+
'documentation_uri' => 'https://primerpages.github.io/jekyll-collection-pages/',
|
|
16
|
+
'rubygems_mfa_required' => 'true'
|
|
17
|
+
}
|
|
18
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
22
|
+
end
|
|
23
|
+
spec.bindir = 'exe'
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = ['lib']
|
|
26
|
+
|
|
27
|
+
spec.add_dependency 'jekyll', '>= 3.7', '< 5.0'
|
|
28
|
+
end
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module CollectionPages
|
|
5
|
+
INDEXFILE = 'index.html'
|
|
6
|
+
|
|
7
|
+
class PathTemplate
|
|
8
|
+
def initialize(raw_template:, tag_field:, collection_name:)
|
|
9
|
+
@tag_field = tag_field
|
|
10
|
+
@collection_name = collection_name
|
|
11
|
+
@template = build_effective_template(raw_template)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def for_tag(tag_value)
|
|
15
|
+
TagPathResolver.new(@template, tag_value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def permalink
|
|
19
|
+
TagPathResolver.new(@template, ':field', slugify_value: false).url_for(1)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def template
|
|
23
|
+
"/#{@template}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def build_effective_template(raw)
|
|
29
|
+
sanitized = sanitize_path(raw)
|
|
30
|
+
sanitized = default_placeholder if sanitized.empty?
|
|
31
|
+
sanitized = add_field_placeholder(sanitized)
|
|
32
|
+
sanitized = add_num_placeholder(sanitized)
|
|
33
|
+
sanitized = add_index(sanitized)
|
|
34
|
+
Jekyll.logger.debug('CollectionPages:', "Using path template '#{sanitized}' for collection '#{@collection_name}'.")
|
|
35
|
+
validate_template(sanitized)
|
|
36
|
+
|
|
37
|
+
sanitized
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def sanitize_path(path)
|
|
41
|
+
path.to_s.strip.sub(%r{^/+}, '').sub(%r{/+\z}, '')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def default_placeholder
|
|
45
|
+
@collection_name.to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_field_placeholder(path)
|
|
49
|
+
return path if path.include?(':field')
|
|
50
|
+
|
|
51
|
+
raise ArgumentError, "Path template '#{path}' must include a ':field' placeholder." if path.end_with?('.html') || path.end_with?('.htm')
|
|
52
|
+
|
|
53
|
+
"#{path}/:field"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_num_placeholder(path)
|
|
57
|
+
return path if path.include?(':num')
|
|
58
|
+
|
|
59
|
+
raise ArgumentError, "Path template '#{path}' must include a ':num' placeholder." if path.end_with?('.html') || path.end_with?('.htm')
|
|
60
|
+
|
|
61
|
+
"#{path}/page:num"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_index(path)
|
|
65
|
+
return path if path.end_with?('.html') || path.end_with?('.htm')
|
|
66
|
+
|
|
67
|
+
"#{path}/#{CollectionPages::INDEXFILE}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_template(path)
|
|
71
|
+
field_count = path.scan(':field').size
|
|
72
|
+
num_count = path.scan(':num').size
|
|
73
|
+
|
|
74
|
+
error_msg = ''
|
|
75
|
+
error_msg += "Path template '#{path}' must include exactly one ':field' placeholder. " if field_count != 1
|
|
76
|
+
error_msg += "Path template '#{path}' must include exactly one ':num' placeholder. " if num_count != 1
|
|
77
|
+
|
|
78
|
+
if num_count.positive? && field_count.positive?
|
|
79
|
+
field_idx = path.index(':field')
|
|
80
|
+
num_idx = path.index(':num')
|
|
81
|
+
error_msg += "In path template '#{path}', ':field' must come before ':num'. " if num_idx < field_idx
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
segments = path.split('/').reject(&:empty?)
|
|
85
|
+
segments.each do |segment|
|
|
86
|
+
if segment.include?(':field') && segment.include?(':num')
|
|
87
|
+
error_msg += "In path template '#{path}', ':field' and ':num' cannot be in the same file segment. "
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
raise ArgumentError, error_msg unless error_msg.empty?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class TagPathResolver
|
|
95
|
+
def initialize(template, field_value, slugify_value: true)
|
|
96
|
+
@template = template
|
|
97
|
+
@value = slugify_value ? Utils.slugify(field_value.to_s) : field_value.to_s
|
|
98
|
+
@segments = template.split('/').reject(&:empty?)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def dir_for(page_number)
|
|
102
|
+
segments = @segments
|
|
103
|
+
segments = apply_field_value(segments)
|
|
104
|
+
segments = page_number == 1 ? remove_paginated_segments(segments) : apply_page_number(segments, page_number)
|
|
105
|
+
segments = drop_file_segment(segments)
|
|
106
|
+
|
|
107
|
+
File.join(*segments)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def filename_for(page_number)
|
|
111
|
+
return CollectionPages::INDEXFILE if page_number == 1
|
|
112
|
+
|
|
113
|
+
segments = @segments.last(1)
|
|
114
|
+
segments = apply_field_value(segments)
|
|
115
|
+
segments = apply_page_number(segments, page_number)
|
|
116
|
+
segments.join
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def url_for(page_number)
|
|
120
|
+
dir = dir_for(page_number)
|
|
121
|
+
filename = filename_for(page_number)
|
|
122
|
+
return formatted_index_path(dir) if filename == CollectionPages::INDEXFILE
|
|
123
|
+
|
|
124
|
+
dir.empty? ? "/#{filename}" : "/#{dir}/#{filename}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def apply_field_value(segments)
|
|
130
|
+
segments.map do |segment|
|
|
131
|
+
segment.include?(':field') ? segment.gsub(':field', @value) : segment
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def apply_page_number(segments, page_number)
|
|
136
|
+
segments.map do |segment|
|
|
137
|
+
segment.include?(':num') ? segment.gsub(':num', page_number.to_s) : segment
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def drop_file_segment(segments)
|
|
142
|
+
if segments.last.end_with?('.html') || segments.last.end_with?('.htm')
|
|
143
|
+
segments[0...-1]
|
|
144
|
+
else
|
|
145
|
+
segments
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def remove_paginated_segments(segments)
|
|
150
|
+
number_index = segments.index { |segment| segment.include?(':num') }
|
|
151
|
+
number_index ? segments[0...number_index] : segments
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def formatted_index_path(dir)
|
|
155
|
+
return '/' if dir.empty?
|
|
156
|
+
|
|
157
|
+
"/#{dir}/"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class TagPagination < Generator
|
|
163
|
+
safe true
|
|
164
|
+
priority :lowest
|
|
165
|
+
|
|
166
|
+
def generate(site)
|
|
167
|
+
return unless site.config['collection_pages']
|
|
168
|
+
|
|
169
|
+
config = site.config['collection_pages']
|
|
170
|
+
|
|
171
|
+
if config.is_a?(Hash)
|
|
172
|
+
Jekyll.logger.debug('CollectionPages:', 'Processing single collection config')
|
|
173
|
+
generate_for_config(site, config)
|
|
174
|
+
elsif config.is_a?(Array)
|
|
175
|
+
Jekyll.logger.debug('CollectionPages:', 'Processing multiple collection config')
|
|
176
|
+
config.each do |collection_config|
|
|
177
|
+
generate_for_config(site, collection_config)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
Jekyll.logger.debug('CollectionPages:', "Generation complete. Total pages: #{site.pages.size}")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def generate_for_config(site, config)
|
|
184
|
+
collection_name = config['collection']
|
|
185
|
+
tag_field = config['field']
|
|
186
|
+
tag_base_path = config['path']
|
|
187
|
+
tag_layout = config['layout'] || 'collection_layout.html'
|
|
188
|
+
per_page = normalize_paginate_value(config['paginate'], collection_name, tag_field)
|
|
189
|
+
Jekyll.logger.debug('CollectionPages:', "Generating pages for collection: #{collection_name}::#{tag_field}")
|
|
190
|
+
|
|
191
|
+
path_template = PathTemplate.new(raw_template: tag_base_path, tag_field: tag_field, collection_name: collection_name)
|
|
192
|
+
|
|
193
|
+
site.data['collection_pages'] ||= {}
|
|
194
|
+
|
|
195
|
+
documents_map, metadata_map = generate_paginated_tags(site, path_template, tag_layout, collection_name, tag_field, per_page)
|
|
196
|
+
|
|
197
|
+
collection_registry = site.data['collection_pages'][collection_name] ||= {}
|
|
198
|
+
collection_registry[tag_field] = {
|
|
199
|
+
'template' => path_template.template,
|
|
200
|
+
'permalink' => path_template.permalink,
|
|
201
|
+
'labels' => metadata_map,
|
|
202
|
+
'pages' => documents_map
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def sorted_tags(site, collection_name, tag_field)
|
|
207
|
+
tags = {}
|
|
208
|
+
collection = site.collections[collection_name]
|
|
209
|
+
return [] unless collection
|
|
210
|
+
|
|
211
|
+
Jekyll.logger.debug('CollectionPages:', "Found collection '#{collection_name}' with #{collection.docs.size} entries.")
|
|
212
|
+
collection.docs.each do |doc|
|
|
213
|
+
doc_tags = doc.data[tag_field]
|
|
214
|
+
next unless doc_tags
|
|
215
|
+
|
|
216
|
+
doc_tags = [doc_tags] if doc_tags.is_a?(String)
|
|
217
|
+
doc_tags.each do |tag|
|
|
218
|
+
tags[tag] ||= []
|
|
219
|
+
tags[tag] << doc
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
tags.keys.sort.map { |tag| [tag, tags[tag]] }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def generate_paginated_tags(site, path_template, tag_layout, collection_name, tag_field, per_page)
|
|
226
|
+
tags_with_docs = sorted_tags(site, collection_name, tag_field)
|
|
227
|
+
|
|
228
|
+
documents_map = {}
|
|
229
|
+
metadata_map = {}
|
|
230
|
+
|
|
231
|
+
tags_with_docs.each do |tag, posts_with_tag|
|
|
232
|
+
tag_path = path_template.for_tag(tag)
|
|
233
|
+
|
|
234
|
+
page_count = TagPager.calculate_pages(posts_with_tag, per_page)
|
|
235
|
+
tag_pages = []
|
|
236
|
+
(1..page_count).each do |page_num|
|
|
237
|
+
paginator = TagPager.new(page_num, per_page, posts_with_tag, tag_path) if per_page
|
|
238
|
+
posts_for_page = paginator ? paginator.posts : posts_with_tag
|
|
239
|
+
page_dir = tag_path.dir_for(page_num)
|
|
240
|
+
page_filename = tag_path.filename_for(page_num)
|
|
241
|
+
tag_page = build_page(site, page_dir, page_filename, tag, tag_layout, posts_for_page, page_num, paginator)
|
|
242
|
+
site.pages << tag_page
|
|
243
|
+
tag_pages << tag_page
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
documents_map[tag] = posts_with_tag
|
|
247
|
+
metadata_map[tag] = {
|
|
248
|
+
'pages' => tag_pages,
|
|
249
|
+
'index' => tag_pages.first
|
|
250
|
+
}
|
|
251
|
+
Jekyll.logger.info('CollectionPages:',
|
|
252
|
+
"Generated #{tag_pages.size} page(s) for tag '#{tag}' in collection '#{collection_name}'.")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
[documents_map, metadata_map]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def build_page(site, dir, page_filename, tag, layout, posts, page_num, paginator = nil)
|
|
259
|
+
TagIndexPage.new(
|
|
260
|
+
site,
|
|
261
|
+
{
|
|
262
|
+
dir: dir,
|
|
263
|
+
name: page_filename,
|
|
264
|
+
tag: tag,
|
|
265
|
+
layout: layout,
|
|
266
|
+
posts: posts,
|
|
267
|
+
page_num: page_num,
|
|
268
|
+
paginator: paginator
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
private
|
|
274
|
+
|
|
275
|
+
def normalize_paginate_value(value, collection_name, tag_field)
|
|
276
|
+
return nil if value.nil?
|
|
277
|
+
|
|
278
|
+
per_page = Integer(value)
|
|
279
|
+
return per_page if per_page.positive?
|
|
280
|
+
|
|
281
|
+
Jekyll.logger.warn('CollectionPages:',
|
|
282
|
+
"Non-positive paginate value #{value.inspect} for collection '#{collection_name}' field '#{tag_field}'. " \
|
|
283
|
+
'Falling back to single page generation.')
|
|
284
|
+
nil
|
|
285
|
+
rescue ArgumentError, TypeError
|
|
286
|
+
raise ArgumentError,
|
|
287
|
+
"Invalid paginate value #{value.inspect} for collection '#{collection_name}' field '#{tag_field}'. Expected a numeric value."
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
class TagIndexPage < PageWithoutAFile
|
|
293
|
+
def initialize(site, attributes)
|
|
294
|
+
dir = attributes[:dir]
|
|
295
|
+
name = attributes[:name]
|
|
296
|
+
tag = attributes[:tag]
|
|
297
|
+
layout = attributes[:layout]
|
|
298
|
+
posts = attributes[:posts]
|
|
299
|
+
page_num = attributes[:page_num]
|
|
300
|
+
paginator = attributes[:paginator]
|
|
301
|
+
|
|
302
|
+
# This sets up a page that has no source file on disk.
|
|
303
|
+
super(site, site.source, dir, name) # also calls process(name) internally
|
|
304
|
+
|
|
305
|
+
self.content = '' # virtual page body (optional)
|
|
306
|
+
|
|
307
|
+
self.data = {
|
|
308
|
+
'layout' => File.basename(layout, '.*'), # layout NAME (no path)
|
|
309
|
+
'tag' => tag,
|
|
310
|
+
'title' => tag.to_s,
|
|
311
|
+
'posts' => posts,
|
|
312
|
+
'page_num' => page_num
|
|
313
|
+
}
|
|
314
|
+
data['paginator'] = paginator if paginator
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
class TagPager
|
|
319
|
+
attr_reader :page, :per_page, :posts, :total_posts, :total_pages,
|
|
320
|
+
:previous_page, :previous_page_path, :next_page, :next_page_path
|
|
321
|
+
|
|
322
|
+
LIQUID_MAP = {
|
|
323
|
+
'page' => :page, # the current page number
|
|
324
|
+
'per_page' => :per_page, # the number of posts per page
|
|
325
|
+
'posts' => :posts, # the paginated posts for this page
|
|
326
|
+
'total_posts' => :total_posts, # the total number of posts being paginated
|
|
327
|
+
'total_pages' => :total_pages, # the total number of pages
|
|
328
|
+
'previous_page' => :previous_page, # the previous page number, or nil
|
|
329
|
+
'previous_page_path' => :previous_page_path, # the previous page path, or nil
|
|
330
|
+
'next_page' => :next_page, # the next page number, or nil
|
|
331
|
+
'next_page_path' => :next_page_path # the next page path, or nil
|
|
332
|
+
}.freeze
|
|
333
|
+
|
|
334
|
+
def self.calculate_pages(all_posts, per_page)
|
|
335
|
+
per_page_value = per_page.to_i
|
|
336
|
+
return 1 if per_page_value <= 0
|
|
337
|
+
|
|
338
|
+
(all_posts.size.to_f / per_page_value).ceil
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def initialize(page_num, per_page, all_posts, path_resolver)
|
|
342
|
+
@page = page_num
|
|
343
|
+
@per_page = per_page.to_i.positive? ? per_page.to_i : 0
|
|
344
|
+
@total_posts = all_posts.size
|
|
345
|
+
@total_pages = self.class.calculate_pages(all_posts, @per_page)
|
|
346
|
+
@posts = slice_posts(all_posts)
|
|
347
|
+
@path_resolver = path_resolver
|
|
348
|
+
@previous_page = previous_page_number
|
|
349
|
+
@next_page = next_page_number(total_pages)
|
|
350
|
+
@previous_page_path = page_path(@previous_page)
|
|
351
|
+
@next_page_path = page_path(@next_page)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def to_liquid
|
|
355
|
+
LIQUID_MAP.transform_values { |reader| public_send(reader) }
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
private
|
|
359
|
+
|
|
360
|
+
attr_reader :path_resolver
|
|
361
|
+
|
|
362
|
+
def slice_posts(all_posts)
|
|
363
|
+
return all_posts if @per_page <= 0
|
|
364
|
+
|
|
365
|
+
start_index = (@page - 1) * @per_page
|
|
366
|
+
all_posts.slice(start_index, @per_page) || []
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def previous_page_number
|
|
370
|
+
@page > 1 ? @page - 1 : nil
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def next_page_number(total_pages)
|
|
374
|
+
@page < total_pages ? @page + 1 : nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def page_path(target_page)
|
|
378
|
+
return unless target_page && target_page <= total_pages
|
|
379
|
+
|
|
380
|
+
path_resolver.url_for(target_page)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-collection-pages
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- allison@allisonthackston.com
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jekyll
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.7'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '3.7'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
description: This Jekyll plugin allows you to generate tag pages for multiple collections,
|
|
34
|
+
with support for pagination.
|
|
35
|
+
email:
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- ".devcontainer/devcontainer.json"
|
|
41
|
+
- ".devcontainer/post-create.sh"
|
|
42
|
+
- ".github/build.sh"
|
|
43
|
+
- ".github/bump.sh"
|
|
44
|
+
- ".github/dependabot.yml"
|
|
45
|
+
- ".github/pr-labeler.yml"
|
|
46
|
+
- ".github/release-drafter.yml"
|
|
47
|
+
- ".github/release.sh"
|
|
48
|
+
- ".github/test.sh"
|
|
49
|
+
- ".github/workflows/ci.yml"
|
|
50
|
+
- ".github/workflows/pr_labeler.yml"
|
|
51
|
+
- ".github/workflows/release.yml"
|
|
52
|
+
- ".github/workflows/release_draft.yml"
|
|
53
|
+
- ".github/workflows/site.yml"
|
|
54
|
+
- ".gitignore"
|
|
55
|
+
- ".rubocop.yml"
|
|
56
|
+
- ".rubocop_todo.yml"
|
|
57
|
+
- ".ruby-version"
|
|
58
|
+
- ".vscode/settings.json"
|
|
59
|
+
- ".vscode/tasks.json"
|
|
60
|
+
- CODE_OF_CONDUCT.md
|
|
61
|
+
- CONTRIBUTING.md
|
|
62
|
+
- Gemfile
|
|
63
|
+
- LICENSE
|
|
64
|
+
- README.md
|
|
65
|
+
- Rakefile
|
|
66
|
+
- VERSION
|
|
67
|
+
- demo/CODE_OF_CONDUCT.md
|
|
68
|
+
- demo/_articles/jekyll-collection-pages-comparison.md
|
|
69
|
+
- demo/_articles/jekyll-collection-pages-indices.md
|
|
70
|
+
- demo/_articles/jekyll-for-documentation.md
|
|
71
|
+
- demo/_articles/linking-obsidian-and-jekyll.md
|
|
72
|
+
- demo/_config.yml
|
|
73
|
+
- demo/_docs/configuration-guide.md
|
|
74
|
+
- demo/_docs/examples.md
|
|
75
|
+
- demo/_docs/generated-data.md
|
|
76
|
+
- demo/_docs/layout-recipes.md
|
|
77
|
+
- demo/_docs/quick-start.md
|
|
78
|
+
- demo/_docs/troubleshooting.md
|
|
79
|
+
- demo/_layouts/collection_layout.html
|
|
80
|
+
- demo/_layouts/tags.html
|
|
81
|
+
- demo/articles.md
|
|
82
|
+
- demo/assets/img/articles.png
|
|
83
|
+
- demo/assets/img/docs.png
|
|
84
|
+
- demo/assets/img/jekyll-collection-pages-preview.png
|
|
85
|
+
- demo/assets/img/jekyll-collection-pages.png
|
|
86
|
+
- demo/assets/img/post-img-1.png
|
|
87
|
+
- demo/assets/img/post-img-2.png
|
|
88
|
+
- demo/assets/img/post-img-3.png
|
|
89
|
+
- demo/assets/img/post-img-4.png
|
|
90
|
+
- demo/contributing.md
|
|
91
|
+
- demo/directory.md
|
|
92
|
+
- demo/docs.md
|
|
93
|
+
- demo/gallery.md
|
|
94
|
+
- demo/index.md
|
|
95
|
+
- demo/tags.md
|
|
96
|
+
- jekyll-collection-pages.gemspec
|
|
97
|
+
- lib/jekyll-collection-pages.rb
|
|
98
|
+
- lib/jekyll/collection_pages.rb
|
|
99
|
+
homepage: https://github.com/PrimerPages/jekyll-collection-pages
|
|
100
|
+
licenses:
|
|
101
|
+
- MIT
|
|
102
|
+
metadata:
|
|
103
|
+
source_code_uri: https://github.com/PrimerPages/jekyll-collection-pages
|
|
104
|
+
bug_tracker_uri: https://github.com/PrimerPages/jekyll-collection-pages/issues
|
|
105
|
+
documentation_uri: https://primerpages.github.io/jekyll-collection-pages/
|
|
106
|
+
rubygems_mfa_required: 'true'
|
|
107
|
+
post_install_message:
|
|
108
|
+
rdoc_options: []
|
|
109
|
+
require_paths:
|
|
110
|
+
- lib
|
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: 2.7.0
|
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
requirements: []
|
|
122
|
+
rubygems_version: 3.5.11
|
|
123
|
+
signing_key:
|
|
124
|
+
specification_version: 4
|
|
125
|
+
summary: A Jekyll plugin for generating tag pages for multiple collections
|
|
126
|
+
test_files: []
|