diff --git a/lib/pressa/posts/metadata.rb b/lib/pressa/posts/metadata.rb index 1b58756..5c7041e 100644 --- a/lib/pressa/posts/metadata.rb +++ b/lib/pressa/posts/metadata.rb @@ -6,7 +6,7 @@ module Pressa class PostMetadata REQUIRED_FIELDS = %w[Title Author Date Timestamp].freeze - attr_reader :title, :author, :date, :formatted_date, :link, :tags + attr_reader :title, :author, :date, :formatted_date, :link, :tags, :image def initialize(yaml_hash) @raw = yaml_hash @@ -39,6 +39,7 @@ module Pressa @formatted_date = @raw["Date"] @link = @raw["Link"] @tags = parse_tags(@raw["Tags"]) + @image = @raw["Image"] end def parse_tags(value) diff --git a/lib/pressa/posts/models.rb b/lib/pressa/posts/models.rb index aaf70cd..11b7246 100644 --- a/lib/pressa/posts/models.rb +++ b/lib/pressa/posts/models.rb @@ -11,6 +11,7 @@ module Pressa attribute :formatted_date, Types::String attribute :link, Types::String.optional.default(nil) attribute :tags, Types::Array.of(Types::String).default([].freeze) + attribute :image, Types::String.optional.default(nil) attribute :body, Types::String attribute :markdown_body, Types::String.default("".freeze) attribute :excerpt, Types::String diff --git a/lib/pressa/posts/repo.rb b/lib/pressa/posts/repo.rb index bc167f3..b18571b 100644 --- a/lib/pressa/posts/repo.rb +++ b/lib/pressa/posts/repo.rb @@ -47,6 +47,7 @@ module Pressa formatted_date: metadata.formatted_date, link: metadata.link, tags: metadata.tags, + image: metadata.image, body: html_body, markdown_body: body_markdown, excerpt:, diff --git a/lib/pressa/posts/writer.rb b/lib/pressa/posts/writer.rb index 8b02e9b..ac4e614 100644 --- a/lib/pressa/posts/writer.rb +++ b/lib/pressa/posts/writer.rb @@ -117,7 +117,10 @@ module Pressa canonical_url: @site.url_for(post.path), content: content_view, page_description: post.excerpt, - page_type: "article" + page_type: "article", + page_image: post.image, + page_tags: post.tags, + page_published_time: post.date.iso8601 ) file_path = File.join(target_path, post.path.sub(/^\//, ""), "index.html") @@ -161,7 +164,10 @@ module Pressa canonical_url:, content:, page_description: nil, - page_type: "website" + page_type: "website", + page_image: nil, + page_tags: [], + page_published_time: nil ) layout = Views::Layout.new( site: @site, @@ -169,6 +175,9 @@ module Pressa canonical_url:, page_description:, page_type:, + page_image:, + page_tags:, + page_published_time:, content: ) diff --git a/lib/pressa/views/layout.rb b/lib/pressa/views/layout.rb index bd0cda5..03439bc 100644 --- a/lib/pressa/views/layout.rb +++ b/lib/pressa/views/layout.rb @@ -8,6 +8,9 @@ module Pressa :page_subtitle, :page_description, :page_type, + :page_image, + :page_tags, + :page_published_time, :canonical_url, :page_scripts, :page_styles, @@ -18,6 +21,9 @@ module Pressa canonical_url:, page_subtitle: nil, page_description: nil, page_type: "website", + page_image: nil, + page_tags: [], + page_published_time: nil, page_scripts: [], page_styles: [], content: nil @@ -26,6 +32,9 @@ module Pressa @page_subtitle = page_subtitle @page_description = page_description @page_type = page_type + @page_image = page_image + @page_tags = page_tags + @page_published_time = page_published_time @canonical_url = canonical_url @page_scripts = page_scripts @page_styles = page_styles @@ -54,7 +63,12 @@ module Pressa meta(property: "og:image", content: og_image_url) if og_image_url meta(property: "og:type", content: page_type) meta(property: "article:author", content: site.author) - meta(name: "twitter:card", content: "summary") + meta(name: "twitter:card", content: twitter_card_type) + + if page_type == "article" + meta(property: "article:published_time", content: page_published_time) if page_published_time + page_tags.each { |tag| meta(property: "article:tag", content: tag) } + end link( rel: "alternate", @@ -108,9 +122,18 @@ module Pressa end def og_image_url + if page_image + return page_image if page_image.start_with?("http://", "https://") + return absolute_asset(page_image) + end + site.image_url end + def twitter_card_type + page_image ? "summary_large_image" : "summary" + end + def all_styles site.styles + page_styles end diff --git a/test/posts/metadata_test.rb b/test/posts/metadata_test.rb index 2bd8a72..cb77883 100644 --- a/test/posts/metadata_test.rb +++ b/test/posts/metadata_test.rb @@ -10,6 +10,7 @@ class Pressa::Posts::PostMetadataTest < Minitest::Test Timestamp: 2025-11-05T10:00:00-08:00 Tags: Ruby, Testing Link: https://example.net/external + Image: /images/blog/test-post.png --- This is the post body. @@ -25,6 +26,7 @@ class Pressa::Posts::PostMetadataTest < Minitest::Test assert_equal(5, metadata.date.day) assert_equal("https://example.net/external", metadata.link) assert_equal(["Ruby", "Testing"], metadata.tags) + assert_equal("/images/blog/test-post.png", metadata.image) end def test_parse_raises_error_when_required_fields_are_missing @@ -56,6 +58,7 @@ class Pressa::Posts::PostMetadataTest < Minitest::Test assert_equal([], metadata.tags) assert_nil(metadata.link) + assert_nil(metadata.image) end def test_parse_raises_error_when_front_matter_is_missing diff --git a/test/posts/writer_test.rb b/test/posts/writer_test.rb index 1e2c935..4625bd5 100644 --- a/test/posts/writer_test.rb +++ b/test/posts/writer_test.rb @@ -23,7 +23,8 @@ class Pressa::Posts::PostWriterTest < Minitest::Test link: "https://example.net/linked", body: "
linked body
", excerpt: "linked body...", - path: "/posts/2025/11/link-post" + path: "/posts/2025/11/link-post", + image: "/images/blog/link-post.png" ) regular_post = Pressa::Posts::Post.new( slug: "regular-post", @@ -73,6 +74,22 @@ class Pressa::Posts::PostWriterTest < Minitest::Test end end + def test_write_posts_uses_post_image_for_og_image_and_article_tags + Dir.mktmpdir do |dir| + writer.write_posts(target_path: dir) + + linked = File.join(dir, "posts/2025/11/link-post/index.html") + html = File.read(linked) + + assert_includes(html, %()) + assert_includes(html, %()) + + regular = File.join(dir, "posts/2024/10/regular-post/index.html") + regular_html = File.read(regular) + assert_includes(regular_html, %()) + end + end + def test_write_recent_posts_writes_index_page Dir.mktmpdir do |dir| writer.write_recent_posts(target_path: dir, limit: 1) diff --git a/test/views/layout_test.rb b/test/views/layout_test.rb index c186f44..1407fc0 100644 --- a/test/views/layout_test.rb +++ b/test/views/layout_test.rb @@ -140,4 +140,66 @@ class Pressa::Views::LayoutTest < Minitest::Test refute_includes(html, "techhub.social") refute_includes(html, "github.com/samsonjs") end + + def test_page_image_overrides_og_image_and_uses_large_image_card + html = Pressa::Views::Layout.new( + site:, + canonical_url: "https://samhuri.net/posts/", + page_image: "/images/blog/post.png", + content: content_view + ).call + + assert_includes(html, %()) + assert_includes(html, %()) + end + + def test_page_image_preserves_absolute_urls + html = Pressa::Views::Layout.new( + site:, + canonical_url: "https://samhuri.net/posts/", + page_image: "https://cdn.example.net/preview.png", + content: content_view + ).call + + assert_includes(html, %()) + end + + def test_default_twitter_card_is_summary_without_page_image + html = Pressa::Views::Layout.new( + site:, + canonical_url: "https://samhuri.net/posts/", + content: content_view + ).call + + assert_includes(html, %()) + end + + def test_article_tags_render_published_time_and_tags + html = Pressa::Views::Layout.new( + site:, + canonical_url: "https://samhuri.net/posts/2025/11/05/post/", + page_type: "article", + page_published_time: "2025-11-05T10:00:00-08:00", + page_tags: ["ruby", "rails"], + content: content_view + ).call + + assert_includes(html, %()) + assert_includes(html, %()) + assert_includes(html, %()) + end + + def test_article_tags_do_not_render_for_non_article_pages + html = Pressa::Views::Layout.new( + site:, + canonical_url: "https://samhuri.net/posts/", + page_type: "website", + page_published_time: "2025-11-05T10:00:00-08:00", + page_tags: ["ruby"], + content: content_view + ).call + + refute_includes(html, "article:published_time") + refute_includes(html, "article:tag") + end end