Skip to content
Merged
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
4 changes: 3 additions & 1 deletion lib/perron/html_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "perron/html_processor/target_blank"
require "perron/html_processor/lazy_load_images"
require "perron/html_processor/absolute_urls"

module Perron
class HtmlProcessor
Expand All @@ -21,7 +22,8 @@ def process

BUILT_IN = {
"target_blank" => Perron::HtmlProcessor::TargetBlank,
"lazy_load_images" => Perron::HtmlProcessor::LazyLoadImages
"lazy_load_images" => Perron::HtmlProcessor::LazyLoadImages,
"absolute_urls" => Perron::HtmlProcessor::AbsoluteUrls
}.tap do |processors|
require "rouge"
require "perron/html_processor/syntax_highlight"
Expand Down
27 changes: 27 additions & 0 deletions lib/perron/html_processor/absolute_urls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Perron
class HtmlProcessor
class AbsoluteUrls < HtmlProcessor::Base
def process
@html.css("img").each do |image|
src = image["src"]

next if src.blank? || absolute_url?(src)

image["src"] = base_url + src
end
end

private

def absolute_url?(src)
src.start_with?("http://", "https://", "//")
end

def base_url
Perron.configuration.url.delete_suffix("/")
end
end
end
end
11 changes: 10 additions & 1 deletion lib/perron/resource/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def apply_fallbacks_and_defaults(to:)

to[:canonical_url] ||= canonical_url

to[:image] = absolute_url(to[:image]) if to[:image]

to[:og_image] ||= to[:image]
to[:twitter_image] ||= to[:og_image]

Expand Down Expand Up @@ -52,13 +54,20 @@ def canonical_url
begin
Rails.application.routes.url_helpers.polymorphic_url(
@resource,
**Perron.configuration.default_url_options
**Perron.configuration.default_url_options
)
rescue
false
end
end

def absolute_url(path)
return path if path.blank?
return path if path.start_with?("http://", "https://", "//")

Perron.configuration.url.delete_suffix("/") + path
end

def site_data
@config.metadata.except(:title_separator, :title_suffix).deep_symbolize_keys || {}
end
Expand Down
4 changes: 2 additions & 2 deletions lib/perron/site/builder/feeds/atom.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@

<% base_url = url_for_resource(resource) %>
<% if base_url %>
<content type="html" xml:base="<%= base_url %>"><![CDATA[<%= Perron::Markdown.render(resource.content) %>]]></content>
<content type="html" xml:base="<%= base_url %>"><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></content>
<% else %>
<content type="html"><![CDATA[<%= Perron::Markdown.render(resource.content) %>]]></content>
<content type="html"><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></content>
<% end %>

<% resource.metadata.tags&.each do |tag| %>
Expand Down
2 changes: 1 addition & 1 deletion lib/perron/site/builder/feeds/json.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
date_published: resource.published_at&.iso8601,
title: resource.metadata.title,
authors: (item_author && item_author.name ? [{ name: item_author.name, email: item_author.email, url: item_author.url, avatar: item_author.avatar }.compact] : nil),
content_html: Perron::Markdown.render(resource.content)
content_html: Perron::Markdown.render(resource.content, processors: ["absolute_urls"])
}.compact
}
}.to_json %>
2 changes: 1 addition & 1 deletion lib/perron/site/builder/feeds/rss.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<% end %>
<title><%= resource.metadata.title %></title>

<description><![CDATA[<%= Perron::Markdown.render(resource.content) %>]]></description>
<description><![CDATA[<%= Perron::Markdown.render(resource.content, processors: ["absolute_urls"]) %>]]></description>
</item>
<% end %>
</channel>
Expand Down
63 changes: 63 additions & 0 deletions test/perron/html_processor/absolute_urls_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'test_helper'

class Perron::HtmlProcessor::AbsoluteUrlsTest < ActionView::TestCase
def process_html(html)
document = Nokogiri::HTML::DocumentFragment.parse(html)

Perron::HtmlProcessor::AbsoluteUrls.new(document).process

document.to_html
end

test 'converts relative image src to absolute URL' do
html = '<img src="/images/photo.jpg" alt="A photo">'
processed = process_html(html)

assert_dom_equal '<img src="http://localhost:3000/images/photo.jpg" alt="A photo">', processed
end

test 'does not modify already absolute http URL' do
html = '<img src="http://example.com/images/photo.jpg" alt="A photo">'
processed = process_html(html)

assert_dom_equal html, processed
end

test 'does not modify already absolute https URL' do
html = '<img src="https://example.com/images/photo.jpg" alt="A photo">'
processed = process_html(html)

assert_dom_equal html, processed
end

test 'does not modify protocol-relative URL' do
html = '<img src="//example.com/images/photo.jpg" alt="A photo">'
processed = process_html(html)

assert_dom_equal html, processed
end

