I wanted to create a templater template, that would update a timestamp in my metadata not automatically, but if I manually ask (with hotkey binding). While I have several useful simple templates with templater, I’m very new to scripting…
And basically tried imitating it. I copied the mutate_frontmatter.js to my templater scripts folder and I made a template in my templates folder that has:
I was expecting that if I trigger this template with the hotkey binding, it would update the frontmatter “updated” value with the current timestamp.
Instead I run into an error, that prints in the console:
plugin:templater-obsidian:61 Templater Error: Template parsing error, aborting.
tp.user.formatted_frontmatter is not a function
log_error @plugin:templater-obsidian:61
errorWrapper @plugin:templater-obsidian:81
await in errorWrapper (async)
append_template_to_active_file @plugin:templater-obsidian:3700
callback @plugin:templater-obsidian:3978
tj @app.js:1
e.executeCommand @app.js:1
e.onTrigger @app.js:1
e.handleKey @app.js:1
e.handleKey @app.js:1
t.handleKey @app.js:1
e.onKeyEvent @app.js:1
So, if I understand correctly, it doesn’t recognize the function (although in my templater settings it shows as found and I have enabled user functions).
Ok, I guess posting here just makes me realize things: it was using a different function as well. Copied that and now there is no error, but it doesn’t update the metadata, but creates a new one where my cursor happens to be…
And that seems to be the intended purpose of those scripts. I can’t see any code suggesting it would actually change the existing frontmatter, but rather lift the existing frontmatter and update a value before producing a new frontmatter.
In other words, these functions seems to be constructed for a context where you replace the existing frontmatter, and not as an insertion which is how you call it now.
So, maybe you’ll get intended result of you work with source mode in properties, and select existing frontmatter before you trigger the template. (I’ve not looked extremely hard into the logic, so I might be wrong, but this is how I see the presented code)
I asked, on Discord, if there was a way to update an existing updated date/time key in Properties without having to go to Source mode (as I was using a one liner Templater template before Properties came to life )
Zachatoo kindly gave me this Templater template :
<%*
const file = tp.file.find_tfile(tp.file.title);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
// Replace "updated" with the key you use
frontmatter["updated"] = tp.file.last_modified_date("YYYY-MM-DD HH:mm:ss");
});
-%>
… which works perfectly to update my updated key.
Now if I completely misunderstood what was asked here: I am sorry and please, just ignore this post
You’re spot on with a better solution, which indeed addresses the question in place, as this uses the Obsidian API to trigger a change to the frontmatter, instead of updating the file directly by yourself.
Thank you! This is exactly what I wanted! I’m just not very good at these (yet). I tried something similar, but obviously was missing something, as that didn’t work, but this did.
Anyway, thanks for letting me know how to do it in a more simple way
I’m not sure if it’s the first one or a random one, it’s definitely arbitrary.
Fun fact: you can actually pass a file path instead of just a file name to tp.file.find_tfile to consistently get the file you want. You can pass as little or as much of the path as you want/need.
Edit: Now that I’ve actually read the post and the context of this thread, you can replace tp.file.title with tp.file.path(true) to consistently always edit the correct file.
<%*
const file = tp.file.find_tfile(tp.file.path(true));
await app.fileManager.processFrontMatter(file, (frontmatter) => {
// Replace "updated" with the key you use
frontmatter["updated"] = tp.file.last_modified_date("YYYY-MM-DD HH:mm:ss");
});
-%>
Hi there, I am facing a similar issue.
I am using the code provided by @Zachatoo. It seems to work briliantly, but somehow it is overriding text which I also want to be shown after applying the template. Not everytime but sometimes, and I could note figure out the issue.
My template:
<%*
const file = tp.file.find_tfile(tp.file.path(true));
await app.fileManager.processFrontMatter(file, (frontmatter) => {
// Replace "updated" with the key you use
frontmatter["updated"] = tp.file.last_modified_date("YYYY-MM-DD");
});
%>
## Hello
this is a test
After applying the template the note it should look like this:
---
updated: 2023-09-15
---
## Hello
this is a test
But it only shows the frontmatter:
---
updated: 2023-09-15
---
Surprissingly, if i click Ctrl+z I see the text, but not the frontmatter, suggesting that somehow the frontmatter-part is overriding everything else.
I am not an expert in this, but I tried to find a solution in the web the whole day, but could not figure it out.
Using the processFrontMatter() on the current file, especially when the file is also being changed by the template itself, can very easily cause race conditions as to whichever process changes the file first/last.
As such I’ve often triggered this function through a slight delay, so try wrapping the function call within a setTimeout(), something like:
<%*
const file = tp.file.find_tfile(tp.file.path(true));
await setTimeout(
async() => {
await app.fileManager.processFrontMatter(
file,
frontmatter => {
// Replace "updated" with the key you use
frontmatter["updated"] = tp.file.last_modified_date("YYYY-MM-DD");
})
},
2000) // 2000 is 2 seconds, adjust as needed
-%>
## Hello
This is a test
Be aware that this will trigger two saves in itself, as the template execution does one save operation, then there is the delay, followed by the frontmatter update, and another save.
( If you have the wrong delay, you’re also allowing yourself to potentially change the file in between, which can be fun, but also mildly confusing. Try changing the delay to 10000 (aka 10 seconds), change the file, and see what happens… Do this on a test file!!! )
Also to reduce the risk of other race conditions occuring, try keeping the code, and thusly execution time, of the inner loop changing the frontmatter to a minimum. Any lengthy calculations/preparations should be done on the outside of the setTimeout() function.
2 seconds is probably overkill for most templates. If your template is fairly simple, 100 ms is plenty, and not very noticeable. I’d start small and bump it up if you start seeing issues.