coffeebrew_jekyll_paginate 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/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +704 -0
- data/lib/coffeebrew_jekyll_paginate/config.yml +13 -0
- data/lib/coffeebrew_jekyll_paginate/generator.rb +113 -0
- data/lib/coffeebrew_jekyll_paginate/individual_paginator.rb +50 -0
- data/lib/coffeebrew_jekyll_paginate/page.rb +110 -0
- data/lib/coffeebrew_jekyll_paginate/page_drop.rb +16 -0
- data/lib/coffeebrew_jekyll_paginate/paginator.rb +82 -0
- data/lib/coffeebrew_jekyll_paginate/validator.rb +136 -0
- data/lib/coffeebrew_jekyll_paginate/version.rb +9 -0
- data/lib/coffeebrew_jekyll_paginate.rb +11 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3ea621dece76bb070ca7414e7ae18bba2b01729d3e4be3a58f16dfd040e091bf
|
4
|
+
data.tar.gz: ba842e477f22c13151198164443cafdca41be95ad8ced809d41655f124bcb9ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4c427ece85c3bc8c60737ebc47d7c130a7de6907bda74abb164184fe735b3d16f5244e448191b17488798ff4573191b9a2c256739201ca50a21ebcdacc36e0f
|
7
|
+
data.tar.gz: 007e60e476b29d01456e5cf6675f1cd48c4c49d4ebb68f59317c4867e4fbf6e5524f10549107352940ebfdedbf880eef0844ec6e6df2d468ac76fadb08bfef9e
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Coffee Brew Apps
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,704 @@
|
|
1
|
+
# Jekyll Paginate Plugin
|
2
|
+
|
3
|
+
A Jekyll plugin to generate site pagination for collections.
|
4
|
+
|
5
|
+
[](https://github.com/coffeebrewapps/coffeebrew_jekyll_paginate/actions/workflows/ruby.yml) [](https://badge.fury.io/rb/coffeebrew_jekyll_paginate)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your site's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'coffeebrew_jekyll_paginate'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then add this line to your site's `_config.yml`:
|
16
|
+
|
17
|
+
```yml
|
18
|
+
plugins:
|
19
|
+
- coffeebrew_jekyll_paginate
|
20
|
+
```
|
21
|
+
|
22
|
+
By default, the plugin doesn't generate any pagination. You need to specify which collections to be paginated. For
|
23
|
+
example,
|
24
|
+
|
25
|
+
```yml
|
26
|
+
---
|
27
|
+
coffeebrew_jekyll_paginate:
|
28
|
+
collections:
|
29
|
+
books:
|
30
|
+
```
|
31
|
+
|
32
|
+
Assuming layouts have been setup (see more in [Layouts](#layouts)), then, the plugin will generate paginated
|
33
|
+
collections using the default config below:
|
34
|
+
|
35
|
+
```yml
|
36
|
+
individual_page_pagination: false
|
37
|
+
frontmatter_defaults_key: "paginated_%{collection_type}"
|
38
|
+
first_page_as_root:
|
39
|
+
enabled: false
|
40
|
+
permalink: /:collection_type/:page_num_one_index
|
41
|
+
index_page: "index"
|
42
|
+
per_page: 5
|
43
|
+
sort_field: "date"
|
44
|
+
sort_reverse: true
|
45
|
+
page_num_label: "%{page_num_one_index}"
|
46
|
+
```
|
47
|
+
|
48
|
+
An example of the generated directory structure is as such:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
_site/
|
52
|
+
├── books
|
53
|
+
│ ├── 1
|
54
|
+
│ │ ├── index.html
|
55
|
+
│ └── 2
|
56
|
+
│ └── index.html
|
57
|
+
└── index.html
|
58
|
+
```
|
59
|
+
|
60
|
+
## Configuration
|
61
|
+
|
62
|
+
You can configure for as many collections as needed, and configure a site-wide pagination config, or configure for each
|
63
|
+
collection to use its own configuration. If there is no configuration for the site default, or the collection, the
|
64
|
+
plugin will use the default values as previously mentioned.
|
65
|
+
|
66
|
+
### Site-wide defaults
|
67
|
+
|
68
|
+
For example, you can configure site-wide defaults that override the plugin's defaults, which will be used for all the
|
69
|
+
collections enabled.
|
70
|
+
|
71
|
+
```yml
|
72
|
+
coffeebrew_jekyll_paginate:
|
73
|
+
defaults:
|
74
|
+
individual_page_pagination: true
|
75
|
+
first_page_as_root:
|
76
|
+
enabled: true
|
77
|
+
permalink: /
|
78
|
+
index_page: /:collection_type
|
79
|
+
per_page: 3
|
80
|
+
page_num_label: "Page %{page_num_one_index}"
|
81
|
+
collections:
|
82
|
+
books:
|
83
|
+
posts:
|
84
|
+
```
|
85
|
+
|
86
|
+
If you do override the configuration, the plugin will perform a simple validation on your overrides according to these rules:
|
87
|
+
|
88
|
+
### Frontmatter defaults key config
|
89
|
+
|
90
|
+
This tells the plugin what is the key used for setting defaults. For example, the plugin will use `paginated_posts` for
|
91
|
+
`posts` type by default, and this will allow Jekyll to lookup defaults for `type: "paginated_posts"` in `_config.yml`.
|
92
|
+
|
93
|
+
```yml
|
94
|
+
defaults:
|
95
|
+
- scope:
|
96
|
+
path: ""
|
97
|
+
type: "paginated_posts"
|
98
|
+
values:
|
99
|
+
layout: "posts_index"
|
100
|
+
```
|
101
|
+
|
102
|
+
### Individual page pagination config
|
103
|
+
|
104
|
+
If set to `true`, this tells the plugin to generate previous/next pagination for individual collection pages. This is
|
105
|
+
useful if you want to have individual page-level pagination to navigate to the previous or next page. For example:
|
106
|
+
|
107
|
+
```yml
|
108
|
+
---
|
109
|
+
layout: default
|
110
|
+
---
|
111
|
+
<h1>Posts</h1>
|
112
|
+
{% raw %}
|
113
|
+
{{ content }}
|
114
|
+
|
115
|
+
<div class="pagination">
|
116
|
+
<ul class="pager">
|
117
|
+
<li class="previous">
|
118
|
+
<a href="{{ page.paginator.previous_page_path }}">< {{ page.paginator.previous_page.title }}</a>
|
119
|
+
</li>
|
120
|
+
<li class="next">
|
121
|
+
<a href="{{ page.paginator.next_page_path }}">{{ page.paginator.next_page.title }} ></a>
|
122
|
+
</li>
|
123
|
+
</ul>
|
124
|
+
</div>
|
125
|
+
{% endraw %}
|
126
|
+
```
|
127
|
+
|
128
|
+
### First page as root config
|
129
|
+
|
130
|
+
This tells the plugin to generate the first page outside of the collection directory in the paginated set. For example,
|
131
|
+
if there are 4 pages in the paginated books collection, then the first page will be rendered in `/books.html`, and the
|
132
|
+
other pages will be rendered as `/books/2/index.html`, `/books/3/index.html` and `/books/4/index.html`.
|
133
|
+
|
134
|
+
This will be useful if you want to have the first page as part of your root pages and include it in the navigation.
|
135
|
+
|
136
|
+
| Key | Allowed Value(s) | Default | Remark |
|
137
|
+
| --- | --- | --- | --- |
|
138
|
+
| enabled | Boolean | false | If set to `true`, then the plugin will render the first page in a separate path as defined by the `permalink` and `index_page` below. |
|
139
|
+
| permalink | String | nil | The directory in which to generate the first page. Normally this will be `/` if you want it to be a root-level page. |
|
140
|
+
| index_page | String | nil | The filename of the first page. Normally this will be the name corresponding to the collection. |
|
141
|
+
|
142
|
+
If `enabled` is `false`, both `permalink` and `index_page` will be ignored, and the plugin will generate the first page
|
143
|
+
the same way as the remaining pages.
|
144
|
+
|
145
|
+
### Pagination config
|
146
|
+
|
147
|
+
This tells the plugin how to generate the pagination pages.
|
148
|
+
|
149
|
+
| Key | Allowed Value(s) | Default | Remark |
|
150
|
+
| --- | --- | --- | --- |
|
151
|
+
| permalink | String | /:collection_type/:page_num_one_index | The directory in which to generate the page. |
|
152
|
+
| index_page | String | index | The filename of the page. |
|
153
|
+
| per_page | Integer | 5 | The number of collection items in each page. |
|
154
|
+
| sort_field | String | date | The field to be used to sort the collection for deterministic pagination. |
|
155
|
+
| sort_reverse | Boolean | true | The collection will be sorted in reverse if set to `true`. |
|
156
|
+
| page_num_label | String | %{page_num_one_index} | The format string for the page number label. |
|
157
|
+
|
158
|
+
A few placeholders are available to be used in the `permalink`, `index_page` and `page_num_label`:
|
159
|
+
|
160
|
+
| Field | Description |
|
161
|
+
| --- | --- |
|
162
|
+
| collection_type | This is the collection type current page, eg. `posts`. |
|
163
|
+
| page_num_zero_index | This will be the 0-index page number of the current page. |
|
164
|
+
| page_num_one_index | This will be the 1-index page number of the current page. |
|
165
|
+
|
166
|
+
### Validation
|
167
|
+
|
168
|
+
If the config overrides have invalid structure, keys or values, the plugin will raise a
|
169
|
+
`Jekyll::Errors::InvalidConfigurationError` during build.
|
170
|
+
|
171
|
+
## Layouts
|
172
|
+
|
173
|
+
The plugin does not provide a default layout. You will need to create your own layout in `_layouts` and configure the
|
174
|
+
defaults in `_config.yml`, for example, if you want to paginate the `books` collection, then you need to configure
|
175
|
+
the layout for the collection:
|
176
|
+
|
177
|
+
```yml
|
178
|
+
---
|
179
|
+
defaults:
|
180
|
+
- scope:
|
181
|
+
type: "paginated_books"
|
182
|
+
values:
|
183
|
+
layout: "books_pagination"
|
184
|
+
- scope:
|
185
|
+
type: "books"
|
186
|
+
values:
|
187
|
+
layout: "book"
|
188
|
+
permalink: /books/:name:output_ext
|
189
|
+
```
|
190
|
+
|
191
|
+
As mentioned earlier, you need to set the `scope.type` here to match the `frontmatters_defaults_key` config of the
|
192
|
+
plugin. Note that there are 2 layouts configured here, `books_pagination` is used for the paginated collection page,
|
193
|
+
and `book` is used for the individual book page.
|
194
|
+
|
195
|
+
In addition to Jekyll's default page data, you can also use the additional page data and page's paginator data generated
|
196
|
+
by the plugin in the layout:
|
197
|
+
|
198
|
+
### Paginated collection page
|
199
|
+
|
200
|
+
Use `page` to access the fields below.
|
201
|
+
|
202
|
+
| Field | Description |
|
203
|
+
| --- | --- |
|
204
|
+
| title | Current page's title. |
|
205
|
+
| collection | Current page's collection. |
|
206
|
+
| collection_type | Current page's collection type. |
|
207
|
+
| page_num_zero_index | Current page's 0-index page number. |
|
208
|
+
| page_num_one_index | Current page's 1-index page number. |
|
209
|
+
| page_num_label | Current page's page number label. |
|
210
|
+
| full_url | Current page full url. Can be used to match the current window url for highlighting purpose. |
|
211
|
+
|
212
|
+
### Paginated collection paginator
|
213
|
+
|
214
|
+
Use `page.paginator` to acceess the fields below.
|
215
|
+
|
216
|
+
| Field | Description |
|
217
|
+
| --- | --- |
|
218
|
+
| total_pages | Total number of pages of the paginated collection. |
|
219
|
+
| pages | All the pages in the collection. |
|
220
|
+
| page_num_zero_index | Current page's 0-index page number. |
|
221
|
+
| page_num_one_index | Current page's 1-index page number. |
|
222
|
+
| page_num_label | Current page's page number label. |
|
223
|
+
| collection | Current page's collection. |
|
224
|
+
| collection_type | Current page's collection type. |
|
225
|
+
| current_page | Current page. |
|
226
|
+
| current_page_path | Current page full url. Can be used to match the current window url for highlighting purpose. |
|
227
|
+
| previous_page | Previous page. |
|
228
|
+
| previous_page_path | Previous page full url. Can be used to generate navigation link. |
|
229
|
+
| next_page | Next page. |
|
230
|
+
| next_page_path | Next page full url. Can be used to generate navigation link. |
|
231
|
+
|
232
|
+
An example of the paginated collection page layout `books_pagination`:
|
233
|
+
|
234
|
+
```html
|
235
|
+
---
|
236
|
+
layout: default
|
237
|
+
---
|
238
|
+
<h1>Books</h1>
|
239
|
+
<div class="collection">
|
240
|
+
{% raw %}
|
241
|
+
{% for book in page.paginator.collection %}
|
242
|
+
<div class="item">
|
243
|
+
<div class="title">
|
244
|
+
<span>{{ book.title }}</span>
|
245
|
+
</div>
|
246
|
+
<p>{{ book.excerpt }}</p>
|
247
|
+
</div>
|
248
|
+
{% endfor %}
|
249
|
+
</div>
|
250
|
+
|
251
|
+
{% if page.paginator.total_pages > 1 %}
|
252
|
+
<div class="pagination">
|
253
|
+
<ul class="pager">
|
254
|
+
{% if page.paginator.previous_page %}
|
255
|
+
<li class="previous">
|
256
|
+
<a href="{{ page.paginator.previous_page_path }}"><i class="fa-solid fa-arrow-left"></i> Newer {{ page.paginator.collection_type | capitalize }}</a>
|
257
|
+
</li>
|
258
|
+
{% endif %}
|
259
|
+
{% if page.paginator.next_page %}
|
260
|
+
<li class="next">
|
261
|
+
<a href="{{ page.paginator.next_page_path }}">Older {{ page.paginator.collection_type | capitalize }} <i class="fa-solid fa-arrow-right"></i></a>
|
262
|
+
</li>
|
263
|
+
{% endif %}
|
264
|
+
</ul>
|
265
|
+
</div>
|
266
|
+
{% endif %}
|
267
|
+
{% endraw %}
|
268
|
+
```
|
269
|
+
|
270
|
+
The resulting index pages using the example configuration and layout are as such:
|
271
|
+
|
272
|
+
### Root page
|
273
|
+
|
274
|
+
Generated at: `_site/books.html`.
|
275
|
+
|
276
|
+
```html
|
277
|
+
<!DOCTYPE html>
|
278
|
+
<html>
|
279
|
+
<head>
|
280
|
+
<meta charset="utf-8">
|
281
|
+
<title>Books</title>
|
282
|
+
</head>
|
283
|
+
<body>
|
284
|
+
<div class="container">
|
285
|
+
<h1>Books</h1>
|
286
|
+
<div class="collection">
|
287
|
+
<div class="item">
|
288
|
+
<div class="title">
|
289
|
+
<span>Harry Potter 7</span>
|
290
|
+
</div>
|
291
|
+
<p>This is the final book in the Harry Potter series.</p>
|
292
|
+
</div>
|
293
|
+
|
294
|
+
<div class="item">
|
295
|
+
<div class="title">
|
296
|
+
<span>Harry Potter 6</span>
|
297
|
+
</div>
|
298
|
+
<p>This is the sixth book in the Harry Potter series.</p>
|
299
|
+
</div>
|
300
|
+
|
301
|
+
<div class="item">
|
302
|
+
<div class="title">
|
303
|
+
<span>Harry Potter 5</span>
|
304
|
+
</div>
|
305
|
+
<p>This is the fifth book in the Harry Potter series.</p>
|
306
|
+
</div>
|
307
|
+
</div>
|
308
|
+
|
309
|
+
<div class="pagination">
|
310
|
+
<ul class="pager">
|
311
|
+
<li class="next">
|
312
|
+
<a href="/books/2/index.html">Older Books <i class="fa-solid fa-arrow-right"></i></a>
|
313
|
+
</li>
|
314
|
+
</ul>
|
315
|
+
</div>
|
316
|
+
</div>
|
317
|
+
</body>
|
318
|
+
</html>
|
319
|
+
```
|
320
|
+
|
321
|
+
### Next page
|
322
|
+
|
323
|
+
Generated at: `_site/books/2/index.html`.
|
324
|
+
|
325
|
+
Note: Header elements omitted for clarity.
|
326
|
+
|
327
|
+
```html
|
328
|
+
<div class="container">
|
329
|
+
<h1>Books</h1>
|
330
|
+
<div class="collection">
|
331
|
+
<div class="item">
|
332
|
+
<div class="title">
|
333
|
+
<span>Harry Potter 4</span>
|
334
|
+
</div>
|
335
|
+
<p>This is the fourth book in the Harry Potter series.</p>
|
336
|
+
</div>
|
337
|
+
|
338
|
+
<div class="item">
|
339
|
+
<div class="title">
|
340
|
+
<span>Harry Potter 3</span>
|
341
|
+
</div>
|
342
|
+
<p>This is the third book in the Harry Potter series.</p>
|
343
|
+
</div>
|
344
|
+
|
345
|
+
<div class="item">
|
346
|
+
<div class="title">
|
347
|
+
<span>Harry Potter 2</span>
|
348
|
+
</div>
|
349
|
+
<p>This is the second book in the Harry Potter series.</p>
|
350
|
+
</div>
|
351
|
+
</div>
|
352
|
+
|
353
|
+
<div class="pagination">
|
354
|
+
<ul class="pager">
|
355
|
+
<li class="previous">
|
356
|
+
<a href="/books.html"><i class="fa-solid fa-arrow-left"></i> Newer Books</a>
|
357
|
+
</li>
|
358
|
+
<li class="next">
|
359
|
+
<a href="/books/3/index.html">Older Books <i class="fa-solid fa-arrow-right"></i></a>
|
360
|
+
</li>
|
361
|
+
</ul>
|
362
|
+
</div>
|
363
|
+
</div>
|
364
|
+
```
|
365
|
+
|
366
|
+
### Last page
|
367
|
+
|
368
|
+
Generated at: `_site/books/3/index.html`.
|
369
|
+
|
370
|
+
Note: Header elements omitted for clarity.
|
371
|
+
|
372
|
+
```html
|
373
|
+
<div class="container">
|
374
|
+
<h1>Books</h1>
|
375
|
+
<div class="collection">
|
376
|
+
<div class="item">
|
377
|
+
<div class="title">
|
378
|
+
<span>Harry Potter 1</span>
|
379
|
+
</div>
|
380
|
+
<p>This is the first book in the Harry Potter series.</p>
|
381
|
+
</div>
|
382
|
+
</div>
|
383
|
+
|
384
|
+
<div class="pagination">
|
385
|
+
<ul class="pager">
|
386
|
+
<li class="previous">
|
387
|
+
<a href="/books/2/index.html"><i class="fa-solid fa-arrow-left"></i> Newer Books</a>
|
388
|
+
</li>
|
389
|
+
</ul>
|
390
|
+
</div>
|
391
|
+
</div>
|
392
|
+
```
|
393
|
+
|
394
|
+
### Individual page paginator
|
395
|
+
|
396
|
+
Use `page.paginator` to access the fields below.
|
397
|
+
|
398
|
+
| Field | Description |
|
399
|
+
| --- | --- |
|
400
|
+
| collection_type | Current page's collection type. |
|
401
|
+
| current_page | Current page. |
|
402
|
+
| current_page_path | Current page full url. Can be used to match the current window url for highlighting purpose. |
|
403
|
+
| previous_page | Previous page. |
|
404
|
+
| previous_page_path | Previous page full url. Can be used to generate navigation link. |
|
405
|
+
| next_page | Next page. |
|
406
|
+
| next_page_path | Next page full url. Can be used to generate navigation link. |
|
407
|
+
|
408
|
+
An example of the individual page layout `book`:
|
409
|
+
|
410
|
+
```html
|
411
|
+
---
|
412
|
+
layout: default
|
413
|
+
---
|
414
|
+
<h1>{{ page.title }}</h1>
|
415
|
+
{% raw %}
|
416
|
+
<div class="title">
|
417
|
+
<span>{{ page.title }}</span>
|
418
|
+
</div>
|
419
|
+
<p>{{ content }}</p>
|
420
|
+
|
421
|
+
{% if page.paginator %}
|
422
|
+
<div class="pagination">
|
423
|
+
<ul class="pager">
|
424
|
+
{% if page.paginator.previous_page %}
|
425
|
+
<li class="previous">
|
426
|
+
<a href="{{ page.paginator.previous_page_path }}"><i class="fa-solid fa-arrow-left"></i> Previous Book</a>
|
427
|
+
</li>
|
428
|
+
{% endif %}
|
429
|
+
{% if page.paginator.next_page %}
|
430
|
+
<li class="next">
|
431
|
+
<a href="{{ page.paginator.next_page_path }}">Next Book <i class="fa-solid fa-arrow-right"></i></a>
|
432
|
+
</li>
|
433
|
+
{% endif %}
|
434
|
+
</ul>
|
435
|
+
</div>
|
436
|
+
{% endif %}
|
437
|
+
{% endraw %}
|
438
|
+
```
|
439
|
+
|
440
|
+
The resulting page using the example configuration and layout are as such:
|
441
|
+
|
442
|
+
### Individual page
|
443
|
+
|
444
|
+
Generated at: `_site/books/book-1.html`
|
445
|
+
|
446
|
+
```html
|
447
|
+
<!DOCTYPE html>
|
448
|
+
<html>
|
449
|
+
<head>
|
450
|
+
<meta charset="utf-8">
|
451
|
+
<title>Books</title>
|
452
|
+
</head>
|
453
|
+
<body>
|
454
|
+
<div class="container">
|
455
|
+
<h1>Book 1</h1>
|
456
|
+
<div class="title">
|
457
|
+
<span>Book 1</span>
|
458
|
+
</div>
|
459
|
+
<p>This is book 1.</p>
|
460
|
+
|
461
|
+
<div class="pagination">
|
462
|
+
<ul class="pager">
|
463
|
+
<li class="next">
|
464
|
+
<a href="/books/book-2.html">Next Book <i class="fa-solid fa-arrow-right"></i></a>
|
465
|
+
</li>
|
466
|
+
</ul>
|
467
|
+
</div>
|
468
|
+
</div>
|
469
|
+
</body>
|
470
|
+
</html>
|
471
|
+
```
|
472
|
+
|
473
|
+
## Contributing
|
474
|
+
|
475
|
+
Contribution to the gem is very much welcome!
|
476
|
+
|
477
|
+
1. Fork it (https://github.com/coffeebrewapps/coffeebrew_jekyll_paginate/fork).
|
478
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
479
|
+
3. Make sure you have setup the repo correctly so that you can run RSpec and Rubocop on your changes. Read more under the [Development](#development) section.
|
480
|
+
4. Commit your changes (`git commit -am 'Add some feature'`).
|
481
|
+
5. If you have added something that is worth mentioning in the README, please also update the README.md accordingly and commit the changes.
|
482
|
+
6. Push to the branch (`git push origin my-new-feature`).
|
483
|
+
7. Create a new Pull Request.
|
484
|
+
|
485
|
+
The repo owner will try to respond to a new PR as soon as possible.
|
486
|
+
|
487
|
+
## Development
|
488
|
+
|
489
|
+
We want to provide a robust gem as much as possible for the users, so writing test cases will be required for any new
|
490
|
+
feature.
|
491
|
+
|
492
|
+
If you are contributing to the gem, please make sure you have setup your development environment correctly so that
|
493
|
+
RSpec and Rubocop can run properly.
|
494
|
+
|
495
|
+
1. After forking the repo, go into the repo directory (`cd coffeebrew_jekyll_paginate/`).
|
496
|
+
2. Make sure you have the correct Ruby version installed. This gem requires Ruby >= 2.7.0.
|
497
|
+
3. Once you have the correct Ruby version, install the development dependencies (`bundle install`).
|
498
|
+
4. To test that you have everything installed correctly, run the test cases (`bundle exec rspec`).
|
499
|
+
5. You should see all test cases pass successfully.
|
500
|
+
|
501
|
+
### Source directory structure
|
502
|
+
|
503
|
+
All the gem logic lives in the `/lib` directory:
|
504
|
+
|
505
|
+
```bash
|
506
|
+
lib
|
507
|
+
├── coffeebrew_jekyll_paginate
|
508
|
+
│ ├── config.yml
|
509
|
+
│ ├── generator.rb
|
510
|
+
│ ├── individual_paginator.rb
|
511
|
+
│ ├── page.rb
|
512
|
+
│ ├── page_drop.rb
|
513
|
+
│ ├── paginator.rb
|
514
|
+
│ ├── validator.rb
|
515
|
+
│ └── version.rb
|
516
|
+
└── coffeebrew_jekyll_paginate.rb
|
517
|
+
```
|
518
|
+
|
519
|
+
The files that are currently in the repo:
|
520
|
+
|
521
|
+
| File | Description |
|
522
|
+
| --- | --- |
|
523
|
+
| `lib/coffeebrew_jekyll_paginate/config.yml` | This contains the default configuration for the plugin to generate pagination. |
|
524
|
+
| `lib/coffeebrew_jekyll_paginate/generator.rb` | This is the generator that reads the configuration and generate pagination. |
|
525
|
+
| `lib/coffeebrew_jekyll_paginate/individual_paginator.rb` | This is the abstract model containing the pagination methods for individual pages. |
|
526
|
+
| `lib/coffeebrew_jekyll_paginate/page.rb` | This is the abstract model of the paginated collection pages. |
|
527
|
+
| `lib/coffeebrew_jekyll_paginate/page_drop.rb` | This is the page drop used by the paginator class so its methods can be used in Liquid template. |
|
528
|
+
| `lib/coffeebrew_jekyll_paginate/paginator.rb` | This is the abstract model containing the pagination methods for paginated collection pages. |
|
529
|
+
| `lib/coffeebrew_jekyll_paginate/validator.rb` | This validates the configuration. |
|
530
|
+
| `lib/coffeebrew_jekyll_paginate/version.rb` | This contains the version number of the gem. |
|
531
|
+
| `lib/coffeebrew_jekyll_paginate.rb` | This is the entry point of the gem, and it loads the dependencies. |
|
532
|
+
|
533
|
+
### Test cases directory structure
|
534
|
+
|
535
|
+
All the test cases and fixtures live in the `/spec` directory:
|
536
|
+
|
537
|
+
Note: Some files have been omitted for clarity.
|
538
|
+
|
539
|
+
```bash
|
540
|
+
spec
|
541
|
+
├── coffeebrew_jekyll_paginate_spec.rb
|
542
|
+
├── dest
|
543
|
+
├── fixtures
|
544
|
+
│ ├── _books
|
545
|
+
│ │ ├── 1997-06-26-harry-potter-1.md
|
546
|
+
│ │ ├── 1998-07-02-harry-potter-2.md
|
547
|
+
│ │ ├── 1999-07-08-harry-potter-3.md
|
548
|
+
│ │ ├── 2000-07-08-harry-potter-4.md
|
549
|
+
│ │ ├── 2003-06-21-harry-potter-5.md
|
550
|
+
│ │ ├── 2005-07-16-harry-potter-6.md
|
551
|
+
│ │ └── 2007-07-21-harry-potter-7.md
|
552
|
+
│ ├── _layouts
|
553
|
+
│ │ ├── book.html
|
554
|
+
│ │ ├── default.html
|
555
|
+
│ │ ├── paginated_books.html
|
556
|
+
│ │ ├── paginated_posts.html
|
557
|
+
│ │ └── post.html
|
558
|
+
│ ├── _posts
|
559
|
+
│ │ ├── 2021-03-12-test-post-1.md
|
560
|
+
│ │ ├── 2021-03-28-test-post-2.md
|
561
|
+
│ │ ├── 2021-05-03-test-post-3.md
|
562
|
+
│ │ ├── 2021-05-03-test-post-4.md
|
563
|
+
│ │ ├── 2022-01-27-test-post-5.md
|
564
|
+
│ │ ├── 2022-03-12-test-post-6.md
|
565
|
+
│ │ ├── 2022-11-23-test-post-7.md
|
566
|
+
│ │ └── 2023-02-21-test-post-8.md
|
567
|
+
│ └── _config.yml
|
568
|
+
├── scenarios
|
569
|
+
│ ├── default
|
570
|
+
│ │ ├── _site
|
571
|
+
│ │ │ ├── books
|
572
|
+
│ │ │ │ ├── 1
|
573
|
+
│ │ │ │ │ └── index.html
|
574
|
+
│ │ │ │ ├── 2
|
575
|
+
│ │ │ │ │ └── index.html
|
576
|
+
│ │ │ │ ├── 1997-06-26-harry-potter-1.html
|
577
|
+
│ │ │ │ ├── 1998-07-02-harry-potter-2.html
|
578
|
+
│ │ │ │ ├── 1999-07-08-harry-potter-3.html
|
579
|
+
│ │ │ │ ├── 2000-07-08-harry-potter-4.html
|
580
|
+
│ │ │ │ ├── 2003-06-21-harry-potter-5.html
|
581
|
+
│ │ │ │ ├── 2005-07-16-harry-potter-6.html
|
582
|
+
│ │ │ │ └── 2007-07-21-harry-potter-7.html
|
583
|
+
│ │ │ └── posts
|
584
|
+
│ │ │ ├── 1
|
585
|
+
│ │ │ │ └── index.html
|
586
|
+
│ │ │ ├── 2
|
587
|
+
│ │ │ │ └── index.html
|
588
|
+
│ │ │ ├── 2021-03-12-test-post-1.html
|
589
|
+
│ │ │ ├── 2021-03-28-test-post-2.html
|
590
|
+
│ │ │ ├── 2021-05-03-test-post-3.html
|
591
|
+
│ │ │ ├── 2021-05-03-test-post-4.html
|
592
|
+
│ │ │ ├── 2022-01-27-test-post-5.html
|
593
|
+
│ │ │ ├── 2022-03-12-test-post-6.html
|
594
|
+
│ │ │ ├── 2022-11-23-test-post-7.html
|
595
|
+
│ │ │ └── 2023-02-21-test-post-8.html
|
596
|
+
│ │ └── context.rb
|
597
|
+
│ └── invalid_config_keys
|
598
|
+
│ └── context.rb
|
599
|
+
└── spec_helper.rb
|
600
|
+
```
|
601
|
+
|
602
|
+
The files that are currently in the repo:
|
603
|
+
|
604
|
+
| File | Description |
|
605
|
+
| --- | --- |
|
606
|
+
| `spec/coffeebrew_jekyll_paginate_spec.rb` | This is the main RSpec file to be executed. It contains all the contexts of various scenarios. |
|
607
|
+
| `spec/dest/` | This directory is where generated files are located. It will be deleted immediately after each context is executed. |
|
608
|
+
| `spec/fixtures/` | This directory follows the Jekyll site source structure and contains the minimal files required to generate the paginated pages. |
|
609
|
+
| `spec/fixtures/_books` | This directory contains the test books, you can add more to it to test your new feature. |
|
610
|
+
| `spec/fixtures/_posts` | This directory contains the test posts, you can add more to it to test your new feature. |
|
611
|
+
| `spec/scenarios/` | This directory contains the expected files of various test scenarios. |
|
612
|
+
| `spec/scenarios/<scenario>/` | This is the scenario name. |
|
613
|
+
| `spec/scenarios/<scenario>/_site/` | This directory contains the expected paginated pages. |
|
614
|
+
| `spec/scenarios/<scenario>/context.rb` | This is the file that sets up the context for the test case. |
|
615
|
+
| `spec/spec_helper.rb` | This contains RSpec configuration and certain convenience methods for the main RSpec file. |
|
616
|
+
|
617
|
+
### Writing test cases
|
618
|
+
|
619
|
+
There is a certain convention to follow when writing new test scenarios. The recommendation is to use the rake tasks
|
620
|
+
provided in the gem to generate the scenario files.
|
621
|
+
|
622
|
+
For success scenarios, run:
|
623
|
+
|
624
|
+
```bash
|
625
|
+
bundle exec rake coffeebrew:jekyll:paginate:test:create_success[test_scenario]
|
626
|
+
```
|
627
|
+
|
628
|
+
This will generate a placeholder file and directory:
|
629
|
+
|
630
|
+
```bash
|
631
|
+
spec
|
632
|
+
├── coffeebrew_jekyll_paginate_spec.rb
|
633
|
+
├── scenarios
|
634
|
+
│ └── test_scenario
|
635
|
+
│ ├── _site
|
636
|
+
│ └── context.rb
|
637
|
+
└── spec_helper.rb
|
638
|
+
```
|
639
|
+
|
640
|
+
Where the `context.rb` file will be pre-populated:
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
# frozen_string_literal: true
|
644
|
+
|
645
|
+
CONTEXT_TEST_SCENARIO = "when using test_scenario config"
|
646
|
+
|
647
|
+
RSpec.shared_context CONTEXT_TEST_SCENARIO do
|
648
|
+
let(:scenario) { "test_scenario" }
|
649
|
+
let(:overrides) {} # TODO: remove if unused
|
650
|
+
let(:generated_files) {} # TODO: remove if unused
|
651
|
+
let(:expected_files) do
|
652
|
+
[
|
653
|
+
]
|
654
|
+
end
|
655
|
+
end
|
656
|
+
```
|
657
|
+
|
658
|
+
For failure scenarios, run:
|
659
|
+
|
660
|
+
```bash
|
661
|
+
bundle exec rake coffeebrew:jekyll:paginate:test:create_failure[test_scenario]
|
662
|
+
```
|
663
|
+
|
664
|
+
This will generate a placeholder file and directory:
|
665
|
+
|
666
|
+
```bash
|
667
|
+
spec
|
668
|
+
├── coffeebrew_jekyll_paginate_spec.rb
|
669
|
+
├── scenarios
|
670
|
+
│ └── test_scenario
|
671
|
+
│ └── context.rb
|
672
|
+
└── spec_helper.rb
|
673
|
+
```
|
674
|
+
|
675
|
+
Where the `context.rb` file will be pre-populated:
|
676
|
+
|
677
|
+
```ruby
|
678
|
+
# frozen_string_literal: true
|
679
|
+
|
680
|
+
CONTEXT_TEST_SCENARIO = "when using test_scenario config"
|
681
|
+
|
682
|
+
RSpec.shared_context CONTEXT_TEST_SCENARIO do
|
683
|
+
let(:scenario) { "test_scenario" }
|
684
|
+
let(:generated_files) { [] }
|
685
|
+
let(:expected_files) { [] }
|
686
|
+
let(:overrides) do
|
687
|
+
{
|
688
|
+
}
|
689
|
+
end
|
690
|
+
|
691
|
+
let(:expected_errors) do
|
692
|
+
[
|
693
|
+
]
|
694
|
+
end
|
695
|
+
end
|
696
|
+
```
|
697
|
+
|
698
|
+
If you do write other test cases that are not asserting the generated files like above, you can initiate your
|
699
|
+
convention. The repo owner will evaluate the PR and accept the convention if it fits the repo existing convention, or
|
700
|
+
will recommend an alternative if it doesn't.
|
701
|
+
|
702
|
+
## License
|
703
|
+
|
704
|
+
See the [LICENSE](LICENSE) file.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
coffeebrew_jekyll_paginate:
|
2
|
+
defaults:
|
3
|
+
individual_page_pagination: false
|
4
|
+
frontmatter_defaults_key: "paginated_%{collection_type}"
|
5
|
+
first_page_as_root:
|
6
|
+
enabled: false
|
7
|
+
permalink: /:collection_type/:page_num_one_index
|
8
|
+
index_page: "index"
|
9
|
+
per_page: 5
|
10
|
+
sort_field: "date"
|
11
|
+
sort_reverse: true
|
12
|
+
page_num_label: "%{page_num_one_index}"
|
13
|
+
collections: []
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./page"
|
4
|
+
require_relative "./individual_paginator"
|
5
|
+
require_relative "./paginator"
|
6
|
+
require_relative "./validator"
|
7
|
+
|
8
|
+
module Coffeebrew
|
9
|
+
module Jekyll
|
10
|
+
module Paginate
|
11
|
+
class Generator < ::Jekyll::Generator
|
12
|
+
safe true
|
13
|
+
priority :lowest
|
14
|
+
|
15
|
+
def generate(site)
|
16
|
+
@site = site
|
17
|
+
|
18
|
+
validate!
|
19
|
+
|
20
|
+
user_collections_config.each do |collection_type, options|
|
21
|
+
collection_options = add_collections_defaults(options)
|
22
|
+
process_collection(collection_type.to_sym, collection_options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate!
|
29
|
+
validator = Coffeebrew::Jekyll::Paginate::Validator.new(@site, user_config)
|
30
|
+
validator.validate!
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_collection(collection_type, options)
|
34
|
+
sort_field = options["sort_field"].to_sym
|
35
|
+
sort_reverse = options["sort_reverse"]
|
36
|
+
records = @site.collections[collection_type.to_s].docs
|
37
|
+
records.sort_by!(&sort_field)
|
38
|
+
records.reverse! if sort_reverse
|
39
|
+
|
40
|
+
individual_pagination = options["individual_page_pagination"]
|
41
|
+
generate_individual_page_paginator(collection_type, records) if individual_pagination
|
42
|
+
|
43
|
+
pages = generate_pages(collection_type, records, options)
|
44
|
+
return if pages.empty?
|
45
|
+
|
46
|
+
generate_paginators(collection_type, pages)
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_individual_page_paginator(collection_type, records)
|
50
|
+
records.each_with_index do |record, index|
|
51
|
+
prev_record = index.positive? ? records[index - 1] : nil
|
52
|
+
next_record = index < records.length ? records[index + 1] : nil
|
53
|
+
paginator = Paginate::IndividualPaginator.new(collection_type, prev_record, record, next_record)
|
54
|
+
record.data["paginator"] = paginator
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_pages(collection_type, records, options)
|
59
|
+
per_page = options["per_page"].to_i
|
60
|
+
first_page_as_root = options["first_page_as_root"]
|
61
|
+
|
62
|
+
records.each_slice(per_page).with_index.map do |page_records, page_num|
|
63
|
+
page_options = options.clone
|
64
|
+
if first_page_as_root["enabled"] && page_num.zero?
|
65
|
+
page_options["permalink"] = first_page_as_root["permalink"]
|
66
|
+
page_options["index_page"] = first_page_as_root["index_page"]
|
67
|
+
end
|
68
|
+
Paginate::Page.new(@site, page_options, collection_type, page_records, page_num)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate_paginators(collection_type, pages)
|
73
|
+
pages.each do |page|
|
74
|
+
paginator = Paginate::Paginator.new(pages, collection_type, page.page_num_zero_index)
|
75
|
+
page.paginator = paginator
|
76
|
+
@site.pages << page
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_collections_defaults(user_options)
|
81
|
+
::Jekyll::Utils.deep_merge_hashes(
|
82
|
+
default_defaults_config,
|
83
|
+
::Jekyll::Utils.deep_merge_hashes(user_defaults_config, user_options)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def user_collections_config
|
88
|
+
@user_collections_config ||= user_config["collections"].to_h.transform_values(&:to_h)
|
89
|
+
end
|
90
|
+
|
91
|
+
def user_defaults_config
|
92
|
+
@user_defaults_config ||= user_config["defaults"].to_h
|
93
|
+
end
|
94
|
+
|
95
|
+
def user_config
|
96
|
+
@user_config ||= @site.config["coffeebrew_jekyll_paginate"].to_h
|
97
|
+
end
|
98
|
+
|
99
|
+
def default_defaults_config
|
100
|
+
@default_defaults_config ||= default_config.dig("coffeebrew_jekyll_paginate", "defaults").to_h
|
101
|
+
end
|
102
|
+
|
103
|
+
def default_config
|
104
|
+
@default_config ||= YAML.safe_load(File.read(default_config_path))
|
105
|
+
end
|
106
|
+
|
107
|
+
def default_config_path
|
108
|
+
@default_config_path ||= File.expand_path("config.yml", __dir__)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./page_drop"
|
4
|
+
|
5
|
+
module Coffeebrew
|
6
|
+
module Jekyll
|
7
|
+
module Paginate
|
8
|
+
class IndividualPaginator
|
9
|
+
attr_reader :collection_type,
|
10
|
+
:current_page, :current_page_path,
|
11
|
+
:previous_page, :previous_page_path,
|
12
|
+
:next_page, :next_page_path
|
13
|
+
|
14
|
+
def initialize(collection_type, previous_page, current_page, next_page)
|
15
|
+
@collection_type = collection_type
|
16
|
+
@current_page = current_page
|
17
|
+
@current_page_path = full_url(@current_page)
|
18
|
+
@previous_page = previous_page
|
19
|
+
@previous_page_path = full_url(@previous_page)
|
20
|
+
@next_page = next_page
|
21
|
+
@next_page_path = full_url(@next_page)
|
22
|
+
end
|
23
|
+
|
24
|
+
def data
|
25
|
+
@data ||= {
|
26
|
+
"collection_type" => collection_type,
|
27
|
+
"current_page" => current_page,
|
28
|
+
"current_page_path" => current_page_path,
|
29
|
+
"previous_page" => previous_page,
|
30
|
+
"previous_page_path" => previous_page_path,
|
31
|
+
"next_page" => next_page,
|
32
|
+
"next_page_path" => next_page_path
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_liquid
|
37
|
+
@to_liquid ||= Paginate::PageDrop.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def full_url(page)
|
43
|
+
return "" if page.nil?
|
44
|
+
|
45
|
+
page.url
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coffeebrew
|
4
|
+
module Jekyll
|
5
|
+
module Paginate
|
6
|
+
class Page < ::Jekyll::Page
|
7
|
+
attr_reader :paginator, :collection_type, :collection,
|
8
|
+
:page_num_zero_index, :page_num_one_index
|
9
|
+
|
10
|
+
def initialize(site, config, collection_type, collection, page_num_zero_index) # rubocop:disable Lint/MissingSuper
|
11
|
+
@site = site
|
12
|
+
@config = config
|
13
|
+
@collection_type = collection_type
|
14
|
+
@collection = collection
|
15
|
+
@page_num_zero_index = page_num_zero_index
|
16
|
+
@page_num_one_index = page_num_zero_index.to_i + 1
|
17
|
+
|
18
|
+
@base = site.source
|
19
|
+
@ext = ".html"
|
20
|
+
|
21
|
+
build_data
|
22
|
+
end
|
23
|
+
|
24
|
+
def paginator=(paginator)
|
25
|
+
@paginator = paginator
|
26
|
+
@data["paginator"] = paginator
|
27
|
+
|
28
|
+
data.default_proc = proc do |_, key|
|
29
|
+
site.frontmatter_defaults.find(relative_path, frontmatter_defaults_key, key)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def frontmatter_defaults_key
|
34
|
+
@frontmatter_defaults_key ||= format(@config["frontmatter_defaults_key"], collection_type: collection_type)
|
35
|
+
end
|
36
|
+
|
37
|
+
def basename
|
38
|
+
@basename ||= ::Jekyll::URL.new(template: nil,
|
39
|
+
placeholders: basename_placeholders,
|
40
|
+
permalink: @config["index_page"])
|
41
|
+
.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
@name ||= "#{basename}#{ext}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def dir
|
49
|
+
@dir ||= ::Jekyll::URL.new(template: nil,
|
50
|
+
placeholders: page_dir_placeholders,
|
51
|
+
permalink: @config["permalink"])
|
52
|
+
.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def page_dir_placeholders
|
56
|
+
@page_dir_placeholders ||= {
|
57
|
+
collection_type: collection_type.to_s,
|
58
|
+
page_num_zero_index: page_num_zero_index.to_s,
|
59
|
+
page_num_one_index: page_num_one_index.to_s,
|
60
|
+
basename: basename,
|
61
|
+
output_ext: output_ext
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def basename_placeholders
|
66
|
+
@basename_placeholders ||= {
|
67
|
+
collection_type: collection_type.to_s,
|
68
|
+
page_num_zero_index: page_num_zero_index.to_s,
|
69
|
+
page_num_one_index: page_num_one_index.to_s
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def url_placeholders
|
74
|
+
{
|
75
|
+
path: dir,
|
76
|
+
basename: basename,
|
77
|
+
output_ext: output_ext
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def page_num_label
|
82
|
+
@page_num_label ||= format(@config["page_num_label"],
|
83
|
+
page_num_zero_index: page_num_zero_index, page_num_one_index: page_num_one_index)
|
84
|
+
end
|
85
|
+
|
86
|
+
def title
|
87
|
+
@title ||= collection_type.capitalize
|
88
|
+
end
|
89
|
+
|
90
|
+
def full_url
|
91
|
+
@full_url ||= [dir, name].join("/").squeeze("/")
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def build_data
|
97
|
+
@data = {
|
98
|
+
"title" => title,
|
99
|
+
"collection" => collection,
|
100
|
+
"collection_type" => collection_type,
|
101
|
+
"page_num_zero_index" => page_num_zero_index,
|
102
|
+
"page_num_one_index" => page_num_one_index,
|
103
|
+
"page_num_label" => page_num_label,
|
104
|
+
"full_url" => full_url
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coffeebrew
|
4
|
+
module Jekyll
|
5
|
+
module Paginate
|
6
|
+
class PageDrop < ::Jekyll::Drops::Drop
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
mutable false
|
10
|
+
|
11
|
+
def_delegators :@obj, :posts, :type, :title, :date, :name, :path, :url, :permalink
|
12
|
+
def_delegator :@obj, :data, :fallback_data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./page_drop"
|
4
|
+
|
5
|
+
module Coffeebrew
|
6
|
+
module Jekyll
|
7
|
+
module Paginate
|
8
|
+
class Paginator
|
9
|
+
attr_reader :total_pages, :pages,
|
10
|
+
:page_num_zero_index, :page_num_one_index, :page_num_label,
|
11
|
+
:collection, :collection_type,
|
12
|
+
:current_page, :current_page_path,
|
13
|
+
:previous_page, :previous_page_path,
|
14
|
+
:next_page, :next_page_path
|
15
|
+
|
16
|
+
def initialize(pages, collection_type, page_num_zero_index = 0)
|
17
|
+
@total_pages = pages.size
|
18
|
+
@pages = pages
|
19
|
+
@page_num_zero_index = page_num_zero_index
|
20
|
+
@collection_type = collection_type
|
21
|
+
|
22
|
+
set_current_page
|
23
|
+
set_prev_page
|
24
|
+
set_next_page
|
25
|
+
end
|
26
|
+
|
27
|
+
def data # rubocop:disable Metrics/MethodLength
|
28
|
+
@data ||= {
|
29
|
+
"total_pages" => total_pages,
|
30
|
+
"pages" => pages,
|
31
|
+
"page_num_zero_index" => page_num_zero_index,
|
32
|
+
"page_num_one_index" => page_num_one_index,
|
33
|
+
"page_num_label" => page_num_label,
|
34
|
+
"collection" => collection,
|
35
|
+
"collection_type" => collection_type,
|
36
|
+
"current_page" => current_page,
|
37
|
+
"current_page_path" => current_page_path,
|
38
|
+
"previous_page" => previous_page,
|
39
|
+
"previous_page_path" => previous_page_path,
|
40
|
+
"next_page" => next_page,
|
41
|
+
"next_page_path" => next_page_path
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_liquid
|
46
|
+
@to_liquid ||= Paginate::PageDrop.new(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def set_current_page
|
52
|
+
@current_page = pages[page_num_zero_index]
|
53
|
+
@current_page_path = full_url(@current_page)
|
54
|
+
|
55
|
+
@collection = current_page&.collection || []
|
56
|
+
@page_num_one_index = @current_page&.page_num_one_index
|
57
|
+
@page_num_label = @current_page&.page_num_label
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_prev_page
|
61
|
+
return if page_num_zero_index.zero?
|
62
|
+
|
63
|
+
@previous_page = pages[page_num_zero_index - 1]
|
64
|
+
@previous_page_path = full_url(@previous_page)
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_next_page
|
68
|
+
return unless page_num_zero_index < total_pages - 1
|
69
|
+
|
70
|
+
@next_page = pages[page_num_zero_index + 1]
|
71
|
+
@next_page_path = full_url(@next_page)
|
72
|
+
end
|
73
|
+
|
74
|
+
def full_url(page)
|
75
|
+
return "" if page.nil?
|
76
|
+
|
77
|
+
page.full_url
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coffeebrew
|
4
|
+
module Jekyll
|
5
|
+
module Paginate
|
6
|
+
class Validator
|
7
|
+
ROOT_KEY = "coffeebrew_jekyll_paginate"
|
8
|
+
COLLECTIONS_KEY = "collections"
|
9
|
+
DEFAULTS_KEY = "defaults"
|
10
|
+
|
11
|
+
ALLOWED_ROOT_KEYS = [
|
12
|
+
COLLECTIONS_KEY,
|
13
|
+
DEFAULTS_KEY
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
ALLOWED_CONFIG_KEYS = {
|
17
|
+
"frontmatter_defaults_key" => [],
|
18
|
+
"individual_page_pagination" => [true, false],
|
19
|
+
"first_page_as_root" => {
|
20
|
+
"enabled" => [true, false],
|
21
|
+
"permalink" => [],
|
22
|
+
"index_page" => []
|
23
|
+
},
|
24
|
+
"permalink" => [],
|
25
|
+
"index_page" => [],
|
26
|
+
"per_page" => [],
|
27
|
+
"sort_field" => [],
|
28
|
+
"sort_reverse" => [true, false],
|
29
|
+
"page_num_label" => []
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
def initialize(site, config)
|
33
|
+
@site = site
|
34
|
+
@config = config
|
35
|
+
@errors = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate!
|
39
|
+
parse_root
|
40
|
+
parse_defaults
|
41
|
+
parse_collection_keys
|
42
|
+
parse_collections_config
|
43
|
+
|
44
|
+
return if @errors.empty?
|
45
|
+
|
46
|
+
::Jekyll.logger.error "'coffeebrew_jekyll_paginate' config is set incorrectly."
|
47
|
+
::Jekyll.logger.error "Errors:", @errors
|
48
|
+
|
49
|
+
raise ::Jekyll::Errors::InvalidConfigurationError, "'coffeebrew_jekyll_paginate' config is set incorrectly."
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def parse_root
|
55
|
+
not_allowed_keys = @config.keys.reject do |key|
|
56
|
+
ALLOWED_ROOT_KEYS.include?(key)
|
57
|
+
end
|
58
|
+
return if not_allowed_keys.empty?
|
59
|
+
|
60
|
+
@errors << { key: ROOT_KEY, expected: ALLOWED_ROOT_KEYS, got: not_allowed_keys }
|
61
|
+
end
|
62
|
+
|
63
|
+
def defaults_config
|
64
|
+
@defaults_config ||= @config[DEFAULTS_KEY].to_h
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_defaults
|
68
|
+
parse(defaults_config, ALLOWED_CONFIG_KEYS, ["#{ROOT_KEY}.#{DEFAULTS_KEY}"])
|
69
|
+
end
|
70
|
+
|
71
|
+
def configured_collections
|
72
|
+
@configured_collections ||= collections_config.keys.sort
|
73
|
+
end
|
74
|
+
|
75
|
+
def allowed_collections
|
76
|
+
@allowed_collections ||= @site.collections.keys
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_collection_keys
|
80
|
+
not_allowed_collections = configured_collections.reject do |configured_collection|
|
81
|
+
allowed_collections.include?(configured_collection)
|
82
|
+
end
|
83
|
+
return if not_allowed_collections.empty?
|
84
|
+
|
85
|
+
@errors << {
|
86
|
+
key: "#{ROOT_KEY}.collections",
|
87
|
+
expected: allowed_collections,
|
88
|
+
got: not_allowed_collections
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def collections_config
|
93
|
+
@collections_config ||= @config[COLLECTIONS_KEY].to_h.transform_values(&:to_h)
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_collections_config
|
97
|
+
collections_config.each do |collection_type, options|
|
98
|
+
parse(options, ALLOWED_CONFIG_KEYS, ["#{ROOT_KEY}.#{COLLECTIONS_KEY}.#{collection_type}"])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse(hash, allowed_keys, parent_key)
|
103
|
+
hash.each do |key, configured_value|
|
104
|
+
allowed_values = allowed_keys[key]
|
105
|
+
new_parent_key = parent_key + [key]
|
106
|
+
|
107
|
+
next if configured_in_allowed_values?(allowed_values, configured_value)
|
108
|
+
|
109
|
+
if same_hash_type?(allowed_values, configured_value)
|
110
|
+
next parse(configured_value, allowed_values, new_parent_key)
|
111
|
+
end
|
112
|
+
|
113
|
+
add_error(new_parent_key, allowed_values, configured_value)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def same_hash_type?(allowed_values, configured_value)
|
118
|
+
allowed_values.is_a?(Hash) && configured_value.is_a?(Hash)
|
119
|
+
end
|
120
|
+
|
121
|
+
def primitive?(value)
|
122
|
+
value.nil? || value.is_a?(String) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
123
|
+
end
|
124
|
+
|
125
|
+
def configured_in_allowed_values?(allowed_values, configured_value)
|
126
|
+
allowed_values.is_a?(Array) && primitive?(configured_value) &&
|
127
|
+
(allowed_values.empty? || allowed_values.include?(configured_value))
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_error(parent_key, allowed_values, configured_value)
|
131
|
+
@errors << { key: parent_key.join("."), expected: allowed_values, got: configured_value }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: coffeebrew_jekyll_paginate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Coffee Brew Apps
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-09 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: '4.0'
|
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: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.0'
|
33
|
+
description: A Jekyll plugin to generate pagination for site collections
|
34
|
+
email:
|
35
|
+
- coffeebrewapps@gmail.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files:
|
39
|
+
- README.md
|
40
|
+
- CHANGELOG.md
|
41
|
+
- LICENSE
|
42
|
+
files:
|
43
|
+
- CHANGELOG.md
|
44
|
+
- LICENSE
|
45
|
+
- README.md
|
46
|
+
- lib/coffeebrew_jekyll_paginate.rb
|
47
|
+
- lib/coffeebrew_jekyll_paginate/config.yml
|
48
|
+
- lib/coffeebrew_jekyll_paginate/generator.rb
|
49
|
+
- lib/coffeebrew_jekyll_paginate/individual_paginator.rb
|
50
|
+
- lib/coffeebrew_jekyll_paginate/page.rb
|
51
|
+
- lib/coffeebrew_jekyll_paginate/page_drop.rb
|
52
|
+
- lib/coffeebrew_jekyll_paginate/paginator.rb
|
53
|
+
- lib/coffeebrew_jekyll_paginate/validator.rb
|
54
|
+
- lib/coffeebrew_jekyll_paginate/version.rb
|
55
|
+
homepage: https://coffeebrewapps.com/coffeebrew_jekyll_paginate
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata:
|
59
|
+
allowed_push_host: https://rubygems.org
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib/coffeebrew_jekyll_paginate
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.7.0
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubygems_version: 3.4.10
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: A Jekyll plugin to generate pagination for site collections
|
80
|
+
test_files: []
|