Page block: links to sub-pages
A one-line link to a sub-page, like a sub-page in Notion. It shows the page's icon and title. Each page is its own document, and your app stores it. The block saves only the page id and a display copy of the title and icon.
Blok never loads or saves a page. Your app connects pages through the four config members below.
The block has three special states:
- New page: the page has no title, so the block shows New page instead. It is still a link.
- Page not found:
resolvereturnednull. - No access:
resolvereturned{ access: 'none' }. The cached title is hidden.
A page that is not found or not accessible is never removed automatically. The block stays, but it stops working as a link.
A plain click calls open. So does Enter when the block is selected from the keyboard. Without open, the link follows href.
Cmd/Ctrl-click, Shift-click and middle click keep their browser meaning, such as opening a new tab. New-tab clicks need href. The link never takes focus, so undo and Escape still work after a click.
page is not in defaultBlockTools. Register it yourself, with a config.
For read-only HTML, call blocksToHtml(data, { pageHref }) from @bloklabs/core/view. It renders the block as an icon and a title, and it is a link only when you pass pageHref. The view reads only the saved cache, so it cannot show the not-found or no-access states. It never renders the page's body.
Import
import { Page } from '@bloklabs/core/tools';Configuration
| Option | Type | Default | Description |
|---|---|---|---|
href | (pageId: string) => string | undefined | Builds the page's URL from its id. Blok puts it on the link, so Cmd/Ctrl-click or middle click opens the page in a new tab. Without it the link has no URL. Unsafe schemes such as |
open | (pageId: string, ctx: { event?: MouseEvent | KeyboardEvent }) => void | undefined | Opens the page. Blok calls it on a plain left click. Without it, a click follows Enter also opens the page when the block is selected from the keyboard (Escape, then the arrow keys). Then Blok also calls it once right after a user inserts a new page from the toolbox. Then |
resolve | (pageId: string) => PageInfo | null | undefined | Promise<…> | undefined | Returns the page's current title and icon. Blok asks once, when the block renders. It may return a value or a promise:
Return the full picture. A missing |
create | (init: { pageId: string }) => void | Promise<void> | undefined | Makes the page in your app. Blok calls it once with the id it minted, when a page block is inserted without a It is never called on load, paste, undo, redo or a collaborator's change, or in read-only mode. If it throws, the block stays with its id and shows "Page not found". Blok still asks |
Save Data
interface PageData {
pageId: string; // Id of the page. Blok mints one for a new page.
cache?: { // Display copy, refreshed from resolve(). It can be stale.
title?: string;
icon?: { type: 'emoji'; value: string } | { type: 'image'; url: string };
};
}
// The page's body is NOT here. It is a separate document your app stores.{
"id": "pg001",
"type": "page",
"data": {
"pageId": "p1",
"cache": {
"title": "Roadmap",
"icon": { "type": "emoji", "value": "🗺" }
}
}
}Usage Example
import { Blok } from '@bloklabs/core';
import { Page } from '@bloklabs/core/tools';
import { blocksToHtml } from '@bloklabs/core/view';
const pageUrl = (pageId) => `/pages/${pageId}`;
const editor = new Blok({
holder: 'editor',
tools: {
// Not in defaultBlockTools: register it with your own config.
page: {
class: Page,
config: {
href: pageUrl,
open: (pageId) => router.push(pageUrl(pageId)),
resolve: (pageId) => myApi.getPageInfo(pageId), // { title, icon } | null
create: ({ pageId }) => myApi.createPage(pageId),
},
},
},
});
// Read-only HTML: pass the same URL builder.
const html = blocksToHtml(savedData, { pageHref: pageUrl });