@commentray/render 0.0.8 → 0.1.0
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/README.md +1 -1
- package/dist/code-browser-client.bundle.js +24 -11
- package/dist/code-browser-client.js +697 -100
- package/dist/code-browser-client.js.map +1 -1
- package/dist/code-browser-intro.css +187 -0
- package/dist/code-browser-wide-intro-controller.d.ts +4 -0
- package/dist/code-browser-wide-intro-controller.d.ts.map +1 -0
- package/dist/code-browser-wide-intro-controller.js +148 -0
- package/dist/code-browser-wide-intro-controller.js.map +1 -0
- package/dist/code-browser-wide-intro-layout.d.ts +3 -0
- package/dist/code-browser-wide-intro-layout.d.ts.map +1 -0
- package/dist/code-browser-wide-intro-layout.js +84 -0
- package/dist/code-browser-wide-intro-layout.js.map +1 -0
- package/dist/code-browser-wide-intro-steps.d.ts +11 -0
- package/dist/code-browser-wide-intro-steps.d.ts.map +1 -0
- package/dist/code-browser-wide-intro-steps.js +108 -0
- package/dist/code-browser-wide-intro-steps.js.map +1 -0
- package/dist/code-browser-wide-intro-ui.d.ts +14 -0
- package/dist/code-browser-wide-intro-ui.d.ts.map +1 -0
- package/dist/code-browser-wide-intro-ui.js +67 -0
- package/dist/code-browser-wide-intro-ui.js.map +1 -0
- package/dist/code-browser.d.ts.map +1 -1
- package/dist/code-browser.js +597 -42
- package/dist/code-browser.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdown-pipeline.d.ts +40 -3
- package/dist/markdown-pipeline.d.ts.map +1 -1
- package/dist/markdown-pipeline.js +110 -23
- package/dist/markdown-pipeline.js.map +1 -1
- package/dist/side-by-side-layout-css.d.ts +7 -0
- package/dist/side-by-side-layout-css.d.ts.map +1 -0
- package/dist/side-by-side-layout-css.js +113 -0
- package/dist/side-by-side-layout-css.js.map +1 -0
- package/dist/side-by-side-layout.embedded.d.ts +3 -0
- package/dist/side-by-side-layout.embedded.d.ts.map +1 -0
- package/dist/side-by-side-layout.embedded.js +3 -0
- package/dist/side-by-side-layout.embedded.js.map +1 -0
- package/dist/side-by-side.d.ts.map +1 -1
- package/dist/side-by-side.js +2 -5
- package/dist/side-by-side.js.map +1 -1
- package/package.json +4 -3
|
@@ -4,8 +4,9 @@ import { mirroredScrollTop, pickCommentrayLineForSourceScroll, pickSourceLine0Fo
|
|
|
4
4
|
import { decodeBase64Utf8 } from "./code-browser-encoding.js";
|
|
5
5
|
import { readEmbeddedRawB64Strings } from "./code-browser-embedded-payload.js";
|
|
6
6
|
import { escapeHtmlHighlightingSearchTokens, filterPairsByDocumentedTreeQuery, findOrderedTokenSpans, lineAtIndex, offsetToLineIndex, pathRowsFromDocumentedPairs, tokenizeQuery, uniqueSourceFilePreviewRows, } from "./code-browser-search.js";
|
|
7
|
-
import { findDocumentedPair, isHubRelativeStaticBrowseHref, isSameDocumentedPair, normPosixPath, resolveStaticBrowseHref, staticBrowseHrefForShellDataAttribute, } from "./code-browser-pair-nav.js";
|
|
7
|
+
import { findDocumentedPair, isHubRelativeStaticBrowseHref, isSameDocumentedPair, normPosixPath, resolveStaticBrowseHref, siteRootPathnameFromPathname, staticBrowseHrefForShellDataAttribute, } from "./code-browser-pair-nav.js";
|
|
8
8
|
import { COMMENTRAY_COLOR_THEME_STORAGE_KEY, applyCommentrayColorTheme, nextCommentrayColorThemeMode, parseCommentrayColorThemeMode, } from "./code-browser-color-theme.js";
|
|
9
|
+
import { wireWideModeIntroTour } from "./code-browser-wide-intro-controller.js";
|
|
9
10
|
import { readWebStorageItem, writeWebStorageItem } from "./code-browser-web-storage.js";
|
|
10
11
|
/**
|
|
11
12
|
* Hub pages emit `./browse/…` relative to the site root. From `/…/browse/current.html` the browser
|
|
@@ -104,9 +105,13 @@ function windowScrollRatio() {
|
|
|
104
105
|
const maxY = Math.max(0, root.scrollHeight - root.clientHeight);
|
|
105
106
|
return maxY > 0 ? clamp(root.scrollTop / maxY, 0, 1) : 0;
|
|
106
107
|
}
|
|
107
|
-
function applyDocToCodeFlipPlanImpl(codePane, _docPane, plan) {
|
|
108
|
+
function applyDocToCodeFlipPlanImpl(codePane, _docPane, plan, lineIdPrefix = "code-line-") {
|
|
109
|
+
const narrowSinglePane = globalThis.matchMedia(DUAL_MOBILE_SINGLE_PANE_MQ).matches;
|
|
108
110
|
if (plan.k === "block") {
|
|
109
|
-
const
|
|
111
|
+
const exact = codePane.querySelector(`#${lineIdPrefix}${String(plan.src0)}`);
|
|
112
|
+
const el = exact instanceof HTMLElement
|
|
113
|
+
? exact
|
|
114
|
+
: findAnchorAtOrAfter(sourceAnchorsFromPrefix(lineIdPrefix), plan.src0);
|
|
110
115
|
if (el) {
|
|
111
116
|
applyRevealChildInPane(codePane, el, 2);
|
|
112
117
|
}
|
|
@@ -119,6 +124,8 @@ function applyDocToCodeFlipPlanImpl(codePane, _docPane, plan) {
|
|
|
119
124
|
if (paneUsesInternalYScroll(codePane)) {
|
|
120
125
|
const maxC = Math.max(0, codePane.scrollHeight - codePane.clientHeight);
|
|
121
126
|
applyScrollTopClamped(codePane, plan.ratio * maxC);
|
|
127
|
+
if (narrowSinglePane)
|
|
128
|
+
applyWindowScrollRatio(plan.ratio);
|
|
122
129
|
}
|
|
123
130
|
else {
|
|
124
131
|
applyWindowScrollRatio(plan.ratio);
|
|
@@ -128,6 +135,10 @@ function applyDocToCodeFlipPlanImpl(codePane, _docPane, plan) {
|
|
|
128
135
|
const nextTop = mirroredScrollTop(plan.docTop, plan.docSH, plan.docCH, codePane.scrollHeight, codePane.clientHeight);
|
|
129
136
|
if (paneUsesInternalYScroll(codePane)) {
|
|
130
137
|
applyScrollTopClamped(codePane, nextTop);
|
|
138
|
+
if (narrowSinglePane) {
|
|
139
|
+
const denom = Math.max(1, codePane.scrollHeight - codePane.clientHeight);
|
|
140
|
+
applyWindowScrollRatio(clamp(nextTop / denom, 0, 1));
|
|
141
|
+
}
|
|
131
142
|
return;
|
|
132
143
|
}
|
|
133
144
|
const denom = Math.max(1, codePane.scrollHeight - codePane.clientHeight);
|
|
@@ -163,7 +174,12 @@ function applyCodeToDocFlipPlanImpl(_codePane, docPane, plan) {
|
|
|
163
174
|
applyWindowScrollRatio(clamp(nextTop / denom, 0, 1));
|
|
164
175
|
}
|
|
165
176
|
function buildDocToCodeFlipPlanBlockAware(docPane, getLinks) {
|
|
166
|
-
const winRatio =
|
|
177
|
+
const winRatio = paneUsesInternalYScroll(docPane)
|
|
178
|
+
? clamp(docPane.scrollTop / Math.max(1, docPane.scrollHeight - docPane.clientHeight), 0, 1)
|
|
179
|
+
: windowScrollRatio();
|
|
180
|
+
const pulledSrc0 = pulledSourceLine0FromPageBreak(docPane);
|
|
181
|
+
if (pulledSrc0 !== null)
|
|
182
|
+
return { k: "block", src0: pulledSrc0, winRatio };
|
|
167
183
|
const links = getLinks();
|
|
168
184
|
const mdLine0 = probeCommentrayLine0FromDoc(docPane);
|
|
169
185
|
const src0 = pickSourceLine0ForCommentrayScroll(links, mdLine0);
|
|
@@ -179,10 +195,10 @@ function buildDocToCodeFlipPlanBlockAware(docPane, getLinks) {
|
|
|
179
195
|
}
|
|
180
196
|
return { k: "mirrorW", ratio: winRatio };
|
|
181
197
|
}
|
|
182
|
-
function buildCodeToDocFlipPlanBlockAware(codePane, _docPane, getLinks) {
|
|
198
|
+
function buildCodeToDocFlipPlanBlockAware(codePane, _docPane, getLinks, lineIdPrefix = "code-line-") {
|
|
183
199
|
const winRatio = windowScrollRatio();
|
|
184
200
|
const links = getLinks();
|
|
185
|
-
const line1 = probeCodeLine1FromViewport(codePane);
|
|
201
|
+
const line1 = probeCodeLine1FromViewport(codePane, lineIdPrefix);
|
|
186
202
|
const mdLine0 = pickCommentrayLineForSourceScroll(links, line1);
|
|
187
203
|
if (mdLine0 === null) {
|
|
188
204
|
if (paneUsesInternalYScroll(codePane)) {
|
|
@@ -703,14 +719,45 @@ function rootScrollNearDocumentEnd(edgePx = 56) {
|
|
|
703
719
|
const maxY = Math.max(0, root.scrollHeight - root.clientHeight);
|
|
704
720
|
return maxY > 0 && root.scrollTop >= maxY - edgePx;
|
|
705
721
|
}
|
|
706
|
-
|
|
707
|
-
|
|
722
|
+
/** When the pane itself is the scrollport (dual desktop), mirror root “near end” behavior. */
|
|
723
|
+
function paneScrollNearEnd(pane, edgePx = 56) {
|
|
724
|
+
const maxY = Math.max(0, pane.scrollHeight - pane.clientHeight);
|
|
725
|
+
return maxY > 0 && pane.scrollTop >= maxY - edgePx;
|
|
726
|
+
}
|
|
727
|
+
function readCommentrayLine0FromAnchor(el) {
|
|
728
|
+
const lineAttr = el.getAttribute("data-commentray-line");
|
|
729
|
+
if (lineAttr === null || lineAttr === "")
|
|
730
|
+
return null;
|
|
731
|
+
return Number(lineAttr);
|
|
732
|
+
}
|
|
733
|
+
/** Greatest marker line whose anchor sits at or above viewport Y `y`. */
|
|
734
|
+
function bestCommentrayAnchorLine0AtOrAboveY(anchors, y) {
|
|
735
|
+
let best = 0;
|
|
736
|
+
for (const a of anchors) {
|
|
737
|
+
const line0 = readCommentrayLine0FromAnchor(a);
|
|
738
|
+
if (line0 === null)
|
|
739
|
+
continue;
|
|
740
|
+
if (a.getBoundingClientRect().top <= y + 1 + 1e-3)
|
|
741
|
+
best = line0;
|
|
742
|
+
else
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
return best;
|
|
746
|
+
}
|
|
747
|
+
function lastCommentrayAnchorLine0(anchors) {
|
|
748
|
+
const last = anchors[anchors.length - 1];
|
|
749
|
+
if (!last)
|
|
750
|
+
return 0;
|
|
751
|
+
return readCommentrayLine0FromAnchor(last) ?? 0;
|
|
752
|
+
}
|
|
753
|
+
function probeCodeLine1FromViewport(codePane, lineIdPrefix = "code-line-") {
|
|
754
|
+
const rows = codePane.querySelectorAll(`[id^="${lineIdPrefix}"]`);
|
|
708
755
|
if (rows.length === 0)
|
|
709
756
|
return 1;
|
|
710
757
|
if (!paneUsesInternalYScroll(codePane)) {
|
|
711
758
|
if (rootScrollNearDocumentEnd()) {
|
|
712
759
|
const last = rows[rows.length - 1];
|
|
713
|
-
const m = /^code-line-(\d+)$/.exec(last.id);
|
|
760
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(last.id);
|
|
714
761
|
if (m)
|
|
715
762
|
return Number(m[1]) + 1;
|
|
716
763
|
return rows.length;
|
|
@@ -723,7 +770,7 @@ function probeCodeLine1FromViewport(codePane) {
|
|
|
723
770
|
for (const el of rows) {
|
|
724
771
|
const r = el.getBoundingClientRect();
|
|
725
772
|
if (r.bottom > y - 1e-3) {
|
|
726
|
-
const m = /^code-line-(\d+)$/.exec(el.id);
|
|
773
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(el.id);
|
|
727
774
|
if (m)
|
|
728
775
|
return Number(m[1]) + 1;
|
|
729
776
|
return 1;
|
|
@@ -731,12 +778,19 @@ function probeCodeLine1FromViewport(codePane) {
|
|
|
731
778
|
}
|
|
732
779
|
return rows.length;
|
|
733
780
|
}
|
|
781
|
+
if (paneScrollNearEnd(codePane)) {
|
|
782
|
+
const last = rows[rows.length - 1];
|
|
783
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(last.id);
|
|
784
|
+
if (m)
|
|
785
|
+
return Number(m[1]) + 1;
|
|
786
|
+
return rows.length;
|
|
787
|
+
}
|
|
734
788
|
const sr = codePane.getBoundingClientRect();
|
|
735
789
|
const y = sr.top + codePane.clientTop + 2;
|
|
736
790
|
for (const el of rows) {
|
|
737
791
|
const r = el.getBoundingClientRect();
|
|
738
792
|
if (r.bottom > y - 1e-3) {
|
|
739
|
-
const m = /^code-line-(\d+)$/.exec(el.id);
|
|
793
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(el.id);
|
|
740
794
|
if (m)
|
|
741
795
|
return Number(m[1]) + 1;
|
|
742
796
|
return 1;
|
|
@@ -749,41 +803,74 @@ function probeCommentrayLine0FromDoc(docPane) {
|
|
|
749
803
|
if (anchors.length === 0)
|
|
750
804
|
return 0;
|
|
751
805
|
if (!paneUsesInternalYScroll(docPane)) {
|
|
752
|
-
if (rootScrollNearDocumentEnd())
|
|
753
|
-
|
|
754
|
-
const lineAttr = last.getAttribute("data-commentray-line");
|
|
755
|
-
return lineAttr !== null && lineAttr !== "" ? Number(lineAttr) : 0;
|
|
756
|
-
}
|
|
806
|
+
if (rootScrollNearDocumentEnd())
|
|
807
|
+
return lastCommentrayAnchorLine0(anchors);
|
|
757
808
|
const dr = docPane.getBoundingClientRect();
|
|
758
809
|
const vh = globalThis.innerHeight;
|
|
759
810
|
const clipT = Math.max(0, dr.top);
|
|
760
811
|
const clipB = Math.min(vh, dr.bottom);
|
|
761
812
|
const y = clipT + Math.max(2, Math.min(40, (clipB - clipT) * 0.15));
|
|
762
|
-
|
|
763
|
-
for (const a of anchors) {
|
|
764
|
-
const lineAttr = a.getAttribute("data-commentray-line");
|
|
765
|
-
if (lineAttr === null || lineAttr === "")
|
|
766
|
-
continue;
|
|
767
|
-
if (a.getBoundingClientRect().top <= y + 1 + 1e-3)
|
|
768
|
-
best = Number(lineAttr);
|
|
769
|
-
else
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
return best;
|
|
813
|
+
return bestCommentrayAnchorLine0AtOrAboveY(anchors, y);
|
|
773
814
|
}
|
|
815
|
+
if (paneScrollNearEnd(docPane))
|
|
816
|
+
return lastCommentrayAnchorLine0(anchors);
|
|
774
817
|
const dr = docPane.getBoundingClientRect();
|
|
775
818
|
const y = dr.top + docPane.clientTop + 2;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
819
|
+
return bestCommentrayAnchorLine0AtOrAboveY(anchors, y);
|
|
820
|
+
}
|
|
821
|
+
function pageBreakPullEnabled() {
|
|
822
|
+
const shell = document.getElementById("shell");
|
|
823
|
+
if (!(shell instanceof HTMLElement))
|
|
824
|
+
return false;
|
|
825
|
+
return shell.getAttribute("data-page-breaks-enabled") === "true";
|
|
826
|
+
}
|
|
827
|
+
function docProbeTopY(docPane) {
|
|
828
|
+
if (!paneUsesInternalYScroll(docPane)) {
|
|
829
|
+
const dr = docPane.getBoundingClientRect();
|
|
830
|
+
const vh = globalThis.innerHeight;
|
|
831
|
+
const clipT = Math.max(0, dr.top);
|
|
832
|
+
const clipB = Math.min(vh, dr.bottom);
|
|
833
|
+
return clipT + Math.max(2, Math.min(40, (clipB - clipT) * 0.15));
|
|
834
|
+
}
|
|
835
|
+
const dr = docPane.getBoundingClientRect();
|
|
836
|
+
return dr.top + docPane.clientTop + 2;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* In long synthetic page-break gaps, shift source toward the next block once
|
|
840
|
+
* the break itself occupies the top reading position.
|
|
841
|
+
*/
|
|
842
|
+
function pulledSourceLine0FromPageBreak(docPane) {
|
|
843
|
+
if (!pageBreakPullEnabled())
|
|
844
|
+
return null;
|
|
845
|
+
const topY = docProbeTopY(docPane);
|
|
846
|
+
const breaks = Array.from(docPane.querySelectorAll(".commentray-page-break[data-next-source-start]"));
|
|
847
|
+
for (const pageBreak of breaks) {
|
|
848
|
+
const nextSourceStartRaw = pageBreak.getAttribute("data-next-source-start");
|
|
849
|
+
if (!nextSourceStartRaw)
|
|
780
850
|
continue;
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
851
|
+
const nextSourceStart = Number.parseInt(nextSourceStartRaw, 10);
|
|
852
|
+
if (!Number.isFinite(nextSourceStart) || nextSourceStart <= 0)
|
|
853
|
+
continue;
|
|
854
|
+
const breakTop = pageBreak.getBoundingClientRect().top;
|
|
855
|
+
const nextLineRaw = pageBreak.getAttribute("data-next-commentray-line");
|
|
856
|
+
const nextLine0 = nextLineRaw ? Number.parseInt(nextLineRaw, 10) : Number.NaN;
|
|
857
|
+
const nextAnchor = Number.isFinite(nextLine0) && nextLine0 >= 0
|
|
858
|
+
? docPane.querySelector(`[data-commentray-line="${String(nextLine0)}"]`)
|
|
859
|
+
: null;
|
|
860
|
+
const nextTop = nextAnchor
|
|
861
|
+
? nextAnchor.getBoundingClientRect().top
|
|
862
|
+
: breakTop + pageBreak.clientHeight;
|
|
863
|
+
if (!(breakTop <= topY && topY < nextTop))
|
|
864
|
+
continue;
|
|
865
|
+
const denom = Math.max(1, nextTop - breakTop);
|
|
866
|
+
const progress = clamp((topY - breakTop) / denom, 0, 1);
|
|
867
|
+
const narrow = globalThis.matchMedia("(max-width: 767px)").matches;
|
|
868
|
+
const pullThreshold = narrow ? 0.2 : 0.35;
|
|
869
|
+
if (progress < pullThreshold)
|
|
870
|
+
return null;
|
|
871
|
+
return nextSourceStart - 1;
|
|
785
872
|
}
|
|
786
|
-
return
|
|
873
|
+
return null;
|
|
787
874
|
}
|
|
788
875
|
/**
|
|
789
876
|
* Programmatic `scrollTop` on the partner pane can emit a `scroll` event after the
|
|
@@ -832,16 +919,20 @@ function wireBidirectionalScroll(codePane, docPane, syncFromCode, syncFromDoc) {
|
|
|
832
919
|
}, { passive: true });
|
|
833
920
|
}
|
|
834
921
|
/** Index-backed scroll sync when `data-scroll-block-links-b64` is present; else see proportional fallback. */
|
|
835
|
-
function wireBlockAwareScrollSync(codePane, docPane, getLinks) {
|
|
922
|
+
function wireBlockAwareScrollSync(codePane, docPane, getLinks, lineIdPrefix, shouldUseProportionalDocToCodeOnMobileFlip) {
|
|
836
923
|
let pendingDocToCode = null;
|
|
837
924
|
let pendingCodeToDoc = null;
|
|
838
925
|
const syncFromCodeToDoc = () => {
|
|
839
|
-
applyCodeToDocFlipPlanImpl(codePane, docPane, buildCodeToDocFlipPlanBlockAware(codePane, docPane, getLinks));
|
|
926
|
+
applyCodeToDocFlipPlanImpl(codePane, docPane, buildCodeToDocFlipPlanBlockAware(codePane, docPane, getLinks, lineIdPrefix()));
|
|
840
927
|
};
|
|
841
928
|
const syncFromDocToCode = () => {
|
|
842
|
-
applyDocToCodeFlipPlanImpl(codePane, docPane, buildDocToCodeFlipPlanBlockAware(docPane, getLinks));
|
|
929
|
+
applyDocToCodeFlipPlanImpl(codePane, docPane, buildDocToCodeFlipPlanBlockAware(docPane, getLinks), lineIdPrefix());
|
|
843
930
|
};
|
|
844
931
|
const prepareMobileFlipToCode = () => {
|
|
932
|
+
if (shouldUseProportionalDocToCodeOnMobileFlip?.() === true) {
|
|
933
|
+
pendingDocToCode = { k: "mirrorW", ratio: windowScrollRatio() };
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
845
936
|
pendingDocToCode = buildDocToCodeFlipPlanBlockAware(docPane, getLinks);
|
|
846
937
|
};
|
|
847
938
|
const finishMobileFlipToCode = () => {
|
|
@@ -849,10 +940,10 @@ function wireBlockAwareScrollSync(codePane, docPane, getLinks) {
|
|
|
849
940
|
return;
|
|
850
941
|
const p = pendingDocToCode;
|
|
851
942
|
pendingDocToCode = null;
|
|
852
|
-
applyDocToCodeFlipPlanImpl(codePane, docPane, p);
|
|
943
|
+
applyDocToCodeFlipPlanImpl(codePane, docPane, p, lineIdPrefix());
|
|
853
944
|
};
|
|
854
945
|
const prepareMobileFlipToDoc = () => {
|
|
855
|
-
pendingCodeToDoc = buildCodeToDocFlipPlanBlockAware(codePane, docPane, getLinks);
|
|
946
|
+
pendingCodeToDoc = buildCodeToDocFlipPlanBlockAware(codePane, docPane, getLinks, lineIdPrefix());
|
|
856
947
|
};
|
|
857
948
|
const finishMobileFlipToDoc = () => {
|
|
858
949
|
if (!pendingCodeToDoc)
|
|
@@ -936,6 +1027,52 @@ function commentaryBandEndYViewport(docScrollEl, next, docTop) {
|
|
|
936
1027
|
bottom = Math.min(bottom, lastKid.getBoundingClientRect().bottom - 4);
|
|
937
1028
|
return bottom;
|
|
938
1029
|
}
|
|
1030
|
+
function sourceAnchorIndexFromId(id, prefix) {
|
|
1031
|
+
if (!id.startsWith(prefix))
|
|
1032
|
+
return null;
|
|
1033
|
+
const n = Number.parseInt(id.slice(prefix.length), 10);
|
|
1034
|
+
return Number.isFinite(n) ? n : null;
|
|
1035
|
+
}
|
|
1036
|
+
function findAnchorAtOrAfter(anchors, line0) {
|
|
1037
|
+
let lo = 0;
|
|
1038
|
+
let hi = anchors.length - 1;
|
|
1039
|
+
let ans = -1;
|
|
1040
|
+
while (lo <= hi) {
|
|
1041
|
+
const mid = (lo + hi) >> 1;
|
|
1042
|
+
const line = anchors[mid]?.line0 ?? -1;
|
|
1043
|
+
if (line >= line0) {
|
|
1044
|
+
ans = mid;
|
|
1045
|
+
hi = mid - 1;
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
lo = mid + 1;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return ans >= 0 ? (anchors[ans]?.el ?? null) : null;
|
|
1052
|
+
}
|
|
1053
|
+
function findAnchorAtOrBefore(anchors, line0) {
|
|
1054
|
+
let lo = 0;
|
|
1055
|
+
let hi = anchors.length - 1;
|
|
1056
|
+
let ans = -1;
|
|
1057
|
+
while (lo <= hi) {
|
|
1058
|
+
const mid = (lo + hi) >> 1;
|
|
1059
|
+
const line = anchors[mid]?.line0 ?? -1;
|
|
1060
|
+
if (line <= line0) {
|
|
1061
|
+
ans = mid;
|
|
1062
|
+
lo = mid + 1;
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
hi = mid - 1;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return ans >= 0 ? (anchors[ans]?.el ?? null) : null;
|
|
1069
|
+
}
|
|
1070
|
+
function sourceAnchorsFromPrefix(prefix) {
|
|
1071
|
+
return Array.from(document.querySelectorAll(`[id^="${prefix}"]`))
|
|
1072
|
+
.map((el) => ({ line0: sourceAnchorIndexFromId(el.id, prefix), el }))
|
|
1073
|
+
.filter((x) => x.line0 !== null)
|
|
1074
|
+
.sort((a, b) => a.line0 - b.line0);
|
|
1075
|
+
}
|
|
939
1076
|
function subscribeBlockRayRedraw(gutter, codePane, docScrollEl, scheduleDraw) {
|
|
940
1077
|
const onScrollOrResize = () => scheduleDraw();
|
|
941
1078
|
codePane.addEventListener("scroll", onScrollOrResize, { passive: true });
|
|
@@ -949,7 +1086,7 @@ function subscribeBlockRayRedraw(gutter, codePane, docScrollEl, scheduleDraw) {
|
|
|
949
1086
|
if (shell)
|
|
950
1087
|
ro.observe(shell);
|
|
951
1088
|
}
|
|
952
|
-
function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based) {
|
|
1089
|
+
function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based, lineIdPrefix) {
|
|
953
1090
|
const links = getLinks();
|
|
954
1091
|
const sorted = sortBlockLinksBySource(links);
|
|
955
1092
|
const gutterRect = gutter.getBoundingClientRect();
|
|
@@ -963,6 +1100,10 @@ function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSource
|
|
|
963
1100
|
svg.setAttribute("viewBox", `0 0 ${String(w)} ${String(h)}`);
|
|
964
1101
|
svg.setAttribute("preserveAspectRatio", "none");
|
|
965
1102
|
const parts = [];
|
|
1103
|
+
const sourceAnchors = Array.from(document.querySelectorAll(`[id^="${lineIdPrefix}"]`))
|
|
1104
|
+
.map((el) => ({ line0: sourceAnchorIndexFromId(el.id, lineIdPrefix), el }))
|
|
1105
|
+
.filter((x) => x.line0 !== null)
|
|
1106
|
+
.sort((a, b) => a.line0 - b.line0);
|
|
966
1107
|
for (let i = 0; i < sorted.length; i++) {
|
|
967
1108
|
const link = sorted[i];
|
|
968
1109
|
if (!link)
|
|
@@ -970,8 +1111,10 @@ function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSource
|
|
|
970
1111
|
const next = sorted[i + 1];
|
|
971
1112
|
const i0 = codeLineDomIndex0(link.sourceStart);
|
|
972
1113
|
const i1 = codeLineDomIndex0(link.sourceEnd);
|
|
973
|
-
const codeTop = document.getElementById(
|
|
974
|
-
|
|
1114
|
+
const codeTop = document.getElementById(`${lineIdPrefix}${String(i0)}`) ??
|
|
1115
|
+
findAnchorAtOrAfter(sourceAnchors, i0);
|
|
1116
|
+
const codeBot = document.getElementById(`${lineIdPrefix}${String(i1)}`) ??
|
|
1117
|
+
findAnchorAtOrBefore(sourceAnchors, i1);
|
|
975
1118
|
const docTop = document.getElementById(`commentray-block-${link.id}`);
|
|
976
1119
|
if (!codeTop || !codeBot || !docTop)
|
|
977
1120
|
continue;
|
|
@@ -1016,6 +1159,7 @@ function drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSource
|
|
|
1016
1159
|
*/
|
|
1017
1160
|
function wireBlockRayConnectors(args) {
|
|
1018
1161
|
const { gutter, codePane, docScrollEl, getLinks, probeTopSourceLine1Based } = args;
|
|
1162
|
+
const sourceLineIdPrefix = args.sourceLineIdPrefix ?? (() => "code-line-");
|
|
1019
1163
|
const svgNs = "http://www.w3.org/2000/svg";
|
|
1020
1164
|
const host = document.createElement("div");
|
|
1021
1165
|
host.className = "gutter__rays";
|
|
@@ -1029,7 +1173,7 @@ function wireBlockRayConnectors(args) {
|
|
|
1029
1173
|
return;
|
|
1030
1174
|
raf = globalThis.requestAnimationFrame(() => {
|
|
1031
1175
|
raf = 0;
|
|
1032
|
-
drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based);
|
|
1176
|
+
drawBlockRaysIntoSvg(svg, gutter, docScrollEl, getLinks, probeTopSourceLine1Based, sourceLineIdPrefix());
|
|
1033
1177
|
});
|
|
1034
1178
|
}
|
|
1035
1179
|
subscribeBlockRayRedraw(gutter, codePane, docScrollEl, scheduleDraw);
|
|
@@ -1183,6 +1327,40 @@ function treeFileLinkTitle(pr) {
|
|
|
1183
1327
|
}
|
|
1184
1328
|
return pr.sourcePath;
|
|
1185
1329
|
}
|
|
1330
|
+
function clearDocumentedTreePairHighlights(tree) {
|
|
1331
|
+
for (const el of tree.querySelectorAll("a.tree-file-link")) {
|
|
1332
|
+
if (!(el instanceof HTMLAnchorElement))
|
|
1333
|
+
continue;
|
|
1334
|
+
el.classList.remove("tree-file-link--current");
|
|
1335
|
+
el.removeAttribute("aria-current");
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
function markFirstDocumentedTreeLinkMatchingPair(tree, curSrc, curCr) {
|
|
1339
|
+
for (const el of tree.querySelectorAll("a.tree-file-link")) {
|
|
1340
|
+
if (!(el instanceof HTMLAnchorElement))
|
|
1341
|
+
continue;
|
|
1342
|
+
const sp = el.getAttribute("data-pair-source-path")?.trim() ?? "";
|
|
1343
|
+
const cp = el.getAttribute("data-pair-commentray-path")?.trim() ?? "";
|
|
1344
|
+
if (!isSameDocumentedPair({ sourcePath: sp, commentrayPath: cp }, curSrc, curCr))
|
|
1345
|
+
continue;
|
|
1346
|
+
el.classList.add("tree-file-link--current");
|
|
1347
|
+
el.setAttribute("aria-current", "page");
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
/** Marks the tree link for the pair shown in `#shell` (pair paths from server or multi-angle swap). */
|
|
1352
|
+
function applyDocumentedTreeCurrentPairHighlight() {
|
|
1353
|
+
const shell = document.getElementById("shell");
|
|
1354
|
+
const tree = document.getElementById("documented-files-tree");
|
|
1355
|
+
if (!(shell instanceof HTMLElement) || !(tree instanceof HTMLElement))
|
|
1356
|
+
return;
|
|
1357
|
+
clearDocumentedTreePairHighlights(tree);
|
|
1358
|
+
const curSrc = shell.getAttribute("data-commentray-pair-source-path")?.trim() ?? "";
|
|
1359
|
+
const curCr = shell.getAttribute("data-commentray-pair-commentray-path")?.trim() ?? "";
|
|
1360
|
+
if (curSrc.length === 0 || curCr.length === 0)
|
|
1361
|
+
return;
|
|
1362
|
+
markFirstDocumentedTreeLinkMatchingPair(tree, curSrc, curCr);
|
|
1363
|
+
}
|
|
1186
1364
|
function renderDocumentedTreeHtml(node) {
|
|
1187
1365
|
const keys = [...node.children.keys()].sort((a, b) => a.localeCompare(b));
|
|
1188
1366
|
if (keys.length === 0)
|
|
@@ -1202,10 +1380,12 @@ function renderDocumentedTreeHtml(node) {
|
|
|
1202
1380
|
const label = escapeHtmlText(treeFileLinkLabel(pr, multi));
|
|
1203
1381
|
const title = escapeHtmlText(treeFileLinkTitle(pr));
|
|
1204
1382
|
const href = escapeHtmlText(treeFileLinkHref(pr));
|
|
1383
|
+
const spAttr = escapeHtmlText(normPosixPath(pr.sourcePath));
|
|
1384
|
+
const crAttr = escapeHtmlText(normPosixPath(pr.commentrayPath));
|
|
1205
1385
|
const useSiteBrowse = (pr.staticBrowseUrl?.trim() ?? "").length > 0;
|
|
1206
1386
|
const external = useSiteBrowse ? "" : ' target="_blank" rel="noopener noreferrer"';
|
|
1207
1387
|
lis.push(`<li><div class="tree-file">` +
|
|
1208
|
-
`<a class="tree-file-link" href="${href}"${external} title="${title}">${label}</a>` +
|
|
1388
|
+
`<a class="tree-file-link" href="${href}" data-pair-source-path="${spAttr}" data-pair-commentray-path="${crAttr}"${external} title="${title}">${label}</a>` +
|
|
1209
1389
|
`</div></li>`);
|
|
1210
1390
|
}
|
|
1211
1391
|
}
|
|
@@ -1223,6 +1403,7 @@ function renderDocumentedPairsIntoHost(treeHost, pairs, emptyBecauseFilter) {
|
|
|
1223
1403
|
for (const p of pairs)
|
|
1224
1404
|
insertSourcePathTrie(root, p);
|
|
1225
1405
|
treeHost.innerHTML = renderDocumentedTreeHtml(root);
|
|
1406
|
+
applyDocumentedTreeCurrentPairHighlight();
|
|
1226
1407
|
}
|
|
1227
1408
|
function loadDocumentedPairs(jsonUrl, embeddedB64) {
|
|
1228
1409
|
let loaded = null;
|
|
@@ -1298,6 +1479,12 @@ function wireDocumentedFilesTreeMobileFlyout(hub) {
|
|
|
1298
1479
|
globalThis.addEventListener("scroll", placeFlyout, true);
|
|
1299
1480
|
return placeFlyout;
|
|
1300
1481
|
}
|
|
1482
|
+
function focusDocumentedFilesFilterInput() {
|
|
1483
|
+
const el = document.getElementById("documented-files-filter");
|
|
1484
|
+
if (!(el instanceof HTMLInputElement))
|
|
1485
|
+
return;
|
|
1486
|
+
el.focus({ preventScroll: true });
|
|
1487
|
+
}
|
|
1301
1488
|
function wireDocumentedFilesTree() {
|
|
1302
1489
|
const hub = document.getElementById("documented-files-hub");
|
|
1303
1490
|
const treeHost = document.getElementById("documented-files-tree");
|
|
@@ -1337,7 +1524,10 @@ function wireDocumentedFilesTree() {
|
|
|
1337
1524
|
hub.addEventListener("toggle", () => {
|
|
1338
1525
|
placeDocHubFlyout();
|
|
1339
1526
|
if (hub.open) {
|
|
1340
|
-
globalThis.requestAnimationFrame(
|
|
1527
|
+
globalThis.requestAnimationFrame(() => {
|
|
1528
|
+
placeDocHubFlyout();
|
|
1529
|
+
focusDocumentedFilesFilterInput();
|
|
1530
|
+
});
|
|
1341
1531
|
}
|
|
1342
1532
|
if (!hub.open)
|
|
1343
1533
|
return;
|
|
@@ -1384,11 +1574,61 @@ function wireSplitter(storageSplit, shell, codePane, gutter, initialPct) {
|
|
|
1384
1574
|
const STORAGE_SPLIT_PCT = "commentray.codeCommentrayStatic.splitPct";
|
|
1385
1575
|
const STORAGE_WRAP_LINES = "commentray.codeCommentrayStatic.wrap";
|
|
1386
1576
|
const STORAGE_DUAL_MOBILE_PANE = "commentray.codeCommentrayStatic.dualMobilePane";
|
|
1577
|
+
const STORAGE_SOURCE_MARKDOWN_PANE_MODE = "commentray.codeCommentrayStatic.sourceMarkdownPaneMode";
|
|
1578
|
+
const STORAGE_PAGE_BREAKS_ENABLED = "commentray.codeCommentrayStatic.pageBreaksEnabled";
|
|
1387
1579
|
/** Matches `code-browser.ts` `@media (max-width: 767px)` (dual column from 768px up). */
|
|
1388
1580
|
const DUAL_MOBILE_SINGLE_PANE_MQ = "(max-width: 767px)";
|
|
1389
1581
|
function normalizedDualMobilePane(v) {
|
|
1390
1582
|
return v === "code" ? "code" : "doc";
|
|
1391
1583
|
}
|
|
1584
|
+
function isNarrowViewport() {
|
|
1585
|
+
return globalThis.matchMedia(DUAL_MOBILE_SINGLE_PANE_MQ).matches;
|
|
1586
|
+
}
|
|
1587
|
+
function wireWideModeIntroTrigger(shell) {
|
|
1588
|
+
const btn = document.getElementById("commentray-help-tour");
|
|
1589
|
+
if (!(btn instanceof HTMLButtonElement))
|
|
1590
|
+
return;
|
|
1591
|
+
btn.addEventListener("click", () => {
|
|
1592
|
+
wireWideModeIntroTour(shell, isNarrowViewport, { force: true });
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
function sourcePaneModeForShell(shell) {
|
|
1596
|
+
return shell.getAttribute("data-source-pane-mode") === "rendered-markdown"
|
|
1597
|
+
? "rendered-markdown"
|
|
1598
|
+
: "source";
|
|
1599
|
+
}
|
|
1600
|
+
function pageBreaksEnabledFromStorage(raw) {
|
|
1601
|
+
const t = (raw ?? "").trim().toLowerCase();
|
|
1602
|
+
if (t === "0" || t === "false" || t === "off")
|
|
1603
|
+
return false;
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
function applyPageBreakFeatureToggle(shell) {
|
|
1607
|
+
const enabled = pageBreaksEnabledFromStorage(readWebStorageItem(localStorage, STORAGE_PAGE_BREAKS_ENABLED));
|
|
1608
|
+
shell.setAttribute("data-page-breaks-enabled", enabled ? "true" : "false");
|
|
1609
|
+
}
|
|
1610
|
+
function wireResponsivePageBreakHeight(shell) {
|
|
1611
|
+
const setHeight = () => {
|
|
1612
|
+
const viewportHeight = Math.max(globalThis.innerHeight, document.documentElement?.clientHeight ?? 0);
|
|
1613
|
+
if (!Number.isFinite(viewportHeight) || viewportHeight <= 0)
|
|
1614
|
+
return;
|
|
1615
|
+
const minHeightPx = Math.round(clamp(viewportHeight * 0.72, 260, 820));
|
|
1616
|
+
shell.style.setProperty("--commentray-page-break-min-height", `${String(minHeightPx)}px`);
|
|
1617
|
+
};
|
|
1618
|
+
globalThis.addEventListener("resize", setHeight, { passive: true });
|
|
1619
|
+
globalThis.addEventListener("orientationchange", setHeight, { passive: true });
|
|
1620
|
+
globalThis.visualViewport?.addEventListener("resize", setHeight, { passive: true });
|
|
1621
|
+
setHeight();
|
|
1622
|
+
}
|
|
1623
|
+
function syncWrapLinesVisibilityForSourcePaneMode(shell) {
|
|
1624
|
+
const wrapToggle = document.querySelector("label.toolbar-wrap-lines");
|
|
1625
|
+
if (!(wrapToggle instanceof HTMLLabelElement))
|
|
1626
|
+
return;
|
|
1627
|
+
wrapToggle.hidden = sourcePaneModeForShell(shell) === "rendered-markdown";
|
|
1628
|
+
}
|
|
1629
|
+
function sourceLineIdPrefixForShell(shell) {
|
|
1630
|
+
return sourcePaneModeForShell(shell) === "rendered-markdown" ? "code-md-line-" : "code-line-";
|
|
1631
|
+
}
|
|
1392
1632
|
/** When the commentary pane is visible, (re)run Mermaid so diagrams are not laid out under display:none. */
|
|
1393
1633
|
function scheduleMermaidWhenDualDocPaneVisible(shell, mq) {
|
|
1394
1634
|
const kick = () => {
|
|
@@ -1440,6 +1680,105 @@ function wireDualMobilePaneFlipScrollAffordance(primaryFlip, scrollFlip, mq) {
|
|
|
1440
1680
|
mq.addEventListener("change", tick);
|
|
1441
1681
|
globalThis.requestAnimationFrame(tick);
|
|
1442
1682
|
}
|
|
1683
|
+
function wireSourceMarkdownPaneFlipAffordance(primaryFlip, scrollFlip) {
|
|
1684
|
+
const hideScroll = () => {
|
|
1685
|
+
scrollFlip.hidden = true;
|
|
1686
|
+
scrollFlip.classList.remove("is-visible");
|
|
1687
|
+
};
|
|
1688
|
+
const showScroll = () => {
|
|
1689
|
+
scrollFlip.hidden = false;
|
|
1690
|
+
scrollFlip.classList.add("is-visible");
|
|
1691
|
+
};
|
|
1692
|
+
const tick = () => {
|
|
1693
|
+
const r = primaryFlip.getBoundingClientRect();
|
|
1694
|
+
const vh = globalThis.innerHeight;
|
|
1695
|
+
const margin = 10;
|
|
1696
|
+
const offScreen = r.bottom < margin || r.top > vh - margin;
|
|
1697
|
+
if (offScreen)
|
|
1698
|
+
showScroll();
|
|
1699
|
+
else
|
|
1700
|
+
hideScroll();
|
|
1701
|
+
};
|
|
1702
|
+
globalThis.addEventListener("scroll", tick, { passive: true });
|
|
1703
|
+
globalThis.addEventListener("resize", tick, { passive: true });
|
|
1704
|
+
globalThis.requestAnimationFrame(tick);
|
|
1705
|
+
}
|
|
1706
|
+
function closestSourceLine0ForPaneTop(codePane, idPrefix) {
|
|
1707
|
+
const rows = codePane.querySelectorAll(`[id^="${idPrefix}"]`);
|
|
1708
|
+
if (rows.length === 0)
|
|
1709
|
+
return null;
|
|
1710
|
+
const y = paneUsesInternalYScroll(codePane)
|
|
1711
|
+
? codePane.getBoundingClientRect().top + codePane.clientTop + 2
|
|
1712
|
+
: Math.max(0, codePane.getBoundingClientRect().top) + 2;
|
|
1713
|
+
for (const el of rows) {
|
|
1714
|
+
const r = el.getBoundingClientRect();
|
|
1715
|
+
if (r.bottom > y - 1e-3) {
|
|
1716
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(el.id);
|
|
1717
|
+
if (!m?.[1])
|
|
1718
|
+
return null;
|
|
1719
|
+
return Number.parseInt(m[1], 10);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const last = rows[rows.length - 1];
|
|
1723
|
+
if (!last)
|
|
1724
|
+
return null;
|
|
1725
|
+
const m = /^(?:code-line-|code-md-line-)(\d+)$/.exec(last.id);
|
|
1726
|
+
if (!m?.[1])
|
|
1727
|
+
return null;
|
|
1728
|
+
return Number.parseInt(m[1], 10);
|
|
1729
|
+
}
|
|
1730
|
+
function wireSourceMarkdownPaneFlip(shell, codePane, flipBtn, flipScrollBtn, onAfterFlip) {
|
|
1731
|
+
function syncSourceMarkdownFlipA11y() {
|
|
1732
|
+
const mode = sourcePaneModeForShell(shell);
|
|
1733
|
+
const renderedActive = mode === "rendered-markdown";
|
|
1734
|
+
const nextModeLabel = renderedActive ? "markdown source" : "rendered markdown";
|
|
1735
|
+
const ariaLabel = `Switch source pane to ${nextModeLabel}`;
|
|
1736
|
+
const title = `Source pane: ${renderedActive ? "rendered markdown" : "markdown source"} (click to switch)`;
|
|
1737
|
+
const apply = (btn) => {
|
|
1738
|
+
if (!(btn instanceof HTMLButtonElement))
|
|
1739
|
+
return;
|
|
1740
|
+
btn.setAttribute("aria-pressed", renderedActive ? "true" : "false");
|
|
1741
|
+
btn.setAttribute("aria-label", ariaLabel);
|
|
1742
|
+
btn.title = title;
|
|
1743
|
+
};
|
|
1744
|
+
apply(flipBtn);
|
|
1745
|
+
apply(flipScrollBtn);
|
|
1746
|
+
}
|
|
1747
|
+
// Keep initial behavior deterministic: source pane starts in rendered markdown mode.
|
|
1748
|
+
shell.setAttribute("data-source-pane-mode", "rendered-markdown");
|
|
1749
|
+
syncSourceMarkdownFlipA11y();
|
|
1750
|
+
syncWrapLinesVisibilityForSourcePaneMode(shell);
|
|
1751
|
+
const runFlip = () => {
|
|
1752
|
+
const cur = sourcePaneModeForShell(shell);
|
|
1753
|
+
const currentPrefix = cur === "rendered-markdown" ? "code-md-line-" : "code-line-";
|
|
1754
|
+
const line0 = closestSourceLine0ForPaneTop(codePane, currentPrefix);
|
|
1755
|
+
const next = cur === "rendered-markdown" ? "source" : "rendered-markdown";
|
|
1756
|
+
const nextPrefix = next === "rendered-markdown" ? "code-md-line-" : "code-line-";
|
|
1757
|
+
shell.setAttribute("data-source-pane-mode", next);
|
|
1758
|
+
writeWebStorageItem(localStorage, STORAGE_SOURCE_MARKDOWN_PANE_MODE, next);
|
|
1759
|
+
syncSourceMarkdownFlipA11y();
|
|
1760
|
+
syncWrapLinesVisibilityForSourcePaneMode(shell);
|
|
1761
|
+
if (line0 !== null) {
|
|
1762
|
+
const row = codePane.querySelector(`#${nextPrefix}${String(line0)}`);
|
|
1763
|
+
if (row instanceof HTMLElement) {
|
|
1764
|
+
applyRevealChildInPane(codePane, row, 2);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
if (next === "rendered-markdown") {
|
|
1768
|
+
const sourceMdBody = document.getElementById("code-pane-markdown-body");
|
|
1769
|
+
if (sourceMdBody instanceof HTMLElement) {
|
|
1770
|
+
runMermaidOnFreshDocNodes(sourceMdBody);
|
|
1771
|
+
rewriteHubRelativeBrowseAnchorsIn(sourceMdBody);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
onAfterFlip?.();
|
|
1775
|
+
};
|
|
1776
|
+
flipBtn.addEventListener("click", runFlip);
|
|
1777
|
+
if (flipScrollBtn) {
|
|
1778
|
+
flipScrollBtn.addEventListener("click", runFlip);
|
|
1779
|
+
wireSourceMarkdownPaneFlipAffordance(flipBtn, flipScrollBtn);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1443
1782
|
function wireDualMobilePaneFlip(shell, flipBtn, scrollRunners, flipScrollBtn) {
|
|
1444
1783
|
const mq = globalThis.matchMedia(DUAL_MOBILE_SINGLE_PANE_MQ);
|
|
1445
1784
|
function readStoredPane() {
|
|
@@ -1458,6 +1797,7 @@ function wireDualMobilePaneFlip(shell, flipBtn, scrollRunners, flipScrollBtn) {
|
|
|
1458
1797
|
return;
|
|
1459
1798
|
const cur = normalizedDualMobilePane(shell.getAttribute("data-dual-mobile-pane"));
|
|
1460
1799
|
const next = cur === "code" ? "doc" : "code";
|
|
1800
|
+
const rootTopBeforeFlip = rootScrollingElement().scrollTop;
|
|
1461
1801
|
if (next === "code") {
|
|
1462
1802
|
scrollRunners.prepareMobileFlipToCode();
|
|
1463
1803
|
}
|
|
@@ -1470,6 +1810,11 @@ function wireDualMobilePaneFlip(shell, flipBtn, scrollRunners, flipScrollBtn) {
|
|
|
1470
1810
|
globalThis.requestAnimationFrame(() => {
|
|
1471
1811
|
if (next === "code") {
|
|
1472
1812
|
scrollRunners.finishMobileFlipToCode();
|
|
1813
|
+
const root = rootScrollingElement();
|
|
1814
|
+
if (rootTopBeforeFlip > 5 && root.scrollTop <= 1) {
|
|
1815
|
+
const maxY = Math.max(0, root.scrollHeight - root.clientHeight);
|
|
1816
|
+
root.scrollTop = clamp(rootTopBeforeFlip, 0, maxY);
|
|
1817
|
+
}
|
|
1473
1818
|
}
|
|
1474
1819
|
else {
|
|
1475
1820
|
scrollRunners.finishMobileFlipToDoc();
|
|
@@ -1630,75 +1975,108 @@ function wireDualPaneNavSearchFetch(shell, embeddedPairs, indexState, mutable, r
|
|
|
1630
1975
|
}
|
|
1631
1976
|
})();
|
|
1632
1977
|
}
|
|
1978
|
+
function applySelectedMultiAngle(args) {
|
|
1979
|
+
const { angle, docBody, mutable, rebuildSearcher, scrollLinksRef, shell, searchInput, searchResults, requestBlockRayRedraw, } = args;
|
|
1980
|
+
docBody.innerHTML = decodeBase64Utf8(angle.docInnerHtmlB64);
|
|
1981
|
+
runMermaidOnFreshDocNodes(docBody);
|
|
1982
|
+
rewriteHubRelativeBrowseAnchorsIn(docBody);
|
|
1983
|
+
mutable.rawMd = decodeBase64Utf8(angle.rawMdB64);
|
|
1984
|
+
mutable.mdLines = mutable.rawMd.split("\n");
|
|
1985
|
+
mutable.commentrayPathLabel = angle.commentrayPathForSearch;
|
|
1986
|
+
rebuildSearcher();
|
|
1987
|
+
scrollLinksRef.current = parseScrollBlockLinksFromShell(angle.scrollBlockLinksB64);
|
|
1988
|
+
shell.setAttribute("data-scroll-block-links-b64", angle.scrollBlockLinksB64);
|
|
1989
|
+
shell.setAttribute("data-search-commentray-path", angle.commentrayPathForSearch);
|
|
1990
|
+
const crIdentity = normPosixPath(angle.commentrayPathForSearch);
|
|
1991
|
+
if (crIdentity.length > 0)
|
|
1992
|
+
shell.setAttribute("data-commentray-pair-commentray-path", crIdentity);
|
|
1993
|
+
else
|
|
1994
|
+
shell.removeAttribute("data-commentray-pair-commentray-path");
|
|
1995
|
+
applyDocumentedTreeCurrentPairHighlight();
|
|
1996
|
+
const docPathEl = document.getElementById("nav-rail-doc-path");
|
|
1997
|
+
if (docPathEl) {
|
|
1998
|
+
const path = angle.commentrayPathForSearch.trim();
|
|
1999
|
+
docPathEl.textContent = path.length > 0 ? path : "—";
|
|
2000
|
+
if (path.length > 0)
|
|
2001
|
+
docPathEl.setAttribute("title", path);
|
|
2002
|
+
else
|
|
2003
|
+
docPathEl.removeAttribute("title");
|
|
2004
|
+
}
|
|
2005
|
+
const browse = angle.staticBrowseUrl?.trim() ?? "";
|
|
2006
|
+
if (browse.length > 0) {
|
|
2007
|
+
const resolved = staticBrowseHrefForShellDataAttribute(browse, globalThis.location.pathname, globalThis.location.origin);
|
|
2008
|
+
shell.setAttribute("data-commentray-pair-browse-href", resolved);
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
const ghu = angle.commentrayOnGithubUrl?.trim();
|
|
2012
|
+
if (ghu)
|
|
2013
|
+
shell.setAttribute("data-commentray-pair-browse-href", ghu);
|
|
2014
|
+
else
|
|
2015
|
+
shell.removeAttribute("data-commentray-pair-browse-href");
|
|
2016
|
+
}
|
|
2017
|
+
searchInput.value = "";
|
|
2018
|
+
searchResults.innerHTML = "";
|
|
2019
|
+
searchResults.hidden = true;
|
|
2020
|
+
requestBlockRayRedraw?.();
|
|
2021
|
+
globalThis.requestAnimationFrame(() => {
|
|
2022
|
+
requestBlockRayRedraw?.();
|
|
2023
|
+
globalThis.requestAnimationFrame(() => {
|
|
2024
|
+
requestBlockRayRedraw?.();
|
|
2025
|
+
});
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
1633
2028
|
function wireDualPaneMultiAngleAndScroll(args) {
|
|
1634
2029
|
const { codePane, docScrollEl, docBody, shell, scrollLinksRef, multiPayload, mutable, rebuildSearcher, searchInput, searchResults, requestBlockRayRedraw, } = args;
|
|
1635
2030
|
if (multiPayload) {
|
|
1636
|
-
const runners = wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current);
|
|
2031
|
+
const runners = wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current, () => sourceLineIdPrefixForShell(shell), () => sourcePaneModeForShell(shell) === "rendered-markdown");
|
|
1637
2032
|
const angleSel = document.getElementById("angle-select");
|
|
1638
2033
|
if (angleSel && docBody) {
|
|
1639
2034
|
angleSel.addEventListener("change", () => {
|
|
1640
2035
|
const a = multiPayload.angles.find((x) => x.id === angleSel.value);
|
|
1641
2036
|
if (!a)
|
|
1642
2037
|
return;
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
const docPathEl = document.getElementById("nav-rail-doc-path");
|
|
1654
|
-
if (docPathEl) {
|
|
1655
|
-
const path = a.commentrayPathForSearch.trim();
|
|
1656
|
-
docPathEl.textContent = path.length > 0 ? path : "—";
|
|
1657
|
-
if (path.length > 0)
|
|
1658
|
-
docPathEl.setAttribute("title", path);
|
|
1659
|
-
else
|
|
1660
|
-
docPathEl.removeAttribute("title");
|
|
1661
|
-
}
|
|
1662
|
-
const browse = a.staticBrowseUrl?.trim() ?? "";
|
|
1663
|
-
if (browse.length > 0) {
|
|
1664
|
-
const resolved = staticBrowseHrefForShellDataAttribute(browse, globalThis.location.pathname, globalThis.location.origin);
|
|
1665
|
-
shell.setAttribute("data-commentray-pair-browse-href", resolved);
|
|
1666
|
-
}
|
|
1667
|
-
else {
|
|
1668
|
-
const ghu = a.commentrayOnGithubUrl?.trim();
|
|
1669
|
-
if (ghu) {
|
|
1670
|
-
shell.setAttribute("data-commentray-pair-browse-href", ghu);
|
|
1671
|
-
}
|
|
1672
|
-
else {
|
|
1673
|
-
shell.removeAttribute("data-commentray-pair-browse-href");
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
searchInput.value = "";
|
|
1677
|
-
searchResults.innerHTML = "";
|
|
1678
|
-
searchResults.hidden = true;
|
|
1679
|
-
requestBlockRayRedraw?.();
|
|
1680
|
-
globalThis.requestAnimationFrame(() => {
|
|
1681
|
-
requestBlockRayRedraw?.();
|
|
1682
|
-
globalThis.requestAnimationFrame(() => {
|
|
1683
|
-
requestBlockRayRedraw?.();
|
|
1684
|
-
});
|
|
2038
|
+
applySelectedMultiAngle({
|
|
2039
|
+
angle: a,
|
|
2040
|
+
docBody,
|
|
2041
|
+
mutable,
|
|
2042
|
+
rebuildSearcher,
|
|
2043
|
+
scrollLinksRef,
|
|
2044
|
+
shell,
|
|
2045
|
+
searchInput,
|
|
2046
|
+
searchResults,
|
|
2047
|
+
requestBlockRayRedraw,
|
|
1685
2048
|
});
|
|
1686
2049
|
});
|
|
1687
2050
|
}
|
|
1688
2051
|
return runners;
|
|
1689
2052
|
}
|
|
1690
2053
|
if (scrollLinksRef.current.length > 0) {
|
|
1691
|
-
return wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current);
|
|
2054
|
+
return wireBlockAwareScrollSync(codePane, docScrollEl, () => scrollLinksRef.current, () => sourceLineIdPrefixForShell(shell), () => sourcePaneModeForShell(shell) === "rendered-markdown");
|
|
1692
2055
|
}
|
|
1693
2056
|
return wireProportionalScrollSync(codePane, docScrollEl);
|
|
1694
2057
|
}
|
|
1695
2058
|
function wireDualPaneCommentrayLocationHash(docScrollEl, mdLineCount) {
|
|
2059
|
+
function commentrayMdLineFromLocationHash(rawHash) {
|
|
2060
|
+
const hash = rawHash.replace(/^#/, "").trim();
|
|
2061
|
+
if (hash.length === 0)
|
|
2062
|
+
return null;
|
|
2063
|
+
const tokens = hash
|
|
2064
|
+
.split(/--|&/)
|
|
2065
|
+
.map((t) => t.trim())
|
|
2066
|
+
.filter((t) => t.length > 0);
|
|
2067
|
+
for (const token of tokens) {
|
|
2068
|
+
const m = /^commentray-md-line-(\d+)$/.exec(token);
|
|
2069
|
+
if (!m?.[1])
|
|
2070
|
+
continue;
|
|
2071
|
+
const line0 = Number.parseInt(m[1], 10);
|
|
2072
|
+
if (Number.isFinite(line0))
|
|
2073
|
+
return line0;
|
|
2074
|
+
}
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
1696
2077
|
function applyCommentrayLocationHash() {
|
|
1697
|
-
const
|
|
1698
|
-
if (
|
|
1699
|
-
return;
|
|
1700
|
-
const line0 = Number.parseInt(m[1], 10);
|
|
1701
|
-
if (!Number.isFinite(line0))
|
|
2078
|
+
const line0 = commentrayMdLineFromLocationHash(globalThis.location.hash);
|
|
2079
|
+
if (line0 === null)
|
|
1702
2080
|
return;
|
|
1703
2081
|
scrollDocToMarkdownLine0(docScrollEl, line0, mdLineCount());
|
|
1704
2082
|
}
|
|
@@ -1707,6 +2085,23 @@ function wireDualPaneCommentrayLocationHash(docScrollEl, mdLineCount) {
|
|
|
1707
2085
|
globalThis.requestAnimationFrame(applyCommentrayLocationHash);
|
|
1708
2086
|
});
|
|
1709
2087
|
}
|
|
2088
|
+
function initializeSourceMarkdownPane(shell) {
|
|
2089
|
+
if (sourcePaneModeForShell(shell) !== "rendered-markdown")
|
|
2090
|
+
return;
|
|
2091
|
+
const sourceMdBody = document.getElementById("code-pane-markdown-body");
|
|
2092
|
+
if (!(sourceMdBody instanceof HTMLElement))
|
|
2093
|
+
return;
|
|
2094
|
+
runMermaidOnFreshDocNodes(sourceMdBody);
|
|
2095
|
+
rewriteHubRelativeBrowseAnchorsIn(sourceMdBody);
|
|
2096
|
+
}
|
|
2097
|
+
function wireSourceMarkdownControls(shell, codePane, onAfterFlip) {
|
|
2098
|
+
const sourceMdFlip = document.getElementById("source-markdown-pane-flip");
|
|
2099
|
+
const sourceMdFlipScroll = document.getElementById("source-markdown-pane-flip-scroll");
|
|
2100
|
+
if (!(sourceMdFlip instanceof HTMLButtonElement))
|
|
2101
|
+
return;
|
|
2102
|
+
wireSourceMarkdownPaneFlip(shell, codePane, sourceMdFlip, sourceMdFlipScroll instanceof HTMLButtonElement ? sourceMdFlipScroll : null, onAfterFlip);
|
|
2103
|
+
initializeSourceMarkdownPane(shell);
|
|
2104
|
+
}
|
|
1710
2105
|
function buildDualPaneSearcherBundle(shell, codePane) {
|
|
1711
2106
|
const { rawCodeB64, rawMdB64 } = readEmbeddedRawB64Strings(shell, codePane);
|
|
1712
2107
|
const rawCode = decodeBase64Utf8(rawCodeB64);
|
|
@@ -1778,12 +2173,11 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
1778
2173
|
shell.style.setProperty("--split-pct", `${String(pct)}%`);
|
|
1779
2174
|
const docPaneEl = document.getElementById("doc-pane");
|
|
1780
2175
|
const docPaneForWrap = docPaneEl instanceof HTMLElement ? docPaneEl : null;
|
|
2176
|
+
const sourceMdBodyForWrap = document.getElementById("code-pane-markdown-body");
|
|
1781
2177
|
const blockRayRedraw = {};
|
|
1782
2178
|
wireWrapToggle(STORAGE_WRAP_LINES, codePane, wrapCb, () => {
|
|
1783
2179
|
blockRayRedraw.request?.();
|
|
1784
|
-
|
|
1785
|
-
docScrollEl.dispatchEvent(new Event("scroll"));
|
|
1786
|
-
}, docPaneForWrap, docBody);
|
|
2180
|
+
}, docPaneForWrap, docBody, sourceMdBodyForWrap instanceof HTMLElement ? sourceMdBodyForWrap : null);
|
|
1787
2181
|
wireSplitter(STORAGE_SPLIT_PCT, shell, codePane, gutter, pct);
|
|
1788
2182
|
const multiScript = document.getElementById("commentray-multi-angle-b64");
|
|
1789
2183
|
const multiPayload = parseMultiAnglePayload(multiScript);
|
|
@@ -1794,7 +2188,8 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
1794
2188
|
codePane,
|
|
1795
2189
|
docScrollEl,
|
|
1796
2190
|
getLinks: () => bundle.scrollLinksRef.current,
|
|
1797
|
-
probeTopSourceLine1Based: () => probeCodeLine1FromViewport(codePane),
|
|
2191
|
+
probeTopSourceLine1Based: () => probeCodeLine1FromViewport(codePane, sourceLineIdPrefixForShell(shell)),
|
|
2192
|
+
sourceLineIdPrefix: () => sourceLineIdPrefixForShell(shell),
|
|
1798
2193
|
})
|
|
1799
2194
|
: undefined;
|
|
1800
2195
|
blockRayRedraw.request = requestBlockRayRedraw;
|
|
@@ -1816,6 +2211,10 @@ function wireDualPaneCodeBrowser(shell, codePane) {
|
|
|
1816
2211
|
if (flipBtn instanceof HTMLButtonElement) {
|
|
1817
2212
|
wireDualMobilePaneFlip(shell, flipBtn, scrollRunners, flipScrollBtn instanceof HTMLButtonElement ? flipScrollBtn : null);
|
|
1818
2213
|
}
|
|
2214
|
+
wireSourceMarkdownControls(shell, codePane, () => {
|
|
2215
|
+
requestBlockRayRedraw?.();
|
|
2216
|
+
});
|
|
2217
|
+
wireWideModeIntroTour(shell, isNarrowViewport);
|
|
1819
2218
|
wireDualPaneCommentrayLocationHash(docScrollEl, () => bundle.mutable.mdLines.length);
|
|
1820
2219
|
}
|
|
1821
2220
|
function commentrayThemeModeLabel(mode) {
|
|
@@ -1917,7 +2316,201 @@ function wireColorThemeToolbar() {
|
|
|
1917
2316
|
document.addEventListener("keydown", onDocumentKeydown, true);
|
|
1918
2317
|
syncUi();
|
|
1919
2318
|
}
|
|
2319
|
+
function safePermalinkHref(raw) {
|
|
2320
|
+
const t = raw.trim();
|
|
2321
|
+
if (t.length === 0)
|
|
2322
|
+
return null;
|
|
2323
|
+
if (/^(javascript|data):/i.test(t))
|
|
2324
|
+
return null;
|
|
2325
|
+
try {
|
|
2326
|
+
return new URL(t, globalThis.location.href).toString();
|
|
2327
|
+
}
|
|
2328
|
+
catch {
|
|
2329
|
+
return null;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
function humaneBrowseAliasPathForSource(sourcePath) {
|
|
2333
|
+
return sourcePath
|
|
2334
|
+
.split("/")
|
|
2335
|
+
.filter((seg) => seg.length > 0)
|
|
2336
|
+
.map((seg) => encodeURIComponent(seg))
|
|
2337
|
+
.join("/");
|
|
2338
|
+
}
|
|
2339
|
+
function companionStemFromCommentrayPath(commentrayPath) {
|
|
2340
|
+
const norm = normPosixPath(commentrayPath);
|
|
2341
|
+
const last = norm.split("/").filter(Boolean).at(-1) ?? "";
|
|
2342
|
+
return last.replace(/\.md$/i, "").trim();
|
|
2343
|
+
}
|
|
2344
|
+
function makeAbsoluteUrlAgainst(raw, baseHref) {
|
|
2345
|
+
return new URL(raw, baseHref).toString();
|
|
2346
|
+
}
|
|
2347
|
+
function shellEligibleForHumaneBackfill(shell, pathname) {
|
|
2348
|
+
if ((shell.getAttribute("data-layout") ?? "dual") !== "dual")
|
|
2349
|
+
return false;
|
|
2350
|
+
if (pathname.includes("/browse/"))
|
|
2351
|
+
return false;
|
|
2352
|
+
return pathname.endsWith("/") || pathname.endsWith("/index.html");
|
|
2353
|
+
}
|
|
2354
|
+
function nextHumaneBrowsePathForShell(shell, pathname) {
|
|
2355
|
+
const sourcePath = normPosixPath(shell.getAttribute("data-commentray-pair-source-path") ?? "");
|
|
2356
|
+
if (sourcePath.length === 0)
|
|
2357
|
+
return null;
|
|
2358
|
+
const alias = humaneBrowseAliasPathForSource(sourcePath);
|
|
2359
|
+
if (alias.length === 0)
|
|
2360
|
+
return null;
|
|
2361
|
+
const angleSel = document.getElementById("angle-select");
|
|
2362
|
+
const selectedAngle = angleSel instanceof HTMLSelectElement ? angleSel.value.trim() : "";
|
|
2363
|
+
const commentrayPath = shell.getAttribute("data-commentray-pair-commentray-path") ?? "";
|
|
2364
|
+
const stem = companionStemFromCommentrayPath(commentrayPath);
|
|
2365
|
+
const angleName = selectedAngle.length > 0 ? selectedAngle : stem;
|
|
2366
|
+
if (angleName.length === 0)
|
|
2367
|
+
return null;
|
|
2368
|
+
const angleAlias = `${alias}@${encodeURIComponent(angleName)}.html`;
|
|
2369
|
+
const siteRoot = siteRootPathnameFromPathname(pathname);
|
|
2370
|
+
return siteRoot === "/" ? `/browse/${angleAlias}` : `${siteRoot}/browse/${angleAlias}`;
|
|
2371
|
+
}
|
|
2372
|
+
function absolutizeNavJsonUrls(shell, beforeHref) {
|
|
2373
|
+
const navSearchRaw = shell.getAttribute("data-nav-search-json-url")?.trim() ?? "";
|
|
2374
|
+
if (navSearchRaw.length > 0) {
|
|
2375
|
+
shell.setAttribute("data-nav-search-json-url", makeAbsoluteUrlAgainst(navSearchRaw, beforeHref));
|
|
2376
|
+
}
|
|
2377
|
+
const navTree = document.getElementById("documented-files-hub");
|
|
2378
|
+
if (navTree instanceof HTMLElement) {
|
|
2379
|
+
const navRaw = navTree.getAttribute("data-nav-json-url")?.trim() ?? "";
|
|
2380
|
+
if (navRaw.length > 0) {
|
|
2381
|
+
navTree.setAttribute("data-nav-json-url", makeAbsoluteUrlAgainst(navRaw, beforeHref));
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
function normalizePairBrowseHrefForCurrentPath(shell, pathname) {
|
|
2386
|
+
const pairBrowseRaw = shell.getAttribute("data-commentray-pair-browse-href")?.trim() ?? "";
|
|
2387
|
+
if (pairBrowseRaw.length > 0 && isHubRelativeStaticBrowseHref(pairBrowseRaw)) {
|
|
2388
|
+
shell.setAttribute("data-commentray-pair-browse-href", resolveStaticBrowseHref(pairBrowseRaw, pathname, globalThis.location.origin));
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
function normalizeDocumentationHomeHrefForCurrentPath() {
|
|
2392
|
+
const home = document.querySelector('a[aria-label="Documentation home"]');
|
|
2393
|
+
if (!(home instanceof HTMLAnchorElement))
|
|
2394
|
+
return;
|
|
2395
|
+
const siteRoot = siteRootPathnameFromPathname(globalThis.location.pathname);
|
|
2396
|
+
const normalized = siteRoot === "/" ? "/" : `${siteRoot}/`;
|
|
2397
|
+
home.setAttribute("href", normalized);
|
|
2398
|
+
}
|
|
2399
|
+
function activeCommentrayHashTokenFromViewport() {
|
|
2400
|
+
const docPane = document.getElementById("doc-pane");
|
|
2401
|
+
if (!(docPane instanceof HTMLElement))
|
|
2402
|
+
return null;
|
|
2403
|
+
const docBody = document.getElementById("doc-pane-body");
|
|
2404
|
+
const docScrollEl = docBody instanceof HTMLElement ? docBody : docPane;
|
|
2405
|
+
const anchors = docScrollEl.querySelectorAll(".commentray-block-anchor");
|
|
2406
|
+
if (anchors.length === 0)
|
|
2407
|
+
return null;
|
|
2408
|
+
const mdLine0 = probeCommentrayLine0FromDoc(docScrollEl);
|
|
2409
|
+
if (!Number.isFinite(mdLine0) || mdLine0 < 0)
|
|
2410
|
+
return null;
|
|
2411
|
+
return `commentray-md-line-${String(mdLine0)}`;
|
|
2412
|
+
}
|
|
2413
|
+
function maybeBackfillAddressBarWithHumanePairLink() {
|
|
2414
|
+
const shell = document.getElementById("shell");
|
|
2415
|
+
if (!(shell instanceof HTMLElement))
|
|
2416
|
+
return;
|
|
2417
|
+
const pathname = globalThis.location.pathname;
|
|
2418
|
+
normalizeDocumentationHomeHrefForCurrentPath();
|
|
2419
|
+
if (!shellEligibleForHumaneBackfill(shell, pathname))
|
|
2420
|
+
return;
|
|
2421
|
+
const nextPath = nextHumaneBrowsePathForShell(shell, pathname);
|
|
2422
|
+
if (nextPath === null)
|
|
2423
|
+
return;
|
|
2424
|
+
const beforeHref = globalThis.location.href;
|
|
2425
|
+
absolutizeNavJsonUrls(shell, beforeHref);
|
|
2426
|
+
normalizePairBrowseHrefForCurrentPath(shell, pathname);
|
|
2427
|
+
globalThis.history.replaceState(null, "", `${nextPath}${globalThis.location.search}${globalThis.location.hash}`);
|
|
2428
|
+
}
|
|
2429
|
+
function permalinkHashSuffixFromUi() {
|
|
2430
|
+
const tokens = [];
|
|
2431
|
+
const pushUnique = (token) => {
|
|
2432
|
+
const t = token.trim();
|
|
2433
|
+
if (t.length === 0)
|
|
2434
|
+
return;
|
|
2435
|
+
if (!tokens.includes(t))
|
|
2436
|
+
tokens.push(t);
|
|
2437
|
+
};
|
|
2438
|
+
const angleSel = document.getElementById("angle-select");
|
|
2439
|
+
if (angleSel instanceof HTMLSelectElement) {
|
|
2440
|
+
const id = angleSel.value.trim();
|
|
2441
|
+
if (id.length > 0) {
|
|
2442
|
+
pushUnique(`angle-${encodeURIComponent(id)}`);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
const activeAnchor = activeCommentrayHashTokenFromViewport();
|
|
2446
|
+
if (activeAnchor)
|
|
2447
|
+
pushUnique(activeAnchor);
|
|
2448
|
+
return tokens.length > 0 ? `#${tokens.join("&")}` : "";
|
|
2449
|
+
}
|
|
2450
|
+
function sharePermalinkFromShell(shell) {
|
|
2451
|
+
const raw = shell.getAttribute("data-commentray-pair-browse-href") ?? "";
|
|
2452
|
+
const canonical = isHubRelativeStaticBrowseHref(raw.trim()) && raw.trim().length > 0
|
|
2453
|
+
? resolveStaticBrowseHref(raw.trim(), globalThis.location.pathname, globalThis.location.origin)
|
|
2454
|
+
: safePermalinkHref(raw);
|
|
2455
|
+
const base = canonical ?? globalThis.location.href;
|
|
2456
|
+
const u = new URL(base, globalThis.location.href);
|
|
2457
|
+
const hash = permalinkHashSuffixFromUi();
|
|
2458
|
+
u.hash = hash.length > 0 ? hash.slice(1) : "";
|
|
2459
|
+
return u.toString();
|
|
2460
|
+
}
|
|
2461
|
+
async function writeTextToClipboard(text) {
|
|
2462
|
+
try {
|
|
2463
|
+
await navigator.clipboard.writeText(text);
|
|
2464
|
+
return true;
|
|
2465
|
+
}
|
|
2466
|
+
catch {
|
|
2467
|
+
const ta = document.createElement("textarea");
|
|
2468
|
+
ta.value = text;
|
|
2469
|
+
ta.setAttribute("readonly", "true");
|
|
2470
|
+
ta.style.position = "fixed";
|
|
2471
|
+
ta.style.top = "-1000px";
|
|
2472
|
+
ta.style.left = "-1000px";
|
|
2473
|
+
document.body.appendChild(ta);
|
|
2474
|
+
ta.select();
|
|
2475
|
+
try {
|
|
2476
|
+
return document.execCommand("copy");
|
|
2477
|
+
}
|
|
2478
|
+
catch {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
finally {
|
|
2482
|
+
document.body.removeChild(ta);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
function wireSharePermalinkButton() {
|
|
2487
|
+
const shell = document.getElementById("shell");
|
|
2488
|
+
const btn = document.getElementById("commentray-share-link");
|
|
2489
|
+
if (!(shell instanceof HTMLElement) || !(btn instanceof HTMLButtonElement))
|
|
2490
|
+
return;
|
|
2491
|
+
const baseLabel = "Copy shareable permalink";
|
|
2492
|
+
let copiedTimer;
|
|
2493
|
+
btn.addEventListener("click", () => {
|
|
2494
|
+
void (async () => {
|
|
2495
|
+
const shareUrl = sharePermalinkFromShell(shell);
|
|
2496
|
+
const copied = await writeTextToClipboard(shareUrl);
|
|
2497
|
+
if (!copied)
|
|
2498
|
+
return;
|
|
2499
|
+
btn.dataset.copied = "true";
|
|
2500
|
+
btn.setAttribute("aria-label", "Permalink copied");
|
|
2501
|
+
btn.title = "Permalink copied";
|
|
2502
|
+
if (copiedTimer !== undefined)
|
|
2503
|
+
globalThis.clearTimeout(copiedTimer);
|
|
2504
|
+
copiedTimer = globalThis.setTimeout(() => {
|
|
2505
|
+
delete btn.dataset.copied;
|
|
2506
|
+
btn.setAttribute("aria-label", baseLabel);
|
|
2507
|
+
btn.title = baseLabel;
|
|
2508
|
+
}, 1200);
|
|
2509
|
+
})();
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
1920
2512
|
function main() {
|
|
2513
|
+
wireSharePermalinkButton();
|
|
1921
2514
|
wireColorThemeToolbar();
|
|
1922
2515
|
wireDocumentedFilesTree();
|
|
1923
2516
|
const shell = document.getElementById("shell");
|
|
@@ -1925,12 +2518,16 @@ function main() {
|
|
|
1925
2518
|
if (!shell || !codePane) {
|
|
1926
2519
|
return;
|
|
1927
2520
|
}
|
|
2521
|
+
applyPageBreakFeatureToggle(shell);
|
|
2522
|
+
wireResponsivePageBreakHeight(shell);
|
|
2523
|
+
wireWideModeIntroTrigger(shell);
|
|
1928
2524
|
const layout = shell.getAttribute("data-layout") || "dual";
|
|
1929
2525
|
if (layout === "stretch") {
|
|
1930
2526
|
wireStretchLayoutChrome(codePane);
|
|
1931
2527
|
return;
|
|
1932
2528
|
}
|
|
1933
2529
|
wireDualPaneCodeBrowser(shell, codePane);
|
|
2530
|
+
maybeBackfillAddressBarWithHumanePairLink();
|
|
1934
2531
|
}
|
|
1935
2532
|
if (document.readyState === "loading") {
|
|
1936
2533
|
document.addEventListener("DOMContentLoaded", main);
|