diff --git a/README.md b/README.md index 9a648181..0307ffb9 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,20 @@ Out of the box, Jekyll requires that any markdown file have YAML front matter (k While that behavior may be helpful for large, complex sites, sometimes it's easier to simply add a plain markdown file and have it render without fanfare. -This plugin does just that. Any Markdown file in your site's source will be treated as a Page and rendered as HTML, even if it doesn't have YAML front matter. +This plugin does just that. Any Markdown file in your site's source will be treated as a Page and rendered as HTML, even if it doesn't have YAML front matter. Additionally, any Markdown file in a collection directory will be treated as a collection document and rendered as HTML, even without front matter. ## Content Conversion -The plugin automatically converts Markdown content to HTML when adding pages without front matter to the site. This ensures that `page.content` contains properly formatted HTML rather than raw Markdown, making it compatible with themes and plugins that expect HTML content during site generation. +The plugin automatically converts Markdown content to HTML when adding pages and collection documents without front matter to the site. This ensures that `page.content` and `document.content` contain properly formatted HTML rather than raw Markdown, making it compatible with themes and plugins that expect HTML content during site generation. + +## Collections Support + +This plugin works with both regular pages and Jekyll collections. For collections: + +- Files with front matter work as they normally would +- Files without front matter in collection directories (e.g., `_posts/`, `_articles/`) will be automatically processed and added to the collection +- The processed documents will have their Markdown content converted to HTML +- When `remove_originals` is enabled, the original static files will be removed from the output ## Usage diff --git a/lib/jekyll-optional-front-matter/generator.rb b/lib/jekyll-optional-front-matter/generator.rb index 342c7ac9..6751fd20 100644 --- a/lib/jekyll-optional-front-matter/generator.rb +++ b/lib/jekyll-optional-front-matter/generator.rb @@ -27,6 +27,15 @@ def generate(site) site.pages << page end + # Add collection documents to the site + collection_documents_to_add.each do |collection_name, documents| + documents.each do |document| + # Pre-convert the content of documents without front matter + convert_document_content(document) + site.collections[collection_name].docs << document + end + end + site.static_files -= static_files_to_remove if cleanup? end @@ -38,6 +47,12 @@ def convert_content(page) page.content = renderer.convert(page.content) end + # Convert markdown content to HTML for collection documents + def convert_document_content(document) + renderer = Jekyll::Renderer.new(site, document) + document.content = renderer.convert(document.content) + end + # An array of Jekyll::Pages to add, *excluding* blacklisted files def pages_to_add pages.reject { |page| blacklisted?(page) } @@ -45,17 +60,67 @@ def pages_to_add # An array of Jekyll::StaticFile's, *excluding* blacklisted files def static_files_to_remove - markdown_files.reject { |page| blacklisted?(page) } + files_to_remove = markdown_files.reject { |file| blacklisted?(page_from_static_file(file)) } + files_to_remove += collection_static_files.reject { |file| blacklisted?(document_from_static_file(file)) } + files_to_remove end - # An array of potential Jekyll::Pages to add, *including* blacklisted files - def pages - markdown_files.map { |static_file| page_from_static_file(static_file) } + # A hash of collection names to Jekyll::Documents to add + def collection_documents_to_add + collection_static_files.group_by { |file| collection_name_from_path(file.relative_path) } + .transform_values { |files| files.map { |file| document_from_static_file(file) } + .reject { |doc| blacklisted?(doc) } } + end + + # An array of Jekyll::StaticFile's in collection directories with markdown extensions + def collection_static_files + site.static_files.select do |file| + file.relative_path.start_with?('_') && + file.relative_path.include?('/') && + markdown_converter.matches(file.extname) && + collection_name_from_path(file.relative_path) && + site.collections.key?(collection_name_from_path(file.relative_path)) + end + end + + # Extract collection name from file path (e.g., "_articles/file.md" -> "articles") + def collection_name_from_path(path) + parts = path.split('/') + return nil unless parts.first&.start_with?('_') + parts.first[1..-1] # Remove the leading underscore + end + + # Given a Jekyll::StaticFile in a collection directory, returns it as a Jekyll::Document + def document_from_static_file(static_file) + collection_name = collection_name_from_path(static_file.relative_path) + collection = site.collections[collection_name] + + # Get file path relative to the site source + path = static_file.path + + # Create a document similar to how Jekyll::Collection#read does it + document = Jekyll::Document.new(path, { + site: site, + collection: collection + }) + + # Read the document content from file + document.read + + document end - # An array of Jekyll::StaticFile's with a site-defined markdown extension + # An array of Jekyll::StaticFile's with a site-defined markdown extension (excluding collection files) def markdown_files - site.static_files.select { |file| markdown_converter.matches(file.extname) } + site.static_files.select do |file| + markdown_converter.matches(file.extname) && + !file.relative_path.start_with?('_') + end + end + + # An array of potential Jekyll::Pages to add, *including* blacklisted files + def pages + markdown_files.map { |static_file| page_from_static_file(static_file) } end # Given a Jekyll::StaticFile, returns the file as a Jekyll::Page @@ -66,17 +131,33 @@ def page_from_static_file(static_file) Jekyll::Page.new(site, base, dir, name) end - # Does the given Jekyll::Page match our filename blacklist? - def blacklisted?(page) - return false if whitelisted?(page) + # Does the given Jekyll::Page or Jekyll::Document match our filename blacklist? + def blacklisted?(page_or_doc) + return false if whitelisted?(page_or_doc) - FILENAME_BLACKLIST.include?(page.basename.upcase) + basename = if page_or_doc.respond_to?(:basename) + page_or_doc.basename + elsif page_or_doc.respond_to?(:name) + File.basename(page_or_doc.name, File.extname(page_or_doc.name)) + else + File.basename(page_or_doc.path, File.extname(page_or_doc.path)) + end + + FILENAME_BLACKLIST.include?(basename.upcase) end - def whitelisted?(page) + def whitelisted?(page_or_doc) return false unless site.config["include"].is_a? Array - entry_filter.included?(page.relative_path) + relative_path = if page_or_doc.respond_to?(:relative_path) + page_or_doc.relative_path + elsif page_or_doc.respond_to?(:path) + Pathname.new(page_or_doc.path).relative_path_from(Pathname.new(site.source)).to_s + else + return false + end + + entry_filter.included?(relative_path) end def markdown_converter diff --git a/spec/fixtures/site-with-collections/_articles/article-with-front-matter.md b/spec/fixtures/site-with-collections/_articles/article-with-front-matter.md new file mode 100644 index 00000000..f24923f4 --- /dev/null +++ b/spec/fixtures/site-with-collections/_articles/article-with-front-matter.md @@ -0,0 +1,7 @@ +--- +title: "Article With Front Matter" +--- + +# Article With Front Matter + +This article has front matter and should work normally. \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/_articles/article-without-front-matter.md b/spec/fixtures/site-with-collections/_articles/article-without-front-matter.md new file mode 100644 index 00000000..74160a54 --- /dev/null +++ b/spec/fixtures/site-with-collections/_articles/article-without-front-matter.md @@ -0,0 +1,3 @@ +# Article Without Front Matter + +This is an article without front matter in a custom collection. \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/_config.yml b/spec/fixtures/site-with-collections/_config.yml new file mode 100644 index 00000000..600ef268 --- /dev/null +++ b/spec/fixtures/site-with-collections/_config.yml @@ -0,0 +1,12 @@ +collections: + posts: + output: true + articles: + output: true + permalink: /:collection/:name/ + +optional_front_matter: + remove_originals: true + +plugins: + - jekyll-optional-front-matter \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/_posts/2023-01-01-post-with-front-matter.md b/spec/fixtures/site-with-collections/_posts/2023-01-01-post-with-front-matter.md new file mode 100644 index 00000000..d6228777 --- /dev/null +++ b/spec/fixtures/site-with-collections/_posts/2023-01-01-post-with-front-matter.md @@ -0,0 +1,7 @@ +--- +title: "Post With Front Matter" +--- + +# Post With Front Matter + +This post has front matter and should work normally. \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/_posts/2023-01-02-post-without-front-matter.md b/spec/fixtures/site-with-collections/_posts/2023-01-02-post-without-front-matter.md new file mode 100644 index 00000000..3379e104 --- /dev/null +++ b/spec/fixtures/site-with-collections/_posts/2023-01-02-post-without-front-matter.md @@ -0,0 +1,3 @@ +# Post Without Front Matter + +This is a post without front matter that should be processed by the plugin. \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/about.md b/spec/fixtures/site-with-collections/about.md new file mode 100644 index 00000000..0234dbc4 --- /dev/null +++ b/spec/fixtures/site-with-collections/about.md @@ -0,0 +1,3 @@ +# About + +This is an about page without front matter. \ No newline at end of file diff --git a/spec/fixtures/site-with-collections/index.md b/spec/fixtures/site-with-collections/index.md new file mode 100644 index 00000000..e7a9023a --- /dev/null +++ b/spec/fixtures/site-with-collections/index.md @@ -0,0 +1,7 @@ +--- +title: "Home" +--- + +# Welcome + +This is the home page with front matter. \ No newline at end of file diff --git a/spec/jekyll-optional-front-matter/collections_spec.rb b/spec/jekyll-optional-front-matter/collections_spec.rb new file mode 100644 index 00000000..c4b919fd --- /dev/null +++ b/spec/jekyll-optional-front-matter/collections_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +describe "JekyllOptionalFrontMatter with Collections" do + let(:site) { fixture_site("site-with-collections") } + let(:generator) { JekyllOptionalFrontMatter::Generator.new(site) } + + before do + site.reset + site.read + end + + context "before generator runs" do + it "has collections defined" do + # Verify collections exist + expect(site.collections.keys).to include("posts", "articles") + end + + it "posts collection processes all markdown files (Jekyll default behavior)" do + # Check posts collection - Jekyll processes all files in _posts even without front matter + posts = site.collections["posts"].docs + puts "Posts found: #{posts.length}" + posts.each { |p| puts " - #{p.basename}" } + + expect(posts.length).to be >= 2 + + post_names = posts.map(&:basename) + expect(post_names).to include("2023-01-01-post-with-front-matter.md") + expect(post_names).to include("2023-01-02-post-without-front-matter.md") + end + + it "custom collections only process files with front matter" do + # Check articles collection - custom collections need front matter + articles = site.collections["articles"].docs + puts "Articles found: #{articles.length}" + articles.each { |a| puts " - #{a.basename}" } + + expect(articles.length).to be >= 1 + + article_names = articles.map(&:basename) + expect(article_names).to include("article-with-front-matter.md") + expect(article_names).not_to include("article-without-front-matter.md") + end + + it "custom collection files without front matter become static files" do + # Let's see if they're in static files instead + static_files = site.static_files + puts "Static files found: #{static_files.length}" + static_files.each { |f| puts " - #{f.relative_path}" } + + static_paths = static_files.map(&:relative_path) + expect(static_paths).to include("_articles/article-without-front-matter.md") + end + end + + context "after generator runs" do + before { generator.generate(site) } + + it "processes collection documents without front matter" do + # After running the generator, custom collections should now include + # documents that were previously only static files + articles = site.collections["articles"].docs + puts "Articles after generator: #{articles.length}" + articles.each { |a| puts " - #{a.basename}" } + + expect(articles.length).to be >= 2 + + article_names = articles.map(&:basename) + expect(article_names).to include("article-with-front-matter.md") + expect(article_names).to include("article-without-front-matter.md") + end + + it "removes static files that became collection documents" do + # The static file should no longer exist + static_files = site.static_files + static_paths = static_files.map(&:relative_path) + expect(static_paths).not_to include("_articles/article-without-front-matter.md") + end + end +end \ No newline at end of file diff --git a/spec/jekyll-optional-front-matter/integration_spec.rb b/spec/jekyll-optional-front-matter/integration_spec.rb new file mode 100644 index 00000000..13fef8a0 --- /dev/null +++ b/spec/jekyll-optional-front-matter/integration_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +describe "JekyllOptionalFrontMatter Integration" do + let(:site) { fixture_site("site-with-collections") } + let(:generator) { JekyllOptionalFrontMatter::Generator.new(site) } + + before do + site.reset + site.read + generator.generate(site) + end + + it "processes both pages and collection documents" do + # The site should have the original pages plus any new ones from the generator + expect(site.pages.length).to be >= 1 + + # Collections should include documents without front matter + articles = site.collections["articles"].docs + expect(articles.length).to be >= 2 + + # Find the document that was created from a file without front matter + article_without_fm = articles.find { |doc| doc.basename == "article-without-front-matter.md" } + expect(article_without_fm).not_to be_nil + expect(article_without_fm.content).to include("