jekyll-paginate-v2-redux 1.7.6

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.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +86 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +22 -0
  5. data/README-AUTOPAGES.md +104 -0
  6. data/README-GENERATOR.md +520 -0
  7. data/README.md +77 -0
  8. data/Rakefile +16 -0
  9. data/jekyll-paginate-v2-redux.gemspec +32 -0
  10. data/lib/jekyll-paginate-v2-redux.rb +34 -0
  11. data/lib/jekyll-paginate-v2-redux/autopages/autoPages.rb +77 -0
  12. data/lib/jekyll-paginate-v2-redux/autopages/defaults.rb +29 -0
  13. data/lib/jekyll-paginate-v2-redux/autopages/pages/baseAutoPage.rb +60 -0
  14. data/lib/jekyll-paginate-v2-redux/autopages/pages/categoryAutoPage.rb +28 -0
  15. data/lib/jekyll-paginate-v2-redux/autopages/pages/collectionAutoPage.rb +28 -0
  16. data/lib/jekyll-paginate-v2-redux/autopages/pages/tagAutoPage.rb +28 -0
  17. data/lib/jekyll-paginate-v2-redux/autopages/utils.rb +74 -0
  18. data/lib/jekyll-paginate-v2-redux/generator/compatibilityUtils.rb +121 -0
  19. data/lib/jekyll-paginate-v2-redux/generator/defaults.rb +24 -0
  20. data/lib/jekyll-paginate-v2-redux/generator/paginationGenerator.rb +141 -0
  21. data/lib/jekyll-paginate-v2-redux/generator/paginationIndexer.rb +93 -0
  22. data/lib/jekyll-paginate-v2-redux/generator/paginationModel.rb +358 -0
  23. data/lib/jekyll-paginate-v2-redux/generator/paginationPage.rb +50 -0
  24. data/lib/jekyll-paginate-v2-redux/generator/paginator.rb +84 -0
  25. data/lib/jekyll-paginate-v2-redux/generator/utils.rb +109 -0
  26. data/lib/jekyll-paginate-v2-redux/version.rb +10 -0
  27. data/spec/generator/defaults_spec.rb +34 -0
  28. data/spec/generator/paginationPage_spec.rb +12 -0
  29. data/spec/generator/paginator_spec.rb +107 -0
  30. data/spec/generator/utils_spec.rb +67 -0
  31. data/spec/spec_helper.rb +13 -0
  32. metadata +139 -0
