class Bridgetown::Paginate::PaginationModel
The main model for the pagination, handles the orchestration of the pagination and calling all the necessary bits and bobs needed :)
Public Class Methods
new( logging_lambda, page_add_lambda, page_remove_lambda, collection_by_name_lambda )
click to toggle source
# File lib/bridgetown-paginate/pagination_model.rb, line 20 def initialize( logging_lambda, page_add_lambda, page_remove_lambda, collection_by_name_lambda ) @logging_lambda = logging_lambda @page_add_lambda = page_add_lambda @page_remove_lambda = page_remove_lambda @collection_by_name_lambda = collection_by_name_lambda end
Public Instance Methods
_debug_print_config_info(config, page_path)
click to toggle source
rubocop:disable Layout/LineLength
# File lib/bridgetown-paginate/pagination_model.rb, line 153 def _debug_print_config_info(config, page_path) # rubocop:todo Metrics/AbcSize r = 20 f = "Pagination: ".rjust(20) # Debug print the config if @debug puts f + "----------------------------" puts f + "Page: " + page_path.to_s puts f + " Active configuration" puts f + " Enabled: ".ljust(r) + config["enabled"].to_s puts f + " Items per page: ".ljust(r) + config["per_page"].to_s puts f + " Permalink: ".ljust(r) + config["permalink"].to_s puts f + " Title: ".ljust(r) + config["title"].to_s puts f + " Limit: ".ljust(r) + config["limit"].to_s puts f + " Sort by: ".ljust(r) + config["sort_field"].to_s puts f + " Sort reverse: ".ljust(r) + config["sort_reverse"].to_s puts f + " Active Filters" puts f + " Collection: ".ljust(r) + config["collection"].to_s puts f + " Offset: ".ljust(r) + config["offset"].to_s puts f + " Category: ".ljust(r) + (config["category"].nil? || config["category"] == "posts" ? "[Not set]" : config["category"].to_s) puts f + " Tag: ".ljust(r) + (config["tag"].nil? ? "[Not set]" : config["tag"].to_s) puts f + " Locale: ".ljust(r) + (config["locale"].nil? ? "[Not set]" : config["locale"].to_s) end end
_debug_print_filtering_info(filter_name, before_count, after_count)
click to toggle source
rubocop:disable Layout/LineLength
# File lib/bridgetown-paginate/pagination_model.rb, line 180 def _debug_print_filtering_info(filter_name, before_count, after_count) # Debug print the config if @debug puts "Pagination: ".rjust(20) + " Filtering by: " + filter_name.to_s.ljust(9) + " " + before_count.to_s.rjust(3) + " => " + after_count.to_s end end
get_docs_in_collections(raw_collection_names, template)
click to toggle source
Returns the combination of all documents in the collections that are specified raw_collection_names can either be a list of collections separated by a ',' or ' ' or a single string
# File lib/bridgetown-paginate/pagination_model.rb, line 119 def get_docs_in_collections(raw_collection_names, template) if raw_collection_names.blank? @logging_lambda.call "Missing collection name for paginated page: " \ "#{template.relative_path}" return [] end if @docs_by_collection_cache[raw_collection_names] return @docs_by_collection_cache[raw_collection_names] end collection_names = if raw_collection_names.is_a?(String) raw_collection_names.split %r!/;|,|\s/! else raw_collection_names end docs = [] # Now for each of the collections get the docs collection_names.each do |coll_name| # Request all the documents for the collection in question, and join # it with the total collection docs += @collection_by_name_lambda.call coll_name.downcase.strip end # Hidden documents should not not be processed anywhere. docs = docs.reject { |doc| doc.data.exclude_from_pagination } @docs_by_collection_cache[raw_collection_names] = docs docs end
paginate(template, config, site_title, documents_payload)
click to toggle source
Paginates the blog's posts. Renders the index.html file into paginated directories, e.g.: page2/index.html, page3/index.html, etc and adds more site-wide data.
site - The Site. template - The index.html Page that requires pagination. config - The configuration settings that should be used
rubocop:todo Metrics/AbcSize
# File lib/bridgetown-paginate/pagination_model.rb, line 197 def paginate(template, config, site_title, documents_payload) # By default paginate on all posts in the site using_posts = documents_payload[:posts] # Now start filtering out any posts that the user doesn't want included # in the pagination if config["category"] before = using_posts.size.to_i using_posts = PaginationIndexer.read_config_value_and_filter_documents( config, "category", using_posts, documents_payload[:categories] ) _debug_print_filtering_info("Category", before, using_posts.size.to_i) end if config["tag"] before = using_posts.size.to_i using_posts = PaginationIndexer.read_config_value_and_filter_documents( config, "tag", using_posts, documents_payload[:tags] ) _debug_print_filtering_info("Tag", before, using_posts.size.to_i) end if config["locale"] before = using_posts.size.to_i using_posts = PaginationIndexer.read_config_value_and_filter_documents( config, "locale", using_posts, documents_payload[:locales] ) _debug_print_filtering_info("Locale", before, using_posts.size.to_i) end if config["where_query"] before = using_posts.size.to_i using_posts = PaginationIndexer.read_config_value_and_filter_documents( config, "where_query", using_posts, documents_payload[:where_matches] ) _debug_print_filtering_info( "Where Query (#{config["where_query"]})", before, using_posts.size.to_i ) end # Apply sorting to the posts if configured, any field for the post is # available for sorting if config["sort_field"] sort_field = config["sort_field"].to_s # There is an issue in Bridgetown related to lazy initialized member # variables that causes iterators to # break when accessing an uninitialized value during iteration. This # happens for document.rb when the <=> comparison function # is called (as this function calls the 'date' field which for drafts # are not initialized.) # So to unblock this common issue for the date field I simply iterate # once over every document and initialize the .date field explicitly if @debug puts "Pagination: ".rjust(20) + "Rolling through the date fields for all documents" end using_posts.each do |u_post| next unless u_post.respond_to?("date") tmp_date = u_post.date next unless !tmp_date || tmp_date.nil? if @debug puts "Pagination: ".rjust(20) + "Explicitly assigning date for doc: #{u_post.data["title"]} | #{u_post.path}" end u_post.date = File.mtime(u_post.path) end using_posts.sort! do |a, b| Utils.sort_values( Utils.sort_get_post_data(a.data, sort_field), Utils.sort_get_post_data(b.data, sort_field) ) end # Remove the first x entries offset_post_count = [0, config["offset"].to_i].max using_posts.pop(offset_post_count) using_posts.reverse! if config["sort_reverse"] end # Calculate the max number of pagination-pages based on the configured per page value total_pages = Utils.calculate_number_of_pages(using_posts, config["per_page"]) # If a upper limit is set on the number of total pagination pages then impose that now if config["limit"].to_i.positive? && config["limit"].to_i < total_pages total_pages = config["limit"].to_i end #### BEFORE STARTING REMOVE THE TEMPLATE PAGE FROM THE SITE LIST! @page_remove_lambda.call template # list of all newly created pages newpages = [] # Consider the index page name and extension # By default, they will be nil and the Page object will infer # it from the template used index_page_name = config["indexpage"].split(".")[0] unless config["indexpage"].nil? index_page_ext = unless index_page_name.nil? || config["extension"].nil? Utils.ensure_leading_dot(config["extension"]) end index_page_with_ext = index_page_name + index_page_ext if index_page_name # In case there are no (visible) posts, generate the index file anyway total_pages = 1 if total_pages.zero? # Now for each pagination page create it and configure the ranges for # the collection # The .paginator member is a built in thing in Bridgetown and references the # paginator implementation # rubocop:disable Metrics/BlockLength (1..total_pages).each do |cur_page_nr| # 1. Create the in-memory page # External Proc call to create the actual page for us (this is # passed in when the pagination is run) newpage = PaginationPage.new( template, cur_page_nr, total_pages, index_page_with_ext, template.extname ) # 2. Create the url for the in-memory page (calc permalink etc), # construct the title, set all page.data values needed paginated_page_url = config["permalink"] first_index_page_url = if template.data["permalink"] template.data["permalink"] elsif template.respond_to?(:relative_url) template.relative_url else template.dir end first_index_page_url = Utils.ensure_trailing_slash( Utils.remove_double_slash(first_index_page_url) ) paginated_page_url = File.join(first_index_page_url, paginated_page_url) # 3. Create the paginator logic for this page, pass in the prev and next # page numbers, assign paginator to in-memory page # TODO: remove .pager by v1.0, deprecated newpage.paginator = newpage.pager = Paginator.new( config["per_page"], first_index_page_url, paginated_page_url, using_posts, cur_page_nr, total_pages, index_page_name, index_page_ext ) # Create the url for the new page, make sure we prepend any permalinks # that are defined in the template page before if newpage.paginator.page_path.end_with? "/" newpage.set_url(File.join(newpage.paginator.page_path, index_page_with_ext)) elsif newpage.paginator.page_path.end_with? index_page_ext.to_s # Support for direct .html files newpage.set_url(newpage.paginator.page_path) else # Support for extensionless permalinks newpage.set_url(newpage.paginator.page_path + index_page_ext.to_s) end newpage.data["permalink"] = newpage.paginator.page_path if template.data["permalink"] # Transfer the title across to the new page tmp_title = if !template.data["title"] site_title else template.data["title"] end # If the user specified a title suffix to be added then let's add that # to all the pages except the first if cur_page_nr > 1 && config.key?("title") newtitle = Utils.format_page_title( config["title"], tmp_title, cur_page_nr, total_pages ) newpage.data["title"] = newtitle.to_s else newpage.data["title"] = tmp_title end # Signals that this page is automatically generated by the pagination logic newpage.data["autogen"] = "bridgetown-paginate" # If there's only one post (like on a per_page: 1 scenario), let's be # helpful and supply a document variable newpage.data["document"] = using_posts.first if using_posts.size == 1 # Add the page to the site @page_add_lambda.call newpage # Store the page in an internal list for later referencing if we need # to generate a pagination number path later on newpages << newpage end # rubocop:enable Metrics/BlockLength # 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 # simplest is to include all of the links to the pages preceeding the # current one (e.g for page 1 you get the list 2, 3, 4.... and for # page 2 you get the list 3,4,5...) if config["trail"] && !config["trail"].nil? && newpages.size.to_i.positive? trail_before = [config["trail"]["before"].to_i, 0].max trail_after = [config["trail"]["after"].to_i, 0].max trail_length = trail_before + trail_after + 1 if trail_before.positive? || trail_after.positive? newpages.select do |npage| # Selecting the beginning of the trail idx_start = [npage.paginator.page - trail_before - 1, 0].max # Selecting the end of the trail idx_end = [idx_start + trail_length, newpages.size.to_i].min # Always attempt to maintain the max total of <trail_length> pages # in the trail (it will look better if the trail doesn't shrink) if idx_end - idx_start < trail_length # Attempt to pad the beginning if we have enough pages # Never go beyond the zero index idx_start = [ idx_start - (trail_length - (idx_end - idx_start)), 0, ].max end # Convert the newpages array into a two dimensional array that has # [index, page_url] as items npage.paginator.page_trail = newpages[idx_start...idx_end] \ .each_with_index.map do |ipage, idx| PageTrail.new( idx_start + idx + 1, ipage.paginator.page_path, ipage.data["title"] ) end end end end end
run(default_config, templates, site_title)
click to toggle source
rubocop:disable Metrics/BlockLength
# File lib/bridgetown-paginate/pagination_model.rb, line 33 def run(default_config, templates, site_title) # rubocop:todo Metrics/AbcSize if templates.size.to_i <= 0 @logging_lambda.call "is enabled in the config, but no paginated pages found." \ " Add 'pagination:\\n collection: <label>' to the front-matter of a page.", "warn" return end @docs_by_collection_cache = {} # Now for each template page generate the paginator for it templates.each do |template| # All pages that should be paginated need to include the pagination # config element unless template.data["pagination"].is_a?(Hash) || template.data["paginate"].is_a?(Hash) next end template_config = Bridgetown::Utils.deep_merge_hashes( default_config, template.data["pagination"] || template.data["paginate"] || {} ).tap do |config| config["collection"] = config["collection"].to_s if config["collection"].is_a?(Symbol) config["category"] = config["category"].to_s if config["category"].is_a?(Symbol) config["tag"] = config["tag"].to_s if config["tag"].is_a?(Symbol) config["locale"] = config["locale"].to_s if config["locale"].is_a?(Symbol) end # Is debugging enabled on the page level @debug = template_config["debug"] _debug_print_config_info(template_config, template.path) next unless template_config["enabled"] @logging_lambda.call "found page: " + template.path, "debug" unless @debug # Request all documents in all collections that the user has requested all_posts = get_docs_in_collections(template_config["collection"], template) # Create the necessary indexes for the posts all_categories = if template_config["category"] PaginationIndexer.index_documents_by(all_posts, "categories") end all_tags = if template_config["tag"] PaginationIndexer.index_documents_by(all_posts, "tags") end all_locales = if template_config["locale"] PaginationIndexer.index_documents_by(all_posts, "locale") end # Load in custom query index, if specified all_where_matches = if template_config["where_query"] PaginationIndexer.index_documents_by( all_posts, template_config["where_query"] ) end documents_payload = { posts: all_posts, tags: all_tags, categories: all_categories, locales: all_locales, where_matches: all_where_matches, } # TODO: NOTE!!! This whole request for posts and indexing results # could be cached to improve performance, leaving like this for # now during testing # Now construct the pagination data for this template page paginate( template, template_config, site_title, documents_payload ) end # Return the total number of templates found templates.size.to_i end