As a result of a recent question on how to centralize the maintenance of several templates from a master template, I came up with this iteration of the Meta Template Picker (MTP), whose idea makers are @tallguyjenks and Pamela Wang.
This Meta Template Picker uses a dictionary (object) for each of the templates. The idea of the dictionaries was recommended by @holroy, and the outcome debated with @gino_m. Thanks to both.
In this MTP, the templates do not live in files but are inline described in each dictionary. It works for me because I was having trouble opening each template file every time I wanted to add/modify a frontmatter property. With this approach I always have an immediate glance at all my frontmatter properties. Also, as you may have experimented, some properties repeat from one template to another. That is why you see a âcommonâ dictionary there.
The use is identical to the video by Pamela Wang; have to place the meta template in templater options under the root folder. Maybe a very small deviation is a call to âDefault Minimal Templateâ which will create a note with only four properties.
Here is the Meta Template Picker:
<%*
/* "Meta Template Picker" idea makers: Bryan Jenksa and Pamela Wang
This iteration by Alfonso R. Reyes. Houston, Texas.
*/
// Common properties will be at the top of frontmatter
const common = {
name: "Common",
fields: `
aliases:
type:
archetype:
summary:
status:
rating:
up:
related:
`
}
/* List of dictionaries/objects are defined here
A central place where to control the properties
name: is the name of the inline-template
short: will be used as a prefix for inline-template selection
long: will be used for the sub-folder
datatable: a Dataview table added below the frontmatter
fields: properties of the inline-template
*/
const choices = [
{ name: "Collection",
short: "collect",
long: "Collections",
datatable: getTableUpRelated(),
fields: `
tags: map/collection
archetype: "[[Collections]]"
`
},
{ name: "Company",
short: "comp",
long: "Companies",
fields: `
country:
industry:
address:
website:
linkedin:
tags: map/company
archetype: "[[Companies]]"
`
},
{ name: "Concept",
short: "concept",
long: "Concepts",
fields: `
tags: map/concept
archetype: "[[Concepts]]"
`
},
{ name: "Discipline",
short: "disc",
long: "Disciplines",
fields: `
tags: map/discipline
archetype: "[[Disciplines]]"
`
},
{ name: "Industry",
short: "ind",
long: "Industries",
datatable: getTableUpRelated(),
fields: `
tags: map/industry
archetype: "[[Industries]]"
`
},
{ name: "Location",
short: "loc",
long: "locations",
datatable: getTableUpRelated(),
fields: `
country:
state:
region:
tags: map/location
archetype: "[[Locations]]"
`
},
{ name: "Media",
short: "med",
long: "Media",
fields: `
tags: map/media
archetype: "[[Media]]"
`
},
{ name: "Object",
short: "obj",
long: "Objects",
fields: `
tags: map/object
archetype: "[[Objects]]"
`
},
{ name: "People",
short: "people",
long: "People",
datatable: getTableUpRelated(),
fields: `
known_for:
company:
BU:
position:
role:
reports_to:
email:
address:
phone:
city:
linkedin:
github:
tags: map/person
archetype: "[[People]]"
`
},
{ name: "Rating",
short: "rating",
long: "Ratings",
fields: `
tags: map/rating
archetype: "[[Ratings]]"
`
},
{ name: "Realm",
short: "realm",
long: "Realms",
fields: `
tags: map/realm
archetype: "[[Realms]]"
`
},
{ name: "Status",
short: "status",
long: "Status",
fields: `
tags: map/status
archetype: "[[Status]]"
`
},
{ name: "Type",
short: "type",
long: "Types",
fields: `
tags: map/type
archetype: "[[Types]]"
`
},
]
let uuid; let yml; let title; let choice;
let folder = "Atlas"; // this is the parent of sub-folders
// find a prefix match to select dictionary member
choices.forEach(item => {
if (tp.file.title.startsWith(item.short)) {
choice = item; }
})
// if the prefix was found
if (choice) {
uuid = get_uuid()
yml = common.fields + choice.fields + uuid
yml = cleanFrontmatter(yml); // remove blank lines, spaces
title = extractTitle(tp.file.title); // get rid of the prefix
await tp.file.rename(title);
folder = "Atlas/" + choice.long + "/";
await tp.file.move(folder + title); // move to sub-folder
// add frontmatter to the new note
addFrontmatter(yml);
// add table view to new note, if defined
if (choice.datatable) {
addTableBelowFrontmatter(choice.datatable)
}
new Notice("New " + choice.name + " just added ...")
} else {
/* Note name prefix was not found in dictionary
or note is "Untitled",
or note is already named and is a link
*/
if (tp.file.title.startsWith("Untitled")) {
// most likely you pressed Ctrl+N (new note)
title = await tp.system.prompt("Note Name")
if (title === null) {
return;
// pressing <Enter> creates "Untitled-20240220104000"
// pressing <Esc> creates empty "Untitled"
} else {
// because sometimes title is not changed; too fast
setTimeout(() => {tp.file.rename(title)}, 350)
}
} else {
// note being created from a link has a dash but prefix unknown
new Notice("Note has a name but not known prefix")
}
// append/run the minimal template when note prefix not known
tR += await tp.file.include("[[Default Minimal Template]]");
}
function cleanFrontmatter(yml) {
// remove blank lines, spaces and tabs at the start of frontmatter
const regex = /^(?=\n)$|^\s*|s*$|\n\n+/gm
yml = yml.split('\n')
.map(function(line) {
return line.replace(regex, "")})
.filter(function(x) {return x}).join("\n");
return yml;
}
function get_uuid() {
// Make an uuid to time stamp each new note
return "uuid: "
+ "'"
+ tp.file.creation_date("YYYYMMDDHHmmss")
+ "'"
}
function getTableUpRelated() {
// Dataview table showing notes
let yml = `
TABLE up, related
FROM ""
WHERE up = link(this.file.name)
OR contains(related, link(this.file.name))
SORT file.name ASC
`
return yml
}
function addTableBelowFrontmatter(dt) {
tR += "\n\n"
tR += "```dataview"
tR += "\n"
tR += cleanFrontmatter(dt)
tR += "\n"
tR += "```"
tR += "\n"
}
function addFrontmatter(yml) {
tR += "---\n"
tR += yml
tR += "\n---\n"
}
function extractTitle(title) {
let result;
let count = (title.match(/-/g) || []).length;
let dateRegex = /(\d{4})([-])(\d{2})([-])(\d{2})/;
switch (count) {
case 0:
result = title.trim()
break
case 1:
result = title.split("-").slice(1)[0].trim()
break
default:
result = dateRegex.test(title) && count == 2
? title.trim()
: title.split("-").slice(1).join("-").trim()
}
return result;
}
_%>
And this the Default Minimal Template:
<%-*
/* Create a new note but including uuid formed by the date en time
*/
var title = tp.file.title
const uuid = tp.file.creation_date("YYYYMMDDHHmmss");
if (tp.file.title.startsWith("Untitled")) {
title = tp.file.title + "-" + uuid // add a timestamp
await tp.file.rename(title);
}
-%>
---
tags: map/minimal
up:
related: []
uuid: '<% uuid %>'
---
<% await tp.file.move("Notes/" + title) %>
Of course, this can be improved, such as adding a sub-folder in the dictionary for each of the inline-templates; use lists instead of a string with backticks for the properties; do not create an "âUntitledâ note when one aborts the prompt for a new note with ; replace some of the functions with native Obsidian functions; remove specific common properties, etc.
Hope you find this useful.