rw-r--r--

chown -R toon blog/ && chmod -R u=rw,go=r $_

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.