feat(markdown): wrap titled images in figures with caption
This commit is contained in:
+40
-1
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "kramdown"
|
||||
require "rexml/document"
|
||||
|
||||
module Volumen
|
||||
# Renders Markdown to HTML using kramdown's GFM parser (tables, fenced code,
|
||||
@@ -8,6 +9,10 @@ module Volumen
|
||||
#
|
||||
# Posts are authored by the single trusted admin, so kramdown's default
|
||||
# behaviour of passing raw HTML through is intentional.
|
||||
#
|
||||
# Images with a title (``) are wrapped in a `<figure>`
|
||||
# element with a `<figcaption>` so the frontend can style captions and
|
||||
# overlay an info icon.
|
||||
module Markdown
|
||||
OPTIONS = {
|
||||
input: "GFM",
|
||||
@@ -17,7 +22,41 @@ module Volumen
|
||||
}.freeze
|
||||
|
||||
def self.render(text)
|
||||
::Kramdown::Document.new(text.to_s, OPTIONS).to_html
|
||||
html = ::Kramdown::Document.new(text.to_s, OPTIONS).to_html
|
||||
wrap_figures(html)
|
||||
end
|
||||
|
||||
# Wrap every `<img title="...">` inside a `<figure>` with `<figcaption>`.
|
||||
# Images without a title are left untouched.
|
||||
def self.wrap_figures(html)
|
||||
doc = REXML::Document.new("<root>#{html}</root>")
|
||||
|
||||
REXML::XPath.each(doc, "//img[@title]") do |img|
|
||||
caption = img.attributes["title"]
|
||||
img.attributes.delete("title")
|
||||
|
||||
figure = REXML::Element.new("figure")
|
||||
figure.add_element(img.deep_clone)
|
||||
|
||||
info = REXML::Element.new("div")
|
||||
info.add_attribute("class", "fig-info")
|
||||
info.add_attribute("aria-hidden", "true")
|
||||
info.text = "i"
|
||||
figure.add_element(info)
|
||||
|
||||
figcaption = REXML::Element.new("figcaption")
|
||||
figcaption.text = caption
|
||||
figure.add_element(figcaption)
|
||||
|
||||
img.parent.replace_child(img, figure)
|
||||
end
|
||||
|
||||
# Strip the wrapping <root> we added.
|
||||
result = +""
|
||||
doc.root.children.each { |child| result << child.to_s }
|
||||
result
|
||||
rescue REXML::ParseException
|
||||
html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,6 +58,10 @@ module Volumen
|
||||
metadata["cover"]
|
||||
end
|
||||
|
||||
def cover_alt
|
||||
metadata["cover_alt"]
|
||||
end
|
||||
|
||||
def fediverse_creator
|
||||
value = metadata["fediverse_creator"]
|
||||
return nil if value.nil?
|
||||
@@ -128,6 +132,7 @@ module Volumen
|
||||
"author" => author,
|
||||
"fediverse_creator" => fediverse_creator,
|
||||
"cover" => cover,
|
||||
"cover_alt" => cover_alt,
|
||||
"reading_time" => reading_time,
|
||||
"translations" => translations,
|
||||
"url" => "/api/volumen/posts/#{slug}"
|
||||
|
||||
@@ -376,7 +376,14 @@ module Volumen
|
||||
end
|
||||
|
||||
def post_from_params(http_params, existing: nil)
|
||||
metadata = {
|
||||
metadata = base_metadata(http_params, existing: existing)
|
||||
post = Post.new(metadata: clean_metadata(metadata), body: http_params["body"].to_s)
|
||||
post.path = existing.path if existing
|
||||
post
|
||||
end
|
||||
|
||||
def base_metadata(http_params, existing:)
|
||||
{
|
||||
"title" => presence(http_params["title"]),
|
||||
"slug" => presence(http_params["slug"]) || existing&.slug,
|
||||
"lang" => presence(http_params["lang"]),
|
||||
@@ -387,12 +394,10 @@ module Volumen
|
||||
"tags" => parse_tags(http_params["tags"]),
|
||||
"excerpt" => presence(http_params["excerpt"]),
|
||||
"cover" => presence(http_params["cover"]),
|
||||
"cover_alt" => presence(http_params["cover_alt"]),
|
||||
"draft" => http_params["draft"] == "on",
|
||||
"all_langs" => http_params["all_langs"] == "on"
|
||||
}
|
||||
post = Post.new(metadata: clean_metadata(metadata), body: http_params["body"].to_s)
|
||||
post.path = existing.path if existing
|
||||
post
|
||||
end
|
||||
|
||||
def fediverse_creator_error(post)
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
<button type="button" id="cover-upload">Upload</button>
|
||||
<button type="button" id="cover-clear">Clear</button>
|
||||
</div>
|
||||
<input type="text" id="cover-alt-input" name="cover_alt" value="<%= h(@post.cover_alt) %>"
|
||||
placeholder="ALT text (describe the image for accessibility)">
|
||||
<img id="cover-preview" class="cover-preview" alt="" hidden>
|
||||
<input type="file" id="cover-file" accept="image/*" hidden>
|
||||
</div>
|
||||
@@ -149,7 +151,10 @@
|
||||
if (tag === "code") { return "`" + node.textContent + "`"; }
|
||||
if (tag === "a") { return "[" + inner + "](" + (node.getAttribute("href") || "") + ")"; }
|
||||
if (tag === "img") {
|
||||
return " || "") + ")";
|
||||
var title = node.getAttribute("title") || "";
|
||||
var imgMd = " || "") + ")";
|
||||
if (title) { imgMd = imgMd.replace(")", ' "' + title + '")'); }
|
||||
return imgMd;
|
||||
}
|
||||
if (tag === "br") { return "\n"; }
|
||||
return inner;
|
||||
|
||||
Reference in New Issue
Block a user