Functions for defining tags and accessing their objects. See API/tag for use cases and longer examples.
Defines a tag, or extends an existing definition. Put tag.define calls in your Library/Config page so they are in place when the index runs.
spec is a table with these fields, all optional except name:
name (string): the tag's name, without #.tagPage (string): the page to open when clicking the tag. Defaults to tag:<name>.schema (table): a JSON Schema the tag's objects are validated against. Build it with the schema helpers. Validation failures show as lint warnings in the editor.mustValidate (boolean): when true, objects that fail schema or validate are not indexed. Defaults to false.validate (function function(object)): custom validation; returns nil when valid, or an error message.icon (string): a Feather icon name for the tag itself, shown in the Workbench. Unlike pageDecoration, it doesn't decorate the tag's pages; when it's absent, pageDecoration.icon is used.pageDecoration (table): a page decoration (icon, prefix, cssClasses, …) for every page with this tag. A page's own pageDecoration wins, then the first of its tags that sets a field; cssClasses from all of them combine. Changing it re-indexes the pages with the tag.transform (function function(object)): called for each indexed object with this tag. Change the object and return it, return {} to skip indexing it, return nil to index it unchanged, or return a list of objects to index instead (one of which must keep the original ref).metatable (table): a Lua metatable set on the tag's objects, e.g. to add computed attributes via __index.renderFrontmatter (function function(pageMeta, ctx)): renders the frontmatter of pages with this tag as a widget. See #Rendering objects.renderDataBlock (function function(object, ctx)): renders each object of a ```#name data block as a widget. See #Rendering objects.Calling tag.define again with the same name merges the new fields into the existing definition, so you can extend built-in tags. Set a field to config.unset to remove it.
Makes attribute required (true) or optional (false) in the schema of tag name, keeping the other required attributes. It runs whenever configuration loads, so requirements a library or built-in tag adds later are kept rather than frozen. The Workbench's Data tab writes these calls when you change whether an attribute is required.
Example:
tag.define {
name = "person",
schema = {
type = "object",
properties = {
email = schema.string(),
manager = schema.ref("person"),
},
},
pageDecoration = { icon = "user" },
}
tag.define can render a tag's objects as a custom widget in the editor, in place of their YAML. Each kind of object has its own hook:
renderFrontmatter = function(pageMeta, ctx) for a page's frontmatterrenderDataBlock = function(object, ctx) for each document of a ```#name data block.By default both render ^Library/Std/Widgets/Object Form.
The first argument is the object with its current, possibly unsaved values: a page's frontmatter plus its name in pageMeta.name, or a data object's YAML plus ref, tag, tags and page. ctx is the render context: the raw YAML (ctx.values, ctx.types, ctx.keys), ctx.kind ("page" or "data"), ctx.readOnly, and ctx.set(key, value), ctx.remove(key) and ctx.move(key, delta) to change it. See Live Edit for what each field holds, how errors and undo work, and an example.
tag.define {
name = "team",
renderFrontmatter = function(pageMeta)
return widget.htmlBlock(dom.div {
class = "team-header",
pageMeta.name .. " · " .. (pageMeta.status or "Unknown status"),
})
end,
}
For frontmatter, the first tag in the page's tags field with a renderFrontmatter wins, and pages without one fall back to the renderer of the page tag. For a ```#name block, the first match wins: the name tag's renderDataBlock, a code widget for #name (see ^Library/Std/APIs/Code Widget), the data tag's renderDataBlock, then plain YAML. A block with several YAML documents (separated by ---) shows one widget per document. Without any renderer, the YAML shows as plain text. The liveEdit.frontmatter and liveEdit.dataBlocks settings turn off the default form (the page and data renderers) without affecting a tag's own renderer.
Shortcut for index.objects("<name>"): all indexed objects with that tag.
-- priority: 50
-- boot: true
tag = tag or {}
--- Defines a tag, or merges `spec` into an existing definition with the same name.
--- @param spec table { name: string, tagPage?: string, schema?: table, mustValidate?: boolean, validate?: function(object), transform?: function(object), metatable?: table, renderFrontmatter?: function(pageMeta, ctx), renderDataBlock?: function(object, ctx) }; set a field to config.unset to remove it
--- @see API/tag
function tag.define(spec)
local fields = {}
local metatable = nil
for k, v in pairs(spec) do
if k == "metatable" and v ~= config.unset then
metatable = v
else
fields[k] = v
end
end
config.merge({"tags", spec.name}, fields)
if metatable then
config.setLuaValue({"tags", spec.name, "metatable"}, metatable)
end
end
--- Makes `attribute` required or optional in the schema of tag `name`, keeping its other required attributes.
--- @param name string the tag's name
--- @param attribute string the attribute
--- @param required boolean whether it is required
--- @see API/tag
function tag.setRequired(name, attribute, required)
local path = {"tags", name, "schema"}
local schema = config.get(path)
if not schema then
config.set(path, { type = "object" })
schema = {}
end
local list = {}
for _, n in ipairs(schema.required or {}) do
if n ~= attribute then
table.insert(list, n)
end
end
if required then
table.insert(list, attribute)
end
-- An empty Lua table would read back as an object, not an empty list
config.set({"tags", name, "schema", "required"}, #list > 0 and list or config.unset)
end
-- Set up tags.* short cut via meta tables
tags = setmetatable({}, {
__index = function(self, tag)
return index.objects(tag)
end
})