Sidenotes / Marginnotes in Kirby

I recently stumbled upon the new design of iA Writers Blog. I really like the idea of using the extra space of today’s screens with sidenotes for additional explanations, references, links, and even for headlines:

As it seems the idea is based on a design concept by Edward Tufte who uses sidenotes (and even side-graphics) heavily in his scientific books and papers. There is a CSS who shows this in action for websites:

I also found a Jekyll Theme which implements the Tufte CSS. Creating the sidenotes seems to be much easier for a static site generator than for a dynamic site. I think it’s not possible without javascript to determine the correct positions.

Has anyone already tried to implement a plugin that creates sidenotes (e.g. by using a new kirbytag)?

3 Likes

I’ve come a step further. Using jQuery.sidenotes and adapting it to jQuery 3 already works in converting markdown footnotes into fully responsive Sidenotes:

Sidenotes with a big resolution:

Sidenotes with a medium resolution

Sidenotes with a small resolution

Looks great! Do you think you’ll share the updated script?

I used this some time ago to keep Markdown footnotes inline on small screens too, like the Tufte CSS implementation:

Also of interest, the sidenotes of gwern.net:
https://www.reddit.com/r/gwern/comments/b13p93/sidenotesjs_new_gwernnet_feature_for_turning

Sure, here you go:

If you need additional instructions on how I made this work with Kirby, just drop me a note.

Thanks for your recommendation.

2 Likes

Have you checked out CSS Grid to create marginalias? We are storing annotations in a structure field and position them with grid to get a similar effect:

It took a bit of time to get the markup right in order to arrange the annotations across breakpoints but it really works nicely without any additional scripts.

1 Like

Here’s how I recently implemented sidenotes, following the original Tufte-CSS markup approach which I liked because it’s pretty clever and doesn’t require javascript.

I only did “sidenotes” (numbered footnotes), but “marginnotes” (floating content without numbers) should be pretty straight forward with the same approach.

First, I added a structure field sidenotes to my article blueprint, below the main blocks field:

sidenotes:
  type: structure
  label: Sidenotes
    fields:
      text:
        label: Text
        type: writer

Then created a plugin for the new Kirbytag (sn:<number>):

// site/plugins/tufte/index.php
Kirby::plugin('bruno/tufte', [
    'tags' => [
        'sn' => [
            'html' => function($tag) {
                $sidenotes = $tag->parent()->sidenotes()->toStructure();
                $return = '';
                if ($sidenotes 
                && $note = $sidenotes->nth($tag->value() - 1)) {
                    $text = $note->text()->kirbyText();
                    $text = preg_replace( '/^<[^>]+>|<\/[^>]+>$/', '', $text);
                    $return = '<label for="sn-' . $tag->value . '" class="sidenote-number"></label><input type="checkbox" id="sn-' . $tag->value . '" class="margin-toggle"/><span class="sidenote">' . $text . '</span>';
                    $return = $return;
                }
                return $return;
            }
        ],
    ],
],

It’s ugly, but it works and produces the following markup:

<label for="sn-1" class="sidenote-number"></label>
<input type="checkbox" id="sn-1" class="margin-toggle">
<span class="sidenote"> {{ SIDENOTE TEXT }} </span>

I included the original Tufte.css file, but added some attributes to make it more customizable – most notably the css variables in root which allow easily tailoring the layout:

/* assets/css/tufte.css */
:root {
    --tufte-width: 350px;
    --tufte-distance: 50px;
    counter-reset: sidenote-counter;
}

.sidenote {
    float: right;
    clear: right;
    width: var(--tufte-width);
    margin-right: calc(var(--tufte-width) * -1);
    margin-bottom: 1rem;
    padding-left: var(--tufte-distance);
    vertical-align: baseline;
    position: relative;
}

.sidenote-number {
    counter-increment: sidenote-counter;
}

.sidenote-number:after,
.sidenote:before {
    position: relative;
    vertical-align: baseline;
}

.sidenote-number:after {
    content: counter(sidenote-counter);
    font-size: .8rem;
    top: -0.5rem;
    left: 0.1rem;
}

.sidenote:before {
    content: counter(sidenote-counter) " ";
    font-size: .7rem;
    top: -0.5rem;
}

input.margin-toggle {
    display: none;
}

label.sidenote-number {
    display: inline;
}

label.margin-toggle:not(.sidenote-number) {
    display: none;
}

@media (max-width: 800px) {
    label.sidenote-number {
        display: inline;
        cursor: pointer;
    }

    .sidenote {
        display: none;
    }

    label.margin-toggle:not(.sidenote-number) {
        display: inline;
        font-size: 1.3rem;
        line-height: 1rem;
    }

    .margin-toggle:checked + .sidenote {
        display: block;
        float: left;
        left: 1rem;
        clear: both;
        width: 95%;
        margin: 1rem 2.5%;
        vertical-align: baseline;
        position: relative;
    }
}

Result:


:exclamation:Notes/Side effects:

  • It’s not semantic.
  • If you copy&paste text with this markup, it will always include the footnotes content mashed in right where they are registered with the (sn:) tag.
  • It’s (probably) not very accessible. Some javascript and aria-attributes should in fact be applied.
1 Like

Great article on sidenotes : https://www.gwern.net/Sidenotes

1 Like

Hi @bruno thanks a lot for sharing how you implemented the “sidenotes”. I have a slightly different use-case as the website (a digital version of a PHD thesis) will not need to be updated regularly, however I still plan to use Kirby’s panel to ensure ease of uploading content.

In your blocks field, what field are you using to implement the Kirbytag? Are you using a markdown field or textarea field rather than the text and other fields blocks ships with?

I ask, just because I am quite new to using Kirbytags and am trying to figure out the best workflow for adding (text, header, quote, sidenote, image, video & audio) content via panel, ideally using the blocks field.

PS. I am a big fan of your projects :cowboy_hat_face:

1 Like

:hugs::revolving_hearts:

In my case, it’s writer fields that contain the (sn:x) tag. But in theory, it can be any field you like, as long as the output is parsed by kirbytext() at some point. It doesn’t even need to be inside a blocks editor field, it could simply be a textarea or any text field.

(Also notice that I’m running a regex on the sidenote writer field:

$text = preg_replace( '/^<[^>]+>|<\/[^>]+>$/', '', $text);

That’s to strip the enclosing <p></p> tags of the writer field, because it didn’t seem to like them. It might be better to have a textarea field instead of a writer field in the sidenotes structure and do kirbytextinline() instead of kirbytext() so that this issue doesn’t come up.)

@bruno ohhhh, thanks for the insight. That all makes sense regarding parsing the output (but didn’t realise it was so easy :man_facepalming:) and your use of the regex. I am going to give it a try later in the week hopefully.

1 Like

Thank you for sharing this link - what a brilliant resource!

I’m following this thread with interest, as I’ve been working – on and off – on “the perfect footnotes/sidenotes solution” as well (spoiler: haven’t found it, yet). Here’s another link with deep thoughts for sidenote enthusiasts: Notes on the web, take 1 - QuirksBlog

The lack of a semantic HTML solution (like a native <footnote> element) for such an essential feature of academic writing is actually quite astonishing. And while a lot of visually appealing implementations exist, most fall short on the accessibility side.

1 Like

I also plan to follow thread. I really want to be able to blog and use notes. Something like in Adobe Copy where you can see foot/side/end notes in text in brackets as you type and then in InDesign they can be placed wherever you want them.
Ideally a blog could display notes as sidetones on large screen and endnotes on phone for example and that this formatting happened in the code