Templater: open file in new tab and append to end of file

First, search the help docs and this forum. Maybe your question has been answered! The debugging steps can help, too. Still stuck? Delete this line and proceed.

What I’m trying to do

want to append to a newly opened file

Things I have tried

taking snippets from here and there, put together this:

<%*  
const files = app.vault.getMarkdownFiles().map(file => {
  const fileCache = app.metadataCache.getFileCache(file);
  if (fileCache?.frontmatter?.aliases) {
    file.display = `${file.basename}\n${fileCache.frontmatter.aliases.join(", ")}`;
  } else {
    file.display = file.basename;
  }
  return file;
});
const selectedItem = (await tp.system.suggester(item => item.display, files)).basename

const note = tp.file.find_tfile(`${selectedItem}.md`);
this.app.workspace.getLeaf(true).openFile(note);

await new Promise(resolve => setTimeout(resolve, 250));

this.app.workspace.activeLeaf.view.editor.setCursor({line: 99999, ch: 0});
tR += "\n\n"  
_%>

after this, I put in queries that uses the title of the file i opened dynamically
the query is many lines and that’s why I want to use templater and not a js file, but now i think because of timing issues – that chatgpt could not solve – i cannot do it the way i want??

i tied everything: set focus, await promise, more time timeouts, removing this, nothing worked

What the script does now

it appends not in the newly opened leaf, but in the one I triggered from

which also begs the question: you can only run templater from an active file??

thanks for any pointers…

To be clear, you want to append a copy of the currently active note‘s contents to the end of the note chosen in the suggester, right?

Thanks!

hi

no, the information that i want to put to the end of the currently active, templater-opened file comes after _%>

but currently no matter what i do, that information gets pasted in the file i triggered from – that could be the templater file i was editing or any file because you cannot run the templater file when no file is open…

why i’d prefer this templater js method is because i can put after _%> any text without having to add \n – or even \\n – and lots of escape characters
you see i want to put inline queries temporarily to the file, which i will delete with apply patterns plugin when i’m done…

something tells me there are limitations with templater…

this was my original template:

<%*  
this.app.workspace.activeLeaf.view.editor.setCursor({line: 99999, ch: 0});
tR += "\n\n"  
_%>

appended content comes here

but then i ran a commander macro to open a file, wait 4-5 secs and run the small snippet…then it worked…

this method would enable me to not use quick switcher (CMD + O) but my own switcher (SHFT + CMD + O) that offers to search with aliases and open the selected file…

it is the next step that is the problem…

i tried to do it in two steps: two files…but there are timing issues… it must be one templater js file but i’m afraid i need to use user script and a lots of \\n’s and stuff

From the template’s perspective, when you update tR it’s only talking about the initial note.

But you can write to the second note using standard Obsidian commands:

const selectedItem = (await tp.system.suggester(item => item.display, files)).basename
const note = tp.file.find_tfile(`${selectedItem}.md`);

// Like this:
await app.vault.adapter.append(note.path, 'Some new text to add')

await new Promise(resolve => setTimeout(resolve, 250));
app.workspace.getLeaf(true).openFile(note);
3 Likes

this works nicely, thanks!
i naively thought i can only paste content to the active or open file…

