@commentray/render 0.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.
@@ -0,0 +1,252 @@
1
+ import { FuzzySearcher, PrefixSearcher, Query, SearcherFactory, SubstringSearcher, } from "@m31coding/fuzzy-search";
2
+ import { findOrderedTokenSpans, lineAtIndex, offsetToLineIndex } from "./code-browser-search.js";
3
+ function tokenizeQuery(q) {
4
+ return q.trim().split(/\s+/).filter(Boolean);
5
+ }
6
+ function clamp(n, lo, hi) {
7
+ return Math.max(lo, Math.min(hi, n));
8
+ }
9
+ function decodeB64(b64) {
10
+ try {
11
+ return decodeURIComponent(escape(atob(b64)));
12
+ }
13
+ catch {
14
+ try {
15
+ return atob(b64);
16
+ }
17
+ catch {
18
+ return "";
19
+ }
20
+ }
21
+ }
22
+ function escapeHtmlText(s) {
23
+ return s
24
+ .replace(/&/g, "&")
25
+ .replace(/</g, "&lt;")
26
+ .replace(/>/g, "&gt;")
27
+ .replace(/"/g, "&quot;")
28
+ .replace(/'/g, "&#39;");
29
+ }
30
+ function snippet(s, maxLen) {
31
+ const t = s.replace(/\s+/g, " ").trim();
32
+ if (t.length <= maxLen)
33
+ return t;
34
+ return `${t.slice(0, maxLen - 1)}…`;
35
+ }
36
+ function mergeHits(rows, max) {
37
+ const byKey = new Map();
38
+ for (const r of rows) {
39
+ const key = `${r.kind}:${r.line}`;
40
+ const prev = byKey.get(key);
41
+ if (!prev || r.score > prev.score) {
42
+ byKey.set(key, r);
43
+ }
44
+ }
45
+ return [...byKey.values()].sort((a, b) => b.score - a.score).slice(0, max);
46
+ }
47
+ function buildOrderedHits(raw, kind, tokens) {
48
+ const spans = findOrderedTokenSpans(raw, tokens);
49
+ const seen = new Set();
50
+ const out = [];
51
+ for (const sp of spans) {
52
+ const line = offsetToLineIndex(raw, sp.start);
53
+ if (seen.has(line))
54
+ continue;
55
+ seen.add(line);
56
+ out.push({
57
+ kind,
58
+ line,
59
+ text: lineAtIndex(raw, line),
60
+ score: 1000,
61
+ source: "ordered",
62
+ });
63
+ }
64
+ return out;
65
+ }
66
+ function buildFuzzyHits(searcher, query, topN) {
67
+ const qstr = query.trim();
68
+ if (!qstr)
69
+ return [];
70
+ const q = new Query(qstr, topN, [
71
+ new FuzzySearcher(0.22),
72
+ new SubstringSearcher(0),
73
+ new PrefixSearcher(0),
74
+ ]);
75
+ const res = searcher.getMatches(q);
76
+ const out = [];
77
+ for (const m of res.matches) {
78
+ const row = m.entity;
79
+ out.push({
80
+ kind: row.kind,
81
+ line: row.line,
82
+ text: row.text,
83
+ score: 100 + m.quality,
84
+ source: "fuzzy",
85
+ });
86
+ }
87
+ return out;
88
+ }
89
+ function wireSearchUi(ctx) {
90
+ const { rawCode, rawMd, mdLines, searcher, searchInput, searchClear, searchResults, docPane } = ctx;
91
+ function runSearch() {
92
+ const tokens = tokenizeQuery(searchInput.value);
93
+ if (tokens.length === 0) {
94
+ searchResults.hidden = true;
95
+ searchResults.innerHTML = "";
96
+ return;
97
+ }
98
+ const orderedCode = buildOrderedHits(rawCode, "code", tokens);
99
+ const orderedMd = buildOrderedHits(rawMd, "md", tokens);
100
+ const fuzzyHits = buildFuzzyHits(searcher, searchInput.value, 60);
101
+ const combined = mergeHits([...orderedCode, ...orderedMd, ...fuzzyHits], 80);
102
+ searchResults.hidden = false;
103
+ if (combined.length === 0) {
104
+ searchResults.innerHTML =
105
+ '<div class="hint">No matches. Try fewer tokens or looser spelling (fuzzy matches per line).</div>';
106
+ return;
107
+ }
108
+ const buf = [];
109
+ buf.push(`<div class="hint">Whole source: whitespace tokens in order (may span lines). Per-line fuzzy ranking for typos. ${combined.length} hit(s).</div>`);
110
+ for (const h of combined) {
111
+ const label = h.kind === "code" ? `Code L${h.line + 1}` : `Commentray L${h.line + 1}`;
112
+ const tag = h.source === "ordered" ? "ordered" : "fuzzy";
113
+ buf.push(`<button type="button" class="hit" data-kind="${h.kind}" data-line="${String(h.line)}">` +
114
+ `<span class="meta">${label} <span class="src-tag">(${tag})</span></span>` +
115
+ `<div class="snippet">${escapeHtmlText(snippet(h.text, 200))}</div></button>`);
116
+ }
117
+ searchResults.innerHTML = buf.join("");
118
+ }
119
+ searchResults.addEventListener("click", (ev) => {
120
+ let t = ev.target;
121
+ while (t && t !== searchResults && (!t.classList || !t.classList.contains("hit"))) {
122
+ t = t.parentElement;
123
+ }
124
+ if (!t || !t.classList || !t.classList.contains("hit"))
125
+ return;
126
+ const kind = t.getAttribute("data-kind");
127
+ const line = parseInt(t.getAttribute("data-line") || "0", 10);
128
+ if (kind === "code") {
129
+ const el = document.getElementById(`code-line-${String(line)}`);
130
+ if (el)
131
+ el.scrollIntoView({ block: "nearest", behavior: "smooth" });
132
+ }
133
+ else {
134
+ const total = mdLines.length;
135
+ if (total <= 0)
136
+ return;
137
+ const ratio = line / Math.max(1, total - 1);
138
+ const maxScroll = docPane.scrollHeight - docPane.clientHeight;
139
+ docPane.scrollTo({ top: ratio * Math.max(0, maxScroll), behavior: "smooth" });
140
+ }
141
+ });
142
+ let debounceTimer;
143
+ searchInput.addEventListener("input", () => {
144
+ clearTimeout(debounceTimer);
145
+ debounceTimer = setTimeout(runSearch, 200);
146
+ });
147
+ searchClear.addEventListener("click", () => {
148
+ clearTimeout(debounceTimer);
149
+ searchInput.value = "";
150
+ searchResults.innerHTML = "";
151
+ searchResults.hidden = true;
152
+ });
153
+ }
154
+ function wireWrapToggle(storageWrap, codePane, wrapCb) {
155
+ const wrap = localStorage.getItem(storageWrap) === "1";
156
+ wrapCb.checked = wrap;
157
+ if (wrap)
158
+ codePane.classList.add("wrap");
159
+ wrapCb.addEventListener("change", () => {
160
+ if (wrapCb.checked) {
161
+ codePane.classList.add("wrap");
162
+ localStorage.setItem(storageWrap, "1");
163
+ }
164
+ else {
165
+ codePane.classList.remove("wrap");
166
+ localStorage.setItem(storageWrap, "0");
167
+ }
168
+ });
169
+ }
170
+ function wireSplitter(storageSplit, shell, codePane, gutter, initialPct) {
171
+ let dragging = false;
172
+ let lastPct = initialPct;
173
+ function onMove(ev) {
174
+ if (!dragging)
175
+ return;
176
+ const rect = shell.getBoundingClientRect();
177
+ const x = ev.clientX - rect.left;
178
+ const p = clamp((x / rect.width) * 100, 15, 85);
179
+ lastPct = p;
180
+ codePane.style.flex = `0 0 ${p}%`;
181
+ }
182
+ function stop() {
183
+ dragging = false;
184
+ window.removeEventListener("mousemove", onMove);
185
+ window.removeEventListener("mouseup", stop);
186
+ document.body.style.cursor = "";
187
+ document.body.style.userSelect = "";
188
+ localStorage.setItem(storageSplit, String(lastPct));
189
+ }
190
+ gutter.addEventListener("mousedown", (e) => {
191
+ e.preventDefault();
192
+ dragging = true;
193
+ document.body.style.cursor = "col-resize";
194
+ document.body.style.userSelect = "none";
195
+ window.addEventListener("mousemove", onMove);
196
+ window.addEventListener("mouseup", stop);
197
+ });
198
+ }
199
+ function main() {
200
+ const storageSplit = "commentray.codeCommentrayStatic.splitPct";
201
+ const storageWrap = "commentray.codeCommentrayStatic.wrap";
202
+ const shell = document.getElementById("shell");
203
+ const codePane = document.getElementById("code-pane");
204
+ const docPane = document.getElementById("doc-pane");
205
+ const gutter = document.getElementById("gutter");
206
+ const wrapCb = document.getElementById("wrap-lines");
207
+ const searchInput = document.getElementById("search-q");
208
+ const searchClear = document.getElementById("search-clear");
209
+ const searchResults = document.getElementById("search-results");
210
+ if (!shell ||
211
+ !codePane ||
212
+ !docPane ||
213
+ !gutter ||
214
+ !wrapCb ||
215
+ !searchInput ||
216
+ !searchClear ||
217
+ !searchResults) {
218
+ return;
219
+ }
220
+ const rawCode = decodeB64(codePane.getAttribute("data-raw-code-b64") || "");
221
+ const rawMd = decodeB64(codePane.getAttribute("data-raw-md-b64") || "");
222
+ const mdLines = rawMd.split("\n");
223
+ const codeLines = rawCode.split("\n");
224
+ const lineRows = [
225
+ ...codeLines.map((text, line) => ({ kind: "code", line, text })),
226
+ ...mdLines.map((text, line) => ({ kind: "md", line, text })),
227
+ ];
228
+ const searcher = SearcherFactory.createDefaultSearcher();
229
+ searcher.indexEntities(lineRows, (e) => `${e.kind}:${e.line}`, (e) => [e.text]);
230
+ wireSearchUi({
231
+ rawCode,
232
+ rawMd,
233
+ mdLines,
234
+ searcher,
235
+ searchInput,
236
+ searchClear,
237
+ searchResults,
238
+ docPane,
239
+ });
240
+ const pct0 = parseFloat(localStorage.getItem(storageSplit) || "50");
241
+ const pct = clamp(Number.isFinite(pct0) ? pct0 : 50, 15, 85);
242
+ codePane.style.flex = `0 0 ${pct}%`;
243
+ wireWrapToggle(storageWrap, codePane, wrapCb);
244
+ wireSplitter(storageSplit, shell, codePane, gutter, pct);
245
+ }
246
+ if (document.readyState === "loading") {
247
+ document.addEventListener("DOMContentLoaded", main);
248
+ }
249
+ else {
250
+ main();
251
+ }
252
+ //# sourceMappingURL=code-browser-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-browser-client.js","sourceRoot":"","sources":["../src/code-browser-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Whole-document search: tokens must appear in order as case-insensitive substrings,
3
+ * but may span multiple lines (any offsets in `text`).
4
+ */
5
+ /**
6
+ * Returns every minimal span [start, end) where each non-empty token appears in order
7
+ * in `text` (case-insensitive). Spans may overlap; iteration advances by one code unit
8
+ * from the start of the previous match.
9
+ */
10
+ export declare function findOrderedTokenSpans(text: string, tokens: string[]): Array<{
11
+ start: number;
12
+ end: number;
13
+ }>;
14
+ /** 0-based line index for the line containing `offset` (offset clamped to [0, length]). */
15
+ export declare function offsetToLineIndex(text: string, offset: number): number;
16
+ export declare function lineAtIndex(text: string, lineIndex: number): string;
17
+ //# sourceMappingURL=code-browser-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-browser-search.d.ts","sourceRoot":"","sources":["../src/code-browser-search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,GACf,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA4BvC;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAInE"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Whole-document search: tokens must appear in order as case-insensitive substrings,
3
+ * but may span multiple lines (any offsets in `text`).
4
+ */
5
+ const MAX_ORDERED_SPANS = 400;
6
+ /**
7
+ * Returns every minimal span [start, end) where each non-empty token appears in order
8
+ * in `text` (case-insensitive). Spans may overlap; iteration advances by one code unit
9
+ * from the start of the previous match.
10
+ */
11
+ export function findOrderedTokenSpans(text, tokens) {
12
+ const parts = tokens.map((t) => t.trim()).filter(Boolean);
13
+ if (parts.length === 0)
14
+ return [];
15
+ const lower = text.toLowerCase();
16
+ const needle = parts.map((t) => t.toLowerCase());
17
+ const out = [];
18
+ let scan = 0;
19
+ let produced = 0;
20
+ while (scan < text.length && produced < MAX_ORDERED_SPANS) {
21
+ let pos = scan;
22
+ let first = -1;
23
+ let ok = true;
24
+ for (const tok of needle) {
25
+ if (tok.length === 0)
26
+ continue;
27
+ const idx = lower.indexOf(tok, pos);
28
+ if (idx < 0) {
29
+ ok = false;
30
+ break;
31
+ }
32
+ if (first < 0)
33
+ first = idx;
34
+ pos = idx + tok.length;
35
+ }
36
+ if (!ok || first < 0)
37
+ break;
38
+ out.push({ start: first, end: pos });
39
+ produced++;
40
+ scan = first + 1;
41
+ }
42
+ return out;
43
+ }
44
+ /** 0-based line index for the line containing `offset` (offset clamped to [0, length]). */
45
+ export function offsetToLineIndex(text, offset) {
46
+ const o = Math.max(0, Math.min(offset, text.length));
47
+ let line = 0;
48
+ for (let i = 0; i < o; i++) {
49
+ if (text[i] === "\n")
50
+ line++;
51
+ }
52
+ return line;
53
+ }
54
+ export function lineAtIndex(text, lineIndex) {
55
+ const lines = text.split("\n");
56
+ if (lineIndex < 0 || lineIndex >= lines.length)
57
+ return "";
58
+ return lines[lineIndex] ?? "";
59
+ }
60
+ //# sourceMappingURL=code-browser-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-browser-search.js","sourceRoot":"","sources":["../src/code-browser-search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,MAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,MAAM,GAAG,GAA0C,EAAE,CAAC;IACtD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,iBAAiB,EAAE,CAAC;QAC1D,IAAI,GAAG,GAAG,IAAI,CAAC;QACf,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QACf,IAAI,EAAE,GAAG,IAAI,CAAC;QACd,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACpC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;gBACZ,EAAE,GAAG,KAAK,CAAC;gBACX,MAAM;YACR,CAAC;YACD,IAAI,KAAK,GAAG,CAAC;gBAAE,KAAK,GAAG,GAAG,CAAC;YAC3B,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,EAAE,IAAI,KAAK,GAAG,CAAC;YAAE,MAAM;QAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACrC,QAAQ,EAAE,CAAC;QACX,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAc;IAC5D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,IAAI,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC1D,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type CodeBrowserPageOptions = {
2
+ title?: string;
3
+ /** Repo-relative (or otherwise meaningful) path to display prominently in the toolbar. */
4
+ filePath?: string;
5
+ code: string;
6
+ language: string;
7
+ commentrayMarkdown: string;
8
+ includeMermaidRuntime?: boolean;
9
+ /** Highlight.js stylesheet base name (e.g. github, github-dark). */
10
+ hljsTheme?: string;
11
+ };
12
+ /**
13
+ * Static HTML shell for a minimal “code browser”: code + rendered commentray,
14
+ * draggable vertical splitter, togglable line wrap for the code pane, and
15
+ * token-in-line quick search (all non-whitespace tokens must appear on the same line).
16
+ */
17
+ export declare function renderCodeBrowserHtml(opts: CodeBrowserPageOptions): Promise<string>;
18
+ //# sourceMappingURL=code-browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-browser.d.ts","sourceRoot":"","sources":["../src/code-browser.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAgPF;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCzF"}
@@ -0,0 +1,241 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { escapeHtml } from "./html-utils.js";
5
+ import { renderFencedCode, renderMarkdownToHtml } from "./markdown-pipeline.js";
6
+ function extractPreCodeInner(html) {
7
+ const m = /<pre(?:\s[^>]*)?>\s*<code(?:\s[^>]*)?>([\s\S]*?)<\/code>\s*<\/pre>/i.exec(html.trim());
8
+ return m ? m[1] : escapeHtml(html);
9
+ }
10
+ /** One highlighted row per source line so in-page search can scroll to a line. */
11
+ async function renderCodeLineBlocks(code, language) {
12
+ const lines = code.split("\n");
13
+ const langAttr = escapeHtml(language);
14
+ const parts = [];
15
+ for (let i = 0; i < lines.length; i++) {
16
+ const line = lines[i] === "" ? " " : lines[i];
17
+ const fence = "```" + language + "\n" + line + "\n```\n";
18
+ const block = await renderFencedCode(fence);
19
+ const inner = extractPreCodeInner(block);
20
+ const num = i + 1;
21
+ parts.push(`<div class="code-line" id="code-line-${i}" data-line="${i}">` +
22
+ `<span class="ln" aria-hidden="true">${num}</span>` +
23
+ `<pre><code class="hljs language-${langAttr}">${inner}</code></pre>` +
24
+ `</div>`);
25
+ }
26
+ return parts.join("\n");
27
+ }
28
+ /** Split a repo-relative path into its directory prefix (with trailing slash) and basename. */
29
+ function splitFilePath(p) {
30
+ const normalized = p.replaceAll("\\", "/").replace(/^\/+/, "");
31
+ const idx = normalized.lastIndexOf("/");
32
+ if (idx < 0)
33
+ return { dir: "", base: normalized };
34
+ return { dir: normalized.slice(0, idx + 1), base: normalized.slice(idx + 1) };
35
+ }
36
+ function renderFilePathLabel(filePath, fallbackTitle) {
37
+ const shown = (filePath ?? "").trim();
38
+ if (!shown) {
39
+ return `<strong class="file-path file-path--title">${escapeHtml(fallbackTitle)}</strong>`;
40
+ }
41
+ const { dir, base } = splitFilePath(shown);
42
+ const dirHtml = dir
43
+ ? `<span class="file-path__dir">${escapeHtml(dir)}</span>`
44
+ : `<span class="file-path__dir file-path__dir--root" title="Repository root">/ </span>`;
45
+ return (`<strong class="file-path" title="${escapeHtml(shown)}">` +
46
+ dirHtml +
47
+ `<span class="file-path__base">${escapeHtml(base)}</span>` +
48
+ `</strong>`);
49
+ }
50
+ /** IIFE produced by `npm run build -w @commentray/render` (esbuild of `code-browser-client.ts`). */
51
+ function loadCodeBrowserClientBundle() {
52
+ const here = dirname(fileURLToPath(import.meta.url));
53
+ const inDist = join(here, "code-browser-client.bundle.js");
54
+ const fromSrc = join(here, "..", "dist", "code-browser-client.bundle.js");
55
+ for (const p of [inDist, fromSrc]) {
56
+ if (existsSync(p)) {
57
+ return readFileSync(p, "utf8");
58
+ }
59
+ }
60
+ throw new Error("Missing code-browser-client.bundle.js. Run `npm run build -w @commentray/render` to bundle the browser client.");
61
+ }
62
+ const CODE_BROWSER_STYLES = `
63
+ :root { color-scheme: light dark; }
64
+ * { box-sizing: border-box; }
65
+ body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
66
+ .toolbar {
67
+ display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 8px 12px;
68
+ border-bottom: 1px solid color-mix(in oklab, CanvasText 18%, Canvas);
69
+ font-size: 13px; flex: 0 0 auto;
70
+ }
71
+ .toolbar label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
72
+ .toolbar .file-path {
73
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
74
+ font-size: 13px; font-weight: 500;
75
+ display: inline-flex; align-items: baseline; gap: 0; margin-right: 4px;
76
+ max-width: 60vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
77
+ }
78
+ .toolbar .file-path__dir {
79
+ color: color-mix(in oklab, CanvasText 55%, Canvas);
80
+ }
81
+ .toolbar .file-path__dir--root { letter-spacing: 0; }
82
+ .toolbar .file-path__base {
83
+ color: CanvasText; font-weight: 600;
84
+ }
85
+ .toolbar .file-path--title { font-weight: 600; }
86
+ .toolbar .search-field {
87
+ display: inline-flex; align-items: center; gap: 6px; flex: 1 1 220px; min-width: 160px;
88
+ }
89
+ .toolbar .search-field input[type="search"] {
90
+ flex: 1; min-width: 0; padding: 4px 8px; font: inherit; border-radius: 6px;
91
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas); background: Canvas;
92
+ color: CanvasText;
93
+ }
94
+ .toolbar button {
95
+ font: inherit; padding: 4px 10px; border-radius: 6px; cursor: pointer;
96
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas); background: color-mix(in oklab, CanvasText 6%, Canvas);
97
+ color: CanvasText;
98
+ }
99
+ .search-results {
100
+ flex: 0 0 auto; max-height: 160px; overflow: auto; padding: 6px 12px 8px;
101
+ border-bottom: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
102
+ font-size: 12px;
103
+ }
104
+ .search-results[hidden] { display: none !important; }
105
+ .search-results .hint { opacity: 0.75; margin-bottom: 6px; }
106
+ .search-results button.hit {
107
+ display: block; width: 100%; text-align: left; margin: 2px 0; padding: 6px 8px;
108
+ border-radius: 6px; border: 1px solid color-mix(in oklab, CanvasText 14%, Canvas);
109
+ background: color-mix(in oklab, CanvasText 5%, Canvas); color: CanvasText; cursor: pointer;
110
+ font: inherit;
111
+ }
112
+ .search-results button.hit:hover { background: color-mix(in oklab, CanvasText 10%, Canvas); }
113
+ .search-results button.hit .meta { opacity: 0.8; font-size: 11px; }
114
+ .search-results button.hit .src-tag { opacity: 0.75; font-weight: 500; font-size: 10px; }
115
+ .search-results button.hit .snippet { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; font-size: 11px; white-space: pre-wrap; word-break: break-word; margin-top: 2px; }
116
+ .shell { display: flex; flex-direction: row; flex: 1; min-height: 0; }
117
+ .pane--code {
118
+ flex: 0 0 50%;
119
+ min-width: 120px; overflow: auto; padding: 12px 16px;
120
+ border-right: 1px solid color-mix(in oklab, CanvasText 15%, Canvas);
121
+ }
122
+ .pane--code .code-line {
123
+ display: grid; grid-template-columns: auto 1fr; column-gap: 12px; align-items: start;
124
+ }
125
+ .pane--code .code-line pre { margin: 0; min-width: 0; }
126
+ .pane--code .code-line .ln {
127
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
128
+ font-variant-numeric: tabular-nums;
129
+ text-align: right; user-select: none; -webkit-user-select: none;
130
+ color: color-mix(in oklab, CanvasText 45%, Canvas);
131
+ padding-right: 8px;
132
+ border-right: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
133
+ min-width: 3ch;
134
+ }
135
+ .pane--code .code-line:target .ln,
136
+ .pane--code .code-line:hover .ln {
137
+ color: color-mix(in oklab, CanvasText 75%, Canvas);
138
+ }
139
+ .pane--code.wrap .code-line pre, .pane--code.wrap .code-line pre code {
140
+ white-space: pre-wrap; word-break: break-word;
141
+ }
142
+ .pane--code:not(.wrap) .code-line pre, .pane--code:not(.wrap) .code-line pre code {
143
+ white-space: pre;
144
+ }
145
+ .gutter {
146
+ flex: 0 0 8px; cursor: col-resize; background: color-mix(in oklab, CanvasText 12%, Canvas);
147
+ position: relative;
148
+ }
149
+ .gutter:hover { background: color-mix(in oklab, CanvasText 22%, Canvas); }
150
+ .gutter::after {
151
+ content: ""; position: absolute; top: 0; bottom: 0; left: -4px; right: -4px;
152
+ }
153
+ .pane--doc {
154
+ flex: 1 1 auto; min-width: 120px; overflow: auto; padding: 12px 16px;
155
+ }
156
+ .pane--doc { font-size: 15px; line-height: 1.45; }
157
+ .pane--doc img { max-width: 100%; height: auto; }
158
+ .pane h2.pane-title { margin: 0 0 10px; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75; }
159
+ .app { display: flex; flex-direction: column; height: 100vh; }
160
+ `;
161
+ function buildCodeBrowserPageHtml(p) {
162
+ const { title, filePathHtml, codeHtml, commentrayHtml, rawCodeB64, rawMdB64, hljs, hljsDark, mermaidScript, } = p;
163
+ return `<!doctype html>
164
+ <html lang="en">
165
+ <head>
166
+ <meta charset="utf-8" />
167
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
168
+ <title>${escapeHtml(title)}</title>
169
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/${escapeHtml(hljs)}.min.css" media="(prefers-color-scheme: light)" />
170
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/${escapeHtml(hljsDark)}.min.css" media="(prefers-color-scheme: dark)" />
171
+ <style>
172
+ ${CODE_BROWSER_STYLES}
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <div class="app">
177
+ <header class="toolbar" aria-label="View options">
178
+ ${filePathHtml}
179
+ <span class="search-field">
180
+ <label for="search-q">Search</label>
181
+ <input type="search" id="search-q" placeholder="Whole source (ordered tokens + fuzzy lines)…" autocomplete="off" spellcheck="false" />
182
+ <button type="button" id="search-clear" title="Clear search">Clear</button>
183
+ </span>
184
+ <label><input type="checkbox" id="wrap-lines" /> Wrap code lines</label>
185
+ </header>
186
+ <div class="search-results" id="search-results" hidden aria-live="polite"></div>
187
+ <div class="shell" id="shell">
188
+ <section class="pane--code" id="code-pane" aria-label="Source code" data-raw-code-b64="${escapeHtml(rawCodeB64)}" data-raw-md-b64="${escapeHtml(rawMdB64)}">
189
+ <h2 class="pane-title">Code</h2>
190
+ ${codeHtml}
191
+ </section>
192
+ <div class="gutter" id="gutter" role="separator" aria-orientation="vertical" aria-label="Resize panes"></div>
193
+ <section class="pane--doc commentray" id="doc-pane" aria-label="Commentray">
194
+ <h2 class="pane-title">Commentray</h2>
195
+ ${commentrayHtml}
196
+ </section>
197
+ </div>
198
+ </div>
199
+ <script>
200
+ ${loadCodeBrowserClientBundle()}
201
+ </script>
202
+ ${mermaidScript}
203
+ </body>
204
+ </html>`;
205
+ }
206
+ /**
207
+ * Static HTML shell for a minimal “code browser”: code + rendered commentray,
208
+ * draggable vertical splitter, togglable line wrap for the code pane, and
209
+ * token-in-line quick search (all non-whitespace tokens must appear on the same line).
210
+ */
211
+ export async function renderCodeBrowserHtml(opts) {
212
+ const [codeHtml, commentrayHtml] = await Promise.all([
213
+ renderCodeLineBlocks(opts.code, opts.language),
214
+ renderMarkdownToHtml(opts.commentrayMarkdown),
215
+ ]);
216
+ const rawCodeB64 = Buffer.from(opts.code, "utf8").toString("base64");
217
+ const rawMdB64 = Buffer.from(opts.commentrayMarkdown, "utf8").toString("base64");
218
+ const title = opts.title ?? opts.filePath ?? "Commentray";
219
+ const filePathHtml = renderFilePathLabel(opts.filePath, title);
220
+ const hljs = opts.hljsTheme ?? "github";
221
+ const hljsDark = opts.hljsTheme?.includes("dark") ? opts.hljsTheme : "github-dark";
222
+ const mermaidScript = opts.includeMermaidRuntime
223
+ ? `<script type="module">
224
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
225
+ mermaid.initialize({ startOnLoad: true, securityLevel: "strict" });
226
+ mermaid.run({ querySelector: ".mermaid" });
227
+ </script>`
228
+ : "";
229
+ return buildCodeBrowserPageHtml({
230
+ title,
231
+ filePathHtml,
232
+ codeHtml,
233
+ commentrayHtml,
234
+ rawCodeB64,
235
+ rawMdB64,
236
+ hljs,
237
+ hljsDark,
238
+ mermaidScript,
239
+ });
240
+ }
241
+ //# sourceMappingURL=code-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-browser.js","sourceRoot":"","sources":["../src/code-browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Escape text for safe inclusion in HTML attributes or text nodes. */
2
+ export declare function escapeHtml(text: string): string;
3
+ //# sourceMappingURL=html-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-utils.d.ts","sourceRoot":"","sources":["../src/html-utils.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
@@ -0,0 +1,10 @@
1
+ /** Escape text for safe inclusion in HTML attributes or text nodes. */
2
+ export function escapeHtml(text) {
3
+ return text
4
+ .replaceAll("&", "&amp;")
5
+ .replaceAll("<", "&lt;")
6
+ .replaceAll(">", "&gt;")
7
+ .replaceAll('"', "&quot;")
8
+ .replaceAll("'", "&#39;");
9
+ }
10
+ //# sourceMappingURL=html-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-utils.js","sourceRoot":"","sources":["../src/html-utils.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI;SACR,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC"}