My Digital Garden Setup

This page is dedicated to my current website setup, my customizations, bugs, rough edges which still exist and potential workarounds.

The Setup

Customizations and Bug Fixes

Here I track any issues I've encountered so far and how I deal with them. In most cases, I fix them within the Digital Garden template. I try to customize this the way it's meant to be customized, to avoid hassle when updating the template in the future, but this is often not possible for the issues I encountered.

General Issues, Bugs, Customizations

Deploying to GitHub Pages

Status: Resolved ✅

Description: The plugin is mainly aimed at Vercel, and lists Github Pages only somewhere in the "Other" section. There is no documentation on how to deploy to GitHub Pages.

Solution:

Formatting breaks if there is no line break above a heading

Status: Worked around ✅

Description: Obsidian renders just fine if you put content directly above a heading, but formatting for a Digital Garden note breaks in this case.

Solution: I am using Obsidian Linter anyways , so all I had to do was to enable the rule "Heading Blank Lines". This prevents the offending formatting from happening.

Inefficient Transclusions

Status: Worked around ✅

Description: The plugin doesn't cache transclusions, so if you transclude e.g. a header in every note, then that header file is compiled for every single note again. This results in performance degradation during publishing.

Solution: Using transclusions as a header has other drawbacks as well, such as the header showing up in the RSS Feed or the built-in search. The Digital Garden plugin has a capability to add custom components, such as a Header, which I used instead of transclusions.

Opening the Publication Center on Mobile sometimes causes an app reload

Status: Watching 👀

Description: Pretty much what it says on the tin.

Solution: My current assumption is that this is due to the performance issues caused by DataviewJS and that this issue will go away once I solve these performance bottlenecks.

The first --- below the front matter is not rendered

Status: Resolved ✅

Description: In any markdown file, the first line below the front matter which only contains --- will disappear instead of rendering a horizontal line.

Solution: This is because of the following setting in the template's eleventy.js:

 eleventyConfig.setFrontMatterParsingOptions({
   excerpt: true
  });

This treats the first --- as a signal that everything between the front matter and this separator should be made available as an excerpt, and the --- is then removed. This can be solved, while retaining the functionality, by changing the options as follows:

eleventyConfig.setFrontMatterParsingOptions({
   excerpt: true,
   excerpt_separator: ""   
});

This makes 11ty use the given separator, keeping the --- intact.

Status: Not a Bug ☑️

Description: Links without a title in the form of [[note-name]] are ignored by the plugin.

Solution: Works as intended, and since I don't need this feature, I haven't invested in fixing this.

Status: Resolved ✅

Description: A link to a heading in the same file, as in: A link to the previous section is not supported and leads to dead links.

Solution: In getAnchorAttributes() within the .eleventy.js, check if fileName is empty and if so, set permalink to "" right away instead of attempting to load the file. Lo and behold: A link to the previous section.

Publishing fails without a visible error

Status: Open 🐛

Description: When publishing a lot of notes (e.g. by changing a setting which requires a re-publishing of all notes), then publishing of some notes fails. The developer console states that you have hit a secondary rate limit at GitHub. Yet, the plugin shows success for all notes.

Solution: None yet. Best workaround is to open the publication center again and check if there are still notes showing up.

Status: Resolved ✅

Description: Obsidian supports different callouts, and their type is define by a string like [!info]. This string shows up in the search.

Solution: Extend the function userEleventySetup in src/helpers/userSetup.js as follows:

eleventyConfig.addFilter("removeObsidianCallouts", function(contentValue) {
  if (!contentValue || typeof contentValue !== 'string') { 
    return contentValue; 
  }
  const calloutRegex = /\[![a-zA-Z0-9-]+(?:\|[a-zA-Z0-9-]+)?\]/g;      
  return contentValue.replace(calloutRegex, '');
});

This filter actually filters out all kinds of names as long as they are in square brackets and start with an !.

Then, in src/site/search-index.njk, use this filter for the content as follows:

