How to create new notes from line items under sections in a note

What I’m trying to do

tl;dr How do I tell templater to select specific line items in a note, notice which section it’s in, notice which file it’s in, and then apply those values to the new note creation… thereby creating many new notes from this one note?

Longer Version

I have a note that has several titled sections, each with 3-4 line items (see Leaders and Enemies section in below picture as an example).

I want each line item to become a note of its own:

  • Title is just the text of the item
  • One property that references the title of the original note being extracted from
  • Another property that references the section title

(Below image shows one note created from one of the Enemy line items… so the “Faction Tag” property should pull in the original note name, and the “person” property should pull in the section h1 name.
image

I’m trying to make a templater to apply to the note that will automatically run through each section and convert each line item into a note.

Things I have tried

Using something that @holroy developed over in https://forum.obsidian.md/t/inherit-or-pass-on-metadata-values-from-active-file-when-creating-new-file/60522/3, I’ve been able to do some of what I wanted to achieve.

I can use their template to basically select each line item and have it create a new note with it, forcing properties that I’ve coded into the template… but this is very manual. I would have to highlight each line item individually, apply the template, and move on to the next.

<%*
//below creates variables related to original file location
let currentFolderPath = tp.file.folder(true);
let currentFilePath = (currentFolderPath == '/' ? '' : currentFolderPath + '/') + tp.file.title + '.md';

// (1) CHANGE subfolder accordingly
const baseFolder = app.vault.getAbstractFileByPath('Faction Tags/Leaders')

const sel = tp.file.selection()
if ( !sel ) {
  windows.alert("Please select some text to be extracted")
  return;
}

const content = 
// (2) CHANGE person and rpgTags accordingly
`---
factionTag: "[[${tp.file.title}]]"
rpgTags: "person"
person: "leader" 
---
`

await tp.file.create_new(content, sel, true, baseFolder)
//below forces active view back to original file
app.workspace.activeLeaf.openFile(app.vault.getAbstractFileByPath(currentFilePath));
_%>

More so, when i get to a new section, I have to change the template text to modify the property values.

So, yeah… tedious but doable. Especially since I will have dozens of notes (with several sections each) that I want to apply this to.

Any thoughts on javascripting some automation into all that?

I haven’t tried it myself, but maybe you can try this plugin. It has a command “Split note by headings”.

Hey Ush, thanks… I’ve used that plugin before and I had dismissed it before because I thought the split action wouldn’t allow me to create the properties I needed, but I’ve been playing with it now.

If I reformat the original line items and switch their formatting with the section head to look like this (line item becomes header, section head becomes text):
image

Then within the Note Refactor settings, I customize Refactored note template as:

---
rpgTag: {{title}}
person: {{new_note_content}}
---

It’ll give me:
image

And if all the line items throughout the note are formatted accordingly, it should convert them all.

Outstanding issues:

  • Annoying bit will be needing to reformat everything
  • This won’t automatically move each new note to the right folder. So that’ll need further work to figure out

My bad, I should’ve read your question more carefully…

BTW try inserting this template:

<%*
// if you want the new notes to be created in a certain folder, replace undefined with app.vault.getAbstractFileByPath('Folder path')
const outputFolder = undefined;

const currentFile = tp.file.find_tfile(tp.file.path(true)); // Equivalent: app.workspace.getActiveFile()
if (!currentFile) return;
const editor = app.workspace.activeEditor.editor;
const cache = app.metadataCache.getFileCache(currentFile);

if (!editor || !cache.headings) return;

await Promise.all(
	cache.headings.map(
		(heading, index, headings) => 
		processSingleHeading(heading, headings[index + 1])
	)
);	

function makeTemplate(heading) {
	return `---
factionTag: "[[${app.metadataCache.fileToLinktext(currentFile, '')}]]"
rpgTags: "person"
person: "${heading.heading.toLowerCase()}" 
---
`;
}

function processSingleHeading(heading, nextHeading) {
	const template = makeTemplate(heading);
	const lineStart = heading.position.start.line + 1;
	const lineEnd = nextHeading ? nextHeading.position.start.line - 1 : editor.lineCount();
	const promises = [];
	for (let lineNumber = lineStart; lineNumber <= lineEnd; lineNumber++) {
		const line = editor.getLine(lineNumber);
		if (line) {
			promises.push(
				tp.file.create_new(template, line, false, outputFolder));
		}
	}
	return Promise.all(promises);
}
_%>

Holy crap dude, that was amazing! Wow, that worked perfectly. I’m in awe of the template you created and hopefully I can learn the ins and outs of it. Thanks so much for busting that out.

Quick follow up request, if I toss the outputFolder into the for loop, can I finagle it to shift into each subfolder that matches the heading (e.g. when dealing with Enemies line items, switch to /Faction Tags/Enemies; then for Leaders /Faction Tags/Leaders, etc.)?

Glad it worked. I wanted to keep it as simple as possible and avoid using Obsidian API directly, but I ended up using a lot of non-Templater functions.

It’s totally possible. Just put the definition of outputFolder inside the processSingleHeading function, and define it depending on the currently processed heading, like so:

<%*
// if you want the new notes to be created in a certain folder, replace undefined with app.vault.getAbstractFileByPath('Folder path')
const outputFolder = undefined;

const currentFile = tp.file.find_tfile(tp.file.path(true)); // Equivalent: app.workspace.getActiveFile()
if (!currentFile) return;
const editor = app.workspace.activeEditor.editor;
const cache = app.metadataCache.getFileCache(currentFile);

if (!editor || !cache.headings) return;

await Promise.all(
	cache.headings.map(
		(heading, index, headings) => 
		processSingleHeading(heading, headings[index + 1])
	)
);	

function makeTemplate(heading) {
	return `---
factionTag: "[[${app.metadataCache.fileToLinktext(currentFile, '')}]]"
rpgTags: "person"
person: "${heading.heading.toLowerCase()}" 
---
`;
}

async function processSingleHeading(heading, nextHeading) {	
    const path = `Faction Tags/${heading.heading}`;
    let outputFolder = app.vault.getAbstractFileByPath(path);
    if (!outputFolder) { // if it doesn't exist, create it
        await app.vault.createFolder(path);
        outputFolder = app.vault.getAbstractFileByPath(path);
        if (!outputFolder) return;
    }
    
	const template = makeTemplate(heading);
	const lineStart = heading.position.start.line + 1;
	const lineEnd = nextHeading ? nextHeading.position.start.line - 1 : editor.lineCount();
	const promises = [];
	for (let lineNumber = lineStart; lineNumber <= lineEnd; lineNumber++) {
		const line = editor.getLine(lineNumber);
		if (line) {
			promises.push(
				tp.file.create_new(template, line, false, outputFolder));
		}
	}
	await Promise.all(promises);
}
_%>
1 Like

Boom, that does it completely. Thanks again @ush. Been banging my head on that for a while, so I really appreciate it.

1 Like

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