Repurposing Hugo as a wiki · ./jm
As a part of a challenge that I’m engaged on, I made a decision to see if I might repurpose the superb Hugo web site framework (which additionally powers this very website) as a single-user wiki of kinds.
Brief model: sure!
Necessities
The principle options that I needed have been:
-
The flexibility to, whereas writing a web page, freely hyperlink to different subject pages that I do know will (or at the very least plan to make) exist in future, with out having to fret about in the event that they exist proper now.
-
An automatically-updating visible indication of whether or not a linked-to web page does or doesn’t exist but.
-
A standard “coming quickly” web page that the lacking hyperlinks will land on (i.e. no precise 404’s for content material linked on this method; regular website 404 performance is untouched).
-
The simplicity of writing in Markdown that I’m so used to already, particularly as a result of this can be a technical challenge that includes a number of code syntax highlighting within the content material.
Why not <wiki software program X>?
The principle motive that I didn’t wish to use a longtime wiki bundle like MediaWiki, the software program behind Wikipedia, was that it appeared like overkill. For this challenge, I don’t want separate authors, I don’t want revisions of pages, and I definitely don’t want a database server.
Hugo exists to create static HTML web sites, helps Markdown formatting, is quick, and has a strong shortcode system which may be prolonged.
So let’s make it occur. However first, earlier than I get too far into the weeds, let’s check out the top consequence and the way easy it’s to make use of whereas writing.
Utilizing the customized shortcode
I created a custom shortcode known as hyperlink
. Right here’s how you utilize it.
-
For linking to a web page the place the page slug is identical because the phrase you need clickable:
This can produce the hyperlink textual content different linking to
/pages/different/
. -
For linking to a web page the place you wish to make clickable some customized textual content:
{{% hyperlink "my textual content right here" other2 %}}
This can produce the hyperlink textual content my textual content right here linking to
/pages/other2/
.
That’s it. However critically: if within the above examples the pages different
or other2
don’t exist, the hyperlink shall be styled in a noticeable approach (in my case, crimson as an alternative of blue) and can hyperlink to (additionally, in my case) /pages/lacking/
. Every time I get round to including stated web page, all present hyperlinks to it’s going to auto-update and turn into blue in future.
Overview of elements
A couple of issues have to occur to carry this configuration collectively.
-
Allow Goldmark renderer “unsafe” mode. This enables rendering of inline HTML in shortcodes. A motive why you’ll not need this enabled is when you have untrusted customers and/or untrusted content material. For me and this challenge, neither of those apply.
In your
hugo.yaml
:markup: goldmark: renderer: unsafe: true
-
Create and configure the widespread “lacking” web page and make it so that unreachable-by-Hugo pages are not fatal errors within the rendering course of.
In your
hugo.yaml
:refLinksErrorLevel: WARNING refLinksNotFoundURL: /pages/lacking/
-
Create the shortcode. As per above, I used the identify
hyperlink
, which suggests I created alayouts/shortcodes/hyperlink.html
containing the next:{{- $hyperlink := (urls.RelRef . (cond (eq (len .Params) 2) (.Get 1) (.Get 0))) -}} <a href="{{ $hyperlink }}"{{ if eq $hyperlink (relref . "lacking") }} class="lacking"{{ finish }}>{{ .Get 0 }}</a>
Please forgive the road wrapping. Since a shortcode will get changed inline in your content material, and since I didn’t wish to add further spacing or linebreaks in my content material, the shortcode is simply two (long-ish) traces.
-
Configure the
lacking
class in your theme’s CSS.foremost#content material a.lacking { coloration: #f00; }
How the shortcode works
The shortcode took essentially the most work, as the opposite bits have been just a bit configuration tweaking and a few strategic net looking so as to puzzle by way of some minor points. Right here’s the way it works.
Line 1: Resolve vacation spot
Within the first line, a $hyperlink
variable is created which advantages from the pliability of specifying both one or two arguments to the shortcode — one if the web page slug and linked textual content are the identical, and two in the event that they differ.
{{- $hyperlink := (urls.RelRef . (cond (eq (len .Params) 2) (.Get 1) (.Get 0))) -}}
I’ll add that spending lots of time just lately in Lisp and different related languages that are composed of S-expressions has made Go syntax like (cond (eq (len .Params) 2)...
far more pure to me.
The perfect half about using urls.RelRef
within the hyperlink buildup is that the hyperlinks are filename- and reorganization-independent. In the event you use a vacation spot web page filename of different.md
, a shortcode argument of different
will discover it. In the event you use a filename of other-page.md
however a slug
inside its entrance matter of different
, it’s going to additionally work. In the event you change the situation of sources however preserve both of those the identical, will probably be discovered. And at last, if the web page can’t be discovered (probably as a result of it doesn’t exist but, one of many necessities of tolerance on this system), the “lacking” widespread web page shall be linked (in my configuration above, /pages/lacking/
).
Line 2: Conditionally fashion hyperlink textual content
Transferring on from the $hyperlink
setup, the opposite line of the shortcode units up a regular HTML anchor hyperlink <a>
tag highlighting the shortcode’s first argument because the hyperlink textual content. Most significantly, if the hyperlink resolves to the widespread lacking web page, this may be detected and the customized “doesn’t exist but” CSS is utilized to the hyperlink.
<a href="{{ $hyperlink }}"{{ if eq $hyperlink (urls.RelRef . "lacking") }} class="lacking"{{ finish }}>{{ .Get 0 }}</a>
Astute readers and/or optimization nerds will discover that this technically depends on duplication of the lacking
time period — as soon as right here and as soon as within the hugo.yaml
half which units up the lacking web page for failed urls.RelRef
decision. Whereas I might have favored to keep away from this, no Hugo operate exists to get on the similar of the refLinksNotFoundURL
variable specified within the website configuration. The closest I obtained was .Site.Config
, however that solely exposes the companies
and privateness
keys as a subset of the configuration. Argh, code duplication.
Thrilling conclusion
All in all, I’m fairly pleased with this. Whereas I’ve solely actually begun the precise buildout of the location for which this technique was contrived, it’s already lowering psychological overhead and permitting me to see some construction to the location with out having to fret about writing all of it and even creating placeholder web page information for now.
It’s positively a case of The General Problem, although.
What can I say? I’m most definitely a developer and toolmaker at coronary heart. I want to take inspiration right here from Abraham Lincoln as quoted within the earlier hyperlink:
If I had six hours to cut down a tree, I might spend the primary 4 sharpening the axe.
Bonus: Visible Studio Code snippets
Not content material with a mere single stage of yak shaving, and in step with the spirit of the previous part, I additionally needed a fast option to insert these shortcode-based hyperlinks into my writing. I discovered about VS Code snippets that are each straightforward and highly effective, and I used to be capable of create a project-specific .vscode/snippets.code-snippets
file with the next:
{
"Insert wiki web page hyperlink": {
"scope": "markdown",
"prefix": "hyperlink",
"physique": ["{{% link ${1:linked text} ${2:slug} %}}"]
}
}
Then, in my challenge, every time I kind hyperlink
, I can Tab
to get a popup suggestion with tab-completion into the 2 fields for straightforward fill-out. And if I solely need one shortcode argument, I can simply delete the second tabbed-to argument.
Within the means of penning this submit in Hugo and about Hugo, I found a puzzler of an issue with together with the instance shortcode within the utilization and snippets examples — the shortcode doesn’t really exist on this website, however was nonetheless being interpreted by Hugo as a shortcode on this submit’s content material, inflicting the web page render to fail. Bless this kind soul, Chris Liatas, for writing a couple of approach to work round this utilizing C-style code feedback, thereby permitting me to show unparsed Hugo code in a submit about Hugo code inside a system operating Hugo code.