lib/middleman-blog/extension.rb
# frozen_string_literal: true
require 'active_support/core_ext/time/zones'
require 'middleman-blog/blog_data'
require 'middleman-blog/blog_article'
require 'middleman-blog/helpers'
module Middleman
class BlogExtension < Extension
extend Forwardable
self.supports_multiple_instances = true
def_delegator :app, :logger
option :name, nil, 'Unique ID for telling multiple blogs apart'
option :prefix, nil, 'Prefix to mount the blog at (modifies permalink, sources, taglink, year_link, month_link, day_link to start with the prefix)'
option :permalink, '/{year}/{month}/{day}/{title}.html', 'Path articles are generated at. Tokens can be omitted or duplicated, and you can use tokens defined in article frontmatter.'
option :sources, '{year}-{month}-{day}-{title}.html', 'Pattern for matching source blog articles (no template extensions)'
option :taglink, 'tags/{tag}.html', 'Path tag pages are generated at.'
option :layout, 'layout', 'Article-specific layout'
option :summary_separator, /(READMORE)/, 'Regex or string that delimits the article summary from the rest of the article.'
option :summary_length, 250, 'Truncate summary to be <= this number of characters. Set to -1 to disable summary truncation.'
option :summary_generator, nil, 'A block that defines how summaries are extracted. It will be passed the rendered article content, max summary length, and ellipsis string as arguments.'
option :year_link, '/{year}.html', 'Path yearly archive pages are generated at.'
option :month_link, '/{year}/{month}.html', 'Path monthly archive pages are generated at.'
option :day_link, '/{year}/{month}/{day}.html', 'Path daily archive pages are generated at.'
option :calendar_template, nil, 'Template path (no template extension) for calendar pages (year/month/day archives).'
option :year_template, nil, 'Template path (no template extension) for yearly archive pages. Defaults to the :calendar_template.'
option :month_template, nil, 'Template path (no template extension) for monthly archive pages. Defaults to the :calendar_template.'
option :day_template, nil, 'Template path (no template extension) for daily archive pages. Defaults to the :calendar_template.'
option :tag_template, nil, 'Template path (no template extension) for tag archive pages.'
option :generate_year_pages, true, 'Whether to generate year pages.'
option :generate_month_pages, true, 'Whether to generate month pages.'
option :generate_day_pages, true, 'Whether to generate day pages.'
option :generate_tag_pages, true, 'Whether to generate tag pages.'
option :paginate, false, 'Whether to paginate lists of articles'
option :per_page, 10, 'Number of articles per page when paginating'
option :filter, nil, 'A proc that can be used to select articles that should be published based on user-defined criteria'
option :page_link, 'page/{num}', 'Path to append for additional pages when paginating'
option :publish_future_dated, false, 'Whether articles with a date in the future should be considered published'
option :custom_collections, {}, 'Hash of custom frontmatter properties to collect articles on and their options (link, template)'
option :preserve_locale, false, 'Use the global Middleman I18n.locale instead of the lang in the article\'s frontmatter'
option :new_article_template, File.expand_path('commands/article.tt', __dir__), 'Path (relative to project root) to an ERb template that will be used to generate new articles from the "middleman article" command.'
option :default_extension, '.markdown', 'Default template extension for articles (used by "middleman article")'
# @return [BlogData] blog data for this blog, which has all information about the blog articles
attr_reader :data
# @return [Symbol] the name of this blog (autogenerated if not provided).
attr_reader :name
# @return [TagPages] tag page handler for this blog
attr_reader :tag_pages
# @return [CalendarPages] calendar page handler for this blog
attr_reader :calendar_pages
# @return [Paginator] pagination handler for this blog
attr_reader :paginator
# @return [Hash<CustomPages>] custom pages handlers for this blog, indexed by property name
attr_reader :custom_pages
# Helpers for use within templates and layouts.
self.defined_helpers = [Middleman::Blog::Helpers]
def initialize(app, options_hash = {}, &block)
super
@custom_pages = {}
# NAME is the name of this particular blog, and how you reference it from #blog_controller or frontmatter.
@name = options.name.to_sym if options.name
# Allow one setting to set all the calendar templates
if options.calendar_template
options.year_template ||= options.calendar_template
options.month_template ||= options.calendar_template
options.day_template ||= options.calendar_template
end
# If "prefix" option is specified, all other paths are relative to it.
return unless options.prefix
options.prefix = "/#{options.prefix}" unless options.prefix.start_with? '/'
options.permalink = File.join(options.prefix, options.permalink)
options.sources = File.join(options.prefix, options.sources)
options.taglink = File.join(options.prefix, options.taglink)
options.year_link = File.join(options.prefix, options.year_link)
options.month_link = File.join(options.prefix, options.month_link)
options.day_link = File.join(options.prefix, options.day_link)
options.custom_collections.each_value do |opts|
opts[:link] = File.join(options.prefix, opts[:link])
end
end
def after_configuration
@name ||= begin
found_name = nil
app.extensions[:blog].values.each_with_index do |ext, i|
found_name = "blog#{i + 1}" if ext == self
end
found_name
end
# TODO: break up into private methods?
@app.ignore(options.calendar_template) if options.calendar_template
@app.ignore(options.year_template) if options.year_template
@app.ignore(options.month_template) if options.month_template
@app.ignore(options.day_template) if options.day_template
@app.ignore options.tag_template if options.tag_template
# Make sure ActiveSupport's TimeZone stuff has something to work with,
# allowing people to set their desired time zone via Time.zone or
# set :time_zone
Time.zone = app.config[:time_zone] if app.config[:time_zone]
time_zone = Time.zone || 'UTC'
zone_default = Time.find_zone!(time_zone)
raise 'Value assigned to time_zone not recognized.' unless zone_default
Time.zone_default = zone_default
# Initialize blog with options
@data = Blog::BlogData.new(@app, self, options)
@app.sitemap.register_resource_list_manipulator(:"blog_#{name}_articles", @data)
if options.tag_template
@app.ignore options.tag_template
require 'middleman-blog/tag_pages'
@tag_pages = Blog::TagPages.new(@app, self)
@app.sitemap.register_resource_list_manipulator(:"blog_#{name}_tags", @tag_pages)
end
if options.year_template || options.month_template || options.day_template
require 'middleman-blog/calendar_pages'
@calendar_pages = Blog::CalendarPages.new(@app, self)
@app.sitemap.register_resource_list_manipulator(:"blog_#{name}_calendar", @calendar_pages)
end
if options.custom_collections
require 'middleman-blog/custom_pages'
register_custom_pages
end
if options.paginate
require 'middleman-blog/paginator'
@paginator = Blog::Paginator.new(@app, self)
@app.sitemap.register_resource_list_manipulator(:"blog_#{name}_paginate", @paginator)
end
logger.info "== Blog Sources: #{options.sources} (:prefix + :sources)"
end
private
# Register any custom page collections that may be set in the config
#
# A custom resource list manipulator will be generated for each key in the
# custom collections hash.
#
# The following will collect posts on the "category" frontmatter property:
# ```
# activate :blog do |blog|
# blog.custom_collections = {
# category: {
# link: "/categories/:category.html",
# template: "/category.html"
# }
# }
# end
# ```
#
# Category pages in the example above will use the category.html as a template file
# and it will be ignored when building.
def register_custom_pages
options.custom_collections.each do |property, options|
@app.ignore options[:template]
@custom_pages[property] = Blog::CustomPages.new(property, @app, self, options)
@app.sitemap.register_resource_list_manipulator(:"blog_#{name}_#{property}", @custom_pages[property])
Blog::Helpers.generate_custom_helper(property)
end
end
end
end