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.
138 lines
2.7 KiB
Ruby
138 lines
2.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "date"
|
|
require_relative "frontmatter"
|
|
require_relative "markdown"
|
|
|
|
module Volumen
|
|
# A single blog post: TOML frontmatter metadata plus a Markdown body.
|
|
class Post
|
|
attr_reader :metadata, :body
|
|
attr_accessor :path
|
|
|
|
def initialize(metadata: {}, body: "")
|
|
@metadata = (metadata || {}).transform_keys(&:to_s)
|
|
@body = body.to_s
|
|
end
|
|
|
|
def self.parse(content)
|
|
metadata, body = Frontmatter.parse(content)
|
|
new(metadata: metadata, body: body)
|
|
end
|
|
|
|
def slug
|
|
metadata["slug"]
|
|
end
|
|
|
|
def title
|
|
metadata["title"]
|
|
end
|
|
|
|
def lang
|
|
metadata["lang"]
|
|
end
|
|
|
|
def author
|
|
metadata["author"]
|
|
end
|
|
|
|
def tags
|
|
Array(metadata["tags"]).map(&:to_s)
|
|
end
|
|
|
|
def draft?
|
|
metadata["draft"] == true
|
|
end
|
|
|
|
def all_langs?
|
|
metadata["all_langs"] == true
|
|
end
|
|
|
|
def translations
|
|
metadata["translations"] || {}
|
|
end
|
|
|
|
def cover
|
|
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
|
|
end
|
|
|
|
def date_string
|
|
value = metadata["date"]
|
|
case value
|
|
when nil then nil
|
|
when Date, Time then value.strftime("%Y-%m-%d")
|
|
else value.to_s
|
|
end
|
|
end
|
|
|
|
def excerpt
|
|
text = metadata["excerpt"]
|
|
return text if text && !text.to_s.empty?
|
|
|
|
derived_excerpt
|
|
end
|
|
|
|
def html
|
|
Markdown.render(body)
|
|
end
|
|
|
|
def to_file
|
|
Frontmatter.dump(metadata, body)
|
|
end
|
|
|
|
def summary
|
|
{
|
|
"slug" => slug,
|
|
"title" => title,
|
|
"excerpt" => excerpt,
|
|
"date" => date_string,
|
|
"lang" => lang,
|
|
"tags" => tags,
|
|
"author" => author,
|
|
"cover" => cover,
|
|
"reading_time" => reading_time,
|
|
"translations" => translations,
|
|
"url" => "/api/volumen/posts/#{slug}"
|
|
}
|
|
end
|
|
|
|
def detail
|
|
summary.merge("body" => body, "html" => html)
|
|
end
|
|
|
|
private
|
|
|
|
def derived_excerpt(limit = 200)
|
|
paragraph = body.split(/\n\s*\n/).map(&:strip)
|
|
.find { |para| !para.empty? && !para.start_with?("#") }
|
|
return "" if paragraph.nil?
|
|
|
|
stripped = paragraph.gsub(/<[^>]+>/, "").gsub(/[*_`>#]/, "").strip
|
|
stripped.length > limit ? "#{stripped[0, limit].rstrip}…" : stripped
|
|
end
|
|
end
|
|
end
|