"content": {{ post.templateContent   | removeObsidianCallouts // rest of line

Adding this filter to src/site/feed.njk removes these callout names also from the RSS feed.

Status: Resolved ✅

Description: The plugin does a great job in gracefully handling dead links. Dead links can happen if:

In either case, the plugin redirects to the 404 page. However, I do not want dead links at all.

Solution:
In the .eleventy.js, I modified the function getAnchorAttributes as follows:

function getAnchorAttributes(filePath, linkTitle) {
  // .. other parts of this function..
  if (deadLink) {
    // Start addition
    
     console.log("Dead link detected! Filepath: " + filePath + " | Link title: " + linkTitle);
     throw new Error("Dead link detected! Filepath: " + filePath + " | Link title: " + linkTitle);
    
    // End addition. Even though the following is dead code I leave it around.
    return {
      attributes: {
        "class": "internal-link is-unresolved",
        "href": "/404",
        "target": "",
      },
      innerHTML: title,
    }
  }

With this, any build will fail on GitHub if a dead link is detected.

Issues with DataView

The digital garden plugin supports Dataview, but I have run into a couple of issues with this. This section covers them.

dv.current() is empty in inline dataview JS queries

Status: Worked Around ✅

Description: Using dv.current() to access the current file in an inline dataview JS query renders just fine in Obsidian but the result is empty on compiling.

Solution: My first solution was to use dv.pages() in combination with Templater to get what I wanted. But given the performance issues I observed with dataview as well as the locale issues with Dataview, I decided to move away from js dataviews. Since I used them effectively for values that belong into the header and footer, I was able to get rid of them by moving that rendering logic to 11ty.

Dates are parsed inconsistently across devices

Status: Worked around ✅

Description: Using dataview to parse dates in a specific format yields to inconsistent results across devices. This is actually an issue with DataView, as on iOS, it uses the locale based on the system device settings for formatting instead of a locale based on the language Obsidian is in. DataView's documentation even states that formatting might have locale issues.

Solution: As mentioned in other sections as well, I was using this functionality mainly to replicate header/footer functionality. I moved this to 11ty.

Regular Dataviews produce bogus output

Status: Worked around ✅

Description: Dataviews sometimes produce a single { .block-language-dataview} below the desired result.

Solution: I migrated to JS Dataviews first, but I ran into performance issues. Instead, I now remove that line during markdown pre-processing. I do this by extending the userMarkdownSetup in the template file src/helpers/userSetup.js as follows:

function preProcessRule(state) {
    let currentContent = state.src;
    currentContent = currentContent
                        .split('\n')
	                    .filter(line => 
	                        !line
	                            .trim()
	                            .startsWith('{ .block-language-dataview}'))
                                .join('\n');
    state.src = currentContent;
  }
  md.core.ruler.before('normalize', 'user_markdown_preprocessor', preProcessRule);

This removes the offending line.

JS Dataviews have performance issues

Status: Partially Resolved ⏳

Description: JS Dataviews come with a significant performance overhead, as a JS environment needs to be spun up for every note that uses one. I had JS Dataviews in every file, which increased the compile time (needed for publishing) from seconds to minutes.

Solution: since most of these dataviews were built to create some kind of header or footer, I replaced most of them with custom components, as described above. There are still some dataviews remaining that I need to migrate.

Getting the 404 right

The default setup with 404 has a couple of issues:

Edit 404s within Obsidian

Status: Resolved ✅

Description: To have better control over what the 404 page looks like, I would like to edit it in Obsidian.

Solution:
This one is fairly easy:

Make 404s work with GitHub Pages

Status: Resolved ✅

Description: For internal broken links, the Digital Garden links correctly to its 404 page (unless you disabled this, as I did here. For invalid URLs, the default GitHub Pages 404 is displayed.

Solution:
If you did not set up your own 404 in Obsidian, all you need to do is to add the following front matter to the src/site/404.njk:

---
permalink: 404.html 
---

If you have set up your own 404, this requires more work. Simply setting dg-permalink to 404.html does not work, because the plugin transforms this to /404.html/, which is not something GitHub Pages can work with. Even setting dg-pass-frontmatter to true does not prevent this.

To solve this, you need to add a file to your template. It needs to be placed right into the same directory where your 404 note ends up (somewhere in src/site/notes), and it needs to be called (notename).11tydata.js. In my case, the 404 note is located in src/site/notes/public and is called 404.md, so I need to create the following file in the following folder: src/site/notes/public/404.11tydata.js .

The file needs to contain the following:

module.exports = {
    eleventyComputed: {
      permalink: data => {
        return "/404.html";
      }
    }
  };

This overrides the plugin setting for dg-permalink and ensures the 404.html is written appropriately.

The following is not strictly needed, as without it, the 404 would still be displayed correctly, but for the sake of completeness I have also modified the getAnchorAttributes within .eleventy.js as follows:

function getAnchorAttributes(filePath, linkTitle) {
  // .. other code ..
	if (deadLink) {
	    //.. (potentially) other code ..
	    
	}
    //.. remainder of the function..
    return {
      attributes: {
        "class": "internal-link is-unresolved",
        "href": "/404.html", // ⇐ add .html here
        "target": "",
      },
      innerHTML: title,
    }
}

Getting the Page Metadata right

Besides the layout and the notes, there are other metadata which need attention:

The Feed and the Sitemap create incompliant output

Status: Resolved ✅

Description: Validation of e.g. the RSS feed fails because of invalid XML. Also, the respective files look a bit weird. For example, look at the following line in src/site/feed.njk (notice the extra slashes at the end):

 <link href="{{ meta.siteBaseUrl }}{{note.url | url }}" ////>

Solution: 11ty puts all files through a series of transformations in order to produce the result. These are defined in the .eleventy.js and are added with eleventyConfig.addTransform.
All transforms which call parse(), so dataview-js-links, callout-block, pictureand table transform, by the way they are using the result of parse, a tag which is valid HTML to a tag which is invalid XHTML (needed for RSS).
Luckily, none of the transformations are actually needed for the feeds, so to fix this, all these transforms need to ignore files which will produce an XML file.

This can be fixed by amending the transformers as follows (taking the one named table as an example):

eleventyConfig.addTransform("table", function (str, outputPath) {
   if (outputPath.endsWith(".xml")) { // ↑↑ note the outputPath up there ↑↑
     return str;
   }
   // .. remaining code
}

With this, the extra slashes in the feed.njk and the sitemap.njk can be removed.

I want to add more meta data to the feed, meta tags to the page and more data outside of my notes

Status: Resolved ✅

Description: The RSS feed isn't compliant out of the box, as it is missing some basic fields that apply to the entire feed, such as the author. I also would like to add more fields to the feed, such as the published date.

And while we are at it:

Solution: The Digital Garden plugin comes with some very limited abilities to add meta tags, but only on a per-note level, which is not sufficient. Besides that, while I do have the created date of my notes available in the front matter property created-date, I can only make this available in the 11ty template if I turn on the option to show timestamps. However, I dislike how the time stamps look and the lack of fine-tuning abilities, so I need to take a different approach.

Firstly, I am enabling dg-pass-frontmatter in the global note settings. Despite the plugin's sternest warnings, the side effects were: None. But the desired effect was that now all my front matter keys and values that I use in Obsidian are available in 11ty.

However, this comes with a minor snag: My keys are mostly using kebab-case , and accessing kebab-cased variable names from within 11ty templates is awkward. And I am unwilling to change my kebab-casing in Obsidian.

This can be fixed by editing src/helpers/userUtils.js. While I am at it, I will add some more stuff in there which will be useful in the upcoming steps. The entire file looks like this:

const { DateTime } = require('luxon'); 

function userComputed(data) {
  return {
    author: "Philipp Flenker",
    email: "hello@philippflenker.com",
    description: "thoughts on stuff, views on things",
    builtAt: new Date(),
    created: DateTime.fromISO(data["created-date"], { zone: "Europe/Berlin" }).toJSDate(),
    updated: DateTime.fromISO(data["updated-date"], { zone: "Europe/Berlin" }).toJSDate()
  };
}

exports.userComputed = userComputed;


All these keys are now available to me. You will soon see where I will use author, description and builtAt. I map my front matter value created-date to created , and updated-date to updated, circumventing the kebab-case issue. At the same time, I take care of another issue:

My obsidian dates are without timezone information (as I don't need this), but I'd like to display timezone information in the RSS feed. And especially for builtAt I need to be mindful of the timezone as I can't guess in which timezone the GitHub servers are located and which value this will produce for builtAt if I'm not looking at the timezones. Since 11ty comes with the luxon library right away, I can make use of it as dealing with JS dates directly is very painful.

With this, I can update src/site/feed.njk as follows ( I placed a logo.svg in src/site/img for the logo tag):

<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ meta.siteBaseUrl }}">
    <!-- Other tags -->
    <updated>{{ userComputed.builtAt | dateToRfc3339 }}</updated>
    <id>{{ meta.siteBaseUrl }}/</id>
    <author>
      <name>{{ userComputed.author }}</name>
      <email>{{ userComputed.email }}</email>
    </author>
    <subtitle>{{userComputed.description}}</subtitle>
    <icon>/img/logo.svg</icon>
    {%- for note in collections.note | reverse %}
        <entry>
			<!-- title tag -->
            <updated>{{note.data.userComputed.created | dateToRfc3339  }}</updated>
            <published>{{note.data.userComputed.updated | dateToRfc3339 }}</published>
			<!-- remaining tags -->
        </entry>
    {%- endfor %}
</feed>

As you can see, I replaced the updated date of the feed with my new builtAt. The template was using some other means to find it, which works just fine as well, but this one is closer to what I think constitutes an update, as I expect any change to my page resulting in a change in one of the items exposed via the feed.

Let's also use these dates in the sitemap. We do so by adjusting the src/site/sitemap.njk as follows:

<!-- Other tags -->
<url>
    <loc>{{ meta.siteBaseUrl }}{{ page.url | url }}</loc>
    <lastmod>{{ page.data.userComputed.updated | dateToRfc3339 }}</lastmod>
</url>
<!-- Other tags -→

Adjusting the title next is easy. Open the file src/site/_includes/layouts/note.njk, look out for the <title> tag and change it as follows:

<title>{% if title %}{{ title }}{% else %}{{ page.fileSlug }}{% endif %} ・ {{ meta.siteName }}</title>

siteNameis the name of the page defined in the Obsidinan plugin description. This layout (note.njk) is used for every file except the one that is defined as the home page.

We're going to add all the relevant meta tags next. For this, we are going to leverage the ability to add custom components and create the following new file: src/site/_includes/components/user/common/head/metatags.njk . This file will house all the metadata that are common between "regular" files and the index/main page.

This is what I added:

<link rel="canonical" href="{{ meta.siteBaseUrl }}{{ page.url | url }}" />
<meta name="author" content="{{userComputed.author}}" />

<meta property="og:title" content="{% if title %}{{ title }}{% else %}{{ page.fileSlug }}{% endif %}" />
<meta property="og:image" content="{{  meta.siteBaseUrl }}/img/logo.png" />
<meta property="og:url" content="{{ meta.siteBaseUrl }}{{ page.url | url }}" />
<meta property="og:site_name" content="{{meta.siteName}}" />
<meta property="og:locale" content="{{meta.mainLanguage}}" />

There are also some properties I only want to show up on the main page. These go into src/site/_includes/components/user/index/head/metatags.njk (The only difference is the folder name "index" instead of "common"):

<link rel="alternate" type="application/rss+xml" title="{{ meta.siteName }} - {{ userComputed.description }}" href="{{  meta.siteBaseUrl }}/feed.xml" />
<meta name="description" content="{{ userComputed.description }}">

<meta name="og:description" content="{{ userComputed.description }}" />
<meta property="og:type" content="profile" />

Last but not least, there are some tags I only want to show up everywhere except for the index page. For these pages, it would be great to add a short description to one of the meta tags if it's available. I want to be able to use a front matter key - excerpt - for this, or alternatively the "excerpt" feature explained in this section above. So we create another file with the same name and a similar path, src/site/_includes/components/user/notes/head/metatags.njk:

<meta name="og:type" content="article" />
<meta property="article:author" content="{{ userComputed.author }}">
<meta property="article:published_time" content="{{ userComputed.created }}">
<meta property="article:modified_time" content="{{ userComputed.updated }}">

{%- set description = excerpt or page.excerpt %}

{%- if excerpt  %}
<meta name="description" content="{{ description }}">
<meta name="og:description" content="{{ description }}" />
{%- endif %}

excerpt will be filed by the property we set in obsidian. page.excerpt will be filled if the given note made use of the excerpt modified described above. If neither is present, no description will be generated.

Since we've enabled passing the front matter, let's also hide the 404 from the feed. It's as easy as setting a new property, eleventyExcludeFromCollections, to true in the front matter of the 404.md.

Setting up Pagination

Status: Resolved ✅

Description: I have a few items for which I want to set up an overview page. I can do this with DataView, but this comes at a couple of drawbacks:

Solution: I generally have two types of notes where I need pagination for: Articles and Blips. Their respective "needs" when it comes to pagination differ from one another, so I will describe how I made both work.

Let's start with Articles. I want all my articles to show up as a paginated list. I also want the 3 most recent articles to be part of my front page.

11ty has the concept of collections and provides means to handle collections in various ways. The Digital Garden template makes use of this, as it puts all notes except the index page (and the ones for which we specifically requested to be taken out of any collection witheleventyExcludeFromCollections) into the collection note.

Collections are best defined using tags, so the first step is to assign a tag to all my Obsidian notes which I want to show up in the pagination. A word of caution: If you want to display tags later on as well, you need to be careful with the tag you assign here. Either you will need to live with the fact that any note with the tag you used will show that as a tag in the pagination, or you will have to live with the fact that the tag will not show up even if you want it to. Inline tags will still show up, but beware of conflicts with the "clickable tag" feature.

I decided to use the tag dgarticle.

Next, I want to define a custom filter that turns my dates into a nice human-readable format. I do this in the userSetup.js as follows:

const { DateTime } = require('luxon'); 
//.. other content ..

function userEleventySetup(eleventyConfig) {
  eleventyConfig.addFilter("dateToShortString", function (date) {
     return  DateTime.fromJSDate(date).toFormat("dd LLL, yyyy");
  });
  // .. other content ..
}
//.. other content ..

OK, now let's create the page which will house the pagination itself. This page will live directly within the 11ty repository, as making this work with a note within Obsidian is pretty complex.

Next, let's add a new file, articles.njk. It should look like this:

---js
{
  "updated-date": new Date().toISOString(),
  "title": "All Articles",
  "layout": "layouts/note.njk",
  "permalink": "articles/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber + 1 }}/{% endif %}", 
  "pagination": {
    "addAllPagesToCollections": true,
      "data": "collections.dgarticle",
      "size": 25,
    "before": (paginationData) => {
    const dataToSort = [...paginationData];
    return dataToSort.sort((a, b) => b.data.userComputed.created - a.data.userComputed.created);
    }
  }
}
---

<ul class="paginated-list">
{%- for item in pagination.items %}
  <li>
    <time datetime="{{ item.data.userComputed.created | dateToRfc3339 }}">{{ item.data.userComputed.created | dateToShortString }}</time>
    </time>
    </span>
    <a href="{{item.url | url}}">{{item.data.title}}</a>
  </li>
{% endfor -%}
</ul>

<span>
{%- if pagination.previousPageHref %}
  <a href="{{ pagination.previousPageHref | url }}">⏪ Previous</a>
{%- endif %}
{%- if pagination.previousPageHref and pagination.nextPageHref %}
⋮
{%- endif %}
{%- if pagination.nextPageHref %}
  <a href="{{ pagination.nextPageHref | url }}">Next ⏩</a>
{%- endif %}
</span>

This will create all pages, starting with articles, then articles/1/ , articles/2/ and so on. The lastmod date in the sitemap will be the date on which the site is generated. A pinch of CSS makes this pagination look good.

There's one snag though: Linking from within Obsidian to this will result in a dead link. Since I do want to link to the article overview, I am going to fix this. To do so, I amend the function getAnchorAttributes in .eleventy.js as follows:

function getAnchorAttributes(filePath, linkTitle) {
     const passThroughPaths = ["articles"];
     // .. 
     if (!fileName) {
    // This is from the "Heading Link Fix" above
    permalink= "";
  } else if (passThroughPaths.includes(filePath)) {
    permalink = "/" + filePath;
  } else {
  
  // ..
  }
  // ..

}

OK, with that being done: I also want to display the latest 3 articles on the main page. Let's implement this as well.

I do this directly in Obsidian. The note which should contain the 3 articles gets a new prop: templateEngineOverride will be set to njk,md.

Then, I can simply add the following directly into the note where I want the articles to show up:

<ul>
{%- for item in collections.dgarticle | sort(attribute="data.userComputed.created") | reverse %}
{%- if loop.index <= 3 %}
 <li>
  <span>
  <i>
    <time datetime="{{ item.data.userComputed.created | dateToRfc3339 }}">{{ item.data.userComputed.created | dateToShortString }}</time>
    </time>
    </i>
    </span>
    <a href="{{item.url | url}}">{{item.data.title}}</a>
  </li>
{%- endif%}
{%- endfor %}
</ul>

That's pretty cool!

OK, with Articles now working as expected, I want to set my eye onto the next thing: Blips. For Blips, I don't only want to list their titles (in fact, the titles are rather less interesting). Instead, I want to transclude the entire blip into the overview.

The setup is pretty much the same as for articles. The difference is in what we do to display each blip in the blips.njk:

<!-- other stuff unchanged -->
{%- for item in pagination.items %}
  {{ item.templateContent | safe }}
  {%- if not loop.last %}
  <hr/>
  {%- endif %}
{% endfor -%}

Displaying the first blip in the index file is almost straightforward. It requires adding the following to the Index:

{%- set item = collections.dgblip | sort(attribute="data.userComputed.created") | reverse | first%}
{{ item.templateContent | safe }}

I say almost, because this runs into an error claiming that I access templateContent too early.

This can be solved by adding the following to the frontmatter:

eleventyImport:
 collections: [dgblip]

The space in front of collections is crucial. dgblip is the tag I assigned to this collection. I found it easier to set in Obsidian's source mode.