elixir/static/script.js
Franciszek Stachura c8d088834d web: Make mobile sidebar state separate
...from desktop sidebar state.

Fixes #331
2024-09-20 14:33:01 +02:00

280 lines
8.9 KiB
JavaScript

/* Tags menu filter */
var versions = document.querySelector('.versions')
var dropdown = document.querySelector('.select-projects')
var input = document.querySelector('.filter-input')
var sidebar = document.querySelector('.sidebar')
var nav = document.querySelector('.sidebar nav')
var results = document.querySelector('.filter-results')
var tags = {}
function getTags () {
var list = document.querySelectorAll('.versions a')
for (var i = 0, l = list.length; i < l; i++) {
tags[list[i].innerText] = list[i].href
}
}
getTags()
function displayFilter (filter) {
var filtered = document.createDocumentFragment()
var reg = new RegExp(filter, 'i')
for (var key in tags) {
if (tags.hasOwnProperty(key)) {
var ok = false
var h = key.replace(reg, function (_) {
if (_) ok = true
return '<strong>' + _ + '</strong>'
})
if (ok) {
var a = document.createElement('a')
a.href = tags[key]
a.innerHTML = h
filtered.appendChild(a)
}
}
}
results.innerHTML = ''
results.appendChild(filtered)
}
input.oninput = function () {
if (this.value === '') {
versions.classList.remove('hide')
results.innerHTML = ''
} else {
versions.classList.add('hide')
displayFilter(this.value)
}
}
// prevent chrome auto-scrolling to element
var arr = []
arr.forEach.call(document.querySelectorAll('input'), function(el) {
el.onkeydown = function (e) {
var before = wrapper.scrollTop
function reset() {
wrapper.scrollTop = before
}
window.requestAnimationFrame(reset)
setTimeout(reset, 0)
}
})
/* Tags menu tree */
// Expand/Collapse tree
versions.onclick = function (e) {
if (e.target && e.target.nodeName == 'SPAN') {
e.target.classList.toggle('active')
}
}
var tag = document.querySelector('.version em')
var openMenu = document.querySelector('.open-menu')
var wrapper = document.querySelector('.wrapper')
openMenu.onclick = tag.onclick = function (e) {
e.preventDefault();
const isWidescreen = getComputedStyle(document.documentElement).getPropertyValue('--is-widescreen') === 'true';
if(isWidescreen) {
const hasShowMenu = document.documentElement.classList.contains('show-menu');
window.localStorage.setItem('show-sidebar', !hasShowMenu);
document.documentElement.classList.toggle('show-menu');
} else {
document.documentElement.classList.toggle('show-menu-mobile');
}
}
sidebar.onclick = function (e) {
if (e.target === this) {
document.documentElement.classList.remove('show-menu');
window.localStorage.setItem('show-sidebar', false);
} else if (e.target.classList.contains('close-menu')) {
document.documentElement.classList.remove('show-menu-mobile');
}
}
/* Linenumbers navigation */
document.querySelector('.go-top').onclick = function() {
wrapper.scrollTop = 0
wrapper.scrollLeft = 0
}
// When using linenumbers's anchor
// it jump the line a the top of the page
// and it's hidden under the fixed topbar element.
// To prevent this let's jump to a few lines behind the top
// This will capture hash changes while on the page
function offsetAnchor(e) {
if (e && e.preventDefault) e.preventDefault()
if (location.hash.length !== 0) {
var el = document.querySelector(location.hash)
if (el) {
var offsetTop = el.offsetTop
wrapper.scrollTop = offsetTop < 100 ? 200 : offsetTop + 100
}
}
}
window.onhashchange = offsetAnchor
// This is here so that when you enter the page with a hash,
// 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 range between (and including) line numbers, scrolls to the
// 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 firstLine = parseInt(firstLineElement.id.substring(1));
let lastLine = parseInt(lastLineElement.id.substring(1));
console.assert(!isNaN(firstLine) && !isNaN(lastLine),
"Elements to highlight have invalid numbers in ids");
console.assert(firstLine < lastLine, "first highlight line is after last highlight line");
const firstCodeLine = document.getElementById(`codeline-${ firstLine }`);
const lastCodeLine = document.getElementById(`codeline-${ lastLine }`);
addClassToRangeOfElements(firstLineElement.parentNode, lastLineElement.parentNode, "line-highlight");
addClassToRangeOfElements(firstCodeLine, lastCodeLine, "line-highlight");
}
function addClassToRangeOfElements(first, last, class_name) {
let element = first;
const elementAfterLast = last !== null ? last.nextElementSibling : null;
while (element !== null && element != elementAfterLast) {
element.classList.add(class_name);
element = element.nextElementSibling;
}
}
// Sets up listeners on 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;
}
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
// TODO: remove after 10.2024
let path = location.pathname.split('/');
if (path.length == 4) {
path[2] = 'latest';
let newPath = path.join('/');
fetch(newPath, {
cache: 'reload',
redirect: 'manual',
// this is to make sure that varnish will cache the response,
// by default fetch sends no-cache in both headers if cache='reload'
headers: {'Cache-Control': 'max-age=86400', 'Pragma': ''}
})
.then(console.log)
.catch(console.error);
}
}