summaryrefslogtreecommitdiff
path: root/vrsite/assets/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'vrsite/assets/app.js')
-rw-r--r--vrsite/assets/app.js49
1 files changed, 49 insertions, 0 deletions
diff --git a/vrsite/assets/app.js b/vrsite/assets/app.js
index 79dc190..104cc85 100644
--- a/vrsite/assets/app.js
+++ b/vrsite/assets/app.js
@@ -138,6 +138,55 @@ for (const ta of document.querySelectorAll(".notform textarea"))
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) ta.form.requestSubmit();
});
+// ── conversation ──────────────────────────────────────────────────────────
+// A session page opened from a note lands on that note, flashed the same way
+// clicking a noted line flashes it.
+const conv = document.querySelector(".conv[data-anchor]");
+if (conv && conv.dataset.anchor) {
+ const here = document.getElementById(conv.dataset.anchor);
+ if (here) {
+ here.scrollIntoView({block: "start"});
+ here.classList.remove("flash"); void here.offsetWidth; here.classList.add("flash");
+ }
+}
+
+// The trip back: a note in a conversation opens the code it was written
+// about. The card is the target, so aim anywhere on it — except at the links
+// it already carries.
+const goTo = w => { location.href = w.dataset.href; };
+for (const w of document.querySelectorAll(".notewrap[data-href]")) {
+ w.addEventListener("click", e => { if (!e.target.closest("a")) goTo(w); });
+ w.addEventListener("keydown", e => {
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); goTo(w); }
+ });
+}
+
+// ── arriving at a note ────────────────────────────────────────────────────
+// ?note=N means someone followed a note here rather than opening the file:
+// show what they came for — notes focus (coverage hides the note), the card
+// flashed in the pane, the lines it covers marked.
+const want = new URLSearchParams(location.search).get("note");
+if (want && document.body.dataset.kind === "file" && /^\d+$/.test(want)) {
+ setMode("notes");
+ const card = document.getElementById("note-" + want);
+ if (card) {
+ // the card's own range, not the lines' data-note: a line belongs to the
+ // first note that claimed it, so overlapping notes would mark nothing
+ const from = +card.dataset.start, to = +card.dataset.end || +card.dataset.start;
+ let first = null;
+ for (let n = from; n > 0 && n <= to; n++) {
+ const li = document.getElementById("L" + n);
+ if (!li) continue;
+ li.classList.add("focus");
+ first = first || li;
+ }
+ // after the browser has had its own go at the #L anchor
+ if (first) requestAnimationFrame(() => first.scrollIntoView({block: "center"}));
+ card.scrollIntoView({block: "nearest"});
+ card.classList.remove("flash"); void card.offsetWidth; card.classList.add("flash");
+ }
+}
+
// ── note filters ──────────────────────────────────────────────────────────
// Live pages let the server filter (htmx); the static export filters the
// rendered list in place, so the same form works either way.