Files
plainleaf/docs/Plugs/Development/Reference.md
T
2026-07-16 12:10:00 +02:00

272 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
references:
- plug-api/types/manifest.ts
- client/plugos/hooks/command.ts
- client/plugos/hooks/syscall.ts
---
This page is the reference surface for plug authors: the `*.plug.yaml` manifest, every hook type, and the full syscall catalog.
# Manifest
A plug is described by a single YAML file, conventionally named `{plugname}.plug.yaml`. Top-level keys:
* `name`: Plug name (required, must be unique)
* `requiredPermissions`: List of permissions this plug may use. Recognized values: `fetch`, `shell`.
* `functions`: function-name to [[#Function definitions|function mappings]] — the heart of the manifest.
* `assets`: Glob patterns of files to bundle into the plug. Readable at runtime via [[API/asset]] calls.
* `build`: Optional pre-bundle transforms (`esbuild` / `sass` / `copy`). Rarely needed.
## Function definitions
Each entry under `functions:` is one exported plug function. Fields:
| Field | Description |
|-------|-------------|
| `path` | `"file.ts:exportedFunction"` — the TypeScript source to bundle. |
| `redirect` | `"otherPlug.function"` — alias to another plug’s function (no local code). |
| `syscall` | `"myplug.foo"` — expose this function as a syscall callable from other plugs and from [[Space Lua]]. |
| _hook fields_ | Zero or more of `command`, `slashCommand`, `events`, `mqSubscriptions`, `codeWidget`, `editor`, `pageNamespace` — each wires this function into an editor event. See [[#Hooks]] below. |
A function with only a `syscall` field (no other hooks) is a pure library function; a function with one or more hook fields is triggered when the corresponding event fires.
## Examples
A minimal, events-only manifest:
```yaml
name: hello
functions:
greet:
path: "./hello.ts:greet"
events:
- editor:init
```
A mixed manifest (excerpted from the built-in `index` plug):
```yaml
name: index
functions:
extractFrontmatter:
path: api.ts:extractFrontmatter
syscall: index.extractFrontmatter
reindexSpaceCommand:
path: ./command.ts:reindexCommand
command:
name: "Space: Reindex"
processIndexQueue:
path: ./queue.ts:processIndexQueue
mqSubscriptions:
- queue: indexQueue
batchSize: 3
autoAck: true
indexPage:
path: indexer.ts:indexPage
events:
- page:index
```
This one manifest declares a syscall, a command, a message-queue subscriber, and an event subscriber.
# Hooks
Each hook type is a different way for a plug function to be triggered. A single function may declare more than one hook.
## `command`
Declares a [[Command]] available from the command palette and (optionally) via a keybinding.
```yaml
toggleDarkMode:
path: "./editor.ts:toggleDarkMode"
command:
name: "Editor: Toggle Dark Mode"
key: "Ctrl-Alt-d"
requireMode: rw # "rw" or "ro"
requireEditor: page # "any", "page", "notpage", or a string editor name
```
All command fields: `name` (required), `key` / `mac`, `priority`, `contexts`, `requireMode`, `requireEditor`, `hide`, `disableInVim`.
```typescript
import { editor } from "@silverbulletmd/silverbullet/syscalls";
export async function toggleDarkMode() {
const current = await editor.getUiOption("darkMode");
await editor.setUiOption("darkMode", !current);
}
```
## `slashCommand`
Declares a [[Slash Command]] that appears after the user types `/`.
```yaml
insertToday:
path: ./dates.ts:insertToday
slashCommand:
name: today
description: "Insert today's date"
```
Fields: `name` (required), `description`, `priority`, `onlyContexts`, `exceptContexts`. The context fields restrict the command to (or exclude it from) certain AST node types, e.g. inside fenced code blocks.
```typescript
import { editor } from "@silverbulletmd/silverbullet/syscalls";
export async function insertToday() {
const today = new Date().toISOString().slice(0, 10);
await editor.insertAtCursor(today);
}
```
## `events`
Subscribes the function to one or more [[Event]] names. Wildcards are supported (e.g. `page:*`). The function receives the event payload.
```yaml
onPageSaved:
path: ./track.ts:onPageSaved
events:
- editor:pageSaved
```
```typescript
import { editor } from "@silverbulletmd/silverbullet/syscalls";
export async function onPageSaved(pageName: string) {
await editor.flashNotification(`Saved ${pageName}`);
}
```
## `mqSubscriptions`
Subscribes the function to a [[API/mq|message queue]]. Useful for background / batched work.
```yaml
processQueue:
path: ./worker.ts:processQueue
mqSubscriptions:
- queue: myQueue
batchSize: 10
pollInterval: 5000
autoAck: true
```
```typescript
import type { Message } from "@silverbulletmd/silverbullet/type/datastore";
export async function processQueue(messages: Message[]) {
for (const msg of messages) {
console.log("processing", msg.body);
}
}
```
## `codeWidget`
Registers a custom renderer for a fenced code block language. The function receives the block body and the current page name, and returns content to render (markdown by default, or HTML in an iframe when `renderMode: iframe`).
```yaml
clockWidget:
path: ./clock.ts:clockWidget
codeWidget: clock
```
````typescript
export async function clockWidget(_body: string, _pageName: string) {
return { markdown: `The time is ${new Date().toLocaleTimeString()}` };
}
````
Triggered when the page contains:
````
```clock
```
````
## `editor` (document editor)
Registers a custom editor for a file extension. The function takes over rendering for files of that type.
```yaml
svgEditor:
path: ./svg.ts:svgEditor
editor: ".svg"
```
## `syscall`
Exposes the function as a syscall, callable from other plugs and from Space Lua. Combines with `path:` to provide an implementation. There is no separate TS sketch here — your function is a normal TypeScript function; whatever it returns is the syscall return value.
```yaml
myAdd:
path: ./math.ts:add
syscall:
name: myplug.add
description: Adds two numbers.
parameters:
- { name: a, type: number }
- { name: b, type: number }
returns:
- { type: number }
```
```typescript
export function add(a: number, b: number) {
return a + b;
}
```
Other plugs can now `await system.invokeFunction("myplug.add", 1, 2)`, and Lua can call `myplug.add(1, 2)`.
# Syscalls
Plug code reaches the editor, space, and storage exclusively through _syscalls_. Import them from `@silverbulletmd/silverbullet/syscalls`:
```typescript
import { editor, space, system } from "@silverbulletmd/silverbullet/syscalls";
await editor.flashNotification("Hi");
const pages = await space.listPages();
```
Under the hood, each of these calls `globalThis.syscall("editor.flashNotification", "Hi")` which is forwarded from the Web Worker to the main thread (`silverbullet/plug-api/syscall.ts`). Syscalls that need a permission throw if the plug didn’t declare it in `requiredPermissions`.
## Available syscalls
All syscalls available to plugs are documented on pages tagged `#api/syscall`:
${query[[ from p = index.pages("api/syscall") where p.tag == "page" order by p.name select templates.pageItem(p) ]]}
## Declaring your own syscall
Any plug function can be exposed as a syscall by adding `syscall: yourplug.foo` to its manifest entry. The object form shown above additionally exposes structured documentation to Space Lua through `spacelua.describe()` and `spacelua.listFunctions()`. Other plugs can call the syscall via `system`, and Space Lua code can call it as `yourplug.foo(...)`. See the [[#Syscalls]] above.
## Bundling assets
The `assets:` manifest key bundles files into the plug binary. Read them at runtime with the [[API/asset|asset]] syscall:
```typescript
import { asset } from "@silverbulletmd/silverbullet/syscalls";
const css = await asset.readAsset("myplug", "styles.css", "utf8");
```
Useful for shipping CSS, templates, images, or any other binary blob alongside your code.
# Shared Components
Panels (opened via [[API/editor#editor.showPanel(id, mode, html, script)]]) can get SilverBullet's standard component styling by linking the shared stylesheet and using `.sb-*` classes.
```html
<link rel="stylesheet" href=".client/components.css">
<button class="sb-button sb-button-primary">Save</button>
<input class="sb-input" placeholder="Name" />
```
The easiest way to set this up is the `panelStyles()` helper from `@silverbulletmd/silverbullet/ui`, which builds a preamble injecting both `.client/components.css` and the user’s [[Space Style]] (custom theming), so panels match the rest of SilverBullet:
```ts
import { panelStyles } from "@silverbulletmd/silverbullet/ui";
const html = (await panelStyles()) + `<div id="root"></div>`;
await editor.showPanel("modal", 100, html, script);
```
Pass options to opt out of either part: `panelStyles({ components: false })` skips the component stylesheet, `panelStyles({ spaceStyles: false })` skips the user's space styles. Append any plug-specific `<style>` after the helper's output so it takes precedence.
`components.css` contains only the theme tokens and component rules (no editor styles), so it is lightweight and safe to include in an iframe. The available classes are:
- **Buttons:** `sb-button`, `sb-button-primary`, `sb-button-danger`, `sb-button-icon`
- **Inputs:** `sb-input`, `sb-select`, `sb-checkbox`
- **Tabs:** `sb-tabs`, `sb-tab`, `sb-tab.sb-active`
- **Alerts:** `sb-alert`, `sb-alert-error`, `sb-alert-warning`, `sb-alert-info`
- **Misc:** `sb-badge`, `sb-progress`, `sb-progress-bar`
Preact-based panels can instead import the typed wrappers from `@silverbulletmd/silverbullet/ui`, which apply the same classes internally.