Page Navigation Functions

These are my space-lua functions that I use to manage the hierarchy of my pages especially in my journal .

List of Page Navigation Functions

Function Returns Description
page.parents(path) table Parent pages of the current one
page.sister() table Sister pages at the same level
page.child(path) table Child pages of the current one
page.meta(where) table Page metadata
page.up() string Parent of the current page
page.prec() string Previous sibling page
page.succ() string Next sibling page
page.nome() string Extracts the page name
page.tags() table Extracts page tags
page.lev() number Returns the page level
page.title(path) string Extracts the first H1 as title
page.childlink() string Lists and links all child pages
breadcrumb() string Creates breadcrumb navigation
page.navp() string Previous page in same section
page.navs() string Next page in same section
page.dprec() string Previous diary page
page.dsucc() string Next diary page

page.parents(path)

-- function page.parents(path)
-- Restituisce una table con l'elenco dei genitori
-- Returns a table with the list of parent pages.
page = page or {}
function page.parents(path)
  local path = path or editor.getCurrentPage()
  local livelli = string.split(path, "/")
  local genitori = {}
  local pathParziale = ""

  for i = 1, #livelli - 1 do
    pathParziale = (i == 1) and livelli[i] or (pathParziale .. "/" .. livelli[i])
    table.insert(genitori, pathParziale)
  end
  return genitori
end

page.sister()

-- function page.sister()
-- raccolta delle pagine "Sorelle", ovverosia allo stesso livello.
-- Collection of “sister” pages at the same level.
page = page or {}
function page.sister()
  local pa = query[[from index.tag "page"
  where string.startsWith(name, page.up() .. "/") 
  and not string.match(name, page.up() .. "/.+/")
  ]]
  return pa
end

page.prec()

-- function page.prec()
-- Returns the previous page at the same level.  
-- Returns an empty string if no page is found.
page = page or {}
function page.prec()
  local filtro1 = page.up() .. "/"
  local filtro2 = page.up() .. "/.+/"
  local paa = editor.getCurrentPage()
  local pa = query[[from index.tag "page"
              where string.startsWith(name, filtro1) 
              and not string.match(name, filtro2)
              and name < paa
              order by name desc
              limit 1]]
  -- Se pa è nil o vuota, restituisce una stringa vuota
  if not pa or #pa == 0 then
    return ""
  end 

  return pa[1].name
end

page.succ()

-- function page.succ()
-- Pagina successiva della pagina attuale.
-- Returns the next page at the same level.  
-- Returns an empty string if no page is found.
page = page or {}
function page.succ()
  local filtro1 = page.up() .. "/"
  local filtro2 = page.up() .. "/.+/"
  local paa = editor.getCurrentPage()
  local pa = query[[from index.tag "page"
              where string.startsWith(name, filtro1) 
              and not string.match(name, filtro2)
              and name > paa
              order by name 
              limit 1]]
    -- Se pa è nil o vuota, restituisce una stringa vuota
    if not pa or #pa == 0 then
      return ""
    end
    return pa[1].name
end

page.up()

-- function page.up()
-- Returns the parent page.
page = page or {}
function page.up(path)
  local path = path or editor.getCurrentPage()
  local parts = {}
  
  -- Suddivide il percorso in segmenti
  for part in string.split(path, "/") do
    table.insert(parts, part)
  end
  for i = 1, #parts - 1 do
    if i == 1 then
      currentPath = parts[i]
    else
      currentPath = currentPath .. "/" .. parts[i]
    end
    
  end
  return currentPath
end

page.child(path)

-- function page.child()
page = page or {}
function page.child(path)
  local path = path or editor.getCurrentPage()
  local parts = {}
  local pa = query[[from index.tag "page"
    where string.startsWith(name, path .. "/") 
    and not string.match(name, path .. "/.+/")
    ]]
  
  if not pa or #pa == 0 then
    -- "😮 Nessuna pagina."
    -- Returns `nil` if no pages are found. (“😮 No pages.”)
    return nil 
  end 

  return pa
end

page.lev()

-- function page.lev()
-- Calculates the number of levels in the path.
page = page or {}
function page.lev(path)
  local path = path or editor.getCurrentPage()
  
  return #string.split(path, "/")
end

page.meta(where)

-- function page.Meta(where)
-- Restituisce i meta di qualsiasi pagina
-- Returns metadata of the specified page (or current page if omitted).  
page = page or {}
function page.meta(where)
  local where = where or editor.getCurrentPage()
  local meta = query[[
    from index.tag "page" 
    where name == where
    ]]
  return meta or Null
end

page.nome()

page = page or {}
-- Extracts the final part of the path as the page name.  
-- If the last part contains an ISO date, returns only the date.  

