diff --git a/README.md b/README.md index 518aced7..69adaf49 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,10 @@ There are several ways to convey author-specific information. Author information The plugin exposes a helper tag to expose the appropriate meta tags to support automated discovery of your feed. Simply place `{% feed_meta %}` someplace in your template's `` section, to output the necessary metadata. +The helper can also generate the link for a given collection: `{% feed_meta my_collection %}` or a given category: `{% feed_meta my_collection my_category %}`. + +To generate links for every collections and categories, call the helper using this syntax: `{% feed_meta include: all %}`. + ### SmartyPants The plugin uses [Jekyll's `smartify` filter](https://jekyllrb.com/docs/templates/) for processing the site title and post titles. This will translate plain ASCII punctuation into "smart" typographic punctuation. This will not render or strip any Markdown you may be using in a title. @@ -170,6 +174,15 @@ feed: path: "/changes.xml" ``` +Collection feed titles will include the capitalized collection name. If you'd like to customize the feed title, specify a collection's custom title as follows: + +```yml +feed: + collections: + changes: + title: "My collection title" +``` + Finally, collections can also have category feeds which are outputted as `/feed//.xml`. Specify categories like so: ```yml diff --git a/lib/jekyll-feed/feed.xml b/lib/jekyll-feed/feed.xml index 4d4fd16d..3c52b498 100644 --- a/lib/jekyll-feed/feed.xml +++ b/lib/jekyll-feed/feed.xml @@ -9,19 +9,7 @@ {{ site.time | date_to_xmlschema }} {{ page.url | absolute_url | xml_escape }} - {% assign title = site.title | default: site.name %} - {% if page.collection != "posts" %} - {% assign collection = page.collection | capitalize %} - {% assign title = title | append: " | " | append: collection %} - {% endif %} - {% if page.category %} - {% assign category = page.category | capitalize %} - {% assign title = title | append: " | " | append: category %} - {% endif %} - - {% if title %} - {{ title | smartify | xml_escape }} - {% endif %} + {{ page.feed_title | smartify | xml_escape }} {% if site.description %} {{ site.description | xml_escape }} diff --git a/lib/jekyll-feed/generator.rb b/lib/jekyll-feed/generator.rb index 13684056..5ffefb00 100644 --- a/lib/jekyll-feed/generator.rb +++ b/lib/jekyll-feed/generator.rb @@ -12,26 +12,14 @@ def generate(site) Jekyll.logger.info "Jekyll Feed:", "Generating feed for #{name}" (meta["categories"] + [nil]).each do |category| path = feed_path(:collection => name, :category => category) + feed_title = feed_title(:collection => name, :category => category) next if file_exists?(path) - @site.pages << make_page(path, :collection => name, :category => category) + @site.pages << make_page(path, feed_title, :collection => name, :category => category) end end end - private - - # Matches all whitespace that follows - # 1. A '>', which closes an XML tag or - # 2. A '}', which closes a Liquid tag - # We will strip all of this whitespace to minify the template - MINIFY_REGEX = %r!(?<=>|})\s+!.freeze - - # Returns the plugin's config or an empty hash if not set - def config - @config ||= @site.config["feed"] || {} - end - # Determines the destination path of a given feed # # collection - the name of a collection, e.g., "posts" @@ -45,7 +33,25 @@ def feed_path(collection: "posts", category: nil) prefix = collection == "posts" ? "/feed" : "/feed/#{collection}" return "#{prefix}/#{category}.xml" if category - collections.dig(collection, "path") || "#{prefix}.xml" + @collections.dig(collection, "path") || "#{prefix}.xml" + end + + # Determines the title of a given feed + # + # collection - the name of a collection, e.g., "posts" + # category - a category within that collection, e.g., "news" + # + # Will return the site title or name + # ...followed by collection title or capitalized name + # ...followed by capitalized category name + def feed_title(collection: "posts", category: nil) + words = [] + words << (@site.config["title"] || @site.config["name"]) + unless collection == "posts" + words << (collections.dig(collection, "title") || collection.capitalize) + end + words << category.capitalize if category + words.compact.join " | " end # Returns a hash representing all collections to be processed and their metadata @@ -69,6 +75,19 @@ def collections @collections end + private + + # Matches all whitespace that follows + # 1. A '>', which closes an XML tag or + # 2. A '}', which closes a Liquid tag + # We will strip all of this whitespace to minify the template + MINIFY_REGEX = %r!(?<=>|})\s+!.freeze + + # Returns the plugin's config or an empty hash if not set + def config + @config ||= @site.config["feed"] || {} + end + # Path to feed.xml template file def feed_source_path @feed_source_path ||= File.expand_path "feed.xml", __dir__ @@ -85,7 +104,7 @@ def file_exists?(file_path) # Generates contents for a file - def make_page(file_path, collection: "posts", category: nil) + def make_page(file_path, title, collection: "posts", category: nil) PageWithoutAFile.new(@site, __dir__, "", file_path).tap do |file| file.content = feed_template file.data.merge!( @@ -93,7 +112,8 @@ def make_page(file_path, collection: "posts", category: nil) "sitemap" => false, "xsl" => file_exists?("feed.xslt.xml"), "collection" => collection, - "category" => category + "category" => category, + "feed_title" => title ) file.output end diff --git a/lib/jekyll-feed/meta-tag.rb b/lib/jekyll-feed/meta-tag.rb index 76da23f0..a1e42ad8 100644 --- a/lib/jekyll-feed/meta-tag.rb +++ b/lib/jekyll-feed/meta-tag.rb @@ -5,10 +5,26 @@ class MetaTag < Liquid::Tag # Use Jekyll's native relative_url filter include Jekyll::Filters::URLFilters + def initialize(tag_name, args, tokens) + super + @args = args + end + def render(context) @context = context - attrs = attributes.map { |k, v| %(#{k}="#{v}") }.join(" ") - "" + if @args.strip == "include: all" + links = [] + generator.collections.each do |collection, meta| + (meta["categories"] + [nil]).each do |category| + links << link(collection, category) + end + end + links.reverse.join "\n" + else + @collection, @category = @args.split(" ") + @collection ||= "posts" + link(@collection, @category) if valid_collection && valid_category + end end private @@ -17,21 +33,47 @@ def config @config ||= @context.registers[:site].config end - def attributes + def generator + @generator ||= @context.registers[:site].generators.select { |it| it.is_a? JekyllFeed::Generator }.first # rubocop:disable Metrics/LineLength + end + + def link(collection, category) + attrs = attributes(collection, category).map { |k, v| %(#{k}="#{v}") }.join(" ") + "" + end + + def attributes(collection, category) + href = absolute_url(generator.feed_path(:collection => collection, :category => category)) + title = generator.feed_title(:collection => collection, :category => category) { :type => "application/atom+xml", :rel => "alternate", - :href => absolute_url(path), + :href => href, :title => title, - }.keep_if { |_, v| v } + }.delete_if { |_, v| v.strip.empty? } + end + + def valid_collection + return true if generator.collections.key? @collection + + invalidate_with_warning("collection") end - def path - config.dig("feed", "path") || "feed.xml" + def valid_category + return true if @collection == "posts" || @category.nil? + + collection = generator.collections[@collection] + return true if collection.key?("categories") && collection["categories"].include?(@category) + + invalidate_with_warning("category") end - def title - config["title"] || config["name"] + def invalidate_with_warning(type) + Jekyll.logger.warn( + "Jekyll Feed:", + "Invalid #{type} name. Please review `{% feed_meta #{@args} %}`" + ) + false end end end diff --git a/spec/jekyll-feed_spec.rb b/spec/jekyll-feed_spec.rb index 9dce5cea..f2a31de5 100644 --- a/spec/jekyll-feed_spec.rb +++ b/spec/jekyll-feed_spec.rb @@ -326,6 +326,66 @@ end end + context "selecting a particular collection" do + let(:overrides) do + { + "collections" => { + "collection" => { + "output" => true, + }, + }, + "feed" => { + "collections" => { + "collection" => { + "categories" => ["news"], + }, + }, + }, + } + end + let(:default_feed) { Liquid::Template.parse("{% feed_meta posts %}").render!(context, {}) } + let(:collection_feed) { Liquid::Template.parse("{% feed_meta collection %}").render!(context, {}) } + let(:category_feed) { Liquid::Template.parse("{% feed_meta collection news %}").render!(context, {}) } + + it "renders the feed meta for the selected collection" do + default_feed_link = '' + collection_feed_link = '' + category_feed_link = '' + expect(default_feed).to eql(default_feed_link) + expect(collection_feed).to eql(collection_feed_link) + expect(category_feed).to eql(category_feed_link) + end + end + + context "requesting all feed links" do + let(:overrides) do + { + "collections" => { + "collection" => { + "output" => true, + }, + }, + "feed" => { + "collections" => { + "collection" => { + "categories" => ["news"], + }, + }, + }, + } + end + let(:full_feed_meta) { Liquid::Template.parse("{% feed_meta include: all %}").render!(context, {}) } + + it "renders the feed meta for all collections and categories" do + default_feed_link = '' + collection_feed_link = '' + category_feed_link = '' + expect(full_feed_meta).to include(default_feed_link) + expect(full_feed_meta).to include(collection_feed_link) + expect(full_feed_meta).to include(category_feed_link) + end + end + context "feed stylesheet" do it "includes the stylesheet" do expect(contents).to include('') @@ -429,6 +489,31 @@ end end + context "with collection title" do + let(:collection_with_title_feed) { File.read(dest_dir("feed/collection_with_title.xml")) } + let(:overrides) do + { + "collections" => { + "collection_with_title" => { + "output" => true, + "path" => 'collection_with_title' + }, + }, + "feed" => { + "collections" => { + "collection_with_title" => { + "title" => "My collection title", + }, + }, + }, + } + end + + it "outputs the collection feed with custom title" do + expect(collection_with_title_feed).to match 'My Awesome Site | My collection title' + end + end + context "with categories" do let(:overrides) do {