j1_paginator 2019.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,377 @@
1
+ module Jekyll
2
+ module J1Paginator::Generator
3
+
4
+ #
5
+ # The main model for the pagination, handles the orchestration of the pagination and calling all the necessary bits and bobs needed :)
6
+ #
7
+ class PaginationModel
8
+
9
+ @debug = false # is debug output enabled?
10
+ @logging_lambda = nil # The lambda to use for logging
11
+ @page_add_lambda = nil # The lambda used to create pages and add them to the site
12
+ @page_remove_lambda = nil # Lambda to remove a page from the site.pages collection
13
+ @collection_by_name_lambda = nil # Lambda to get all documents/posts in a particular collection (by name)
14
+
15
+ # ctor
16
+ def initialize(logging_lambda, page_add_lambda, page_remove_lambda, collection_by_name_lambda)
17
+ @logging_lambda = logging_lambda
18
+ @page_add_lambda = page_add_lambda
19
+ @page_remove_lambda = page_remove_lambda
20
+ @collection_by_name_lambda = collection_by_name_lambda
21
+ end
22
+
23
+
24
+ def run(default_config, site_pages, site_title)
25
+ # By default if pagination is enabled we attempt to find all index.html pages in the site
26
+ templates = self.discover_paginate_templates(site_pages)
27
+ if templates.size <= 0
28
+ @logging_lambda.call "Is enabled, but I couldn't find any pagination page. Skipping pagination. "+
29
+ "Pages must have 'pagination: enabled: true' in their front-matter for pagination to work.", "warn"
30
+ return
31
+ end
32
+
33
+ # Now for each template page generate the paginator for it
34
+ templates.each do |template|
35
+ # All pages that should be paginated need to include the pagination config element
36
+ if template.data['pagination'].is_a?(Hash)
37
+ template_config = Jekyll::Utils.deep_merge_hashes(default_config, template.data['pagination'] || {})
38
+
39
+ # Handling deprecation of configuration values
40
+ self._fix_deprecated_config_features(template_config)
41
+
42
+ @debug = template_config['debug'] # Is debugging enabled on the page level
43
+
44
+ self._debug_print_config_info(template_config, template.path)
45
+
46
+ # Only paginate the template if it is explicitly enabled
47
+ # This makes the logic simpler by avoiding the need to determine which index pages
48
+ # were generated automatically and which weren't
49
+ if template_config['enabled'].to_s == 'true'
50
+ if !@debug
51
+ @logging_lambda.call "found page: "+template.path, 'debug'
52
+ end
53
+
54
+ # Request all documents in all collections that the user has requested
55
+ all_posts = self.get_docs_in_collections(template_config['collection'])
56
+
57
+ # Create the necessary indexes for the posts
58
+ all_categories = PaginationIndexer.index_posts_by(all_posts, 'categories')
59
+ all_categories['posts'] = all_posts; # Populate a category for all posts (this is here for backward compatibility, do not use this as it will be decommissioned 2018-01-01)
60
+ # (this is a default and must not be used in the category system)
61
+ all_tags = PaginationIndexer.index_posts_by(all_posts, 'tags')
62
+ all_locales = PaginationIndexer.index_posts_by(all_posts, 'locale')
63
+
64
+ # TODO: NOTE!!! This whole request for posts and indexing results could be cached to improve performance, leaving like this for now during testing
65
+
66
+ # Now construct the pagination data for this template page
67
+ self.paginate(template, template_config, site_title, all_posts, all_tags, all_categories, all_locales)
68
+ end
69
+ end
70
+ end #for
71
+
72
+ # Return the total number of templates found
73
+ return templates.size
74
+ end # function run
75
+
76
+ #
77
+ # This function is here to retain the old compatability logic with the jekyll-paginate gem
78
+ # no changes should be made to this function and it should be retired and deleted after 2018-01-01
79
+ # (REMOVE AFTER 2018-01-01)
80
+ #
81
+ def run_compatability(legacy_config, site_pages, site_title, all_posts)
82
+
83
+ # Decomissioning error
84
+ if( date = Date.strptime("20180101","%Y%m%d") <= Date.today )
85
+ raise ArgumentError.new("Legacy jekyll-paginate configuration compatibility mode has expired. Please upgrade to j1_paginator configuration.")
86
+ end
87
+
88
+ # Two month warning or general notification
89
+ if( date = Date.strptime("20171101","%Y%m%d") <= Date.today )
90
+ @logging_lambda.call "Legacy pagination logic will stop working on Jan 1st 2018, update your configs before that time.", "warn"
91
+ else
92
+ @logging_lambda.call "Detected legacy jekyll-paginate logic running. "+
93
+ "Please update your configs to use the j1_paginator logic. This compatibility function "+
94
+ "will stop working after Jan 1st 2018 and your site build will throw an error.", "warn"
95
+ end
96
+
97
+ if template = CompatibilityUtils.template_page(site_pages, legacy_config['legacy_source'], legacy_config['permalink'])
98
+ CompatibilityUtils.paginate(legacy_config, all_posts, template, @page_add_lambda)
99
+ else
100
+ @logging_lambda.call "Legacy pagination is enabled, but I couldn't find " +
101
+ "an index.html page to use as the pagination page. Skipping pagination.", "warn"
102
+ end
103
+ end # function run_compatability (REMOVE AFTER 2018-01-01)
104
+
105
+ # Returns the combination of all documents in the collections that are specified
106
+ # raw_collection_names can either be a list of collections separated by a ',' or ' ' or a single string
107
+ def get_docs_in_collections(raw_collection_names)
108
+ if raw_collection_names.is_a?(String)
109
+ collection_names = raw_collection_names.split(/;|,|\s/)
110
+ else
111
+ collection_names = raw_collection_names
112
+ end
113
+
114
+ docs = []
115
+ # Now for each of the collections get the docs
116
+ collection_names.each do |coll_name|
117
+ # Request all the documents for the collection in question, and join it with the total collection
118
+ docs += @collection_by_name_lambda.call(coll_name.downcase.strip)
119
+ end
120
+
121
+ # Hidden documents should not not be processed anywhere.
122
+ docs = docs.reject { |doc| doc['hidden'] }
123
+
124
+ return docs
125
+ end
126
+
127
+ def _fix_deprecated_config_features(config)
128
+ keys_to_delete = []
129
+
130
+ # As of v1.5.1 the title_suffix is deprecated and 'title' should be used
131
+ # but only if title has not been defined already!
132
+ if( !config['title_suffix'].nil? )
133
+ if( config['title'].nil? )
134
+ config['title'] = ":title" + config['title_suffix'].to_s # Migrate the old key to title
135
+ end
136
+ keys_to_delete << "title_suffix" # Always remove the depricated key if found
137
+ end
138
+
139
+ # Delete the depricated keys
140
+ config.delete_if{ |k,| keys_to_delete.include? k }
141
+ end
142
+
143
+ LOG_KEY = 'Pagination: '.rjust(20).freeze
144
+ DIVIDER = ('-' * 80).freeze
145
+ NOT_SET = '[Not set]'.freeze
146
+
147
+ # Debug print the config
148
+ def _debug_log(topic, message = nil)
149
+ return unless @debug
150
+
151
+ message = message.to_s
152
+ topic = "#{topic.ljust(24)}: " unless message.empty?
153
+ puts LOG_KEY + topic + message
154
+ end
155
+
156
+ # Debug print the config
157
+ def _debug_print_config_info(config, page_path)
158
+ return unless @debug
159
+
160
+ puts ''
161
+ puts LOG_KEY + "Page: #{page_path}"
162
+ puts LOG_KEY + DIVIDER
163
+ _debug_log ' Active configuration'
164
+ _debug_log ' Enabled', config['enabled']
165
+ _debug_log ' Items per page', config['per_page']
166
+ _debug_log ' Permalink', config['permalink']
167
+ _debug_log ' Title', config['title']
168
+ _debug_log ' Limit', config['limit']
169
+ _debug_log ' Sort by', config['sort_field']
170
+ _debug_log ' Sort reverse', config['sort_reverse']
171
+ _debug_log ' Active Filters'
172
+ _debug_log ' Collection', config['collection']
173
+ _debug_log ' Offset', config['offset']
174
+ _debug_log ' Category', (config['category'].nil? || config['category'] == 'posts' ? NOT_SET : config['category'])
175
+ _debug_log ' Tag', config['tag'] || NOT_SET
176
+ _debug_log ' Locale', config['locale'] || NOT_SET
177
+
178
+ return unless config['legacy']
179
+
180
+ _debug_log ' Legacy Paginate Code Enabled'
181
+ _debug_log ' Legacy Paginate', config['per_page']
182
+ _debug_log ' Legacy Source', config['legacy_source']
183
+ _debug_log ' Legacy Path', config['paginate_path']
184
+ end
185
+
186
+ # Debug print the config
187
+ def _debug_print_filtering_info(filter_name, before_count, after_count)
188
+ return unless @debug
189
+
190
+ filter_name = filter_name.to_s.ljust(9)
191
+ before_count = before_count.to_s.rjust(3)
192
+ _debug_log " Filtering by #{filter_name}", "#{before_count} => #{after_count}"
193
+ end
194
+
195
+ #
196
+ # Rolls through all the pages passed in and finds all pages that have pagination enabled on them.
197
+ # These pages will be used as templates
198
+ #
199
+ # site_pages - All pages in the site
200
+ #
201
+ def discover_paginate_templates(site_pages)
202
+ candidates = []
203
+ site_pages.select do |page|
204
+ # If the page has the enabled config set, supports any type of file name html or md
205
+ if page.data['pagination'].is_a?(Hash) && page.data['pagination']['enabled']
206
+ candidates << page
207
+ end
208
+ end
209
+ return candidates
210
+ end # function discover_paginate_templates
211
+
212
+ # Paginates the blog's posts. Renders the index.html file into paginated
213
+ # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
214
+ # site-wide data.
215
+ #
216
+ # site - The Site.
217
+ # template - The index.html Page that requires pagination.
218
+ # config - The configuration settings that should be used
219
+ #
220
+ def paginate(template, config, site_title, all_posts, all_tags, all_categories, all_locales)
221
+ # By default paginate on all posts in the site
222
+ using_posts = all_posts
223
+
224
+ # Now start filtering out any posts that the user doesn't want included in the pagination
225
+ before = using_posts.size
226
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'category', using_posts, all_categories)
227
+ self._debug_print_filtering_info('Category', before, using_posts.size)
228
+ before = using_posts.size
229
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'tag', using_posts, all_tags)
230
+ self._debug_print_filtering_info('Tag', before, using_posts.size)
231
+ before = using_posts.size
232
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'locale', using_posts, all_locales)
233
+ self._debug_print_filtering_info('Locale', before, using_posts.size)
234
+
235
+ # Apply sorting to the posts if configured, any field for the post is available for sorting
236
+ if config['sort_field']
237
+ sort_field = config['sort_field'].to_s
238
+
239
+ # There is an issue in Jekyll related to lazy initialized member variables that causes iterators to
240
+ # break when accessing an uninitialized value during iteration. This happens for document.rb when the <=> compaison function
241
+ # is called (as this function calls the 'date' field which for drafts are not initialized.)
242
+ # So to unblock this common issue for the date field I simply iterate once over every document and initialize the .date field explicitly
243
+ if @debug
244
+ Jekyll.logger.info "Pagination:", "Rolling through the date fields for all documents"
245
+ end
246
+ using_posts.each do |u_post|
247
+ if u_post.respond_to?('date')
248
+ tmp_date = u_post.date
249
+ if( !tmp_date || tmp_date.nil? )
250
+ if @debug
251
+ Jekyll.logger.info "Pagination:", "Explicitly assigning date for doc: #{u_post.data['title']} | #{u_post.path}"
252
+ end
253
+ u_post.date = File.mtime(u_post.path)
254
+ end
255
+ end
256
+ end
257
+
258
+ using_posts.sort!{ |a,b| Utils.sort_values(Utils.sort_get_post_data(a.data, sort_field), Utils.sort_get_post_data(b.data, sort_field)) }
259
+
260
+ # Remove the first x entries
261
+ offset_post_count = [0, config['offset'].to_i].max
262
+ using_posts.pop(offset_post_count)
263
+
264
+ if config['sort_reverse']
265
+ using_posts.reverse!
266
+ end
267
+ end
268
+
269
+ # Calculate the max number of pagination-pages based on the configured per page value
270
+ total_pages = Utils.calculate_number_of_pages(using_posts, config['per_page'])
271
+
272
+ # If a upper limit is set on the number of total pagination pages then impose that now
273
+ if config['limit'] && config['limit'].to_i > 0 && config['limit'].to_i < total_pages
274
+ total_pages = config['limit'].to_i
275
+ end
276
+
277
+ #### BEFORE STARTING REMOVE THE TEMPLATE PAGE FROM THE SITE LIST!
278
+ @page_remove_lambda.call( template )
279
+
280
+ # list of all newly created pages
281
+ newpages = []
282
+
283
+ # Consider the default index page name and extension
284
+ indexPageName = config['indexpage'].nil? ? '' : config['indexpage'].split('.')[0]
285
+ indexPageExt = config['extension'].nil? ? '' : Utils.ensure_leading_dot(config['extension'])
286
+ indexPageWithExt = indexPageName + indexPageExt
287
+
288
+ # In case there are no (visible) posts, generate the index file anyway
289
+ total_pages = 1 if total_pages.zero?
290
+
291
+ # Now for each pagination page create it and configure the ranges for the collection
292
+ # This .pager member is a built in thing in Jekyll and defines the paginator implementation
293
+ # Simpy override to use mine
294
+ (1..total_pages).each do |cur_page_nr|
295
+
296
+ # 1. Create the in-memory page
297
+ # External Proc call to create the actual page for us (this is passed in when the pagination is run)
298
+ newpage = PaginationPage.new( template, cur_page_nr, total_pages, indexPageWithExt )
299
+
300
+ # 2. Create the url for the in-memory page (calc permalink etc), construct the title, set all page.data values needed
301
+ first_index_page_url = Utils.validate_url(template)
302
+ paginated_page_url = File.join(first_index_page_url, config['permalink'])
303
+
304
+ # 3. Create the pager logic for this page, pass in the prev and next page numbers, assign pager to in-memory page
305
+ newpage.pager = Paginator.new( config['per_page'], first_index_page_url, paginated_page_url, using_posts, cur_page_nr, total_pages, indexPageName, indexPageExt)
306
+
307
+ # Create the url for the new page, make sure we prepend any permalinks that are defined in the template page before
308
+ pager_path = newpage.pager.page_path
309
+ if pager_path.end_with? '/'
310
+ newpage.url = File.join(pager_path, indexPageWithExt)
311
+ elsif pager_path.end_with? indexPageExt
312
+ # Support for direct .html files
313
+ newpage.url = pager_path
314
+ else
315
+ # Support for extensionless permalinks
316
+ newpage.url = pager_path + indexPageExt
317
+ end
318
+
319
+ if( template.data['permalink'] )
320
+ newpage.data['permalink'] = pager_path
321
+ end
322
+
323
+ # Transfer the title across to the new page
324
+ tmp_title = template.data['title'] || site_title
325
+ if cur_page_nr > 1 && config.has_key?('title')
326
+ # If the user specified a title suffix to be added then let's add that to all the pages except the first
327
+ newpage.data['title'] = "#{Utils.format_page_title(config['title'], tmp_title, cur_page_nr, total_pages)}"
328
+ else
329
+ newpage.data['title'] = tmp_title
330
+ end
331
+
332
+ # Signals that this page is automatically generated by the pagination logic
333
+ # (we don't do this for the first page as it is there to mask the one we removed)
334
+ if cur_page_nr > 1
335
+ newpage.data['autogen'] = "j1_paginator"
336
+ end
337
+
338
+ # Add the page to the site
339
+ @page_add_lambda.call( newpage )
340
+
341
+ # Store the page in an internal list for later referencing if we need to generate a pagination number path later on
342
+ newpages << newpage
343
+ end #each.do total_pages
344
+
345
+ # Now generate the pagination number path, e.g. so that the users can have a prev 1 2 3 4 5 next structure on their page
346
+ # simplest is to include all of the links to the pages preceeding the current one
347
+ # (e.g for page 1 you get the list 2, 3, 4.... and for page 2 you get the list 3,4,5...)
348
+ if config['trail'] && newpages.size > 1
349
+ trail_before = [config['trail']['before'].to_i, 0].max
350
+ trail_after = [config['trail']['after'].to_i, 0].max
351
+ trail_length = trail_before + trail_after + 1
352
+
353
+ if( trail_before > 0 || trail_after > 0 )
354
+ newpages.select do | npage |
355
+ idx_start = [ npage.pager.page - trail_before - 1, 0].max # Selecting the beginning of the trail
356
+ idx_end = [idx_start + trail_length, newpages.size].min # Selecting the end of the trail
357
+
358
+ # Always attempt to maintain the max total of <trail_length> pages in the trail (it will look better if the trail doesn't shrink)
359
+ if( idx_end - idx_start < trail_length )
360
+ # Attempt to pad the beginning if we have enough pages
361
+ idx_start = [idx_start - ( trail_length - (idx_end - idx_start) ), 0].max # Never go beyond the zero index
362
+ end
363
+
364
+ # Convert the newpages array into a two dimensional array that has [index, page_url] as items
365
+ #puts( "Trail created for page #{npage.pager.page} (idx_start:#{idx_start} idx_end:#{idx_end})")
366
+ npage.pager.page_trail = newpages[idx_start...idx_end].each_with_index.map {|ipage,idx| PageTrail.new(idx_start+idx+1, ipage.pager.page_path, ipage.data['title'])}
367
+ #puts( npage.pager.page_trail )
368
+ end #newpages.select
369
+ end #if trail_before / trail_after
370
+ end # if config['trail']
371
+
372
+ end # function paginate
373
+
374
+ end # class PaginationV2
375
+
376
+ end # module J1Paginator
377
+ end # module Jekyll
@@ -0,0 +1,66 @@
1
+ module Jekyll
2
+ module J1Paginator::Generator
3
+
4
+ #
5
+ # This page handles the creation of the fake pagination pages based on the original page configuration
6
+ # The code does the same things as the default Jekyll/page.rb code but just forces the code to look
7
+ # into the template instead of the (currently non-existing) pagination page.
8
+ #
9
+ # This page exists purely in memory and is not read from disk
10
+ #
11
+ class PaginationPage < Page
12
+ attr_reader :relative_path
13
+
14
+ def initialize(page_to_copy, cur_page_nr, total_pages, index_pageandext)
15
+ @site = page_to_copy.site
16
+ @base = ''
17
+ @url = ''
18
+ @relative_path = page_to_copy.relative_path
19
+
20
+ if cur_page_nr == 1
21
+ @dir = File.dirname(page_to_copy.dir)
22
+ @name = page_to_copy.name
23
+ else
24
+ @name = index_pageandext.nil? ? 'index.html' : index_pageandext
25
+ end
26
+
27
+ self.process(@name) # Creates the basename and ext member values
28
+
29
+ # Copy page data over site defaults
30
+ defaults = @site.frontmatter_defaults.all(page_to_copy.relative_path, type)
31
+ self.data = Jekyll::Utils.deep_merge_hashes(defaults, page_to_copy.data)
32
+
33
+ if defaults.has_key?('permalink')
34
+ self.data['permalink'] = Jekyll::URL.new(:template => defaults['permalink'], :placeholders => self.url_placeholders).to_s
35
+ @use_permalink_for_url = true
36
+ end
37
+
38
+ if !page_to_copy.data['autopage']
39
+ self.content = page_to_copy.content
40
+ else
41
+ # If the page is an auto page then migrate the necessary autopage info across into the
42
+ # new pagination page (so that users can get the correct keys etc)
43
+ if( page_to_copy.data['autopage'].has_key?('display_name') )
44
+ self.data['autopages'] = Jekyll::Utils.deep_merge_hashes( page_to_copy.data['autopage'], {} )
45
+ end
46
+ end
47
+
48
+ # Store the current page and total page numbers in the pagination_info construct
49
+ self.data['pagination_info'] = {"curr_page" => cur_page_nr, 'total_pages' => total_pages }
50
+
51
+ # Perform some validation that is also performed in Jekyll::Page
52
+ validate_data! page_to_copy.path
53
+ validate_permalink! page_to_copy.path
54
+
55
+ # Trigger a page event
56
+ #Jekyll::Hooks.trigger :pages, :post_init, self
57
+ end
58
+
59
+ def url=(url_value)
60
+ @url = @use_permalink_for_url ? self.data['permalink'] : url_value
61
+ end
62
+ alias_method :set_url, :url=
63
+ end # class PaginationPage
64
+
65
+ end # module J1Paginator
66
+ end # module Jekyll
@@ -0,0 +1,107 @@
1
+ module Jekyll
2
+ module J1Paginator::Generator
3
+
4
+ #
5
+ # Handles the preparation of all the posts based on the current page index
6
+ #
7
+ class Paginator
8
+ attr_reader :page, :per_page, :posts, :total_posts, :total_pages,
9
+ :previous_page, :previous_page_path, :next_page, :next_page_path, :page_path, :page_trail,
10
+ :first_page, :first_page_path, :last_page, :last_page_path
11
+
12
+ def page_trail=(page_array)
13
+ @page_trail = page_array
14
+ end
15
+
16
+ # Initialize a new Paginator.
17
+ #
18
+ def initialize(config_per_page, first_index_page_url, paginated_page_url, posts, cur_page_nr, num_pages, default_indexpage, default_ext)
19
+ @page = cur_page_nr
20
+ @per_page = config_per_page.to_i
21
+ @total_pages = num_pages
22
+
23
+ if @page > @total_pages
24
+ raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
25
+ end
26
+
27
+ init = (@page - 1) * @per_page
28
+ offset = (init + @per_page - 1) >= posts.size ? posts.size : (init + @per_page - 1)
29
+
30
+ # Ensure that the current page has correct extensions if needed
31
+ this_page_url = Utils.ensure_full_path(@page == 1 ? first_index_page_url : paginated_page_url,
32
+ !default_indexpage || default_indexpage.length == 0 ? 'index' : default_indexpage,
33
+ !default_ext || default_ext.length == 0 ? '.html' : default_ext)
34
+
35
+ # To support customizable pagination pages we attempt to explicitly append the page name to
36
+ # the url incase the user is using extensionless permalinks.
37
+ if default_indexpage && default_indexpage.length > 0
38
+ # Adjust first page url
39
+ first_index_page_url = Utils.ensure_full_path(first_index_page_url, default_indexpage, default_ext)
40
+ # Adjust the paginated pages as well
41
+ paginated_page_url = Utils.ensure_full_path(paginated_page_url, default_indexpage, default_ext)
42
+ end
43
+
44
+ @total_posts = posts.size
45
+ @posts = posts[init..offset]
46
+ @page_path = Utils.format_page_number(this_page_url, cur_page_nr, @total_pages)
47
+
48
+ @previous_page = @page != 1 ? @page - 1 : nil
49
+ @previous_page_path = @page == 1 ? nil :
50
+ @page == 2 ? Utils.format_page_number(first_index_page_url, 1, @total_pages) :
51
+ Utils.format_page_number(paginated_page_url, @previous_page, @total_pages)
52
+ @next_page = @page != @total_pages ? @page + 1 : nil
53
+ @next_page_path = @page != @total_pages ? Utils.format_page_number(paginated_page_url, @next_page, @total_pages) : nil
54
+
55
+ @first_page = 1
56
+ @first_page_path = Utils.format_page_number(first_index_page_url, 1, @total_pages)
57
+ @last_page = @total_pages
58
+ @last_page_path = Utils.format_page_number(paginated_page_url, @total_pages, @total_pages)
59
+ end
60
+
61
+ # Convert this Paginator's data to a Hash suitable for use by Liquid.
62
+ #
63
+ # Returns the Hash representation of this Paginator.
64
+ def to_liquid
65
+ {
66
+ 'per_page' => per_page,
67
+ 'posts' => posts,
68
+ 'total_posts' => total_posts,
69
+ 'total_pages' => total_pages,
70
+ 'page' => page,
71
+ 'page_path' => page_path,
72
+ 'previous_page' => previous_page,
73
+ 'previous_page_path' => previous_page_path,
74
+ 'next_page' => next_page,
75
+ 'next_page_path' => next_page_path,
76
+ 'first_page' => first_page,
77
+ 'first_page_path' => first_page_path,
78
+ 'last_page' => last_page,
79
+ 'last_page_path' => last_page_path,
80
+ 'page_trail' => page_trail
81
+ }
82
+ end
83
+
84
+ end # class Paginator
85
+
86
+ # Small utility class that handles individual pagination trails
87
+ # and makes them easier to work with in Liquid
88
+ class PageTrail
89
+ attr_reader :num, :path, :title
90
+
91
+ def initialize( num, path, title )
92
+ @num = num
93
+ @path = path
94
+ @title = title
95
+ end #func initialize
96
+
97
+ def to_liquid
98
+ {
99
+ 'num' => num,
100
+ 'path' => path,
101
+ 'title' => title
102
+ }
103
+ end
104
+ end #class PageTrail
105
+
106
+ end # module J1Paginator
107
+ end # module Jekyll