Agreed. I am still learning how Obsidian does all this black magic, so I am not sure where I am supposed to place the JavaScript file and where the template code is supposed to live.
Also, what was the final version of the JavaScript code?
Agreed. I am still learning how Obsidian does all this black magic, so I am not sure where I am supposed to place the JavaScript file and where the template code is supposed to live.
Also, what was the final version of the JavaScript code?
Hi Feralflora
I have been studying your code to understand how to proceed with mine. As you seem to have understanding of this, I need a little help, please.
In the code below:
tp.hooks.on_all_templates_executed(async () => {
const filePos = await tp.file.find_tfile(tp.file.path(true));
console.log("file position", filePos);
// get the creation date
const createDate = await tp.date.now("YYYY-MM-DDTHH:mm:ss");
// get the fileMetadata
let fileCache = app.metadataCache.getFileCache(filePos).frontmatter;
console.log("frontmatter cached", fileCache);
});
Why would fileCache be empty?
I am following the normal cmd n - for new note - hotkey for templater shortcut methodology.
If I implement the normal code as described in these posts, the properties of the âuntitledâ new note gets replaced, the new ones gets added, the rest of the headings (contents) are deleted. I have traced the problem to the fileCache variable - essentially the frontmatter that will be accessed in your merge_frontmatter user function, being empty. I have an idea that it has to do with timing, but not sure. I am stumped now!
Any help or pointer will be greatly appreciated.
Many thanks in advance.
Hi Henry,
Itâs best to make new help posts for new questions, as this thread is getting very long and unwieldy.
Anyway, I looked and tested your code there, and it logged the frontmatter of the note that I executed it in without issues. Does the file that you execute it in actually have any properties / frontmatter?
If itâs a new note, and the code you shared here is the only template in it, then of course nothing is logged, because the new note is empty. Is that the case?
As for the issue with content being deleted, I would have to see your full template for that. I guess it has to do with how the template inserts the content.
Hi Feralflora
Many thanks for the reply, much appreciated. Sorry for budging in here, didnât know how to relate to you or this post in new post.
How would I send you the full template? Thanks again for the help.
Hi
Please excuse the ignorance on my part. Iâm new to this. I think I figured out a way to copy the complete template.
---
Created:
Title: Text
Subject: Text
---
---
<% moment(tp.file.title,'YYYY-MM-DD').format("dddd, MMMM DD, YYYY") %>
## Attendees
- John
- Hendrik
-
## Agenda
- CallGate
## Notes
-
## Attachments
## Next meeting
- [ ]
## Action Items
<% tp.file.cursor() %>
---
<%*
const shortTitle = "testing";
const subject = "Test subject";
tp.hooks.on_all_templates_executed(async () => {
const filePos = await tp.file.find_tfile(tp.file.path(true));
//const filePos = app.workspace.getActiveFile();
console.log("file position", filePos);
// get the creation date
const createDate = await tp.date.now("YYYY-MM-DDTHH:mm:ss");
// get the fileMetadata
let fileCache = app.metadataCache.getFileCache(filePos).frontmatter;
console.log("frontmatter cached", fileCache);
// change the frontmatter
const properties = [
{key:'Title', value: shortTitle, treatment: 'update'},
{key: 'Subject', value: subject, treatment: 'update'},
{key: 'Created', value: createDate, treatment: 'update'},
{key: 'Tags', value: null, treatment: 'add'}
];
await tp.user.merge_frontmatter_function2(filePos, properties);
});
-%>
Youâre welcome. You can tag people and link to posts / comments / quotes, in another post.
I tested your full template, as-is, and it worked without any note content being deleted. I used the âCreate new note from templateâ command to create a new note with the template applied.
In some instances, the frontmatter changes were reverted (you can use control + Z to undo the reversion and see which frontmatter changes were applied), most likely due to some timing issue (race condition), as you mentioned. But the headings and such in the note were never deleted in my tests.
Btw, the following only works for notes with dates as their title:
<% moment(tp.file.title,'YYYY-MM-DD').format("dddd, MMMM DD, YYYY") %>
Also, not sure if you are aware, but the note doesnât have to have any frontmatter to begin with. merge_frontmatter will add it as needed. Demo:
Hi
Thanks so much for your time and effort. Greatly appreciated! The strange thing is, that if I use the âmagicâ command like you did - âCreate new note from templateâ all works perfectly!!
If I create a new note with cmd n followed by the shortcut for the Templater assigned keys for the template, I run into all sorts of the described problems.
I guess my understanding of templates and Templater and there differences, is just lacking. What I find frustrating is that the documentation seems sketchy and mostly incomplete.
Thankfully there are people like you!!
Coming from a Golang background (documentation extremely complete), I find this frustrating. Not being able, or not having enough knowledge of the editing and debugging tools, for javascript, probably adds to this. I have tinkered with typescript(types), but not having a good grounding in javascript messes me around.
The Obsidian workflow I find interesting and challenging. I can see the potential, but battle with the javascript running inside javascript running inside javascript when it comes to running user functions inside Templater inside Obsidian.
Again, thanks so much for the help and understanding.
No worries!
Turns out that this is a known issue, which was previously resolved, but has recently returned, so the issue was reopened:
I found a way to reproduce the issue with a minimal example, which I adapted from your template. The key thing seems to be the number of blank lines in the empty note. As far as I can tell, the issue only occurs when the empty note has only a single blank line. Add more lines, and the issue doesnât occur.
So it has nothing to do with the userscript or anything else, besides the usage of tp.hooks.on_all_templates_executed
+ app.fileManager.processFrontMatter
.
To be clear, the Templates core plugin and the Templater community plugin have no relation to each other, and itâs best to use one or the other.
Yeah, the docs could definitely be better.
Great! Thought I was loosing it somehow. Thanks for enlightening me!
Thanks again for all the effort.
Hi Feralflora
If you are interested, here is a related problem (my initial purpose) to which you might know the answer.
The aim is to:
2024-04-26 My Meeting
,The strange thing is the first steps work, but the frontmatter does not get modified as when running it without the first steps.
Here is the code for you to try if you so desire.
---
Created:
Title: My Meeting
Subject: Text
---
<%*
let title = tp.file.title;
let shortTitle = "My Meeting";
let subject = "";
// get the relative path - includes the filename
let relPath = await tp.file.path(true);
console.log("relative path", relPath);
// if newly created file
if (title.startsWith("Untitled")) {
title = tp.date.now("YYYY-MM-DD") + " " + shortTitle;
console.log("title", title);
console.log("shortTitle", shortTitle);
subject = await tp.system.prompt("Subject?");
}
// rename the file
await tp.file.rename(title);
// get the correct folder
const folder = await app.vault.getAbstractFileByPath("Meetings/MyMeeting");
console.log("folder", folder);
// get the file's tfile instance
const fileName = await tp.file.find_tfile(title);
// make the destination - folder/filename
const dest = folder.path + "/" + title;
console.log("destination", dest);
// move the file to the destination folder
await tp.file.move(dest, fileName);
// modify the frontmatter
tp.hooks.on_all_templates_executed(async () => {
const filePos = await tp.file.find_tfile(tp.file.path(true));
//const filePos = app.workspace.getActiveFile();
console.log("file position", filePos);
// get the creation date
const createDate = await tp.date.now("YYYY-MM-DDTHH:mm:ss");
// get the fileMetadata
let fileCache = app.metadataCache.getFileCache(filePos).frontmatter;
console.log("frontmatter cached", fileCache);
// change the frontmatter
const properties = [
{key:'Title', value: shortTitle, treatment: 'update'},
{key: 'Subject', value: subject, treatment: 'update'},
{key: 'Created', value: createDate, treatment: 'update'},
{key: 'Tags', value: null, treatment: 'add'}
];
await tp.user.merge_frontmatter_function2(filePos, properties);
});
-%>
## Attendees
- John
- Hendrik
Please excuse the clumsy code. I need to log everything to see the steps.
Hi again, Hendrik
Please make a new help post, if you want help with this issue.
Many thanks, did at Possible timing issues when renaming, moving a newly created note and then modifying frontmatter
Thank you very much. Thatâs what I need now.
I think I am going mad here. I have two problems with this thread.
The first is that I canât find anywhere the merge_frontmatter_function2
script, which is called in several of the examples. There are numerous versions of merge_frontmatter_function
and the most recent of these almost works.
That is to say it does update the frontmatter exactly as I would like, but it also adds a copy of the updated frontmatter into the note I am calling it from. This happens after the frontmatter has been modified, as I can tell by Ctr-Z
-ing the file.
I canât see anything in the code that should cause this to happen. It doesnât seem to happen to anyone else. What am I doing wrong?
fwiw I am using this template
<%*
tp.hooks.on_all_templates_executed(async () => {
const date_modified = tp.date.now("YYYY-MM-DD HH:mm");
const date_created = await tp.file.creation_date("YYYY-MM-DD HH:mm")
// const parentFile = await tp.config.active_file;
// Use tp.config.target_file to target the current file
const targetFile = await tp.config.target_file;
console.log("Targetfile is ..." + targetFile.basename);
// const parentLink = app.fileManager.generateMarkdownLink(parentFile, tp.file.folder(true));
const properties = [
// Adding keys without values
{key:'created_date', value: date_created},
{key:'modified_date', value: date_modified, treatment: 'update'}
// Passing a variable as a value
//{key:'origin', value: parentLink},
// Deleting a key
//{key:'toDelete', treatment: 'delete'}
];
await tp.user.mergefrontmatter(targetFile, properties);
});
-%>
to call this script
/**
* Merge frontmatter properties into a target file's frontmatter.
* @param {Tfile} target - Target's TFile
* @param {Array<Object>} properties - Array of property objects to merge.
* @param {string|null} [override=null] - Optional parameter to specify override behavior.
*/
async function mergeFrontmatter(target, properties, override = null) {
try {
// Get the target file's frontmatter cache
const fileCache = app.metadataCache.getFileCache(target)?.frontmatter;
// Object containing treatment functions for different property treatments
const treatments = {
'delete': (frontmatter, prop) => {
console.log(`Deleting ${prop.key} from ${target.basename}`);
if (override !== 'safe') delete frontmatter[prop.key];
},
'custom': (frontmatter, prop, value) => {
if (override !== 'safe') {
try {
const customFunction = new Function(`return this.${prop.code}`);
const newValue = customFunction.call(value);
console.log(`Changing ${prop.key} from "${value}" to "${newValue}" using custom code: ${prop.code}`);
frontmatter[prop.key] = newValue;
} catch (error) {
console.error(`Error applying custom treatment ${prop.code} for ${prop.key}: ${error}`);
}
}
},
'increment': (frontmatter, prop) => {
if (override !== 'safe') {
console.log(`Incrementing ${prop.key} by ${prop.value} in ${target.basename}`);
frontmatter[prop.key] += prop.value;
}
},
'add': (frontmatter, prop, value) => {
if (!value || (value && override === 'destructive' && value !== prop.value)) {
console.log(`Assigning ${prop.value} to ${prop.key} in ${target.basename}`);
frontmatter[prop.key] = prop.value;
} else if (value && Array.isArray(value) && prop.value != null) {
arrayPush(target, frontmatter, value, prop);
}
},
'update': (frontmatter, prop, value) => {
if ((override !== 'safe') && (value !== prop.value)) {
console.log(`Updating ${prop.key} to ${prop.value} in ${target.basename}`);
frontmatter[prop.key] = prop.value;
}
}
};
// Callback function to handle frontmatter
const frontmatterCallback = (frontmatter) => {
properties.forEach((prop) => {
const value = fileCache?.[prop.key];
// Check if property exists, if not, add it
if (!fileCache || !fileCache.hasOwnProperty(prop.key)) {
console.log(`${target.basename} doesn't contain ${prop.key}`);
console.log(`Adding property: ${prop.key} to ${target.basename} with value: ${prop.value}`);
frontmatter[prop.key] = prop.value;
// If property exists, handle it according to treatment type
} else {
console.log(`${target.basename} contains property: ${prop.key}`);
if (treatments.hasOwnProperty(prop.treatment)) {
treatments[prop.treatment](frontmatter, prop, value);
} else {
// Default treatment
treatments['add'](frontmatter, prop, value);
}
}
});
};
// Process frontmatter of the target file
await app.fileManager.processFrontMatter(target, frontmatterCallback);
} catch (error) {
console.error("Error in processing frontmatter: ", error);
}
}
function arrayPush(target, frontmatter, value, prop) {
if (Array.isArray(value) && prop.value != null) {
console.log(`${prop.key} is an array`);
const valueSet = new Set(value);
for (const item of prop.value) {
if (!valueSet.has(item)) {
console.log(`Adding ${item} to ${prop.key} in ${target.basename}`);
frontmatter[prop.key].push(item);
}
}
}
}
module.exports = mergeFrontmatter;
Than you very much, I finally fixed my problem
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.