New Properties and templater prompts and commands?

I’m going to leave this thread now. I feel we’ve diverged to far from the original request, which also has been marked with solved. There could be made a multitude of new requests based upon this thread, but I’m out for now.

Quits Obsidian on iPad alas, so had to revert to old versions for now.

Oh really? I haven’t gotten around to testing the new vesion on mobile. I am going to check it out on the mobile console when I have time sometime this week.

1 Like

Shouldn’t this be <%_* tR = "" _%>? It didn’t work for me until I removed the +. Otherwise, it’s not removing anything.

I’m going to have to read this a few more times to get the full jest of this.

…but what I’m understanding is: if you use Templater ‘code’ in your YAML, you have to create a full block of code, including all properties you currently have in the Template.

For example, I use these along with some ‘standard’ ones:

modification date: <% tp.file.last_modified_date(“dddd Do MMMM YYYY HH:mm:ss”) %>

UID: <% Math.round(((Math.random()10**15)(tp.date.now(“YYYYMMwwDDHHmmss”))/(Math.random()*10**15))) %>

File Creation Date: <% tp.file.creation_date() %>

The ‘modification date’ will never again update though…

I have read this a few times and obviously am missing things. I get the basic functionality ( replacing and adding frontmatter in existing file) working, but the rest of my content ( other headings etc…) are somehow replaced when the script starts running. Any idea why? My basic operation is cmd n - for a new note, followed by a shortcut key for the Templater script. Any direction would be highly appreciated.

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.

NAoeN14

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:

Obsidian_JLe5xn68ZJ

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! :slight_smile:

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.

Obsidian_or1E68IZY2

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.

1 Like

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:

  • create a meeting note from a template,
  • rename it to the date + title - here 2024-04-26 My Meeting,
  • move it to the correct destination folder( here: Meetings/MyMeeting) and
  • then modify the frontmatter as before.

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;