Logo Trilium Rocks!

Trilium Rocks

So you want your share to look like this website? Well lucky for you all the fancy stuff that makes this site is open source and freely available on GitHub. In this guide I'll walk you through turning that git repository into this beautiful website.

 

Prerequisites#

Some of these will be more obvious than others

  1. Have Trilium installed and a set of notes to share
  2. Have git (optional, see below) and nodejs installed to build the site files
  3. Be familiar with note attributes and sharing in Trilium

Building The Files#

So first things first, grab a copy of the repository either via git clone or by downloading it as a zip through the GitHub UI. This technically makes the git prerequisite optional. Once you've done that you just need to run a couple commands to build all the files used for the website.

npm install
npm run dist

You should see both a new node_modules directory from installing dependencies, as well as a dist directory which contains all the built files. I emphasize that because there will also be files you need under the src directory. We'll get to that later.

Setting Up Notes#

You can think of this as a “two-step” process where the first step is really really big, and the second step is tiny. Basically step one is to setup all the important files you just built, and step two is to tell your share pages to use that setup.

Meta Tree#

I call this the meta tree, but it's basically a tree of notes that hold the CSS, JS, and templates that are used to make the share page as fancy as it is. For reference, here is what this tree looks like for this website.

Article Image

You can obviously tailor and customize this to fit your own needs, but this should give you a general idea of what we're going for. But no matter how you organize it, be sure that all the notes are shared. You'll notice every note in my tree including the root are shared. If you forget to do this, the Trilium share module will not be able to pick up and use those notes. You'll probably also want to add the #shareHiddenFromTree attribute to prevent them from showing in the navigation.

Root Setup#

So let's go note-by-note and show what setting this up entails. To start with, the root is really just a simple note with a couple attributes. The only necessary one is #shareHiddenFromTree(inheritable). This causes all the descendants to automatically be hidden from the share tree so we don't have to apply that attribute one-by-one. This note needs no content.

Code Notes#

The next note, Rocks CSS, is a CSS Code Note with the attribute #shareRaw. This causes the note to be shared explicitly as the content of the note with no HTML wrapper. This makes it easy to import and use as CSS. The content of this code note should be the dist/styles.css you built earlier.

Similarly the Rocks JS note is a JS frontend code note with the same #shareRaw attribute. This should be populated with the content of dist/scripts.js.

Template Notes#

The next note (and its child notes) are special templates that override Trilium's built-in templates for share pages. This isn't what I would call dangerous as there's no real security implication due to how Trilium's share module is structured. But it is most certainly volatile; If done incorrectly, the share feature will just go kaputt with no easy way to debug it in production. Thankfully, the person that implemented this custom templating feature (me) added a way to try and automatically recover from issues with the templates and revert to the default. This can result in pretty wonky webpages if your JS and CSS are heavily dependent on the custom template, but is nonetheless a bit better than ending up with a blank non-functioning page.

As for the note itself, it is yet another code note but this time of the type Embedded JavaScript. If you don't see this in your dropdown list when creating the note, you'll have to go enable it in Trilium's settings. No special note attributes are needed here, but once you have the note setup go ahead and paste the contents of src/templates/page.ejs inside.

You'll notice the two child notes here have very specific names. These correspond to names used in the code of the main template you just created. They also correspond to two files in the src/templates directory. You can go ahead and set them up exactly like that in Embedded Javascript notes. Just keep the name of the note in mind. If you do want to change the names for whatever reason, you would have to update the template code as well.

Icon#

This can be any icon you want, of any size, of any image format. This one is where you have the most freedom. There are no special attributes for this, it just needs to be an actual image, and not an image pasted inside of a text note. You'll end up using this later on.

 

Share Tree#

Now that you have the hard part all taken care of, let's tackle the share tree. This is the tree of notes you actually want shared to the public. This is something you should already have from the prerequisites. It doesn't have to be anything fancy, just content you want to share. As an example, here's how the share tree for this site looks at the time of writing (with one tree expanded for show).

Article Image

You'll notice that there are only real notes with contents at the bottom of the trees, and everything above it acts like a folder. This is one of the quirks of this specific template that I'll talk a bit about in the next section. Just know that this is what enables the page to have the fancy collapsing navigation to your left.

Now comes the fun part, telling our share tree to use the notes we setup in the last big step. At your root of your share tree (Trilium Rocks! in the example image above), you will need to apply a series of attributes to get things going.

Root Attributes#

AttributeValue
~shareCss(inheritable)This should point to the CSS note you created in the last step.
~shareJs(inheritable)This should point to the JS frontend note from before.
~shareTemplate(inheritable)Point this to the parent template we made in the setup.
~shareLogoSet this to the logo from your meta tree.
#toc(inheritable)Set this to hide this just prevents Trilium from trying to generate the default table of contents.
#shareDescriptionYou can make this any string, it is shown to search engines and places that attempt to embed information about linked content like Discord or Twitter.

Obviously #shareDescription is technically optional, but it really doesn't hurt to set it. The others, though, are all required to get things working properly. You might also note that we only point to the parent template and not the child templates. This is because the child templates automatically get bundled into the parent template by Trilium using the note names. That's why we had to set up those specific names before.

But that's basically all there is to do in order to get your share tree using your new setup! If you're curious about some of the quirks of this template from either a developer perspective or the prospective of a user of this template that just wants to know the pitfalls and features…. Keep reading on.

 

The Features#

