Org mode blogging: Clickable headlines
posted on 2018-12-13
Intro
Recently I’ve migrated my blog from Jekyll to Org mode. I’m not gonna write down the entire process, cause there are plenty of articles about that on the internet already. Although if you are interested, the sources of this blog can be found online.
This post is the first in a series of posts about problems I’ve encountered and no one on the internet seemed to have fixed already. It’s about clickable headlines.
Objective
When Org mode publishes files to HTML, it adds the id
attribute to
all headlines, making them anchors to link to. But when someone wants
to copy the link to a headline, they’ll need to web-inspect the page
to find the id. So most blogging engines make the headlines clickable,
with a link to the headline self. Unfortunately, Org mode does not do
that out of the box.
Solution
When you define the org-publish-project-alist
, you can specify the
:html-format-headline-function
property. This property should be set
to a function taking the following six arguments:
todo
- the todo keyword (string or
nil
) todo-type
- the type of todo (symbol:
'todo
,'done
,nil
) priority
- the priority of the headline (integer or
nil
) text
- the main headline text (string)
tags
- the tags (string or
nil
) info
- the export options (plist)
By default this property is set to
org-html-format-headline-default-function
.
So I attempted to write a function that calls the original function,
passing text
as a hyperlink.
This shouldn’t be too hard, although there was one problem, where do I
get the value of the ID
from?
Propertized strings
It took me a lot of debugging to figure this out. I started inspecting
the values of the six arguments, and I noticed something weird was
going on with the text
variable.
#("Introduction" 0 12 (:parent (headline (:raw-value "Introduction" :begin 105 :end 507 :pre-blank 0 :contents-begin 121 :contents-end 506 ...
The variable contains the text of the headline, but apparently also some other things.
I tried to inspect the type of the variable:
(type-of text)
string
But that didn’t help me much…
I could not understand what #(
meant. So I started searching the web
and eventually I stumbled on a StackOverflow answer. It turns out, and
I really didn’t know that, strings in Emacs lisp can have Text
Properties.
Interesting…
In the example above, there is a text property :parent
from
character 0
with length 12
.
Apparently, this property contains the headline element. And Org mode
has functions to read properties of that element. All what remained to
do was extract the :CUSTOM_ID:
or :ID
property and wrap the text
in <a href...>
& </a>
tags.
Eventually I ended up with this function:
(defun my-org-html-format-headline-function (todo todo-type priority text tags info) "Format a headline with a link to itself." (let* ((headline (get-text-property 0 :parent text)) (id (or (org-element-property :CUSTOM_ID headline) (org-export-get-reference headline info) (org-element-property :ID headline))) (link (if id (format "<a href=\"#%s\">%s</a>" id text) text))) (org-html-format-headline-default-function todo todo-type priority link tags info)))
You can find the exact function I’m using here.
And then all you need to do is specify this function in your
org-publish-project-alist
.
(setq org-publish-project-alist (list (list "blog-posts" :base-directory "posts" :base-extension "org" ;; ... :html-format-headline-function 'my-org-html-format-headline-function ;; ... ) ;; ... ))
Comments are welcome on Reddit.