Reusable page list widget

Hey there,

was playing a little with the new lua integration after migrating to v2 and took the html / dom widget options for a spin. My goal was to create a reusable widget that consumes a page query and renders the result using styled html elements as a list. This allows to use it with any page query, in my case mainly targeting my index page for recently used notes. I also wanted to get familiar with space-lua and maybe use this approach for other widgets (e.g. task lists) later so I tried keeping it modular. It should also be easy to adapt the code to your preferences as.

Each page is rendered with name, prefix, modification date and a list of tags. The full entry acts as a link that can be clicked to reach the page, and the tags are also clickable as in other areas of silverbullet.

I am new to lua so feel free to leave feedback and ideas for improvements :slight_smile: .

Screenshots

(desktop light, mobile dark)

Code Snippets

Lua code

```space-lua
widget = widget or {}
domElements = domElements or {}

function domElements.tagButtonRow(tags)
  -- prepare tag list container
  local tagsContainer = dom.span {
    class="page-tags-container", 
    dom.b {"# "}
  }
  -- provide a fallback in case of no tags
  if table.concat(tags) == "" then
    local noTagsItem = dom.i {"no tags"}
    tagsContainer.appendChild(noTagsItem)
  end
  -- add tags as clickable elements
  for _, tag in ipairs(tags) do
    local tagItem = dom.a {
      class="sb-hashtag",
      href="/tag:" .. tag,
      rel="tag",
      tag
    }
    tagsContainer.appendChild(tagItem)
  end
  return tagsContainer
end

function domElements.pageListEntry(page)
  -- collect page info
  local pagePrefix = (page.pageDecoration and page.pageDecoration.prefix or "")
  local pageName = pagePrefix .. page.name
  local pageModified = page.lastModified
  local pageTags = page.tags
  -- assemble page list entries
  return dom.a {
    href=page.ref,
    class="page-list-entry",
    dom.div {
      class="page-name",
      pageName
    },
    domElements.tagButtonRow(pageTags),
    dom.span {
      class="page-modified",
      pageModified
    }, 
  }
end

function widget.pageList(pageQuery)
  -- prepare list container element
  local pageList = dom.div {
    class="page-list"
  }
  -- add entry for each page
  for _, page in ipairs(pageQuery) do
      local pageListEntry = domElements.pageListEntry(page)
      pageList.appendChild(pageListEntry)
  end
  -- render as html
  return widget.html(pageList)
end
```

Styling

```space-style
.page-list {
  border-radius: 12px;
  box-shadow: 0px 0px 2px grey;
}

.page-list-entry {
  text-decoration: none;
  color: inherit;
  display: block;
  margin-left: 1rem;
  margin-right: 1rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  border-bottom-color: #00000022;
  border-bottom-style: solid;
  border-bottom-width: 2px;
}
.page-list-entry:last-child {
  border-bottom-style: none;
}


.page-list-entry .page-modified {
  color: grey;
  font-size: 0.8rem;
}

.page-tags-container {
  display: flex;
  margin-top: 0.2rem;
  gap: 0.5rem;
  font-size: 0.8rem;
  vertical-align: middle;
  color: lightgrey;
}
```

Usage

${widget.pageList(query[[from index.tag "page" limit 10]])}
2 Likes

May I ask for the reason for not using a “normal” lua-query in combination with a specific template?
Should be possible to get a similar result with less effort.

On the other hand I have to admit, this way you are more flexible with the design of the output :slight_smile:

Could you elaborate what you mean by normal lua-query with template? You mean rendering using markdown templating? Or maybe it is something I am not yet aware of :smiley:.

Yes, flexibility with the design was the key aspect for me with this solution, as it allows me to also use elements not necessarily available within markdown only and attaching classes to them to fine-tune how the result is rendered.

yes, I meant something like this:

${template.each(query[[
  from index.tag "page" 
  limit 10
  order by lastModified desc
]], template.new[==[ 
[[${name}]]
    ${itags}
    ${lastModified}  
]==])}

But now, as I try it, I realize the huge difference in style :wink:
OK, mine could be tweaked some more, but I must admit, that it is way more limited :wink:

So, OK, why not use some more eloberate code to get some pretty output :slight_smile:

1 Like

Okay understood, that’s indeed too limited for what I aimed for.
And I also just wanted to try the html rendering APIs of silverbullet v2 a little anyway :smiley:.