universal-ast-mapper 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +261 -12
- package/dist/ai-refactor.js +185 -0
- package/dist/ai-testgen.js +105 -0
- package/dist/analysis.js +134 -0
- package/dist/arch-rules.js +82 -0
- package/dist/callgraph.js +467 -0
- package/dist/check.js +112 -0
- package/dist/cli.js +2284 -0
- package/dist/complexity.js +98 -0
- package/dist/config.js +53 -0
- package/dist/contextpack.js +79 -0
- package/dist/coupling.js +35 -0
- package/dist/covmerge.js +176 -0
- package/dist/crosslang.js +425 -0
- package/dist/dashboard.js +259 -0
- package/dist/diagram.js +264 -0
- package/dist/diskcache.js +97 -0
- package/dist/docgen.js +156 -0
- package/dist/embeddings.js +136 -0
- package/dist/explain.js +123 -0
- package/dist/explorer.js +123 -0
- package/dist/extractors/c.js +204 -0
- package/dist/extractors/common.js +56 -0
- package/dist/extractors/cpp.js +272 -0
- package/dist/extractors/csharp.js +209 -0
- package/dist/extractors/go.js +212 -0
- package/dist/extractors/java.js +152 -0
- package/dist/extractors/kotlin.js +159 -0
- package/dist/extractors/php.js +208 -0
- package/dist/extractors/python.js +153 -0
- package/dist/extractors/ruby.js +146 -0
- package/dist/extractors/rust.js +249 -0
- package/dist/extractors/swift.js +192 -0
- package/dist/extractors/typescript.js +577 -0
- package/dist/fix.js +92 -0
- package/dist/gitdiff.js +178 -0
- package/dist/graph-analysis.js +279 -0
- package/dist/graph.js +165 -0
- package/dist/history.js +36 -0
- package/dist/html.js +658 -0
- package/dist/incremental.js +122 -0
- package/dist/index.js +1945 -0
- package/dist/indexstore.js +105 -0
- package/dist/layers.js +36 -0
- package/dist/lsp.js +238 -0
- package/dist/modulecoupling.js +0 -0
- package/dist/parser.js +84 -0
- package/dist/patch.js +199 -0
- package/dist/plugins.js +88 -0
- package/dist/pool.js +114 -0
- package/dist/prompts.js +67 -0
- package/dist/registry.js +87 -0
- package/dist/report.js +441 -0
- package/dist/resolver.js +222 -0
- package/dist/roots.js +47 -0
- package/dist/search.js +68 -0
- package/dist/security.js +178 -0
- package/dist/semantic.js +365 -0
- package/dist/serve.js +185 -0
- package/dist/sfc.js +27 -0
- package/dist/similar.js +98 -0
- package/dist/skeleton.js +132 -0
- package/dist/smells.js +285 -0
- package/dist/sourcemap.js +60 -0
- package/dist/testgen.js +280 -0
- package/dist/testmap.js +167 -0
- package/dist/tsconfig.js +212 -0
- package/dist/typeflow.js +124 -0
- package/dist/types.js +5 -0
- package/dist/unused-params.js +127 -0
- package/dist/webapp.js +341 -0
- package/dist/worker.js +27 -0
- package/dist/workspace.js +330 -0
- package/package.json +2 -1
package/dist/html.js
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
const KIND_COLORS = {
|
|
2
|
+
class: "#7c3aed",
|
|
3
|
+
interface: "#0ea5e9",
|
|
4
|
+
struct: "#0d9488",
|
|
5
|
+
function: "#2563eb",
|
|
6
|
+
method: "#4f46e5",
|
|
7
|
+
type: "#db2777",
|
|
8
|
+
enum: "#ea580c",
|
|
9
|
+
const: "#65a30d",
|
|
10
|
+
var: "#ca8a04",
|
|
11
|
+
field: "#64748b",
|
|
12
|
+
namespace: "#9333ea",
|
|
13
|
+
};
|
|
14
|
+
const LANG_COLOR = {
|
|
15
|
+
typescript: "#3178c6", javascript: "#f7df1e", python: "#3572a5",
|
|
16
|
+
go: "#00acd7", rust: "#dea584", java: "#b07219", "c++": "#f34b7d",
|
|
17
|
+
c: "#555555", csharp: "#239120", kotlin: "#a97bff", swift: "#f05138",
|
|
18
|
+
tsx: "#3178c6", jsx: "#f7df1e",
|
|
19
|
+
};
|
|
20
|
+
function esc(s) {
|
|
21
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
22
|
+
}
|
|
23
|
+
function badge(kind) {
|
|
24
|
+
const color = KIND_COLORS[kind] ?? "#64748b";
|
|
25
|
+
return `<span class="badge" style="background:${color}1a;color:${color};border:1px solid ${color}44;" data-kind="${kind}">${kind}</span>`;
|
|
26
|
+
}
|
|
27
|
+
function langDot(lang) {
|
|
28
|
+
const color = LANG_COLOR[lang] ?? "#94a3b8";
|
|
29
|
+
return `<span class="lang-dot" style="background:${color}" title="${esc(lang)}"></span>`;
|
|
30
|
+
}
|
|
31
|
+
function renderSymbol(sym, depth = 0) {
|
|
32
|
+
const vis = sym.visibility === "private"
|
|
33
|
+
? `<span class="vis priv" title="private">pvt</span>` : "";
|
|
34
|
+
const exported = sym.exported
|
|
35
|
+
? `<span class="vis exp" title="exported">exp</span>` : "";
|
|
36
|
+
const sig = sym.signature
|
|
37
|
+
? `<code class="sig" title="${esc(sym.signature)}">${esc(sym.signature)}</code>` : "";
|
|
38
|
+
const lines = `<span class="lines">L${sym.range.startLine}–${sym.range.endLine}</span>`;
|
|
39
|
+
const doc = sym.doc ? `<div class="doc">${esc(sym.doc)}</div>` : "";
|
|
40
|
+
const copyBtn = `<button class="copy-btn" title="Copy name" onclick="event.stopPropagation();copyText('${esc(sym.name)}',this)">⎘</button>`;
|
|
41
|
+
const head = `${badge(sym.kind)}<span class="name">${esc(sym.name)}</span>${exported}${vis}${sig}${lines}${copyBtn}`;
|
|
42
|
+
if (sym.children.length > 0) {
|
|
43
|
+
const kids = sym.children.map((c) => renderSymbol(c, depth + 1)).join("");
|
|
44
|
+
return `<details open class="node" data-kind="${sym.kind}"><summary>${head}</summary>${doc}<div class="children">${kids}</div></details>`;
|
|
45
|
+
}
|
|
46
|
+
return `<div class="node leaf" data-kind="${sym.kind}">${head}${doc}</div>`;
|
|
47
|
+
}
|
|
48
|
+
function collectAllKinds(symbols) {
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
const walk = (syms) => {
|
|
51
|
+
for (const s of syms) {
|
|
52
|
+
counts.set(s.kind, (counts.get(s.kind) ?? 0) + 1);
|
|
53
|
+
if (s.children.length)
|
|
54
|
+
walk(s.children);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
walk(symbols);
|
|
58
|
+
return counts;
|
|
59
|
+
}
|
|
60
|
+
function collectSymbolNames(symbols) {
|
|
61
|
+
const names = [];
|
|
62
|
+
for (const sym of symbols) {
|
|
63
|
+
names.push(sym.name);
|
|
64
|
+
if (sym.children.length > 0)
|
|
65
|
+
names.push(...collectSymbolNames(sym.children));
|
|
66
|
+
}
|
|
67
|
+
return names;
|
|
68
|
+
}
|
|
69
|
+
function renderFileSection(skel, index) {
|
|
70
|
+
const body = skel.symbols.length > 0
|
|
71
|
+
? skel.symbols.map((s) => renderSymbol(s)).join("")
|
|
72
|
+
: `<p class="empty">No top-level symbols found.</p>`;
|
|
73
|
+
const lc = LANG_COLOR[skel.language] ?? "#94a3b8";
|
|
74
|
+
return `<section id="file-${index}" class="file-section">
|
|
75
|
+
<div class="file-header" onclick="toggleSection(${index})">
|
|
76
|
+
<span class="toggle-icon" id="tog-${index}">▾</span>
|
|
77
|
+
<span class="fs-path">${esc(skel.file)}</span>
|
|
78
|
+
<span class="fs-lang" style="background:${lc}22;color:${lc};border:1px solid ${lc}44">${esc(skel.language)}</span>
|
|
79
|
+
<span class="fs-count">${skel.symbolCount} symbols</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="fs-body" id="fsbody-${index}"><div class="tree">${body}</div></div>
|
|
82
|
+
</section>`;
|
|
83
|
+
}
|
|
84
|
+
export function renderCombinedHtml(skeletons) {
|
|
85
|
+
const sections = skeletons.map((s, i) => renderFileSection(s, i)).join("\n");
|
|
86
|
+
const totalSymbols = skeletons.reduce((n, s) => n + s.symbolCount, 0);
|
|
87
|
+
const generatedAt = new Date().toISOString();
|
|
88
|
+
// Count all kinds globally
|
|
89
|
+
const allKindCounts = new Map();
|
|
90
|
+
for (const skel of skeletons) {
|
|
91
|
+
const kc = collectAllKinds(skel.symbols);
|
|
92
|
+
for (const [k, v] of kc)
|
|
93
|
+
allKindCounts.set(k, (allKindCounts.get(k) ?? 0) + v);
|
|
94
|
+
}
|
|
95
|
+
const sortedKinds = [...allKindCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
96
|
+
// Language distribution
|
|
97
|
+
const langCounts = new Map();
|
|
98
|
+
for (const skel of skeletons)
|
|
99
|
+
langCounts.set(skel.language, (langCounts.get(skel.language) ?? 0) + 1);
|
|
100
|
+
const sortedLangs = [...langCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
101
|
+
const kindPills = sortedKinds.map(([k]) => {
|
|
102
|
+
const color = KIND_COLORS[k] ?? "#64748b";
|
|
103
|
+
return `<button class="kind-pill" data-kind="${k}" onclick="toggleKind('${k}',this)" style="--kc:${color}">${k}</button>`;
|
|
104
|
+
}).join("");
|
|
105
|
+
const fileData = JSON.stringify(skeletons.map((s, i) => ({
|
|
106
|
+
id: i,
|
|
107
|
+
file: s.file,
|
|
108
|
+
lang: s.language,
|
|
109
|
+
n: s.symbolCount,
|
|
110
|
+
syms: collectSymbolNames(s.symbols).join(" "),
|
|
111
|
+
})));
|
|
112
|
+
const kindData = JSON.stringify(Object.fromEntries(allKindCounts));
|
|
113
|
+
const langData = JSON.stringify(sortedLangs);
|
|
114
|
+
return `<!doctype html>
|
|
115
|
+
<html lang="en">
|
|
116
|
+
<head>
|
|
117
|
+
<meta charset="utf-8">
|
|
118
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
119
|
+
<title>AST Map — ${skeletons.length} files · ${totalSymbols} symbols</title>
|
|
120
|
+
<style>
|
|
121
|
+
:root{
|
|
122
|
+
color-scheme:light dark;
|
|
123
|
+
--bg:#f6f8fa;--bg2:#fff;--fg:#0f172a;--fg2:#64748b;--bdr:#e2e8f0;
|
|
124
|
+
--hover:#f1f5f9;--sb-bg:#fff;--sb-w:272px;--accent:#6366f1;
|
|
125
|
+
--accent2:#8b5cf6;--topbar-h:52px;--filter-h:40px;
|
|
126
|
+
--shadow:0 1px 3px rgba(0,0,0,.06),0 1px 2px rgba(0,0,0,.04);
|
|
127
|
+
}
|
|
128
|
+
@media(prefers-color-scheme:dark){
|
|
129
|
+
:root{
|
|
130
|
+
--bg:#0d1117;--bg2:#161b22;--fg:#e6edf3;--fg2:#7d8590;--bdr:#21262d;
|
|
131
|
+
--hover:#1c2128;--sb-bg:#13181f;
|
|
132
|
+
--shadow:0 1px 3px rgba(0,0,0,.3),0 1px 2px rgba(0,0,0,.2);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
*{box-sizing:border-box;margin:0;padding:0;}
|
|
136
|
+
body{font:13px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:var(--bg);color:var(--fg);display:flex;flex-direction:column;height:100vh;overflow:hidden;}
|
|
137
|
+
|
|
138
|
+
/* ── Topbar ──────────────────────────────────────────────── */
|
|
139
|
+
.topbar{
|
|
140
|
+
display:flex;align-items:center;gap:10px;padding:0 16px;
|
|
141
|
+
height:var(--topbar-h);background:var(--bg2);
|
|
142
|
+
border-bottom:1px solid var(--bdr);flex-shrink:0;z-index:10;
|
|
143
|
+
box-shadow:var(--shadow);
|
|
144
|
+
}
|
|
145
|
+
.topbar-logo{display:flex;align-items:center;gap:7px;font-weight:700;font-size:14px;color:var(--accent);flex-shrink:0;}
|
|
146
|
+
.topbar-logo svg{opacity:.85;}
|
|
147
|
+
.topbar-sep{width:1px;height:20px;background:var(--bdr);flex-shrink:0;}
|
|
148
|
+
.topbar-meta{font-size:12px;color:var(--fg2);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
149
|
+
.topbar-actions{display:flex;gap:6px;flex-shrink:0;}
|
|
150
|
+
.btn{font:inherit;cursor:pointer;border:1px solid var(--bdr);background:transparent;color:inherit;border-radius:7px;padding:4px 11px;font-size:12px;transition:background .12s,border-color .12s;}
|
|
151
|
+
.btn:hover{background:var(--hover);border-color:color-mix(in srgb,var(--accent) 30%,var(--bdr));}
|
|
152
|
+
.btn-accent{background:var(--accent);color:#fff;border-color:var(--accent);}
|
|
153
|
+
.btn-accent:hover{background:var(--accent2);border-color:var(--accent2);}
|
|
154
|
+
|
|
155
|
+
/* ── Filter bar ──────────────────────────────────────────── */
|
|
156
|
+
.filter-bar{
|
|
157
|
+
display:flex;align-items:center;gap:6px;padding:0 12px;
|
|
158
|
+
height:var(--filter-h);background:var(--bg2);
|
|
159
|
+
border-bottom:1px solid var(--bdr);flex-shrink:0;overflow-x:auto;
|
|
160
|
+
scrollbar-width:none;
|
|
161
|
+
}
|
|
162
|
+
.filter-bar::-webkit-scrollbar{display:none;}
|
|
163
|
+
.filter-label{font-size:11px;color:var(--fg2);flex-shrink:0;font-weight:500;text-transform:uppercase;letter-spacing:.04em;}
|
|
164
|
+
.kind-pill{
|
|
165
|
+
font:11px/1 ui-sans-serif,system-ui,sans-serif;cursor:pointer;
|
|
166
|
+
border:1px solid var(--kc,var(--bdr));
|
|
167
|
+
background:color-mix(in srgb,var(--kc,var(--bdr)) 8%,transparent);
|
|
168
|
+
color:var(--kc,var(--fg2));border-radius:999px;
|
|
169
|
+
padding:3px 10px;white-space:nowrap;transition:all .12s;flex-shrink:0;
|
|
170
|
+
}
|
|
171
|
+
.kind-pill:hover{background:color-mix(in srgb,var(--kc,var(--bdr)) 18%,transparent);}
|
|
172
|
+
.kind-pill.active{background:var(--kc,var(--fg2));color:#fff;border-color:var(--kc,var(--fg2));}
|
|
173
|
+
.filter-div{width:1px;height:16px;background:var(--bdr);flex-shrink:0;margin:0 2px;}
|
|
174
|
+
|
|
175
|
+
/* ── Layout ──────────────────────────────────────────────── */
|
|
176
|
+
.layout{display:flex;flex:1;min-height:0;}
|
|
177
|
+
|
|
178
|
+
/* ── Sidebar ─────────────────────────────────────────────── */
|
|
179
|
+
.sidebar{
|
|
180
|
+
width:var(--sb-w);flex-shrink:0;background:var(--sb-bg);
|
|
181
|
+
border-right:1px solid var(--bdr);
|
|
182
|
+
display:flex;flex-direction:column;overflow:hidden;
|
|
183
|
+
}
|
|
184
|
+
.search-wrap{padding:8px 10px;border-bottom:1px solid var(--bdr);position:relative;}
|
|
185
|
+
.search-icon{position:absolute;left:18px;top:50%;transform:translateY(-50%);opacity:.4;pointer-events:none;font-size:12px;}
|
|
186
|
+
#search{
|
|
187
|
+
width:100%;font:inherit;font-size:12px;padding:5px 8px 5px 26px;
|
|
188
|
+
border:1px solid var(--bdr);border-radius:8px;
|
|
189
|
+
background:var(--bg);color:var(--fg);outline:none;
|
|
190
|
+
transition:border-color .15s;
|
|
191
|
+
}
|
|
192
|
+
#search:focus{border-color:var(--accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 20%,transparent);}
|
|
193
|
+
.search-hint{font-size:10px;color:var(--fg2);text-align:right;padding:2px 2px 0;opacity:.7;}
|
|
194
|
+
|
|
195
|
+
/* Sidebar stats */
|
|
196
|
+
.sb-section{border-bottom:1px solid var(--bdr);padding:8px 10px;}
|
|
197
|
+
.sb-title{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--fg2);margin-bottom:6px;}
|
|
198
|
+
.sb-stat-row{display:flex;align-items:center;gap:6px;margin:3px 0;}
|
|
199
|
+
.sb-stat-bar{flex:1;height:4px;background:var(--bdr);border-radius:2px;overflow:hidden;}
|
|
200
|
+
.sb-stat-fill{height:100%;border-radius:2px;}
|
|
201
|
+
.sb-stat-label{font-size:11px;color:var(--fg2);min-width:60px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
202
|
+
.sb-stat-num{font-size:11px;color:var(--fg2);min-width:24px;text-align:right;flex-shrink:0;}
|
|
203
|
+
.lang-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;display:inline-block;}
|
|
204
|
+
|
|
205
|
+
/* Nav tree */
|
|
206
|
+
.nav-tree{flex:1;overflow-y:auto;padding:6px 4px;scrollbar-width:thin;scrollbar-color:var(--bdr) transparent;}
|
|
207
|
+
.dir-node{margin:1px 0;}
|
|
208
|
+
.dir-node>summary{
|
|
209
|
+
list-style:none;cursor:pointer;padding:3px 7px;border-radius:6px;
|
|
210
|
+
font-size:12px;font-weight:600;color:var(--fg2);
|
|
211
|
+
display:flex;align-items:center;gap:5px;user-select:none;
|
|
212
|
+
}
|
|
213
|
+
.dir-node>summary::-webkit-details-marker{display:none;}
|
|
214
|
+
.dir-node>summary::before{content:"\\25B8";font-size:9px;opacity:.5;transition:transform .12s;flex-shrink:0;}
|
|
215
|
+
.dir-node[open]>summary::before{transform:rotate(90deg);}
|
|
216
|
+
.dir-node>summary:hover{background:var(--hover);}
|
|
217
|
+
.dir-children{padding-left:12px;}
|
|
218
|
+
a.file-link{
|
|
219
|
+
display:flex;align-items:center;padding:3px 7px;border-radius:6px;
|
|
220
|
+
text-decoration:none;color:var(--fg);font-size:12px;cursor:pointer;gap:5px;
|
|
221
|
+
transition:background .1s;
|
|
222
|
+
}
|
|
223
|
+
a.file-link:hover{background:var(--hover);}
|
|
224
|
+
a.file-link.active{background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent);}
|
|
225
|
+
.fname{font-family:ui-monospace,monospace;font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;}
|
|
226
|
+
.fmeta{font-size:10px;color:var(--fg2);flex-shrink:0;}
|
|
227
|
+
|
|
228
|
+
/* ── Main panel ──────────────────────────────────────────── */
|
|
229
|
+
.main-panel{flex:1;overflow-y:auto;padding:14px 16px;scrollbar-width:thin;scrollbar-color:var(--bdr) transparent;}
|
|
230
|
+
|
|
231
|
+
/* File sections */
|
|
232
|
+
.file-section{border:1px solid var(--bdr);border-radius:12px;margin-bottom:12px;background:var(--bg2);overflow:hidden;box-shadow:var(--shadow);}
|
|
233
|
+
.file-header{
|
|
234
|
+
display:flex;align-items:center;gap:8px;padding:10px 14px;
|
|
235
|
+
cursor:pointer;user-select:none;
|
|
236
|
+
border-bottom:1px solid transparent;transition:background .1s;
|
|
237
|
+
}
|
|
238
|
+
.file-header:hover{background:var(--hover);}
|
|
239
|
+
.file-section.open .file-header{border-bottom-color:var(--bdr);}
|
|
240
|
+
.toggle-icon{font-size:11px;opacity:.5;transition:transform .15s;flex-shrink:0;color:var(--fg2);}
|
|
241
|
+
.file-section:not(.open) .toggle-icon{transform:rotate(-90deg);}
|
|
242
|
+
.fs-path{font-family:ui-monospace,monospace;font-weight:700;font-size:12px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
243
|
+
.fs-lang{font-size:10px;font-weight:600;padding:2px 7px;border-radius:999px;flex-shrink:0;}
|
|
244
|
+
.fs-count{font-size:11px;color:var(--fg2);flex-shrink:0;}
|
|
245
|
+
.fs-body{padding:8px 12px 12px;}
|
|
246
|
+
.file-section:not(.open) .fs-body{display:none;}
|
|
247
|
+
|
|
248
|
+
/* Symbol tree */
|
|
249
|
+
.tree{display:flex;flex-direction:column;gap:3px;}
|
|
250
|
+
details.node{border:1px solid var(--bdr);border-radius:9px;padding:1px 3px;transition:border-color .12s;}
|
|
251
|
+
details.node[open]{border-color:color-mix(in srgb,var(--accent) 25%,var(--bdr));}
|
|
252
|
+
summary{list-style:none;cursor:pointer;padding:5px 7px;border-radius:7px;display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
|
|
253
|
+
summary::-webkit-details-marker{display:none;}
|
|
254
|
+
summary::before{content:"\\25B8";opacity:.4;transition:transform .15s;font-size:10px;flex-shrink:0;}
|
|
255
|
+
details[open]>summary::before{transform:rotate(90deg);}
|
|
256
|
+
.leaf{padding:5px 7px 5px 22px;border-radius:7px;display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
|
|
257
|
+
summary:hover,.leaf:hover{background:var(--hover);}
|
|
258
|
+
.children{margin:2px 0 5px 16px;padding-left:10px;border-left:2px solid color-mix(in srgb,var(--accent) 15%,var(--bdr));display:flex;flex-direction:column;gap:3px;}
|
|
259
|
+
.badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:999px;letter-spacing:.04em;flex-shrink:0;}
|
|
260
|
+
.name{font-family:ui-monospace,monospace;font-weight:600;font-size:12px;}
|
|
261
|
+
.vis{font-size:10px;padding:1px 5px;border-radius:5px;flex-shrink:0;}
|
|
262
|
+
.vis.priv{background:#ef44441a;color:#ef4444;}
|
|
263
|
+
.vis.exp{background:#22c55e1a;color:#16a34a;}
|
|
264
|
+
.sig{font-family:ui-monospace,monospace;font-size:11px;background:var(--hover);padding:1px 6px;border-radius:5px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;}
|
|
265
|
+
.lines{font-size:10px;opacity:.5;margin-left:auto;font-family:ui-monospace,monospace;flex-shrink:0;}
|
|
266
|
+
.doc{font-size:11px;opacity:.75;margin:2px 0 5px 26px;white-space:pre-wrap;font-style:italic;color:var(--fg2);}
|
|
267
|
+
.copy-btn{
|
|
268
|
+
opacity:0;font-size:11px;padding:1px 5px;border-radius:4px;border:1px solid var(--bdr);
|
|
269
|
+
background:transparent;color:var(--fg2);cursor:pointer;transition:opacity .12s,background .12s;
|
|
270
|
+
flex-shrink:0;margin-left:2px;
|
|
271
|
+
}
|
|
272
|
+
.copy-btn:hover{background:var(--hover);color:var(--fg);}
|
|
273
|
+
summary:hover .copy-btn,.leaf:hover .copy-btn{opacity:1;}
|
|
274
|
+
.copy-btn.copied{opacity:1;color:#16a34a;border-color:#16a34a;}
|
|
275
|
+
|
|
276
|
+
.empty{opacity:.6;font-size:12px;padding:4px 0;}
|
|
277
|
+
.no-match{padding:60px 20px;text-align:center;color:var(--fg2);font-size:13px;}
|
|
278
|
+
.no-match-icon{font-size:32px;margin-bottom:8px;opacity:.4;}
|
|
279
|
+
.hidden-kind{display:none !important;}
|
|
280
|
+
|
|
281
|
+
/* ── Keyboard hint tooltip ───────────────────────────────── */
|
|
282
|
+
.kbd{font-size:10px;background:var(--bdr);color:var(--fg2);border-radius:4px;padding:1px 5px;font-family:ui-monospace,monospace;border:1px solid color-mix(in srgb,var(--fg) 20%,var(--bdr));}
|
|
283
|
+
|
|
284
|
+
/* ── Toast ───────────────────────────────────────────────── */
|
|
285
|
+
#toast{
|
|
286
|
+
position:fixed;bottom:16px;left:50%;transform:translateX(-50%);
|
|
287
|
+
background:#0f172a;color:#fff;font-size:12px;padding:6px 14px;
|
|
288
|
+
border-radius:8px;opacity:0;pointer-events:none;z-index:99;
|
|
289
|
+
transition:opacity .2s;box-shadow:0 4px 12px rgba(0,0,0,.3);
|
|
290
|
+
}
|
|
291
|
+
#toast.show{opacity:1;}
|
|
292
|
+
</style>
|
|
293
|
+
</head>
|
|
294
|
+
<body>
|
|
295
|
+
|
|
296
|
+
<header class="topbar">
|
|
297
|
+
<div class="topbar-logo">
|
|
298
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
|
299
|
+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
|
|
300
|
+
</svg>
|
|
301
|
+
AST Map
|
|
302
|
+
</div>
|
|
303
|
+
<div class="topbar-sep"></div>
|
|
304
|
+
<span class="topbar-meta">${skeletons.length} files · ${totalSymbols} symbols · <time title="${esc(generatedAt)}">${esc(generatedAt.slice(0, 10))}</time></span>
|
|
305
|
+
<div class="topbar-actions">
|
|
306
|
+
<button class="btn" id="btn-expand" title="Expand all sections">Expand all</button>
|
|
307
|
+
<button class="btn" id="btn-collapse" title="Collapse all sections">Collapse all</button>
|
|
308
|
+
<button class="btn btn-accent" id="btn-export" title="Export skeleton as JSON">Export JSON</button>
|
|
309
|
+
</div>
|
|
310
|
+
</header>
|
|
311
|
+
|
|
312
|
+
<div class="filter-bar" id="filter-bar">
|
|
313
|
+
<span class="filter-label">Filter:</span>
|
|
314
|
+
${kindPills}
|
|
315
|
+
<div class="filter-div"></div>
|
|
316
|
+
<button class="kind-pill" id="clear-filter" style="--kc:#64748b" onclick="clearKindFilter()">× clear</button>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div class="layout">
|
|
320
|
+
<nav class="sidebar">
|
|
321
|
+
<div class="search-wrap">
|
|
322
|
+
<span class="search-icon">⌕</span>
|
|
323
|
+
<input id="search" type="search" placeholder="Search files or symbols…" autocomplete="off" spellcheck="false" aria-label="Search">
|
|
324
|
+
<div class="search-hint">Press <kbd class="kbd">/</kbd> to focus</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<div class="sb-section" id="lang-stats"></div>
|
|
328
|
+
<div class="sb-section" id="kind-stats"></div>
|
|
329
|
+
|
|
330
|
+
<div id="nav-tree" class="nav-tree"></div>
|
|
331
|
+
</nav>
|
|
332
|
+
|
|
333
|
+
<main id="main" class="main-panel">
|
|
334
|
+
${sections}
|
|
335
|
+
<div id="no-match" class="no-match" style="display:none">
|
|
336
|
+
<div class="no-match-icon">⊘</div>
|
|
337
|
+
No matching files or symbols found.
|
|
338
|
+
</div>
|
|
339
|
+
</main>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div id="toast"></div>
|
|
343
|
+
|
|
344
|
+
<script>
|
|
345
|
+
(function(){
|
|
346
|
+
'use strict';
|
|
347
|
+
const FILES=${fileData};
|
|
348
|
+
const KIND_COUNTS=${kindData};
|
|
349
|
+
const LANG_DATA=${langData};
|
|
350
|
+
const KIND_COLORS={class:"#7c3aed",interface:"#0ea5e9",struct:"#0d9488",function:"#2563eb",method:"#4f46e5",type:"#db2777",enum:"#ea580c","const":"#65a30d","var":"#ca8a04",field:"#64748b",namespace:"#9333ea"};
|
|
351
|
+
const LANG_COLORS={typescript:"#3178c6",javascript:"#f7df1e",python:"#3572a5",go:"#00acd7",rust:"#dea584",java:"#b07219","c++":"#f34b7d",c:"#555555",csharp:"#239120",kotlin:"#a97bff",swift:"#f05138",tsx:"#3178c6",jsx:"#f7df1e"};
|
|
352
|
+
|
|
353
|
+
// ── Open state ─────────────────────────────────────────────
|
|
354
|
+
const openState=new Set();
|
|
355
|
+
FILES.forEach(f=>openState.add(f.id));
|
|
356
|
+
|
|
357
|
+
function toggleSection(id){
|
|
358
|
+
const sec=document.getElementById('file-'+id);
|
|
359
|
+
if(!sec)return;
|
|
360
|
+
if(openState.has(id)){openState.delete(id);sec.classList.remove('open');}
|
|
361
|
+
else{openState.add(id);sec.classList.add('open');}
|
|
362
|
+
document.getElementById('tog-'+id).textContent=openState.has(id)?'▾':'▸';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Initialise open state
|
|
366
|
+
FILES.forEach(f=>{
|
|
367
|
+
const sec=document.getElementById('file-'+f.id);
|
|
368
|
+
if(sec){sec.classList.add('open');}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ── Sidebar stats ──────────────────────────────────────────
|
|
372
|
+
function e(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
373
|
+
|
|
374
|
+
function buildLangStats(){
|
|
375
|
+
const el=document.getElementById('lang-stats');
|
|
376
|
+
if(!LANG_DATA.length){el.style.display='none';return;}
|
|
377
|
+
const max=LANG_DATA[0][1];
|
|
378
|
+
let html='<div class="sb-title">Languages</div>';
|
|
379
|
+
for(const[lang,cnt]of LANG_DATA){
|
|
380
|
+
const c=LANG_COLORS[lang]||'#94a3b8';
|
|
381
|
+
const pct=Math.round(cnt/max*100);
|
|
382
|
+
html+=\`<div class="sb-stat-row">
|
|
383
|
+
<span class="lang-dot" style="background:\${c}"></span>
|
|
384
|
+
<span class="sb-stat-label">\${e(lang)}</span>
|
|
385
|
+
<div class="sb-stat-bar"><div class="sb-stat-fill" style="width:\${pct}%;background:\${c}"></div></div>
|
|
386
|
+
<span class="sb-stat-num">\${cnt}</span>
|
|
387
|
+
</div>\`;
|
|
388
|
+
}
|
|
389
|
+
el.innerHTML=html;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function buildKindStats(){
|
|
393
|
+
const el=document.getElementById('kind-stats');
|
|
394
|
+
const entries=Object.entries(KIND_COUNTS).sort((a,b)=>b[1]-a[1]);
|
|
395
|
+
if(!entries.length){el.style.display='none';return;}
|
|
396
|
+
const max=entries[0][1];
|
|
397
|
+
let html='<div class="sb-title">Symbol kinds</div>';
|
|
398
|
+
for(const[kind,cnt]of entries.slice(0,8)){
|
|
399
|
+
const c=KIND_COLORS[kind]||'#64748b';
|
|
400
|
+
const pct=Math.round(cnt/max*100);
|
|
401
|
+
html+=\`<div class="sb-stat-row">
|
|
402
|
+
<span style="width:8px;height:8px;border-radius:2px;background:\${c};flex-shrink:0;display:inline-block"></span>
|
|
403
|
+
<span class="sb-stat-label">\${e(kind)}</span>
|
|
404
|
+
<div class="sb-stat-bar"><div class="sb-stat-fill" style="width:\${pct}%;background:\${c}44"></div></div>
|
|
405
|
+
<span class="sb-stat-num">\${cnt}</span>
|
|
406
|
+
</div>\`;
|
|
407
|
+
}
|
|
408
|
+
el.innerHTML=html;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
buildLangStats();
|
|
412
|
+
buildKindStats();
|
|
413
|
+
|
|
414
|
+
// ── Nav tree ───────────────────────────────────────────────
|
|
415
|
+
function buildTreeData(files){
|
|
416
|
+
const root={dirs:{},files:[]};
|
|
417
|
+
for(const f of files){
|
|
418
|
+
const parts=f.file.split('/');
|
|
419
|
+
let node=root;
|
|
420
|
+
for(let i=0;i<parts.length-1;i++){
|
|
421
|
+
if(!node.dirs[parts[i]])node.dirs[parts[i]]={dirs:{},files:[]};
|
|
422
|
+
node=node.dirs[parts[i]];
|
|
423
|
+
}
|
|
424
|
+
node.files.push(f);
|
|
425
|
+
}
|
|
426
|
+
return root;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const linkMap=new Map();
|
|
430
|
+
function renderTreeNode(node,container){
|
|
431
|
+
const dirs=Object.entries(node.dirs).sort(([a],[b])=>a.localeCompare(b));
|
|
432
|
+
for(const[name,child]of dirs){
|
|
433
|
+
const det=document.createElement('details');
|
|
434
|
+
det.className='dir-node';det.open=true;
|
|
435
|
+
const sum=document.createElement('summary');
|
|
436
|
+
sum.textContent=name;
|
|
437
|
+
det.appendChild(sum);
|
|
438
|
+
const inner=document.createElement('div');
|
|
439
|
+
inner.className='dir-children';
|
|
440
|
+
renderTreeNode(child,inner);
|
|
441
|
+
det.appendChild(inner);
|
|
442
|
+
container.appendChild(det);
|
|
443
|
+
}
|
|
444
|
+
for(const f of node.files){
|
|
445
|
+
const a=document.createElement('a');
|
|
446
|
+
a.href='#file-'+f.id;
|
|
447
|
+
a.className='file-link';
|
|
448
|
+
a.dataset.id=String(f.id);
|
|
449
|
+
const fname=f.file.split('/').pop()||f.file;
|
|
450
|
+
const lc=LANG_COLORS[f.lang]||'#94a3b8';
|
|
451
|
+
a.innerHTML=\`<span class="lang-dot" style="background:\${lc}"></span><span class="fname">\${e(fname)}</span><span class="fmeta">\${f.n}</span>\`;
|
|
452
|
+
a.addEventListener('click',ev=>{
|
|
453
|
+
ev.preventDefault();
|
|
454
|
+
const sec=document.getElementById('file-'+f.id);
|
|
455
|
+
if(sec){
|
|
456
|
+
if(!openState.has(f.id)){toggleSection(f.id);}
|
|
457
|
+
sec.scrollIntoView({behavior:'smooth',block:'start'});
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
container.appendChild(a);
|
|
461
|
+
linkMap.set(f.id,a);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const navTree=document.getElementById('nav-tree');
|
|
466
|
+
renderTreeNode(buildTreeData(FILES),navTree);
|
|
467
|
+
|
|
468
|
+
// ── Search ─────────────────────────────────────────────────
|
|
469
|
+
const searchEl=document.getElementById('search');
|
|
470
|
+
const mainEl=document.getElementById('main');
|
|
471
|
+
const noMatch=document.getElementById('no-match');
|
|
472
|
+
|
|
473
|
+
searchEl.addEventListener('input',applyFilter);
|
|
474
|
+
function applyFilter(){
|
|
475
|
+
const q=searchEl.value.trim().toLowerCase();
|
|
476
|
+
let vis=0;
|
|
477
|
+
FILES.forEach(f=>{
|
|
478
|
+
const sec=document.getElementById('file-'+f.id);
|
|
479
|
+
if(!sec)return;
|
|
480
|
+
const match=!q||f.file.toLowerCase().includes(q)||f.syms.toLowerCase().includes(q);
|
|
481
|
+
sec.style.display=match?'':'none';
|
|
482
|
+
const link=linkMap.get(f.id);
|
|
483
|
+
if(link)link.style.display=match?'':'none';
|
|
484
|
+
if(match)vis++;
|
|
485
|
+
});
|
|
486
|
+
noMatch.style.display=vis===0&&q?'':'none';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ── Kind filter ────────────────────────────────────────────
|
|
490
|
+
let activeKinds=new Set();
|
|
491
|
+
function toggleKind(kind,btn){
|
|
492
|
+
if(activeKinds.has(kind)){activeKinds.delete(kind);btn.classList.remove('active');}
|
|
493
|
+
else{activeKinds.add(kind);btn.classList.add('active');}
|
|
494
|
+
applyKindFilter();
|
|
495
|
+
}
|
|
496
|
+
function clearKindFilter(){
|
|
497
|
+
activeKinds.clear();
|
|
498
|
+
document.querySelectorAll('.kind-pill').forEach(p=>p.classList.remove('active'));
|
|
499
|
+
applyKindFilter();
|
|
500
|
+
}
|
|
501
|
+
function applyKindFilter(){
|
|
502
|
+
if(!activeKinds.size){
|
|
503
|
+
document.querySelectorAll('.node[data-kind]').forEach(n=>n.classList.remove('hidden-kind'));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
document.querySelectorAll('.node[data-kind]').forEach(n=>{
|
|
507
|
+
const k=n.getAttribute('data-kind');
|
|
508
|
+
n.classList.toggle('hidden-kind',!activeKinds.has(k));
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
window.toggleKind=toggleKind;
|
|
512
|
+
window.clearKindFilter=clearKindFilter;
|
|
513
|
+
|
|
514
|
+
// ── Expand / Collapse ──────────────────────────────────────
|
|
515
|
+
document.getElementById('btn-expand').addEventListener('click',()=>{
|
|
516
|
+
FILES.forEach(f=>{
|
|
517
|
+
const sec=document.getElementById('file-'+f.id);
|
|
518
|
+
if(sec&&!openState.has(f.id)){toggleSection(f.id);}
|
|
519
|
+
});
|
|
520
|
+
document.querySelectorAll('details.node').forEach(d=>d.open=true);
|
|
521
|
+
});
|
|
522
|
+
document.getElementById('btn-collapse').addEventListener('click',()=>{
|
|
523
|
+
FILES.forEach(f=>{
|
|
524
|
+
const sec=document.getElementById('file-'+f.id);
|
|
525
|
+
if(sec&&openState.has(f.id)){toggleSection(f.id);}
|
|
526
|
+
});
|
|
527
|
+
document.querySelectorAll('details.node').forEach(d=>d.open=false);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// ── Export JSON ────────────────────────────────────────────
|
|
531
|
+
document.getElementById('btn-export').addEventListener('click',()=>{
|
|
532
|
+
const data=JSON.stringify(FILES,null,2);
|
|
533
|
+
const blob=new Blob([data],{type:'application/json'});
|
|
534
|
+
const url=URL.createObjectURL(blob);
|
|
535
|
+
const a=document.createElement('a');
|
|
536
|
+
a.href=url;a.download='ast-map.json';a.click();
|
|
537
|
+
URL.revokeObjectURL(url);
|
|
538
|
+
showToast('Exported ast-map.json');
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// ── Active sidebar link on scroll ─────────────────────────
|
|
542
|
+
const io=new IntersectionObserver(entries=>{
|
|
543
|
+
for(const en of entries){
|
|
544
|
+
if(en.isIntersecting){
|
|
545
|
+
const idx=parseInt(en.target.id.replace('file-',''),10);
|
|
546
|
+
linkMap.forEach((a,id)=>a.classList.toggle('active',id===idx));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
},{root:mainEl,threshold:0.1});
|
|
550
|
+
document.querySelectorAll('.file-section').forEach(s=>io.observe(s));
|
|
551
|
+
|
|
552
|
+
// ── Keyboard shortcuts ─────────────────────────────────────
|
|
553
|
+
document.addEventListener('keydown',ev=>{
|
|
554
|
+
if(ev.key==='/'&&document.activeElement!==searchEl){
|
|
555
|
+
ev.preventDefault();
|
|
556
|
+
searchEl.focus();searchEl.select();
|
|
557
|
+
}
|
|
558
|
+
if(ev.key==='Escape'&&document.activeElement===searchEl){
|
|
559
|
+
searchEl.value='';applyFilter();searchEl.blur();
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ── Copy helper ────────────────────────────────────────────
|
|
564
|
+
function copyText(text,btn){
|
|
565
|
+
navigator.clipboard.writeText(text).then(()=>{
|
|
566
|
+
btn.classList.add('copied');
|
|
567
|
+
btn.textContent='✓';
|
|
568
|
+
showToast('Copied: '+text);
|
|
569
|
+
setTimeout(()=>{btn.classList.remove('copied');btn.textContent='⎘';},1500);
|
|
570
|
+
}).catch(()=>{showToast('Copy failed');});
|
|
571
|
+
}
|
|
572
|
+
window.copyText=copyText;
|
|
573
|
+
|
|
574
|
+
// ── Toast ──────────────────────────────────────────────────
|
|
575
|
+
let toastTimer;
|
|
576
|
+
function showToast(msg){
|
|
577
|
+
const t=document.getElementById('toast');
|
|
578
|
+
t.textContent=msg;t.classList.add('show');
|
|
579
|
+
clearTimeout(toastTimer);
|
|
580
|
+
toastTimer=setTimeout(()=>t.classList.remove('show'),2200);
|
|
581
|
+
}
|
|
582
|
+
window.showToast=showToast;
|
|
583
|
+
|
|
584
|
+
// ── window.toggleSection ───────────────────────────────────
|
|
585
|
+
window.toggleSection=toggleSection;
|
|
586
|
+
|
|
587
|
+
})();
|
|
588
|
+
</script>
|
|
589
|
+
</body>
|
|
590
|
+
</html>`;
|
|
591
|
+
}
|
|
592
|
+
export function renderHtml(skel) {
|
|
593
|
+
const body = skel.symbols.length > 0
|
|
594
|
+
? skel.symbols.map((s) => renderSymbol(s)).join("")
|
|
595
|
+
: `<p class="empty">No top-level symbols found.</p>`;
|
|
596
|
+
const lc = LANG_COLOR[skel.language] ?? "#94a3b8";
|
|
597
|
+
return `<!doctype html>
|
|
598
|
+
<html lang="en">
|
|
599
|
+
<head>
|
|
600
|
+
<meta charset="utf-8">
|
|
601
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
602
|
+
<title>AST Map — ${esc(skel.file)}</title>
|
|
603
|
+
<style>
|
|
604
|
+
:root{color-scheme:light dark;--bg:#f6f8fa;--bg2:#fff;--fg:#0f172a;--fg2:#64748b;--bdr:#e2e8f0;--hover:#f1f5f9;--accent:#6366f1;}
|
|
605
|
+
@media(prefers-color-scheme:dark){:root{--bg:#0d1117;--bg2:#161b22;--fg:#e6edf3;--fg2:#7d8590;--bdr:#21262d;--hover:#1c2128;}}
|
|
606
|
+
*{box-sizing:border-box;margin:0;padding:0;}
|
|
607
|
+
body{font:13px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--fg);padding:20px;}
|
|
608
|
+
header.meta{background:var(--bg2);border:1px solid var(--bdr);border-radius:12px;padding:14px 18px;margin-bottom:14px;}
|
|
609
|
+
header.meta h1{font-size:14px;font-family:ui-monospace,monospace;font-weight:700;margin-bottom:4px;}
|
|
610
|
+
header.meta .sub{font-size:11px;color:var(--fg2);}
|
|
611
|
+
.lang-badge{display:inline-block;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;margin-right:8px;}
|
|
612
|
+
.toolbar{margin:10px 0;display:flex;gap:6px;flex-wrap:wrap;}
|
|
613
|
+
.btn{font:12px ui-sans-serif,sans-serif;cursor:pointer;border:1px solid var(--bdr);background:transparent;color:inherit;border-radius:7px;padding:4px 11px;}
|
|
614
|
+
.btn:hover{background:var(--hover);}
|
|
615
|
+
.tree{display:flex;flex-direction:column;gap:3px;}
|
|
616
|
+
details.node{border:1px solid var(--bdr);border-radius:9px;padding:1px 3px;}
|
|
617
|
+
details.node[open]{border-color:color-mix(in srgb,var(--accent) 25%,var(--bdr));}
|
|
618
|
+
summary{list-style:none;cursor:pointer;padding:5px 7px;border-radius:7px;display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
|
|
619
|
+
summary::-webkit-details-marker{display:none;}
|
|
620
|
+
summary::before{content:"\\25B8";opacity:.4;transition:transform .15s;font-size:10px;}
|
|
621
|
+
details[open]>summary::before{transform:rotate(90deg);}
|
|
622
|
+
.leaf{padding:5px 7px 5px 22px;border-radius:7px;display:flex;align-items:center;gap:7px;flex-wrap:wrap;}
|
|
623
|
+
summary:hover,.leaf:hover{background:var(--hover);}
|
|
624
|
+
.children{margin:2px 0 5px 16px;padding-left:10px;border-left:2px solid color-mix(in srgb,var(--accent) 15%,var(--bdr));display:flex;flex-direction:column;gap:3px;}
|
|
625
|
+
.badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:999px;letter-spacing:.04em;}
|
|
626
|
+
.name{font-family:ui-monospace,monospace;font-weight:600;font-size:12px;}
|
|
627
|
+
.vis{font-size:10px;padding:1px 5px;border-radius:5px;}
|
|
628
|
+
.vis.priv{background:#ef44441a;color:#ef4444;}.vis.exp{background:#22c55e1a;color:#16a34a;}
|
|
629
|
+
.sig{font-family:ui-monospace,monospace;font-size:11px;background:var(--hover);padding:1px 6px;border-radius:5px;max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
630
|
+
.lines{font-size:10px;opacity:.5;margin-left:auto;font-family:ui-monospace,monospace;}
|
|
631
|
+
.doc{font-size:11px;opacity:.75;margin:2px 0 5px 26px;white-space:pre-wrap;font-style:italic;color:var(--fg2);}
|
|
632
|
+
.copy-btn{opacity:0;font-size:11px;padding:1px 5px;border-radius:4px;border:1px solid var(--bdr);background:transparent;color:var(--fg2);cursor:pointer;transition:opacity .12s;}
|
|
633
|
+
summary:hover .copy-btn,.leaf:hover .copy-btn{opacity:1;}
|
|
634
|
+
.copy-btn.copied{opacity:1;color:#16a34a;border-color:#16a34a;}
|
|
635
|
+
.empty{opacity:.6;}
|
|
636
|
+
</style>
|
|
637
|
+
</head>
|
|
638
|
+
<body>
|
|
639
|
+
<header class="meta">
|
|
640
|
+
<h1>${esc(skel.file)}</h1>
|
|
641
|
+
<div class="sub">
|
|
642
|
+
<span class="lang-badge" style="background:${lc}22;color:${lc};border:1px solid ${lc}44">${esc(skel.language)}</span>
|
|
643
|
+
${skel.symbolCount} symbols · ${esc(skel.parser.grammar)} · <time>${esc(skel.generatedAt.slice(0, 10))}</time>
|
|
644
|
+
</div>
|
|
645
|
+
</header>
|
|
646
|
+
<div class="toolbar">
|
|
647
|
+
<button class="btn" onclick="document.querySelectorAll('details').forEach(d=>d.open=true)">Expand all</button>
|
|
648
|
+
<button class="btn" onclick="document.querySelectorAll('details').forEach(d=>d.open=false)">Collapse all</button>
|
|
649
|
+
</div>
|
|
650
|
+
<div class="tree">
|
|
651
|
+
${body}
|
|
652
|
+
</div>
|
|
653
|
+
<script>
|
|
654
|
+
function copyText(text,btn){navigator.clipboard.writeText(text).then(()=>{btn.classList.add('copied');btn.textContent='✓';setTimeout(()=>{btn.classList.remove('copied');btn.textContent='⎘';},1500);});}
|
|
655
|
+
</script>
|
|
656
|
+
</body>
|
|
657
|
+
</html>`;
|
|
658
|
+
}
|