function page.nome(path)
  -- Estrai la parte finale dopo l'ultimo "/"
  -- Extracts the last part after the final “/”.    

  local lastPart = path:match(".*/(.*)$") or path
  -- Cerca una data in formato ISO (AAAA-MM-GG)
  -- Searches for an ISO-formatted date (YYYY-MM-DD).  

  local dateISO = lastPart:match("(%d%d%d%d%-%d%d%-%d%d)")
  if dateISO then return dateISO end

  local cleaned = lastPart:gsub("_", " ")
  -- Sostituisci gli underscore con spazi
  -- Replaces underscores with spaces.  
  -- Decodifica i caratteri URL-encoded (es. %27 → ')
  -- Decodes URL-encoded characters (e.g. %27 → ').
  cleaned = cleaned:gsub("%%(%x%x)", function(hex)
    return string.char(tonumber(hex, 16))
  end)

  return cleaned
end

page.title(“path”)

-- Returns the first H1 header as the page title; 
-- if none exists, uses `page.nome()`.

page = page or {} -- function page.title(pagina)
function page.title(pagina)
  pagina = pagina or editor.getCurrentPage()

  -- Cerca un header di livello 1
  -- Searches for a level-1 header.

  local titolo = query[[
      from index.tag "header" where
        page == pagina and
        level == 1
        order by pos
        limit 1
    ]]
  if titolo then
    return tostring(titolo[1].name)
  else
    return page.nome(pagina)
  end
end

Widget

page.childlink() (widget)

Aggiunge l’elenco delle sotto pagine.

-- function page.childlink(div)
-- Widget that lists and links all subpages.  
-- Returns “No subpages.” if none exist.
-- div is the delimiter of the page list
page = page or {}

function page.childlink(div)
  local div = div or "\n* "
  local subpage = page.child()
  local subpagenum = #subpage
  local result = {}

  if subpagenum == 0 then
    return "Non ci sono pagine sottese."
  end

  table.insert(result, subpagenum .. " pagine sottese:\n")
  for i = 1, subpagenum do
    local s = subpage[i]
    local label = page.title(s.name) or s.displayName or s.name 
    table.insert(result, string.format("[[%s|%s]]", s.name, label))
  end

  return table.concat(result, div)
end

breadcrumb (markdown)

-- Creates breadcrumb navigation links up to the parent page,  
-- optionally showing previous/next arrows (👈 👉).  
-- Checks `navp` and `navs` for nil or empty values.  
-- Uses `displayName` when available.

function breadcrumb()
  local path = editor.getCurrentPage()
  local child = page.child()
  local parts = string.split(path, "/")
  local breadcrumbs = {}
  local currentPath = ""

  local navp_raw = page.navp()
  local navs_raw = page.navs()

  local navp = (navp_raw and navp_raw ~= "") and ("[[" .. navp_raw .. "|👈]]") or nil
  local navs = (navs_raw and navs_raw ~= "") and ("[[" .. navs_raw .. "|👉]]") or nil

  if parts[1] == "Diario" then
    return
  elseif parts[1] == "index" then
    return
  end

  local function getDisplayName(pagePath)
    local meta = query[[
      from index.tag "page"
      where name == pagePath
      select { displayName = displayName }
    ]]
    if meta[1] and meta[1].displayName then
      return meta[1].displayName
    else
      return string.match(pagePath, "([^/]+)$") or pagePath
    end
  end

  for i, part in ipairs(parts) do
    if i == 1 then
      currentPath = part
    else
      currentPath = currentPath .. "/" .. part
    end
    local label = page.title(currentPath)
    if i == #parts then
      if navp then table.insert(breadcrumbs, navp) end
      table.insert(breadcrumbs, "**" .. label .. "**")
    else
      table.insert(breadcrumbs, string.format("[[%s|%s]]", currentPath, label))
    end
  end

  if navs then table.insert(breadcrumbs, navs) end

  return table.concat(breadcrumbs, "|")
end
event.listen {
  name = "hooks:renderTopWidgets",
  run = function(e)
    if editor.getCurrentPage().startsWith("Diario/") then
      return
    elseif editor.getCurrentPage().startsWith("index") then
      return
    end
    return widget.new {markdown = "\n\n".. breadcrumb().."\n\n"}
  end
}

page.navp()

-- function page.navp()
-- Returns the previous page within the same section.  
-- Returns an empty string if no page is found.

page = page or {}
function page.navp()
  local paa = editor.getCurrentPage()
  local p = string.split(paa, "/")
  local pa = query[[from index.tag "page"
      where string.startsWith(name, tostring(p[1]))
      and name < paa
      order by name desc
      limit 1]]
  -- Se pa è nil o vuota, restituisce una stringa vuota
  -- Returns an empty string if no page is found.  
  if not pa or #pa == 0 then
    return ""
  end 

  return pa[1].name
end

page.navs()

-- function page.succ() 
-- Pagina successiva della pagina attuale.
-- Returns the next page within the same section.  
-- Returns an empty string if no page is found.  
page = page or {}
function page.navs()
  local paa = editor.getCurrentPage()
  local p = string.split(paa, "/")
  local pa = query[[from index.tag "page"
              where string.startsWith(name, tostring(p[1]))              
              and name > paa
              order by name 
              limit 1]]
    -- Se pa è nil o vuota, restituisce una stringa vuota
    -- Returns an empty string if no page is found.  
    if not pa or #pa == 0 then
      return ""
    end
    return pa[1].name
end
7 Likes

It will be awesome to share It like silverbullet libraries

I will study how to publish this as a library, I saw the ongoing discussion about the library module. But I keep finding edge cases where this space-script fails…

You can wait the next release to have something stable

maybe silverbullet -p 3001 "F:\your\path\to\another\test_space" to init another empty space and test them? I’m doing this way to test plugs individually, after knowing this -_-||.

1 Like