Journal previous day and next day navigation

I use the Journal plug, and it’s neat! I wanted to be able to easily navigate from one day’s entry to the previous or next, and there wasn’t anything out-of-the-box. It was pretty simple to add though.

This script and template will put something like the following (with working links) at the top of each Journal page (only Journal pages - other pages are left alone).

:arrow_left: Previous | :date: Journal/Day/2024-10-27 | Next :arrow_right:

‘Previous’ takes you to the previous day, and clicking ‘Previous’ on that page will take you to the day before, and so on. It’s possible to navigate back days and weeks using this, which I find useful sometimes if I don’t know which particular day’s notes I want.

Here’s what worked for me. You need to create 2 new pages, but it doesn’t matter much where you put them. (I have mine in Custom/Extensions).

  1. Create a new page for the space script (I called mine JournalNavigationScript) and add the following:
```space-script
silverbullet.registerFunction({name: "newJournalNameFromExistingJournalName"}, (page, days) => {
  const parts = page.split("/");
  const date = parts[parts.length - 1];
  const plainDate = Temporal.PlainDate.from(date);
  const offsetDate = plainDate.add(Temporal.Duration.from({ days: Number(days) }));
  const result = parts.slice(0, -1);
  result.push(offsetDate.toString());
  return result.join("/");
});

(Not rendered here - three backticks to close the script.)

  1. Create a new page for the template (I called mine JournalNavigationTemplate) and add the following frontmatter and template:
---
description: Adds Journal navigation to journal pages.
tags: template
hooks.top.where: 'name =~ /^Journal\/Day/'
---
```template
[[{{newJournalNameFromExistingJournalName(@page.name, -1)}}|⬅️ Previous]] | [[{{@page.name}}|📅 {{@page.name}}]] | [[{{newJournalNameFromExistingJournalName(@page.name, 1)}}|Next ➡️]]

(Not rendered here - three backticks to close the template.)

The frontmatter and template does need to be on a page on its own, but the script can go on a page with other scripts if you prefer.

Wow, Thank you so much for this! :pancakes:

Glad you like it - I really hope it’s useful!

Lua version of this,

:arrow_left: Previous | :date: Journal/Day/2024-10-27 | Next :arrow_right:

  1. Create a new page for the space-lua