@@ -0,0 +1,121 @@
1
+ module Jekyll
2
+ module PaginateV2::Generator
3
+
4
+ class CompatibilityPaginationPage < Page
5
+ def initialize(site, base, dir, template_path)
6
+ @site = site
7
+ @base = base
8
+ @dir = dir
9
+ @template = template_path
10
+ @name = 'index.html'
11
+
12
+ templ_dir = File.dirname(template_path)
13
+ templ_file = File.basename(template_path)
14
+
15
+ # Path is only used by the convertible module and accessed below when calling read_yaml
16
+ # in our case we have the path point to the original template instead of our faux new pagination page
17
+ @path = if site.in_theme_dir(base) == base # we're in a theme
18
+ site.in_theme_dir(base, templ_dir, templ_file)
19
+ else
20
+ site.in_source_dir(base, templ_dir, templ_file)
21
+ end
22
+
23
+ self.process(@name)
24
+ self.read_yaml(templ_dir, templ_file)
25
+
26
+ data.default_proc = proc do |_, key|
27
+ site.frontmatter_defaults.find(File.join(templ_dir, templ_file), type, key)
28
+ end
29
+
30
+ end
31
+ end # class CompatibilityPaginationPage
32
+
33
+ #
34
+ # Static utility functions that provide backwards compatibility with the old
35
+ # jekyll-paginate gem that this new version superseeds (this code is here to ensure)
36
+ # that sites still running the old gem work without problems
37
+ # (REMOVE AFTER 2018-01-01)
38
+ #
39
+ # THIS CLASS IS ADAPTED FROM THE ORIGINAL IMPLEMENTATION AND WILL BE REMOVED, THERE ARE DELIBERATELY NO TESTS FOR THIS CLASS
40
+ #
41
+ class CompatibilityUtils
42
+
43
+ # Public: Find the Jekyll::Page which will act as the pager template
44
+ #
45
+ # Returns the Jekyll::Page which will act as the pager template
46
+ def self.template_page(site_pages, config_source, config_paginate_path)
47
+ site_pages.select do |page|
48
+ CompatibilityUtils.pagination_candidate?(config_source, config_paginate_path, page)
49
+ end.sort do |one, two|
50
+ two.path.size <=> one.path.size
51
+ end.first
52
+ end
53
+
54
+ # Static: Determine if a page is a possible candidate to be a template page.
55
+ # Page's name must be `index.html` and exist in any of the directories
56
+ # between the site source and `paginate_path`.
57
+ def self.pagination_candidate?(config_source, config_paginate_path, page)
58
+ page_dir = File.dirname(File.expand_path(Utils.remove_leading_slash(page.path), config_source))
59
+ paginate_path = Utils.remove_leading_slash(config_paginate_path)
60
+ paginate_path = File.expand_path(paginate_path, config_source)
61
+ page.name == 'index.html' && CompatibilityUtils.in_hierarchy(config_source, page_dir, File.dirname(paginate_path))
62
+ end
63
+
64
+ # Determine if the subdirectories of the two paths are the same relative to source
65
+ #
66
+ # source - the site source
67
+ # page_dir - the directory of the Jekyll::Page
68
+ # paginate_path - the absolute paginate path (from root of FS)
69
+ #
70
+ # Returns whether the subdirectories are the same relative to source
71
+ def self.in_hierarchy(source, page_dir, paginate_path)
72
+ return false if paginate_path == File.dirname(paginate_path)
73
+ return false if paginate_path == Pathname.new(source).parent
74
+ page_dir == paginate_path ||
75
+ CompatibilityUtils.in_hierarchy(source, page_dir, File.dirname(paginate_path))
76
+ end
77
+
78
+ # Paginates the blog's posts. Renders the index.html file into paginated
79
+ # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
80
+ # site-wide data.
81
+ #
82
+ def self.paginate(legacy_config, all_posts, page, page_add_lambda )
83
+ pages = Utils.calculate_number_of_pages(all_posts, legacy_config['per_page'].to_i)
84
+ (1..pages).each do |num_page|
85
+ pager = Paginator.new( legacy_config['per_page'], page.url, legacy_config['permalink'], all_posts, num_page, pages )
86
+ if num_page > 1
87
+ template_full_path = File.join(page.site.source, page.path)
88
+ template_dir = File.dirname(page.path)
89
+ newpage = CompatibilityPaginationPage.new(page.site, page.site.source, template_dir, template_full_path)
90
+ newpage.pager = pager
91
+ newpage.dir = CompatibilityUtils.paginate_path(page.url, num_page, legacy_config['permalink'])
92
+ newpage.data['autogen'] = "jekyll-paginate-v2-redux" # Signals that this page is automatically generated by the pagination logic
93
+ page_add_lambda.call(newpage)
94
+ else
95
+ page.pager = pager
96
+ end
97
+ end
98
+ end
99
+
100
+ # Static: Return the pagination path of the page
101
+ #
102
+ # site - the Jekyll::Site object
103
+ # cur_page_nr - the pagination page number
104
+ # config - the current configuration in use
105
+ #
106
+ # Returns the pagination path as a string
107
+ def self.paginate_path(template_url, cur_page_nr, permalink_format)
108
+ return nil if cur_page_nr.nil?
109
+ return template_url if cur_page_nr <= 1
110
+ if permalink_format.include?(":num")
111
+ permalink_format = Utils.format_page_number(permalink_format, cur_page_nr)
112
+ else
113
+ raise ArgumentError.new("Invalid pagination path: '#{permalink_format}'. It must include ':num'.")
114
+ end
115
+
116
+ Utils.ensure_leading_slash(permalink_format)
117
+ end #function paginate_path
118
+
119
+ end # class CompatibilityUtils
120
+ end # module PaginateV2
121
+ end # module Jekyll
@@ -0,0 +1,24 @@
1
+ module Jekyll
2
+ module PaginateV2::Generator
3
+
4
+ # The default configuration for the Paginator
5
+ DEFAULT = {
6
+ 'enabled' => false,
7
+ 'collection' => 'posts',
8
+ 'per_page' => 10,
9
+ 'permalink' => '/page:num/', # Supports :num as customizable elements
10
+ 'title' => ':title - page :num', # Supports :num as customizable elements
11
+ 'page_num' => 1,
12
+ 'sort_reverse' => false,
13
+ 'sort_field' => 'date',
14
+ 'limit' => 0, # Limit how many content objects to paginate (default: 0, means all)
15
+ 'trail' => {
16
+ 'before' => 0, # Limits how many links to show before the current page in the pagination trail (0, means off, default: 0)
17
+ 'after' => 0, # Limits how many links to show after the current page in the pagination trail (0 means off, default: 0)
18
+ },
19
+ 'debug' => false, # Turns on debug output for the gem
20
+ 'legacy' => false # Internal value, do not use (will be removed after 2018-01-01)
21
+ }
22
+
23
+ end # module PaginateV2
24
+ end # module Jekyll
@@ -0,0 +1,141 @@
1
+ module Jekyll
2
+ module PaginateV2::Generator
3
+
4
+ #
5
+ # The main entry point into the generator, called by Jekyll
6
+ # this function extracts all the necessary information from the jekyll end and passes it into the pagination
7
+ # logic. Additionally it also contains all site specific actions that the pagination logic needs access to
8
+ # (such as how to create new pages)
9
+ #
10
+ class PaginationGenerator < Generator
11
+ # This generator is safe from arbitrary code execution.
12
+ safe true
13
+
14
+ # This generator should be passive with regard to its execution
15
+ priority :lowest
16
+
17
+ # Generate paginated pages if necessary (Default entry point)
18
+ # site - The Site.
19
+ #
20
+ # Returns nothing.
21
+ def generate(site)
22
+ #begin
23
+ # Retrieve and merge the pagination configuration from the site yml file
24
+ default_config = Jekyll::Utils.deep_merge_hashes(DEFAULT, site.config['pagination'] || {})
25
+
26
+ # Compatibility Note: (REMOVE AFTER 2018-01-01)
27
+ # If the legacy paginate logic is configured then read those values and merge with config
28
+ if !site.config['paginate'].nil?
29
+ # You cannot run both the new code and the old code side by side
30
+ if !site.config['pagination'].nil?
31
+ err_msg = "The new jekyll-paginate-v2-redux and the old jekyll-paginate logic cannot both be configured in the site config at the same time. Please disable the old 'paginate:' config settings."
32
+ Jekyll.logger.error err_msg
33
+ raise ArgumentError.new(err_msg)
34
+ end
35
+
36
+ default_config['per_page'] = site.config['paginate'].to_i
37
+ default_config['legacy_source'] = site.config['source']
38
+ if !site.config['paginate_path'].nil?
39
+ default_config['permalink'] = site.config['paginate_path'].to_s
40
+ end
41
+ # In case of legacy, enable pagination by default
42
+ default_config['enabled'] = true
43
+ default_config['legacy'] = true
44
+ end # Compatibility END (REMOVE AFTER 2018-01-01)
45
+
46
+ # If disabled then simply quit
47
+ if !default_config['enabled']
48
+ Jekyll.logger.info "Pagination:","Disabled in site.config."
49
+ return
50
+ end
51
+
52
+ # Handle deprecation of settings and features
53
+ if( !default_config['title_suffix' ].nil? )
54
+ Jekyll::Deprecator.deprecation_message "Pagination: The 'title_suffix' configuration has been deprecated. Please use 'title'. See https://github.com/sverrirs/jekyll-paginate-v2/blob/master/README-GENERATOR.md#site-configuration"
55
+ end
56
+
57
+ Jekyll.logger.debug "Pagination:","Starting"
58
+
59
+ ################ 0 ####################
60
+ # Get all pages in the site (this will be used to find the pagination templates)
61
+ all_pages = site.pages
62
+
63
+ # Get the default title of the site (used as backup when there is no title available for pagination)
64
+ site_title = site.config['title']
65
+
66
+ ################ 1 ####################
67
+ # Specify the callback function that returns the correct docs/posts based on the collection name
68
+ # "posts" are just another collection in Jekyll but a specialized version that require timestamps
69
+ # This collection is the default and if the user doesn't specify a collection in their front-matter then that is the one we load
70
+ # If the collection is not found then empty array is returned
71
+ collection_by_name_lambda = lambda do |collection_name|
72
+ coll = []
73
+ if collection_name == "all"
74
+ # the 'all' collection_name is a special case and includes all collections in the site (except posts!!)
75
+ # this is useful when you want to list items across multiple collections
76
+ for coll_name, coll_data in site.collections
77
+ if( !coll_data.nil? && coll_name != 'posts')
78
+ coll += coll_data.docs.select { |doc| !doc.data.has_key?('pagination') } # Exclude all pagination pages
79
+ end
80
+ end
81
+ else
82
+ # Just the one collection requested
83
+ if !site.collections.has_key?(collection_name)
84
+ return []
85
+ end
86
+
87
+ coll = site.collections[collection_name].docs.select { |doc| !doc.data.has_key?('pagination') } # Exclude all pagination pages
88
+ end
89
+ return coll
90
+ end
91
+
92
+ ################ 2 ####################
93
+ # Create the proc that constructs the real-life site page
94
+ # This is necessary to decouple the code from the Jekyll site object
95
+ page_add_lambda = lambda do | newpage |
96
+ site.pages << newpage # Add the page to the site so that it is generated correctly
97
+ return newpage # Return the site to the calling code
98
+ end
99
+
100
+ ################ 2.5 ####################
101
+ # lambda that removes a page from the site pages list
102
+ page_remove_lambda = lambda do | page_to_remove |
103
+ site.pages.delete_if {|page| page == page_to_remove }
104
+ end
105
+
106
+ ################ 3 ####################
107
+ # Create a proc that will delegate logging
108
+ # Decoupling Jekyll specific logging
109
+ logging_lambda = lambda do | message, type="info" |
110
+ if type == 'debug'
111
+ Jekyll.logger.debug "Pagination:","#{message}"
112
+ elsif type == 'error'
113
+ Jekyll.logger.error "Pagination:", "#{message}"
114
+ elsif type == 'warn'
115
+ Jekyll.logger.warn "Pagination:", "#{message}"
116
+ else
117
+ Jekyll.logger.info "Pagination:", "#{message}"
118
+ end
119
+ end
120
+
121
+ ################ 4 ####################
122
+ # Now create and call the model with the real-life page creation proc and site data
123
+ model = PaginationModel.new(logging_lambda, page_add_lambda, page_remove_lambda, collection_by_name_lambda)
124
+ if( default_config['legacy'] ) #(REMOVE AFTER 2018-01-01)
125
+ Jekyll.logger.warn "Pagination:", "You are running jekyll-paginate backwards compatible pagination logic. Please ignore all earlier warnings displayed related to the old jekyll-paginate gem."
126
+ all_posts = site.site_payload['site']['posts'].reject { |post| post['hidden'] }
127
+ model.run_compatability(default_config, all_pages, site_title, all_posts) #(REMOVE AFTER 2018-01-01)
128
+ else
129
+ count = model.run(default_config, all_pages, site_title)
130
+ Jekyll.logger.info "Pagination:", "Complete, processed #{count} pagination page(s)"
131
+ end
132
+
133
+ #rescue => ex
134
+ # puts ex.backtrace
135
+ # raise
136
+ #end
137
+ end # function generate
138
+ end # class PaginationGenerator
139
+
140
+ end # module PaginateV2
141
+ end # module Jekyll
@@ -0,0 +1,93 @@
1
+ module Jekyll
2
+ module PaginateV2::Generator
3
+
4
+ #
5
+ # Performs indexing of the posts or collection documents
6
+ # as well as filtering said collections when requested by the defined filters.
7
+ class PaginationIndexer
8
+ #
9
+ # Create a hash index for all post based on a key in the post.data table
10
+ #
11
+ def self.index_posts_by(all_posts, index_key)
12
+ return nil if all_posts.nil?
13
+ return all_posts if index_key.nil?
14
+ index = {}
15
+ for post in all_posts
16
+ next if post.data.nil?
17
+ next if !post.data.has_key?(index_key)
18
+ next if post.data[index_key].nil?
19
+ next if post.data[index_key].size <= 0
20
+ next if post.data[index_key].to_s.strip.length == 0
21
+
22
+ # Only tags and categories come as premade arrays, locale does not, so convert any data
23
+ # elements that are strings into arrays
24
+ post_data = post.data[index_key]
25
+ if post_data.is_a?(String)
26
+ post_data = post_data.split(/;|,|\s/)
27
+ end
28
+
29
+ for key in post_data
30
+ key = key.to_s.downcase.strip
31
+ # If the key is a delimetered list of values
32
+ # (meaning the user didn't use an array but a string with commas)
33
+ for k_split in key.split(/;|,/)
34
+ k_split = k_split.to_s.downcase.strip #Clean whitespace and junk
35
+ if !index.has_key?(k_split)
36
+ index[k_split.to_s] = []
37
+ end
38
+ index[k_split.to_s] << post
39
+ end
40
+ end
41
+ end
42
+ return index
43
+ end # function index_posts_by
44
+
45
+ #
46
+ # Creates an intersection (only returns common elements)
47
+ # between multiple arrays
48
+ #
49
+ def self.intersect_arrays(first, *rest)
50
+ return nil if first.nil?
51
+ return nil if rest.nil?
52
+
53
+ intersect = first
54
+ rest.each do |item|
55
+ return [] if item.nil?
56
+ intersect = intersect & item
57
+ end
58
+ return intersect
59
+ end #function intersect_arrays
60
+
61
+ #
62
+ # Filters posts based on a keyed source_posts hash of indexed posts and performs a intersection of
63
+ # the two sets. Returns only posts that are common between all collections
64
+ #
65
+ def self.read_config_value_and_filter_posts(config, config_key, posts, source_posts)
66
+ return nil if posts.nil?
67
+ return nil if source_posts.nil? # If the source is empty then simply don't do anything
68
+ return posts if config.nil?
69
+ return posts if !config.has_key?(config_key)
70
+ return posts if config[config_key].nil?
71
+
72
+ # Get the filter values from the config (this is the cat/tag/locale values that should be filtered on)
73
+ config_value = config[config_key]
74
+
75
+ # If we're dealing with a delimitered string instead of an array then let's be forgiving
76
+ if( config_value.is_a?(String))
77
+ config_value = config_value.split(/;|,/)
78
+ end
79
+
80
+ # Now for all filter values for the config key, let's remove all items from the posts that
81
+ # aren't common for all collections that the user wants to filter on
82
+ for key in config_value
83
+ key = key.to_s.downcase.strip
84
+ posts = PaginationIndexer.intersect_arrays(posts, source_posts[key])
85
+ end
86
+
87
+ # The fully filtered final post list
88
+ return posts
89
+ end #function read_config_value_and_filter_posts
90
+ end #class PaginationIndexer
91
+
92
+ end #module PaginateV2
93
+ end #module Jekyll
@@ -0,0 +1,358 @@
1
+ module Jekyll
2
+ module PaginateV2::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.to_i <= 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
+ for template in templates
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
+ # requiring this makes the logic simpler as I don't need to determine which index pages
48
+ # were generated automatically and which weren't
49
+ if( template_config['enabled'] )
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.to_i
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 jekyll-paginate-v2 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 jekyll-paginate-v2 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
+ for coll_name in collection_names
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
+ return docs
122
+ end
123
+
124
+ def _fix_deprecated_config_features(config)
125
+ keys_to_delete = []
126
+
127
+ # As of v1.5.1 the title_suffix is deprecated and 'title' should be used
128
+ # but only if title has not been defined already!
129
+ if( !config['title_suffix'].nil? )
130
+ if( config['title'].nil? )
131
+ config['title'] = ":title" + config['title_suffix'].to_s # Migrate the old key to title
132
+ end
133
+ keys_to_delete << "title_suffix" # Always remove the depricated key if found
134
+ end
135
+
136
+ # Delete the depricated keys
137
+ config.delete_if{ |k,| keys_to_delete.include? k }
138
+ end
139
+
140
+ def _debug_print_config_info(config, page_path)
141
+ r = 20
142
+ f = "Pagination: ".rjust(20)
143
+ # Debug print the config
144
+ if @debug
145
+ puts f + "----------------------------"
146
+ puts f + "Page: "+page_path.to_s
147
+ puts f + " Active configuration"
148
+ puts f + " Enabled: ".ljust(r) + config['enabled'].to_s
149
+ puts f + " Items per page: ".ljust(r) + config['per_page'].to_s
150
+ puts f + " Permalink: ".ljust(r) + config['permalink'].to_s
151
+ puts f + " Title: ".ljust(r) + config['title'].to_s
152
+ puts f + " Limit: ".ljust(r) + config['limit'].to_s
153
+ puts f + " Sort by: ".ljust(r) + config['sort_field'].to_s
154
+ puts f + " Sort reverse: ".ljust(r) + config['sort_reverse'].to_s
155
+
156
+ puts f + " Active Filters"
157
+ puts f + " Collection: ".ljust(r) + config['collection'].to_s
158
+ puts f + " Category: ".ljust(r) + (config['category'].nil? || config['category'] == "posts" ? "[Not set]" : config['category'].to_s)
159
+ puts f + " Tag: ".ljust(r) + (config['tag'].nil? ? "[Not set]" : config['tag'].to_s)
160
+ puts f + " Locale: ".ljust(r) + (config['locale'].nil? ? "[Not set]" : config['locale'].to_s)
161
+
162
+ if config['legacy']
163
+ puts f + " Legacy Paginate Code Enabled"
164
+ puts f + " Legacy Paginate: ".ljust(r) + config['per_page'].to_s
165
+ puts f + " Legacy Source: ".ljust(r) + config['legacy_source'].to_s
166
+ puts f + " Legacy Path: ".ljust(r) + config['paginate_path'].to_s
167
+ end
168
+ end
169
+ end
170
+
171
+ def _debug_print_filtering_info(filter_name, before_count, after_count)
172
+ # Debug print the config
173
+ if @debug
174
+ puts "Pagination: ".rjust(20) + " Filtering by: "+filter_name.to_s.ljust(9) + " " + before_count.to_s.rjust(3) + " => " + after_count.to_s
175
+ end
176
+ end
177
+
178
+ #
179
+ # Rolls through all the pages passed in and finds all pages that have pagination enabled on them.
180
+ # These pages will be used as templates
181
+ #
182
+ # site_pages - All pages in the site
183
+ #
184
+ def discover_paginate_templates(site_pages)
185
+ candidates = []
186
+ site_pages.select do |page|
187
+ # If the page has the enabled config set, supports any type of file name html or md
188
+ if page.data['pagination'].is_a?(Hash) && page.data['pagination']['enabled']
189
+ candidates << page
190
+ end
191
+ end
192
+ return candidates
193
+ end # function discover_paginate_templates
194
+
195
+ # Paginates the blog's posts. Renders the index.html file into paginated
196
+ # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
197
+ # site-wide data.
198
+ #
199
+ # site - The Site.
200
+ # template - The index.html Page that requires pagination.
201
+ # config - The configuration settings that should be used
202
+ #
203
+ def paginate(template, config, site_title, all_posts, all_tags, all_categories, all_locales)
204
+ # By default paginate on all posts in the site
205
+ using_posts = all_posts
206
+
207
+ # Now start filtering out any posts that the user doesn't want included in the pagination
208
+ before = using_posts.size.to_i
209
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'category', using_posts, all_categories)
210
+ self._debug_print_filtering_info('Category', before, using_posts.size.to_i)
211
+ before = using_posts.size.to_i
212
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'tag', using_posts, all_tags)
213
+ self._debug_print_filtering_info('Tag', before, using_posts.size.to_i)
214
+ before = using_posts.size.to_i
215
+ using_posts = PaginationIndexer.read_config_value_and_filter_posts(config, 'locale', using_posts, all_locales)
216
+ self._debug_print_filtering_info('Locale', before, using_posts.size.to_i)
217
+
218
+ # Apply sorting to the posts if configured, any field for the post is available for sorting
219
+ if config['sort_field']
220
+ sort_field = config['sort_field'].to_s
221
+
222
+ # There is an issue in Jekyll related to lazy initialized member variables that causes iterators to
223
+ # break when accessing an uninitialized value during iteration. This happens for document.rb when the <=> compaison function
224
+ # is called (as this function calls the 'date' field which for drafts are not initialized.)
225
+ # So to unblock this common issue for the date field I simply iterate once over every document and initialize the .date field explicitly
226
+ if @debug
227
+ puts "Pagination: ".rjust(20) + "Rolling through the date fields for all documents"
228
+ end
229
+ for p in using_posts
230
+ tmp_date = p.date
231
+ if( !tmp_date || tmp_date.nil? )
232
+ if @debug
233
+ puts "Pagination: ".rjust(20) + "Explicitly assigning date for doc: #{p.data['title']} | #{p.path}"
234
+ end
235
+ p.date = File.mtime(p.path)
236
+ end
237
+ end
238
+
239
+ 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)) }
240
+
241
+ if config['sort_reverse']
242
+ using_posts.reverse!
243
+ end
244
+ end
245
+
246
+ # Calculate the max number of pagination-pages based on the configured per page value
247
+ total_pages = Utils.calculate_number_of_pages(using_posts, config['per_page'])
248
+
249
+ # If a upper limit is set on the number of total pagination pages then impose that now
250
+ if config['limit'] && config['limit'].to_i > 0 && config['limit'].to_i < total_pages
251
+ total_pages = config['limit'].to_i
252
+ end
253
+
254
+ #### BEFORE STARTING REMOVE THE TEMPLATE PAGE FROM THE SITE LIST!
255
+ @page_remove_lambda.call( template )
256
+
257
+ # list of all newly created pages
258
+ newpages = []
259
+
260
+ if total_pages == 0
261
+ total_pages = 1
262
+ end
263
+ # Now for each pagination page create it and configure the ranges for the collection
264
+ # This .pager member is a built in thing in Jekyll and defines the paginator implementation
265
+ # Simpy override to use mine
266
+ (1..total_pages).each do |cur_page_nr|
267
+
268
+ # 1. Create the in-memory page
269
+ # External Proc call to create the actual page for us (this is passed in when the pagination is run)
270
+ newpage = PaginationPage.new( template, cur_page_nr, total_pages )
271
+
272
+ # 2. Create the url for the in-memory page (calc permalink etc), construct the title, set all page.data values needed
273
+ paginated_page_url = config['permalink']
274
+ first_index_page_url = ""
275
+ if template.data['permalink']
276
+ first_index_page_url = Utils.ensure_trailing_slash(template.data['permalink'])
277
+ else
278
+ first_index_page_url = Utils.ensure_trailing_slash(template.dir)
279
+ end
280
+ paginated_page_url = File.join(first_index_page_url, paginated_page_url)
281
+
282
+ # 3. Create the pager logic for this page, pass in the prev and next page numbers, assign pager to in-memory page
283
+ newpage.pager = Paginator.new( config['per_page'], first_index_page_url, paginated_page_url, using_posts, cur_page_nr, total_pages)
284
+
285
+ # Create the url for the new page, make sure we prepend any permalinks that are defined in the template page before
286
+ if newpage.pager.page_path.end_with? '/'
287
+ newpage.set_url(File.join(newpage.pager.page_path, 'index.html'))
288
+ elsif newpage.pager.page_path.end_with? '.html'
289
+ # Support for direct .html files
290
+ newpage.set_url(newpage.pager.page_path)
291
+ else
292
+ # Support for extensionless permalinks
293
+ newpage.set_url(newpage.pager.page_path+'.html')
294
+ end
295
+
296
+ if( template.data['permalink'] )
297
+ newpage.data['permalink'] = newpage.pager.page_path
298
+ end
299
+
300
+ # Transfer the title across to the new page
301
+ if( !template.data['title'] )
302
+ tmp_title = site_title
303
+ else
304
+ tmp_title = template.data['title']
305
+ end
306
+ # If the user specified a title suffix to be added then let's add that to all the pages except the first
307
+ if( cur_page_nr > 1 && config.has_key?('title') )
308
+ newpage.data['title'] = "#{Utils.format_page_title(config['title'], tmp_title, cur_page_nr, total_pages)}"
309
+ else
310
+ newpage.data['title'] = tmp_title
311
+ end
312
+
313
+ # Signals that this page is automatically generated by the pagination logic
314
+ # (we don't do this for the first page as it is there to mask the one we removed)
315
+ if cur_page_nr > 1
316
+ newpage.data['autogen'] = "jekyll-paginate-v2-redux"
317
+ end
318
+
319
+ # Add the page to the site
320
+ @page_add_lambda.call( newpage )
321
+
322
+ # Store the page in an internal list for later referencing if we need to generate a pagination number path later on
323
+ newpages << newpage
324
+ end #each.do total_pages
325
+
326
+ # 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
327
+ # simplest is to include all of the links to the pages preceeding the current one
328
+ # (e.g for page 1 you get the list 2, 3, 4.... and for page 2 you get the list 3,4,5...)
329
+ if( config['trail'] && !config['trail'].nil? && newpages.size.to_i > 1 )
330
+ trail_before = [config['trail']['before'].to_i, 0].max
331
+ trail_after = [config['trail']['after'].to_i, 0].max
332
+ trail_length = trail_before + trail_after + 1
333
+
334
+ if( trail_before > 0 || trail_after > 0 )
335
+ newpages.select do | npage |
336
+ idx_start = [ npage.pager.page - trail_before - 1, 0].max # Selecting the beginning of the trail
337
+ idx_end = [idx_start + trail_length, newpages.size.to_i].min # Selecting the end of the trail
338
+
339
+ # Always attempt to maintain the max total of <trail_length> pages in the trail (it will look better if the trail doesn't shrink)
340
+ if( idx_end - idx_start < trail_length )
341
+ # Attempt to pad the beginning if we have enough pages
342
+ idx_start = [idx_start - ( trail_length - (idx_end - idx_start) ), 0].max # Never go beyond the zero index
343
+ end
344
+
345
+ # Convert the newpages array into a two dimensional array that has [index, page_url] as items
346
+ #puts( "Trail created for page #{npage.pager.page} (idx_start:#{idx_start} idx_end:#{idx_end})")
347
+ 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'])}
348
+ #puts( npage.pager.page_trail )
349
+ end #newpages.select
350
+ end #if trail_before / trail_after
351
+ end # if config['trail']
352
+
353
+ end # function paginate
354
+
355
+ end # class PaginationV2
356
+
357
+ end # module PaginateV2
358
+ end # module Jekyll