but the issues you caused me with your ( and backtick syntax …dear me… had to use ${selectedItem} in place of <%tp.file.title%> as well as i had constant parsing probs…

btw, i looked in the obsidian typescript docs and couldn’t find something like this (app.vault.adapter.append): do you take these from developers’s plugins or where?

2 Likes

Bookmark this file and search it for likely keywords whenever you want to find any function :grin:

https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts

To find out where the function appears in the app object, you can either browse backwards through that file, or you can browse fowards through the app object by typing app into the dev console in Obsidian (Ctrl + Shift + I), and press Enter.

3 Likes

will do…

many thanks…

have a nice day!!

Found some good stuff here, thanks to the participants.

I changed some of the script to suit my needs: I found that if these temporary queries are above the main content, they are less likely to start querying the page again and again when I move my cursor to edit the material and come back to the top to check and click on results.

<%*
const files = app.vault.getMarkdownFiles().map(file => {
  const fileCache = app.metadataCache.getFileCache(file);
  if (fileCache?.frontmatter?.aliases) {
    file.display = `${file.basename}\n${fileCache.frontmatter.aliases.join(", ")}`;
  } else {
    file.display = file.basename;
  }
  return file;
});
const selectedItem = (await tp.system.suggester(item => item.display, files)).basename;

const note = tp.file.find_tfile(`${selectedItem}.md`);

const insertedContent = `\n\n%%\n\`\`\`query\nfile: /^${selectedItem}.md/ /\\b[a-záàäæãééíóöőüű]+\\s*=\\s+[^\`]*?(?=\\s|\\z)|(?:(?:German|English|Greek|Hebrew|Arabic|Slavic|Turkic|Turkish|Ossetian|Alanian|Avestan|Sanskrit|Persian|Egyptian|Sumerian|Accadian|Akkadian|Assyrian|Assyr-Babilonian|Hittite[^\`]*?))\\s([^\`]+?)\\s/\n\`\`\`\n\`\`\`query\nfile: /^${selectedItem}.md/ /(Evernote|otherstuffiwanttocommentout)(?!.*\\%\\%)/\n\`\`\`\n%%`;

const fileContent = await app.vault.read(note);
const separator = '---';

const firstSeparatorPos = fileContent.indexOf(separator);
const secondSeparatorPos = fileContent.indexOf(separator, firstSeparatorPos + 1);

if (secondSeparatorPos !== -1) {
  const updatedContent = fileContent.slice(0, secondSeparatorPos + separator.length) +
    `${insertedContent}` +
    fileContent.slice(secondSeparatorPos + separator.length);
  
  await app.vault.modify(note, updatedContent);
}

await new Promise(resolve => setTimeout(resolve, 250));
app.workspace.getLeaf(true).openFile(note);
_%>

The tp.file.title parts had to be indeed exchanged with selectedItem variables in all queries, otherwise the title of file triggered from was used.

In the content to be added, I had to single escape backticks and add an extra slash where there was one already in the query.

As for the queries: in the second query, I check for cue words I don’t want to be in my published material (like Evernote stuff I don’t want to link to) and other things I need to comment out. I have many. In the language part, I only listed a limited number as well. No need to clutter up the code here now.

These temporary queries can help one review one’s material before publishing them.

If you use Linter, turn on Lint on File Change (generally, not for this specific purpose, don’t forget to exclude your template and other folders where your meta stuff is) and in the regex section (last but one), add:

Regex to find: \n\n^%%\n```query[\d\D]*?^%%
Regex to replace: leave this box empty.

So if leave your file now, the query will be gone next time you enter the file (the usual way).

The code expects the user to have a valid YAML with a pair of --- separators.

If you want to add to the active file, use:

<%*
const separator = '---';

const currentFile = app.workspace.getActiveFile();
if (!currentFile) return;
const editor = app.workspace.activeEditor.editor;

const fileContent = await app.vault.read(currentFile);
const firstSeparatorPos = fileContent.indexOf(separator);
const secondSeparatorPos = fileContent.indexOf(separator, firstSeparatorPos + 1);

if (secondSeparatorPos !== -1) {
  const dynamicContent = `\n\n%%\n\`\`\`query\nfile: /^${tp.file.title}.md/ /\\b[a-záàäæãééíóöőüű]+\\s*=\\s+[^\`]*?(?=\\s|\\z)|(?:(?:German|English|Greek|Hebrew|Arabic|Slavic|Turkic|Turkish|Ossetian|Alanian|Avestan|Sanskrit|Persian|Egyptian|Sumerian|Accadian|Akkadian|Assyrian|Assyr-Babilonian|Hittite[^\`]*?))\\s([^\`]+?)\\s/\n\`\`\`\n\`\`\`query\nfile: /^${tp.file.title}.md/ /(Evernote|otherstuffiwanttocommentout)(?!.*\\%\\%)/\n\`\`\`\n%%`;

  // Replace placeholders with actual values
  const contentToAdd = dynamicContent.replace('${currentFile}', currentFile);

  const updatedContent = fileContent.slice(0, secondSeparatorPos + separator.length) +
    contentToAdd +
    fileContent.slice(secondSeparatorPos + separator.length);

  await app.vault.modify(currentFile, updatedContent);
editor.setCursor({ line: 0, ch: 0 });
}
_%>
  • And of course, change your queries…
2 Likes

Looking at the solutions from @AlanG and @gino_m, I am curious whether a modified version of this script could be used to accomplish what these threads are looking for:

Essentially, the goal would be to take the text that is selected in the current source note, cut it, prompt the user for a destination note, then prompt the user for a heading name. Upon entering the heading name, the script would create a heading based on the entered heading name at the end of the destination note, and paste the selected(cut) text within the heading. In place of the selected text in the source note would be an embed to the heading added to the destination note.

This method is just how I imagine it. But anything that could accomplish this type of thing would be amazing. For example, maybe it would be easier if the user was required to have already cut the selection before triggering the script.

Here’s a relevant feature request: Extract to specified heading with Note Composer

Thanks!

@I-d-as yes, that’s quite easy with Templater (or QuickAdd I assume also). It’s also quite a cool idea!

Here’s how you do it in Templater:

4 Likes

Incredible! Thanks so much!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.