{{ postTitle }}
-{{ authors or "Adrian Victor" }} - {{ date | postDate }}
+{{ authors or "Adrian Victor" }} - {{ date | postDate }}{% if lastModified | postDate !== date | postDate %} ({{ i18n[langKey].lastEditedIn }} {{ lastModified | postDate }}){% endif %}
{% if altLanguages.length > 0 %} {{ i18n[langKey].availableInOtherLanguages }}: {% for alt in altLanguages %} diff --git a/static/main.css b/static/main.css index 09f0cb9..5db40e4 100644 --- a/static/main.css +++ b/static/main.css @@ -184,8 +184,7 @@ aside.metromenu { width: 30vw; background-color: black; height: 100vh; - transition: 0.2s; - transition-timing-function: cubic-bezier(0.1, 0.2, 0.3, 0.955); + transition: transform 0.6s cubic-bezier(0.19, 1, 0.22, 1); padding: 2em; } @@ -213,6 +212,28 @@ aside.metromenu #content { /* Global classes and IDs */ +.notificationBox { + transition: transform 1s cubic-bezier(0.19, 1, 0.22, 1); + width: fit-content; + position: fixed; + bottom: 2em; + left: 0; + background-color: black; + transform: translateX(-100%); + padding: 1em; + outline: thin solid var(--theme-color); + margin-right: 8em; + max-width: 40em; +} + +.notificationBox.shown { + transform: none; +} + +.notificationBox h1 { + font-size: x-large; +} + li.inlineList { display: inline; } diff --git a/static/scripts/music.js b/static/scripts/music.js index c11da94..0aedc43 100644 --- a/static/scripts/music.js +++ b/static/scripts/music.js @@ -1,4 +1,5 @@ // This script handles the playback of music in the header's miniplayer ;) +import { showNotification } from './notification.js'; const body = document.querySelector("body"); const musicdiv = document.getElementById("music"); @@ -42,7 +43,7 @@ optionsAside.classList.add("metromenu");Volume
- +${headeri18n.hideBackground}
@@ -54,6 +55,9 @@ optionsAside.classList.add("metromenu"); } body.appendChild(optionsAside); +document.getElementById("volume").addEventListener("input", (e) => { + setVolume(e.target.value / 100); +}); // dirty workaround to replace inline function calling in the volume input const toggleIMG = document.querySelector('#sound'); toggleIMG.addEventListener('click', () => { @@ -146,11 +150,21 @@ let audio = new Audio(`/static/music/${audioSelect.value}`); const savedTime = localStorage.getItem("audioTime"); const savedVolume = localStorage.getItem("volume"); + +if (savedVolume !== null) { + audio.volume = parseFloat(savedVolume); +} else { + audio.volume = 0.8; +} + const wasPlaying = localStorage.getItem("audioPlaying") === 'true'; function play() { - audio.volume = localStorage.getItem("volume"); - audio.play(); + audio.volume = localStorage.getItem("volume") ?? 0.8; + audio.play().catch(() => { + stop(); + showNotification(headeri18n.permissionIssue, headeri18n.permissionIssueNotificationContent, 5000); + });; localStorage.setItem("audioPlaying", "true") toggleIMG.src = "/static/images/sound-on.png" console.log(`[Music Player] playing ${audioSelect.value}`) diff --git a/static/scripts/notification.js b/static/scripts/notification.js new file mode 100644 index 0000000..a05b793 --- /dev/null +++ b/static/scripts/notification.js @@ -0,0 +1,45 @@ +import { registerElementHint } from "./tips.js"; + +const notificationBox = document.createElement('div'); +notificationBox.classList.add('notificationBox'); + +export async function showNotification(title, subtitle, time, hint) { + if (!hint) { + hint = headeri18n.notificationDefaultHint; + } + const notificationBox = document.createElement('div'); + notificationBox.classList.add('notificationBox'); + notificationBox.dataset.tip = hint; + + const notificationTitle = document.createElement('h1'); + notificationTitle.innerHTML = title; + + const notificationSubtitle = document.createElement('p'); + notificationSubtitle.innerHTML = subtitle; + + notificationBox.appendChild(notificationTitle); + notificationBox.appendChild(notificationSubtitle); + document.querySelector('body').appendChild(notificationBox); + + registerElementHint(notificationBox); + + let clicked = false; + + notificationBox.addEventListener('click', () => { + hideNotification(notificationBox); + }) + + requestAnimationFrame(() => { + notificationBox.classList.add('shown'); + }); + await new Promise(r => setTimeout(r, time)); + if (!clicked) { + hideNotification(notificationBox); + } +} + +async function hideNotification(notificationBox) { + notificationBox.classList.remove('shown'); + await new Promise(r => setTimeout(r, 1000)); + notificationBox.remove(); +} \ No newline at end of file diff --git a/static/scripts/tips.js b/static/scripts/tips.js index 8100cd6..acc69be 100644 --- a/static/scripts/tips.js +++ b/static/scripts/tips.js @@ -1,8 +1,4 @@ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); -if (isMobile) { - return; -} - const body = document.querySelector('body'); const elements = document.querySelectorAll('[data-tip]'); const hint = document.querySelector("#headerSubtitle"); @@ -10,36 +6,11 @@ const hintPanelDefaultText = hint.innerHTML; let fixedHint; let currentObserver; -elements.forEach(el => { - el.addEventListener('mouseenter', function() { - cleanup(); - - if (currentObserver) { - currentObserver.disconnect(); - } - - currentObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (!entry.isIntersecting) { - fixedHint = document.createElement('p'); - fixedHint.id = "fixedHint"; - fixedHint.innerHTML = el.dataset.tip; - fixedHint.setAttribute('aria-hidden', 'true'); - body.appendChild(fixedHint); - } else { - hint.innerHTML = el.dataset.tip; - } - }) - }); - - hint.innerHTML = el.dataset.tip; - currentObserver.observe(hint); - }); - - el.addEventListener('mouseleave', function() { - cleanup(); - }); -}) +if (!isMobile) { + elements.forEach(el => { + registerElementHint(el); + }) +} function cleanup() { hint.innerHTML = hintPanelDefaultText; @@ -52,3 +23,34 @@ function cleanup() { currentObserver = null; } } + +export function registerElementHint(el) { + el.addEventListener('mouseenter', function() { + cleanup(); + + if (currentObserver) { + currentObserver.disconnect(); + } + + currentObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (!entry.isIntersecting) { + fixedHint = document.createElement('p'); + fixedHint.id = "fixedHint"; + fixedHint.innerHTML = el.dataset.tip; + fixedHint.setAttribute('aria-hidden', 'true'); + body.appendChild(fixedHint); + } else { + hint.innerHTML = el.dataset.tip; + } + }) + }); + + hint.innerHTML = el.dataset.tip; + currentObserver.observe(hint); + }); + + el.addEventListener('mouseleave', function() { + cleanup(); + }); +} \ No newline at end of file