I’d like to create a page that shows my completed todos, sorted by the time they were completed. From what I can see, todos themselves don’t have timestamps (no creation time or modification time), while full pages do have them.
Is there any workaround or recommended approach to achieve this? How would you organize such functionality in SilverBullet?
Silverbullet doesn’t assign a timestamp to the completed tasks by default.
But we can tell it to do this, with this workaround. use this space-lua script:
event.listen {
name = "task:stateChange",
run = function(e)
if e.data.newState == "x" and not e.data.text:find("%[completed:") then
local text = e.data.text
local completedAt = os.date("%Y-%m-%dT%H:%M:%S")
local newText = text:gsub("^%[%s*%]", "[x]") .. " [completed: " .. completedAt .. "]"
editor.dispatch({ changes = { from = e.data.from, to = e.data.to, insert = newText } })
end
end
}
What this does:
Only trigger if the you just checked the box (‘x’) AND if there isn’t already a timestamp (prevents loops)
and with this in place you can sort a query based on the completed attribute.
This simple script can be even extended to also remove the completed attribute, if you uncheck the task.
that would look something like this:
event.listen {
name = "task:stateChange",
run = function(e)
local data = e.data
local text = data.text
-- Safety check: ensure text exists to avoid 'nil value' errors
if not text then return end
-- CASE 1: Task marked as complete ('x')
if data.newState == "x" then
-- Check if timestamp already exists to prevent infinite loops/double-clicks
if not string.find(text, "%[completed:") then
-- Some sandboxes restrict os.date; we use a simple format
local completedAt = os.date("%Y-%m-%dT%H:%M:%S")
-- Append the timestamp at the end
local newText = string.gsub(text, "%[%s*%]", "[x]") .. " [completed: " .. completedAt .. "]"
editor.dispatch({
changes = {
from = data.from,
to = data.to,
insert = newText
}
})
end
-- CASE 2: Task marked as incomplete (' ')
elseif data.newState == " " then
-- Check if there is actually a timestamp to remove
if string.find(text, "%[completed:") then
-- Remove the [completed: ...] tag and any leading space
local cleanText = string.gsub(text, "%[[xX]%]", "[ ]")
local newText = string.gsub(cleanText, "%s*%[completed: [^%]]+]", "")
editor.dispatch({
changes = {
from = data.from,
to = data.to,
insert = newText
}
})
end
end
end
}
Thanks, your solution works! However, if I complete the task not in the place where it was originally created, but from the index page (I’m collecting all active tasks via query), the task:stateChange event doesn’t get triggered and the completed attribute isn’t added. Is there a way to fix this?