fixes, styling improvement including inlined code
This commit is contained in:
parent
97f9c4a9f2
commit
816fdbb484
5 changed files with 121 additions and 27 deletions
|
@ -63,7 +63,7 @@ defmodule Home73k.Blog.Post do
|
||||||
# """ parse_body/1
|
# """ parse_body/1
|
||||||
# Convert body markdown to html, and highlight code fence blocks
|
# Convert body markdown to html, and highlight code fence blocks
|
||||||
defp parse_body({fm, md}) do
|
defp parse_body({fm, md}) do
|
||||||
html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks()
|
html = Earmark.as_html!(md) |> Highlighter.highlight_all()
|
||||||
Map.put(fm, :body, html)
|
Map.put(fm, :body, html)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,14 @@ defmodule Home73k.Highlighter do
|
||||||
@chroma_bin Home73k.app_chroma_bin() |> Path.expand()
|
@chroma_bin Home73k.app_chroma_bin() |> Path.expand()
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Highlights all code block in an already generated HTML document.
|
Highlights all code in HTML (fenced code blocks and inlined code)
|
||||||
|
"""
|
||||||
|
def highlight_all(html) do
|
||||||
|
highlight_code_blocks(html) |> highlight_code_inlined()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Highlights fenced code blocks in HTML document
|
||||||
"""
|
"""
|
||||||
def highlight_code_blocks(html) do
|
def highlight_code_blocks(html) do
|
||||||
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|
||||||
|
@ -16,6 +23,31 @@ defmodule Home73k.Highlighter do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp highlight_code_block(_full_block, lang, code) do
|
defp highlight_code_block(_full_block, lang, code) do
|
||||||
|
# perform the code highlighting
|
||||||
|
highlighted = highlight_code(lang, code)
|
||||||
|
# return properly wrapped highlighted code
|
||||||
|
~s(<pre class="chroma"><code class="language-#{lang}">#{highlighted}</code></pre>)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Highlights inlined code in HTML document
|
||||||
|
"""
|
||||||
|
def highlight_code_inlined(html) do
|
||||||
|
~r/<code(?:\s+class="inline lang-(\w*)")?>([^<]*)<\/code>/
|
||||||
|
|> Regex.replace(html, &highlight_code_inline(&1, &2, &3))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp highlight_code_inline(_full_block, lang, code) do
|
||||||
|
# perform the code highlighting
|
||||||
|
highlighted = highlight_code(lang, code)
|
||||||
|
# return properly wrapped highlighted code
|
||||||
|
~s(<code class="inline chroma language-#{lang}">#{highlighted}</code>)
|
||||||
|
end
|
||||||
|
|
||||||
|
# """
|
||||||
|
# Performs code highlighting using chroma
|
||||||
|
# """
|
||||||
|
defp highlight_code(lang, code) do
|
||||||
# unescape the code
|
# unescape the code
|
||||||
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
|
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
|
||||||
|
|
||||||
|
@ -25,12 +57,12 @@ defmodule Home73k.Highlighter do
|
||||||
|
|
||||||
# use chroma to highlight the code via temp file
|
# use chroma to highlight the code via temp file
|
||||||
bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file]
|
bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file]
|
||||||
{highlighted, _} = System.cmd(@chroma_bin, bin_args)
|
System.cmd(@chroma_bin, bin_args) |> elem(0)
|
||||||
|
|
||||||
# return properly wrapped highlighted code
|
|
||||||
~s(<pre class="chroma"><code class="language-#{lang}">#{highlighted}</code></pre>)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# """
|
||||||
|
# Code below for unescaping html, since it's escaped by Earmark markdown parsing
|
||||||
|
# """
|
||||||
entities = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}]
|
entities = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}]
|
||||||
|
|
||||||
for {encoded, decoded} <- entities do
|
for {encoded, decoded} <- entities do
|
||||||
|
|
|
@ -60,7 +60,7 @@ defmodule Home73kWeb.BlogLive do
|
||||||
defp init_per_live_action(:show, socket, %{"id" => id}) do
|
defp init_per_live_action(:show, socket, %{"id" => id}) do
|
||||||
post = Blog.get_post_by_id!(id)
|
post = Blog.get_post_by_id!(id)
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, "Blog \\ post.title")
|
|> assign(:page_title, "Blog \\ #{post.title}")
|
||||||
|> assign(:posts, [post])
|
|> assign(:posts, [post])
|
||||||
|> assign(:page_count, nil)
|
|> assign(:page_count, nil)
|
||||||
|> assign_prev_next(0)
|
|> assign_prev_next(0)
|
||||||
|
|
|
@ -35,7 +35,7 @@ I also whitelisted several relevant domains from both the bottom of the Big Bloc
|
||||||
|
|
||||||
We found my partner couldn't use a website related to her profession. It would just get stuck loading and it wasn't clear why. With Chrome (can do this in Firefox, too) I was able to use F12 to open the DevTools, click Network, reload the page, and see in red what was failing to load.
|
We found my partner couldn't use a website related to her profession. It would just get stuck loading and it wasn't clear why. With Chrome (can do this in Firefox, too) I was able to use F12 to open the DevTools, click Network, reload the page, and see in red what was failing to load.
|
||||||
|
|
||||||
In this case, the Pi-hole was blocking "cdns.gigya.com," a content delivery service hosting a javascript the page wanted to use. Off to the Pi-hole web interface (or cli with `pihole -w`) to quickly whitelist, and the site worked again.
|
In this case, the Pi-hole was blocking "cdns.gigya.com," a content delivery service hosting a javascript the page wanted to use. Off to the Pi-hole web interface (or cli with ``pihole -w``{:.lang-shell}) to quickly whitelist, and the site worked again.
|
||||||
|
|
||||||
The same for "prod.imgur.map.fastlylb.net," apparently needed for imgur to work.
|
The same for "prod.imgur.map.fastlylb.net," apparently needed for imgur to work.
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,9 @@ I set to incorporating my blog posts and previous static webpack-based design in
|
||||||
|
|
||||||
For one, as much as I love Elxiir, the only server-side syntax-highlighting package out there doesn't support enough languages for my needs. I didn't like the idea of using client-side javascript, so I decided to leverage [Chroma's cli](https://github.com/alecthomas/chroma#command-line-interface). Hooking elixir's System.cmd/3 in with a trimmed & modified version of [the ExDoc syntax highlighter code](https://github.com/elixir-lang/ex_doc/blob/d5cde30f55c7e0cde486ec3878067aee82ccc924/lib/ex_doc/highlighter.ex), after parsing the markdown to html, I'm able to isolate all fenced code in a post, write each code fragment to a temp file, apply highlight classes via chroma, and replace the code in the post.
|
For one, as much as I love Elxiir, the only server-side syntax-highlighting package out there doesn't support enough languages for my needs. I didn't like the idea of using client-side javascript, so I decided to leverage [Chroma's cli](https://github.com/alecthomas/chroma#command-line-interface). Hooking elixir's System.cmd/3 in with a trimmed & modified version of [the ExDoc syntax highlighter code](https://github.com/elixir-lang/ex_doc/blob/d5cde30f55c7e0cde486ec3878067aee82ccc924/lib/ex_doc/highlighter.ex), after parsing the markdown to html, I'm able to isolate all fenced code in a post, write each code fragment to a temp file, apply highlight classes via chroma, and replace the code in the post.
|
||||||
|
|
||||||
*(As an aside, I at first tried using [Pygments' `pygmentize` cli](https://pygments.org/docs/cmdline/), which worked fine, but I realized it didn't seem to support vue templates, and I do some work with vue. At some point it might be fun to try to figure out how to implement the lexers I need in the elixir native project [makeup](https://github.com/elixir-makeup/makeup) but that's a big task to tackle some other day!)*
|
*(As an aside, I at first tried using the [Pygments cli](https://pygments.org/docs/cmdline/), which worked fine, but I realized it didn't seem to support vue templates, and I do some work with vue. At some point it might be fun to try to figure out how to implement the lexers I need in the elixir native project [makeup](https://github.com/elixir-makeup/makeup) but that's a big task to tackle some other day!)*
|
||||||
|
|
||||||
|
Incidentally, [Earmark](https://github.com/pragdave/earmark)'s markdown parsing includes support for setting the language for inline code with a braced suffix --- e.g., `` `console.log('test');`{:.lang-javascript} ``{:.lang-markdown} --- so my highlighter code below includes support for such code elements.
|
||||||
|
|
||||||
I also found code hot-reloading wasn't working great if I added or removed a file, even if I canceled all elixir processes and started it up again. I had to get this fixed because the plan was to deploy new content and other changes via git repository post-receive hook, and while I can script the recompilation, and probably add in a `--force` flag or something, I wanted it cleaner. Plus I just wanted the convenience of writing posts in dev mode with a preview.
|
I also found code hot-reloading wasn't working great if I added or removed a file, even if I canceled all elixir processes and started it up again. I had to get this fixed because the plan was to deploy new content and other changes via git repository post-receive hook, and while I can script the recompilation, and probably add in a `--force` flag or something, I wanted it cleaner. Plus I just wanted the convenience of writing posts in dev mode with a preview.
|
||||||
|
|
||||||
|
@ -35,15 +37,60 @@ end
|
||||||
|
|
||||||
Following that change, new markdown files are recognized and included as expected.
|
Following that change, new markdown files are recognized and included as expected.
|
||||||
|
|
||||||
And, FWIW, here's the meat of my modified highlighter using chroma (NOTE: the CSS styles can be exported separately (like so: `~/go/bin/chroma -s base16-snazzy --html-styles > _chroma.css`), or you can use styles from Pygments, including custom styles like [nord_pygments](https://github.com/sbrisard/nord_pygments)). Once included in your app.scss, if you use purgecss like me, you'll need to add the chroma class (or whatever class you're using) to the safelist for the webpack plugin: `safelist: {greedy: [/phx/, /topbar/, /inline/, /chroma/]}` .)
|
And, FWIW, here's my modified highlighter module using chroma (I've exported the CSS styles separately (like so: `~/go/bin/chroma -s base16-snazzy --html-styles > _chroma.css`{:.lang-shell}) to include in my `app.scss`. If you use purgecss like me, you'll need to add the `.chroma`{:.lang-css} class to the safelist for the webpack plugin: `safelist: {greedy: [/phx/, /topbar/, /inline/, /chroma/]}`{:.lang-javascript} .)
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
|
defmodule Home73k.Highlighter do
|
||||||
|
@moduledoc """
|
||||||
|
Performs code highlighting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Home73k.Temp
|
||||||
|
|
||||||
|
# This refers to a method that gets the chroma binary location from application config
|
||||||
|
@chroma_bin Home73k.app_chroma_bin() |> Path.expand()
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Highlights all code in HTML (fenced code blocks and inlined code)
|
||||||
|
"""
|
||||||
|
def highlight_all(html) do
|
||||||
|
highlight_code_blocks(html) |> highlight_code_inlined()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Highlights fenced code blocks in HTML document
|
||||||
|
"""
|
||||||
def highlight_code_blocks(html) do
|
def highlight_code_blocks(html) do
|
||||||
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|
||||||
|> Regex.replace(html, &highlight_code_block(&1, &2, &3))
|
|> Regex.replace(html, &highlight_code_block(&1, &2, &3))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp highlight_code_block(_full_block, lang, code) do
|
defp highlight_code_block(_full_block, lang, code) do
|
||||||
|
# perform the code highlighting
|
||||||
|
highlighted = highlight_code(lang, code)
|
||||||
|
# return properly wrapped highlighted code
|
||||||
|
~s(<pre class="chroma"><code class="language-#{lang}">#{highlighted}</code></pre>)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Highlights inlined code in HTML document
|
||||||
|
"""
|
||||||
|
def highlight_code_inlined(html) do
|
||||||
|
~r/<code(?:\s+class="inline lang-(\w*)")?>([^<]*)<\/code>/
|
||||||
|
|> Regex.replace(html, &highlight_code_inline(&1, &2, &3))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp highlight_code_inline(_full_block, lang, code) do
|
||||||
|
# perform the code highlighting
|
||||||
|
highlighted = highlight_code(lang, code)
|
||||||
|
# return properly wrapped highlighted code
|
||||||
|
~s(<code class="inline chroma language-#{lang}">#{highlighted}</code>)
|
||||||
|
end
|
||||||
|
|
||||||
|
# """
|
||||||
|
# Performs code highlighting using chroma
|
||||||
|
# """
|
||||||
|
defp highlight_code(lang, code) do
|
||||||
# unescape the code
|
# unescape the code
|
||||||
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
|
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
|
||||||
|
|
||||||
|
@ -53,11 +100,26 @@ defp highlight_code_block(_full_block, lang, code) do
|
||||||
|
|
||||||
# use chroma to highlight the code via temp file
|
# use chroma to highlight the code via temp file
|
||||||
bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file]
|
bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file]
|
||||||
# The '@chroma_bin' module attribute retrieves the configured
|
System.cmd(@chroma_bin, bin_args) |> elem(0)
|
||||||
# location of the chroma cli binary from the application config.
|
end
|
||||||
{highlighted, _} = System.cmd(@chroma_bin, bin_args)
|
|
||||||
|
|
||||||
# return properly wrapped highlighted code
|
# """
|
||||||
~s(<pre class="chroma"><code class="language-#{lang}">#{highlighted}</code></pre>)
|
# Code below for unescaping html, since it's escaped by Earmark markdown parsing
|
||||||
|
# """
|
||||||
|
entities = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}]
|
||||||
|
|
||||||
|
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
|
end
|
||||||
```
|
```
|
Loading…
Reference in a new issue