Pages
tcmts.vscode.pages_panel.Pages · #a3rm3qg5 · class
import { html, markdown } from "#codebase/tcmts/vscode/pages_panel";
import type { PageActions, PageFind, PageMessage, PageTarget } from "#codebase/tcmts/vscode/pages_panel";
import type { Request } from "#codebase/tcmts/vscode/releases";
import * as vscode from "vscode";
export class Pages {
request: Request;
actions: PageActions;
icon: vscode.Uri;
panels = new Map<string, { panel: vscode.WebviewPanel; target: PageTarget }>();
/**
* `request` sends a request to the language server; `actions` run editor commands for a page's buttons;
* `icon` is the tab icon.
*/
constructor(request: Request, actions: PageActions, icon: vscode.Uri) {
this.request = request;
this.actions = actions;
this.icon = icon;
}
/** Open a page (focusing it if it's open), showing `find` in it if given: the page scrolls to it, marked. */
async open(target: PageTarget, find?: PageFind): Promise<void> {
const key = target.release ? `rel:${target.release.name}` : target.package ? `pkg:${target.package.name}@${target.package.range}` : target.hash ? `def:${target.hash}` : `ns:${target.namespace}`;
const open = this.panels.get(key);
if (open) {
open.panel.reveal();
if (find) open.panel.webview.postMessage({ type: "find", ...find });
return;
}
const panel = vscode.window.createWebviewPanel("tcm.page", target.title ?? "…", vscode.ViewColumn.Active, { enableScripts: true, retainContextWhenHidden: true, enableCommandUris: false });
panel.iconPath = this.icon;
this.panels.set(key, { panel, target });
panel.onDidDispose(() => this.panels.delete(key));
panel.webview.onDidReceiveMessage((m: PageMessage) => {
if (m.type === "open") return this.open(m.package ? { package: m.package } : m.hash ? { hash: m.hash } : { namespace: m.namespace });
if (m.type === "action") return this.actions[m.action!]?.(m.page);
if (m.type === "external") return vscode.env.openExternal(vscode.Uri.parse(m.url!));
});
panel.webview.html = html(panel.webview);
await this.render(key);
if (find) panel.webview.postMessage({ type: "find", ...find });
}
/** Fetch a page (cheap: examples are cached by hash) and draw it; the tab is named after it. */
async render(key: string): Promise<void> {
const open = this.panels.get(key);
if (!open) return;
const { panel, target } = open;
try {
if (target.release) {
const page = await this.request("tcm/releasePage", { name: target.release.name });
panel.title = `${page.name} (releases)`;
const [nextHtml, ...notesHtml] = await Promise.all([markdown(page.next.notes), ...(page.releases as { body: string }[]).map((r) => markdown(r.body))]);
panel.webview.postMessage({ type: "page", card: { kind: "release", page, nextHtml, notesHtml } });
} else if (target.package) {
const page = await this.request("tcm/packagePage", target.package);
panel.title = `${page.name} ${page.range} (package)`;
const docs = await Promise.all((page.api as { doc?: string }[]).map((a) => (a.doc ? markdown(a.doc) : "")));
panel.webview.postMessage({ type: "page", card: { kind: "package", page, readmeHtml: page.readme ? await markdown(page.readme) : "", docs } });
} else if (target.hash) {
const page = await this.request("tcm/page", { hash: target.hash });
const name: string = page.names[0] ?? page.short;
panel.title = name.slice(name.lastIndexOf(".") + 1);
panel.webview.postMessage({ type: "page", card: { kind: "def", page, docHtml: page.docMarkdown ? await markdown(page.docMarkdown) : "" } });
} else {
const page = await this.request("tcm/namespace", { namespace: target.namespace });
panel.title = `${target.namespace || "(root)"} (namespace)`;
panel.webview.postMessage({ type: "page", card: { kind: "namespace", page, readmeHtml: page.readme ? await markdown(page.readme) : "" } });
}
} catch (e) {
panel.webview.postMessage({ type: "page", card: { error: (e as Error).message } });
}
}
refresh(): void {
for (const key of this.panels.keys()) this.render(key);
}
}
uses
used by