test 'does not modify image without src attribute' do
html = '<img alt="A photo">'
processed = process_html(html)

assert_dom_equal html, processed
end

test 'processes multiple images' do
html = '<img src="/images/photo1.jpg"><img src="/images/photo2.jpg">'
processed = process_html(html)

assert_dom_equal '<img src="http://localhost:3000/images/photo1.jpg"><img src="http://localhost:3000/images/photo2.jpg">',
processed
end

test 'does not affect content without images' do
html = '<p>Some text, but no images.</p>'
processed = process_html(html)

assert_dom_equal html, processed
end
end
106 changes: 53 additions & 53 deletions test/perron/resource/metadata_test.rb
Original file line number Diff line number Diff line change
@@ -1,80 +1,80 @@
require "test_helper"
require 'test_helper'

class Perron::Resource::MetadataTest < ActiveSupport::TestCase
include ConfigurationHelper

def setup
@post = Content::Post.new("test/dummy/app/content/posts/2023-05-15-sample-post.md")
@post = Content::Post.new('test/dummy/app/content/posts/2023-05-15-sample-post.md')
@post_frontmatter = Perron::Resource::Separator.new(@post.raw_content).frontmatter
@posts_collection = Perron::Site.collection("posts")
@posts_collection = Perron::Site.collection('posts')

@custom_page = Content::Page.new("test/dummy/app/content/pages/custom.md")
@custom_page = Content::Page.new('test/dummy/app/content/pages/custom.md')
@custom_page_frontmatter = Perron::Resource::Separator.new(@custom_page.raw_content).frontmatter
@pages_collection = Perron::Site.collection("pages")
@pages_collection = Perron::Site.collection('pages')

@root_page = Content::Page.new("test/dummy/app/content/pages/root.erb")
@root_page = Content::Page.new('test/dummy/app/content/pages/root.erb')
@root_page_frontmatter = Perron::Resource::Separator.new(@root_page.raw_content).frontmatter

@about_page = Content::Page.new("test/dummy/app/content/pages/about.md")
@about_page = Content::Page.new('test/dummy/app/content/pages/about.md')
@about_page_frontmatter = Perron::Resource::Separator.new(@about_page.raw_content).frontmatter
end

test "generates basic metadata with fallbacks for a standard blog post" do
test 'generates basic metadata with fallbacks for a standard blog post' do
metadata = Perron::Resource::Metadata.new(
resource: @post,
frontmatter: @post_frontmatter,
collection: @posts_collection
).data

assert_equal "Sample Post", metadata.title
assert_equal "Describing sample post", metadata.description
assert_equal 'Sample Post', metadata.title
assert_equal 'Describing sample post', metadata.description

assert_equal "http://localhost:3000/blog/sample-post/", metadata.og_url
assert_equal 'http://localhost:3000/blog/sample-post/', metadata.og_url
assert_equal Date.new(2023, 5, 15).to_datetime, metadata.article_published_time

assert_equal "Dummy App", metadata.og_site_name
assert_equal 'Dummy App', metadata.og_site_name

assert_equal "summary_large_image", metadata.twitter_card
assert_equal 'summary_large_image', metadata.twitter_card

assert_equal "Sample Post", metadata.og_title
assert_equal "Sample Post", metadata.twitter_title
assert_equal "Describing sample post", metadata.og_description
assert_equal "Describing sample post", metadata.twitter_description
assert_equal 'Sample Post', metadata.og_title
assert_equal 'Sample Post', metadata.twitter_title
assert_equal 'Describing sample post', metadata.og_description
assert_equal 'Describing sample post', metadata.twitter_description
end

test "frontmatter values take precedence over all defaults and fallbacks" do
test 'frontmatter values take precedence over all defaults and fallbacks' do
metadata = Perron::Resource::Metadata.new(
resource: @custom_page,
frontmatter: @custom_page_frontmatter,
collection: @pages_collection
).data

assert_equal "Custom OG Title For Sharing", metadata.title
assert_equal "Custom OG Title For Sharing", metadata.og_title, "og_title should be from frontmatter, not a fallback"
assert_equal "summary", metadata.twitter_card, "twitter_card should be from frontmatter, not the default"
assert_equal 'Custom OG Title For Sharing', metadata.title
assert_equal 'Custom OG Title For Sharing', metadata.og_title, 'og_title should be from frontmatter, not a fallback'
assert_equal 'summary', metadata.twitter_card, 'twitter_card should be from frontmatter, not the default'

assert_equal "/image.jpg", metadata.image
assert_equal "/og-image.jpg", metadata.og_image, "og_image should be from frontmatter, not a fallback from image"
assert_equal "/og-image.jpg", metadata.twitter_image, "twitter_image should fall back to the specific og_image"
assert_equal 'http://localhost:3000/image.jpg', metadata.image
assert_equal '/og-image.jpg', metadata.og_image, 'og_image should be from frontmatter, not a fallback from image'
assert_equal '/og-image.jpg', metadata.twitter_image, 'twitter_image should fall back to the specific og_image'
end

