Recently, I've been participating in some very fun TTRPG games. Obsidian has become my favorite tool for taking notes.
One plugin I've found which I quite like is Folder Notes.
I ended up getting a Publish subscription, and wanted mimic the behavior of Folder Notes on the published site.
This weekend, I sat down and hacked on it a bit.
I'm relatively pleased with the end result and wanted to share.
I'm happy to report Obsidian feels pleasantly hackable, it's clearly a tool designed with malleability in mind.
I'm not much of a JS hacker anymore, but this was fun enough that I look foward to doing more.
Besides, it's nice to flex old muscles occasionally.
I found this implementation by gardener
which was a good start, but didn't quite work how I wanted it to. So I took the ideas from it and wrote my own.
Without further ado, add the following to your publish.js
:
const folderNoteExpandableSelector = "div.tree-item-self.mod-collapsible:not(.mod-root)";
const foderNoteIndividualSelector = "div.tree-item-self:not(.mod-collapsible):not(.mod-root)";
function updateFolderNoteExpandable(expandableElem) {
let path = expandableElem.getAttribute("data-path");
let pathWithFolderNote = `${path}/${path.split("/").last()}.md`;
let detectedFolderNote = app.site.cache.getCache(pathWithFolderNote);
if (detectedFolderNote === null || detectedFolderNote === undefined) {
return;
}
expandableElem.classList.add("has-folder-note");
let expandableInnerElem = expandableElem.querySelector("div.tree-item-inner");
expandableInnerElem.addEventListener("click", _ => {
app.navigate(pathWithFolderNote, "", null);
});
let expandableChildren = expandableElem.childNodes;
let replacerElem = expandableElem.cloneNode(false);
replacerElem.replaceChildren(...expandableChildren);
expandableElem.replaceWith(replacerElem);
}
function updateFolderNoteIndividual(individualElem) {
let individualElemPath = individualElem.getAttribute("data-path");
let fileName = individualElemPath.split("/").last();
if (!fileName.endsWith(".md")) {
console.warn("Got an individual update for a folder?", individualElem);
return;
}
let fileNameExtensionless = fileName.replace(".md", "");
let folderNotePath = individualElemPath.replace(`${fileNameExtensionless}/${fileName}`, fileNameExtensionless);
if (individualElemPath == folderNotePath) {
return;
}
let folderNoteSelector = `${folderNoteExpandableSelector}[data-path="${folderNotePath}"]`;
let detectedFolderNoteElem = document.querySelector(folderNoteSelector);
if (detectedFolderNoteElem === null) {
return;
}
individualElem.classList.add("folder-note");
}
function updateFolderNotes(rootElem) {
for (individualElem of rootElem.querySelectorAll(foderNoteIndividualSelector)) {
updateFolderNoteIndividual(individualElem);
}
for (expandableElem of rootElem.querySelectorAll(folderNoteExpandableSelector)) {
updateFolderNoteExpandable(expandableElem);
}
}
function injectFolderNoteObserver() {
let config = { childList: true, subtree: true };
let callback = (mutationList, _observer) => {
for (let mutation of mutationList) {
if (mutation.type !== "childList") {
return;
}
for (addedNode of mutation.addedNodes) {
updateFolderNotes(addedNode);
}
}
};
updateFolderNotes(document);
let observer = new MutationObserver(callback);
let nav = document.querySelector("div.nav-view");
observer.observe(nav, config);
}
injectFolderNoteObserver();
Then, in your publish.css
add this rule:
div.folder-note {
display: none;
}
You apparently need a Custom Domain for this. I had one, so it wasn't a problem for me.
This may break in future versions of Obsidian Publish. I likely won't update this unless I am still using Obsidian Publish myself, and Folder Notes, and notice, and remember to update this page. You might need to hack yourself, so I left it well commented.