```space-lua
Journal = Journal or {}

function Journal.navigateWidget(currentJournal)
    -- currentJournal should be in the format "path/year-month-day"
    local path, year, month, day = string.match(currentJournal, "(.+/)(%d+)-(%d+)-(%d+)")

    if not (path and year and month and day) then
        print("Invalid date format: " .. tostring(currentJournal))
        return "Invalid date format"
    end

    local date = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})

    local prevWeek = os.date("%Y-%m-%d", date - 7 * 24 * 60 * 60)
    local nextWeek = os.date("%Y-%m-%d", date + 7 * 24 * 60 * 60)

    local prevWeekLink = path .. prevWeek
    local nextWeekLink = path .. nextWeek

    local link = "[[" .. prevWeekLink .. "|⬅️ Previous]] | [[" .. tostring(currentJournal) .."|📅 " .. tostring(currentJournal) .. "]] |[[" .. nextWeekLink .. "| Next ➡️]]"
    -- local link = "[[" .. prevWeekLink .. "|⬅️ Previous]]" ..  tostring(currentJournal)  .. "[[" .. nextWeekLink .. "| Next ➡️]]"
  
    return link
end

Create a new page for the template

---
description: Adds Journal navigation to journal pages.
tags: template
hooks.top.where: 'name =~ /^Journal\/Day/'
---

${Journal.navigateWidget(editor.getCurrentPage())}

Thanks so much for this!

I think widgets need widget.new now?

The function Journal.navigateWidget returns markdown text, so doesn’t need widget.new.

I think I have just worked out what a widget is

hi @LogeshG5 , i’m running on the latest v2 of Silverbullet and i don’t know why isn’t working.
I create a journalTemplate like this

---
command: Journal Note
description: Adds Journal navigation to journal pages.
key: "Alt-Shift-j"
tags: meta/template/page
hooks.top.where: 'name =~ /^Journal\/Day/'
---

${Journal.navigateWidget(editor.getCurrentPage())}

and a journalScript same as you.

When i CRTL+SHIFT+j with multiple test, these what i see in console:

silverbullet_1  | Error GETting file 2025-08-31.md Not found
silverbullet_1  | Writing file 2025-08-31.md
silverbullet_1  | Writing file 2025-08-31.md
silverbullet_1  | Error GETting file 2025-8-31.md Not found
silverbullet_1  | Writing file 2025-8-31.md
silverbullet_1  | Writing file 2025-8-31.md
silverbullet_1  | Error GETting file Journaux/2025-08-31.md Not found
silverbullet_1  | Writing file Journaux/2025-08-31.md
silverbullet_1  | Writing file Journaux/2025-08-31.md
silverbullet_1  | Error GETting file Journaux/2025-8-31.md Not found
silverbullet_1  | Writing file Journaux/2025-8-31.md
silverbullet_1  | Writing file Journaux/2025-8-31.md

And my pages always show an Invalid date format
I think the script run before the page was created
can you help me?

I use this in v2, note the change string.match(currentJournal, "(.+/)(%d+)%-(%d+)%-(%d+)")

event.listen {
  name = "hooks:renderTopWidgets", -- or hooks:renderBottomWidgets
  run = function(e)
    if not editor.getCurrentPage().startsWith("Journal/Week/") then
        return
    end
    return widget.new {
      markdown = Journal.navigateWidget(editor.getCurrentPage()) .. "\n"
    }
  end
}

Journal = Journal or {}

function Journal.navigateWidget(currentJournal)
    -- current Journal should be in the format "path/year-month-day"
    local path, year, month, day = string.match(currentJournal, "(.+/)(%d+)%-(%d+)%-(%d+)")

    if not (path and year and month and day) then
        print("Invalid date format: " .. tostring(currentJournal))
        return "Invalid date format: " .. tostring(path) .. tostring(year) .. tostring(month) .. tostring(day)
    end

    local date = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})

    local prevWeek = os.date("%Y-%m-%d", date - 7 * 24 * 60 * 60)
    local nextWeek = os.date("%Y-%m-%d", date + 7 * 24 * 60 * 60)

    local prevWeekLink = path .. prevWeek
    local nextWeekLink = path .. nextWeek

    local link = "[[" .. prevWeekLink .. "|⬅️ Previous]] | [[" .. tostring(currentJournal) .."|📅 " .. tostring(currentJournal) .. "]] |[[" .. nextWeekLink .. "| Next ➡️]]"
    -- local link = "[[" .. prevWeekLink .. "|⬅️ Previous]]" ..  tostring(currentJournal)  .. "[[" .. nextWeekLink .. "| Next ➡️]]"
  
    return link
end

I’ve changed mine a bit, but since I’m moving away from Silverbullet I figured I’d put all my code for my version here in case someone finds it useful.

I have 4 files, and I put them in Custom/Journal.

Custom/Journal/IndexPage:

```space-lua
local function redirectToTodaysJournal()
  if editor.getCurrentPage() == "index" then
    editor.navigate({kind = "page", page = "Journal/Day/" .. date.today()})
  end
end

-- Trigger the above function when the editor is first loaded
event.listen {
  name = "editor:init",
  run = redirectToTodaysJournal
}

-- And also when any page is loaded (because it may be the index page)
event.listen {
  name = "editor:pageLoaded",
  run = redirectToTodaysJournal
}

Custom/Journal/JournalWidget:

