Tufte style sidenotes and marginnotes in Pollen
When evaluating Pollen I complained about markdown/pandoc’s lack of sidenote handling. I have solved it for Pollen but felt it deserved it’s own post.
A caveat: I generated Tufte CSS style sidenotes and marginnotes which made it more complex than if I had simply generated “standard” sidenotes. If you want to adapt this yourself I’m sure you can simplify the code to fit your needs.
So in Pollen markup I want to be able to write this:
Lisp is a pretty nice ◊sn language.
◊ndef Some may say it's the language to rule them all.
}
To generate this:
Lisp is a pretty nice
>
/>
Some may say it's the language to rule them all.
language.
The order of ◊sn
and ◊ndef
shouldn’t matter.
By having the sidenote span right in the middle of the text it allows us to toggle it without javascript. This appeals to me as a heavy noscript user but it has a significant drawback: you cannot have block level tags like div
or p
inside a span
.
You also cannot use an aside
instead of a span
directly here as you cannot have it inside a paragraph tag.
Tufte has both sidenotes and marginnotes which we can implement in a general way. This is the markup:
This has a sidenote with numbers.◊sn
This has a marginnote without numbers.◊mn
◊ndef The note itself
}
These are the Pollen tags with their markup difference:
#:label-content "⊕"
#:span-class "marginnote"
#:ref ref-in))
#:label-content ""
#:span-class "sidenote"
#:ref ref-in))
We’ll get to the note-ref
definition in a little bit.
We can use the same markup for sidenote and marginnote content. The idea is to store the content in a map and look it up and insert it into the markup later.
;; The note ref -> definition map
;; The tag
"")
Simple enough right? If we want to apply post-processing, like adding paragraphs, we need to do some more work. Especially since we cannot have p
tags inside the span
! I solved this by instead wrapping paragraphs with a span I style like paragraphs. This is the actual code:
;; Because p doesn't allow block elements
;; and span doesn't allow p elements
;; use a special span .snp element to emulate paragraphs.
;; This is workaround is required as we want to inject a whole sidenote
;; inline to use the checkbox css toggling to avoid javascript.
)
#:txexpr-elements-proc
#:string-proc string-proc))
"")
Here string-proc
can contain smart quotes expansion or whatever extra decoding you want to use.
Now to another problem: we want to have refs before defs and vice versa. This means we might parse the references before we’ve registered the definitions. We can solve this by making a decode pass in the root tag and marking refs with a special symbol which we replace. This can be made very general:
;; Register symbols which gets inline replaced
;; by function return values.
)
x)))
Which is used like this:
#:entity-proc replace-stubs)))
Where root
in Pollen allows you to transform the whole document.
And now we can get back to our reference tag:
#:label-content label-content
#:span-class span-class
#:ref ref-in)
))
ref)
It’s basically just registering a replacement function which returns the markup:
))
Are we done? Almost. This would create markup wrapped in a span, like:
>
/>
Some may say it's the language to rule them all.
The replacement function can only return a single element so we had to wrap it in something. In this particular case it’s not a big deal but it is indeed a general limitation of Pollen tags. Is it something we can get around?
Yes it is. We can add a special symbol in the markup:
))
And then do an extra post process step to replace it with it’s content, inline:
;; A splicing tag to support returning multiple inline
;; values. So '((splice-me "a" "b")) becomes '("a" "b")
[#t]
[else #f]))
;; Expand '(splice-me ...) into surrounding list
))
'
in)
in))
#:entity-proc replace-stubs))
;; Expand splices afterwards
)
We need to do it after decode-elements
since it doesn’t support such a transformation.
And we’re done! It wasn’t very straightforward to implement Tufte style notes but with Pollen you do get the ability to do it.