Motivation

Around 04/16/2024, after hearing again somewhere on the Internet that [[Roam]] is dead, I decided to try some alternatives, and this time around, I focused on Obsidian.

I would prefer an [[AsciiDoc]]-based system to a [[MarkDown]]-based one, but such does not exist…

I am not paranoid about my notes being stored in the cloud, and actually prefer them to be, since it gets me the ability to synchronize them across my devices and also guarantees that I am not going to lose them. Still, I thought that if I set up backup to something like Google Drive, Obsidian could work for me - although I won’t be able to use it from my phone unless I pay for Obsidian Sync.

One of the advantages of the move: I want to be able to publish my notes easily, in a way that preserves internal links between them (Digital Garden?); [[Roam]] doesn’t let me do this, and Obsidian does.

More: I can publish my notes and my blog (currently generated by Jekyll and hosted by GitHub pages) in the same place.

More still: I can host the notes themselves in the same repository as my website and blog! This way, I do not need any separate backup to Google Drive, and everything is kept together, modified together and published together (notes that I do not want published need to be moved out of the repository).

Also, there is something to be said for the ability to edit my notes using tools other than Obsidian - e.g., IntelliJ Idea ;)

Import

To move my notes from [[Roam]], I installed Importer plugin in Obsidian and followed the export/import instructions; results of the import required manual clean-up:

  • outline bullets from Roam got translated into stars at wrong indent level;
  • tags like #jewish-calendar that I had at the start of some Roam notes moved into the YAML frontmatter’s tags array;
  • tags like #buy and #learn were converted to page references like [[buy]] and [[learn]] so that thy are recognized as links by Obsidian and back-links are available at the appropriate pages;
  • similarly, TODOs were prefixed by [[TODO]], to facilitate seeing them in the list of back-links; once done, [[TODO]] becomes a [[DONE]];
    • [[TODO]] look into Obsidian plugins that support TODOs
  • code blocks needed touch-up;
  • I used Find Orphaned Files and Broken Links Obsidian plugin to clean up broken links (it found two block references :)).

Setup

In this unified setup:

  • blog posts are, as usual, under _posts;
  • blog post drafts - under _drafts;
  • notes are under notes; in Obsidian Settings / Files and Links:
    • Default location for new notes: In the folder specified below
    • Folder to create new notes in: “notes”
  • daily notes are under days; in Obsidian Settings / Core plugins:
    • Daily notes / New file location: “days”
  • Outliner Obsidian plugin could allegedly help with the Roam-style outlining.

Publishing

[[Obsidian]] sells its own Obsidian Publish, but - although Obsidian Publish and Obsidian Sync together cost less than [[Roam]] - I want to explore other publishing options: I would like to publish my notes using a custom domain, but Obsidian Publish supports custom domains only at CloudFlare or some such; also, my goal is to keep my notes in the same GitHub repository as my blog and publish everything from there on GitHub Pages.

I looked (briefly) at Obsidian digital garden, Quartz and PublishKit, but decided to see first if I can add publish my notes using Jekyll, which I use currently to publish my blog.

GitHub Publisher is an Obsidian plugin which converts [[Obsidian]] document into a form suitable for publication and pushes them into a GitHub repository. Unfortunately, it can not handle the situation where the source documents are kept in the same GitHub repository: “If you use your vault directly in a repository, the upload will corrupt your files! This module is not intended for this type of workflow.”

Even if there was a way to keep both the sources of the notes and the result of their conversion to the publishable form in the same repository, just as I rely on Jekyll running on GitHub pages to convert my [[MarkDown]] to HTML, and do not check in the results of running [[Jekyll]] locally, I prefer not to check in the notes processed for publication by some Obsidian plugins. For now I am pursuing the approach where all the processing needed for publication is handled by Jekyll (with some plugins and other customization), without relying on any Obsidian plugins. Of course, if it turns out to be impossible to make Jekyll produce the results I want, I may have to reconsider this choice of approach.

It used to be the case that GitHub Pages restricted Jekyll plugins that could be used; since I recently switched to running Jekyll using GitHub Actions workflow (and soon everyone will switch), this is no longer a problem: all Jekyll trickery is available now ;) The question is: how to make Jekyll do what needs to be done?

File Names and Titles

Unlike Obsidian, which uses document’s file name as its title, many Jekyll plugins and code snippets that I need to make Jekyll do what I want require the pages they process to have titles; my notes, as imported from [[Roam]], do not, so I need to do something about it.

