diff --git a/website/Completion.md b/website/Completion.md new file mode 100644 index 00000000..813525bb --- /dev/null +++ b/website/Completion.md @@ -0,0 +1,28 @@ +SilverBullet provides context-aware autocomplete to help you write faster. Completions are triggered automatically or via keyboard shortcuts as you type. + +# Page link completion +Type `[[` to trigger page name completion. SilverBullet searches across all pages in your space and offers matching suggestions. Select one to insert a [[Link]] to that page. If no page with that name exists, a link is still created — navigating to it will create the page (these are tracked as [[Aspiring Pages]]). + +# Tag completion +Type `#` to trigger tag completion. SilverBullet suggests existing tags from across your space. This helps maintain consistent tagging — no more typos creating duplicate tags. + +# Emoji completion +Type `:` followed by a keyword to search for emoji. For example, `:rocket` offers the rocket emoji. Press Enter to insert it. + +# Frontmatter key completion +Inside a [[Frontmatter]] block, SilverBullet suggests attribute keys that are already used elsewhere in your space. This helps keep your metadata schema consistent. + +# Slash commands +Type `/` at the beginning of a line (or after a space) to trigger [[Slash Command]] completion. Slash commands can insert templates, perform actions, or trigger custom behavior defined in [[Space Lua]]. + +# Lua code completion +Inside `space-lua` fenced code blocks, SilverBullet provides code completion for: + +* Global functions and variables +* API namespaces (`editor.`, `space.`, `index.`, etc.) +* Table fields and methods + +# Custom completions +You can extend the completion system by subscribing to the `editor:complete` event via [[Space Lua]]. Your handler receives the current cursor context and can return additional completion items. + +See also: [[API/event]] diff --git a/website/Glossary.md b/website/Glossary.md new file mode 100644 index 00000000..cd617119 --- /dev/null +++ b/website/Glossary.md @@ -0,0 +1,8 @@ +A quick-reference guide to SilverBullet-specific terminology: +${template.each(query[[ + from p = tags.glossary + where p.tag == "page" + order by p.name +]], template.new [==[ + - [[${name}]]: ${description} +]==])} diff --git a/website/Guide/Knowledge Base.md b/website/Guide/Knowledge Base.md new file mode 100644 index 00000000..f11c1b26 --- /dev/null +++ b/website/Guide/Knowledge Base.md @@ -0,0 +1,72 @@ +#guide + +This guide walks you through building a personal knowledge base with SilverBullet. You'll learn how pages, links, tags, queries, and transclusions work together to create an interconnected web of knowledge. + +# 1. Create your first topic pages +Start with 3–4 pages on topics you know well: a programming language, a recipe, a book you've read. Open the [[Page Picker]] (`Cmd-k` / `Ctrl-k`), type a name, and press Enter. + +Keep each page focused on one topic. A page about “Rust” covers Rust. A page about “Ownership” covers ownership. This “atomic note” approach makes pages easy to link and reuse. + +Write freely — a paragraph or two is enough to get started. + +# 2. Link as you write +As you write on one page, naturally reference another. Type `[[` and SilverBullet autocompletes page names: + +```markdown +Rust's [[Ownership]] model prevents data races at compile time. +``` + +Don’t worry about organizing pages into the “right” folder structure upfront (or at all). Just write and link. The structure emerges from the connections. + +# 3. Discover connections via backlinks +Navigate to one of your linked pages (click a `[[link]]` or use the Page Picker). Scroll to the bottom and look for the **Linked Mentions** section. + +This shows every page that links _to_ the current page — connections you didn't have to manually create. For example, your “Ownership” page will show that your “Rust” page mentions it. + +This is how a knowledge base builds itself: write naturally, link as you go, and let [[Linked Mention|linked mentions]] surface the connections. + +# 4. Add structure when it's useful +When you want to categorize or query pages, add some structure. + +**Hashtags** on an empty line tag the page (see [[Markdown/Hashtags#Scope rules]]): +```markdown +#concept +``` + +**[[Frontmatter]]** at the top of a page adds structured data: +```yaml +--- +tags: book +author: Italo Calvino +status: reading +--- +``` + +Now this page is tagged `book` and has queryable `author` and `status` attributes. You can add tags to a page either with the `#book` syntax, or via a [[Frontmatter]] attribute. + +# 5. Query your knowledge +[[Space Lua/Lua Integrated Query]] lets you pull live data from your pages. Create a “Currently Reading” page with a query: + +```lua +${template.each(query[[ + from b = tags.book + where b.status == "reading" + order by b.lastModified desc +]], templates.pageItem)} +``` + +See [[Template]] for more rendering options. + +# 6. Embed content across pages +Use [[Transclusions]] to pull content from one page into another. For instance on your [[Index Page]], transclude your “Currently Reading” page: + +```markdown +![[Currently Reading]] +``` + +# What's next? +You now have the core pattern: create pages, link them, add structure with tags and frontmatter, and query across your space. + +* [[Guide/Task Management]] — track projects and tasks +* [[Guide/People Notes]] — keep track of people and conversations +* [[Manual]] — the full user manual diff --git a/website/Guide/People Notes.md b/website/Guide/People Notes.md new file mode 100644 index 00000000..ef400ae6 --- /dev/null +++ b/website/Guide/People Notes.md @@ -0,0 +1,83 @@ +#guide + +This guide walks you through building a personal CRM with SilverBullet. You'll use person pages, meeting notes, linked mentions, linked tasks, and page decorations to automatically track your interactions with people. + +# 1. Create person pages +Make a page for someone you interact with, let’s say “Alice.” Add [[Frontmatter]] to describe them: + +```yaml +--- +--- +``` + +Create a few more person pages the same way. + +## Make person pages stand out +Add a `tag.define` call to your [[CONFIG]] page so person pages get a visual prefix: + +```space-lua +tag.define { + name = "person", + transform = function(o) + o.pageDecoration = { prefix = "🧑 " } + return o + end +} +``` + +After a reload, person pages will show a 🧑 prefix in the [[Page Picker]], [[Completion|completions]], and editor. See [[Page Decorations]] and [[API/tag]] for more options. + +> **warning** Warning +> Tag customization is still a beta feature, it may change in the future. + +# 2. Take meeting notes +Create a page like “Meeting/2026-03-04” and write naturally, linking to attendees: + +```markdown +Met with [[Alice]] and [[Bob]] to discuss the Q2 roadmap. Alice will lead the backend migration. +``` + +The `[[Alice]]` and `[[Bob]]` links connect this meeting note to their person pages. + +# 3. Add tasks that mention people +Write tasks that reference person pages: + +```markdown +* [ ] Send proposal to [[Alice]] +* [ ] Schedule follow-up with [[Bob]] +* [ ] Share roadmap doc with [[Alice]] and [[Bob]] +``` + +These tasks are now linked to both the meeting notes page and the person pages. + +# 4. See a person's full history +Open Alice’s page. Two things happen automatically: + +* **[[Linked Tasks]]** at the top shows all incomplete tasks from other pages that mention Alice (like “Send proposal to Alice” from your meeting notes). +* **[[Linked Mention|Linked Mentions]]** at the bottom shows every page that references Alice — meeting notes, project pages, anything. + +Alice’s page becomes an automatic activity log without you maintaining it. + +# 5. Query across your network +Build useful views on a “People” page or your home page: + +```lua +# People at Acme Corp +${query[[from p = tags.person where p.company == "Acme Corp"]]} +``` + +Show all people, grouped by company: + +```lua +${query[[ + from p = tags.person + order by p.company +]]} +``` + +# What’s next? +You now have a very basic personal CRM: person pages with structured data, meeting notes that link to attendees, tasks that reference people, and automatic activity logs via linked mentions and linked tasks. + +* [[Guide/Knowledge Base]] — build a personal knowledge base +* [[Guide/Task Management]] — track projects and tasks +* [[Manual]] — the full user manual diff --git a/website/Guide/Task Management.md b/website/Guide/Task Management.md new file mode 100644 index 00000000..d0d4c13b --- /dev/null +++ b/website/Guide/Task Management.md @@ -0,0 +1,99 @@ +#guide + +This guide walks you through a project and task management workflow. You'll learn how tasks, frontmatter, linked tasks, and queries combine into a lightweight project tracker — no extra tools needed. + +# 1. Create a project page +Make a page called "Website Redesign" with structured [[Frontmatter]]: + +```yaml +--- +tags: project +status: active +priority: high +--- +``` + +Below the frontmatter, write a brief description of the project and its goals. + +# 2. Add tasks +Type `/task` to insert a task (or just type `* [ ] ` directly). Add a few tasks on the project page: + +```markdown +* [ ] Write the project proposal +* [ ] Create wireframes +* [ ] Set up staging environment +``` + +Click a checkbox to mark a task as done. All tasks are automatically indexed and queryable. + +# 3. Annotate tasks with attributes +Add metadata to tasks using [[Attribute]] syntax: + +```markdown +* [ ] Write the proposal [deadline: "2026-03-15"] [priority: high] +* [ ] Review design mockups [assignee: Alice] +``` + +Optionally hashtags on tasks to categorize them: + +```markdown +* [ ] Get client approval #waiting +``` + +This task is now tagged both `task` and `waiting`, so you can query either tag. + +# 4. Scatter tasks across pages +Real tasks don’t all live in one place — they come up in meetings, while reading, or during other work. Create a page like "Meeting Notes/2026-03-04" and write tasks that link back to the project: + +```markdown +* [ ] Send updated mockups to client [[Website Redesign]] +* [ ] Schedule review meeting [[Website Redesign]] +``` + +The `[[Website Redesign]]` link connects these tasks to the project. + +# 5. See linked tasks automatically +Navigate to your "Website Redesign" page. At the top, the **[[Linked Tasks]]** widget shows all incomplete tasks from _other_ pages that link to this page — including the ones from your meeting notes. + +You can check off a linked task from either page; the state change propagates. No manual copying or moving of tasks needed. + +# 6. Build a dashboard +Create a "Dashboard" page that pulls everything together using [[Space Lua/Lua Integrated Query]]: + +```lua +# Active projects +${query[[from p = tags.project where p.status == "active"]]} +``` + +Add a section for (recently) open tasks (maximum 10): + +```lua +# Open tasks +${template.each(query[[ + from t = tags.task + where not t.done + order by t.lastModified desc + limit 10 +]], templates.taskItem)} +``` + +And a section for tasks with deadlines: + +```lua +# Due soon +${template.each(query[[ + from t = tags.task + where not t.done and t.deadline + order by t.deadline + limit 5 +]], templates.taskItem)} +``` + +Each section updates live as you add, complete, or modify tasks across your space. + +# What's next? +You now have a project tracking system: project pages with frontmatter, tasks scattered naturally across pages, linked tasks that connect everything, and a dashboard for the big picture. + +* [[Guide/Knowledge Base]] — build a personal knowledge base +* [[Guide/People Notes]] — keep track of people and conversations +* [[Manual]] — the full user manual diff --git a/website/Quick Start.md b/website/Quick Start.md new file mode 100644 index 00000000..b259185a --- /dev/null +++ b/website/Quick Start.md @@ -0,0 +1,94 @@ +#getting-started + +Welcome! This guide gets you from zero to productive with SilverBullet in about five minutes. + +# 1. Install and run +The fastest way to get started is with Docker: + +```shell +docker run -p 3000:3000 -v ./space:/space ghcr.io/silverbulletmd/silverbullet +``` + +Or download the [[Install/Binary|single binary]] and run it: + +```shell +silverbullet ./space +``` + +Now open http://localhost:3000 in your browser. + +> **note** Tip +> For a full breakdown of installation options, see [[Install]]. + +# 2. Create your first page +Click the page icon in the [[Top Bar]] (or press `Cmd-k` / `Ctrl-k`) to open the [[Page Picker]]. Type a page name like "My First Page" and press Enter — SilverBullet creates it instantly. + +Start typing. Everything is [[Markdown]]. + +# 3. Link pages together +Type `[[` to link to another page. SilverBullet autocompletes page names for you. Links are bi-directional: the linked page will show a [[Linked Mention]] back to the page you're writing. + +# 4. Add some structure +Tag a page by adding a hashtag on an empty line (see [[Markdown/Hashtags#Scope rules]]), for instance `#project`. Or add structured data using [[Frontmatter]] at the top of a page: + +```yaml +--- +status: active +priority: high +tags: project +--- +``` + +These attributes become queryable, which we’re going to do next. + +# 5. Run your first queries +Pages in SilverBullet can use [[Space Lua]] and [[Space Lua/Lua Integrated Query]] feature specifically to dynamically generate content. Add this to any page: + +```lua +${query[[from tags.page limit 5]]} +``` + +As you move your mouse cursor outside this code fragment, it renders a live table of your pages, right inline. Queries update as your space changes dynamically. + +Now, let’s write a query that finds all your pages tagged with `#project` (as done in the previous step) filtered on only high priority ones: + +```lua +${query[[from p = tags.project where p.priority == "high"]]} +``` + +# 6. Use a template to customize query rendering +By default, queries are rendered as tables, however you can render them in other ways as well. SilverBullet comes with a set of pre-defined [[^Library/Std/Infrastructure/Query Templates]] you can use, for instance: + +```lua +# High qriority projects +${template.each(query[[ + from p = tags.project + where p.priority == "high" + order by p.lastModified desc +]], templates.pageItem)} +``` + +This renders your 5 most recently updated high-priority projects as a bulleted list (an `item` in SilverBullet parlance). + +Of course, you can also create custom templates. Either as reusable Lua functions, or inline: + +```lua +# High qriority projects +${template.each(query[[ + from p = tags.project + where p.priority == "high" + order by p.lastModified desc +]], template.new[==[ + * ${name} (status: ${status}) +]==])} +``` + +# What's next? +Now that you know the basics, explore these guides for real-world workflows: + +* [[Guide/Knowledge Base]] — build a personal knowledge base +* [[Guide/Task Management]] — track projects and tasks +* [[Guide/People Notes]] — keep track of people and conversations +* [[Manual]] — the full user manual +* [[Space Lua]] — learn more about the scripting language that gives SilverBullet a lot of its power +* [[Object]] — understand how SilverBullet indexes your content diff --git a/website/Space Lua/DOM.md b/website/Space Lua/DOM.md new file mode 100644 index 00000000..07b2e876 --- /dev/null +++ b/website/Space Lua/DOM.md @@ -0,0 +1,100 @@ +The DOM builder API provides a clean, declarative way to construct HTML elements from [[Space Lua]]. It is typically used to build [[API/widget|Widgets]] for rendering dynamic UI in your pages. + +The full API reference is at [[API/dom]]. + +# Basic usage +Every HTML tag is available as a function on the `dom` table. Call it with a table of attributes and children: + +```lua +dom.div { + class = "my-container", + dom.h2 { "Hello!" }, + dom.p { "This is a paragraph." } +} +``` + +This creates a `
`. + +# Attributes and content +String keys in the table become HTML attributes. Numeric entries become children: + +```lua +dom.a { + href = "https://example.com", + "Click here" +} +``` + +**Markdown support**: String children are automatically processed as markdown. So `"**bold**"` renders as bold text. + +**Nested elements**: Other `dom.*` calls can be nested as children to build up a tree. + +# Event handlers +Attributes starting with `on` are registered as event listeners: + +```lua +dom.button { + onclick = function() + editor.flashNotification("Clicked!") + end, + "Click me" +} +``` + +This is equivalent to calling `addEventListener("click", fn)` on the button element. + +# Rendering as a widget +DOM elements need to be wrapped in a widget to display on a page. Use `widget.html` for inline or `widget.htmlBlock` for block-level: + +```lua +-- Inline widget (rendered within text flow) +widget.html(dom.span { class = "badge", "New" }) + +-- Block widget (gets its own block) +widget.htmlBlock(dom.table { + dom.tr { + dom.td { "Name" }, + dom.td { "Value" } + } +}) +``` + +# Building tables dynamically +A common pattern is building HTML tables from query results: + +```lua +local rows = {} +for page in query[[ from index.tag "page" limit 5 ]] do + table.insert(rows, dom.tr { + dom.td { "[[" .. page.name .. "]]" }, + dom.td { os.date("%Y-%m-%d", page.lastModified) } + }) +end + +return widget.htmlBlock(dom.table { + dom.thead { + dom.tr { + dom.td { "Page" }, + dom.td { "Modified" } + } + }, + dom.tbody(rows) +}) +``` + +# Embedding widgets inside DOM +Widget objects (like buttons from `widgets.button`) can be nested inside DOM elements: + +```lua +dom.div { + "Status: ", + widgets.button("Refresh", function() + editor.invokeCommand("System: Reload") + end) +} +``` + +# How it works +Under the hood, `dom` uses a Lua metatable so that any property access (e.g. `dom.span`) returns a constructor function. That function calls `js.window.document.createElement(tag)` and processes the spec table to set attributes, add event listeners, and append children. + +See also: [[API/dom]], [[API/widget]], [[Space Lua/Widget]] diff --git a/website/Space Lua/JavaScript Interop.md b/website/Space Lua/JavaScript Interop.md new file mode 100644 index 00000000..f9e9b480 --- /dev/null +++ b/website/Space Lua/JavaScript Interop.md @@ -0,0 +1,64 @@ +Space Lua runs in the browser and has direct access to JavaScript APIs through the `js` module. This enables you to use the browser's native capabilities and import external JavaScript libraries. Use this functionality with caution. With great power comes comes great responsibility. + +The full API reference is at [[API/js]]. + +# Accessing browser APIs +Use `js.window` to access the browser's `window` object: + +```lua +-- Get the current URL +local url = js.window.location.href + +-- Set a timeout +js.window.setTimeout(function() + editor.flashNotification("Timer fired!") +end, 3000) +``` + +# Importing JavaScript modules +Use `js.import` to load JavaScript modules from URLs (typically via CDNs like esm.sh): + +```lua +local lodash = js.import("https://esm.sh/lodash@4.17.21") +local chunks = lodash.chunk({1, 2, 3, 4, 5, 6}, 2) +print(js.stringify(chunks)) -- [[1,2],[3,4],[5,6]] +``` + +# Converting between Lua and JavaScript values +Lua tables and JavaScript objects/arrays are different types. Space Lua tries its best to map between them the best in can, but sometimes you may need finer grained control: + +* `js.tojs(luaValue)` — converts a Lua value to its JavaScript equivalent +* `js.tolua(jsValue)` — converts a JavaScript value to its Lua equivalent + +# Creating JavaScript objects +Use `js.new` to instantiate JavaScript classes: + +```lua +local obj = js.new(SomeConstructor, arg1, arg2) +``` + +# Async transparency +Space Lua handles JavaScript promises transparently. When a JavaScript function returns a Promise, Space Lua automatically awaits it — you don't need to write any special async/await code: + +```lua +-- This just works, even though fetch() returns a Promise +local response = js.window.fetch("https://api.example.com/data") +``` + +# Iterating JavaScript async iterables +Use `js.eachIterable` to iterate over JavaScript async iterables: + +```lua +for value in js.eachIterable(someAsyncIterable) do + print(value) +end +``` + +# Logging +Use `js.log` to write to the browser's developer console: + +```lua +js.log("Debug:", {name = "test", count = 42}) +``` + +See also: [[API/js]], [[Space Lua]] diff --git a/website/Space Lua/Standard Library.md b/website/Space Lua/Standard Library.md new file mode 100644 index 00000000..fdebb138 --- /dev/null +++ b/website/Space Lua/Standard Library.md @@ -0,0 +1,108 @@ +Space Lua includes a comprehensive standard library based on Lua 5.4, with additional non-standard extensions useful for text processing and note-taking workflows. + +This page gives an overview. Each module has its own detailed API reference page. + +# Global functions +The following functions are available globally (no module prefix needed): + +| Function | Description | +|---|---| +| `print(...)` | Print to the log (browser console or server log) | +| `type(v)` | Returns the type of a value as a string | +| `tostring(v)` | Converts a value to its string representation | +| `tonumber(s)` | Converts a string to a number | +| `assert(expr, msg?)` | Raises an error if `expr` is falsy | +| `error(msg)` | Throws an error | +| `pcall(fn, ...)` | Calls a function in protected mode, catching errors | +| `xpcall(fn, handler)` | Like `pcall` but with a custom error handler | +| `pairs(t)` | Iterator over all key-value pairs in a table | +| `ipairs(t)` | Iterator over integer-keyed entries in order | +| `unpack(t)` | Unpacks a table into individual values | +| `setmetatable(t, mt)` | Sets the metatable for a table | +| `getmetatable(t)` | Gets the metatable of a table | +| `rawset(t, k, v)` | Sets a table key bypassing metamethods | +| `dofile(path)` | Loads and executes a `.lua` file from your space | + +**Non-standard globals:** + +| Function | Description | +|---|---| +| `each(t)` | Iterator over values only (no indices) | +| `some(v)` | Returns `nil` if value is "empty" (empty table, whitespace-only string, inf, nan), otherwise returns the value unchanged | + +`some()` is particularly useful in templates and queries for handling missing data gracefully: + +```lua +print(some("hello")) -- hello +print(some("")) -- nil +print(some({})) -- nil +print(some({}) or "empty") -- empty +``` + +Full reference: [[API/global]] + +# string +Standard Lua string operations plus useful extensions. Since strings have `string` as their metatable, you can call these as methods: `s:startsWith("h")`. + +**Non-standard extensions:** + +| Function | Description | +|---|---| +| `string.split(s, sep)` | Splits a string by separator | +| `string.startsWith(s, prefix)` | Tests if a string starts with a prefix | +| `string.endsWith(s, suffix)` | Tests if a string ends with a suffix | +| `string.trim(s)` | Strips whitespace from both ends | +| `string.trimStart(s)` | Strips leading whitespace | +| `string.trimEnd(s)` | Strips trailing whitespace | +| `string.matchRegex(s, pattern)` | Matches against a JavaScript regex | +| `string.matchRegexAll(s, pattern)` | Iterator over all JavaScript regex matches | + +> **warning** Lua patterns vs. regex +> Standard Lua `string.find`, `string.match`, `string.gmatch`, and `string.gsub` use Lua patterns, which are _not_ regular expressions. See [[API/string]] for differences. Use `matchRegex`/`matchRegexAll` when you need full regex support. + +Full reference: [[API/string]] + +# table +Table manipulation functions, plus non-standard extensions: + +| Function | Description | +|---|---| +| `table.keys(t)` | Returns an array of all keys | +| `table.includes(t, value)` | Checks if a list contains a value | +| `table.find(t, fn, from?)` | Finds first element matching a criteria function | +| `table.select(t, keys...)` | Returns a new table with only selected keys | + +Full reference: [[API/table]] + +# math +Standard Lua math functions (`abs`, `ceil`, `floor`, `max`, `min`, `sqrt`, `sin`, `cos`, trigonometric functions, etc.) plus: + +| Function | Description | +|---|---| +| `math.cosineSimilarity(a, b)` | Cosine similarity between two vectors | + +Full reference: [[API/math]] + +# os +Date and time functions: + +| Function | Description | +|---|---| +| `os.time(table?)` | Current Unix timestamp, or timestamp for a specific date | +| `os.date(format?, timestamp?)` | Formats a timestamp as a string | + +Full reference: [[API/os]] + +# encoding +Functions for encoding and decoding data: + +| Function | Description | +|---|---| +| `encoding.base64Encode(data)` | Encode data as base64 | +| `encoding.base64Decode(s)` | Decode a base64 string | +| `encoding.utf8Encode(s)` | Encode a UTF-8 string to bytes | +| `encoding.utf8Decode(data)` | Decode bytes to a UTF-8 string | + +Full reference: [[API/encoding]] + +See also: [[Space Lua]], [[Space Lua/JavaScript Interop]] diff --git a/website/Virtual Pages.md b/website/Virtual Pages.md new file mode 100644 index 00000000..53d4cdaf --- /dev/null +++ b/website/Virtual Pages.md @@ -0,0 +1,75 @@ +--- +description: A page generated dynamically by code rather than stored as a file. +tags: glossary +--- + +Virtual pages are read-only pages that don't exist as files in your space. Instead, they are generated dynamically when you navigate to them. This is useful for building pages whose content is computed on-the-fly — for example, pages that show all objects with a particular tag. + +# How it works +You define a virtual page by calling `virtualPage.define` with a Lua pattern and a function. When someone navigates to a page name matching the pattern, SilverBullet calls your function instead of loading a file from disk. + +The function receives the captured groups from the pattern as arguments, and returns the markdown content to display. + +```lua +virtualPage.define { + pattern = "greeting:(.+)", + run = function(name) + return "# Hello, " .. name .. "!\nWelcome to this virtual page." + end +} +``` + +Navigating to `greeting:World` renders a page with the heading "Hello, World!" — but no file is created. + +Virtual pages are always **read-only**. The editor disables editing controls automatically. + +# Built-in virtual pages + +## Tag pages +The most commonly used virtual pages are **tag pages**. When you click a hashtag like `#project`, SilverBullet navigates to `tag:project`, which is a virtual page that lists all objects with that tag — grouped by type (pages, tasks, items, data, etc.). + +Tag pages are defined in the standard library and work out of the box. You can override the default tag page by defining your own `virtualPage.define` with the pattern `tag:(.+)`. + +## URI pages +Navigating to a page named `uri:https://example.com/page` fetches and displays the content from that URL. This is useful for pulling in external markdown content. + +# Defining your own virtual pages +Here's a more complete example that queries the object index: + +```lua +virtualPage.define { + pattern = "recent:(%d+)", + run = function(count) + local n = tonumber(count) + local pages = query[[ + from index.tag "page" + order by lastModified desc + limit n + ]] + local result = "# " .. count .. " Most Recent Pages\n" + for _, page in ipairs(pages) do + result = result .. "* [[" .. page.name .. "]]\n" + end + return result + end +} +``` + +Navigate to `recent:10` to see the 10 most recently modified pages. + +# Multiple capture groups +Patterns can capture multiple groups, each passed as a separate argument: + +```lua +virtualPage.define { + pattern = "lookup:(.+):(.+)", + run = function(type, id) + return "# " .. type .. "\nLooking up: " .. id + end +} +``` + +# How it works under the hood +Virtual page definitions are stored in the config system under the `virtualPages` key. When a page is being created, SilverBullet fires the `editor:pageCreating` event. The standard library's event listener checks all registered patterns against the page name. If a match is found, the corresponding `run` function is called, and its return value becomes the page content with read-only permissions. + +See also: [[API/event]], [[API/config]]