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"]
|
metadata["cover"]
|
||||||
end
|
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
|
def reading_time
|
||||||
words = body.split(/\s+/).count { |word| !word.empty? }
|
words = body.split(/\s+/).count { |word| !word.empty? }
|
||||||
[(words / 200.0).ceil, 1].max
|
[(words / 200.0).ceil, 1].max
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ module Volumen
|
|||||||
end
|
end
|
||||||
|
|
||||||
def published_posts
|
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
|
end
|
||||||
|
|
||||||
def site_payload
|
def site_payload
|
||||||
@@ -369,6 +370,7 @@ module Volumen
|
|||||||
"lang" => presence(http_params["lang"]),
|
"lang" => presence(http_params["lang"]),
|
||||||
"author" => presence(http_params["author"]),
|
"author" => presence(http_params["author"]),
|
||||||
"date" => parse_date(http_params["date"]),
|
"date" => parse_date(http_params["date"]),
|
||||||
|
"publish_at" => parse_date(http_params["publish_at"]),
|
||||||
"tags" => parse_tags(http_params["tags"]),
|
"tags" => parse_tags(http_params["tags"]),
|
||||||
"excerpt" => presence(http_params["excerpt"]),
|
"excerpt" => presence(http_params["excerpt"]),
|
||||||
"cover" => presence(http_params["cover"]),
|
"cover" => presence(http_params["cover"]),
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
<label>Language <input type="text" name="lang" value="<%= h(@post.lang) %>"></label>
|
<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>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>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
|
<label>Tags
|
||||||
<input type="text" name="tags" value="<%= h(@post.tags.join(", ")) %>"
|
<input type="text" name="tags" value="<%= h(@post.tags.join(", ")) %>"
|
||||||
placeholder="comma, separated">
|
placeholder="comma, separated">
|
||||||
|
|||||||
@@ -62,4 +62,19 @@ class PostTest < Minitest::Test
|
|||||||
post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "short")
|
post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "short")
|
||||||
assert_equal 1, post.reading_time
|
assert_equal 1, post.reading_time
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -126,4 +126,19 @@ class ServerTest < Minitest::Test
|
|||||||
assert_equal "*", last_response.headers["Access-Control-Allow-Origin"]
|
assert_equal "*", last_response.headers["Access-Control-Allow-Origin"]
|
||||||
assert_equal "GET, OPTIONS", last_response.headers["Access-Control-Allow-Methods"]
|
assert_equal "GET, OPTIONS", last_response.headers["Access-Control-Allow-Methods"]
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user