@commentray/render 0.0.5 → 0.0.6
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/dist/browse-page-slug.d.ts +6 -0
- package/dist/browse-page-slug.d.ts.map +1 -0
- package/dist/browse-page-slug.js +9 -0
- package/dist/browse-page-slug.js.map +1 -0
- package/dist/build-commentray-nav-search.d.ts +11 -5
- package/dist/build-commentray-nav-search.d.ts.map +1 -1
- package/dist/build-commentray-nav-search.js +11 -11
- package/dist/build-commentray-nav-search.js.map +1 -1
- package/dist/code-browser-block-rays.d.ts +48 -0
- package/dist/code-browser-block-rays.d.ts.map +1 -0
- package/dist/code-browser-block-rays.js +95 -0
- package/dist/code-browser-block-rays.js.map +1 -0
- package/dist/code-browser-client.bundle.js +12 -12
- package/dist/code-browser-client.js +388 -70
- package/dist/code-browser-client.js.map +1 -1
- package/dist/code-browser-pair-nav.d.ts +23 -0
- package/dist/code-browser-pair-nav.d.ts.map +1 -0
- package/dist/code-browser-pair-nav.js +59 -0
- package/dist/code-browser-pair-nav.js.map +1 -0
- package/dist/code-browser-search.d.ts +45 -0
- package/dist/code-browser-search.d.ts.map +1 -1
- package/dist/code-browser-search.js +89 -0
- package/dist/code-browser-search.js.map +1 -1
- package/dist/code-browser.d.ts +22 -1
- package/dist/code-browser.d.ts.map +1 -1
- package/dist/code-browser.js +342 -103
- package/dist/code-browser.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown-pipeline.d.ts.map +1 -1
- package/dist/markdown-pipeline.js +2 -1
- package/dist/markdown-pipeline.js.map +1 -1
- package/dist/mermaid-runtime-html.d.ts.map +1 -1
- package/dist/mermaid-runtime-html.js +3 -1
- package/dist/mermaid-runtime-html.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { FuzzySearcher, PrefixSearcher, Query, SearcherFactory, SubstringSearcher, } from "@m31coding/fuzzy-search";
|
|
2
|
+
import { activeBlockIdForViewport, clampViewportYToGutterLocal, codeLineDomIndex0, gutterRayBezierPaths, sortBlockLinksBySource, } from "./code-browser-block-rays.js";
|
|
2
3
|
import { mirroredScrollTop, pickCommentrayLineForSourceScroll, pickSourceLine0ForCommentrayScroll, } from "./code-browser-scroll-sync.js";
|
|
3
4
|
import { decodeBase64Utf8 } from "./code-browser-encoding.js";
|
|
4
5
|
import { readEmbeddedRawB64Strings } from "./code-browser-embedded-payload.js";
|
|
5
|
-
import { escapeHtmlHighlightingSearchTokens, findOrderedTokenSpans, lineAtIndex, offsetToLineIndex, } from "./code-browser-search.js";
|
|
6
|
+
import { escapeHtmlHighlightingSearchTokens, filterPairsByDocumentedTreeQuery, findOrderedTokenSpans, lineAtIndex, offsetToLineIndex, pathRowsFromDocumentedPairs, tokenizeQuery, uniqueSourceFilePreviewRows, } from "./code-browser-search.js";
|
|
7
|
+
import { findDocumentedPair, isSameDocumentedPair, normPosixPath, resolveStaticBrowseHref, } from "./code-browser-pair-nav.js";
|
|
6
8
|
import { readWebStorageItem, writeWebStorageItem } from "./code-browser-web-storage.js";
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
+
function runMermaidOnFreshDocNodes(docBody) {
|
|
10
|
+
if (typeof globalThis.location !== "undefined" && globalThis.location.protocol === "file:")
|
|
11
|
+
return;
|
|
12
|
+
const nodes = docBody.querySelectorAll(".mermaid");
|
|
13
|
+
if (nodes.length === 0)
|
|
14
|
+
return;
|
|
15
|
+
const m = globalThis
|
|
16
|
+
.commentrayMermaid;
|
|
17
|
+
if (!m)
|
|
18
|
+
return;
|
|
19
|
+
const list = Array.from(nodes);
|
|
20
|
+
void m.run({ nodes: list }).catch(() => { });
|
|
9
21
|
}
|
|
10
22
|
function clamp(n, lo, hi) {
|
|
11
23
|
return Math.max(lo, Math.min(hi, n));
|
|
@@ -154,6 +166,35 @@ function searchResultsInnerHtml(scope, combined, tokens, ctx) {
|
|
|
154
166
|
}
|
|
155
167
|
return buf.join("");
|
|
156
168
|
}
|
|
169
|
+
function emptyBrowsePreviewHint(scope, rowCount, totalUnique, usedIndexFallback) {
|
|
170
|
+
if (scope === "full") {
|
|
171
|
+
return "Documented source for this page. Type to search.";
|
|
172
|
+
}
|
|
173
|
+
if (usedIndexFallback) {
|
|
174
|
+
return "Documented source on this page. Type to search the index when it is available.";
|
|
175
|
+
}
|
|
176
|
+
if (totalUnique > rowCount) {
|
|
177
|
+
return `Indexed source files (${String(rowCount)} of ${String(totalUnique)} shown). Type to search.`;
|
|
178
|
+
}
|
|
179
|
+
return `Indexed source files (${String(totalUnique)}). Type to search.`;
|
|
180
|
+
}
|
|
181
|
+
function emptySearchBrowsePreviewInnerHtml(hint, rows, ctx) {
|
|
182
|
+
const tokens = [];
|
|
183
|
+
const buf = [`<div class="hint">${escapeHtmlText(hint)}</div>`];
|
|
184
|
+
const hits = rows.map((r, i) => ({
|
|
185
|
+
kind: "path",
|
|
186
|
+
line: i,
|
|
187
|
+
text: r.sourcePath,
|
|
188
|
+
score: 1000,
|
|
189
|
+
source: "ordered",
|
|
190
|
+
spPath: r.sourcePath,
|
|
191
|
+
crPath: r.commentrayPath,
|
|
192
|
+
}));
|
|
193
|
+
for (const h of hits) {
|
|
194
|
+
buf.push(searchHitButtonHtml(h, tokens, ctx));
|
|
195
|
+
}
|
|
196
|
+
return buf.join("");
|
|
197
|
+
}
|
|
157
198
|
function scrollDocToMarkdownLine0(docScrollEl, line0, mdLineCount) {
|
|
158
199
|
const el = docScrollEl.querySelector(`#commentray-md-line-${String(line0)}`);
|
|
159
200
|
if (el instanceof HTMLElement) {
|
|
@@ -169,22 +210,19 @@ function scrollDocToMarkdownLine0(docScrollEl, line0, mdLineCount) {
|
|
|
169
210
|
const maxScroll = docScrollEl.scrollHeight - docScrollEl.clientHeight;
|
|
170
211
|
docScrollEl.scrollTo({ top: ratio * Math.max(0, maxScroll), behavior: "smooth" });
|
|
171
212
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (p.staticBrowseUrl?.trim()) {
|
|
177
|
-
const u = new URL(p.staticBrowseUrl.trim(), globalThis.location.href);
|
|
213
|
+
function navigateToDocumentedPair(pair, mdLine0) {
|
|
214
|
+
if (pair.staticBrowseUrl?.trim()) {
|
|
215
|
+
const href = resolveStaticBrowseHref(pair.staticBrowseUrl.trim(), globalThis.location.pathname, globalThis.location.origin);
|
|
216
|
+
const u = new URL(href);
|
|
178
217
|
if (mdLine0 !== null && mdLine0 >= 0)
|
|
179
218
|
u.hash = `commentray-md-line-${String(mdLine0)}`;
|
|
180
|
-
globalThis.
|
|
219
|
+
globalThis.location.assign(u.toString());
|
|
181
220
|
return;
|
|
182
221
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
globalThis.open(url, "_blank", "noopener,noreferrer");
|
|
222
|
+
const gh = (pair.commentrayOnGithub ?? "").trim();
|
|
223
|
+
if (gh.length > 0) {
|
|
224
|
+
const url = mdLine0 !== null && mdLine0 >= 0 ? `${gh}#L${String(mdLine0 + 1)}` : gh;
|
|
225
|
+
globalThis.location.assign(url);
|
|
188
226
|
}
|
|
189
227
|
}
|
|
190
228
|
function readSearchScopeFromShell(shell) {
|
|
@@ -243,18 +281,25 @@ function scrollCodeHitToView(line) {
|
|
|
243
281
|
el.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
244
282
|
}
|
|
245
283
|
function handlePathSearchHit(button, deps) {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
284
|
+
const hitCr = (button.getAttribute("data-cr-path") ?? "").trim();
|
|
285
|
+
const hitSp = (button.getAttribute("data-sp-path") ?? "").trim();
|
|
286
|
+
const pair = findDocumentedPair(deps.mutable.documentedPairs, hitCr, hitSp);
|
|
287
|
+
if (pair && isSameDocumentedPair(pair, deps.filePathLabel, deps.mutable.commentrayPathLabel)) {
|
|
250
288
|
deps.docScrollEl.scrollTo({ top: 0, behavior: "smooth" });
|
|
251
289
|
return;
|
|
252
290
|
}
|
|
253
|
-
|
|
291
|
+
if (pair)
|
|
292
|
+
navigateToDocumentedPair(pair, null);
|
|
254
293
|
}
|
|
255
294
|
function handleMdSearchHit(line, crHit, deps) {
|
|
256
|
-
|
|
257
|
-
|
|
295
|
+
const curCr = deps.mutable.commentrayPathLabel.trim();
|
|
296
|
+
const cr = crHit.trim();
|
|
297
|
+
if (cr.length > 0 && normPosixPath(cr) !== normPosixPath(curCr)) {
|
|
298
|
+
const pair = findDocumentedPair(deps.mutable.documentedPairs, cr, "");
|
|
299
|
+
if (pair) {
|
|
300
|
+
navigateToDocumentedPair(pair, line);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
258
303
|
return;
|
|
259
304
|
}
|
|
260
305
|
scrollDocToMarkdownLine0(deps.docScrollEl, line, deps.mutable.mdLines.length);
|
|
@@ -273,6 +318,36 @@ function handleSearchHitButtonClick(button, deps) {
|
|
|
273
318
|
}
|
|
274
319
|
handleMdSearchHit(line, crHit, deps);
|
|
275
320
|
}
|
|
321
|
+
/** Empty query + ArrowDown: browse preview HTML, or null when there is nothing to show. */
|
|
322
|
+
function emptyBrowsePreviewInnerHtml(scope, filePathLabel, mutable) {
|
|
323
|
+
const hitCtx = {
|
|
324
|
+
currentCommentrayPath: mutable.commentrayPathLabel,
|
|
325
|
+
currentSourcePath: filePathLabel,
|
|
326
|
+
};
|
|
327
|
+
if (scope === "full") {
|
|
328
|
+
const sp = filePathLabel.trim();
|
|
329
|
+
if (sp.length === 0)
|
|
330
|
+
return null;
|
|
331
|
+
const rows = [
|
|
332
|
+
{ sourcePath: sp, commentrayPath: mutable.commentrayPathLabel.trim() },
|
|
333
|
+
];
|
|
334
|
+
const hint = emptyBrowsePreviewHint("full", rows.length, rows.length, false);
|
|
335
|
+
return emptySearchBrowsePreviewInnerHtml(hint, rows, hitCtx);
|
|
336
|
+
}
|
|
337
|
+
const { rows, totalUnique } = uniqueSourceFilePreviewRows(mutable.documentedPairs);
|
|
338
|
+
if (rows.length > 0) {
|
|
339
|
+
const hint = emptyBrowsePreviewHint("commentray-and-paths", rows.length, totalUnique, false);
|
|
340
|
+
return emptySearchBrowsePreviewInnerHtml(hint, rows, hitCtx);
|
|
341
|
+
}
|
|
342
|
+
const sp = filePathLabel.trim();
|
|
343
|
+
if (sp.length === 0)
|
|
344
|
+
return null;
|
|
345
|
+
const fb = [
|
|
346
|
+
{ sourcePath: sp, commentrayPath: mutable.commentrayPathLabel.trim() },
|
|
347
|
+
];
|
|
348
|
+
const hint = emptyBrowsePreviewHint("commentray-and-paths", fb.length, fb.length, true);
|
|
349
|
+
return emptySearchBrowsePreviewInnerHtml(hint, fb, hitCtx);
|
|
350
|
+
}
|
|
276
351
|
function wireSearchUi(ctx) {
|
|
277
352
|
const { scope, filePathLabel, mutable, rawCode, searchInput, searchClear, searchResults, docScrollEl, } = ctx;
|
|
278
353
|
let debounceTimer;
|
|
@@ -283,6 +358,13 @@ function wireSearchUi(ctx) {
|
|
|
283
358
|
searchResults.innerHTML = "";
|
|
284
359
|
searchResults.hidden = true;
|
|
285
360
|
}
|
|
361
|
+
function renderEmptyBrowsePreview() {
|
|
362
|
+
const html = emptyBrowsePreviewInnerHtml(scope, filePathLabel, mutable);
|
|
363
|
+
if (html === null)
|
|
364
|
+
return;
|
|
365
|
+
searchResults.hidden = false;
|
|
366
|
+
searchResults.innerHTML = html;
|
|
367
|
+
}
|
|
286
368
|
function runSearch() {
|
|
287
369
|
const tokens = tokenizeQuery(searchInput.value);
|
|
288
370
|
if (tokens.length === 0) {
|
|
@@ -308,7 +390,7 @@ function wireSearchUi(ctx) {
|
|
|
308
390
|
currentSourcePath: filePathLabel,
|
|
309
391
|
});
|
|
310
392
|
}
|
|
311
|
-
const hitClickDeps = { mutable, docScrollEl };
|
|
393
|
+
const hitClickDeps = { mutable, docScrollEl, filePathLabel };
|
|
312
394
|
searchResults.addEventListener("click", (ev) => {
|
|
313
395
|
const hit = findSearchHitButton(ev.target, searchResults);
|
|
314
396
|
if (!hit)
|
|
@@ -319,6 +401,14 @@ function wireSearchUi(ctx) {
|
|
|
319
401
|
clearTimeout(debounceTimer);
|
|
320
402
|
debounceTimer = setTimeout(runSearch, 200);
|
|
321
403
|
});
|
|
404
|
+
searchInput.addEventListener("keydown", (e) => {
|
|
405
|
+
if (e.key !== "ArrowDown")
|
|
406
|
+
return;
|
|
407
|
+
if (tokenizeQuery(searchInput.value).length > 0)
|
|
408
|
+
return;
|
|
409
|
+
renderEmptyBrowsePreview();
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
});
|
|
322
412
|
searchClear.addEventListener("click", clearSearch);
|
|
323
413
|
document.addEventListener("keydown", (e) => {
|
|
324
414
|
if (e.key !== "Escape")
|
|
@@ -477,15 +567,151 @@ function wireProportionalScrollSync(codePane, docPane) {
|
|
|
477
567
|
codePane.scrollTop = mirroredScrollTop(docPane.scrollTop, docPane.scrollHeight, docPane.clientHeight, codePane.scrollHeight, codePane.clientHeight);
|
|
478
568
|
});
|
|
479
569
|
}
|
|
570
|
+
function centerYInViewport(el) {
|
|
571
|
+
const r = el.getBoundingClientRect();
|
|
572
|
+
return (r.top + r.bottom) / 2;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Vertical anchor for gutter rays on the source side. Must match the **numbered row** the reader
|
|
576
|
+
* sees: the full `.code-line` row (line number + highlighted code) shares one grid row with
|
|
577
|
+
* aligned line-heights. Measuring only `pre code` can shift Y (hljs spans, sub-pixel layout) so
|
|
578
|
+
* rays sit above the line labels; the row’s geometric center tracks `lines:a-b` anchors reliably.
|
|
579
|
+
*/
|
|
580
|
+
function codeLineHighlightCenterYViewport(lineEl) {
|
|
581
|
+
return centerYInViewport(lineEl);
|
|
582
|
+
}
|
|
583
|
+
function commentaryBandEndYViewport(docScrollEl, next, docTop) {
|
|
584
|
+
if (next) {
|
|
585
|
+
const nextEl = document.getElementById(`commentray-block-${next.id}`);
|
|
586
|
+
return nextEl ? nextEl.getBoundingClientRect().top - 3 : centerYInViewport(docTop);
|
|
587
|
+
}
|
|
588
|
+
const dr = docScrollEl.getBoundingClientRect();
|
|
589
|
+
let bottom = dr.bottom - 4;
|
|
590
|
+
const lastKid = docScrollEl.children[docScrollEl.children.length - 1];
|
|
591
|
+
if (lastKid)
|
|
592
|
+
bottom = Math.min(bottom, lastKid.getBoundingClientRect().bottom - 4);
|
|
593
|
+
return bottom;
|
|
594
|
+
}
|
|
595
|
+
function subscribeBlockRayRedraw(gutter, codePane, docScrollEl, scheduleDraw) {
|
|
596
|
+
const onScrollOrResize = () => scheduleDraw();
|
|
597
|
+
codePane.addEventListener("scroll", onScrollOrResize, { passive: true });
|
|
598
|
+
docScrollEl.addEventListener("scroll", onScrollOrResize, { passive: true });
|
|
599
|
+
globalThis.addEventListener("resize", onScrollOrResize);
|
|
600
|
+
const ro = new ResizeObserver(onScrollOrResize);
|
|
601
|
+
ro.observe(gutter);
|
|
602
|
+
ro.observe(codePane);
|
|
603
|
+
ro.observe(docScrollEl);
|
|
604
|
+
const shell = gutter.parentElement;
|
|
605
|
+
if (shell)
|
|
606
|
+
ro.observe(shell);
|
|
607
|
+
}
|
|
608
|
+
function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based) {
|
|
609
|
+
const links = getLinks();
|
|
610
|
+
const sorted = sortBlockLinksBySource(links);
|
|
611
|
+
const gutterRect = gutter.getBoundingClientRect();
|
|
612
|
+
const w = gutterRect.width;
|
|
613
|
+
const h = gutterRect.height;
|
|
614
|
+
if (w <= 0 || h <= 0 || sorted.length === 0) {
|
|
615
|
+
svg.replaceChildren();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const activeId = activeBlockIdForViewport(links, probeTopSourceLine1Based());
|
|
619
|
+
svg.setAttribute("viewBox", `0 0 ${String(w)} ${String(h)}`);
|
|
620
|
+
svg.setAttribute("preserveAspectRatio", "none");
|
|
621
|
+
const parts = [];
|
|
622
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
623
|
+
const link = sorted[i];
|
|
624
|
+
if (!link)
|
|
625
|
+
continue;
|
|
626
|
+
const next = sorted[i + 1];
|
|
627
|
+
const i0 = codeLineDomIndex0(link.sourceStart);
|
|
628
|
+
const i1 = codeLineDomIndex0(link.sourceEnd);
|
|
629
|
+
const codeTop = document.getElementById(`code-line-${String(i0)}`);
|
|
630
|
+
const codeBot = document.getElementById(`code-line-${String(i1)}`);
|
|
631
|
+
const docTop = document.getElementById(`commentray-block-${link.id}`);
|
|
632
|
+
if (!codeTop || !codeBot || !docTop)
|
|
633
|
+
continue;
|
|
634
|
+
const docEndYViewport = commentaryBandEndYViewport(docScrollEl, next, docTop);
|
|
635
|
+
const yCodeTop = codeLineHighlightCenterYViewport(codeTop);
|
|
636
|
+
const yCodeBot = codeLineHighlightCenterYViewport(codeBot);
|
|
637
|
+
const yDocTop = docTop.getBoundingClientRect().top + 2;
|
|
638
|
+
const yDocEnd = Math.max(docEndYViewport, yDocTop + 4);
|
|
639
|
+
const c0 = clampViewportYToGutterLocal(yCodeTop, gutterRect.top, h);
|
|
640
|
+
const c1 = clampViewportYToGutterLocal(yDocTop, gutterRect.top, h);
|
|
641
|
+
const c2 = clampViewportYToGutterLocal(yCodeBot, gutterRect.top, h);
|
|
642
|
+
const c3 = clampViewportYToGutterLocal(yDocEnd, gutterRect.top, h);
|
|
643
|
+
const strokeClass = link.id === activeId ? "gutter__rays-path gutter__rays-path--active" : "gutter__rays-path";
|
|
644
|
+
const trailClass = `${strokeClass} gutter__rays-path--trail`;
|
|
645
|
+
const topPaths = gutterRayBezierPaths(0, c0.y, w, c1.y, {
|
|
646
|
+
tension: 0.38,
|
|
647
|
+
clipStart: c0.clipped,
|
|
648
|
+
clipEnd: c1.clipped,
|
|
649
|
+
});
|
|
650
|
+
const botPaths = gutterRayBezierPaths(0, c2.y, w, c3.y, {
|
|
651
|
+
tension: 0.38,
|
|
652
|
+
clipStart: c2.clipped,
|
|
653
|
+
clipEnd: c3.clipped,
|
|
654
|
+
});
|
|
655
|
+
const topExtra = topPaths.dotted ? `<path class="${trailClass}" d="${topPaths.dotted}" />` : "";
|
|
656
|
+
const botExtra = botPaths.dotted ? `<path class="${trailClass}" d="${botPaths.dotted}" />` : "";
|
|
657
|
+
parts.push(`<g class="gutter__rays-block" data-commentray-block="${escapeHtmlText(link.id)}">` +
|
|
658
|
+
`<path class="${strokeClass}" d="${topPaths.solid}" />` +
|
|
659
|
+
topExtra +
|
|
660
|
+
`<path class="${strokeClass}" d="${botPaths.solid}" />` +
|
|
661
|
+
botExtra +
|
|
662
|
+
`</g>`);
|
|
663
|
+
}
|
|
664
|
+
svg.innerHTML = parts.join("");
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Splines in the gutter between each block’s source range and its commentary band (dual pane,
|
|
668
|
+
* index-backed blocks). Emphasizes the block aligned with the current source viewport; clamps
|
|
669
|
+
* off-screen endpoints so readers see which way to scroll.
|
|
670
|
+
*/
|
|
671
|
+
function wireBlockRayConnectors(args) {
|
|
672
|
+
const { gutter, codePane, docScrollEl, getLinks, probeTopSourceLine1Based } = args;
|
|
673
|
+
const svgNs = "http://www.w3.org/2000/svg";
|
|
674
|
+
const host = document.createElement("div");
|
|
675
|
+
host.className = "gutter__rays";
|
|
676
|
+
host.setAttribute("aria-hidden", "true");
|
|
677
|
+
const svg = document.createElementNS(svgNs, "svg");
|
|
678
|
+
host.appendChild(svg);
|
|
679
|
+
gutter.appendChild(host);
|
|
680
|
+
let raf = 0;
|
|
681
|
+
function scheduleDraw() {
|
|
682
|
+
if (raf !== 0)
|
|
683
|
+
return;
|
|
684
|
+
raf = globalThis.requestAnimationFrame(() => {
|
|
685
|
+
raf = 0;
|
|
686
|
+
drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
subscribeBlockRayRedraw(gutter, codePane, docScrollEl, scheduleDraw);
|
|
690
|
+
scheduleDraw();
|
|
691
|
+
/** First paint can report gutter height 0 before flex layout settles; redraw after layout. */
|
|
692
|
+
globalThis.requestAnimationFrame(() => {
|
|
693
|
+
scheduleDraw();
|
|
694
|
+
globalThis.requestAnimationFrame(scheduleDraw);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
480
697
|
function isDocumentedPairNav(x) {
|
|
481
698
|
if (typeof x !== "object" || x === null)
|
|
482
699
|
return false;
|
|
483
700
|
const o = x;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
701
|
+
if (typeof o.sourcePath !== "string" || typeof o.commentrayPath !== "string")
|
|
702
|
+
return false;
|
|
703
|
+
if (o.staticBrowseUrl !== undefined && typeof o.staticBrowseUrl !== "string")
|
|
704
|
+
return false;
|
|
705
|
+
const sg = o.sourceOnGithub;
|
|
706
|
+
const cg = o.commentrayOnGithub;
|
|
707
|
+
const hasSg = typeof sg === "string";
|
|
708
|
+
const hasCg = typeof cg === "string";
|
|
709
|
+
if (hasSg !== hasCg)
|
|
710
|
+
return false;
|
|
711
|
+
const browseOk = typeof o.staticBrowseUrl === "string" && o.staticBrowseUrl.trim().length > 0;
|
|
712
|
+
if (!browseOk && !hasSg)
|
|
713
|
+
return false;
|
|
714
|
+
return true;
|
|
489
715
|
}
|
|
490
716
|
function pairsFromJsonArray(raw) {
|
|
491
717
|
const pairs = [];
|
|
@@ -497,27 +723,6 @@ function pairsFromJsonArray(raw) {
|
|
|
497
723
|
}
|
|
498
724
|
return pairs;
|
|
499
725
|
}
|
|
500
|
-
function pathRowsFromDocumentedPairs(pairs) {
|
|
501
|
-
const seen = new Set();
|
|
502
|
-
const out = [];
|
|
503
|
-
let line = 0;
|
|
504
|
-
for (const p of pairs) {
|
|
505
|
-
for (const seg of [p.sourcePath, p.commentrayPath]) {
|
|
506
|
-
const t = seg.trim();
|
|
507
|
-
if (!t || seen.has(t))
|
|
508
|
-
continue;
|
|
509
|
-
seen.add(t);
|
|
510
|
-
out.push({
|
|
511
|
-
kind: "path",
|
|
512
|
-
line: line++,
|
|
513
|
-
text: t,
|
|
514
|
-
spPath: p.sourcePath,
|
|
515
|
-
crPath: p.commentrayPath,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return out;
|
|
520
|
-
}
|
|
521
726
|
function commentrayLineRowFromNavJson(r) {
|
|
522
727
|
if (r.kind !== "commentrayLine")
|
|
523
728
|
return null;
|
|
@@ -612,6 +817,25 @@ function treeFileLinkLabel(pr, disambiguate) {
|
|
|
612
817
|
const stem = companionDocStem(pr.commentrayPath);
|
|
613
818
|
return stem !== "" && stem !== base ? `${base} · ${stem}` : base;
|
|
614
819
|
}
|
|
820
|
+
/** Prefer static hub browse page; optional SCM blob URL when the export has no `staticBrowseUrl`. */
|
|
821
|
+
function treeFileLinkHref(pr) {
|
|
822
|
+
const browse = (pr.staticBrowseUrl ?? "").trim();
|
|
823
|
+
if (browse.length > 0) {
|
|
824
|
+
return resolveStaticBrowseHref(browse, globalThis.location.pathname, globalThis.location.origin);
|
|
825
|
+
}
|
|
826
|
+
const gh = (pr.commentrayOnGithub ?? "").trim();
|
|
827
|
+
return gh.length > 0 ? gh : "#";
|
|
828
|
+
}
|
|
829
|
+
function treeFileLinkTitle(pr) {
|
|
830
|
+
const browse = (pr.staticBrowseUrl ?? "").trim();
|
|
831
|
+
if (browse.length > 0) {
|
|
832
|
+
return `${pr.sourcePath} — open this pair in the site viewer`;
|
|
833
|
+
}
|
|
834
|
+
if ((pr.commentrayOnGithub ?? "").trim().length > 0) {
|
|
835
|
+
return `${pr.sourcePath} — open companion commentray on the repository host`;
|
|
836
|
+
}
|
|
837
|
+
return pr.sourcePath;
|
|
838
|
+
}
|
|
615
839
|
function renderDocumentedTreeHtml(node) {
|
|
616
840
|
const keys = [...node.children.keys()].sort((a, b) => a.localeCompare(b));
|
|
617
841
|
if (keys.length === 0)
|
|
@@ -629,19 +853,23 @@ function renderDocumentedTreeHtml(node) {
|
|
|
629
853
|
const multi = ch.pairs.length > 1;
|
|
630
854
|
for (const pr of ch.pairs) {
|
|
631
855
|
const label = escapeHtmlText(treeFileLinkLabel(pr, multi));
|
|
632
|
-
const title = escapeHtmlText(
|
|
856
|
+
const title = escapeHtmlText(treeFileLinkTitle(pr));
|
|
857
|
+
const href = escapeHtmlText(treeFileLinkHref(pr));
|
|
858
|
+
const useSiteBrowse = (pr.staticBrowseUrl?.trim() ?? "").length > 0;
|
|
859
|
+
const external = useSiteBrowse ? "" : ' target="_blank" rel="noopener noreferrer"';
|
|
633
860
|
lis.push(`<li><div class="tree-file">` +
|
|
634
|
-
`<a class="tree-file-link" href="${
|
|
861
|
+
`<a class="tree-file-link" href="${href}"${external} title="${title}">${label}</a>` +
|
|
635
862
|
`</div></li>`);
|
|
636
863
|
}
|
|
637
864
|
}
|
|
638
865
|
}
|
|
639
866
|
return `<ul>${lis.join("")}</ul>`;
|
|
640
867
|
}
|
|
641
|
-
function renderDocumentedPairsIntoHost(treeHost, pairs) {
|
|
868
|
+
function renderDocumentedPairsIntoHost(treeHost, pairs, emptyBecauseFilter) {
|
|
642
869
|
if (pairs.length === 0) {
|
|
643
|
-
treeHost.innerHTML =
|
|
644
|
-
'<p class="nav-rail__doc-hub-hint" role="status">No
|
|
870
|
+
treeHost.innerHTML = emptyBecauseFilter
|
|
871
|
+
? '<p class="nav-rail__doc-hub-hint" role="status">No paths match this filter.</p>'
|
|
872
|
+
: '<p class="nav-rail__doc-hub-hint" role="status">No documented pairs in this export.</p>';
|
|
645
873
|
return;
|
|
646
874
|
}
|
|
647
875
|
const root = { children: new Map(), pairs: [] };
|
|
@@ -680,6 +908,7 @@ function loadDocumentedPairs(jsonUrl, embeddedB64) {
|
|
|
680
908
|
function wireDocumentedFilesTree() {
|
|
681
909
|
const hub = document.getElementById("documented-files-hub");
|
|
682
910
|
const treeHost = document.getElementById("documented-files-tree");
|
|
911
|
+
const filterInput = document.getElementById("documented-files-filter");
|
|
683
912
|
const shell = document.getElementById("shell");
|
|
684
913
|
if (!(hub instanceof HTMLDetailsElement) || !(treeHost instanceof HTMLElement)) {
|
|
685
914
|
return;
|
|
@@ -690,12 +919,23 @@ function wireDocumentedFilesTree() {
|
|
|
690
919
|
if (jsonUrl.length === 0 && embeddedB64.length === 0)
|
|
691
920
|
return;
|
|
692
921
|
const ensureLoaded = loadDocumentedPairs(jsonUrl, embeddedB64);
|
|
922
|
+
let cachedPairs = null;
|
|
923
|
+
function applyFilterAndRender() {
|
|
924
|
+
if (cachedPairs === null)
|
|
925
|
+
return;
|
|
926
|
+
const q = filterInput instanceof HTMLInputElement ? filterInput.value : "";
|
|
927
|
+
const pairs = filterPairsByDocumentedTreeQuery(cachedPairs, q);
|
|
928
|
+
const filterActive = q.trim().length > 0;
|
|
929
|
+
renderDocumentedPairsIntoHost(treeMount, pairs, filterActive && cachedPairs.length > 0 && pairs.length === 0);
|
|
930
|
+
}
|
|
693
931
|
async function hydrateTree() {
|
|
694
932
|
try {
|
|
695
933
|
const pairs = await ensureLoaded();
|
|
696
|
-
|
|
934
|
+
cachedPairs = pairs;
|
|
935
|
+
applyFilterAndRender();
|
|
697
936
|
}
|
|
698
937
|
catch {
|
|
938
|
+
cachedPairs = null;
|
|
699
939
|
treeMount.innerHTML =
|
|
700
940
|
'<p class="nav-rail__doc-hub-hint" role="alert">Could not load the file list.</p>';
|
|
701
941
|
}
|
|
@@ -705,6 +945,13 @@ function wireDocumentedFilesTree() {
|
|
|
705
945
|
return;
|
|
706
946
|
void hydrateTree();
|
|
707
947
|
});
|
|
948
|
+
if (filterInput instanceof HTMLInputElement) {
|
|
949
|
+
filterInput.addEventListener("input", () => {
|
|
950
|
+
if (!hub.open || cachedPairs === null)
|
|
951
|
+
return;
|
|
952
|
+
applyFilterAndRender();
|
|
953
|
+
});
|
|
954
|
+
}
|
|
708
955
|
}
|
|
709
956
|
function wireSplitter(storageSplit, shell, codePane, gutter, initialPct) {
|
|
710
957
|
let dragging = false;
|
|
@@ -805,7 +1052,46 @@ function initialCommentrayScopePathState(shell, scope, filePathLabel, commentray
|
|
|
805
1052
|
: [filePathLabel, commentrayPathLabel].filter((s) => s.trim().length > 0).join("\n");
|
|
806
1053
|
return { documentedPairs, pathRowsForOrdering, pathBlobWide };
|
|
807
1054
|
}
|
|
808
|
-
|
|
1055
|
+
/**
|
|
1056
|
+
* Fetched `commentray-nav-search.json` sometimes omits `staticBrowseUrl` on pairs; the hub embed
|
|
1057
|
+
* carries browse URLs from the same build — merge so search hits open `_site/browse/…`, not GitHub.
|
|
1058
|
+
*/
|
|
1059
|
+
function mergeFetchedDocumentedPairsWithEmbeddedBrowse(embedded, fetched) {
|
|
1060
|
+
if (fetched.length === 0)
|
|
1061
|
+
return embedded;
|
|
1062
|
+
if (embedded.length === 0)
|
|
1063
|
+
return fetched;
|
|
1064
|
+
const browseByCr = new Map();
|
|
1065
|
+
for (const p of embedded) {
|
|
1066
|
+
const b = (p.staticBrowseUrl ?? "").trim();
|
|
1067
|
+
if (b.length === 0)
|
|
1068
|
+
continue;
|
|
1069
|
+
browseByCr.set(normPosixPath(p.commentrayPath), b);
|
|
1070
|
+
}
|
|
1071
|
+
return fetched.map((p) => {
|
|
1072
|
+
const have = (p.staticBrowseUrl ?? "").trim();
|
|
1073
|
+
if (have.length > 0)
|
|
1074
|
+
return p;
|
|
1075
|
+
const fromEmb = browseByCr.get(normPosixPath(p.commentrayPath));
|
|
1076
|
+
if (fromEmb !== undefined && fromEmb.length > 0) {
|
|
1077
|
+
return { ...p, staticBrowseUrl: fromEmb };
|
|
1078
|
+
}
|
|
1079
|
+
return p;
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
function resolvedNavSearchJsonUrl(shell) {
|
|
1083
|
+
const raw = shell.getAttribute("data-nav-search-json-url")?.trim() ?? "";
|
|
1084
|
+
if (raw.length === 0)
|
|
1085
|
+
return "";
|
|
1086
|
+
try {
|
|
1087
|
+
return new URL(raw, globalThis.location.href).href;
|
|
1088
|
+
}
|
|
1089
|
+
catch {
|
|
1090
|
+
return raw;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function wireDualPaneNavSearchFetch(shell, embeddedPairs, indexState, mutable, rebuildSearcher, searchInput) {
|
|
1094
|
+
const navSearchUrl = resolvedNavSearchJsonUrl(shell);
|
|
809
1095
|
if (navSearchUrl.length === 0)
|
|
810
1096
|
return;
|
|
811
1097
|
void (async () => {
|
|
@@ -815,9 +1101,10 @@ function wireDualPaneNavSearchFetch(navSearchUrl, indexState, mutable, rebuildSe
|
|
|
815
1101
|
return;
|
|
816
1102
|
const doc = (await res.json());
|
|
817
1103
|
const fetched = pairsFromJsonArray(doc.documentedPairs);
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1104
|
+
const mergedPairs = mergeFetchedDocumentedPairsWithEmbeddedBrowse(embeddedPairs, fetched);
|
|
1105
|
+
if (mergedPairs.length > 0) {
|
|
1106
|
+
indexState.documentedPairs = mergedPairs;
|
|
1107
|
+
mutable.documentedPairs = mergedPairs;
|
|
821
1108
|
}
|
|
822
1109
|
const nr = rowsFromNavSearchJson(doc);
|
|
823
1110
|
if (nr.length === 0)
|
|
@@ -837,10 +1124,9 @@ function wireDualPaneNavSearchFetch(navSearchUrl, indexState, mutable, rebuildSe
|
|
|
837
1124
|
})();
|
|
838
1125
|
}
|
|
839
1126
|
function wireDualPaneMultiAngleAndScroll(args) {
|
|
840
|
-
const { codePane, docScrollEl, docBody, shell,
|
|
841
|
-
const activeLinks = { current: scrollLinks };
|
|
1127
|
+
const { codePane, docScrollEl, docBody, shell, scrollLinksRef, multiPayload, mutable, rebuildSearcher, searchInput, searchResults, } = args;
|
|
842
1128
|
if (multiPayload) {
|
|
843
|
-
wireBlockAwareScrollSync(codePane, docScrollEl, () =>
|
|
1129
|
+
wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current);
|
|
844
1130
|
const angleSel = document.getElementById("angle-select");
|
|
845
1131
|
if (angleSel && docBody) {
|
|
846
1132
|
angleSel.addEventListener("change", () => {
|
|
@@ -848,16 +1134,39 @@ function wireDualPaneMultiAngleAndScroll(args) {
|
|
|
848
1134
|
if (!a)
|
|
849
1135
|
return;
|
|
850
1136
|
docBody.innerHTML = decodeBase64Utf8(a.docInnerHtmlB64);
|
|
1137
|
+
runMermaidOnFreshDocNodes(docBody);
|
|
851
1138
|
mutable.rawMd = decodeBase64Utf8(a.rawMdB64);
|
|
852
1139
|
mutable.mdLines = mutable.rawMd.split("\n");
|
|
853
1140
|
mutable.commentrayPathLabel = a.commentrayPathForSearch;
|
|
854
1141
|
rebuildSearcher();
|
|
855
|
-
|
|
1142
|
+
scrollLinksRef.current = parseScrollBlockLinksFromShell(a.scrollBlockLinksB64);
|
|
856
1143
|
shell.setAttribute("data-scroll-block-links-b64", a.scrollBlockLinksB64);
|
|
857
1144
|
shell.setAttribute("data-search-commentray-path", a.commentrayPathForSearch);
|
|
1145
|
+
const docPathEl = document.getElementById("nav-rail-doc-path");
|
|
1146
|
+
if (docPathEl) {
|
|
1147
|
+
const path = a.commentrayPathForSearch.trim();
|
|
1148
|
+
docPathEl.textContent = path.length > 0 ? path : "—";
|
|
1149
|
+
if (path.length > 0)
|
|
1150
|
+
docPathEl.setAttribute("title", path);
|
|
1151
|
+
else
|
|
1152
|
+
docPathEl.removeAttribute("title");
|
|
1153
|
+
}
|
|
858
1154
|
const gh = document.getElementById("toolbar-commentray-github");
|
|
859
|
-
if (gh instanceof HTMLAnchorElement
|
|
860
|
-
|
|
1155
|
+
if (gh instanceof HTMLAnchorElement) {
|
|
1156
|
+
const browse = a.staticBrowseUrl?.trim() ?? "";
|
|
1157
|
+
if (browse.length > 0) {
|
|
1158
|
+
gh.href = resolveStaticBrowseHref(browse, globalThis.location.pathname, globalThis.location.origin);
|
|
1159
|
+
gh.removeAttribute("target");
|
|
1160
|
+
gh.setAttribute("rel", "noopener");
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
const ghu = a.commentrayOnGithubUrl?.trim();
|
|
1164
|
+
if (ghu) {
|
|
1165
|
+
gh.href = ghu;
|
|
1166
|
+
gh.target = "_blank";
|
|
1167
|
+
gh.setAttribute("rel", "noopener noreferrer");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
861
1170
|
}
|
|
862
1171
|
searchInput.value = "";
|
|
863
1172
|
searchResults.innerHTML = "";
|
|
@@ -866,8 +1175,8 @@ function wireDualPaneMultiAngleAndScroll(args) {
|
|
|
866
1175
|
}
|
|
867
1176
|
return;
|
|
868
1177
|
}
|
|
869
|
-
if (
|
|
870
|
-
wireBlockAwareScrollSync(codePane, docScrollEl, () =>
|
|
1178
|
+
if (scrollLinksRef.current.length > 0) {
|
|
1179
|
+
wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current);
|
|
871
1180
|
return;
|
|
872
1181
|
}
|
|
873
1182
|
wireProportionalScrollSync(codePane, docScrollEl);
|
|
@@ -896,6 +1205,7 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
896
1205
|
const rawCode = decodeBase64Utf8(rawCodeB64);
|
|
897
1206
|
const rawMd = decodeBase64Utf8(rawMdB64);
|
|
898
1207
|
const scrollLinks = parseScrollBlockLinksFromShell(shell.getAttribute("data-scroll-block-links-b64") || "");
|
|
1208
|
+
const scrollLinksRef = { current: scrollLinks };
|
|
899
1209
|
const { scope, filePathLabel, commentrayPathLabel } = readSearchScopeFromShell(shell);
|
|
900
1210
|
const pathInit = initialCommentrayScopePathState(shell, scope, filePathLabel, commentrayPathLabel);
|
|
901
1211
|
const indexState = {
|
|
@@ -934,8 +1244,7 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
934
1244
|
searchResults,
|
|
935
1245
|
docScrollEl,
|
|
936
1246
|
});
|
|
937
|
-
|
|
938
|
-
wireDualPaneNavSearchFetch(navSearchUrl, indexState, mutable, rebuildSearcher, searchInput);
|
|
1247
|
+
wireDualPaneNavSearchFetch(shell, pathInit.documentedPairs, indexState, mutable, rebuildSearcher, searchInput);
|
|
939
1248
|
const pct0 = parseFloat(readWebStorageItem(localStorage, STORAGE_SPLIT_PCT) || "50");
|
|
940
1249
|
const pct = clamp(Number.isFinite(pct0) ? pct0 : 50, 15, 85);
|
|
941
1250
|
codePane.style.flex = `0 0 ${pct}%`;
|
|
@@ -948,13 +1257,22 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
948
1257
|
docScrollEl,
|
|
949
1258
|
docBody,
|
|
950
1259
|
shell,
|
|
951
|
-
|
|
1260
|
+
scrollLinksRef,
|
|
952
1261
|
multiPayload,
|
|
953
1262
|
mutable,
|
|
954
1263
|
rebuildSearcher,
|
|
955
1264
|
searchInput,
|
|
956
1265
|
searchResults,
|
|
957
1266
|
});
|
|
1267
|
+
if (scrollLinksRef.current.length > 0) {
|
|
1268
|
+
wireBlockRayConnectors({
|
|
1269
|
+
gutter,
|
|
1270
|
+
codePane,
|
|
1271
|
+
docScrollEl,
|
|
1272
|
+
getLinks: () => scrollLinksRef.current,
|
|
1273
|
+
probeTopSourceLine1Based: () => probeCodeLine1FromViewport(codePane),
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
958
1276
|
wireDualPaneCommentrayLocationHash(docScrollEl, () => mutable.mdLines.length);
|
|
959
1277
|
}
|
|
960
1278
|
function main() {
|