html
tcmts.vscode.pages_panel.html · #r556lyhn · term · runs anywhere
import type * as vscode from "vscode";
export function html(webview: vscode.Webview): string {
const nonce = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
return `<!doctype html>
<html><head><meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} https: data:; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
<style>
body { margin: 0; padding: 8px 16px 24px; font-family: var(--vscode-font-family); font-size: var(--vscode-font-size); color: var(--vscode-foreground); }
.toolbar { position: sticky; top: 0; z-index: 2; display: flex; justify-content: flex-end; padding: 4px 12px; background: var(--vscode-editor-background); border-bottom: 1px solid var(--vscode-panel-border); }
.empty { padding: 32px; opacity: .7; line-height: 1.6; }
.card { margin: 0 auto; max-width: 1100px; padding: 6px 4px 0; }
.card.focus { outline: 1px solid var(--vscode-focusBorder); }
.head { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--vscode-panel-border); flex-wrap: wrap; }
.head h2 { font-size: 1.15em; margin: 0; font-weight: 600; font-family: var(--vscode-editor-font-family); }
.badge { font-size: .8em; padding: 1px 7px; border-radius: 9px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); white-space: nowrap; }
.badge.server { background: var(--vscode-charts-purple); color: #fff; } .badge.browser { background: var(--vscode-charts-blue); color: #fff; }
.badge.passed { background: var(--vscode-testing-iconPassed); color: #fff; } .badge.failed { background: var(--vscode-testing-iconFailed); color: #fff; }
.hash { opacity: .55; font-family: var(--vscode-editor-font-family); }
.actions { margin-left: auto; display: flex; gap: 2px; }
.actions button { background: none; border: none; color: var(--vscode-textLink-foreground); cursor: pointer; padding: 2px 6px; font-size: .9em; }
.actions button:hover { text-decoration: underline; }
.section { padding: 8px 14px; }
.why { opacity: .75; font-size: .92em; }
.doc { padding: 4px 14px 2px; line-height: 1.55; }
.doc pre, .src { font-family: var(--vscode-editor-font-family); font-size: var(--vscode-editor-font-size); background: var(--vscode-textCodeBlock-background); padding: 8px 10px; border-radius: 4px; overflow-x: auto; }
.doc code { font-family: var(--vscode-editor-font-family); }
.doc a, a.ref, .list a { color: var(--vscode-textLink-foreground); cursor: pointer; text-decoration: none; }
.doc a:hover, a.ref:hover, .list a:hover { text-decoration: underline; }
.doc blockquote { margin: 6px 0; padding: 2px 10px; border-left: 3px solid var(--vscode-editorWarning-foreground); }
.doc table { border-collapse: collapse; } .doc th, .doc td { border: 1px solid var(--vscode-panel-border); padding: 3px 8px; }
.src { margin: 0 14px 10px; white-space: pre; }
::highlight(tcm-find) { background-color: var(--vscode-editor-findMatchBackground); color: inherit; }
.file-image { display: block; margin: 8px 14px 12px; max-width: calc(100% - 28px); max-height: 60vh; }
/* VS Code doesn't give webviews the theme's syntax colours, so these are its default themes' (Dark+, Light+,
High Contrast), picked by the theme's kind. Code in rendered docs (highlight.js classes) uses the same. */
body { --k: #569cd6; --x: #c586c0; --s: #ce9178; --n: #b5cea8; --c: #6a9955; --f: #dcdcaa; --t: #4ec9b0; --g: #569cd6; --a: #9cdcfe; }
body.vscode-light { --k: #0000ff; --x: #af00db; --s: #a31515; --n: #098658; --c: #008000; --f: #795e26; --t: #267f99; --g: #800000; --a: #e50000; }
body.vscode-high-contrast { --k: #569cd6; --x: #c586c0; --s: #ce9178; --n: #b5cea8; --c: #7ca668; --f: #dcdcaa; --t: #4ec9b0; --g: #569cd6; --a: #9cdcfe; }
body.vscode-high-contrast-light { --k: #0f4a85; --x: #b5200d; --s: #0f4a85; --n: #096d48; --c: #515151; --f: #5e2cbc; --t: #185e73; --g: #0f4a85; --a: #264f78; }
.k, .hljs-keyword, .hljs-built_in, .hljs-literal { color: var(--k); }
.x, .hljs-meta { color: var(--x); }
.s, .hljs-string, .hljs-regexp { color: var(--s); }
.n, .hljs-number { color: var(--n); }
.c, .hljs-comment { color: var(--c); font-style: italic; }
.f, .hljs-title.function_, .hljs-title { color: var(--f); }
a.ref.t, .hljs-type, .hljs-title.class_ { color: var(--t); }
/* JSX: tags (a component is a type) and attribute names */
.g, .hljs-name { color: var(--g); } .t { color: var(--t); } .a, .hljs-attr { color: var(--a); }
/* links in code keep the colour of what they are (a string, a call, a type, a name): only hovering says they're links */
.src a.ref { color: inherit; }
.src a.ref.s { color: var(--s); } .src a.ref.f { color: var(--f); } .src a.ref.t { color: var(--t); }
.src a.ref:hover { text-decoration: underline; text-decoration-color: var(--vscode-textLink-foreground); }
.lists { display: flex; gap: 28px; padding: 4px 14px 12px; flex-wrap: wrap; font-size: .92em; }
.list h4 { margin: 4px 0; font-size: .85em; text-transform: uppercase; opacity: .65; letter-spacing: .04em; }
.list ul { list-style: none; margin: 0; padding: 0; } .list li { margin: 2px 0; }
.sig { opacity: .6; font-family: var(--vscode-editor-font-family); }
.problem { color: var(--vscode-errorForeground); padding: 4px 14px; }
.group h3 { margin: 10px 14px 4px; font-size: .95em; opacity: .8; }
details.kind > summary { margin: 8px 14px 2px; font-size: .95em; font-weight: 600; cursor: pointer; }
details.kind > summary .muted { font-weight: normal; }
.tabs { display: flex; gap: 2px; margin: 12px 14px 6px; border-bottom: 1px solid var(--vscode-panel-border); }
.tab { font: inherit; background: none; border: none; border-bottom: 2px solid transparent; color: var(--vscode-foreground); opacity: .7; padding: 6px 12px; margin-bottom: -1px; cursor: pointer; }
.tab:hover { opacity: 1; }
.tab.on { opacity: 1; border-bottom-color: var(--vscode-focusBorder); font-weight: 600; }
.tab .muted { font-weight: normal; }
.group ul { list-style: none; margin: 0 14px 8px; padding: 0; }
.group li { margin: 3px 0; } .group .summary { opacity: .75; }
/* package (releases) pages */
.lede { margin: 10px 14px 4px; font-size: 1.05em; }
.facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 10px 14px 18px; font-size: .92em; }
.facts dt { opacity: .6; } .facts dd { margin: 0; }
code { font-family: var(--vscode-editor-font-family); font-size: .95em; background: var(--vscode-textCodeBlock-background); padding: 0 4px; border-radius: 3px; }
.muted { opacity: .65; }
.next { margin: 0 14px 24px; padding: 12px 16px 10px; border: 1px solid var(--vscode-panel-border); border-left: 4px solid var(--vscode-charts-blue); border-radius: 6px; background: var(--vscode-sideBar-background, transparent); }
.next.level-major { border-left-color: var(--vscode-charts-red); } .next.level-minor { border-left-color: var(--vscode-charts-yellow); }
.next.done { border-left-color: var(--vscode-charts-green); } .next.blocked { border-left-color: var(--vscode-charts-orange); }
.next-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.next-head h3 { margin: 0; font-size: 1.1em; }
.next-actions { margin-left: auto; display: flex; gap: 6px; }
button.primary, .next-actions button { font: inherit; font-size: .9em; padding: 3px 10px; border-radius: 3px; border: 1px solid var(--vscode-button-border, transparent); cursor: pointer;
background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); }
.next-actions button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); }
.next h4 { margin: 14px 0 4px; font-size: .78em; text-transform: uppercase; letter-spacing: .06em; }
.level-major { color: var(--vscode-charts-red); } .level-minor { color: var(--vscode-charts-yellow); } .level-patch { color: var(--vscode-charts-blue); }
ul.findings { list-style: none; margin: 0; padding: 0; }
ul.findings li { padding: 4px 0; border-top: 1px solid var(--vscode-panel-border); }
ul.findings li:first-child { border-top: none; }
ul.findings a { color: var(--vscode-textLink-foreground); cursor: pointer; font-family: var(--vscode-editor-font-family); }
ul.findings a:hover { text-decoration: underline; }
ul.findings .why { opacity: .65; font-size: .88em; margin-top: 1px; } .lint { font-family: var(--vscode-editor-font-family); }
details.preview { margin-top: 10px; } details.preview summary, .timeline summary { cursor: pointer; }
.section-title { margin: 0 14px 8px; font-size: 1.1em; }
ol.timeline { list-style: none; margin: 0 14px; padding: 0 0 0 16px; border-left: 2px solid var(--vscode-panel-border); }
ol.timeline > li { position: relative; margin: 0 0 14px; }
ol.timeline > li::before { content: ""; position: absolute; left: -22px; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--vscode-charts-blue); }
.timeline summary { display: flex; align-items: baseline; gap: 10px; }
.timeline .version { font-weight: 600; font-size: 1.05em; font-family: var(--vscode-editor-font-family); }
.notes { padding: 4px 0 0; }
.notes h3 { font-size: .78em; text-transform: uppercase; letter-spacing: .06em; opacity: .7; margin: 12px 0 2px; }
.notes ul { margin: 2px 0 6px; padding-left: 18px; } .notes p { margin: 4px 0 8px; }
</style></head>
<body>
<div id="cards"></div>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
const RUNS = { server: "on the server", browser: "in the browser", anywhere: "anywhere" };
function link(item, from) {
return '<a data-hash="' + item.hash + '" data-from="' + from + '">' + esc(item.name) + '</a> <span class="sig">' + esc(item.kind === "type" ? "type" : item.signature ?? "") + "</span>";
}
function defCard(c) {
const p = c.page;
const badges = [
'<span class="badge">' + (p.kind === "file" ? esc(p.format || "file") + " file" : p.kind === "type" ? "type" : p.kind === "class" ? "class" : "term") + "</span>",
p.place && p.place !== "anywhere" ? '<span class="badge ' + p.place + '">' + p.place + "</span>" : "",
p.endpoint ? '<span class="badge">' + (p.streaming ? "streaming endpoint" : "endpoint") + "</span>" : "",
p.test ? '<span class="badge ' + p.test + '">test ' + p.test + "</span>" : "",
].join("");
// (an app starts, with tcm dev, rather than running as a program)
const actions = (p.kind === "file" ? ["edit", "rename", "copy"] : ["source", "edit", "usages", p.app ? "start" : p.kind === "term" ? "run" : "", p.kind === "term" ? "debug" : "", "rename", "copy"]).filter(Boolean)
.map((a) => '<button data-action="' + a + '">' + a[0].toUpperCase() + a.slice(1) + "</button>").join("");
let h = '<div class="head"><h2>' + esc(p.names[0] ?? p.short) + '</h2><span class="hash">' + esc(p.short) + "</span>" + badges +
'<div class="actions">' + actions + "</div></div>";
if (p.names.length > 1) h += '<div class="section why">also named ' + p.names.slice(1).map(esc).join(", ") + "</div>";
if (p.place) h += '<div class="section why">Runs ' + RUNS[p.place] + (p.why ? ": it " + esc(p.why) : "") + "." + (p.endpoint ? " The browser calls it by hash." : "") + "</div>";
if (c.docHtml) h += '<div class="doc">' + c.docHtml + "</div>";
for (const prob of p.docProblems) h += '<div class="problem">' + esc(prob) + "</div>";
// (a file: its image, or its text; a Markdown file is its doc, above)
if (p.image) h += '<img class="file-image" src="' + esc(p.image) + '">';
if (p.sourceHtml) h += '<pre class="src">' + p.sourceHtml + "</pre>";
const lists = [];
if (p.uses.length) lists.push('<div class="list"><h4>Uses</h4><ul>' + p.uses.map((u) => "<li>" + link(u, c.key) + "</li>").join("") + "</ul></div>");
if (p.usedBy.length) lists.push('<div class="list"><h4>Used by</h4><ul>' + p.usedBy.map((u) => "<li>" + link(u, c.key) + "</li>").join("") + "</ul></div>");
// react@19 opens the package page; builtins and #tcm/host are just named
const pkgLink = (e) => { const m = /^((?:@[^/@]+\\/)?[^@/]+)@([^/#]+)/.exec(e); return m ? '<a data-package-name="' + esc(m[1]) + '" data-package-range="' + esc(m[2]) + '">' + esc(e) + "</a>" : esc(e); };
if (p.externals.length) lists.push('<div class="list"><h4>Packages</h4><ul>' + p.externals.map((e) => "<li>" + pkgLink(e) + "</li>").join("") + "</ul></div>");
if (lists.length) h += '<div class="lists">' + lists.join("") + "</div>";
return h;
}
const ROLES = [["app", "Apps"], ["type", "Types"], ["table", "Data"], ["schedule", "Jobs"], ["server", "Server"], ["browser", "Browser"], ["shared", "Shared"], ["test", "Tests"], ["file", "Files"]];
function packageCard(c) {
const p = c.page;
let h = '<div class="head"><h2>' + esc(p.name) + '</h2><span class="badge">range ' + esc(p.range) + '</span><span class="hash">' + esc(p.version) + "</span>" +
(p.license ? '<span class="badge">' + esc(p.license) + "</span>" : "") +
'<div class="actions"><button data-action="upgradePackage">Upgrade</button><button data-action="upgradePackageTo">Move to another range</button>' +
(p.homepage ? '<button data-url="' + esc(p.homepage) + '">Homepage</button>' : "") + '<button data-url="https://www.npmjs.com/package/' + esc(p.name) + '/v/' + esc(p.version) + '">npm</button></div></div>';
if (p.description) h += '<div class="section why">' + esc(p.description) + "</div>";
const facts = [p.types ? "types from @types (" + esc(p.types) + ")" : "", p.integrity ? "tarball " + esc(p.integrity.slice(0, 24)) + "…" : "",
p.otherRanges.length ? "also runs " + p.otherRanges.map((r) => '<a data-package-name="' + esc(p.name) + '" data-package-range="' + esc(r) + '">' + esc(r) + "</a>").join(", ") : ""].filter(Boolean);
if (facts.length) h += '<div class="section why">' + facts.join(" · ") + "</div>";
// the rest in tabs: README, API, what uses it
const tabs = [];
if (c.readmeHtml) tabs.push(["readme", "README", '<div class="doc">' + c.readmeHtml + "</div>"]);
if (p.api.length) {
let api = "";
const entry = (a, i) => '<div class="section"><b class="f">' + esc(a.name) + '</b> <span class="badge">' + esc(a.kind) + "</span>" +
(a.signature ? '<pre class="src">' + (a.html || esc(a.signature)) + "</pre>" : "") + (c.docs[i] ? '<div class="doc">' + c.docs[i] + "</div>" : "") + "</div>";
// by kind, each section folding
const known = API_KINDS.map(([k]) => k);
for (const [kind, label] of [...API_KINDS, ["", "Other"]]) {
const items = p.api.map((a, i) => [a, i]).filter(([a]) => (kind ? a.kind === kind : !known.includes(a.kind)));
if (!items.length) continue;
api += '<details class="kind" open><summary>' + label + ' <span class="muted">' + items.length + "</span></summary>" + items.map(([a, i]) => entry(a, i)).join("") + "</details>";
}
tabs.push(["api", 'API <span class="muted">' + p.api.length + "</span>", api]);
}
if (p.users.length) tabs.push(["users", 'Used by <span class="muted">' + p.users.length + "</span>", '<div class="lists"><div class="list"><ul>' + p.users.map((u) => "<li>" + link({ hash: u.hash, name: u.path, kind: u.kind, signature: u.signature }, c.key) + "</li>").join("") + "</ul></div></div>"]);
if (!tabs.length) return h;
// the tab last chosen stays chosen when the page is drawn again
const current = tabs.some(([id]) => id === packageTab) ? packageTab : tabs[0][0];
h += '<div class="tabs">' + tabs.map(([id, label]) => '<button class="tab' + (id === current ? " on" : "") + '" data-tab="' + id + '">' + label + "</button>").join("") + "</div>";
h += tabs.map(([id, , body]) => '<div class="tabpanel" data-panel="' + id + '"' + (id === current ? "" : " hidden") + ">" + body + "</div>").join("");
return h;
}
/** The package page tab last chosen. */
let packageTab;
/** A package's exports, by kind, in the order its page shows them. */
const API_KINDS = [["function", "Functions"], ["class", "Classes"], ["interface", "Interfaces"], ["type", "Types"], ["enum", "Enums"], ["value", "Values"], ["namespace", "Namespaces"]];
const LEVELS = [["major", "Breaking", "level-major"], ["minor", "Additions", "level-minor"], ["patch", "Same API", "level-patch"]];
const day = (ts) => new Date(ts).toISOString().slice(0, 10);
function releaseCard(c) {
const p = c.page;
const n = p.next;
const latest = p.releases[0];
const sub = (s) => (s === "." ? p.name : p.name + s.slice(1));
let h = '<div class="head"><h2>' + esc(p.name) + '</h2><span class="badge">' + esc(latest ? latest.version : "unreleased") + "</span>" +
'<div class="actions"><button data-action="packPackage">Pack</button>' + (p.releases.length ? '<button data-action="saveChangelog">Save changelog</button>' : "") +
'<button data-hash="' + esc(p.manifestHash) + '">Manifest</button></div></div>';
if (p.description) h += '<p class="lede">' + esc(p.description) + "</p>";
const facts = [
["Namespace", "<code>" + esc(p.namespace || "(the codebase)") + "</code>"],
["Exports", p.exports.map((e) => "<code>" + esc(sub(e.subpath)) + "</code> " + e.count + " definition" + (e.count === 1 ? "" : "s")).join(", ")],
p.bin.length ? ["Commands", p.bin.map((b) => "<code>" + esc(b.command) + "</code> runs " + esc(b.target)).join(", ")] : null,
p.with.length ? ["Also needs", p.with.map((w) => "<code>" + esc(w) + "</code>").join(", ")] : null,
["Releases", p.releases.length ? p.releases.length + ", latest " + esc(latest.version) + " on " + day(latest.ts) : "none yet"],
].filter(Boolean);
h += '<dl class="facts">' + facts.map(([k, v]) => "<dt>" + k + "</dt><dd>" + v + "</dd>").join("") + "</dl>";
// the next release: what it would be, and why
const tone = n.blocked ? "blocked" : n.upToDate ? "done" : n.level ? "level-" + n.level : "level-patch";
h += '<section class="next ' + tone + '"><div class="next-head">';
if (n.upToDate) h += '<h3>Up to date</h3><span class="muted">nothing new since ' + esc(n.after) + "</span>";
else h += "<h3>Next release: " + esc(n.version) + '</h3><span class="muted">' + (n.after ? "after " + esc(n.after) + " · " : "") + esc(n.reason) + "</span>";
if (!n.upToDate) h += '<div class="next-actions">' + (n.blocked ? "" : '<button class="primary" data-action="release">Release ' + esc(n.version) + "</button>") + '<button data-action="draftRelease">Write notes</button></div>';
h += "</div>";
if (n.blocked) h += '<p class="problem">' + esc(n.blocked) + "</p>";
for (const [level, label, cls] of LEVELS) {
const fs = n.findings.filter((f) => f.level === level);
if (!fs.length) continue;
h += '<h4 class="' + cls + '">' + label + '</h4><ul class="findings">' + fs.map((f) =>
'<li><div>' + (f.hash ? '<a data-hash="' + esc(f.hash) + '">' + esc(f.path) + "</a>" : "<code>" + esc(f.path) + "</code>") + " " + esc(f.summary) +
(f.detail ? ": <code>" + esc(f.detail) + "</code>" : "") + '</div><div class="why">' + esc(f.why) + ' · <span class="lint">' + esc(f.lint) + "</span></div></li>").join("") + "</ul>";
}
if (n.following.length) h += '<p class="muted">' + n.following.length + " definition(s) only follow these changes.</p>";
if (!n.upToDate && c.nextHtml) h += '<details class="preview"><summary>Preview its notes</summary><div class="doc notes">' + c.nextHtml + "</div></details>";
h += "</section>";
// the changelog
h += '<h3 class="section-title">Releases</h3>';
if (!p.releases.length) h += '<p class="muted">None yet.</p>';
h += '<ol class="timeline">' + p.releases.map((r, i) =>
'<li><details' + (i < 3 ? " open" : "") + '><summary><span class="version">' + esc(r.version) + '</span><span class="muted">' + day(r.ts) + " · " + esc(r.author) + " · " + esc(r.causal) + "</span></summary>" +
'<div class="doc notes">' + c.notesHtml[i] + "</div></details></li>").join("") + "</ol>";
return h;
}
function namespaceCard(c) {
const p = c.page;
let h = '<div class="head"><h2>' + esc(p.namespace || "(root)") + '</h2><span class="badge">namespace</span></div>';
if (c.readmeHtml) h += '<div class="doc">' + c.readmeHtml + "</div>";
if (p.namespaces.length) h += '<div class="group"><h3>Namespaces</h3><ul>' + p.namespaces.map((n) => '<li><a data-namespace="' + esc(n.path) + '" data-from="' + c.key + '">' + esc(n.name) + "</a> <span class=\\"sig\\">" + n.count + "</span></li>").join("") + "</ul></div>";
for (const [role, label] of ROLES) {
const defs = p.definitions.filter((d) => d.role === role);
if (!defs.length) continue;
h += '<div class="group"><h3>' + label + "</h3><ul>" + defs.map((d) => "<li>" + link(d, c.key) + (d.summary ? '<div class="summary">' + esc(d.summary) + "</div>" : "") + "</li>").join("") + "</ul></div>";
}
return h;
}
// the text from offset start to end in an element, as a range (across the spans highlighting makes)
const rangeIn = (el, start, end) => {
const range = document.createRange();
const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
let at = 0;
for (let n = walk.nextNode(); n; n = walk.nextNode()) {
const len = n.textContent.length;
if (at <= start && start <= at + len) range.setStart(n, start - at);
if (at <= end && end <= at + len) { range.setEnd(n, end - at); break; }
at += len;
}
return range;
};
// a search's match: in the line it was found on (in the code, or the doc), else wherever the text first is
const find = (f) => {
const scopes = [...document.querySelectorAll(".src, .doc")];
const fold = (s) => (f.caseSensitive ? s : s.toLowerCase());
let hit;
const line = f.line.replace(/^\\*\\s?/, "");
const offset = f.offset - (f.line.length - line.length);
for (const el of scopes) {
const i = line.trim() ? el.textContent.indexOf(line) : -1;
if (i >= 0 && offset >= 0) { hit = [el, i + offset]; break; }
}
for (const el of hit ? [] : scopes) {
const i = fold(el.textContent).indexOf(fold(f.text));
if (i >= 0) { hit = [el, i]; break; }
}
if (!hit || !window.CSS?.highlights) return;
const range = rangeIn(hit[0], hit[1], hit[1] + f.text.length);
CSS.highlights.set("tcm-find", new Highlight(range));
const box = range.getBoundingClientRect();
window.scrollBy({ top: box.top - window.innerHeight / 3 });
// (and across, in a wide line)
const pre = hit[0].closest(".src");
if (pre) pre.scrollLeft = Math.max(0, box.left - pre.getBoundingClientRect().left + pre.scrollLeft - 80);
};
window.addEventListener("message", (e) => {
const m = e.data;
if (m.type === "find") return find(m);
if (m.type !== "page") return;
const c = m.card;
const root = document.getElementById("cards");
root.innerHTML = '<div class="card">' + (c.error ? '<div class="problem">' + esc(c.error) + "</div>" : c.kind === "def" ? defCard(c) : c.kind === "package" ? packageCard(c) : c.kind === "release" ? releaseCard(c) : namespaceCard(c)) + "</div>";
root.firstChild.addEventListener("click", (ev) => {
const a = ev.target.closest("a, button");
if (!a) return;
// a tab: its panel shows, the others hide
if (a.dataset.tab) {
packageTab = a.dataset.tab;
for (const t of root.querySelectorAll(".tab")) t.classList.toggle("on", t === a);
for (const panel of root.querySelectorAll(".tabpanel")) panel.hidden = panel.dataset.panel !== packageTab;
return;
}
if (a.dataset.hash) return vscode.postMessage({ type: "open", hash: a.dataset.hash });
if (a.dataset.namespace !== undefined) return vscode.postMessage({ type: "open", namespace: a.dataset.namespace });
if (a.dataset.packageName) return vscode.postMessage({ type: "open", package: { name: a.dataset.packageName, range: a.dataset.packageRange } });
if (a.dataset.url) return vscode.postMessage({ type: "external", url: a.dataset.url });
const href = a.getAttribute("href") || "";
if (href.startsWith("tcm-page:")) { ev.preventDefault(); return vscode.postMessage({ type: "open", hash: href.slice(9) }); }
if (a.dataset.action) return vscode.postMessage({ type: "action", action: a.dataset.action, page: c.page });
});
});
</script></body></html>`;
}
used by