Page templates can be used to automate or simplify the creation of pages based on a template.
To create a page template, create a page and tag it with #meta/template/page.
The last component of the page name will be used as the template name.
In the template’s body text, you can optionally use |^| as a placeholder for where the cursor should be after creating
the page.
Optional keys you can set in the page’s frontmatter:
suggestedName: the proposed name for the new page, can use embedded Lua expressions, like Daily/${date.today()}.confirmName: Confirm the suggested page name before creating it (defaults to true). Only effects pages created by a key binding, otherwise you will always be prompted to confirm the new page name.openIfExists: If a page with the suggestedName already exists, open it rather than showing an error.command: expose the page template as a command with this name.key: Bind the snippet to a keyboard shortcut (note: this requires to also specify the command configuration).mac: Bind the snippet to a Mac-specific keyboard shortcut.frontmatter: Frontmatter to set in the resulting page. Use a YAML literal block scalar
(frontmatter: |) so that the value is passed as a string and ${...} Lua interpolation
works. A nested YAML mapping is also accepted (and serialized verbatim), but interpolation
will be skipped in that case.priority: Similar to how space lua scripts are loaded, this controls the order in which page template commands are
created (see "overriding page templates" below)The following creates a page template that can be run using the Journal: Daily Note command, it automatically creates
Daily/today’s date if it does not already exist with a bulleted list, putting the cursor at the first bullet. If the
page already exists, it navigates there.
---
command: "Journal: Daily Note"
suggestedName: "Daily/${date.today()}"
confirmName: false
openIfExists: true
tags: meta/template/page
---
* |^|
${query[ from p = index.pages("meta/template/page") select templates.fullPageItem(p) ](< from p = index.pages("meta/template/page") select templates.fullPageItem(p)
)}
You can create a page based on a page template via the ${widgets.commandButton("Page: From Template")} command, or via the command name that you defined in your template’s frontmatter.
If you would like to override an existing page template (for instance the Quick Note) template with your own, you can
take advantage of the load order determined by the priority frontmatter. Built in page templates will have a priority
set that is higher than the default. Therefore, their commands and keybindings will be set early. Therefore, by simply
defining your own version of the page template with the same command name will let that version override the versions
that are built in.
---
command: Quick Note
key: "Ctrl-q q"
suggestedName: "Quick notes/${os.date('%Y-%m-%d/%H-%M-%S')}"
confirmName: false
tags: meta/template/page
---
This is my quick note version
-- priority: 10
tag.define {
name = "meta/template/page",
schema = {
type = "object",
properties = {
tags = {
anyOf = {
{ type = "array", items = schema.string() },
schema.string(),
},
},
frontmatter = {
anyOf = { schema.string(), schema.object() },
},
suggestedName = schema.string(),
confirmName = schema.boolean(),
openIfExists = schema.boolean(),
command = schema.string(),
key = {
anyOf = {
{ type = "array", items = schema.string() },
schema.string(),
},
},
mac = {
anyOf = {
{ type = "array", items = schema.string() },
schema.string(),
},
},
priority = schema.number(),
description = schema.string(),
},
}
}
function template.createPageFromTemplate(templatePage, pageName, openIfExists)
-- Won't override an existing page
if space.pageExists(pageName) then
if openIfExists then
editor.navigate(pageName)
return
else
editor.flashNotification("Page " .. pageName .. " already exists", "error")
return
end
end
local tpl, fm = template.fromPage(templatePage)
local initialText = ""
if fm.frontmatter then
local frontmatterText
if type(fm.frontmatter) == "string" then
-- String form: supports `${...}` Lua interpolation
frontmatterText = template.new(fm.frontmatter)()
else
-- Table form (e.g. nested YAML mapping): encode as YAML
frontmatterText = yaml.stringify(fm.frontmatter)
end
initialText = "---\n"
.. string.trim(frontmatterText)
.. "\n---\n"
end
space.writePage(pageName, initialText)
editor.navigate(pageName)
-- Insert there, supporting |^| cursor placeholder
editor.insertAtPos(tpl(), #initialText, true)
end
-- Create commands for all page templates with a command key in frontmatter
for pt in query[
from tp = index.pages("meta/template/page")
where tp.command
order by tp.priority desc
](<
from tp = index.pages("meta/template/page")
where tp.command
order by tp.priority desc
>) do
command.define {
name = pt.command,
key = pt.key,
mac = pt.mac,
run = function()
local pageName
if pt.suggestedName then
pageName = (template.new(pt.suggestedName))()
end
if pt.confirmName != false then
pageName = string.trim(some(editor.prompt("Page name", pageName)) or "")
end
if pageName == "" then
editor.flashNotification("No page name given for '" .. pt.command .. "', unable to continue.", "error")
return
end
template.createPageFromTemplate(pt.name, pageName, pt.openIfExists)
end
}
end
local function cleanName(path)
local parts = path:split("/")
return parts[#parts]
end
-- The second half of picking a template: name the page it makes, then make
-- it. Shared by the navigator view and the filterBox fallback below, because
-- the naming step is the part that has to behave identically either way.
local function instantiateTemplate(fullName, suggestedName, openIfExists)
local pageName
if suggestedName ~= nil then
pageName = (template.new(suggestedName))()
end
pageName = string.trim(some(editor.prompt("Page name", pageName)) or "")
if pageName == "" then
editor.flashNotification("No page name given for template '" .. cleanName(fullName) .. "', unable to continue.", "error")
return
end
template.createPageFromTemplate(fullName, pageName, openIfExists)
end
navigator.define {
name = "std.pageTemplates",
title = "Page templates",
dock = "modal",
refreshOn = { "index" },
presentation = {
mode = "list",
row = {
primary = function(obj) return cleanName(obj.name) end,
description = function(obj) return obj.description end,
decorations = function(obj)
if obj.suggestedName then
return { { text = "auto-named", position = "right" } }
end
end,
icon = "file-plus",
},
},
source = function()
return query [from index.pages("meta/template/page") order by _.name](<from index.pages("meta/template/page") order by _.name>)
end,
onSelect = function(obj)
editor.hidePanel("modal")
instantiateTemplate(obj.name, obj.suggestedName, obj.openIfExists)
return false
end,
}
command.define {
name = "Page: From Template",
key = "Ctrl-q t",
run = function()
if editor.openNavigator("std.pageTemplates") then
-- The panel focuses its own filter input.
return false
end
local pageTemplates = query[
from index.pages("meta/template/page")
select {
name = cleanName(_.name),
fullName = _.name,
suggestedName = _.suggestedName,
openIfExists = _.openIfExists
}
](<
from index.pages("meta/template/page")
select {
name = cleanName(_.name),
fullName = _.name,
suggestedName = _.suggestedName,
openIfExists = _.openIfExists
}
>)
local selected = editor.filterBox("Page template", pageTemplates, "Pick the template you would like to instantiate")
if not selected then
return
end
instantiateTemplate(selected.fullName, selected.suggestedName, selected.openIfExists)
end
}