I’ve been trying out Silverbullet 2, making sure that I can port my project management space to the new lua system. Perhaps this can be helpful/interesting to Zef in the Road To Silverbullet 2.0.
The expected result looks something like this (formatting slightly altered; some real projects included so you can get a sense of how I use this)
Tasks
These are all open tasks from Journal or ones tagged
#next
.
- SilverBullet offline requirements@710 Double-check syncthing
Active
P1
Japanese classes
#japanese
P1
Medical log
#medical
#reminder
P2
Solar system homeassistant
P2 Try Silverbullet 2
Snoozed
(
2025-03-29)
P1
Switch electricity
(2025-03-30)
P0 Dust top of office
#house
(2025-04-10)
P1
Ride bike to office
(2025-05-01)
P2
Water heat efficiency and info
#house
More: Snoozed reminders
Ready
P2
Air quality sensors
#electronics
P2 🕮Books to read
P2
Shows to watch
P2 SilverBullet sync issues
#coding
P2 🎜Songs to learn
#music
P3 Advent of Code
#coding
This system worked just fine in Silverbullet 1, though the code has a lot of repetition. I’ve created a Silverbullet 2.0 version to make sure it’ll suit my needs. The output looks the same; the code now looks like this:
# Tasks
These are all open tasks from Journal or ones tagged `#next`.
${tasks_widget(active_tasks())}
# Active
${projects_widget(active_projects())}
# Snoozed
${projects_widget(snoozed_projects())}
# Ready
${projects_widget(ready_projects(20))}
# Blocked
${projects_widget(blocked_projects())}
…with the space-lua section below.
function active_tasks()
return query[[
from index.tag "task"
where done == false and (
table.includes(tags, "next") or
string.startsWith(ref, "Journal")
)
]]
end
function tasks_widget(tasks)
local task_template = template.new[==[- [ ] [[${ref}]] ${text}
]==]
return widget.new{
markdown=template.each(tasks,task_template)
}
end
function active_projects()
return query[[from index.tag "page"
where string.startsWith(name, "Projects/")
and status == "Active"
and not is_snoozed(snooze_until)
order by priority
]]
end
function ready_projects(n)
return query[[from index.tag "page"
where string.startsWith(name, "Projects/")
and status == "Ready"
order by priority
limit n
]]
end
function blocked_projects()
return query[[from index.tag "page"
where string.startsWith(name, "Projects/")
and status == "Blocked"
order by priority
]]
end
function snoozed_projects()
return query[[from index.tag "page"
where string.startsWith(name, "Projects/")
and status == "Active"
and is_snoozed(snooze_until)
and not is_reminder(_)
order by snooze_until
]]
end
function is_snoozed(snooze_until)
if snooze_until == nil then
return false
end
if not string.match(snooze_until, "^....%-..%-..$") then
-- TODO: These should pop a warning at the top,
-- like the one I already have for projects with
-- an invalid state.
return false
end
return snooze_until > os.date("%Y-%m-%d")
end
function is_reminder(project)
return table.includes(project.tags, "reminder")
end
function tagify(tags)
return tags and
'#' .. table.concat(tags, ' #') or
''
end
function priority_string(priority)
local priority_icon = ({
["P0"]="🟥",
["P1"]="🟨",
["P2"]="🟩",
})[priority] or "🤷"
return priority_icon .. priority
end
function snooze_prefix(snooze_until)
if not is_snoozed(snooze_until) then
return ""
end
return "(😴" .. snooze_until .. ")"
end
function projects_widget(projects)
local project_template = template.new[==[${snooze_prefix(snooze_until)}${priority_string(priority)}[[${name}]] ${tagify(tags)}
]==]
return widget.new{
markdown=template.each(projects,project_template)
}
end
Overall notes:
- It’s really nice to be able to put functions together in one page, I believe multiple separate pages were needed for this before.
- New-style
${}
templates don’t appear to support nested loops, which is why I needed thetagify
function. - I didn’t think too hard about the function names here, but… namespacing is perhaps something to consider? Apparently all lua functions are meant to be public across the entire space, which is not necessarily desirable, but I guess it does keep things simple.