bridgetown-paginate 0.8.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/.rspec +2 -0
- data/.rubocop.yml +52 -0
- data/Rakefile +3 -0
- data/bridgetown-paginate.gemspec +18 -0
- data/lib/bridgetown-paginate.rb +26 -0
- data/lib/bridgetown-paginate/defaults.rb +32 -0
- data/lib/bridgetown-paginate/pagination_generator.rb +122 -0
- data/lib/bridgetown-paginate/pagination_indexer.rb +105 -0
- data/lib/bridgetown-paginate/pagination_model.rb +419 -0
- data/lib/bridgetown-paginate/pagination_page.rb +60 -0
- data/lib/bridgetown-paginate/paginator.rb +139 -0
- data/lib/bridgetown-paginate/utils.rb +139 -0
- metadata +68 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7af8a59db674148dbfbb66a2ade0333ceb589917f22eef04533cde1cd097d65d
|
|
4
|
+
data.tar.gz: e75a96fc01a65161609c90923a222883dfcf500102dc9f36d7924468545fc04f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b261fecd3cbae9ac8e8f0dfd3e5232e1fc516cd6a667cba3467e23152109d3e5a07364cfee7f694b36cd35f524532f0a01db79415649a9e999faa67c3aceb81a
|
|
7
|
+
data.tar.gz: 2a1d1d909a289a7f0578792b11b9eb2d1b2fc30d6cf39de4c52313f0d7eb4237fc8540ad23d994820f3d9fc03355222344bdc5c397cf1ccd3b5657b875d9606b
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
inherit_from: ../.rubocop.yml
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
Include:
|
|
6
|
+
- lib/**/*.rb
|
|
7
|
+
- spec/**/*.rb
|
|
8
|
+
|
|
9
|
+
Bridgetown/NoPutsAllowed:
|
|
10
|
+
Exclude:
|
|
11
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
12
|
+
Layout/CommentIndentation:
|
|
13
|
+
Exclude:
|
|
14
|
+
- lib/bridgetown-paginate/defaults.rb
|
|
15
|
+
Metrics/AbcSize:
|
|
16
|
+
Exclude:
|
|
17
|
+
- lib/bridgetown-paginate/pagination_generator.rb
|
|
18
|
+
- lib/bridgetown-paginate/pagination_indexer.rb
|
|
19
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
20
|
+
- lib/bridgetown-paginate/pagination_page.rb
|
|
21
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
22
|
+
Metrics/BlockNesting:
|
|
23
|
+
Exclude:
|
|
24
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
25
|
+
Metrics/ClassLength:
|
|
26
|
+
Exclude:
|
|
27
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Exclude:
|
|
30
|
+
- lib/bridgetown-paginate/pagination_generator.rb
|
|
31
|
+
- lib/bridgetown-paginate/pagination_indexer.rb
|
|
32
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
33
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
34
|
+
Metrics/MethodLength:
|
|
35
|
+
Exclude:
|
|
36
|
+
- lib/bridgetown-paginate/pagination_generator.rb
|
|
37
|
+
- lib/bridgetown-paginate/pagination_indexer.rb
|
|
38
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
39
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
40
|
+
Metrics/ParameterLists:
|
|
41
|
+
Exclude:
|
|
42
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
43
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
44
|
+
Metrics/PerceivedComplexity:
|
|
45
|
+
Exclude:
|
|
46
|
+
- lib/bridgetown-paginate/pagination_generator.rb
|
|
47
|
+
- lib/bridgetown-paginate/pagination_indexer.rb
|
|
48
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
49
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
50
|
+
Style/Next:
|
|
51
|
+
Exclude:
|
|
52
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
data/Rakefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../bridgetown-core/lib/bridgetown-core/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "bridgetown-paginate"
|
|
7
|
+
spec.version = Bridgetown::VERSION
|
|
8
|
+
spec.author = "Bridgetown Team"
|
|
9
|
+
spec.email = "maintainers@bridgetownrb.com"
|
|
10
|
+
spec.summary = "A Bridgetown plugin to add pagination support for posts and collection indices."
|
|
11
|
+
spec.homepage = "https://github.com/bridgetownrb/bridgetown-paginate"
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
|
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|script|spec|features)/!) }
|
|
15
|
+
spec.require_paths = ["lib"]
|
|
16
|
+
|
|
17
|
+
spec.add_dependency("bridgetown-core", Bridgetown::VERSION)
|
|
18
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#################################################
|
|
4
|
+
# Special thanks to Sverrir Sigmundarson and the
|
|
5
|
+
# contributors to Jekyll::Paginate V2 for the
|
|
6
|
+
# basis of this gem.
|
|
7
|
+
# https://github.com/sverrirs/jekyll-paginate-v2
|
|
8
|
+
#################################################
|
|
9
|
+
|
|
10
|
+
require "bridgetown-core"
|
|
11
|
+
require "bridgetown-core/version"
|
|
12
|
+
|
|
13
|
+
unless ENV["BRIDGETOWN_DISABLE_PAGINATE_GEM"] == "true"
|
|
14
|
+
module Bridgetown
|
|
15
|
+
module Paginate
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
require "bridgetown-paginate/defaults"
|
|
20
|
+
require "bridgetown-paginate/utils"
|
|
21
|
+
require "bridgetown-paginate/pagination_indexer"
|
|
22
|
+
require "bridgetown-paginate/paginator"
|
|
23
|
+
require "bridgetown-paginate/pagination_page"
|
|
24
|
+
require "bridgetown-paginate/pagination_model"
|
|
25
|
+
require "bridgetown-paginate/pagination_generator"
|
|
26
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
# The default configuration for the Paginator
|
|
7
|
+
DEFAULT = {
|
|
8
|
+
"enabled" => false,
|
|
9
|
+
"collection" => "posts",
|
|
10
|
+
"offset" => 0, # Supports skipping x number of posts from the
|
|
11
|
+
# beginning of the post list
|
|
12
|
+
"per_page" => 10,
|
|
13
|
+
"permalink" => "/page/:num/", # Supports :num as customizable elements
|
|
14
|
+
"title" => ":title (Page :num)", # Supports :num as customizable elements
|
|
15
|
+
"page_num" => 1,
|
|
16
|
+
"sort_reverse" => false,
|
|
17
|
+
"sort_field" => "date",
|
|
18
|
+
"limit" => 0, # Limit how many content objects to paginate (default: 0, means all)
|
|
19
|
+
"trail" => {
|
|
20
|
+
"before" => 0, # Limits how many links to show before the current page
|
|
21
|
+
# in the pagination trail (0, means off, default: 0)
|
|
22
|
+
"after" => 0, # Limits how many links to show after the current page
|
|
23
|
+
# in the pagination trail (0 means off, default: 0)
|
|
24
|
+
},
|
|
25
|
+
"indexpage" => nil, # The default name of the index pages
|
|
26
|
+
"extension" => "html", # The default extension for the output pages
|
|
27
|
+
# (ignored if indexpage is nil)
|
|
28
|
+
"debug" => false, # Turns on debug output for the gem
|
|
29
|
+
}.freeze
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
#
|
|
7
|
+
# The main entry point into the generator, called by Bridgetown
|
|
8
|
+
# this function extracts all the necessary information from the Bridgetown
|
|
9
|
+
# end and passes it into the pagination logic. Additionally it also
|
|
10
|
+
# contains all site specific actions that the pagination logic needs access
|
|
11
|
+
# to (such as how to create new pages)
|
|
12
|
+
#
|
|
13
|
+
class PaginationGenerator < Bridgetown::Generator
|
|
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
|
+
# Retrieve and merge the pagination configuration from the site yml file
|
|
23
|
+
default_config = Bridgetown::Utils.deep_merge_hashes(
|
|
24
|
+
DEFAULT,
|
|
25
|
+
site.config["pagination"] || {}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# If disabled then simply quit
|
|
29
|
+
unless default_config["enabled"]
|
|
30
|
+
Bridgetown.logger.info "Pagination:", "Disabled in site.config."
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Bridgetown.logger.debug "Pagination:", "Starting"
|
|
35
|
+
|
|
36
|
+
################ 0 ####################
|
|
37
|
+
# Get all pages in the site (this will be used to find the pagination
|
|
38
|
+
# templates)
|
|
39
|
+
all_pages = site.pages
|
|
40
|
+
|
|
41
|
+
# Get the default title of the site (used as backup when there is no
|
|
42
|
+
# title available for pagination)
|
|
43
|
+
site_title = site.data.dig("metadata", "title") || site.config["title"]
|
|
44
|
+
|
|
45
|
+
################ 1 ####################
|
|
46
|
+
# Specify the callback function that returns the correct docs/posts
|
|
47
|
+
# based on the collection name
|
|
48
|
+
# "posts" are just another collection in Bridgetown but a specialized
|
|
49
|
+
# version that require timestamps
|
|
50
|
+
# This collection is the default and if the user doesn't specify a
|
|
51
|
+
# collection in their front-matter then that is the one we load
|
|
52
|
+
# If the collection is not found then empty array is returned
|
|
53
|
+
collection_by_name_lambda = lambda do |collection_name|
|
|
54
|
+
coll = []
|
|
55
|
+
if collection_name == "all"
|
|
56
|
+
# the 'all' collection_name is a special case and includes all
|
|
57
|
+
# collections in the site (except posts!!)
|
|
58
|
+
# this is useful when you want to list items across multiple collections
|
|
59
|
+
site.collections.each do |coll_name, coll_data|
|
|
60
|
+
next unless !coll_data.nil? && coll_name != "posts"
|
|
61
|
+
|
|
62
|
+
# Exclude all pagination pages
|
|
63
|
+
coll += coll_data.docs.reject do |doc|
|
|
64
|
+
doc.data.key?("pagination")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
# Just the one collection requested
|
|
69
|
+
return [] unless site.collections.key?(collection_name)
|
|
70
|
+
|
|
71
|
+
# Exclude all pagination pages
|
|
72
|
+
coll = site.collections[collection_name].docs.reject do |doc|
|
|
73
|
+
doc.data.key?("pagination")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
return coll
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
################ 2 ####################
|
|
80
|
+
# Create the proc that constructs the real-life site page
|
|
81
|
+
# This is necessary to decouple the code from the Bridgetown site object
|
|
82
|
+
page_add_lambda = lambda do |newpage|
|
|
83
|
+
site.pages << newpage # Add the page to the site so that it is generated correctly
|
|
84
|
+
return newpage # Return the site to the calling code
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
################ 2.5 ####################
|
|
88
|
+
# lambda that removes a page from the site pages list
|
|
89
|
+
page_remove_lambda = lambda do |page_to_remove|
|
|
90
|
+
site.pages.delete_if { |page| page == page_to_remove }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
################ 3 ####################
|
|
94
|
+
# Create a proc that will delegate logging
|
|
95
|
+
# Decoupling Bridgetown specific logging
|
|
96
|
+
logging_lambda = lambda do |message, type = "info"|
|
|
97
|
+
if type == "debug"
|
|
98
|
+
Bridgetown.logger.debug "Pagination:", message.to_s
|
|
99
|
+
elsif type == "error"
|
|
100
|
+
Bridgetown.logger.error "Pagination:", message.to_s
|
|
101
|
+
elsif type == "warn"
|
|
102
|
+
Bridgetown.logger.warn "Pagination:", message.to_s
|
|
103
|
+
else
|
|
104
|
+
Bridgetown.logger.info "Pagination:", message.to_s
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
################ 4 ####################
|
|
109
|
+
# Now create and call the model with the real-life page creation proc and site data
|
|
110
|
+
model = PaginationModel.new(
|
|
111
|
+
logging_lambda,
|
|
112
|
+
page_add_lambda,
|
|
113
|
+
page_remove_lambda,
|
|
114
|
+
collection_by_name_lambda
|
|
115
|
+
)
|
|
116
|
+
count = model.run(default_config, all_pages, site_title)
|
|
117
|
+
Bridgetown.logger.info "Pagination:", "Complete, processed #{count} pagination page(s)"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
#
|
|
7
|
+
# Performs indexing of the posts or collection documents as well as
|
|
8
|
+
# filtering said collections when requested by the defined filters.
|
|
9
|
+
#
|
|
10
|
+
class PaginationIndexer
|
|
11
|
+
#
|
|
12
|
+
# Create a hash index for all documents based on a key in the
|
|
13
|
+
# document.data table
|
|
14
|
+
#
|
|
15
|
+
def self.index_documents_by(all_documents, index_key)
|
|
16
|
+
return nil if all_documents.nil?
|
|
17
|
+
return all_documents if index_key.nil?
|
|
18
|
+
|
|
19
|
+
index = {}
|
|
20
|
+
all_documents.each do |document|
|
|
21
|
+
next if document.data.nil?
|
|
22
|
+
next unless document.data.key?(index_key)
|
|
23
|
+
next if document.data[index_key].nil?
|
|
24
|
+
next if document.data[index_key].size <= 0
|
|
25
|
+
next if document.data[index_key].to_s.strip.empty?
|
|
26
|
+
|
|
27
|
+
# Only tags and categories come as premade arrays, locale does not,
|
|
28
|
+
# so convert any data elements that are strings into arrays
|
|
29
|
+
document_data = document.data[index_key]
|
|
30
|
+
document_data = document_data.split(%r!;|,|\s!) if document_data.is_a?(String)
|
|
31
|
+
|
|
32
|
+
document_data.each do |key|
|
|
33
|
+
key = key.to_s.downcase.strip
|
|
34
|
+
# If the key is a delimetered list of values
|
|
35
|
+
# (meaning the user didn't use an array but a string with commas)
|
|
36
|
+
key.split(%r!;|,!).each do |k_split|
|
|
37
|
+
k_split = k_split.to_s.downcase.strip # Clean whitespace and junk
|
|
38
|
+
index[k_split.to_s] = [] unless index.key?(k_split)
|
|
39
|
+
index[k_split.to_s] << document
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
index
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# Creates an intersection (only returns common elements)
|
|
49
|
+
# between multiple arrays
|
|
50
|
+
#
|
|
51
|
+
def self.intersect_arrays(first, *rest)
|
|
52
|
+
return nil if first.nil?
|
|
53
|
+
return nil if rest.nil?
|
|
54
|
+
|
|
55
|
+
intersect = first
|
|
56
|
+
rest.each do |item|
|
|
57
|
+
return [] if item.nil?
|
|
58
|
+
|
|
59
|
+
intersect &= item
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
intersect
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Filters documents based on a keyed source_documents hash of indexed
|
|
66
|
+
# documents and performs a intersection of the two sets. Returns only
|
|
67
|
+
# documents that are common between all collections
|
|
68
|
+
def self.read_config_value_and_filter_documents(
|
|
69
|
+
config,
|
|
70
|
+
config_key,
|
|
71
|
+
documents,
|
|
72
|
+
source_documents
|
|
73
|
+
)
|
|
74
|
+
return nil if documents.nil?
|
|
75
|
+
|
|
76
|
+
# If the source is empty then simply don't do anything
|
|
77
|
+
return nil if source_documents.nil?
|
|
78
|
+
|
|
79
|
+
return documents if config.nil?
|
|
80
|
+
return documents unless config.key?(config_key)
|
|
81
|
+
return documents if config[config_key].nil?
|
|
82
|
+
|
|
83
|
+
# Get the filter values from the config (this is the cat/tag/locale
|
|
84
|
+
# values that should be filtered on)
|
|
85
|
+
config_value = config[config_key]
|
|
86
|
+
|
|
87
|
+
# If we're dealing with a delimitered string instead of an array then
|
|
88
|
+
# let's be forgiving
|
|
89
|
+
config_value = config_value.split(%r!;|,!) if config_value.is_a?(String)
|
|
90
|
+
|
|
91
|
+
# Now for all filter values for the config key, let's remove all items
|
|
92
|
+
# from the documents that aren't common for all collections that the
|
|
93
|
+
# user wants to filter on
|
|
94
|
+
config_value.each do |key|
|
|
95
|
+
key = key.to_s.downcase.strip
|
|
96
|
+
documents = PaginationIndexer.intersect_arrays(documents, source_documents[key])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The fully filtered final document list
|
|
100
|
+
documents
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
#
|
|
7
|
+
# The main model for the pagination, handles the orchestration of the
|
|
8
|
+
# pagination and calling all the necessary bits and bobs needed :)
|
|
9
|
+
#
|
|
10
|
+
class PaginationModel
|
|
11
|
+
@debug = false # is debug output enabled?
|
|
12
|
+
# The lambda to use for logging
|
|
13
|
+
@logging_lambda = nil
|
|
14
|
+
# The lambda used to create pages and add them to the site
|
|
15
|
+
@page_add_lambda = nil
|
|
16
|
+
# Lambda to remove a page from the site.pages collection
|
|
17
|
+
@page_remove_lambda = nil
|
|
18
|
+
# Lambda to get all documents/posts in a particular collection (by name)
|
|
19
|
+
@collection_by_name_lambda = nil
|
|
20
|
+
|
|
21
|
+
def initialize(
|
|
22
|
+
logging_lambda,
|
|
23
|
+
page_add_lambda,
|
|
24
|
+
page_remove_lambda,
|
|
25
|
+
collection_by_name_lambda
|
|
26
|
+
)
|
|
27
|
+
@logging_lambda = logging_lambda
|
|
28
|
+
@page_add_lambda = page_add_lambda
|
|
29
|
+
@page_remove_lambda = page_remove_lambda
|
|
30
|
+
@collection_by_name_lambda = collection_by_name_lambda
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run(default_config, site_pages, site_title)
|
|
34
|
+
# By default if pagination is enabled we attempt to find all index.html
|
|
35
|
+
# pages in the site
|
|
36
|
+
templates = discover_paginate_templates(site_pages)
|
|
37
|
+
if templates.size.to_i <= 0
|
|
38
|
+
@logging_lambda.call "Is enabled, but I couldn't find any pagination page." \
|
|
39
|
+
" Skipping pagination. Pages must have 'pagination: enabled: true'" \
|
|
40
|
+
" in their front-matter for pagination to work.", "warn"
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Now for each template page generate the paginator for it
|
|
45
|
+
templates.each do |template|
|
|
46
|
+
# All pages that should be paginated need to include the pagination
|
|
47
|
+
# config element
|
|
48
|
+
if template.data["pagination"].is_a?(Hash)
|
|
49
|
+
template_config = Bridgetown::Utils.deep_merge_hashes(
|
|
50
|
+
default_config,
|
|
51
|
+
template.data["pagination"] || {}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Is debugging enabled on the page level
|
|
55
|
+
@debug = template_config["debug"]
|
|
56
|
+
|
|
57
|
+
_debug_print_config_info(template_config, template.path)
|
|
58
|
+
|
|
59
|
+
# Only paginate the template if it is explicitly enabled
|
|
60
|
+
# requiring this makes the logic simpler as I don't need to
|
|
61
|
+
# determine which index pages were generated automatically and
|
|
62
|
+
# which weren't
|
|
63
|
+
if template_config["enabled"]
|
|
64
|
+
@logging_lambda.call "found page: " + template.path, "debug" unless @debug
|
|
65
|
+
|
|
66
|
+
# Request all documents in all collections that the user has requested
|
|
67
|
+
all_posts = get_docs_in_collections(template_config["collection"])
|
|
68
|
+
|
|
69
|
+
# Create the necessary indexes for the posts
|
|
70
|
+
all_categories = PaginationIndexer.index_documents_by(all_posts, "categories")
|
|
71
|
+
all_tags = PaginationIndexer.index_documents_by(all_posts, "tags")
|
|
72
|
+
all_locales = PaginationIndexer.index_documents_by(all_posts, "locale")
|
|
73
|
+
|
|
74
|
+
# TODO: NOTE!!! This whole request for posts and indexing results
|
|
75
|
+
# could be cached to improve performance, leaving like this for
|
|
76
|
+
# now during testing
|
|
77
|
+
|
|
78
|
+
# Now construct the pagination data for this template page
|
|
79
|
+
paginate(
|
|
80
|
+
template,
|
|
81
|
+
template_config,
|
|
82
|
+
site_title,
|
|
83
|
+
all_posts,
|
|
84
|
+
all_tags,
|
|
85
|
+
all_categories,
|
|
86
|
+
all_locales
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Return the total number of templates found
|
|
93
|
+
templates.size.to_i
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the combination of all documents in the collections that are
|
|
97
|
+
# specified
|
|
98
|
+
# raw_collection_names can either be a list of collections separated by a
|
|
99
|
+
# ',' or ' ' or a single string
|
|
100
|
+
def get_docs_in_collections(raw_collection_names)
|
|
101
|
+
collection_names = if raw_collection_names.is_a?(String)
|
|
102
|
+
raw_collection_names.split %r!/;|,|\s/!
|
|
103
|
+
else
|
|
104
|
+
raw_collection_names
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
docs = []
|
|
108
|
+
# Now for each of the collections get the docs
|
|
109
|
+
collection_names.each do |coll_name|
|
|
110
|
+
# Request all the documents for the collection in question, and join
|
|
111
|
+
# it with the total collection
|
|
112
|
+
docs += @collection_by_name_lambda.call coll_name.downcase.strip
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Hidden documents should not not be processed anywhere.
|
|
116
|
+
docs = docs.reject { |doc| doc["hidden"] }
|
|
117
|
+
|
|
118
|
+
docs
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# rubocop:disable Layout/LineLength
|
|
122
|
+
def _debug_print_config_info(config, page_path)
|
|
123
|
+
r = 20
|
|
124
|
+
f = "Pagination: ".rjust(20)
|
|
125
|
+
# Debug print the config
|
|
126
|
+
if @debug
|
|
127
|
+
puts f + "----------------------------"
|
|
128
|
+
puts f + "Page: " + page_path.to_s
|
|
129
|
+
puts f + " Active configuration"
|
|
130
|
+
puts f + " Enabled: ".ljust(r) + config["enabled"].to_s
|
|
131
|
+
puts f + " Items per page: ".ljust(r) + config["per_page"].to_s
|
|
132
|
+
puts f + " Permalink: ".ljust(r) + config["permalink"].to_s
|
|
133
|
+
puts f + " Title: ".ljust(r) + config["title"].to_s
|
|
134
|
+
puts f + " Limit: ".ljust(r) + config["limit"].to_s
|
|
135
|
+
puts f + " Sort by: ".ljust(r) + config["sort_field"].to_s
|
|
136
|
+
puts f + " Sort reverse: ".ljust(r) + config["sort_reverse"].to_s
|
|
137
|
+
|
|
138
|
+
puts f + " Active Filters"
|
|
139
|
+
puts f + " Collection: ".ljust(r) + config["collection"].to_s
|
|
140
|
+
puts f + " Offset: ".ljust(r) + config["offset"].to_s
|
|
141
|
+
puts f + " Category: ".ljust(r) + (config["category"].nil? || config["category"] == "posts" ? "[Not set]" : config["category"].to_s)
|
|
142
|
+
puts f + " Tag: ".ljust(r) + (config["tag"].nil? ? "[Not set]" : config["tag"].to_s)
|
|
143
|
+
puts f + " Locale: ".ljust(r) + (config["locale"].nil? ? "[Not set]" : config["locale"].to_s)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
# rubocop:enable Layout/LineLength
|
|
147
|
+
|
|
148
|
+
# rubocop:disable Layout/LineLength
|
|
149
|
+
def _debug_print_filtering_info(filter_name, before_count, after_count)
|
|
150
|
+
# Debug print the config
|
|
151
|
+
if @debug
|
|
152
|
+
puts "Pagination: ".rjust(20) + " Filtering by: " + filter_name.to_s.ljust(9) + " " + before_count.to_s.rjust(3) + " => " + after_count.to_s
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
# rubocop:enable Layout/LineLength
|
|
156
|
+
|
|
157
|
+
#
|
|
158
|
+
# Rolls through all the pages passed in and finds all pages that have
|
|
159
|
+
# pagination enabled on them.
|
|
160
|
+
# These pages will be used as templates
|
|
161
|
+
#
|
|
162
|
+
# site_pages - All pages in the site
|
|
163
|
+
#
|
|
164
|
+
def discover_paginate_templates(site_pages)
|
|
165
|
+
site_pages.select do |page|
|
|
166
|
+
page.data["pagination"].is_a?(Hash) && page.data["pagination"]["enabled"]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Paginates the blog's posts. Renders the index.html file into paginated
|
|
171
|
+
# directories, e.g.: page2/index.html, page3/index.html, etc and adds more
|
|
172
|
+
# site-wide data.
|
|
173
|
+
#
|
|
174
|
+
# site - The Site.
|
|
175
|
+
# template - The index.html Page that requires pagination.
|
|
176
|
+
# config - The configuration settings that should be used
|
|
177
|
+
#
|
|
178
|
+
def paginate(
|
|
179
|
+
template,
|
|
180
|
+
config,
|
|
181
|
+
site_title,
|
|
182
|
+
all_posts,
|
|
183
|
+
all_tags,
|
|
184
|
+
all_categories,
|
|
185
|
+
all_locales
|
|
186
|
+
)
|
|
187
|
+
# By default paginate on all posts in the site
|
|
188
|
+
using_posts = all_posts
|
|
189
|
+
|
|
190
|
+
# Now start filtering out any posts that the user doesn't want included
|
|
191
|
+
# in the pagination
|
|
192
|
+
before = using_posts.size.to_i
|
|
193
|
+
using_posts = PaginationIndexer.read_config_value_and_filter_documents(
|
|
194
|
+
config,
|
|
195
|
+
"category",
|
|
196
|
+
using_posts,
|
|
197
|
+
all_categories
|
|
198
|
+
)
|
|
199
|
+
_debug_print_filtering_info("Category", before, using_posts.size.to_i)
|
|
200
|
+
before = using_posts.size.to_i
|
|
201
|
+
using_posts = PaginationIndexer.read_config_value_and_filter_documents(
|
|
202
|
+
config,
|
|
203
|
+
"tag",
|
|
204
|
+
using_posts,
|
|
205
|
+
all_tags
|
|
206
|
+
)
|
|
207
|
+
_debug_print_filtering_info("Tag", before, using_posts.size.to_i)
|
|
208
|
+
before = using_posts.size.to_i
|
|
209
|
+
using_posts = PaginationIndexer.read_config_value_and_filter_documents(
|
|
210
|
+
config,
|
|
211
|
+
"locale",
|
|
212
|
+
using_posts,
|
|
213
|
+
all_locales
|
|
214
|
+
)
|
|
215
|
+
_debug_print_filtering_info("Locale", before, using_posts.size.to_i)
|
|
216
|
+
|
|
217
|
+
# Apply sorting to the posts if configured, any field for the post is
|
|
218
|
+
# available for sorting
|
|
219
|
+
if config["sort_field"]
|
|
220
|
+
sort_field = config["sort_field"].to_s
|
|
221
|
+
|
|
222
|
+
# There is an issue in Bridgetown related to lazy initialized member
|
|
223
|
+
# variables that causes iterators to
|
|
224
|
+
# break when accessing an uninitialized value during iteration. This
|
|
225
|
+
# happens for document.rb when the <=> comparison function
|
|
226
|
+
# is called (as this function calls the 'date' field which for drafts
|
|
227
|
+
# are not initialized.)
|
|
228
|
+
# So to unblock this common issue for the date field I simply iterate
|
|
229
|
+
# once over every document and initialize the .date field explicitly
|
|
230
|
+
if @debug
|
|
231
|
+
puts "Pagination: ".rjust(20) + "Rolling through the date fields for all documents"
|
|
232
|
+
end
|
|
233
|
+
using_posts.each do |u_post|
|
|
234
|
+
next unless u_post.respond_to?("date")
|
|
235
|
+
|
|
236
|
+
tmp_date = u_post.date
|
|
237
|
+
if !tmp_date || tmp_date.nil?
|
|
238
|
+
if @debug
|
|
239
|
+
puts "Pagination: ".rjust(20) +
|
|
240
|
+
"Explicitly assigning date for doc: #{u_post.data["title"]} | #{u_post.path}"
|
|
241
|
+
end
|
|
242
|
+
u_post.date = File.mtime(u_post.path)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
using_posts.sort! do |a, b|
|
|
247
|
+
Utils.sort_values(
|
|
248
|
+
Utils.sort_get_post_data(a.data, sort_field),
|
|
249
|
+
Utils.sort_get_post_data(b.data, sort_field)
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Remove the first x entries
|
|
254
|
+
offset_post_count = [0, config["offset"].to_i].max
|
|
255
|
+
using_posts.pop(offset_post_count)
|
|
256
|
+
|
|
257
|
+
using_posts.reverse! if config["sort_reverse"]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Calculate the max number of pagination-pages based on the configured per page value
|
|
261
|
+
total_pages = Utils.calculate_number_of_pages(using_posts, config["per_page"])
|
|
262
|
+
|
|
263
|
+
# If a upper limit is set on the number of total pagination pages then impose that now
|
|
264
|
+
if config["limit"].to_i.positive? && config["limit"].to_i < total_pages
|
|
265
|
+
total_pages = config["limit"].to_i
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
#### BEFORE STARTING REMOVE THE TEMPLATE PAGE FROM THE SITE LIST!
|
|
269
|
+
@page_remove_lambda.call template
|
|
270
|
+
|
|
271
|
+
# list of all newly created pages
|
|
272
|
+
newpages = []
|
|
273
|
+
|
|
274
|
+
# Consider the default index page name and extension
|
|
275
|
+
index_page_name = if config["indexpage"].nil?
|
|
276
|
+
""
|
|
277
|
+
else
|
|
278
|
+
config["indexpage"].split(".")[0]
|
|
279
|
+
end
|
|
280
|
+
index_page_ext = if config["extension"].nil?
|
|
281
|
+
""
|
|
282
|
+
else
|
|
283
|
+
Utils.ensure_leading_dot(config["extension"])
|
|
284
|
+
end
|
|
285
|
+
index_page_with_ext = index_page_name + index_page_ext
|
|
286
|
+
|
|
287
|
+
# In case there are no (visible) posts, generate the index file anyway
|
|
288
|
+
total_pages = 1 if total_pages.zero?
|
|
289
|
+
|
|
290
|
+
# Now for each pagination page create it and configure the ranges for
|
|
291
|
+
# the collection
|
|
292
|
+
# The .pager member is a built in thing in Bridgetown and references the
|
|
293
|
+
# paginator implementation
|
|
294
|
+
# rubocop:disable Metrics/BlockLength
|
|
295
|
+
(1..total_pages).each do |cur_page_nr|
|
|
296
|
+
# 1. Create the in-memory page
|
|
297
|
+
# External Proc call to create the actual page for us (this is
|
|
298
|
+
# passed in when the pagination is run)
|
|
299
|
+
newpage = PaginationPage.new template, cur_page_nr, total_pages, index_page_with_ext
|
|
300
|
+
|
|
301
|
+
# 2. Create the url for the in-memory page (calc permalink etc),
|
|
302
|
+
# construct the title, set all page.data values needed
|
|
303
|
+
paginated_page_url = config["permalink"]
|
|
304
|
+
first_index_page_url = if template.data["permalink"]
|
|
305
|
+
Utils.ensure_trailing_slash(
|
|
306
|
+
template.data["permalink"]
|
|
307
|
+
)
|
|
308
|
+
else
|
|
309
|
+
Utils.ensure_trailing_slash(template.dir)
|
|
310
|
+
end
|
|
311
|
+
paginated_page_url = File.join(first_index_page_url, paginated_page_url)
|
|
312
|
+
|
|
313
|
+
# 3. Create the pager logic for this page, pass in the prev and next
|
|
314
|
+
# page numbers, assign pager to in-memory page
|
|
315
|
+
newpage.pager = Paginator.new(
|
|
316
|
+
config["per_page"],
|
|
317
|
+
first_index_page_url,
|
|
318
|
+
paginated_page_url,
|
|
319
|
+
using_posts,
|
|
320
|
+
cur_page_nr,
|
|
321
|
+
total_pages,
|
|
322
|
+
index_page_name,
|
|
323
|
+
index_page_ext
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Create the url for the new page, make sure we prepend any permalinks
|
|
327
|
+
# that are defined in the template page before
|
|
328
|
+
if newpage.pager.page_path.end_with? "/"
|
|
329
|
+
newpage.set_url(File.join(newpage.pager.page_path, index_page_with_ext))
|
|
330
|
+
elsif newpage.pager.page_path.end_with? index_page_ext
|
|
331
|
+
# Support for direct .html files
|
|
332
|
+
newpage.set_url(newpage.pager.page_path)
|
|
333
|
+
else
|
|
334
|
+
# Support for extensionless permalinks
|
|
335
|
+
newpage.set_url(newpage.pager.page_path + index_page_ext)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
newpage.data["permalink"] = newpage.pager.page_path if template.data["permalink"]
|
|
339
|
+
|
|
340
|
+
# Transfer the title across to the new page
|
|
341
|
+
tmp_title = if !template.data["title"]
|
|
342
|
+
site_title
|
|
343
|
+
else
|
|
344
|
+
template.data["title"]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# If the user specified a title suffix to be added then let's add that
|
|
348
|
+
# to all the pages except the first
|
|
349
|
+
if cur_page_nr > 1 && config.key?("title")
|
|
350
|
+
newtitle = Utils.format_page_title(
|
|
351
|
+
config["title"],
|
|
352
|
+
tmp_title,
|
|
353
|
+
cur_page_nr,
|
|
354
|
+
total_pages
|
|
355
|
+
)
|
|
356
|
+
newpage.data["title"] = newtitle.to_s
|
|
357
|
+
else
|
|
358
|
+
newpage.data["title"] = tmp_title
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Signals that this page is automatically generated by the pagination logic
|
|
362
|
+
# (we don't do this for the first page as it is there to mask the one we removed)
|
|
363
|
+
newpage.data["autogen"] = "bridgetown-paginate" if cur_page_nr > 1
|
|
364
|
+
|
|
365
|
+
# Add the page to the site
|
|
366
|
+
@page_add_lambda.call newpage
|
|
367
|
+
|
|
368
|
+
# Store the page in an internal list for later referencing if we need
|
|
369
|
+
# to generate a pagination number path later on
|
|
370
|
+
newpages << newpage
|
|
371
|
+
end
|
|
372
|
+
# rubocop:enable Metrics/BlockLength
|
|
373
|
+
|
|
374
|
+
# Now generate the pagination number path, e.g. so that the users can
|
|
375
|
+
# have a prev 1 2 3 4 5 next structure on their page
|
|
376
|
+
# simplest is to include all of the links to the pages preceeding the
|
|
377
|
+
# current one (e.g for page 1 you get the list 2, 3, 4.... and for
|
|
378
|
+
# page 2 you get the list 3,4,5...)
|
|
379
|
+
if config["trail"] && !config["trail"].nil? && newpages.size.to_i.positive?
|
|
380
|
+
trail_before = [config["trail"]["before"].to_i, 0].max
|
|
381
|
+
trail_after = [config["trail"]["after"].to_i, 0].max
|
|
382
|
+
trail_length = trail_before + trail_after + 1
|
|
383
|
+
|
|
384
|
+
if trail_before.positive? || trail_after.positive?
|
|
385
|
+
newpages.select do |npage|
|
|
386
|
+
# Selecting the beginning of the trail
|
|
387
|
+
idx_start = [npage.pager.page - trail_before - 1, 0].max
|
|
388
|
+
# Selecting the end of the trail
|
|
389
|
+
idx_end = [idx_start + trail_length, newpages.size.to_i].min
|
|
390
|
+
|
|
391
|
+
# Always attempt to maintain the max total of <trail_length> pages
|
|
392
|
+
# in the trail (it will look better if the trail doesn't shrink)
|
|
393
|
+
if idx_end - idx_start < trail_length
|
|
394
|
+
# Attempt to pad the beginning if we have enough pages
|
|
395
|
+
# Never go beyond the zero index
|
|
396
|
+
idx_start = [
|
|
397
|
+
idx_start - (trail_length - (idx_end - idx_start)),
|
|
398
|
+
0,
|
|
399
|
+
].max
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Convert the newpages array into a two dimensional array that has
|
|
403
|
+
# [index, page_url] as items
|
|
404
|
+
npage.pager.page_trail = newpages[idx_start...idx_end] \
|
|
405
|
+
.each_with_index.map do |ipage, idx|
|
|
406
|
+
PageTrail.new(
|
|
407
|
+
idx_start + idx + 1,
|
|
408
|
+
ipage.pager.page_path,
|
|
409
|
+
ipage.data["title"]
|
|
410
|
+
)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
#
|
|
7
|
+
# This page handles the creation of the fake pagination pages based on the
|
|
8
|
+
# original page configuration.
|
|
9
|
+
# The code does the same things as the default Bridgetown page.rb code but
|
|
10
|
+
# just forces the code to look into the template instead of the (currently
|
|
11
|
+
# non-existing) pagination page. This page exists purely in memory and is
|
|
12
|
+
# not read from disk
|
|
13
|
+
#
|
|
14
|
+
class PaginationPage < Bridgetown::Page
|
|
15
|
+
def initialize(page_to_copy, cur_page_nr, total_pages, index_pageandext)
|
|
16
|
+
@site = page_to_copy.site
|
|
17
|
+
@base = ""
|
|
18
|
+
@url = ""
|
|
19
|
+
@name = index_pageandext.nil? ? "index.html" : index_pageandext
|
|
20
|
+
@path = page_to_copy.path
|
|
21
|
+
|
|
22
|
+
# Creates the basename and ext member values
|
|
23
|
+
process(@name)
|
|
24
|
+
|
|
25
|
+
# Only need to copy the data part of the page as it already contains the
|
|
26
|
+
# layout information
|
|
27
|
+
self.data = Bridgetown::Utils.deep_merge_hashes page_to_copy.data, {}
|
|
28
|
+
if !page_to_copy.data["autopage"]
|
|
29
|
+
self.content = page_to_copy.content
|
|
30
|
+
elsif page_to_copy.data["autopage"].key?("display_name")
|
|
31
|
+
# If the page is an auto page then migrate the necessary autopage info
|
|
32
|
+
# across into the new pagination page (so that users can get the
|
|
33
|
+
# correct keys etc)
|
|
34
|
+
data["autopages"] = Bridgetown::Utils.deep_merge_hashes(
|
|
35
|
+
page_to_copy.data["autopage"], {}
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Store the current page and total page numbers in the pagination_info construct
|
|
40
|
+
data["pagination_info"] = { "curr_page" => cur_page_nr, "total_pages" => total_pages }
|
|
41
|
+
|
|
42
|
+
# Perform some validation that is also performed in Bridgetown::Page
|
|
43
|
+
validate_data! page_to_copy.path
|
|
44
|
+
validate_permalink! page_to_copy.path
|
|
45
|
+
|
|
46
|
+
# TODO: Trigger a page event
|
|
47
|
+
# Bridgetown::Hooks.trigger :pages, :post_init, self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
51
|
+
def set_url(url_value)
|
|
52
|
+
@path = url_value.delete_prefix "/"
|
|
53
|
+
@dir = File.dirname(@path)
|
|
54
|
+
@url = url_value
|
|
55
|
+
end
|
|
56
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
#
|
|
7
|
+
# Handles the preparation of all the documents based on the current page index
|
|
8
|
+
#
|
|
9
|
+
class Paginator
|
|
10
|
+
attr_reader :page, :per_page, :documents, :total_documents, :total_pages,
|
|
11
|
+
:previous_page, :previous_page_path, :next_page, :next_page_path, :page_path,
|
|
12
|
+
:first_page, :first_page_path, :last_page, :last_page_path
|
|
13
|
+
attr_accessor :page_trail
|
|
14
|
+
|
|
15
|
+
# Initialize a new Paginator.
|
|
16
|
+
#
|
|
17
|
+
def initialize(
|
|
18
|
+
config_per_page,
|
|
19
|
+
first_index_page_url,
|
|
20
|
+
paginated_page_url,
|
|
21
|
+
documents,
|
|
22
|
+
cur_page_nr,
|
|
23
|
+
num_pages,
|
|
24
|
+
default_indexpage,
|
|
25
|
+
default_ext
|
|
26
|
+
)
|
|
27
|
+
@page = cur_page_nr
|
|
28
|
+
@per_page = config_per_page.to_i
|
|
29
|
+
@total_pages = num_pages
|
|
30
|
+
|
|
31
|
+
if @page > @total_pages
|
|
32
|
+
raise "page number can't be greater than total pages:" \
|
|
33
|
+
" #{@page} > #{@total_pages}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
init = (@page - 1) * @per_page
|
|
37
|
+
offset = if init + @per_page - 1 >= documents.size
|
|
38
|
+
documents.size
|
|
39
|
+
else
|
|
40
|
+
init + @per_page - 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Ensure that the current page has correct extensions if needed
|
|
44
|
+
this_page_url = Utils.ensure_full_path(
|
|
45
|
+
@page == 1 ? first_index_page_url : paginated_page_url,
|
|
46
|
+
!default_indexpage || default_indexpage.empty? ? "index" : default_indexpage,
|
|
47
|
+
!default_ext || default_ext.empty? ? ".html" : default_ext
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# To support customizable pagination pages we attempt to explicitly
|
|
51
|
+
# append the page name to the url incase the user is using extensionless permalinks.
|
|
52
|
+
if default_indexpage&.length&.positive?
|
|
53
|
+
# Adjust first page url
|
|
54
|
+
first_index_page_url = Utils.ensure_full_path(
|
|
55
|
+
first_index_page_url, default_indexpage, default_ext
|
|
56
|
+
)
|
|
57
|
+
# Adjust the paginated pages as well
|
|
58
|
+
paginated_page_url = Utils.ensure_full_path(
|
|
59
|
+
paginated_page_url, default_indexpage, default_ext
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@total_documents = documents.size
|
|
64
|
+
@documents = documents[init..offset]
|
|
65
|
+
@page_path = Utils.format_page_number(this_page_url, cur_page_nr, @total_pages)
|
|
66
|
+
|
|
67
|
+
@previous_page = @page != 1 ? @page - 1 : nil
|
|
68
|
+
@previous_page_path = unless @page == 1
|
|
69
|
+
if @page == 2
|
|
70
|
+
Utils.format_page_number(
|
|
71
|
+
first_index_page_url, 1, @total_pages
|
|
72
|
+
)
|
|
73
|
+
else
|
|
74
|
+
Utils.format_page_number(
|
|
75
|
+
paginated_page_url,
|
|
76
|
+
@previous_page,
|
|
77
|
+
@total_pages
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
@next_page = @page != @total_pages ? @page + 1 : nil
|
|
82
|
+
@next_page_path = if @page != @total_pages
|
|
83
|
+
Utils.format_page_number(
|
|
84
|
+
paginated_page_url, @next_page, @total_pages
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@first_page = 1
|
|
89
|
+
@first_page_path = Utils.format_page_number(first_index_page_url, 1, @total_pages)
|
|
90
|
+
@last_page = @total_pages
|
|
91
|
+
@last_page_path = Utils.format_page_number(paginated_page_url, @total_pages, @total_pages)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Convert this Paginator's data to a Hash suitable for use by Liquid.
|
|
95
|
+
#
|
|
96
|
+
# Returns the Hash representation of this Paginator.
|
|
97
|
+
def to_liquid
|
|
98
|
+
{
|
|
99
|
+
"per_page" => per_page,
|
|
100
|
+
"documents" => documents,
|
|
101
|
+
"total_documents" => total_documents,
|
|
102
|
+
"total_pages" => total_pages,
|
|
103
|
+
"page" => page,
|
|
104
|
+
"page_path" => page_path,
|
|
105
|
+
"previous_page" => previous_page,
|
|
106
|
+
"previous_page_path" => previous_page_path,
|
|
107
|
+
"next_page" => next_page,
|
|
108
|
+
"next_page_path" => next_page_path,
|
|
109
|
+
"first_page" => first_page,
|
|
110
|
+
"first_page_path" => first_page_path,
|
|
111
|
+
"last_page" => last_page,
|
|
112
|
+
"last_page_path" => last_page_path,
|
|
113
|
+
"page_trail" => page_trail,
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Small utility class that handles individual pagination trails
|
|
119
|
+
# and makes them easier to work with in Liquid
|
|
120
|
+
class PageTrail
|
|
121
|
+
attr_reader :num, :path, :title
|
|
122
|
+
|
|
123
|
+
def initialize(num, path, title)
|
|
124
|
+
@num = num
|
|
125
|
+
@path = path
|
|
126
|
+
@title = title
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def to_liquid
|
|
130
|
+
{
|
|
131
|
+
"num" => num,
|
|
132
|
+
"path" => path,
|
|
133
|
+
"title" => title,
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bridgetown
|
|
4
|
+
module Paginate
|
|
5
|
+
module Generator
|
|
6
|
+
# Static utility functions that are used in the code and
|
|
7
|
+
# don't belong in once place in particular
|
|
8
|
+
class Utils
|
|
9
|
+
# Static: Calculate the number of pages.
|
|
10
|
+
#
|
|
11
|
+
# all_posts - The Array of all Posts.
|
|
12
|
+
# per_page - The Integer of entries per page.
|
|
13
|
+
#
|
|
14
|
+
# Returns the Integer number of pages.
|
|
15
|
+
def self.calculate_number_of_pages(all_posts, per_page)
|
|
16
|
+
(all_posts.size.to_f / per_page.to_i).ceil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Static: returns a fully formatted string with the current (:num) page
|
|
20
|
+
# number and maximum (:max) page count replaced if configured
|
|
21
|
+
#
|
|
22
|
+
def self.format_page_number(to_format, cur_page_nr, total_page_count = nil)
|
|
23
|
+
s = to_format.sub(":num", cur_page_nr.to_s)
|
|
24
|
+
s = s.sub(":max", total_page_count.to_s) unless total_page_count.nil?
|
|
25
|
+
|
|
26
|
+
s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Static: returns a fully formatted string with the :title variable and
|
|
30
|
+
# the current (:num) page number and maximum (:max) page count replaced
|
|
31
|
+
#
|
|
32
|
+
def self.format_page_title(to_format, title, cur_page_nr = nil, total_page_count = nil)
|
|
33
|
+
format_page_number(to_format.sub(":title", title.to_s), cur_page_nr, total_page_count)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Static: Return a String version of the input which has a leading dot.
|
|
37
|
+
# If the input already has a dot in position zero, it will be
|
|
38
|
+
# returned unchanged.
|
|
39
|
+
#
|
|
40
|
+
# path - a String path
|
|
41
|
+
#
|
|
42
|
+
# Returns the path with a leading slash
|
|
43
|
+
def self.ensure_leading_dot(path)
|
|
44
|
+
path[0..0] == "." ? path : ".#{path}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Static: Return a String version of the input which has a leading slash.
|
|
48
|
+
# If the input already has a forward slash in position zero, it will be
|
|
49
|
+
# returned unchanged.
|
|
50
|
+
#
|
|
51
|
+
# path - a String path
|
|
52
|
+
#
|
|
53
|
+
# Returns the path with a leading slash
|
|
54
|
+
def self.ensure_leading_slash(path)
|
|
55
|
+
path[0..0] == "/" ? path : "/#{path}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Static: Return a String version of the input without a leading slash.
|
|
59
|
+
#
|
|
60
|
+
# path - a String path
|
|
61
|
+
#
|
|
62
|
+
# Returns the input without the leading slash
|
|
63
|
+
def self.remove_leading_slash(path)
|
|
64
|
+
path[0..0] == "/" ? path[1..-1] : path
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Static: Return a String version of the input which has a trailing slash.
|
|
68
|
+
# If the input already has a forward slash at the end, it will be
|
|
69
|
+
# returned unchanged.
|
|
70
|
+
#
|
|
71
|
+
# path - a String path
|
|
72
|
+
#
|
|
73
|
+
# Returns the path with a trailing slash
|
|
74
|
+
def self.ensure_trailing_slash(path)
|
|
75
|
+
path[-1] == "/" ? path : "#{path}/"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
# Sorting routine used for ordering posts by custom fields.
|
|
80
|
+
# Handles Strings separately as we want a case-insenstive sorting
|
|
81
|
+
#
|
|
82
|
+
# rubocop:disable Naming/MethodParameterName, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
83
|
+
def self.sort_values(a, b)
|
|
84
|
+
if a.nil? && !b.nil?
|
|
85
|
+
return -1
|
|
86
|
+
elsif !a.nil? && b.nil?
|
|
87
|
+
return 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
return a.downcase <=> b.downcase if a.is_a?(String)
|
|
91
|
+
|
|
92
|
+
if a.respond_to?("to_datetime") && b.respond_to?("to_datetime")
|
|
93
|
+
return a.to_datetime <=> b.to_datetime
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# By default use the built in sorting for the data type
|
|
97
|
+
a <=> b
|
|
98
|
+
end
|
|
99
|
+
# rubocop:enable Naming/MethodParameterName, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
100
|
+
|
|
101
|
+
# Retrieves the given sort field from the given post
|
|
102
|
+
# the sort_field variable can be a hierarchical value on the form
|
|
103
|
+
# "parent_field:child_field" repeated as many times as needed
|
|
104
|
+
# only the leaf child_field will be retrieved
|
|
105
|
+
def self.sort_get_post_data(post_data, sort_field)
|
|
106
|
+
# Begin by splitting up the sort_field by (;,:.)
|
|
107
|
+
sort_split = sort_field.split(":")
|
|
108
|
+
sort_value = post_data
|
|
109
|
+
|
|
110
|
+
sort_split.each do |r_key|
|
|
111
|
+
key = r_key.downcase.strip # Remove any erronious whitespace and convert to lower case
|
|
112
|
+
return nil unless sort_value.key?(key)
|
|
113
|
+
|
|
114
|
+
# Work my way through the hash
|
|
115
|
+
sort_value = sort_value[key]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# If the sort value is a hash then return nil else return the value
|
|
119
|
+
if sort_value.is_a?(Hash)
|
|
120
|
+
nil
|
|
121
|
+
else
|
|
122
|
+
sort_value
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Ensures that the passed in url has a index and extension applied
|
|
127
|
+
def self.ensure_full_path(url, default_index, default_ext)
|
|
128
|
+
if url.end_with?("/")
|
|
129
|
+
url + default_index + default_ext
|
|
130
|
+
elsif !url.include?(".")
|
|
131
|
+
url + default_index
|
|
132
|
+
else
|
|
133
|
+
url
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bridgetown-paginate
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.8.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bridgetown Team
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-04-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bridgetown-core
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.8.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.8.0
|
|
27
|
+
description:
|
|
28
|
+
email: maintainers@bridgetownrb.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- ".rspec"
|
|
34
|
+
- ".rubocop.yml"
|
|
35
|
+
- Rakefile
|
|
36
|
+
- bridgetown-paginate.gemspec
|
|
37
|
+
- lib/bridgetown-paginate.rb
|
|
38
|
+
- lib/bridgetown-paginate/defaults.rb
|
|
39
|
+
- lib/bridgetown-paginate/pagination_generator.rb
|
|
40
|
+
- lib/bridgetown-paginate/pagination_indexer.rb
|
|
41
|
+
- lib/bridgetown-paginate/pagination_model.rb
|
|
42
|
+
- lib/bridgetown-paginate/pagination_page.rb
|
|
43
|
+
- lib/bridgetown-paginate/paginator.rb
|
|
44
|
+
- lib/bridgetown-paginate/utils.rb
|
|
45
|
+
homepage: https://github.com/bridgetownrb/bridgetown-paginate
|
|
46
|
+
licenses:
|
|
47
|
+
- MIT
|
|
48
|
+
metadata: {}
|
|
49
|
+
post_install_message:
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.0.6
|
|
65
|
+
signing_key:
|
|
66
|
+
specification_version: 4
|
|
67
|
+
summary: A Bridgetown plugin to add pagination support for posts and collection indices.
|
|
68
|
+
test_files: []
|