```space-lua
event.listen {
  name = "hooks:renderTopWidgets",
  run = function(e)
    local currentPage = editor.getCurrentPage()
    if not currentPage.startsWith("Journal/Day") then
        return
    end

    -- currentPage should be in the format "path/year-month-day"
    local path, year, month, day = string.match(currentPage, "(Journal/Day/)(%d%d%d%d)%-(%d%d)%-(%d%d)")
    
    if not (path and year and month and day) then
        print("Invalid Journal date format: " .. currentPage)
        return "Invalid Journal date format: " .. currentPage
    end

    local currentPageDate = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})

    local previousDate = currentPageDate - 24 * 60 * 60
    local nextDate = currentPageDate + 24 * 60 * 60

    return widget.new {
      markdown = "[[" ..
        path .. os.date("%Y-%m-%d", previousDate) ..
        "|" ..
        "⬅️ (" .. os.date("%Y-%m-%d", previousDate) .. ") Previous" ..
      "]] | [[" ..
        tostring(currentPage) ..
        "|📅 " ..
        os.date("%Y-%m-%d", currentPageDate) ..
      "]] | [[" ..
        path .. os.date("%Y-%m-%d", nextDate) ..
        "|" ..
        "Next (" .. os.date("%Y-%m-%d", nextDate) .. ") ➡️" ..
      "]]"
    }
  end
}

Custom/Journal/JToday:

---
description: Insert a link to today's Journal entry
tags: meta/template/slash
exceptContexts:
- "FencedCode"
- "LuaDirective"
---
[[Journal/Day/${os.date("%Y-%m-%d")}]] 

Custom/Journal/PageTemplate:

---
command: "Journal: Daily Note"
suggestedName: "Journal/Day/${date.today()}"
confirmName: false
openIfExists: true
tags: meta/template/page
---
* |^|
├── CONFIG.md
├── Custom
│   └── Extensions
│       ├── JournalNavScript.md
│       └── JournalNavTemplate.md
├── index.md
├── Journal
│   ├── Day
│   │   └── 2025.md
│   └── Day2.md
└── Library
    └── Page Templates
        └── JournalNavTemplate.md
  1. I can’t get both the OP 2 file version and 4 file version to work
  2. The nav buttons do not show on a journal page at /Journal/Day/2025.md or Day2.md
  3. What am I missing?

Hi,

Sorry you’re having difficulties here.

Two quick questions may help track down the issue(s):

  1. What are the exact contents of your JournalNavScript.md file?
  2. What (if any) messages appear in the browser console when you try to load, say, /Journal/Day/2025.md?

I wrote a forward and backward navigation until for journaling. You might find it a useful reference: silverbullet-libraries/Library/thepaperpilot/CBT-Journal/Navigation Section.md at main · thepaperpilot/silverbullet-libraries · GitHub

thanks, but i have no clue as to where to paste your code

You can place that page anywhere in your space, reload, then write ${cbt_journal.navigation_section("^Journal/%d+%-%d+%-%d+$")} anywhere you want the navigation to show up (changing the regex as appropriate to handle the naming scheme for your journal pages - if you don’t have any files in the “Journal” folder besides journal entries, you could just do ${cbt_journal.navigation_section("^Journal/")})

Thanks all, I managed to restore my v1 header navigation buttons

Another possibility to add navigation buttons to your journal is this, create a page with the following content and reload. No template needs to be changed. Will only work for SB V2

```space-lua
-- Navigation buttons for journal pages (prev/next day)

cbt_journal = cbt_journal or {}

function cbt_journal.navigation_section(pattern)
  local pages = query[[
    from index.tag "page"
    where _.name:match(pattern)
    select {
      name = _.name
    }
    order by name desc
  ]]

  if #pages == 0 then
    return nil
  end

  local currentIndex
  for i, p in ipairs(pages) do
    if p.name == editor.getCurrentPage() then
      currentIndex = I
      break
    end
  end

  if not currentIndex then
    return nil
  end

  local nextp = pages[currentIndex - 1] and pages[currentIndex - 1].name
  local prev  = pages[currentIndex + 1] and pages[currentIndex + 1].name

  local buttons = {}

  if prev then
    table.insert(buttons, widgets.button("← prev", function()
      editor.navigate(prev)
    end))
  end

  table.insert(buttons, dom.span { style = "flex-grow: 1" })

  if nextp then
    table.insert(buttons, widgets.button("next →", function()
      editor.navigate(nextp)
    end))
  end

  return widget.html(dom.div {
    style = "display: flex",
    table.unpack(buttons)
  })
end

-- Automatically render the widget at the top of every journal page
event.listen {
  name = "hooks:renderTopWidgets",
  run = function(e)
    local currentPage = editor.getCurrentPage()
    if not currentPage:match("^Journal/%d+%-%d+%-%d+$") then
      return
    end
    return cbt_journal.navigation_section("^Journal/%d+%-%d+%-%d+$")
  end
}

I like doing it by tag instead:

-- Do I have to be this defensive? Maybe not, what's it to you?
event.listen({
  name = 'hooks:renderTopWidgets',
  run = function()
    local currentPage = editor.getCurrentPage()
    local page = index.getObjectByRef(currentPage, "page", currentPage)
    if not page or not table.includes(page.tags, "dailies") then
      return
    end
    local dailies = query[[
      from p = index.pages 'dailies'
      order by p.created desc
      limit 900
    ]]
    if not dailies then return end
    local currentIndex, _ = table.find(dailies, function(p)
      return p.name == currentPage
    end)
    if not currentIndex or currentIndex < 1 or currentIndex > #dailies then return end
    local previous = dailies[currentIndex + 1]
    if not previous then return end
    local created = previous.created
    if not created then return end
    local day = created:match('^%d+%-%d+%-%d+')
    if not day then return end
    return widget.markdown('## [[' .. previous.name ..'|Yesterday (' .. day .. ')]]')
  end,
})

The limit is in there because I don't want it completely unbounded... even as I sit here and realize that more than 900 things isn't that big of an array. :smile:

I'm using the internal function journal.neighbor() . The my journal templates contains the buttons. Easy does it.

${widgets.button("Prev", function() editor.navigate(journal.neighbor("previous").name) end)}

and

${widgets.button("Next", function() editor.navigate(journal.neighbor().name) end)}