Files
plainleaf/libraries/Library/Std/Journal/Journal.md
T

5.2 KiB

#meta

This page implements the built-in daily journal feature: four commands (Journal: Today, Journal: Previous Day, Journal: Next Day, Journal: Picker) and a default template at ^Library/Std/Journal/Template.

Configuration

The journal feature can be configured via the journal config key:

  • journal.enabled — set to false to disable all journal commands.
  • journal.template — page name to use as the template (defaults to Library/Std/Journal/Template).
  • journal.prefix — page-name prefix for new journal entries (defaults to Journal/). Must end with a / if you want entries grouped under a folder.
  • journal.tag — tag used to mark journal pages (defaults to journal). The template, Prev/Next commands, and the default index page all key off this tag.

Configuration

The schema, helpers, and command definitions live in two fenced blocks. The first (priority: 10) registers the config schema and defines helpers attached to the journal namespace. The second (priority: -1) runs after user CONFIG has loaded, so config.get("journal.enabled") reflects the user's override.

-- priority: 10
journal = journal or {}

config.defineCategory {
  name = "Journal",
  description = "Configure the built-in daily journal feature.",
  priority = 15,
}

config.define("journal", {
  description = "Configure the built-in journal feature",
  type = "object",
  properties = {
    enabled = {
      type = "boolean",
      default = true,
      description = "Enable the built-in Journal commands",
      ui = { category = "Journal", label = "Enable journal", priority = 4 },
    },
    template = {
      type = "string",
      default = "Library/Std/Journal/Template",
      description = "Page name to use as the template for new journal entries",
      ui = { category = "Journal", label = "Journal template page", priority = 3 },
    },
    prefix = {
      type = "string",
      default = "Journal/",
      description = "Page-name prefix for new journal entries (e.g. 'Journal/' yields 'Journal/2026-05-12'). Must end with '/' to group under a folder.",
      ui = { category = "Journal", label = "Journal page prefix", priority = 2 },
    },
    tag = {
      type = "string",
      default = "journal",
      description = "Tag used to mark journal pages. Prev/Next and the index-page section both key off this tag.",
      ui = { category = "Journal", label = "Journal tag", priority = 1 },
    },
  },
  additionalProperties = false,
})

API

-- priority: 10
function journal.openOrCreate(dateStr)
  local pageName = config.get("journal.prefix") .. dateStr
  if space.pageExists(pageName) then
    editor.navigate(pageName)
    return
  end
  local templatePage = config.get("journal.template")
  template.createPageFromTemplate(templatePage, pageName, true)
end

function journal.entries()
  return query[[
    from j = index.pages(config.get("journal.tag"))
    order by j.date desc
  ]]
end

function journal.neighbor(direction)
  local entries = journal.entries()
  if #entries == 0 then return nil end
  local currentPage = editor.getCurrentPage()
  local pivot = date.today()
  for _, e in ipairs(entries) do
    if e.name == currentPage and e.date then
      pivot = e.date
      break
    end
  end
  if direction == "previous" then
    for _, e in ipairs(entries) do
      if e.date and e.date < pivot then
        return e
      end
    end
  else
    local result
    for _, e in ipairs(entries) do
      if e.date and e.date > pivot then
        result = e
      end
    end
    return result
  end
  return nil
end

Commands

-- priority: -1
if config.get("journal.enabled", true) then
  -- using command.update here (instead of command.define) to support key binding overrides (executed before)
  command.update {
    name = "Journal: Today",
    key = "Ctrl-q j",
    run = function()
      journal.openOrCreate(date.today())
    end,
  }
  command.update {
    name = "Journal: Previous Day",
    key = "Ctrl-q p",
    run = function()
      local entry = journal.neighbor("previous")
      if entry then
        editor.navigate(entry.name)
      else
        editor.flashNotification("No earlier journal entries")
      end
    end,
  }
  command.update {
    name = "Journal: Next Day",
    key = "Ctrl-q n",
    run = function()
      local entry = journal.neighbor("next")
      if entry then
        editor.navigate(entry.name)
      else
        editor.flashNotification("No later journal entries")
      end
    end,
  }
  command.update {
    name = "Journal: Picker",
    run = function()
      local entries = journal.entries()
      if #entries == 0 then
        editor.flashNotification("No journal entries yet")
        return
      end
      local prefix = config.get("journal.prefix")
      local items = {}
      for _, e in ipairs(entries) do
        local label = e.name
        if prefix ~= "" and label:startsWith(prefix) then
          label = label:sub(#prefix + 1)
        end
        table.insert(items, { name = label, fullName = e.name })
      end
      local selected = editor.filterBox("Journal entry", items, "Pick a journal entry to open")
      if not selected then return end
      editor.navigate(selected.fullName)
    end,
  }
end