Breadcrumbs for hierarchical pages

Hi!

I like to structure my pages in an hierarchical way and wanted to see the current nesting / “breadcrumbs”. I added a Template Widget and some space script for it, see below in case anyone else is interested. Happy to hear any comments or suggestions!

---
description: Adds breadcrumbs to pages
tags: template
hooks.top.where: 'true'
---
[[index]] > {{#each getSuperPaths(@page.name)}}[[{{.}}]] > {{/each}}{{getChildPageName(@page.name)}}
Child pages:
{{#if @page.name = "index"}}
```query
page where name =~ "^[^/]+$" render [[Library/Core/Query/Page]]
```
{{else}}
```query
page where name =~ "^" + @page.name + "/[^/]+$" render [[Library/Core/Query/Page]]
```
{{/if}}
```space-script
silverbullet.registerFunction({name: "getSuperPaths"}, (name) => {
  const nameComponents = name.split('/');
  const superPaths = [];
  for (let i = 0; i < nameComponents.length-1; i++) {
    const path = nameComponents.slice(0, i+1).join('/');
    superPaths.push(path);
  }
  return superPaths;
});

silverbullet.registerFunction({name: "getChildPageName"}, (name) => {
  const nameComponents = name.split('/');
  if (nameComponents.length < 1) {
    return "";
  } else {
    return nameComponents.at(-1)
  }
});
```
````
17 Likes

Nice! I ran into the same need and did a similar thing in space-lua before I discovered this solution.
Here it is for reference and comparison:

```space-lua
yg=yg or {}
function yg.breadcrumbs(path)
  local mypage = path or editor.get_current_page()
  local parts = string.split(mypage,"/")
  local crumbs = {}
  for i,part in ipairs(parts) do
    local current = ""
    for j=1,i do
      if current != "" then current=current.."/" end
      current = current..parts[j]
    end
    table.insert(crumbs, {name = current})
  end
  return crumbs
end

yg.t_bc = template.new([==[/[[${name}]]]==])

function yg.bc(path)
  return "[[index|🏠]]"..(template.each(yg.breadcrumbs(path),yg.t_bc)) 
end

(I ran into problems withtable.concat() and unpack(), so the sub-path assembly is loopy)

Usage (should also work in templates/live widgets) :

${yg.bc()}

Raw table:
${yg.breadcrumbs()}

For a different path:
${ yg.bc("Milky Way/Solar System/Earth") }

Will look like that:

4 Likes

For anyone else trying this: editor.get_current_page() was changed to editor.getCurrentPage().
Works fine with this adjustment, thank you!

1 Like

Hello I’m trying this method

```space-lua
yg=yg or {}
function yg.breadcrumbs(path)
  local mypage = path or editor.getCurrentPage()
  local parts = string.split(mypage,"/")
  local crumbs = {}
  for i,part in ipairs(parts) do
    local current = ""
    for j=1,i do
      if current != "" then current=current.."/" end
      current = current..parts[j]
    end
    table.insert(crumbs, {name = current})
  end
  return crumbs
end

yg.t_bc = template.new([==[/[[${name}]]]==])

function yg.bc(path)
  return "[[index|🏠]]"..(template.each(yg.breadcrumbs(path),yg.t_bc)) 
end

But I have error message while calling the function ${yg.bc()} on my pages

Lua Error : Attempting to index a nil value

I’m sorry if this is obvious but lua is a bit confusing to me.


Edit : After resyncing /reloading / resetting the application and the client : It works :partying_face:

So other question : How this is possible to have this Breadcrumbs positioned above the menu and the frontmatter ?

yg.t_bc = template.new[==[/[[${name}]] ]==]
yg.t_bcsub = template.new[==[-[[${name}]] ]==]

function yg.breadcrumbs(path)
  local mypage = path or editor.getCurrentPage()
  local parts = string.split(mypage,"/")
  local crumbs = {}
  for i,part in ipairs(parts) do
    local current = ""
    for j=1,i do
      if current ~= "" then
        current=current.."/"
      end
      current = current..parts[j]
    end
      table.insert(crumbs, {name = current})
  end
  return crumbs
end

function yg.bc(path)
  return "[[home]]"..(template.each(yg.breadcrumbs(path),yg.t_bc)).." "..(template.each(yg.children(path),yg.t_bcsub)) 
end

function compareDate(a, b)
  print(a.lastModified  > b.lastModified )
  return a.lastModified  > b.lastModified 
end


function yg.children(path)
  local crumbsChildren = {}
  local mypage = path or editor.getCurrentPage()
  for page in each(table.sort(space.listPages(), compareDate)) do
   --print(mypage,page.name,string.find(page.name,mypage) )
    if (string.find(page.name,mypage) and mypage ~= page.name and #crumbsChildren <7)
    then
          table.insert(crumbsChildren, {name = page.ref})
    end
  end
  return crumbsChildren
end

parent and last 7 modified children are visible in breadcrumbs.

---
description: Adds breadcrumbs
tags: template
hooks.top:
  where: 'not pageDecoration.disableTOC'
  # Show all the way at the top
  order: -1
---
${yg.bc()}

Template

If those interested in breadcrumbs for v2 then you don’t need the template, but a widget definition and an event listener:

function widgets.breadcrumbs()
  return widget.new {
    markdown = yg.bc()
  }
end

event.listen {
  name = "hooks:renderTopWidgets",
  run = function(e) 
    return widgets.breadcrumbs()
  end
}
2 Likes

I’ve noticed that the breadcrumb widget “collides” with the table of contents.
This is because widgets by default are generated with the span html tag to make them inline well.

I’ve tried adding display = "block" to the widget definition, or even cssClasses but it seems to only work with the html widget - not the markdown one. @zef is this a correct/expected behavior? It sure is a bit confusing :wink:

As a terrible workaround (that works), to fix this add this space-style:

div > div.content > span.wrapper > span.p {
  display: block;
}

Right, the implementation is a bit limited right now. Effectively what happens is that the renderTop/BottomWidgets events are triggered, and all the HTML and markdown that those return are simply concatenated, block or css classes are ignored. I’d have to think how to do this differently.

1 Like