Help creating a dynamic TOC but for folder contents

Hi all, I'm new to SB. I'm trying to write a function inspired by this obsidian plugin where it creates a TOC of what is inside a folder. I was using Treeview on desktop but now that I want to use SB on mobile, I found that it's basically unusable in a small screen size.

So far, I've written this function, but currently it lists all the pages that has that path in its name. I'd like it so that it only lists the pages and folders, but not the contents of said folders.

function folderTOC(path)
  return query [[
  from p = index.tag "page"
  where p.name:find(path)
  select templates.pageItem(p)
  ]]
end

So for example, if path is "foo/"
foo
|---bar
|---|---bip.md
|---|---bop.md
|---bir.md
|---bus.md

I'd like it so it only shows

  • bar
  • bir
  • bus

instead of

  • foo
  • bar
  • bip
  • bop
  • bir
  • bus

Thanks in advance!

Delved deeper into the APIs and found a solution.

function folderTOC()
  local path = editor.getCurrentPage()
  return query [[
  from p = tags.page
  where string.matchRegex(p.name, "^" .. path .. "/[^/]+$")
  order by p.name asc
  select templates.pageItem(p)
  ]]
end

The function now automatically grabs the path the page is in.

This is great, and I tried to achieve that already without success :slight_smile:
However, I have a small problem with it:
Taking the structure from your example, it only works as expected, when there is a own page for every directory (like your "bar"). Without an existing bar.md the directory does not show.
Sometimes I have only pages "under" an directory without a "top" page for it...

I guess that's one limitation yes. I imported most of my notes from obsidian and on there I use folder notes for a directory/overview of the contents inside that folder. So basically I already had the note/page and it never crossed my mind that when there's no folder note, you'd have to create one yourself.

Also, since the function I wrote depends more on how silverbullet handles page names, it never really touches any of the folders in the filesystem.

To avoid having to create empty pages to "represent" folders, you can leverage the new query group APIs.

This should get you started:

query[[
  from index.tag "page"
  where _.name:startsWith(editor.getCurrentPage() .. "/")
  group by _.name:match(literalize(editor.getCurrentPage()) .. "/([^/]+)")
  select {
    name = key,
    children = #group - 1,
  }
  order by key desc
]]

In my space, I ended up creating a command for navigating "down" across my directory tree. This is what I use:

command.define {
  name = "Navigate: Down",
  key = "Ctrl-Alt-ArrowDown",
  mac = "Cmd-Alt-ArrowDown",
  run = function()
    local function generateDescription(count)
      if count == 1 then
        return ""
      end
      return count - 1 .. " pages"
    end

    local function literalize(str)
      return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end)
    end
    
    local cur = editor.getCurrentPage()
    local pages = query[[
      from index.tag "page"
      where _.name:startsWith(cur .. "/")
      group by _.name:match(literalize(cur) .. "/([^/]+)")
      select {
        name = key,
        description = generateDescription(#group),
        ref = key,
      }
      order by key desc
    ]]

    if not pages or #pages == 0 then
      editor.flashNotification("No child pages found", "error")
      return
    end
    local result = editor.filterBox("Directory:", pages, #pages .. " children pages found")
    if not result then
      return
    end
    editor.navigate(cur .. "/" .. result.ref)
  end,
}