syntax highlighting and blog/post liveviews working; numerous other styling updates

This commit is contained in:
Adam Piontek 2021-04-05 16:51:59 -04:00
parent a6186ba639
commit 2218a678b1
35 changed files with 610 additions and 120 deletions

View file

@ -8,7 +8,9 @@ defmodule Home73k do
"""
@app_vars Application.compile_env(:home73k, :app_global_vars, time_zone: "America/New_York")
@app_time_zone @app_vars[:time_zone]
def app_time_zone, do: @app_time_zone
def app_vars, do: @app_vars
def app_time_zone, do: @app_vars[:time_zone]
def app_blog_content, do: @app_vars[:blog_content]
def app_pygmentize_bin, do: @app_vars[:pygmentize_bin]
end

View file

@ -3,7 +3,7 @@ defmodule Home73k.Blog do
Application.ensure_all_started(:earmark)
posts_paths = "priv/content/**/*.md" |> Path.wildcard()
posts_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
posts =
for post_path <- posts_paths do
@ -11,7 +11,7 @@ defmodule Home73k.Blog do
Post.parse!(post_path)
end
@posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime})
@posts Enum.sort_by(posts, & &1.date, {:desc, Date})
@tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()

View file

@ -1,4 +1,6 @@
defmodule Home73k.Blog.Post do
alias Home73k.Highlighter
@enforce_keys [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
defstruct [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
@ -59,12 +61,10 @@ defmodule Home73k.Blog.Post do
defp parse_lede(_), do: nil
# """ parse_body/1
# Convert body markdown to html
# TODO: handle syntax highlighting
# Convert body markdown to html, and highlight code fence blocks
defp parse_body({fm, md}) do
Map.put(fm, :body, Earmark.as_html!(md))
# TODO: Earmark.as_ast(md) |> parse_body(fm)
# def parse_body({:ok, ast, _}, fm)
html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks()
Map.put(fm, :body, html)
end
defp parse_body(_), do: nil
@ -128,8 +128,10 @@ defmodule Home73k.Blog.Post do
# Handle split of post body. If lede found, return as html with body.
# Otherwise return nil with body.
# """
defp extract_lede([lede, body]),
do: {String.trim_trailing(lede) |> Earmark.as_html!(), String.trim_leading(body)}
defp extract_lede([lede, body]) do
lede_html = String.trim_trailing(lede) |> Earmark.as_html!() |> Highlighter.highlight_code_blocks()
{lede_html, String.trim_leading(body)}
end
defp extract_lede([body]), do: {nil, body}

View file

@ -0,0 +1,52 @@
defmodule Home73k.Highlighter do
@moduledoc """
Performs code highlighting.
"""
alias Home73k.Temp
@pygments_cmd Home73k.app_pygmentize_bin() |> Path.expand()
@doc """
Highlights all code block in an already generated HTML document.
"""
def highlight_code_blocks(html) do
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|> Regex.replace(html, &highlight_code_block(&1, &2, &3))
end
defp highlight_code_block(_full_block, lang, code) do
# unescape the code
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
# write code to temp file
tmp_file = Temp.file()
File.write!(tmp_file, unescaped_code)
# pygmentize the code via temp file
pyg_args = ["-l", lang, "-f", "html", "-O", "cssclass=pygments", tmp_file]
{highlighted, _} = System.cmd(@pygments_cmd, pyg_args)
# correct pygment wrapping markup
highlighted
|> String.replace("<span></span>", "")
|> String.replace("<div class=\"pygments\"><pre>", "<pre class=\"pygments\"><code class=\"language-#{lang}\">")
|> String.replace("</pre></div>", "</code></pre>")
end
entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]
for {encoded, decoded} <- entities do
defp unescape_html(unquote(encoded) <> rest) do
[unquote(decoded) | unescape_html(rest)]
end
end
defp unescape_html(<<c, rest::binary>>) do
[c | unescape_html(rest)]
end
defp unescape_html(<<>>) do
[]
end
end

19
lib/home73k/temp.ex Normal file
View file

@ -0,0 +1,19 @@
defmodule Home73k.Temp do
@moduledoc """
Simple module to generate temporary files
"""
def file do
System.tmp_dir!()
|> Path.join(random_filename())
|> touch_file()
end
defp random_filename do
:crypto.strong_rand_bytes(32) |> Base.url_encode64 |> binary_part(0, 32)
end
defp touch_file(fdname) do
File.touch!(fdname)
fdname
end
end

View file

@ -93,6 +93,9 @@ defmodule Home73kWeb do
# Import SVG Icon helper
import Home73kWeb.IconHelpers
# Import Date formatter helper
import Home73kWeb.DateHelpers
import Home73kWeb.ErrorHelpers
import Home73kWeb.Gettext
alias Home73kWeb.Router.Helpers, as: Routes

View file

@ -48,7 +48,4 @@ defmodule Home73kWeb.BlogLive do
# do: {app, vsn}
# end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end

View file

@ -1,22 +1,22 @@
<main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 pb-2 mb-4 mt-3">
<div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
<%= for post <- @posts do %>
<div class="post border-bottom border-gray pb-4 mb-3">
<h2 class="post-title fs-2 fw-normal mb-2">
<h2 class="post-title fs-2 fw-600 mb-2">
<%= live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %>
</h2>
<div class="post-date font-monospace text-gray-500 <%= if length(post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
<%= post.date |> format_date() %>
<div class="post-date font-monospace text-gray-400 <%= if length(post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= post.date |> format_date() %>
by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
</div>
<%= if length(post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3">
<div class="post-tags fs-smaller mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(post.tags) do %>
#<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %>
@ -24,7 +24,7 @@
</div>
<% end %>
<div class="post-lede lead">
<div class="post-lede">
<%= raw post.lede %>
</div>

View file

@ -55,7 +55,4 @@ defmodule Home73kWeb.PostLive do
# do: {app, vsn}
# end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end

View file

@ -6,13 +6,13 @@
<h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2>
<div class="post-date font-monospace text-gray-500 <%= if length(@post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
<%= @post.date |> format_date() %>
<div class="post-date font-monospace text-gray-400 <%= if length(@post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= @post.date |> format_date() %>
by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
</div>
<%= if length(@post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3">
<div class="post-tags fs-smaller mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(@post.tags) do %>
#<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
@ -20,7 +20,7 @@
</div>
<% end %>
<div class="post-lede lead">
<div class="post-lede">
<%= raw @post.lede %>
</div>

View file

@ -1,6 +1,6 @@
<main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 border-bottom border-gray pb-3 mb-5 mt-3">
<div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 border-bottom border-gray pb-3 mb-5 mt-3">
<h2 class="fs-2 fw-600 mb-0">About</h2>

View file

@ -13,7 +13,7 @@
<div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5 col-xxl-4 justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account-hard-hat", [class: "icon baseline me-2"] %><span>Working on it!</span>
</h2>

View file

@ -13,7 +13,7 @@
<div class="col-auto justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2>

View file

@ -4,7 +4,7 @@
<div class="col-auto justify-content-start">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2>

View file

@ -0,0 +1,9 @@
defmodule Home73kWeb.DateHelpers do
@moduledoc """
Formatters for dates
"""
def format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end