koffi
Version:
Fast and simple C FFI (foreign function interface) for Node.js
162 lines (135 loc) • 4.78 kB
JavaScript
// Copyright (C) 2024 Niels Martignène <niels.martignene@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
window.addEventListener('load', e => {
initDeploy();
initMenu();
initSide();
initScroll();
});
function initDeploy() {
let el = document.querySelector('#deploy');
if (el != null)
el.addEventListener('click', deploy);
}
function deploy() {
let top = document.querySelector('nav#top');
top.classList.toggle('active');
}
function initMenu() {
let items = document.querySelectorAll('nav#top li');
document.body.addEventListener('click', e => {
if (e.target.tagName == 'DIV' && findParent(e.target, el => el.id == 'top'))
return;
// Expand top menu categories
{
let target = findParent(e.target, el => el.tagName == 'LI');
if (target?.querySelector('div') != null) {
for (let item of items) {
if (item == target) {
item.classList.toggle('active');
} else {
item.classList.remove('active');
}
}
}
}
if (e.target.tagName == 'A' && e.target.getAttribute('href') == '#')
e.preventDefault();
});
}
function initSide() {
// Find all side menu items, and the pointed to elements
let links = [].slice.call(document.querySelectorAll('nav#side a'));
let items = [].slice.call(links).map(link => {
let anchor = document.querySelector(link.getAttribute('href'));
return [link, anchor];
});
if (!items.length)
return;
let pending_request = false;
let active_idx = null;
function highlight(idx) {
if (pending_request)
return;
window.requestAnimationFrame(() => {
pending_request = false;
if (idx !== active_idx) {
if (active_idx !== null)
items[active_idx][0].classList.remove('active');
items[idx][0].classList.add('active');
active_idx = idx;
}
});
pending_request = true;
}
// Event handlers, for clicks
let ignore_scroll = false;
function highlightOnClick(idx) {
highlight(idx);
setTimeout(() => { ignore_scroll = false; }, 50);
ignore_scroll = true;
}
function highlightOnScroll() {
if (ignore_scroll)
return;
let style = window.getComputedStyle(document.documentElement);
let treshold = parseFloat(style.getPropertyValue('scroll-padding-top')) + 10;
let idx;
for (idx = 0; idx < items.length; idx++) {
let rect = items[idx][1].getBoundingClientRect();
if (rect.top >= treshold)
break;
}
if (idx)
idx--;
highlight(idx);
}
// The scroll handler is not enough because it does not work for
// small sections at the end of the page.
for (let i = 0; i < items.length; i++) {
items[i][0].addEventListener('click', highlightOnClick.bind(this, i));
}
// Enable only on big screens, when the side menu is fixed
let mql = window.matchMedia('(min-width: 801px)');
if (mql.matches) {
window.addEventListener('scroll', highlightOnScroll);
highlightOnScroll();
}
mql.addListener(mql => {
if (mql.matches) {
window.addEventListener('scroll', highlightOnScroll);
highlightOnScroll();
} else {
window.removeEventListener('scroll', highlightOnScroll);
}
});
}
function initScroll() {
window.addEventListener('scroll', adjust_top);
adjust_top();
function adjust_top() {
let top = document.querySelector('nav#top');
if (top != null)
top.classList.toggle('border', window.pageYOffset >= 20);
}
}
function findParent(el, func) {
while (el && !func(el))
el = el.parentElement;
return el;
}
document.documentElement.classList.remove('nojs');
document.documentElement.classList.add('js');
export { deploy }