Skip to content

Instantly share code, notes, and snippets.

@wmudge
Last active July 18, 2023 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wmudge/ff0561eae8b6bbefad0d26929a00b5c0 to your computer and use it in GitHub Desktop.
Save wmudge/ff0561eae8b6bbefad0d26929a00b5c0 to your computer and use it in GitHub Desktop.
TiddlyWiki to Obsidian.md
// Derived from https://github.com/davidag/tiddlywiki-migrator/blob/master/scripts/safe-rename.js
const fs = require('fs');
const path = require('path');
const entities = require('html-entities');
if (process.argv.length != 4) {
console.log("Wrong parameters. <source directory> <destination directory> expected.");
process.exit(1);
}
const SRC = process.argv[2].endsWith('/') ? process.argv[2] : process.argv[2] + '/';
const DST = process.argv[3].endsWith('/') ? process.argv[3] : process.argv[3] + '/';
const INTERNAL = /<.*?href="#(.*?)".*?>(.*?)<\/a>/g
const EXTERNAL = /<.*?href="(.*?)".*?>(.*?)<\/a>/g
// List all MD filenames in SRC directory
fs.readdir(SRC, (err, files) => {
if (err) throw err;
files
.filter((filename) => filename.endsWith('.md'))
.forEach((filename) => {
fs.readFile(path.join(SRC, filename), 'utf8', (err, data) => {
if (err) throw err;
// Format the links
let body = data.replace(INTERNAL, internalLink);
body = body.replace(EXTERNAL, externalLink);
// Read in the frontmatter
frontmatter = fs.readFileSync(path.join(SRC, filename.slice(0,-3) + ".meta"), 'utf8', (err, data) =>
{
if (err) throw err;
return data;
});
// Create the final Markdown document
let final = [
"---",
frontmatter.trim(),
"---",
"",
body
]
// Write final document
fs.writeFile(path.join(DST, filename), final.join("\n"), 'utf8', err => { if (err) return console.lo
g(err) });
});
});
});
function internalLink(match, target, alias, offset, string) {
let t = entities.decode(decodeURIComponent(target));
let a = entities.decode(alias);
if (t == a) {
return `[[${t}]]`;
} else {
return `[[${t}|${a}]]`
}
}
function externalLink(match, target, alias, offset, string) {
let a = entities.decode(alias);
return `[${a}](${target})`;
}
// See https://github.com/davidag/tiddlywiki-migrator/blob/master/scripts/safe-rename.js
const fs = require('fs');
if (process.argv.length != 3) {
console.log("Wrong parameters. Directory name expected.");
process.exit(1);
}
const PATHNAME = process.argv[2].endsWith('/') ?
process.argv[2] : process.argv[2] + '/';
// List all filenames in dir received as argument
fs.readdir(PATHNAME, (err, files) => {
if (err) throw err;
files.forEach((filename) => {
let newFilename = decodeURIComponent(filename);
// Clean filename
newFilename = newFilename
// Remove accents/diacritics
.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
// Remove any non-safe character for Obsidian files, i.e. *"\/<>:|?
.replace(/[\*"\\\/\<\>:\|?]/g, "");
fs.rename(PATHNAME + filename, PATHNAME + newFilename, (err) => {
if (err) throw err;
});
});
});

I wanted to convert my small-ish TiddlyWiki site to an Obsidian.md vault, and while the instructions I found on forums, et al, were immensely helpful, I still had to hack at the process a bit.

Note My TiddlyWiki installation is a Nodejs server running on Alpine (via a Docker container), but my Obsidian installation is running on Windows, iOS, and others. YMMV.

My journey ...

  1. First, read this forum post and the original blog post to understand the basic workflow.

  2. Copy the two Nodejs scripts, safe-construct.js and safe-rename.js

  3. Create a custom export template in your source installation for the frontmatter section of each converted Tiddler. Adjust this template to your ❤️'s content.

Tiddler template name: $:/core/template/frontmatter

created: <$view field="created" format="date" template="YYYY-0MM-0DD"/>
creator: <$view field="creator" format="text" />
tags:
<$list filter="[all[current]tags[]!is[system]sort[title]]">  - <$view field="title"/>
</$list>

You can read up on the ViewWidget, FieldsWidget, and TiddlerFields to adjust as needed.

  1. Install html-entities from NPM in order to convert any errant HTML entities in filenames.
npm install html-entities
  1. Install pandoc for your OS.

  2. Create a new, empty TiddlyWiki installation with the necessary TW plugins for the export and your source TW, and load the old TiddlyWiki site into this new installation.

tiddlywiki --verbose +plugins/tiddlywiki/markdown --load /src --savewikifolder ./dst

Note Adjust the plugins as needed, e.g. add +plugins/tiddlywiki/tw2parser to read TW2 files (legacy format files)

  1. Change into the dst directory and render the Tiddlers into two files: a .meta file which will have the Obsidian frontmatter and a .html file which will have the body of the Tiddler as standard HTML.
 tiddlywiki --verbose ./dst \
   --render [!is[system]] [encodeuricomponent[]addsuffix[.html]] \
   --render [!is[system]] [encodeuricomponent[]addsuffix[.meta]] text/plain '$:/core/templates/frontmatter'

The first --render produces the HTML body and the second produces the frontmatter header for the final Obsidian Markdown file into the dst/output directory. You might what to run this render process a couple of times to get the resulting files correct.

  1. Rename all of the rendered Tiddlers, i.e. the .meta and .html files. (Adjust script location accordingly.)
node safe-rename.js output
  1. Change into the output directory and convert the HTML files within the directory into Markdown using pandoc.
for f in *.html; do pandoc "$f" -f html-native_divs-native_spans -t commonmark --wrap=preserve -o "${f%.html}.md"; done
  1. Change back to the export directory and make a final directory where the constructed Markdown files will land.

  2. Construct the final Markdown files, converting the HTML links to Obsidian links.

node safe-construct.js output final
  1. Move or copy the final directory into your Obsidian vault.

  2. Profit!

@cpjobling
Copy link

Step seven - no need for ./dst and arguments need to be quoted to avoid interpretation by the shell:

tiddlywiki --verbose \
   --render '[!is[system]]' '[encodeuricomponent[]addsuffix[.html]]' \
   --render '[!is[system]]' '[encodeuricomponent[]addsuffix[.meta]]' 'text/plain' '$:/core/templates/frontmatter'

@cpjobling
Copy link

cpjobling commented Jul 18, 2023

Step seven - no need for ./dst and arguments need to be quoted:

tiddlywiki --verbose \
   --render '[!is[system]]' '[encodeuricomponent[]addsuffix[.html]]' \
   --render '[!is[system]]' '[encodeuricomponent[]addsuffix[.meta]]' 'text/plain' '$:/core/templates/frontmatter'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment