Syntax highlighting (#15)

* update markdown and org renderers to use chrome highlighting

* add example code blocks

* fix missing nav in default layout

* remove problematic code nowrap

* add language to tutorial samples

* set theme explicitly

* allow setting highlight theme with config

* fix config key for hl

* try to handle both light dark settings gracefully

* fix some problematic css color settings

* fix missing raw template

* fix weird chroma code height artifacts

* improve tabler rendering in mobile

* remove redundant default
This commit is contained in:
Facundo Olano 2024-02-27 08:45:29 -03:00 committed by GitHub
parent 924e4629b2
commit e344dcb6ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 260 additions and 68 deletions

View file

@ -1,6 +1,7 @@
--- ---
layout: base layout: base
--- ---
{% include nav.html %}
<div class="content layout-{{ page.layout }}" lang="{{ page.lang | default:site.config.lang | default:'en' }}"> <div class="content layout-{{ page.layout }}" lang="{{ page.lang | default:site.config.lang | default:'en' }}">
<header {% if page.cover-img %}class="with-cover"{% endif %}> <header {% if page.cover-img %}class="with-cover"{% endif %}>

View file

@ -60,9 +60,14 @@ a:hover {
} }
a.title { a.title {
color: unset!important; color: black;
padding-right: .25rem; padding-right: .25rem;
} }
@media (prefers-color-scheme: dark) {
a.title {
color: white;
}
}
.footer, .footer a, .date, .tags, .tags a { .footer, .footer a, .date, .tags, .tags a {
color: silver; color: silver;
@ -129,7 +134,8 @@ img.cover-img {
margin-top: -0.5rem; margin-top: -0.5rem;
} }
.src pre { pre {
font-size: 1rem;
overflow-x: auto; overflow-x: auto;
line-height: 1.5; line-height: 1.5;
padding: 1rem; padding: 1rem;
@ -137,21 +143,52 @@ img.cover-img {
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.src pre, code {
background: whitesmoke;
font-size: 1rem;
hyphens: none;
}
code {
white-space: nowrap;
}
/* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */
.src pre::-webkit-scrollbar { .src pre::-webkit-scrollbar {
display: none; display: none;
} }
/*
hack to fix height weirdness in code snippets
https://github.com/alecthomas/chroma/issues/617#issuecomment-1063251066
*/
code > span[style^="display:flex;"] {
display: inline !important;
}
/* There are several code rendering scenarios that need to be handled reasonably:
- light vs dark preferred color scheme
- pre code blocks vs inline code spans
- language set vs no language set
- markdown vs org rendering
Need to decide between:
- fixing the color scheme of the page + using one of chroma's themes
- supporting both light/dark and disabling syntax highlighting altogether
- supporting light/dark, picking a theme and patching it to render decently
with both preferences (as done below, assuming chroma's github theme)
*/
.layout-post :not(pre) > code {
padding: .2em .4em;
background-color: whitesmoke!important;
color: black;
}
@media (prefers-color-scheme: dark) {
.layout-post :not(pre) > code {
background-color: rgba(110, 118, 129, 0.4)!important;
color: white;
}
}
.layout-post pre, .layout-post code {
border-radius: 6px;
background-color: whitesmoke!important;
font-size: 1rem;
hyphens: none;
color: black;
}
blockquote { blockquote {
border-left: 2px solid whitesmoke; border-left: 2px solid whitesmoke;
padding-left: 1rem; padding-left: 1rem;
@ -208,6 +245,10 @@ table tr {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
@media screen and (max-width: 480px) {
td { display: inline-block }
}
td, th { td, th {
padding: .5rem; padding: .5rem;
} }

View file

@ -10,4 +10,16 @@ layout: post
For the record, even though it has *org* in the name, jorge can also render markdown, For the record, even though it has *org* in the name, jorge can also render markdown,
thanks to [goldmark](https://github.com/yuin/goldmark/). thanks to [goldmark](https://github.com/yuin/goldmark/).
Let's look at some code:
``` python
import os
def hello():
print("Hello World!")
os.exit(0)
hello()
```
[Next time](./hello-org), I'll talk about org-mode posts. [Next time](./hello-org), I'll talk about org-mode posts.

View file

@ -15,6 +15,18 @@ As you can see, /italics/ and *bold* render as expected, and you can even use fo
All of this is powered by [[https://github.com/niklasfasching/go-org][go-org]], btw[fn:2]. All of this is powered by [[https://github.com/niklasfasching/go-org][go-org]], btw[fn:2].
Let's look at some code:
#+begin_src python
import os
def hello():
print("Hello World!")
os.exit(0)
hello()
#+end_src
** Notes ** Notes
[fn:1] See? [fn:1] See?

View file

@ -26,9 +26,10 @@ type Config struct {
IncludesDir string IncludesDir string
DataDir string DataDir string
SiteUrl string SiteUrl string
PostFormat string PostFormat string
Lang string Lang string
HighlightTheme string
Minify bool Minify bool
LiveReload bool LiveReload bool
@ -48,18 +49,19 @@ func Load(rootDir string) (*Config, error) {
// TODO allow to disable minify // TODO allow to disable minify
config := &Config{ config := &Config{
RootDir: rootDir, RootDir: rootDir,
SrcDir: filepath.Join(rootDir, "src"), SrcDir: filepath.Join(rootDir, "src"),
TargetDir: filepath.Join(rootDir, "target"), TargetDir: filepath.Join(rootDir, "target"),
LayoutsDir: filepath.Join(rootDir, "layouts"), LayoutsDir: filepath.Join(rootDir, "layouts"),
IncludesDir: filepath.Join(rootDir, "includes"), IncludesDir: filepath.Join(rootDir, "includes"),
DataDir: filepath.Join(rootDir, "data"), DataDir: filepath.Join(rootDir, "data"),
PostFormat: "blog/:title.org", PostFormat: "blog/:title.org",
Lang: "en", Lang: "en",
Minify: true, HighlightTheme: "github",
LiveReload: false, Minify: true,
LinkStatic: false, LiveReload: false,
pageDefaults: map[string]interface{}{}, LinkStatic: false,
pageDefaults: map[string]interface{}{},
} }
// load overrides from config.yml // load overrides from config.yml
@ -79,6 +81,7 @@ func Load(rootDir string) (*Config, error) {
} }
// set user-provided overrides of declared config keys // set user-provided overrides of declared config keys
// TODO less copypasty way of declaring config overrides
if url, found := config.overrides["url"]; found { if url, found := config.overrides["url"]; found {
config.SiteUrl = url.(string) config.SiteUrl = url.(string)
} }
@ -88,6 +91,9 @@ func Load(rootDir string) (*Config, error) {
if format, found := config.overrides["lang"]; found { if format, found := config.overrides["lang"]; found {
config.Lang = format.(string) config.Lang = format.(string)
} }
if format, found := config.overrides["highlight_theme"]; found {
config.HighlightTheme = format.(string)
}
return config, nil return config, nil
} }

View file

@ -60,9 +60,14 @@ a:hover {
} }
a.title { a.title {
color: unset!important; color: black;
padding-right: .25rem; padding-right: .25rem;
} }
@media (prefers-color-scheme: dark) {
a.title {
color: white;
}
}
.footer, .footer a, .date, .tags, .tags a { .footer, .footer a, .date, .tags, .tags a {
color: silver; color: silver;
@ -129,7 +134,7 @@ img.cover-img {
margin-top: -0.5rem; margin-top: -0.5rem;
} }
.src pre { pre {
font-size: 1rem; font-size: 1rem;
overflow-x: auto; overflow-x: auto;
line-height: 1.5; line-height: 1.5;
@ -138,21 +143,52 @@ img.cover-img {
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.layout-post .src pre, .layout-post code {
background: whitesmoke;
font-size: 1rem;
hyphens: none;
}
code {
white-space: nowrap;
}
/* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */
.src pre::-webkit-scrollbar { .src pre::-webkit-scrollbar {
display: none; display: none;
} }
/*
hack to fix height weirdness in code snippets
https://github.com/alecthomas/chroma/issues/617#issuecomment-1063251066
*/
code > span[style^="display:flex;"] {
display: inline !important;
}
/* There are several code rendering scenarios that need to be handled reasonably:
- light vs dark preferred color scheme
- pre code blocks vs inline code spans
- language set vs no language set
- markdown vs org rendering
Need to decide between:
- fixing the color scheme of the page + using one of chroma's themes
- supporting both light/dark and disabling syntax highlighting altogether
- supporting light/dark, picking a theme and patching it to render decently
with both preferences (as done below, assuming chroma's github theme)
*/
.layout-post :not(pre) > code {
padding: .2em .4em;
background-color: whitesmoke!important;
color: black
}
@media (prefers-color-scheme: dark) {
.layout-post :not(pre) > code {
background-color: rgba(110, 118, 129, 0.4)!important;
color: white;
}
}
.layout-post pre, .layout-post code {
border-radius: 6px;
background-color: whitesmoke!important;
font-size: 1rem;
hyphens: none;
color: black;
}
blockquote { blockquote {
border-left: 2px solid whitesmoke; border-left: 2px solid whitesmoke;
padding-left: 1rem; padding-left: 1rem;
@ -209,6 +245,11 @@ table tr {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
@media screen and (max-width: 480px) {
td { display: inline-block }
}
td, th { td, th {
padding: .5rem; padding: .5rem;
} }

View file

@ -10,4 +10,16 @@ layout: post
For the record, even though it has *org* in the name, jorge can also render markdown, For the record, even though it has *org* in the name, jorge can also render markdown,
thanks to [goldmark](https://github.com/yuin/goldmark/). thanks to [goldmark](https://github.com/yuin/goldmark/).
Let's look at some code:
``` python
import os
def hello():
print("Hello World!")
os.exit(0)
hello()
```
[Next time](./hello-org), I'll talk about org-mode posts. [Next time](./hello-org), I'll talk about org-mode posts.

View file

@ -15,6 +15,18 @@ As you can see, /italics/ and *bold* render as expected, and you can even use fo
All of this is powered by [[https://github.com/niklasfasching/go-org][go-org]], btw[fn:2]. All of this is powered by [[https://github.com/niklasfasching/go-org][go-org]], btw[fn:2].
Let's look at some code:
#+begin_src python
import os
def hello():
print("Hello World!")
os.exit(0)
hello()
#+end_src
** Notes ** Notes
[fn:1] See? [fn:1] See?

View file

@ -11,7 +11,7 @@ index: 5
So far you've seen how to [[file:jorge-init][start a project]], [[file:jorge-serve][serve it locally]] and [[file:jorge-post][add some content]] to it. The last part of job is to prepare your site for the public, and ~jorge build~ will help with that: So far you've seen how to [[file:jorge-init][start a project]], [[file:jorge-serve][serve it locally]] and [[file:jorge-post][add some content]] to it. The last part of job is to prepare your site for the public, and ~jorge build~ will help with that:
#+begin_src #+begin_src console
$ jorge build $ jorge build
wrote target/2024-02-23-another-kind-of-post.html wrote target/2024-02-23-another-kind-of-post.html
wrote target/blog/my-own-blog-post.html wrote target/blog/my-own-blog-post.html
@ -34,7 +34,7 @@ Once you have your static site rendered, it's just a matter to putting it out in
But for the sake of completenss, this is how this site is deployed: I have a VPS box running Debian Linux and with the [[https://www.nginx.com/][nginx]] server installed on it. I added this configuration to ~/etc/nginx/sites-enabled/jorge~: But for the sake of completenss, this is how this site is deployed: I have a VPS box running Debian Linux and with the [[https://www.nginx.com/][nginx]] server installed on it. I added this configuration to ~/etc/nginx/sites-enabled/jorge~:
#+begin_src #+begin_src nginx
server { server {
charset utf-8; charset utf-8;
root /var/www/jorge; root /var/www/jorge;
@ -52,7 +52,7 @@ I instructed my DNS provider to point ~jorge.olano.dev~ to the box and I ran [[h
I then created the ~/var/www/jorge~ directory in the server, and deployed from my laptop using ~rsync~: I then created the ~/var/www/jorge~ directory in the server, and deployed from my laptop using ~rsync~:
#+begin_src #+begin_src console
$ jorge build $ jorge build
$ rsync -vPrz --delete target/ root@olano.dev:/var/www/jorge $ rsync -vPrz --delete target/ root@olano.dev:/var/www/jorge
#+end_src #+end_src

View file

@ -11,12 +11,11 @@ index: 2
Once jorge is [[file:installation][installed]], you can start a new site with the ~init~ command, specifying a project directory (~myblog~ in the example): Once jorge is [[file:installation][installed]], you can start a new site with the ~init~ command, specifying a project directory (~myblog~ in the example):
#+begin_src bash #+begin_src console
$ jorge init myblog $ jorge init myblog
site name: My Blog > site name: My Blog
site url: https://myblog.example.com > site url: https://myblog.example.com
author: Jorge Blog > author: Jorge Blog
added myblog/config.yml added myblog/config.yml
added myblog/README.md added myblog/README.md
added myblog/.gitignore added myblog/.gitignore

View file

@ -30,14 +30,14 @@ Each website has its own layout so it's hard to predict what you may need to do
From the root directory of your project run ~jorge post "My own blog post"~: From the root directory of your project run ~jorge post "My own blog post"~:
#+begin_src #+begin_src console
$ jorge post "My own blog post" $ jorge post "My own blog post"
added src/blog/my-own-blog-post.org added src/blog/my-own-blog-post.org
#+end_src #+end_src
If you open the new file with your editor, you should see something like this: If you open the new file with your editor, you should see something like this:
#+begin_src #+begin_src org
--- ---
title: My own blog post title: My own blog post
date: 2024-02-23 11:45:30 date: 2024-02-23 11:45:30
@ -65,18 +65,18 @@ With ~jorge serve~ running, you can start filling in some content on this new po
** Customizing the post format ** Customizing the post format
As you may have noticed, the ~jorge post~ command makes a lot of assumptions: where to put it, how to name it, and what format to use. You can control some of these decisions by setting the ~post_format~ configuration key. The default is: As you may have noticed, the ~jorge post~ command makes a lot of assumptions: where to put it, how to name it, and what format to use. You can control some of these decisions by setting the ~post_format~ configuration key. The default is:
#+begin_src #+begin_src yaml
post_format: "blog/:title.org" post_format: "blog/:title.org"
#+end_src #+end_src
Let's say that you want to put posts in the root folder, include the date in them, and use the markdown format by default. Add this key to your ~config.yml~: Let's say that you want to put posts in the root folder, include the date in them, and use the markdown format by default. Add this key to your ~config.yml~:
#+begin_src #+begin_src yaml
post_format: ":year-:month-:day-:title.md" post_format: ":year-:month-:day-:title.md"
#+end_src #+end_src
The, if you add a new post: The, if you add a new post:
#+begin_src #+begin_src console
$ jorge post "Another kind of post" $ jorge post "Another kind of post"
added src/2024-02-23-another-kind-of-post.md added src/2024-02-23-another-kind-of-post.md
#+end_src #+end_src

View file

@ -11,7 +11,7 @@ index: 3
Now that you have some [[file:jorge-init][default files]] in place, let's see how the website looks. Run ~jorge serve~ on the website directory: Now that you have some [[file:jorge-init][default files]] in place, let's see how the website looks. Run ~jorge serve~ on the website directory:
#+begin_src #+begin_src console
$ cd myblog $ cd myblog
$ jorge serve $ jorge serve
building site building site
@ -31,7 +31,7 @@ If you open your browser at http://localhost:4001 you'll see the website we just
If you open ~src/index.html~ in your editor, you should see something roughly matching what you see on the browser: If you open ~src/index.html~ in your editor, you should see something roughly matching what you see on the browser:
#+begin_src #+begin_src html
{% raw %} {% raw %}
--- ---
layout: default layout: default
@ -50,11 +50,13 @@ This file is a [[https://shopify.github.io/][liquid template]] for an HTML file.
2. The rest of the file contents will be rendered according to the liquid template syntax. 2. The rest of the file contents will be rendered according to the liquid template syntax.
3. If it's an org-mode or markdown file, its contents will be converted to their corresponding HTML in the target. 3. If it's an org-mode or markdown file, its contents will be converted to their corresponding HTML in the target.
{% raw %}
In the example above, the ~layout: default~ instructs jorge to embed the index.html rendered output inside the layout defined at ~layouts/default.html~. And the liquid variables expressed by ~{{ site.config.name }}~ and ~{{ site.config.author }}~ will be replaced by the values found at ~config.yml~. In the example above, the ~layout: default~ instructs jorge to embed the index.html rendered output inside the layout defined at ~layouts/default.html~. And the liquid variables expressed by ~{{ site.config.name }}~ and ~{{ site.config.author }}~ will be replaced by the values found at ~config.yml~.
{% endraw %}
If you change the markup in ~src/index.html~, you should see your browser tab refresh automatically to reflect those changes. Try, for instance, changing the title in the first header: If you change the markup in ~src/index.html~, you should see your browser tab refresh automatically to reflect those changes. Try, for instance, changing the title in the first header:
#+begin_src #+begin_src html
<h2><a href="#home" class="title" id="home">Home sweet home</a></h2> <h2><a href="#home" class="title" id="home">Home sweet home</a></h2>
#+end_src #+end_src

3
go.mod
View file

@ -3,6 +3,7 @@ module github.com/facundoolano/jorge
go 1.22.0 go 1.22.0
require ( require (
github.com/alecthomas/chroma/v2 v2.5.0
github.com/alecthomas/kong v0.8.1 github.com/alecthomas/kong v0.8.1
github.com/elliotchance/orderedmap/v2 v2.2.0 github.com/elliotchance/orderedmap/v2 v2.2.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
@ -10,6 +11,7 @@ require (
github.com/osteele/liquid v1.3.2 github.com/osteele/liquid v1.3.2
github.com/tdewolff/minify/v2 v2.20.16 github.com/tdewolff/minify/v2 v2.20.16
github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark v1.7.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
golang.org/x/net v0.0.0-20201224014010-6772e930b67b golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/text v0.3.3 golang.org/x/text v0.3.3
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -17,6 +19,7 @@ require (
) )
require ( require (
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/osteele/tuesday v1.0.3 // indirect github.com/osteele/tuesday v1.0.3 // indirect
github.com/tdewolff/parse/v2 v2.7.11 // indirect github.com/tdewolff/parse/v2 v2.7.11 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.16.0 // indirect

22
go.sum
View file

@ -1,11 +1,19 @@
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=
github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q= github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@ -20,6 +28,8 @@ github.com/osteele/tuesday v1.0.3 h1:SrCmo6sWwSgnvs1bivmXLvD7Ko9+aJvvkmDjB5G4FTU
github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M= github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U= github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U=
@ -29,8 +39,11 @@ github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -44,5 +57,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -9,9 +9,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
"github.com/niklasfasching/go-org/org" "github.com/niklasfasching/go-org/org"
"github.com/osteele/liquid" "github.com/osteele/liquid"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
gm_highlight "github.com/yuin/goldmark-highlighting/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -105,7 +111,7 @@ func (templ Template) Ext() string {
// Renders the liquid template with the given context as bindings. // Renders the liquid template with the given context as bindings.
// If the template source is org or md, convert them to html after the // If the template source is org or md, convert them to html after the
// liquid rendering. // liquid rendering.
func (templ Template) Render(context map[string]interface{}) ([]byte, error) { func (templ Template) Render(context map[string]interface{}, hlTheme string) ([]byte, error) {
// liquid rendering // liquid rendering
content, err := templ.liquidTemplate.Render(context) content, err := templ.liquidTemplate.Render(context)
if err != nil { if err != nil {
@ -121,6 +127,7 @@ func (templ Template) Render(context map[string]interface{}) ([]byte, error) {
// make * -> h1, ** -> h2, etc // make * -> h1, ** -> h2, etc
htmlWriter.TopLevelHLevel = 1 htmlWriter.TopLevelHLevel = 1
htmlWriter.HighlightCodeBlock = highlightCodeBlock(hlTheme)
contentStr, err := doc.Write(htmlWriter) contentStr, err := doc.Write(htmlWriter)
if err != nil { if err != nil {
@ -130,7 +137,12 @@ func (templ Template) Render(context map[string]interface{}) ([]byte, error) {
} else if ext == ".md" { } else if ext == ".md" {
// markdown rendering // markdown rendering
var buf bytes.Buffer var buf bytes.Buffer
if err := goldmark.Convert(content, &buf); err != nil { md := goldmark.New(goldmark.WithExtensions(
gm_highlight.NewHighlighting(
gm_highlight.WithStyle(hlTheme),
),
))
if err := md.Convert(content, &buf); err != nil {
return nil, err return nil, err
} }
content = buf.Bytes() content = buf.Bytes()
@ -138,3 +150,28 @@ func (templ Template) Render(context map[string]interface{}) ([]byte, error) {
return content, nil return content, nil
} }
func highlightCodeBlock(hlTheme string) func(source string, lang string, inline bool, params map[string]string) string {
// from https://github.com/niklasfasching/go-org/blob/a32df1461eb34a451b1e0dab71bd9b2558ea5dc4/blorg/util.go#L58
return func(source, lang string, inline bool, params map[string]string) string {
var w strings.Builder
l := lexers.Get(lang)
if l == nil {
l = lexers.Fallback
}
l = chroma.Coalesce(l)
it, _ := l.Tokenise(nil, source)
options := []html.Option{}
if params[":hl_lines"] != "" {
ranges := org.ParseRanges(params[":hl_lines"])
if ranges != nil {
options = append(options, html.HighlightLines(ranges))
}
}
_ = html.New(options...).Format(&w, styles.Get(hlTheme), it)
if inline {
return `<div class="highlight-inline">` + "\n" + w.String() + "\n" + `</div>`
}
return `<div class="highlight">` + "\n" + w.String() + "\n" + `</div>`
}
}

View file

@ -26,7 +26,7 @@ tags: ["software", "web"]
assertEqual(t, templ.Metadata["tags"].([]interface{})[0], "software") assertEqual(t, templ.Metadata["tags"].([]interface{})[0], "software")
assertEqual(t, templ.Metadata["tags"].([]interface{})[1], "web") assertEqual(t, templ.Metadata["tags"].([]interface{})[1], "web")
content, err := templ.Render(nil) content, err := templ.Render(nil, "github")
assertEqual(t, err, nil) assertEqual(t, err, nil)
assertEqual(t, string(content), "<p>Hello World!</p>") assertEqual(t, string(content), "<p>Hello World!</p>")
} }
@ -103,7 +103,7 @@ tags: ["software", "web"]
templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name()) templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name())
assertEqual(t, err, nil) assertEqual(t, err, nil)
ctx := map[string]interface{}{"page": templ.Metadata} ctx := map[string]interface{}{"page": templ.Metadata}
content, err := templ.Render(ctx) content, err := templ.Render(ctx, "github")
assertEqual(t, err, nil) assertEqual(t, err, nil)
expected := `<h1>my new post</h1> expected := `<h1>my new post</h1>
<h2>a blog post</h2> <h2>a blog post</h2>
@ -133,7 +133,7 @@ tags: ["software", "web"]
templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name()) templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name())
assertEqual(t, err, nil) assertEqual(t, err, nil)
content, err := templ.Render(nil) content, err := templ.Render(nil, "github")
assertEqual(t, err, nil) assertEqual(t, err, nil)
expected := `<div id="outline-container-headline-1" class="outline-1"> expected := `<div id="outline-container-headline-1" class="outline-1">
<h1 id="headline-1"> <h1 id="headline-1">
@ -175,7 +175,7 @@ tags: ["software", "web"]
templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name()) templ, err := Parse(NewEngine("https://olano.dev", "includes"), file.Name())
assertEqual(t, err, nil) assertEqual(t, err, nil)
content, err := templ.Render(nil) content, err := templ.Render(nil, "github")
assertEqual(t, err, nil) assertEqual(t, err, nil)
expected := `<h1>My title</h1> expected := `<h1>My title</h1>
<h2>my Subtitle</h2> <h2>my Subtitle</h2>

View file

@ -295,7 +295,7 @@ func (site *Site) render(templ *markup.Template) ([]byte, error) {
} }
ctx["page"] = templ.Metadata ctx["page"] = templ.Metadata
content, err := templ.Render(ctx) content, err := templ.Render(ctx, site.Config.HighlightTheme)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -306,7 +306,7 @@ func (site *Site) render(templ *markup.Template) ([]byte, error) {
if layout_templ, ok := site.layouts[layout.(string)]; ok { if layout_templ, ok := site.layouts[layout.(string)]; ok {
ctx["layout"] = layout_templ.Metadata ctx["layout"] = layout_templ.Metadata
ctx["content"] = content ctx["content"] = content
content, err = layout_templ.Render(ctx) content, err = layout_templ.Render(ctx, site.Config.HighlightTheme)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -364,7 +364,7 @@ func getExcerpt(templ *markup.Template) string {
ctx := map[string]interface{}{ ctx := map[string]interface{}{
"page": templ.Metadata, "page": templ.Metadata,
} }
content, err := templ.Render(ctx) content, err := templ.Render(ctx, "tango")
if err != nil { if err != nil {
return "" return ""
} }