Jekyll Collections

One possibility is to make notes and days into Jekyll Collections, where the member documents have their title, if it is not set explicitly, automatically populated based on the file name - but:

  • Jekyll collection directories have their names start with _, so the notes will have to reside in the directory _notes both in the GitHub repository and on the published website;
  • sub-directories of Jekyll collections get ignored, but I want the ability to have sub-directories in my notes;
  • when Jekyll populates the title of the document from its file name, it capitalizes the first letter of each dash-separated segment, and I am not sure that this is what I want.

Liquid Code

Another possibility is to adjust the code that requires titles to use filename instead (when the title is not set explicitly) - but:

  • this works for Liquid and Ruby code snippets, but not for Jekyll plugins - and I probably need those;
  • without the title being set explicitly, my notes as they are rendered by Jekyll do not have titles - and I want the titles!

Linter Obsidian Plugin

So, I need to make sure that my notes always have titles, just like the blog posts. Obviously, adding the title manually to every little note created by just referencing it from another note is tedious and error-prone; I need to automate setting the title for the notes.

Linter Obsidian plugin can set the title property in each note to its file name; in Obsidian Settings / Community plugins / Linter / Settings:

  • General / Lint on save: enabled
  • YAML / YAML Title / Insert the title of the file into the YAML frontmatter: enabled

Sometimes I need the title to be different from the file name, for instance, for the manually created index.md files that I want to add to Jekyll’s listheader_pages in the _config.yml; in such cases, appropriate Linter rule can be disabled by adding a property to the file’s YAML frontmatter:

---
disabled rules: [yaml-title]
---

Jekyll

Jekyll needs to be enhanced (using Jekyll plugins, layouts and includes and Liquid code snippets) to publish everything the way I want it.

MathJax

I freshened up my Jekyll MathJax setup by upgrading to MathJax 3; I followed the instructions of Bodun Hu - a rare find that addresses MathJax 3, as opposed to the more wide-spread instructions for MathJax 2 integration.

To enable MathJax on a page, add math: true to its front matter:

---  
...  
math: true  
---
...

In _includes/head.html:

<head>
  ...
  {%- if page.math == true -%}  
    {% include mathjax.html -%}  
  {%- endif -%}
</head>

In _includes.mathjax.html:

<script>  
  MathJax = {  
    tex: {inlineMath: [ ['$', '$'], ['\\(', '\\)'] ]},  
    svg: {fontCache: 'global'}  
  };  
</script>  
<script  
  type="text/javascript" id="MathJax-script" async  
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"  
>  
</script>

Directory Listing

With the notes published on my website, I want a list of them to be published too - otherwise, nobody can find the individual notes. I found a solution; thank you, Michael Currin!

I did not make sub-folders appear in the lists automatically yet, not just files, so I add them manually, as content of the index files.

  • [[TODO]] add sub-folders to the folder listing automatically;
  • [[TODO]] generate the index files themselves automatically.

Also, there does not seem to be any way to paginate the page lists.

In _layouts/page.html:

<div class="post-content">
  {{ content }}
  {% if page.name == 'index.md' %}  
    {% include page-list.html %}  
  {% endif %}  
</div>

In _includes/page-list.html:

<ul class="page-list">  
  {% assign pages = site.pages | sort_natural: 'title' %}  
  {%- for item in pages %}  
  {%- if item.dir == page.dir and item.path != page.path %}  
  <li>
    <a href="{{ item.url | relative_url }}">
      <span>{{ item.title }}</span>
    </a>
  </li>  
  {% endif -%}  
  {% endfor -%}  
</ul>

In _sass/custom.scss:

.page-list {  
  font-size: 18px;  
  
  & i {  
    font-size: 14px;  
    // Based on blockquote.  
    color: #828282;  
  }  
}

Unlike [[Obsidian]], Jekyll does not understand wiki-links. There is a Jekyll plugin for that: Jekyll Wikirefs, but it ts latest release does not work with current Jekyll.

  • [[TODO]] see if I need this plugin handle fancy wikilinks, and if yes - ask maintainers for a refresher release
  • [[TODO]] Is there a way to get the graph view and the like?
  • [[TODO]] look deeper into wikibonsai

I found (and tweaked a bit) a piece of Liquid code that does what needs to be done with basic wiki-links: brackettest:

In _layouts/default.html:

