Spaces:
Sleeping
Sleeping
| // Event listener helper function | |
| function addEvent(element, event, handler) { | |
| if (element?.attachEvent) { | |
| return element.attachEvent('on' + event, handler); | |
| } | |
| return element?.addEventListener(event, handler, false); | |
| } | |
| // DOM element getters | |
| const getElement = id => document.getElementById(id); | |
| const getElements = selector => document.querySelectorAll(selector); | |
| // Logotype width calculation | |
| function recalcLogotypeWidth() { | |
| const logotype = getElement('logotype'); | |
| const logotypeText = getElement('logotype__text'); | |
| logotype?.setAttribute('width', `${logotypeText.getBoundingClientRect().width}px`); | |
| } | |
| // Viewport adaptation | |
| function adaptViewport() { | |
| // fix logotype when font loading delayed | |
| document.fonts.ready.then(() => { | |
| recalcLogotypeWidth(); | |
| }); | |
| if (window.innerWidth < 640) { | |
| getElement('has-search')?.setAttribute('open', 'open'); | |
| getElement('has-search')?.removeAttribute('name'); | |
| getElement('has-more-menu')?.setAttribute('open', 'open'); | |
| } else { | |
| getElement('top-nav')?.setAttribute('open', 'open') | |
| addEvent(visualViewport, 'resize', adaptViewport); | |
| } | |
| } | |
| addEvent(window, 'DOMContentLoaded', adaptViewport); | |
| // Node collapse handlers | |
| const collapseParentNode = getElements('.js-cpn'); | |
| const collapseGrandParentNode = getElements('.js-cgpn'); | |
| const detailsElements = getElements('details.js-details'); | |
| collapseParentNode.forEach(element => { | |
| const handler = () => element.parentNode?.removeAttribute('open'); | |
| addEvent(element, 'click', handler); | |
| }); | |
| collapseGrandParentNode.forEach(element => { | |
| const handler = () => element.parentNode?.parentNode?.removeAttribute('open'); | |
| addEvent(element, 'click', handler); | |
| }); | |
| if (window.innerWidth > 640) { | |
| // Details element handler for firefox based browsers which do not respect the same name attribute | |
| detailsElements.forEach(detail => { | |
| const handler = (e) => { | |
| const name = detail.getAttribute('name'); | |
| if (name) { | |
| getElements(`details.js-details[name="${name}"]`).forEach(otherDetail => { | |
| if (otherDetail !== detail && otherDetail.hasAttribute('open')) { | |
| otherDetail.removeAttribute('open'); | |
| } | |
| }); | |
| } | |
| }; | |
| addEvent(detail, 'click', handler); | |
| }); | |
| } | |
| // Share functionality | |
| if (typeof navigatorShare !== 'undefined') { | |
| getElement('navigatorShare')?.setAttribute( | |
| 'href', | |
| 'javascript:navigator.share({title: document.title, url: window.location.href})' | |
| ); | |
| if (location.protocol === 'https:') { | |
| getElement('copyPermalink')?.removeAttribute('class'); | |
| } | |
| } | |
| getElement('print-button')?.removeAttribute('class'); | |
| getElement('back')?.removeAttribute('class'); | |
| // Date handling | |
| const date = new Date(); | |
| // Mastodon and QR code functionality | |
| if (typeof mastodonInstance !== 'undefined') { | |
| getElement('has-mastodon').className = 'active'; | |
| const mastodonHandler = () => { | |
| mastodonTitle.disabled = true; | |
| mastodonPermalink.disabled = true; | |
| mastodonText.disabled = false; | |
| mastodon?.setAttribute('action', `${mastodonInstance.value}/share`); | |
| }; | |
| addEvent(mastodonInstance, 'input', mastodonHandler); | |
| if (typeof QRCode !== 'undefined') { | |
| getElement('colophon').removeAttribute('style'); | |
| qr?.appendChild( | |
| QRCode({ | |
| msg: window.location.href, | |
| ecl: 'M', | |
| pal: ['#000', '#fff'], | |
| pad: 2, | |
| dim: 96, | |
| }) | |
| ); | |
| const isoTime = date.toISOString(); | |
| const timeStamp = getElement('time-stamp'); | |
| timeStamp.innerHTML = isoTime; | |
| timeStamp?.setAttribute('datetime', isoTime); | |
| } | |
| } | |
| // Digital well-being clock | |
| const hour = date.getHours(); | |
| const isDaytime = hour > 6 && hour < 21; | |
| function toggleNightElements(hidden) { | |
| const elements = ['grain', 'dwclock']; | |
| elements.forEach(id => { | |
| const element = getElement(id); | |
| element?.[hidden ? 'setAttribute' : 'removeAttribute']('hidden', 'hidden'); | |
| }); | |
| } | |
| if (isDaytime) { | |
| toggleNightElements(true); | |
| } else { | |
| toggleNightElements(false); | |
| let clockInterval; | |
| function updateClock() { | |
| const minutes = date.getMinutes(); | |
| const seconds = date.getSeconds(); | |
| const minutesDegrees = ((minutes / 60) * 360) + ((seconds/60)*6); | |
| const hourDegrees = ((hour / 12) * 360) + ((minutes/60)*30); | |
| const transforms = ['transform', 'webkitTransform', 'mozTransform', 'msTransform', 'oTransform']; | |
| const hands = { | |
| '#min': minutesDegrees, | |
| '#hour': hourDegrees | |
| }; | |
| Object.entries(hands).forEach(([selector, degrees]) => { | |
| const hand = document.querySelector(selector); | |
| transforms.forEach(transform => { | |
| hand.style[transform] = `rotate(${degrees}deg)`; | |
| }); | |
| }); | |
| } | |
| updateClock(); | |
| clockInterval = setInterval(updateClock, 10000); | |
| } | |
| // Overwrite browser search bar (canceled) | |
| // addEvent(document, 'keydown', function(e) { | |
| // if (e.ctrlKey && e.key === 'k') { | |
| // e.preventDefault(); | |
| // getElement('has-search')?.setAttribute('open', 'open'); | |
| // const searchInput = document.querySelector('.pagefind-ui__search-input'); | |
| // searchInput?.focus(); | |
| // } | |
| // }); | |
| // clashes with details handler, need workaround | |
| addEvent(window, 'beforeprint', function() { | |
| getElements('[name="redaction-history"]')?.forEach(e => { | |
| e.setAttribute('open', 'open'); | |
| }); | |
| }); | |