-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathmetalsmith-pagination.js
218 lines (179 loc) · 5.29 KB
/
metalsmith-pagination.js
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
var toFn = require('to-function')
var extend = require('xtend')
/**
* Page collection defaults.
*
* @type {Object}
*/
var DEFAULTS = {
perPage: 10,
noPageOne: false
}
/**
* Paginate based on the collection.
*
* @param {Object} options
* @return {Function}
*/
module.exports = function (options) {
var keys = Object.keys(options)
return function (files, metalsmith, done) {
var metadata = metalsmith.metadata()
// Iterate over all the paginate names and match with collections.
var complete = keys.every(function (name) {
var collection
var pageOptions = extend(DEFAULTS, options[name])
// Catch nested collection reference errors.
try {
collection = toFn(name)(metadata)
} catch (error) {}
if (!collection) {
done(new TypeError('Collection not found (' + name + ')'))
return false
}
var toShow = collection
var groupBy = toFn(pageOptions.groupBy || groupByPagination)
if (pageOptions.filter) {
toShow = collection.filter(toFn(pageOptions.filter))
}
if (!pageOptions.template && !pageOptions.layout && !pageOptions.pageContents) {
done(new TypeError('A template, layout or pageContents option is required (' + name + ')'))
return false
}
if (pageOptions.template && pageOptions.layout) {
done(new TypeError(
'Template and layout can not be used simultaneously (' + name + ')'
))
return false
}
if (!pageOptions.path) {
done(new TypeError('The path is required (' + name + ')'))
return false
}
// Can't specify both
if (pageOptions.noPageOne && !pageOptions.first) {
done(new TypeError(
'When `noPageOne` is enabled, a first page must be set (' + name + ')'
))
return false
}
// Put a `pages` property on the original collection.
var pages = collection.pages = []
var pageMap = {}
if (toShow.length === 0 && pageOptions.empty) {
var pageName = String(groupBy(pageOptions.empty, 0, pageOptions))
pageMap[pageName] = createPagination(pages, files, pageName, pageOptions)
}
// Sort pages into "categories".
toShow.forEach(function (file, index) {
var name = String(groupBy(file, index, pageOptions))
// Keep pages in the order they are returned. E.g. Allows sorting
// by published year to work.
if (!Object.prototype.hasOwnProperty.call(pageMap, name)) {
const pagination = createPagination(pages, files, name, pageOptions)
pageMap[name] = pagination
// Copy collection metadata onto every page "collection".
pagination.files.metadata = collection.metadata
}
// Files are kept sorted within their own category.
pageMap[name].files.push(file)
})
// Add page utilities.
pages.forEach(function (page) {
page.pagination.first = pages[0]
page.pagination.last = pages[pages.length - 1]
})
return true
})
return complete && done()
}
}
/**
* Create a pagination object.
*
* @param {Array} pages
* @param {Array} files
* @param {String} name
* @param {Object} pageOptions
*/
function createPagination (pages, files, name, pageOptions) {
var length = pages.length
var pagination = {
name: name,
index: length,
num: length + 1,
pages: pages,
files: [],
getPages: createPagesUtility(pages, length)
}
// Generate the page data.
var page = extend(pageOptions.pageMetadata, {
template: pageOptions.template,
layout: pageOptions.layout,
contents: Buffer.from(pageOptions.pageContents || ''),
path: interpolate(pageOptions.path, pagination),
pagination: pagination
})
if (length === 0) {
if (!pageOptions.noPageOne) {
files[page.path] = page
}
if (pageOptions.first) {
// Extend the "first page" over the top of "page one".
page = extend(page, {
path: interpolate(pageOptions.first, page.pagination)
})
files[page.path] = page
}
} else {
files[page.path] = page
page.pagination.previous = pages[length - 1]
pages[length - 1].pagination.next = page
}
pages.push(page)
return pagination
}
/**
* Interpolate the page path with pagination variables.
*
* @param {String} path
* @param {Object} data
* @return {String}
*/
function interpolate (path, data) {
return path.replace(/:(\w+)/g, function (match, param) {
return data[param]
})
}
/**
* Group by pagination by default.
*
* @param {Object} file
* @param {number} index
* @param {Object} options
* @return {number}
*/
function groupByPagination (file, index, options) {
return Math.ceil((index + 1) / options.perPage)
}
/**
* Create a "get pages" utility for people to use when rendering.
*
* @param {Array} pages
* @param {number} index
* @return {Function}
*/
function createPagesUtility (pages, index) {
return function getPages (number) {
var offset = Math.floor(number / 2)
var start, end
if (index + offset >= pages.length) {
start = Math.max(0, pages.length - number)
end = pages.length
} else {
start = Math.max(0, index - offset)
end = Math.min(start + number, pages.length)
}
return pages.slice(start, end)
}
}