When you insert a template into an already existing file, you canât use the triple-dash fences around new properties. Youâll need to use the processFrontMatter()
method, and when dealing with the current file remember to use the setTimeout()
as described earlier.
Other than that, it comes down to knowing how to code and pass variables around in your script. If you donât know that, youâll need some basic learning of javascript first.
thanks, but I have not used triple-dash at any time.
I just put HELLO before applying the template to the new note and it made that so strange. I did it so that the result could be seen.
If, for example, after applying the template and deleting everything except what is in the code, press crtl+z and my original template appears without the parameter data that we have passed to it by code
If anyone can tell me a solution to what my obsidian is doing to me, I would greatly appreciate it.
Good day!
With triple-dash I mean the code fence / text in front and after the frontmatter, aka ---
. In your last screen shot, you already have a frontmatter with triple-dashes around the father
and son
property. You then proceed to insert the plantilla Favorito
template, which also has a frontmatter, and it inserts the template just as you asked for with another set of triple-dashes.
Instead of inserting that text, from plantilla Favorito
, after the frontmatter which already exists, you should have updated the frontmatter using the processFrontMatter()
method, and do stuff like:
frontmatter["Padre"] = '"[[Listado de Plantillas]]"'
(Possibly with another syntax to ensure the correct formatting of links. Not sure how theyâre treated within processFrontMatter()
as I havenât tested that part)
In other words, when you insert the plantilla Favorito
template, it does just what it is asked to do, and that is insert any text from that template at the current cursor location, and execute any template command blocks as it encounters them. In this case itâll update the father
and son
part of the frontmatter after a slight delay of 200ms. Just as expected.
This is wonderful AlanG, thank you very much!
I have two suggestions for improvement or enhancement, but unfortunately my JS skills are not sufficient to do this myself:
- Do you know how to add property-keys to already existing ones and not overwriting them?
- The disadvantage of using templater templates is, that updating properties in the property pane wonât update templater templates. A workaround of this disadvantage could be, to include normal templates (which will be updated) through templater scripts. ⌠I think there is the function tp.file.include(), which is probably meant for that, but I didnât manage to get this workingâŚ
Thanks a lot in advance!
The last part of the code in this post could help you.
Iâm using the multi-line YAML key value convention Obsidian expects since the introduction of Properties (you can also set in Linterâs YAML section which key values you want to be multi-line) and the script is working well enough for me to use it as boilerplate now). I expect if you change tags
with another key name, it will work too.
Hi gino_m,
wow thanks!!!
Using ChatGPT, I understood that if have to use that snippet of your code:
<%*
// Check if the 'Englishtexttranslated' tag exists before adding it
setTimeout(() => {
app.fileManager.processFrontMatter(tp.config.target_file, frontmatter => {
if (!frontmatter['tags'] || !frontmatter['tags'].includes('Englishtexttranslated')) {
const tagsArray = Array.isArray(frontmatter['tags']) ? frontmatter['tags'] : [];
if (!tagsArray.includes('Englishtexttranslated')) {
tagsArray.push('Englishtexttranslated');
frontmatter['tags'] = tagsArray;
}
}
});
}, 350); // Timeout to allow preceding clipboard operations to complete
_%>
and change âtagsâ to any key I want and change âEnglishtexttranslatedâ to any value I want to add.
If I want to have more properties added, I have to copy that part of the snippet:
if (!frontmatter['tags'] || !frontmatter['tags'].includes('Englishtexttranslated')) {
const tagsArray = Array.isArray(frontmatter['tags']) ? frontmatter['tags'] : [];
if (!tagsArray.includes('Englishtexttranslated')) {
tagsArray.push('Englishtexttranslated');
frontmatter['tags'] = tagsArray;
}
}
Thanks a lot gino!
Thatâs pretty much what I do as a non-coder; take something that works and re-use it.
My recent efforts came with some issues that I first didnât entirely understand, then since using timeouts and a large value (say, 2000ms) seems to have sorted out my issues.
As I understand it, Obsidian doesnât like to have your properties modified while it is in the process of loading the metadatacache + backlinks (+ outlinks). So if I enter a huge file (say, more than 35kbs), I should need to wait to have things settle a bit or effect the changes with a delay.
Hi gino,
I use the following script in order to:
- if the key âKey1â doesnât exist in that file, I want to add that property to that file with the value âhelloâ. If it already exist, I just want to add the âhelloâ-value to that property as a list-element
- if the key âKey2â doesnât exist in that file, I want to add that property to that file
<%*
// Check if the 'value' tag exists before adding it
setTimeout(() => {
app.fileManager.processFrontMatter(tp.config.target_file, frontmatter => {
let key = "Key1"
let value = "hello"
if (!frontmatter[key] || !frontmatter[key].includes(value)) {
const prop_Array = Array.isArray(frontmatter[key]) ? frontmatter[key] : [];
if (!prop_Array.includes(value)) {
prop_Array.push(value);
frontmatter[key] = prop_Array;
}
}
key = "Key2"
value = ""
if (!frontmatter[key] || !frontmatter[key].includes(value)) {
const prop_Array = Array.isArray(frontmatter[key]) ? frontmatter[key] : [];
if (!prop_Array.includes(value)) {
prop_Array.push(value);
frontmatter[key] = prop_Array;
}
}
});
}, 150); // Timeout to allow preceding clipboard operations to complete
await tp.file.move("Atlas/Notes/Things/" + tp.file.title)
_%>
When I use this script it produces the following frontmatter:
---
Key1:
- hello
Key2:
- ""
---
Question
Do you know how can I get rid of that -ââ as âKey2â-value?
You set it to be the empty value, so why are you surprised is empty? What do you want instead?
I guess he just wants the property created with an empty string with no ""
showing up.
A workaround is to put in null
, as I cannot run the script with any types of quotes without getting ""
's in the value field.
- Thatâs
null
, without quotes, by the way.
Also, for a larger vault, adding a timeout larger than the current 150ms could be necessary.
Also, Silias, delete the clipboard word from the comment part. You are not doing clipboard operations, as I told you somewhere else.
Hereâs my solution for merging template properties into existing files using a user script called merge_frontmatter.js
, and the new hooks module (tp.hooks.on_all_templates_executed
) instead of a timeout.
To use the user script, you pass it a properties array with the properties (keys) and values you want added to the properties / frontmatter. For example, hereâs what I use in my concept note template:
<%*
tp.hooks.on_all_templates_executed(async () => {
const date = tp.date.now("YYYY-MM-DD HH:mm");
const parentFile = tp.config.active_file;
const parentLink = app.fileManager.generateMarkdownLink(parentFile, tp.file.folder(true));
const properties = [
{key:'connections', value: null},
{key:'aliases', value: null},
{key:'tags', value: ['concept']},
{key:'status', value: null},
{key:'date-created', value: date},
{key:'date-modified', value: date},
{key:'origin', value: parentLink},
];
await tp.user.merge_frontmatter(properties, tp);
});
-%>
If you just want to add the key without a value, set the value to null. If you want to add multiple values (like multiple tags), use square brackets like so:
{key:'tags', value: ['concept', 'tag2']}
See the full concept note template here, for how itâs used in context of adding properties to the current note:
Concept note usage example
> [!info]+ Definition
> <% tp.file.cursor(0) %>
## Notes
- <% tp.file.cursor(1) %>
> [!tip]+ Unrequited notes
> These notes point directly to this note. But this note doesn't point back (yet).
>
> ```dataview
> LIST
> FROM [[]]
> and !outgoing([[]])
> SORT file.mtime desc
> ```
<%*
tp.hooks.on_all_templates_executed(async () => {
const date = tp.date.now("YYYY-MM-DD HH:mm");
const parentFile = tp.config.active_file;
const parentLink = app.fileManager.generateMarkdownLink(parentFile, tp.file.folder(true));
const properties = [
{key:'connections', value: null},
{key:'aliases', value: null},
{key:'tags', value: ['concept']},
{key:'status', value: null},
{key:'date-created', value: date},
{key:'date-modified', value: date},
{key:'origin', value: parentLink},
];
await tp.user.merge_frontmatter(properties, tp);
});
-%>
I made the merge_Frontmatter.js
user script non-destructive, in that it will not overwrite anything. You can follow the logic from the console logs.
Of course, if there is no exiting frontmatter, it just adds everything as is. But if there is a frontmatter in the note, it makes some checks. First, it checks if the key exists. If it does, it checks is the key is an array. If it is, it adds the value to the array. If the key doesnât exist, it creates the key with the value specified. So it wonât replace a non-array value with another value.
User script
async function mergeFrontmatter (properties, tp) {
try {
let file = await tp.config.target_file;
await Promise.all(properties.map(async (prop) => {
if (app.metadataCache.getFileCache(file)?.frontmatter == null) {
console.log("Frontmatter is empty");
await app.fileManager.processFrontMatter(file, (frontmatter) => {
console.log(`adding new property: ${prop.key} to ${file.basename} with value: ${prop.value}`);
frontmatter[prop.key] = prop.value;
});
} else if (app.metadataCache.getFileCache(file)?.frontmatter?.hasOwnProperty(prop.key)) {
console.log(`${file.basename} contains property: ${prop.key}`);
const value = app.metadataCache.getFileCache(file)?.frontmatter[prop.key];
if (Array.isArray(value) && prop.value != null) {
console.log(`${prop.key} is an array`);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
for (let i = 0; i < prop.value.length; i++) {
console.log(`adding ${prop.value[i]} to ${prop.key} in ${file.basename}`);
frontmatter[prop.key].push(prop.value[i]);
}
});
}
} else {
console.log(`${file.basename} doesn't contain ${prop.key}`);
console.log(`adding property: ${prop.key} to ${file.basename} with value: ${prop.value}`);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[prop.key] = prop.value;
});
}
}));
} catch (error) {
console.error("Error in processing frontmatter: ", error);
}
}
module.exports = mergeFrontmatter;
Thanks for this and the use case with the DV callout as well.
Havenât had time to thoroughly test it but it seems to me that if e.g. tags (or maybe other properties) are present with no value, then it doesnât add the defined values. If there is at least one valid tag value, then it adds them below as expected.
If there is an empty value with tag but the dash is there, then the values are added but not in first position, but 2nd, 3rd, etc.
Same when - null
is below tags
property.
Hi Gino, thanks for testing and the feedback.
I tried to replicate your experience when adding tags to an empty tags property, but I couldnât replicate the issue. In my case, the tag is added. See this test:
Can you clarify this part? Iâm not sure I follow. Which dash are you referring to? Like if you are in source mode, and there is only a dash below the tags:
key? Also, why would there be a - null
?
I did test these scenarios, if I understand them correctly, and could confirm that the tag is added below the empty dash or - null
. But I donât understand why these should be present in any case. If you create a tags
property using the property editor, without adding any values, the format is:
tags: []
.
As for the dataview callout, I canât take credit for that, as I got the idea from the Ideaverse vault I did make some modifications, though, because it wasnât working as intended.
@gino_m Actually, I tested again and got the same result as you this time. I was wrong about the format that the property editor creates. Before testing the previous time, I added a tag with the editor, the removed it again.
This left this format: tags: [ ]
When I just create properties from scratch using ---
in live preview mode, and then add the tags
property using the editor, the format is just: tags:
with nothing following the key.
When I tested in this condition, no tag was added.
Hi again @gino_m,
I think I solved the issue with this refactor:
Refactored mergeFrontmatter script
async function mergeFrontmatter(properties, tp) {
try {
const file = await tp.config.target_file;
const fileCache = app.metadataCache.getFileCache(file)?.frontmatter;
await Promise.all(properties.map(async (prop) => {
if (!fileCache) {
console.log("Frontmatter is empty");
} else if (fileCache.hasOwnProperty(prop.key)) {
console.log(`${file.basename} contains property: ${prop.key}`);
const value = fileCache[prop.key];
if (Array.isArray(value) && prop.value != null) {
console.log(`${prop.key} is an array`);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
for (let i = 0; i < prop.value.length; i++) {
if (!value.includes(prop.value[i])) {
console.log(`adding ${prop.value[i]} to ${prop.key} in ${file.basename}`);
frontmatter[prop.key].push(prop.value[i]);
}
}
});
} else if (!value && prop.value != null) {
console.log(`adding ${prop.value} to ${prop.key} in ${file.basename}`);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[prop.key] = prop.value;
});
}
return;
}
console.log(`${file.basename} doesn't contain ${prop.key}`);
console.log(`adding property: ${prop.key} to ${file.basename} with value: ${prop.value}`);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[prop.key] = prop.value;
});
}));
} catch (error) {
console.error("Error in processing frontmatter: ", error);
}
}
module.exports = mergeFrontmatter;
Itâs still non-destructive (wonât replace existing values), but you can make the current script destructive (will replace existing values) by removing the !value
condition on line 22.
I am experimenting with a version where you can specify the treatment in the properties array with the options: add only, update (replace existing value), and delete.
It seems to be working so far, but itâs a bit lengthy, so Iâm gonna try refactoring it.
Increment should probably be another option, but that could perhaps be automated based on whether the property is a number, or something like that.
Okay, just arrived to my desktop.
Indeed, when linting a file, []
is added.
I may have ended up with zero strings at tags following a mass search and replaceâŚ? I donât quite know.
I tried the new js and is working well or better.
I am not sure in what scenario an empty string in a multi-line array list would be created with the dash (where the âdashâ or âhyphenâ is the standard prefix or whatyoumaycallit) and the - null
value would probably be made if used with Linter (when forcing the multi-line values on certain properties)? (Again, I cannot see or list all scenarios as to what does what and what itâll look like.)
I said âbetterâ because the empty multi-line value still is not overwritten with the new value but is added second in line. Properties UI doesnât dislike this though, truth be told and when you lint the file with Linter, the empty value is duly deleted the new values are bumped back up into first, second, etc. position.
Thanks for testing again. To be clear, I didnât try to solve the issue with tags being added below existing empty dashes, because I didnât see the value of this scenario. What I mean is, there shouldnât be empty dashes or - null
. It would probably be more worthwhile to figure out, why Linter is adding these things, if thatâs the case.
So I only solved the logic issue that prevented the properties value being added, if the key is entirely empty, like tags:
with nothing to follow.
In the scenario you raise, the empty dash or - null
makes the value of the tags
key be considered an array, and therefore, the values you add with mergeFrontmatter
are pushed to the array and appended below.
Thatâs fine. Thanks. Iâm only bringing these up because the thread is public and everyone has different use cases, plugins installed and their own ways to mess up (search and replaces globally may cause what I wrote about and Linter is very well integrated with Obsidian as well so I cannot be sure what may cause - <empty>
or - null
if anything).
As the js script stands now, it should suffice.
But Iâm curious about what benefits the hook and the separate js might give the user?
As someone dipping his toes in scripting (Js for Obsidian and Python for outside of Obsidian use), my challenges usually involve timing issues (with js) and the merging changes automatically
pop-ups â will this script or method prevent those pop-ups? I think I saw it once yesterdayâŚ
The merging changes pop-up issues I raised with @AlanG (author of the date modified updater plugin) back in the day and showed that time and again some gibberish (e.g. âwwworkâ or âdddoesâ) is entered in the editor. I want to eliminate these especially.
When I work, I use Templater scripts to add tags or status values and hop in and out of notes rather quickly, so I need to beware that no rubbish is left in my notes. I even mentioned cases when a full note was exchanged with the content of another noteâŚ
It is a quirk in Obsidian one needs to be aware of and it is about the every 2 second automatic save, which when coinciding with editor changes (including YAML property updates) happens â as far as I knowâŚ
tp.hooks.on_all_templates_executed
is very useful because:
- It removes the need to set fixed and arbitrary timeouts before processing the frontmatter that could either be insufficient or excessive, introducing lag.
- It ensures that the frontmatter is not modified, until the rest of the template has been added and processed. This ensures that the cache has been updated, and it prevents conflicts where one part of the template might overwrite another.
I made the separate userscript because I wanted to be able to merge new properties into existing properties, without modifying the existing properties.
At the time, I was experimenting with some templater scripts that were multi-step. Basically, I was defining some properties based on a note I was extracting information from. Then, I got a template suggester for which template to apply to the new note. The mergeFrontmatter userscript allows the template to seamlessly apply its properties without overwriting the properties defined in the previous step.
So the bottom line is, this allows me to insert templates into any existing file, and the template will apply its properties without any issues, merging them with the existing ones.
I think it does, or at least in my case, I havenât gotten those in a while. But I am not sure. When you say you saw one yesterday, do you mean that you saw one when testing my user script?