175 lines
5.3 KiB
Markdown
175 lines
5.3 KiB
Markdown
---
|
|
name: silverbullet-query
|
|
description: Write Space Lua queries (SLIQ) for SilverBullet notes. Use this skill whenever the user wants to build a query, filter, or live view in their SilverBullet space — including filtering pages by folder or tag, listing tasks, showing recently modified notes, building dashboards, or embedding any kind of dynamic query widget in a note. Triggers on phrases like "make a query," "show all pages tagged," "build a filter," "list tasks in," "show recent notes," "create a silverbullet view," or "make a live widget."
|
|
---
|
|
|
|
# SilverBullet Query Skill
|
|
|
|
SilverBullet uses **SLIQ** (Space Lua Integrated Query) — an SQL-inspired query language embedded in Lua. Queries live inside `${...}` expressions that render as live widgets in any note.
|
|
|
|
## Quick reference
|
|
|
|
### Embedding a query in a note
|
|
|
|
Queries render inline using the `${...}` expression syntax:
|
|
|
|
```
|
|
${template.each(query[[
|
|
from p = index.tag "page"
|
|
where p.name:startsWith("proj/")
|
|
order by p.lastModified desc
|
|
]], templates.pageItem)}
|
|
```
|
|
|
|
Use `template.each(collection, template)` to render a list. Use `query[[...]]` alone when you just need the raw data or a single computed value.
|
|
|
|
### Data sources
|
|
|
|
| What you want | `from` clause |
|
|
|---|---|
|
|
| All pages | `from p = index.tag "page"` |
|
|
| All tasks | `from t = index.tag "task"` |
|
|
| All bullet items | `from i = index.tag "item"` |
|
|
| All paragraphs | `from p = index.tag "paragraph"` |
|
|
| All tags | `from t = index.tag "tag"` |
|
|
| A Lua list | `from n = {1, 2, 3}` |
|
|
|
|
Always use the explicit variable binding form (`p =`) — it's more reliable and future-proof.
|
|
|
|
### Filtering (`where`)
|
|
|
|
```
|
|
-- Pages in a folder
|
|
where p.name:startsWith("proj/")
|
|
|
|
-- Pages with a specific tag
|
|
where table.includes(p.tags, "account")
|
|
|
|
-- Pages WITHOUT a specific tag
|
|
where not table.includes(p.tags, "account")
|
|
|
|
-- Pages matching name
|
|
where p.name:startsWith("Person")
|
|
|
|
-- Tasks on the current page only
|
|
where t.page == _CTX.currentPage.name
|
|
|
|
-- Combine conditions
|
|
where p.name:startsWith("proj/") and table.includes(p.tags, "active")
|
|
```
|
|
|
|
> **Important:** `p.tags` is always an array (possibly empty) on every page. Do NOT use `p.tags` or `not p.tags` as a boolean check — an empty array is still truthy in Lua, so `not p.tags` is always false. Always use `table.includes()` to check for specific tags.
|
|
|
|
### Sorting (`order by`)
|
|
|
|
```
|
|
order by p.lastModified desc -- newest first
|
|
order by p.lastModified asc -- oldest first
|
|
order by p.name -- alphabetical
|
|
order by p.lastModified desc, p.name -- multi-key
|
|
```
|
|
|
|
Nulls default to last (asc) or first (desc). Override with `nulls first` / `nulls last`.
|
|
|
|
### Limits and offsets
|
|
|
|
```
|
|
limit 10
|
|
limit 10, 2 -- limit 10, skip first 2 (inline offset)
|
|
offset 5
|
|
```
|
|
|
|
### Selecting / transforming results
|
|
|
|
```
|
|
select p.name -- just the name
|
|
select { name = p.name, mod = p.lastModified } -- custom object
|
|
select p -- full object (default)
|
|
```
|
|
|
|
### Render templates
|
|
|
|
| Template | Renders as |
|
|
|---|---|
|
|
| `templates.pageItem` | `* [[page name]]` |
|
|
| `templates.fullPageItem` | `* [[page name\|page name]]` |
|
|
| `templates.taskItem` | `* [state] [[ref]] task text` |
|
|
| `templates.itemItem` | `* [[ref]] item text` |
|
|
| `templates.paragraphItem` | `* [[ref]] paragraph text` |
|
|
| `templates.tagItem` | `* [[tag:name\|#name]]` |
|
|
|
|
## Common patterns
|
|
|
|
### Pages in a folder, newest first
|
|
|
|
```
|
|
${template.each(query[[
|
|
from p = index.tag "page"
|
|
where p.name:startsWith("FOLDER/")
|
|
order by p.lastModified desc
|
|
]], templates.pageItem)}
|
|
```
|
|
|
|
### Pages with a given tag
|
|
|
|
```
|
|
${template.each(query[[
|
|
from p = index.tag "page"
|
|
where table.includes(p.tags, "TAGNAME")
|
|
order by p.lastModified desc
|
|
]], templates.pageItem)}
|
|
```
|
|
|
|
### Open tasks on the current page
|
|
|
|
```
|
|
${template.each(query[[
|
|
from t = index.tag "task"
|
|
where t.page == _CTX.currentPage.name and t.state == " "
|
|
]], templates.taskItem)}
|
|
```
|
|
|
|
### 5 most recently modified pages
|
|
|
|
```
|
|
${template.each(query[[
|
|
from p = index.tag "page"
|
|
order by p.lastModified desc
|
|
limit 5
|
|
]], templates.pageItem)}
|
|
```
|
|
|
|
### Inline count (no template)
|
|
|
|
```
|
|
${#query[[from p = index.tag "page" where p.name:startsWith("proj/")]]} projects
|
|
```
|
|
|
|
## How to write a query for the user
|
|
|
|
1. Ask what objects they want (pages, tasks, items, tags, etc.)
|
|
2. Ask what filter applies (folder prefix, tag, current page, a condition)
|
|
3. Ask how to sort (newest first is `lastModified desc`, alphabetical is `p.name`)
|
|
4. Ask if they want a limit
|
|
5. Pick the right `templates.*` for the object type
|
|
6. Wrap in `${template.each(...)}` and drop it into the note
|
|
|
|
If the user already described the intent (e.g., "show all proj pages newest first"), skip the questions and just write it.
|
|
|
|
## Placing the query
|
|
|
|
Queries go directly in the note as Markdown text — no fenced code block needed. The `${...}` syntax renders on Alt-click (or view mode). Drop the expression on its own line for clean rendering.
|
|
|
|
If the user wants the query to live in a `space-lua` block as a reusable function rather than an inline expression, wrap it:
|
|
|
|
```space-lua
|
|
function myView()
|
|
return template.each(query[[
|
|
from p = index.tag "page"
|
|
where p.name:startsWith("proj/")
|
|
order by p.lastModified desc
|
|
]], templates.pageItem)
|
|
end
|
|
```
|
|
|
|
Then call it anywhere: `${myView()}`
|