test "inherits metadata from collection configuration" do
collection = Perron::Site.collection("posts")
test 'inherits metadata from collection configuration' do
collection = Perron::Site.collection('posts')
metadata = Perron::Resource::Metadata.new(
resource: @post,
frontmatter: @post_frontmatter,
collection: collection
).data

assert_equal "The Post Collection Team", metadata.author
assert_equal "The Post Collection Team", metadata.og_author, "og_author should fall back to collection author"
assert_equal "article", metadata.type
assert_equal "article", metadata.og_type, "og_type should fall back to collection type"
assert_equal 'The Post Collection Team', metadata.author
assert_equal 'The Post Collection Team', metadata.og_author, 'og_author should fall back to collection author'
assert_equal 'article', metadata.type
assert_equal 'article', metadata.og_type, 'og_type should fall back to collection type'
end

test "inherits and merges metadata from site configuration" do
test 'inherits and merges metadata from site configuration' do
Perron.configure do |config|
config.metadata = { author: "The Dummy App Team", locale: "en_GB", description: "Site-wide description" }
config.metadata = { author: 'The Dummy App Team', locale: 'en_GB', description: 'Site-wide description' }
end

metadata = Perron::Resource::Metadata.new(
Expand All @@ -83,68 +83,68 @@ def setup
collection: @pages_collection
).data

assert_equal "The Dummy App Team", metadata.author
assert_equal "en_GB", metadata.locale
assert_equal "en_GB", metadata.og_locale
assert_equal 'The Dummy App Team', metadata.author
assert_equal 'en_GB', metadata.locale
assert_equal 'en_GB', metadata.og_locale

assert_equal "This is the about page.", metadata.description
assert_equal "This is the about page.", metadata.og_description
assert_equal 'This is the about page.', metadata.description
assert_equal 'This is the about page.', metadata.og_description
end

test "metadata precedence is frontmatter > collection > site" do
resource = Content::Post.new("test/dummy/app/content/posts/2023-06-15-another-post.md")
test 'metadata precedence is frontmatter > collection > site' do
resource = Content::Post.new('test/dummy/app/content/posts/2023-06-15-another-post.md')
frontmatter = Perron::Resource::Separator.new(resource.raw_content).frontmatter

Perron.configure do |config|
config.metadata.author = "Site Author"
config.metadata.author = 'Site Author'
end

collection = Perron::Site.collection("posts")
collection = Perron::Site.collection('posts')
metadata = Perron::Resource::Metadata.new(resource: resource, frontmatter: frontmatter, collection: collection).data

assert_equal "Kendall", metadata.author, "Frontmatter author should take highest precedence"
assert_equal 'Kendall', metadata.author, 'Frontmatter author should take highest precedence'
end

test "removes nil values from final data after processing" do
test 'removes nil values from final data after processing' do
metadata = Perron::Resource::Metadata.new(
resource: @about_page,
frontmatter: @about_page_frontmatter,
collection: @pages_collection
).data

assert_not metadata.key?(:image), "key :image should be removed"
assert_not metadata.key?(:og_image), "key :og_image should be removed"
assert_not metadata.key?(:twitter_image), "key :twitter_image should be removed"
assert_not metadata.key?(:author), "key :author should be removed"
assert_not metadata.key?(:image), 'key :image should be removed'
assert_not metadata.key?(:og_image), 'key :og_image should be removed'
assert_not metadata.key?(:twitter_image), 'key :twitter_image should be removed'
assert_not metadata.key?(:author), 'key :author should be removed'
end

test "generates canonical url for root" do
test 'generates canonical url for root' do
metadata = Perron::Resource::Metadata.new(
resource: @root_page,
frontmatter: @root_page_frontmatter,
collection: @pages_collection
).data

assert_equal "http://localhost:3000/", metadata.og_url
assert_equal 'http://localhost:3000/', metadata.og_url
end

test "generates canonical url with trailing slash when configured" do
test 'generates canonical url with trailing slash when configured' do
metadata = Perron::Resource::Metadata.new(
resource: @about_page,
frontmatter: @about_page_frontmatter,
collection: @pages_collection
).data

assert_equal "http://localhost:3000/about/", metadata.og_url
assert_equal 'http://localhost:3000/about/', metadata.og_url
end

test "title falls back to site name if not present anywhere" do
test 'title falls back to site name if not present anywhere' do
metadata = Perron::Resource::Metadata.new(
resource: @about_page,
frontmatter: {},
collection: @pages_collection
).data

assert_equal "Dummy App", metadata.title, "Title should fall back to the configured site_name"
assert_equal 'Dummy App', metadata.title, 'Title should fall back to the configured site_name'
end
end
Loading