Honestly, since this was originally purpose built for this website, there aren't a ton of customizable features to write home about. I'll talk about a couple customizable features that you can take advantage of as a user, then I'll also highlight some general built-in features of this template.

External Links#

You'll probably notice in the left navigation that there a couple of links to external webpages. Normally this is not possible from a Trilium share navigation*. This is one customizable feature courtesy of this template. If you want to add a new external link anywhere in the tree (as long as it has no child notes, see below), you just need to provide a #shareExternal attribute that holds the external URL to link to. It's a pretty straightforward but useful feature.

* This has since been integrated (by me) into Trilium directly using a different attribute, #shareExternalLink.

Share Logo

This one probably seems like a given based on the previous setup guide, but this is actually technically optional in this template. It properly checks whether you have set a share logo or not, and adjusts itself accordingly. So if you happen to not see the need for one, that's totally okay, go ahead and emit it!

Open Graph Tags#

These tags are what allow websites to have fancy embeds and previews in other websites. If you've ever used Twitter you've probably seen a thumbnail and page description for a link. In Discord you may have seen that a link gets an embed with additional text information and maybe an image. These are all powered by these tags

Article Image

Unfortunately, these tags are not included in the default share template. But this template provides them all. It even adjusts the tags for the current note so the link title will change depending on which specific page is linked. Additionally the description is configurable from the #shareDescription attribute mentioned earlier. Currently, the image is hardcoded to Trilium's official screenshot preview on GitHub, but that is very easy to change for your own purposes. This is the same case with the color tag that uses the green from the Trilium logo.

Fancy Table of Content#

If you've used the default Trilium share, you've probably felt the existing table of content feature was a bit lackluster. This template creates its own table of contents that more closely matches the headings in the page.

Article Image

Additionally, it makes the table of contents come alive by actively scrolling along with you indicating your place in the page. Complete with clickable links to heading that scrolls you smoothly to your destination.

Code Highlighting#

The JS bundle we set up earlier in this guide will use Highlight.js (provided by a note in Trilium*) to detect and automatically add syntax highlighting to any and all code blocks on the page.

class MyWidget extends api.BasicWidget {
    get position() {return 1;}
    get parentWidget() {return "left-pane"}
    
    doRender() {
        this.$widget = $("<div id='my-widget'>");
        return this.$widget;
    }
}

module.exports = new MyWidget();

* = This is once again a case of me hard coding note IDs at the time of writing. So if you were looking forward to this feature and find it not working, give me a poke to go and fix it if I haven't already.

Search#

This template and scripts enabled searching through notes both by note names and by content. This is complete with minor previews to help distinguish between notes and trees that may look similar.

Article Image

Interesting tidbit: The backend of searching is something I actually added to Trilium itself a while ago, but the default share template still does not take advantage of this. It's on my to-do list still (sorry zadam!) to create a new version of the default share that's a compromise between a fancy featureful one like this site and a barebones no-nonsense version like the default theme.

Mobile Site#

If you've ever visited this site on your phone, then you've probably noticed that it adapted to your device pretty well right? This template and the theme that comes along with it will automatically adjust for small and handheld devices to act more intuitively using a hamburger slide out type menu.

Article Image

Theme Selector#

Speaking of themes, this template allows you to easily switch between a built-in light and dark mode with dark being the default. It also stores this info per user that way you don't get flashes of the wrong theme as the page loads in.

Article Image

 

The Quirks#

I can't in good conscience write this entire guide without adding some sort of warning or notice or just something to let people know that this template is not suitable for every site. I tried to make this template somewhat generic, but ultimately it was meant for this website you see in front of you. If you want it to look exactly like this or at least very very close, then you're probably set and won't have any issues. But if you have a different way of structuring things, this template falls apart fast.

Content Placement#

In this template and theme, page content should only be placed in notes that have no children. That's because every note that has child note(s) is automatically treated as a folder-esque item instead by the custom template and scripts. This works very very well for this purpose, and it's a great way to logically structure a website, especially one that is documentation-centric. But that doesn't work for all cases, and it certainly isn't the “Trilium way” so-to-speak. Trilium allows you to have content in any and all notes no matter the hierarchy. So if you're used to that, it may be hard to adapt to that requirement in this template.

However, if you're a developer reading this. It's certainly possible to have both worlds. You can simply make it so the navigation treats only the little arrow as the “button” to expand the tree. That would leave the note label as a clickable link that can then serve and show content from the note. I did consider this when creating this template, but in the name of user experience I opted against it. I felt that it was better UX to have a larger area to click than a tiny arrow, and it also felt a bit more intuitive and akin to other note sharing webpages (*cough cough* Obsidian).

Custom Pages#

If you need pages that don't look like the others, or have slight variations, you ultimately have two choices. You either create a separate new template for that page and override the ~shareTemplate attribute for that note specifically. Or you alter the existing template to add a new conditional like checking for a specific attribute on a note.

If you check the code of the existing template, you'll see that I opted for the latter when it came to implementing the ETAPI page here on this site. On the ETAPI note, I provide a #shareSwagger attribute with a value that points to Trilium's OpenAPI definition file. Using this single attribute the template can determine that it not only needs a custom layout, but it needs to set up and and initialize the Swagger library to be used. That Swagger library is then given the value of the attribute which is used to display the actual API definitions. You'll probably also notice in that same conditional that (at the time of writing) I hard coded the note IDs for my copy of the Swagger library rather than using attributes or relations. Hopefully by the time you're reading this I've fixed it.