/
paginator.rb
163 lines (129 loc) · 6.09 KB
/
paginator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# frozen_string_literal: true
require 'middleman-blog/uri_templates'
module Middleman
module Blog
# A sitemap plugin that splits indexes (including tag
# and calendar indexes) over multiple pages
class Paginator
include UriTemplates
def initialize(app, blog_controller)
@app = app
@blog_controller = blog_controller
@per_page = blog_controller.options.per_page
@page_link = blog_controller.options.page_link
end
# Update the main sitemap resource list
# @return [void]
def manipulate_resource_list(resources)
new_resources = []
resources.each do |res|
next if res.ignored?
# Avoid recomputing metadata over and over
md = res.metadata
next unless md[:page][:pageable]
# Skip other blogs' resources
next unless match_blog(res, md)
# "articles" local variable is populated by Calendar and Tag page generators
# If it's not set then use the complete list of articles
# TODO: Some way to allow the frontmatter to specify the article filter?
articles = md[:locals]['articles'] || @blog_controller.data.articles
articles.select! { |article| article.lang == md[:options][:locale] } if md.fetch(:options, false) && md[:options].fetch(:locale, false)
# Allow blog.per_page and blog.page_link to be overridden in the frontmatter
per_page = md[:page][:per_page] || @per_page
page_link = uri_template(md[:page][:page_link] || @page_link)
num_pages = (articles.length / per_page.to_f).ceil
# Add the pagination metadata to the base page (page 1)
res.add_metadata locals: page_locals(1, num_pages, per_page, nil, articles)
prev_page_res = res
# Create additional resources for the 2nd and subsequent pages.
2.upto(num_pages) do |page_num|
p = page_resource(res, page_num, page_link)
# Copy the metadata from the base page
p.add_metadata md
p.add_metadata locals: page_locals(page_num, num_pages, per_page, prev_page_res, articles)
# Add a reference in the previous page to this page
prev_page_res.add_metadata locals: { 'next_page' => p }
prev_page_res = p
new_resources << p
end
end
resources + new_resources
end
private
# Does this resource match the blog controller for this paginator?
# @return [Boolean]
def match_blog(res, md)
res_controller = md[:locals]['blog_controller'] || (res.respond_to?(:blog_controller) && res.blog_controller)
return false if res_controller && res_controller != @blog_controller
override_controller = md[:page][:blog]
return false if override_controller && override_controller.to_s != @blog_controller.name.to_s
true
end
# Generate a resource for a particular page
# @param [Sitemap::Resource] res the original resource
# @param [Integer] page_num the page number to generate a resource for
# @param [String] page_link The pagination link path component template
def page_resource(res, page_num, page_link)
path = page_sub(res, page_num, page_link)
if res.is_a? Sitemap::ProxyResource
Sitemap::ProxyResource.new(@app.sitemap, path, res.target)
else
Sitemap::Resource.new(@app.sitemap, path, res.source_file)
end
end
# @param [Integer] page_num the page number to generate a resource for
# @param [Integer] num_pages Total number of pages
# @param [Integer] per_page How many articles per page
# @param [Sitemap::Resource] prev_page_res The resource of the previous page
# @param [Array<Sitemap::Resource>] articles The list of all articles
def page_locals(page_num, num_pages, per_page, prev_page_res, articles)
# Index into articles of the first article of this page
page_start = (page_num - 1) * per_page
# Index into articles of the last article of this page
page_end = (page_num * per_page) - 1
{
# Set a flag to allow templates to be used with and without pagination
'paginate' => true,
# Include the numbers, useful for displaying "Page X of Y"
'page_number' => page_num,
'num_pages' => num_pages,
'per_page' => per_page,
# The range of article numbers on this page
# (1-based, for showing "Items X to Y of Z")
'page_start' => page_start + 1,
'page_end' => [page_end + 1, articles.length].min,
# These contain the next and previous page.
# They are set to nil if there are no more pages.
# The nils are overwritten when the later pages are generated, below.
'next_page' => nil,
'prev_page' => prev_page_res,
# The list of articles for this page.
'page_articles' => articles[page_start..page_end],
# Include the articles so that non-proxied pages can use "articles" instead
# of "blog.articles" for consistency with the calendar and tag templates.
'articles' => articles,
'blog_controller' => @blog_controller
}
end
# Substitute the page number into the resource URL.
# @param [Middleman::Sitemap::Resource] res The resource to generate pages for
# @param [Integer] page_num The page page_number
# @param [String] page_link The pagination link path component template
# @return [String]
def page_sub(res, page_num, page_link)
if page_num == 1
# First page has an unmodified URL.
res.path
else
page_url = apply_uri_template page_link, num: page_num
index_re = %r{(^|/)#{Regexp.escape(@app.config[:index_file])}$}
if res.path&.match?(index_re)
res.path.sub(index_re, "\\1#{page_url}/#{@app.config[:index_file]}")
else
res.path.sub(%r{(^|/)([^/]*)\.([^/]*)$}, "\\1\\2/#{page_url}.\\3")
end
end
end
end
end
end