Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
105 changes: 93 additions & 12 deletions lib/jekyll-optional-front-matter/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -38,24 +47,80 @@ 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) }
end

# 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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: "Article With Front Matter"
---

# Article With Front Matter

This article has front matter and should work normally.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Article Without Front Matter

This is an article without front matter in a custom collection.
12 changes: 12 additions & 0 deletions spec/fixtures/site-with-collections/_config.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: "Post With Front Matter"
---

# Post With Front Matter

This post has front matter and should work normally.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Post Without Front Matter

This is a post without front matter that should be processed by the plugin.
3 changes: 3 additions & 0 deletions spec/fixtures/site-with-collections/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# About

This is an about page without front matter.
7 changes: 7 additions & 0 deletions spec/fixtures/site-with-collections/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: "Home"
---

# Welcome

This is the home page with front matter.
79 changes: 79 additions & 0 deletions spec/jekyll-optional-front-matter/collections_spec.rb
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions spec/jekyll-optional-front-matter/integration_spec.rb
Original file line number Diff line number Diff line change
@@ -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("<h1")
expect(article_without_fm.content).to include("Article Without Front Matter")
end

it "handles posts collection correctly" do
# Posts collection should still work normally since Jekyll processes all posts
posts = site.collections["posts"].docs
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
end