<main ...>  
  <div class="wrapper">  
    {% assign contentarray = content | split:'[[' %}  
    {% for item in contentarray %}  
      {% if forloop.index > 1 %}  
        {% assign itemparts = item | split:']]' %}  
        {% assign link = itemparts[0] %}  
        {% if forloop.index == 2 %}  
          {% assign links = link %}  
        {% else %}  
          {% assign links = links | append: ',' | append: link %}  
        {% endif %}  
        {% assign result = site.pages | where: 'title', link %}
        {% if forloop.index == 2 %}  
          {% assign urls = result[0].url %}  
        {% else %}  
          {% assign urls = urls | append: ',' | append: result[0].url %}  
        {% endif %}  
      {% endif %}  
    {% endfor %}  
  
    {% assign urlarray = urls | split:',' %}  
    {% assign linkarray = links | split:',' %}  
 
    {% assign replacedcontent = content %}  
    {% for item in linkarray %}  
      {% assign linktext = '<a href="' | append: urlarray[forloop.index0] | append: '">' | append: '[[' | append: item | append: ']]' | append: '</a>' %}  
      {% assign bracketlink = '[[' | append: item | append: ']]' %}  
      {% assign replacedcontent = replacedcontent | replace: bracketlink, linktext %}  
    {% endfor %}  
  
    {{ replacedcontent | markdownify }}  
  </div>  
</main>

Page Tags

To display tags for posts and for pages like notes, in _includes/tags.html:

{% if page.tags %} |  
{% for tag in page.tags %}  
<a class="post-tag" href="{{ site.baseurl }}/tags/#{{ tag | slugify }}">{{ tag }}</a>  
{% endfor %}  
{% endif %}

In _layouts/post.html, between the date and the author:

{%- include tags.html -%}

And in _layouts/page.html, after the title:

<p class="post-meta">  
{%- include tags.html -%}  
</p>

In _sass/custom.scss:

.post-tag {  
  display: inline-block;  
  background: $grey-color-light;  
  padding: 0 .5rem;  
  margin-right: .5rem;  
  border-radius: 4px;  
  color: $text-color;  
  font-size: 90%;  
  &:before {  
    content: "\f02b";  
    font-family: FontAwesome;  
    padding-right: .5em;  
  }  
  &:hover {  
    text-decoration: none;  
    background: $grey-color;  
    color: $background-color;  
  }  
}

To use the fancy tag character, make fancy font available in _includes/head.html:

<head>
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
</head>
  • [[TODO]] update the font - or get rid of it if possible

Tag Pages

Now that I have my blog and my notes in the same repository and in the same Obsidian vault, I can cross-link blog posts and notes; both can have tags in their YAML frontmatter, and any overall list of tags, tag cloud or whatever I end up publishing on the site needs to include both posts and notes.

There are many code snippets floating around that add listing of tags and documents tagged with them to a site generated by [[Jekyll]] (for example: “an easy way to support tags in a jekyll blog”, “listing jekyll posts by tag”, “https://www.maggie98choy.com/Add Tags in Jekyll/”, jekyll-tagging [[Jekyll]] plugin ); I am using a file tags.html (which I list under header_pages in _config.yml):

---  
layout: page  
title: "Tags"  
disabled rules: [yaml-title]  
description: "Posts by tags"  
permalink: /tags/  
---
<div id="tags">  
  {% assign sorted_tags = site.tags | sort %}  
  <h2>All tags</h2>  
  <p>
    {% for tag in sorted_tags %}  
    <a class="post-tag" href="{{ site.baseurl }}/tags/#{{ tag[0] | slugify }}">{{ tag[0] }}</a>  
    {% endfor %}  
  </p>  
  <h2>Posts by tags</h2>  
  {% for tag in sorted_tags %}  
  <div id="{{ tag[0] | slugify }}">  
    <h3>{{ tag[0] }}</h3>  
    {% for post in tag[1] %}  
    <ul>  
      <p>
        <a class="post-link" href="{{ post.url | relative_url }}">{{ post.title | escape }}</a>  
      </p>
    </ul>
    {% endfor %}  
  </div>  
  {% endfor %}  
</div>

All of the samples I saw use site.tags, which maps tags to lists of posts that have the tags, but only posts are included, not pages; it seems that I will have to build a similar map that includes both posts and pages myself!

In addition, I may want to look into tag pages aliased to tags (with the help of the Tag Wrangler Obsidian plugin?) to bring tags like #computer closer to classifier pages like [[buy]] and [[learn]] (while in [[Roam]] they were equivalent).

Zotero Integration