Instead of writing your Templater JavaScript command directly in the command block (<%* do some JavaScript %>) you can write the JS in a script.js
file and call that from a Templater command.
Although it does require two files instead of one, you end up with a much cleaner Templater command. The JS file can be written using an actual code editor with proper syntax highlighting and it makes debugging problems easier. My opinion is that this approach, although requiring a bit more setup, is easier for sharing more complex Templater commands with end users.
This guide assumes you already have Templater installed and a Templates folder defined in the Templater Settings.
This is the folder where you'll store the .js
files for Templater to call
Now that you've setup your JS folder, it's time to add your first Javascript file. Let's create a simple script that prompts for input and then displays that input in a notification (not super useful in reality, but should show some basics).
Open up VSCode or your favourite code editor and create a new file notice.js
inside the TemplaterJS folder in your vault. Paste the following into the newly created file:
async function notice(tp) {
const text = await tp.system.prompt("What's Good?")
new Notice(text, 5000)
}
module.exports = notice
We'll breakdown this script to understand it below
Now that we have our JavaScript, we can write a Templater command that calls it. Create a new note in your Templates folder and name it Notice. Paste the following (as plain text!):
<%* tp.user.notice(tp) %>
it appears that Templater uses the name of the JS file and not the function name when you call the user command. I suggest naming the file the same as your exported function to avoid confusion.
Open up the Command Palette and choose Templater: Insert Template. Chose the new Notice template you just created. A prompt should appear, type in something fun and hit enter. If everything was copy/pasted correctly, you should see a notification pop up with what you typed.
So what happened? Let's break this down a bit, so you can understand and maybe start writing you're own scripts!
async function notice(tp) {
We create a new function called notice
. Whatever we set as parameters to notice
are the arguments we provide in the Templater command. In this case, we want to run other tp
methods, so we set a notice(tp) parameter. If we wanted to pass other options, we could include additional parameters.
It's an async
function because we're doing some asynchronous JavaScript. Read more about async/await here.
const text = await tp.system.prompt("What's Good?")
Because we passed in tp
, we can use all of the Templater command methods! In this case we're displaying a prompt.
new Notice(text, 5000)
Because our script is running inside Obsidian, we also have access to the full Obsidian API! In this case we're creating a new Notice
and displaying the text
provided in the prompt. We could also access anything off of app
like app.vault
or app.workspace
.
module.exports = notice
This export line is important! This is how Templater will be able to read the notice
function we created. Just make sure to export your user function.
Because we've put all our logic inside the notice.js
file, the Templater Command is pretty simple.
<%*
This specific command doesn't have any expected output, that's why we're using the *
version instead of <%
. If we wanted to return something from our JS file, we could use the standard Templater syntax.
tp.user.notice(tp)
This is calling the function we created in notice.js
. Two things to note here:
tp.user
notice(tp)
parameter when writing the notice
function? We're supplying tp
as an argument when calling the function. We could supply additional arguments if our JS function required them.Now that we have the basics, let's look at a more advanced example. We're going to take a command that creates a new block-reference:
<%*
let cmEditorAct = this.app.workspace.activeLeaf.view.sourceMode.cmEditor;
let curLine = cmEditorAct.getCursor().line;
cmEditorAct.setSelection({ line: curLine, ch: 0 }, { line: curLine, ch: 9999 });
function createBlockHash() {
let result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < 7; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
let id = createBlockHash();
let block = ![[${tp.file.title}#^${id}]].split("\n").join("");
navigator.clipboard.writeText(block).then(text => text);
tR = tp.file.selection() + ^${id}.split("\n").join("");
%>
And convert it into a JS file + Command. We won't walk through this, but there will be comments in the codeblocks. Some of the. code will be cleaned up as well, but don't worry about that too much.
Remember, this needs to be inside your TemplaterJS folder
function addBlockRef(tp) {
//Use the Obsidian API to get the CodeMirror Editor
const editor = app.workspace.activeLeaf.view.editor
const cursorLine = editor.getCursor().line
//Set the selection in the CodeMirror Editor
editor.setSelection({line: cursorLine, ch: 0}, {line: cursorLine, ch: 9999})
const id = createBlockHash();
const block = `![[${tp.file.title}#^${id}]]`.split("\n").join("");
// Copy the block reference to the clipboard
navigator.clipboard.writeText(block).then(text => text);
//Return the selected text and the generated block id
return tp.file.selection() + `^${id}`.split("\n").join("");
}
//A simple function to create a random block id
function createBlockHash() {
let result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < 7; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
//export the addBlockRef function
module.exports = addBlockRef
Remember, this should be plaintext in a note inside your Templates folder.
<% tp.user.addBlockRef(tp) %>
Obsidian Sync now supports syncing all file types! The scripts below are no longer needed!.
Obsidian Sync won't sync your .js
files inside of the TemplaterJS folder. Here is a couple of Templater Commands that will convert the .js
files to Markdown for syncing and then convert back to .js
once synced.
Create a file called sync.js
inside the Templater JS folder.
async function jsToMarkdown({js: jsFolder, md: markdownFolder}) {
await checkFolder(markdownFolder);
const jsTemplater = app.vault.getAbstractFileByPath(jsFolder).children;
jsTemplater.forEach(async (file) => {
const content = await app.vault.cachedRead(file);
const mdFile = file.name.concat(".md");
const mdFilePath = `${markdownFolder}/${mdFile}`;
const exists = await app.vault.exists(mdFilePath);
if (exists) {
const mdFile = await app.vault.getAbstractFileByPath(mdFilePath)
await app.vault.modify(mdFile, content);
}
if (!exists) {
await app.vault.create(mdFilePath, content);
}
});
}
async function jsFromMarkdown({js: jsFolder, md: mdFolder}) {
await checkFolder(jsFolder);
const mdTemplater = app.vault.getAbstractFileByPath(mdFolder).children;
mdTemplater.forEach(async (file) => {
const content = await app.vault.cachedRead(file);
const jsFile = file.name.replace(".md", "")
const jsFilePath = `${jsFolder}/${jsFile}`;
const exists = await app.vault.exists(jsFilePath);
if (exists) {
const jsFile = await app.vault.getAbstractFileByPath(jsFilePath);
await app.vault.modify(jsFile, content);
}
if (!exists) {
await app.vault.create(jsFilePath, content);
}
});
}
//nabbed from @zsviczian's awesome settings sync scripts
const checkFolder = async (folderPath) => {
const folder = app.vault.getAbstractFileByPath(folderPath);
if (!folder) await app.vault.createFolder(folderPath);
};
function sync() {
return {jsToMarkdown, jsFromMarkdown}
}
module.exports = sync
Create two notes inside your Templates folder (remember to paste this as plain text):
Convert from .js
to a markdown file
<%* tp.user.sync().jsToMarkdown({js: "TemplaterJS", md: "TemplaterMD"}) %>
Convert from a markdown file to .js
<%* tp.user.sync().jsFromMarkdown({js: "TemplaterJS", md: "TemplaterMD"}) %>
The arguments in the above commands allow you to specify folder names in case you're using something different than TemplaterJS or want the markdown sync folder to be named something else.
Feel free to hit me up on the OMG Discord @shabegom if you have questions or run into problems with any of the above!
I hope this is a good introduction to using custom JavaScript commands in Templater! Enjoy!