Appearance
Detail Views
The detail panel (the right-hand panel with Outline, History and References) can host extension tabs. A view is one of three kinds:
| Kind | Manifest | Tier |
|---|---|---|
| Rule-based | query | L0 |
| Page | entry | L1 |
Fed by main | "provider": "main" | L2 |
Common fields:
| Field | Required | Description |
|---|---|---|
id | yes | Local id of the view. |
title | yes | Tab title. |
icon | Icon name for the tab: list-checks, hash, network, tags, chart, calendar, terminal, image, image-play or puzzle (the default). | |
sfSymbol | SF Symbol name for the view's item in the macOS menus (View and the tab strip's right-click menu), e.g. checklist. Defaults to info.circle.text.page. |
Rule-based views
A rule-based view lists blocks or inline elements that match a query. Typora re-checks only the blocks an edit touched, so these views stay fast on long documents. Clicking an item selects it in the editor.
json
"detailViews": [
{
"id": "open-tasks",
"title": "Open Tasks",
"icon": "list-checks",
"sfSymbol": "checklist",
"query": { "block": ["list_item"], "where": { "checked": false } },
"item": { "label": "{text}", "groupBy": "heading" }
},
{
"id": "todo",
"title": "TODO",
"icon": "hash",
"query": { "block": ["paragraph", "heading"], "text": "\\b(TODO|FIXME|XXX)\\b[::]?\\s*(.*)" },
"item": { "label": "{$1}: {$2}", "groupBy": "heading" }
},
{
"id": "links",
"title": "Links",
"icon": "network",
"query": { "inline": ["link", "autolink", "url", "wikilink"] },
"item": { "label": "{text}" }
}
]query
| Field | Description |
|---|---|
block | Block types to match (list below). |
inline | Inline types to match inside every block (list below). Each match is its own item. |
where | Block attributes that must be equal, e.g. { "level": 2 } for headings, { "checked": false } for open tasks, { "lang": "python" } for code fences. |
text | Regular expression the block's (or inline's) source must match. Capture groups are available to the label. |
flags | Extra regex flags: i, m, s (u is always on). |
Block types: paragraph, heading, blockquote, custom_container, fences, hr, page_break, def_link, def_footnote, table, meta_block, math_block, html_block, list, toc, list_item, table_row, table_cell.
Inline types (common ones): strong, em, code, del, underline, highlight, link, reflink, autolink, url, wikilink, wikiembed, image, refimg, footnote, citation, emoji, inline_math, superscript, subscript, html_inline.
item
| Field | Description |
|---|---|
label | Template for the item text. Placeholders: {text} (first line of the block, heading marks removed, or the inline's text), {match} (the whole regex match), {$1}…{$9} (capture groups), {level}, {lang}, {type}. Default {text}. |
groupBy | "heading" groups items under the heading they appear in. |
Page views
A page view is an HTML page shown in the tab. With the document permission it receives the document's blocks, and again after every edit (debounced), and can select ranges in the editor.
json
{
"permissions": { "document": "read" },
"contributes": {
"detailViews": [{ "id": "mindmap", "title": "Mind Map", "icon": "network", "entry": "views/mindmap.html" }]
}
}js
const view = window.typora.view;
view.onDocument(({ blocks }) => {
// blocks: [{ id, type, text, attrs, depth, parent }] in document order
const headings = blocks.filter((b) => b.type === "heading");
draw(headings, (heading) => view.reveal({ blockId: heading.id }));
});With document: "write" the page can also insert at the caret with view.insertMarkdown(md) — the Openverse Images sample inserts the image you click. Without the document permission the page gets no document and view.reveal is refused. A page can still use settings, storage and (with network) view.fetch — for example the Writing Stats sample shows numbers its background script stores. See the View SDK.
Views fed by main
For lists that need logic, declare "provider": "main" and register a checker in main. A checker is a pure function of one block; Typora sends only the blocks an edit touched to the Worker and keeps the results for the rest.
json
{
"contributes": {
"detailViews": [{ "id": "long-paragraphs", "title": "Long Paragraphs", "provider": "main" }]
},
"main": "main.js"
}js
export function activate(ctx) {
ctx.subscriptions.push(
typora.iteration.register("long-paragraphs", (block) => {
if (block.type !== "paragraph") return [];
const words = block.text.split(/\s+/).filter(Boolean).length;
return words > 150 ? [{ label: `${words} words: ${block.text.slice(0, 40)}…`, group: "Too long" }] : [];
}),
);
}The checker receives { id, type, text, attrs, depth, parent } and returns a list of items:
| Field | Description |
|---|---|
label | Item text. |
from, to | Optional offsets into the block's text to select on click. Omit to select the whole block. |
group | Optional group heading. |
main is started the first time the view is shown (onDetailView:<id>).