#meta
Implements some useful general purpose widgets. Specifically:
Types of button widgets:
widgets.button(text, callback) renders a simple button running the callback when clickedwidgets.commandButton(commandName) renders a button for a particular command (where the button text is the command name itself)widgets.commandButton(text, commandName) renders a button for a particular command with a custom button textwidgets.commandButton(text, commandName, args) renders a button for a particular command and arguments (specified as a table list) with a custom button textExamples:
${widgets.button("Hello", function() editor.flashNotification "Hi there!" end)}
${widgets.commandButton("System: Reload")}
These can each be individually enabled/disabled and configured in your CONFIG page (use space-lua instead of lua):
-- Disable TOC altogether
config.set("std.widgets.toc.enabled", false)
-- Only render a TOC when there's >= 5 headers
config.set("std.widgets.toc.minHeaders", 5)
-- Disable linked mentions altogether
config.set("std.widgets.linkedMentions.enabled", false)
-- Disable linked tasks altogether
config.set("std.widgets.linkedTasks.enabled", false)
-- priority: 10
function widgets.button(text, callback, attrs)
local buttonEl = {
onclick = callback,
text
}
-- attrs can be used for additional customization
if attrs then
for k, v in pairs(attrs) do
buttonEl[k] = v
end
end
return widget.html(dom.button(buttonEl))
end
function widgets.commandButton(text, commandName, args)
if not commandName then
-- When only passed one argument, then let's assume it's a command name
commandName = text
end
return widget.html(dom.button {
onclick = function()
editor.invokeCommand(commandName, args)
end,
text
})
end
function widgets.subPages(pageName)
local prefix = (pageName or editor.getCurrentPage()) .. "/"
return widget.markdown(template.each(query[
from index.tag "page"
where string.startsWith(_.name, prefix)
](
from index.tag "page"
where string.startsWith(_.name, prefix)
), templates.pageItem))
end
.sb-toc-summary {
cursor: pointer;
font-weight: bold;
user-select: none;
padding: 15px 10px;
margin: -10px;
background-color: var(--editor-widget-background-color);
}
.sb-toc-content {
padding-top: 0.5rem;
}
.sb-toc-item {
padding: 0.1rem 0;
}
.sb-toc-link {
cursor: pointer;
text-decoration: none;
}
-- priority: 10
widgets = widgets or {}
-- configuration schema
config.define("std.widgets.toc", {
type = "object",
properties = {
enabled = schema.boolean(),
minHeaders = schema.number(),
}
})
-- configuration default values
config.set("std.widgets.toc", {
enabled = true,
minHeaders = 3
})
function widgets.toc(options)
options = options or config.get("std.widgets.toc")
options.minHeaders = options.minHeaders or 3
options.minLevel = options.minLevel or 1
options.header = options.header or "Table of Contents"
local defaultOpen = (options.defaultOpen ~= false) or nil
local text = editor.getText()
local pageName = editor.getCurrentPage()
local parsedMarkdown = markdown.parseMarkdown(text)
-- Collect all headers
local headers = {}
for topLevelChild in parsedMarkdown.children do
if topLevelChild.type then
local headerLevel = string.match(topLevelChild.type, "^ATXHeading(%d+)")
if headerLevel then
local text = ""
table.remove(topLevelChild.children, 1)
for child in topLevelChild.children do
text = text .. string.trim(markdown.renderParseTree(child))
end
-- Strip link syntax to avoid nested brackets in TOC
text = string.gsub(text, "%[%[(.-)%]%]", "%1")
if text != "" then
table.insert(headers, {
name = text,
pos = topLevelChild.from,
level = tonumber(headerLevel)
})
end
end
end
end
if options.minHeaders and options.minHeaders > #headers then
return widget.new{}
end
-- Filter headers to display
local headersToDisplay = {}
for _, header in ipairs(headers) do
if not (options.maxHeader and header.level > options.maxHeader or
header.level < options.minLevel) then
table.insert(headersToDisplay, header)
end
end
-- Find min level
local minLevel = 6
for _, header in ipairs(headersToDisplay) do
minLevel = math.min(minLevel, header.level)
end
-- Build TOC entries as DOM elements
local tocItems = {}
for _, header in ipairs(headersToDisplay) do
local indent = (header.level - minLevel) * 1.5
table.insert(tocItems, dom.div {
class = "sb-toc-item",
style = "margin-left: " .. indent .. "rem;",
dom.a {
onclick = function()
editor.navigate({ page = pageName, pos = header.pos })
end,
class = "sb-toc-link",
"- " .. header.name
}
})
end
-- Wrap in a <details> element for native show/hide toggle
return widget.new {
html = dom.details {
open = defaultOpen,
dom.summary {
class = "sb-toc-summary",
options.header
},
dom.div {
class = "sb-toc-content",
table.unpack(tocItems)
}
},
display = "block"
}
end
-- priority: -1
if config.get("std.widgets.toc.enabled") then
event.listen {
name = "hooks:renderTopWidgets",
run = function(e)
local pageText = editor.getText()
local fm = index.extractFrontmatter(pageText)
if fm.frontmatter.pageDecoration and fm.frontmatter.pageDecoration.disableTOC then
return
end
return widgets.toc()
end
}
end
-- priority: 10
widgets = widgets or {}
local mentionTemplate = template.new [==[
**[${_.ref}|${_.ref}](${_.ref}|${_.ref})**:
${_.snippet}
]==]
-- configuration schema
config.define("std.widgets.linkedMentions", {
type = "object",
properties = {
enabled = schema.boolean(),
}
})
-- configuration default values
config.set("std.widgets.linkedMentions", {
enabled = true,
})
function widgets.linkedMentions(pageName)
pageName = pageName or editor.getCurrentPage()
local linkedMentions = query[
from l = index.tag "link"
where l.page != pageName and l.toPage == pageName
order by l.pageLastModified desc, l.pos
](
from l = index.tag "link"
where l.page != pageName and l.toPage == pageName
order by l.pageLastModified desc, l.pos
)
if #linkedMentions > 0 then
return widget.new {
markdown = "# Linked Mentions\n"
.. template.each(linkedMentions, mentionTemplate)
}
end
end
-- priority: -1
if config.get("std.widgets.linkedMentions.enabled") then
event.listen {
name = "hooks:renderBottomWidgets",
run = function(e)
return widgets.linkedMentions()
end
}
end
-- priority: 10
-- configuration schema
config.define("std.widgets.linkedTasks", {
type = "object",
properties = {
enabled = schema.boolean(),
}
})
-- configuration default values
config.set("std.widgets.linkedTasks", {
enabled = true,
})
function widgets.linkedTasks(pageName)
pageName = pageName or editor.getCurrentPage()
local tasks = query[
from t = index.tag "task"
where not t.done and table.includes(t.ilinks, pageName)
order by t.page
](
from t = index.tag "task"
where not t.done and table.includes(t.ilinks, pageName)
order by t.page
)
local md = ""
if #tasks > 0 then
md = "# Linked Tasks\n"
.. template.each(tasks, templates.taskItem)
else
md = ""
end
return widget.new {
markdown = md
}
end
-- priority: -1
if config.get("std.widgets.linkedTasks.enabled") then
event.listen {
name = "hooks:renderTopWidgets",
run = function(e)
return widgets.linkedTasks()
end
}
end