How to list completed todos sorted by completion time?

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)
    1. Replace the empty checkbox with
    1. Append the timestamp to the end of the line

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
}
5 Likes