feat: add scheduled publishing with publish_at frontmatter field
Posts with a future publish_at date are hidden from the public API and feeds, same as drafts. The admin form exposes a Publish at field.
This commit is contained in:
@@ -56,6 +56,24 @@ module Volumen
|
||||
metadata["cover"]
|
||||
end
|
||||
|
||||
def publish_at
|
||||
metadata["publish_at"]
|
||||
end
|
||||
|
||||
def scheduled?
|
||||
value = publish_at
|
||||
return false if value.nil?
|
||||
|
||||
date = case value
|
||||
when Date then value
|
||||
when Time then value.to_date
|
||||
else Date.iso8601(value.to_s)
|
||||
end
|
||||
date > Date.today
|
||||
rescue ArgumentError
|
||||
false
|
||||
end
|
||||
|
||||
def reading_time
|
||||
words = body.split(/\s+/).count { |word| !word.empty? }
|
||||
[(words / 200.0).ceil, 1].max
|
||||
|
||||
@@ -79,7 +79,8 @@ module Volumen
|
||||
end
|
||||
|
||||
def published_posts
|
||||
store.all.reject(&:draft?).sort_by { |post| post.date_string || "" }.reverse
|
||||
store.all.reject { |post| post.draft? || post.scheduled? }
|
||||
.sort_by { |post| post.date_string || "" }.reverse
|
||||
end
|
||||
|
||||
def site_payload
|
||||
@@ -369,6 +370,7 @@ module Volumen
|
||||
"lang" => presence(http_params["lang"]),
|
||||
"author" => presence(http_params["author"]),
|
||||
"date" => parse_date(http_params["date"]),
|
||||
"publish_at" => parse_date(http_params["publish_at"]),
|
||||
"tags" => parse_tags(http_params["tags"]),
|
||||
"excerpt" => presence(http_params["excerpt"]),
|
||||
"cover" => presence(http_params["cover"]),
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<label>Language <input type="text" name="lang" value="<%= h(@post.lang) %>"></label>
|
||||
<label>Author <input type="text" name="author" value="<%= h(@post.author) %>"></label>
|
||||
<label>Date <input type="date" name="date" value="<%= h(@post.date_string) %>"></label>
|
||||
<label>Publish at
|
||||
<input type="date" name="publish_at"
|
||||
value="<%= @post.publish_at.is_a?(Date) ? @post.publish_at.strftime("%Y-%m-%d") : "" %>"
|
||||
placeholder="leave empty for immediate">
|
||||
</label>
|
||||
<label>Tags
|
||||
<input type="text" name="tags" value="<%= h(@post.tags.join(", ")) %>"
|
||||
placeholder="comma, separated">
|
||||
|
||||
@@ -62,4 +62,19 @@ class PostTest < Minitest::Test
|
||||
post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "short")
|
||||
assert_equal 1, post.reading_time
|
||||
end
|
||||
|
||||
def test_scheduled_post_is_not_scheduled_when_publish_at_is_past
|
||||
post = Volumen::Post.new(metadata: { "publish_at" => Date.today - 1 })
|
||||
refute_predicate post, :scheduled?
|
||||
end
|
||||
|
||||
def test_scheduled_post_is_scheduled_when_publish_at_is_future
|
||||
post = Volumen::Post.new(metadata: { "publish_at" => Date.today + 7 })
|
||||
assert_predicate post, :scheduled?
|
||||
end
|
||||
|
||||
def test_post_without_publish_at_is_not_scheduled
|
||||
post = Volumen::Post.new(metadata: {})
|
||||
refute_predicate post, :scheduled?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -126,4 +126,19 @@ class ServerTest < Minitest::Test
|
||||
assert_equal "*", last_response.headers["Access-Control-Allow-Origin"]
|
||||
assert_equal "GET, OPTIONS", last_response.headers["Access-Control-Allow-Methods"]
|
||||
end
|
||||
|
||||
def test_scheduled_post_is_hidden
|
||||
future = (Date.today + 30).strftime("%Y-%m-%d")
|
||||
File.write(File.join(@dir, "later.md"), <<~POST)
|
||||
+++
|
||||
title = "Later"
|
||||
publish_at = #{future}
|
||||
+++
|
||||
|
||||
Not yet.
|
||||
POST
|
||||
get "/api/volumen/posts"
|
||||
slugs = JSON.parse(last_response.body)["posts"].map { |post| post["slug"] }
|
||||
refute_includes slugs, "later"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user