web: Add javascript based range links
Click line anchor and another line anchor holding shift to make a link to a range.
This commit is contained in:
parent
378dcf7e49
commit
3b4bcd852a
2 changed files with 125 additions and 0 deletions
117
static/script.js
117
static/script.js
|
|
@ -117,11 +117,128 @@ window.onhashchange = offsetAnchor
|
|||
// it can provide the offset in that case too.
|
||||
window.requestAnimationFrame(offsetAnchor)
|
||||
|
||||
// Parses URL hash (anchor) in format La-Lb where a and b are line numbers,
|
||||
// highlights line numbers, scrolls to first line number in range
|
||||
function handleLineRange(hashStr) {
|
||||
const hash = hashStr.substring(1).split("-");
|
||||
if (hash.length != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLineElement = document.getElementById(hash[0]);
|
||||
const lastLineElement = document.getElementById(hash[1]);
|
||||
if (firstLineElement === undefined || lastLineElement === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
highlightFromTo(firstLineElement, lastLineElement);
|
||||
firstLineElement.scrollIntoView();
|
||||
}
|
||||
|
||||
// Highlights line number elements from firstLineElement to lastLineElement
|
||||
function highlightFromTo(firstLineElement, lastLineElement) {
|
||||
let line = firstLineElement.parentNode;
|
||||
const afterLastLineElement = lastLineElement.parentNode.nextElementSibling;
|
||||
while (line !== null && line != afterLastLineElement) {
|
||||
line.firstChild.classList.add("line-highlight");
|
||||
line = line.nextElementSibling;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sets up listeners element that contains line numbers to handle
|
||||
// shift-clicks for range highlighting
|
||||
function setupLineRangeHandlers() {
|
||||
// Check if page contains the element with line numbers
|
||||
// If not, then likely script is not executed in context of the source page
|
||||
const linenodiv = document.querySelector(".linenodiv");
|
||||
if (linenodiv === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rangeStart, rangeEnd;
|
||||
linenodiv.addEventListener("click", ev => {
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
|
||||
// Handler is set on the element that contains all line numbers, check if the
|
||||
// event is directed at an actual line number element
|
||||
const el = ev.target;
|
||||
if (typeof(el.id) !== "string" || el.id[0] !== "L" || el.tagName !== "A") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove range highlight
|
||||
const highlightElements = Array.from(document.getElementsByClassName("line-highlight"));
|
||||
for (let el of highlightElements) {
|
||||
el.classList.remove("line-highlight");
|
||||
}
|
||||
|
||||
if (rangeStart === undefined || !ev.shiftKey) {
|
||||
rangeStart = el;
|
||||
rangeStart.classList.add("line-highlight");
|
||||
rangeEnd = undefined;
|
||||
window.location.hash = rangeStart.id;
|
||||
} else if(ev.shiftKey) {
|
||||
if (rangeEnd === undefined) {
|
||||
rangeEnd = el;
|
||||
highlightFromTo(rangeStart, rangeEnd);
|
||||
window.location.hash = `${rangeStart.id}-${rangeEnd.id}`;
|
||||
} else {
|
||||
let rangeStartNumber = parseInt(rangeStart.id.substring(1));
|
||||
let rangeEndNumber = parseInt(rangeEnd.id.substring(1));
|
||||
const elNumber = parseInt(el.id.substring(1));
|
||||
console.assert(!isNaN(rangeStartNumber) && !isNaN(rangeEndNumber) && !isNaN(elNumber),
|
||||
"Elements to highlight have invalid numbers in ids");
|
||||
|
||||
// Swap range elements to support "#L2-L1" format. Postel's law.
|
||||
if (rangeStartNumber > rangeEndNumber) {
|
||||
const rangeTmp = rangeStart;
|
||||
rangeStart = rangeEnd;
|
||||
rangeEnd = rangeTmp;
|
||||
|
||||
const numberTmp = rangeStartNumber;
|
||||
rangeStartNumber = rangeEndNumber;
|
||||
rangeEndNumber = numberTmp;
|
||||
}
|
||||
|
||||
if (elNumber < rangeStartNumber) {
|
||||
// Expand if element above range
|
||||
rangeStart = el;
|
||||
} else if (elNumber > rangeEndNumber) {
|
||||
// Expand if element below range
|
||||
rangeEnd = el;
|
||||
} else {
|
||||
// Shrink moving the edge that's closest to the selection.
|
||||
// Move end if center was selected.
|
||||
const distanceFromStart = Math.abs(rangeStartNumber-elNumber);
|
||||
const distanceFromEnd = Math.abs(rangeEndNumber-elNumber);
|
||||
if (distanceFromStart < distanceFromEnd) {
|
||||
rangeStart = el;
|
||||
} else if (distanceFromStart > distanceFromEnd) {
|
||||
rangeEnd = el;
|
||||
} else {
|
||||
rangeEnd = el;
|
||||
}
|
||||
}
|
||||
|
||||
highlightFromTo(rangeStart, rangeEnd);
|
||||
window.location.hash = `${rangeStart.id}-${rangeEnd.id}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// recalculate scroll when page is fully loaded
|
||||
// in case of slow rendering very long pages.
|
||||
window.onload = function () {
|
||||
window.requestAnimationFrame(offsetAnchor)
|
||||
|
||||
handleLineRange(window.location.hash);
|
||||
setupLineRangeHandlers();
|
||||
|
||||
// fix incorrectly issued 301 redirect
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect
|
||||
|
|
|
|||
|
|
@ -776,9 +776,17 @@ h2 {
|
|||
display: inline-block;
|
||||
padding: 0 1em;
|
||||
width: 100%;
|
||||
scroll-margin: 15vh;
|
||||
scroll-margin-top: 15vh;
|
||||
}
|
||||
.line-highlight {
|
||||
background: #ccc;
|
||||
}
|
||||
.linenodiv pre a.line-highlight,
|
||||
.linenodiv pre a:target {
|
||||
color: #444;
|
||||
}
|
||||
.linenodiv pre a:target {
|
||||
background: #ccc;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue