mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-06-23 04:44:54 +00:00
Add dynamic drafts index page listing unpublished drafts
This commit is contained in:
parent
93be8ad37d
commit
65e9734b8e
8 changed files with 324 additions and 0 deletions
13
lib/pressa/drafts/entry.rb
Normal file
13
lib/pressa/drafts/entry.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
require "dry-struct"
|
||||
require "pressa/site"
|
||||
|
||||
module Pressa
|
||||
class Drafts
|
||||
class Entry < Dry::Struct
|
||||
attribute :slug, Types::String
|
||||
attribute :title, Types::String
|
||||
attribute :timestamp, Types::Params::DateTime
|
||||
attribute :path, Types::String
|
||||
end
|
||||
end
|
||||
end
|
||||
48
lib/pressa/drafts/repo.rb
Normal file
48
lib/pressa/drafts/repo.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
require "yaml"
|
||||
require "date"
|
||||
require "pressa/drafts"
|
||||
require "pressa/drafts/entry"
|
||||
|
||||
module Pressa
|
||||
class Drafts
|
||||
class Repo
|
||||
def initialize(dir:)
|
||||
@dir = dir
|
||||
end
|
||||
|
||||
def read_entries
|
||||
Dir.glob(File.join(@dir, "*.md")).sort.filter_map { |file_path| read_entry(file_path) }
|
||||
.sort_by(&:timestamp)
|
||||
.reverse
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_entry(file_path)
|
||||
content = File.read(file_path)
|
||||
return nil unless content =~ /\A---\s*\n(.*?)\n---\s*\n/m
|
||||
|
||||
metadata = YAML.safe_load($1, permitted_classes: [Date, Time]) || {}
|
||||
title = metadata["Title"]
|
||||
timestamp_value = metadata["Timestamp"]
|
||||
return nil unless title && timestamp_value
|
||||
|
||||
slug = File.basename(file_path, ".md")
|
||||
timestamp = parse_timestamp(timestamp_value)
|
||||
|
||||
Entry.new(slug:, title:, timestamp:, path: "/drafts/#{slug}/")
|
||||
end
|
||||
|
||||
def parse_timestamp(value)
|
||||
case value
|
||||
when String
|
||||
DateTime.parse(value)
|
||||
when Integer
|
||||
Time.at(value).to_datetime
|
||||
else
|
||||
value.to_datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
lib/pressa/drafts/writer.rb
Normal file
30
lib/pressa/drafts/writer.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
require "pressa/utils/file_writer"
|
||||
require "pressa/views/layout"
|
||||
require "pressa/views/drafts_view"
|
||||
require "pressa/drafts"
|
||||
|
||||
module Pressa
|
||||
class Drafts
|
||||
class Writer
|
||||
def initialize(site:, entries:)
|
||||
@site = site
|
||||
@entries = entries
|
||||
end
|
||||
|
||||
def write_index(target_path:)
|
||||
content_view = Views::DraftsView.new(entries: @entries, site: @site)
|
||||
|
||||
layout = Views::Layout.new(
|
||||
site: @site,
|
||||
page_subtitle: "Drafts",
|
||||
canonical_url: @site.url_for("/drafts/"),
|
||||
page_description: "Unpublished drafts",
|
||||
content: content_view
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, "drafts", "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: layout.call)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
require "fileutils"
|
||||
require "pressa/utils/file_writer"
|
||||
require "pressa/drafts/repo"
|
||||
require "pressa/drafts/writer"
|
||||
|
||||
module Pressa
|
||||
class SiteGenerator
|
||||
|
|
@ -23,10 +25,19 @@ module Pressa
|
|||
|
||||
copy_static_files(source_path, target_path)
|
||||
process_public_directory(source_path, target_path)
|
||||
write_drafts_index(source_path, target_path) if site.output_format == "html"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def write_drafts_index(source_path, target_path)
|
||||
drafts_dir = File.join(source_path, "public", "drafts")
|
||||
return unless Dir.exist?(drafts_dir)
|
||||
|
||||
entries = Drafts::Repo.new(dir: drafts_dir).read_entries
|
||||
Drafts::Writer.new(site:, entries:).write_index(target_path:)
|
||||
end
|
||||
|
||||
def validate_paths!(source_path:, target_path:)
|
||||
source_abs = absolute_path(source_path)
|
||||
target_abs = absolute_path(target_path)
|
||||
|
|
|
|||
37
lib/pressa/views/drafts_view.rb
Normal file
37
lib/pressa/views/drafts_view.rb
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
require "phlex"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
class DraftsView < Phlex::HTML
|
||||
def initialize(entries:, site:)
|
||||
@entries = entries
|
||||
@site = site
|
||||
end
|
||||
|
||||
def view_template
|
||||
div(class: "container") do
|
||||
h1 { "Drafts" }
|
||||
|
||||
ul(class: "posts") do
|
||||
@entries.each do |entry|
|
||||
render_entry(entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_entry(entry)
|
||||
li do
|
||||
a(href: entry.path) { entry.title }
|
||||
time { short_date(entry.timestamp) }
|
||||
end
|
||||
end
|
||||
|
||||
def short_date(timestamp)
|
||||
timestamp.strftime("%-d %b %Y")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
89
test/drafts/repo_test.rb
Normal file
89
test/drafts/repo_test.rb
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
require "test_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
class Pressa::Drafts::RepoTest < Minitest::Test
|
||||
def test_read_entries_parses_title_and_timestamp
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, "powder-day.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: Shaun White
|
||||
Title: Powder Day at Baker
|
||||
Date: unpublished
|
||||
Timestamp: 2025-11-05T10:00:00-08:00
|
||||
Tags:
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
entries = Pressa::Drafts::Repo.new(dir:).read_entries
|
||||
|
||||
assert_equal(1, entries.length)
|
||||
entry = entries.first
|
||||
assert_equal("powder-day", entry.slug)
|
||||
assert_equal("Powder Day at Baker", entry.title)
|
||||
assert_equal("/drafts/powder-day/", entry.path)
|
||||
assert_equal(2025, entry.timestamp.year)
|
||||
end
|
||||
end
|
||||
|
||||
def test_read_entries_handles_legacy_unix_timestamps
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, "old-draft.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: Greg Graffin
|
||||
Title: Old Draft
|
||||
Date: unpublished
|
||||
Timestamp: 1435424525
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
entry = Pressa::Drafts::Repo.new(dir:).read_entries.first
|
||||
|
||||
assert_equal(2015, entry.timestamp.year)
|
||||
end
|
||||
end
|
||||
|
||||
def test_read_entries_sorts_newest_first
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, "older.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: Fat Mike
|
||||
Title: Older Draft
|
||||
Date: unpublished
|
||||
Timestamp: 2024-01-01T00:00:00-08:00
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
File.write(File.join(dir, "newer.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: El Hefe
|
||||
Title: Newer Draft
|
||||
Date: unpublished
|
||||
Timestamp: 2025-01-01T00:00:00-08:00
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
entries = Pressa::Drafts::Repo.new(dir:).read_entries
|
||||
|
||||
assert_equal(["Newer Draft", "Older Draft"], entries.map(&:title))
|
||||
end
|
||||
end
|
||||
|
||||
def test_read_entries_skips_files_without_front_matter
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, "no-front-matter.md"), "# Just a heading\n")
|
||||
|
||||
entries = Pressa::Drafts::Repo.new(dir:).read_entries
|
||||
|
||||
assert_empty(entries)
|
||||
end
|
||||
end
|
||||
end
|
||||
38
test/drafts/writer_test.rb
Normal file
38
test/drafts/writer_test.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
class Pressa::Drafts::WriterTest < Minitest::Test
|
||||
def site
|
||||
@site ||= Pressa::Site.new(
|
||||
author: "Sami Samhuri",
|
||||
email: "sami@samhuri.net",
|
||||
title: "samhuri.net",
|
||||
description: "blog",
|
||||
url: "https://samhuri.net"
|
||||
)
|
||||
end
|
||||
|
||||
def entries
|
||||
@entries ||= [
|
||||
Pressa::Drafts::Entry.new(
|
||||
slug: "powder-day",
|
||||
title: "Powder Day at Baker",
|
||||
timestamp: DateTime.parse("2025-11-05T10:00:00-08:00"),
|
||||
path: "/drafts/powder-day/"
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
def test_write_index_writes_drafts_index_page
|
||||
Dir.mktmpdir do |dir|
|
||||
Pressa::Drafts::Writer.new(site:, entries:).write_index(target_path: dir)
|
||||
|
||||
index_path = File.join(dir, "drafts", "index.html")
|
||||
assert(File.exist?(index_path))
|
||||
html = File.read(index_path)
|
||||
assert_includes(html, "Drafts")
|
||||
assert_includes(html, "Powder Day at Baker")
|
||||
assert_includes(html, "/drafts/powder-day/")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -176,6 +176,64 @@ class Pressa::SiteGeneratorRenderingTest < Minitest::Test
|
|||
end
|
||||
end
|
||||
|
||||
def test_generate_writes_drafts_index_for_html_output
|
||||
Dir.mktmpdir do |root|
|
||||
source_path = File.join(root, "source")
|
||||
target_path = File.join(root, "target")
|
||||
drafts_dir = File.join(source_path, "public", "drafts")
|
||||
FileUtils.mkdir_p(drafts_dir)
|
||||
|
||||
File.write(File.join(drafts_dir, "powder-day.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: Shaun White
|
||||
Title: Powder Day at Baker
|
||||
Date: unpublished
|
||||
Timestamp: 2025-11-05T10:00:00-08:00
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
plugin = PluginSpy.new
|
||||
renderer = MarkdownRendererSpy.new
|
||||
site = build_site(plugin:, renderer:)
|
||||
|
||||
Pressa::SiteGenerator.new(site:).generate(source_path:, target_path:)
|
||||
|
||||
index_path = File.join(target_path, "drafts", "index.html")
|
||||
assert(File.exist?(index_path))
|
||||
assert_includes(File.read(index_path), "Powder Day at Baker")
|
||||
end
|
||||
end
|
||||
|
||||
def test_generate_skips_drafts_index_for_gemini_output
|
||||
Dir.mktmpdir do |root|
|
||||
source_path = File.join(root, "source")
|
||||
target_path = File.join(root, "target")
|
||||
drafts_dir = File.join(source_path, "public", "drafts")
|
||||
FileUtils.mkdir_p(drafts_dir)
|
||||
|
||||
File.write(File.join(drafts_dir, "powder-day.md"), <<~MARKDOWN)
|
||||
---
|
||||
Author: Shaun White
|
||||
Title: Powder Day at Baker
|
||||
Date: unpublished
|
||||
Timestamp: 2025-11-05T10:00:00-08:00
|
||||
---
|
||||
|
||||
TKTK
|
||||
MARKDOWN
|
||||
|
||||
plugin = PluginSpy.new
|
||||
renderer = MarkdownRendererSpy.new
|
||||
site = build_gemini_site(plugin:, renderer:)
|
||||
|
||||
Pressa::SiteGenerator.new(site:).generate(source_path:, target_path:)
|
||||
|
||||
refute(File.exist?(File.join(target_path, "drafts", "index.html")))
|
||||
end
|
||||
end
|
||||
|
||||
def test_generate_skips_tweets_directory_for_gemini_output
|
||||
Dir.mktmpdir do |root|
|
||||
source_path = File.join(root, "source")
|
||||
|
|
|
|||
Loading…
Reference in a new issue