Add metro notification system in JS with styling, fix audio playback before first volume change, change tip.js to export function to register new element with hint.
This commit is contained in:
parent
9aedcfb31f
commit
e31bb2b40e
8 changed files with 164 additions and 70 deletions
60
.eleventy.js
60
.eleventy.js
|
|
@ -27,45 +27,47 @@ module.exports = function(eleventyConfig) {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
eleventyConfig.addPassthroughCopy("static");
|
eleventyConfig.addPassthroughCopy("static");
|
||||||
|
|
||||||
eleventyConfig.addNunjucksFilter("alternateLanguages", function(collection, postId, currentLanguageKey) {
|
eleventyConfig.addNunjucksFilter("alternateLanguages", function(collection, postId, currentLanguageKey) {
|
||||||
return collection.filter(post =>
|
return collection.filter(post =>
|
||||||
post.data.postId === postId && post.data.langKey !== currentLanguageKey
|
post.data.postId === postId && post.data.langKey !== currentLanguageKey
|
||||||
)
|
)
|
||||||
.map(post => ({
|
.map(post => ({
|
||||||
lang: post.data.langKey,
|
lang: post.data.langKey,
|
||||||
url: post.url,
|
url: post.url,
|
||||||
title: post.data.title
|
title: post.data.title
|
||||||
}))
|
}))
|
||||||
});
|
|
||||||
|
|
||||||
eleventyConfig.addFilter("absoluteUrl", function(path) {
|
|
||||||
const base = "https://adrianvic.github.io";
|
|
||||||
return base + path;
|
|
||||||
});
|
|
||||||
|
|
||||||
eleventyConfig.addFilter("postDate", (dateObj) => {
|
|
||||||
return dateObj.toLocaleString(undefined, {
|
|
||||||
year: "numeric",
|
|
||||||
month: "numeric",
|
|
||||||
day: "numeric",
|
|
||||||
timeZone: "America/Sao_Paulo"
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
eleventyConfig.addNunjucksFilter("smartTitle", function(str) {
|
eleventyConfig.addFilter("absoluteUrl", function(path) {
|
||||||
if (!str) return "";
|
const base = "https://adrianvic.github.io";
|
||||||
const smallWords = ["a","an","and","at","but","by","for","in","nor","of","on","or","so","the","to","up","yet",
|
return base + path;
|
||||||
"e","de","do","da","dos","das","a","o","um","uma","em","por","para","com","no","na","nos"];
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter("postDate", (dateObj) => {
|
||||||
|
if (!dateObj) return "";
|
||||||
|
return dateObj.toLocaleString(undefined, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "numeric",
|
||||||
|
day: "numeric",
|
||||||
|
timeZone: "America/Sao_Paulo"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addNunjucksFilter("smartTitle", function(str) {
|
||||||
|
if (!str) return "";
|
||||||
|
const smallWords = ["a","an","and","at","but","by","for","in","nor","of","on","or","so","the","to","up","yet",
|
||||||
|
"e","de","do","da","dos","das","a","o","um","uma","em","por","para","com","no","na","nos"];
|
||||||
return str.toLowerCase().split(" ").map((word, i) => {
|
return str.toLowerCase().split(" ").map((word, i) => {
|
||||||
if (i === 0) return word.charAt(0).toUpperCase() + word.slice(1);
|
if (i === 0) return word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
return smallWords.includes(word) ? word : word.charAt(0).toUpperCase() + word.slice(1);
|
return smallWords.includes(word) ? word : word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
}).join(" ");
|
}).join(" ");
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir: {
|
dir: {
|
||||||
output: "docs"
|
output: "docs"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,11 @@ module.exports = {
|
||||||
websiteDescription: "Personal website/blog of Adrian Victor.",
|
websiteDescription: "Personal website/blog of Adrian Victor.",
|
||||||
miscellaneous: "Miscellaneous",
|
miscellaneous: "Miscellaneous",
|
||||||
i88x31hover: "Click to expand",
|
i88x31hover: "Click to expand",
|
||||||
lastEditedIn: "last edited in"
|
lastEditedIn: "last edited in",
|
||||||
|
permissionIssue: "Permission issue",
|
||||||
|
permissionIssueNotificationContent: "Unable to continue playing background music, please enable audio <b>autoplay</b> for this website.",
|
||||||
|
notificationDefaultHint: "<b>Click to dismiss</b>"
|
||||||
|
|
||||||
},
|
},
|
||||||
pt: {
|
pt: {
|
||||||
language: "português",
|
language: "português",
|
||||||
|
|
@ -125,6 +129,9 @@ module.exports = {
|
||||||
websiteDescription: "Website/blog pessoal de Adrian Victor.",
|
websiteDescription: "Website/blog pessoal de Adrian Victor.",
|
||||||
miscellaneous: "Miscelâneo",
|
miscellaneous: "Miscelâneo",
|
||||||
i88x31hover: "Clique para expandir",
|
i88x31hover: "Clique para expandir",
|
||||||
lastEditedIn: "editado por último em"
|
lastEditedIn: "editado por último em",
|
||||||
|
permissionIssue: "Problema de permissão",
|
||||||
|
permissionIssueNotificationContent: "Não foi possivel continuar tocando a música de fundo, por favor habilite <b>reprodução automática</b> de áudio para esse website.",
|
||||||
|
notificationDefaultHint: "<b>Clique para ignorar</b>"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
by: "{{ i18n[langKey].by | safe }}",
|
by: "{{ i18n[langKey].by | safe }}",
|
||||||
options: "{{ i18n[langKey].options | safe }}",
|
options: "{{ i18n[langKey].options | safe }}",
|
||||||
hideBackground: "{{ i18n[langKey].hideBackground | safe }}",
|
hideBackground: "{{ i18n[langKey].hideBackground | safe }}",
|
||||||
back: "{{ i18n[langKey].back | safe }}"
|
back: "{{ i18n[langKey].back | safe }}",
|
||||||
|
permissionIssue: "{{ i18n[langKey].permissionIssue | safe }}",
|
||||||
|
permissionIssueNotificationContent: "{{ i18n[langKey].permissionIssueNotificationContent | safe }}",
|
||||||
|
notificationDefaultHint: "{{ i18n[langKey].notificationDefaultHint | safe }}",
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ title: Adrian Victor:Blog
|
||||||
<article>
|
<article>
|
||||||
<div id="postHeader">
|
<div id="postHeader">
|
||||||
<h1>{{ postTitle }}</h1>
|
<h1>{{ postTitle }}</h1>
|
||||||
<p>{{ authors or "Adrian Victor" }} - {{ date | postDate }}</p>
|
<p>{{ authors or "Adrian Victor" }} - <b>{{ date | postDate }}</b>{% if lastModified | postDate !== date | postDate %} ({{ i18n[langKey].lastEditedIn }} {{ lastModified | postDate }}){% endif %}</p>
|
||||||
{% if altLanguages.length > 0 %}
|
{% if altLanguages.length > 0 %}
|
||||||
{{ i18n[langKey].availableInOtherLanguages }}:
|
{{ i18n[langKey].availableInOtherLanguages }}:
|
||||||
{% for alt in altLanguages %}
|
{% for alt in altLanguages %}
|
||||||
|
|
|
||||||
|
|
@ -184,8 +184,7 @@ aside.metromenu {
|
||||||
width: 30vw;
|
width: 30vw;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
transition: 0.2s;
|
transition: transform 0.6s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
transition-timing-function: cubic-bezier(0.1, 0.2, 0.3, 0.955);
|
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,6 +212,28 @@ aside.metromenu #content {
|
||||||
|
|
||||||
/* Global classes and IDs */
|
/* 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 {
|
li.inlineList {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// This script handles the playback of music in the header's miniplayer ;)
|
// This script handles the playback of music in the header's miniplayer ;)
|
||||||
|
import { showNotification } from './notification.js';
|
||||||
const body = document.querySelector("body");
|
const body = document.querySelector("body");
|
||||||
|
|
||||||
const musicdiv = document.getElementById("music");
|
const musicdiv = document.getElementById("music");
|
||||||
|
|
@ -42,7 +43,7 @@ optionsAside.classList.add("metromenu");
|
||||||
<div id="playlist"></div>
|
<div id="playlist"></div>
|
||||||
<div>
|
<div>
|
||||||
<p>Volume</p>
|
<p>Volume</p>
|
||||||
<input id="volume" oninput="setVolume(this.value / 100)" type="range" min="0" max="100"></input>
|
<input id="volume" type="range" min="0" max="100"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<p>${headeri18n.hideBackground}</p>
|
<p>${headeri18n.hideBackground}</p>
|
||||||
|
|
@ -54,6 +55,9 @@ optionsAside.classList.add("metromenu");
|
||||||
}
|
}
|
||||||
body.appendChild(optionsAside);
|
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');
|
const toggleIMG = document.querySelector('#sound');
|
||||||
toggleIMG.addEventListener('click', () => {
|
toggleIMG.addEventListener('click', () => {
|
||||||
|
|
@ -146,11 +150,21 @@ let audio = new Audio(`/static/music/${audioSelect.value}`);
|
||||||
|
|
||||||
const savedTime = localStorage.getItem("audioTime");
|
const savedTime = localStorage.getItem("audioTime");
|
||||||
const savedVolume = localStorage.getItem("volume");
|
const savedVolume = localStorage.getItem("volume");
|
||||||
|
|
||||||
|
if (savedVolume !== null) {
|
||||||
|
audio.volume = parseFloat(savedVolume);
|
||||||
|
} else {
|
||||||
|
audio.volume = 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
const wasPlaying = localStorage.getItem("audioPlaying") === 'true';
|
const wasPlaying = localStorage.getItem("audioPlaying") === 'true';
|
||||||
|
|
||||||
function play() {
|
function play() {
|
||||||
audio.volume = localStorage.getItem("volume");
|
audio.volume = localStorage.getItem("volume") ?? 0.8;
|
||||||
audio.play();
|
audio.play().catch(() => {
|
||||||
|
stop();
|
||||||
|
showNotification(headeri18n.permissionIssue, headeri18n.permissionIssueNotificationContent, 5000);
|
||||||
|
});;
|
||||||
localStorage.setItem("audioPlaying", "true")
|
localStorage.setItem("audioPlaying", "true")
|
||||||
toggleIMG.src = "/static/images/sound-on.png"
|
toggleIMG.src = "/static/images/sound-on.png"
|
||||||
console.log(`[Music Player] playing ${audioSelect.value}`)
|
console.log(`[Music Player] playing ${audioSelect.value}`)
|
||||||
|
|
|
||||||
45
static/scripts/notification.js
Normal file
45
static/scripts/notification.js
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
if (isMobile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = document.querySelector('body');
|
const body = document.querySelector('body');
|
||||||
const elements = document.querySelectorAll('[data-tip]');
|
const elements = document.querySelectorAll('[data-tip]');
|
||||||
const hint = document.querySelector("#headerSubtitle");
|
const hint = document.querySelector("#headerSubtitle");
|
||||||
|
|
@ -10,36 +6,11 @@ const hintPanelDefaultText = hint.innerHTML;
|
||||||
let fixedHint;
|
let fixedHint;
|
||||||
let currentObserver;
|
let currentObserver;
|
||||||
|
|
||||||
elements.forEach(el => {
|
if (!isMobile) {
|
||||||
el.addEventListener('mouseenter', function() {
|
elements.forEach(el => {
|
||||||
cleanup();
|
registerElementHint(el);
|
||||||
|
})
|
||||||
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();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
hint.innerHTML = hintPanelDefaultText;
|
hint.innerHTML = hintPanelDefaultText;
|
||||||
|
|
@ -52,3 +23,34 @@ function cleanup() {
|
||||||
currentObserver = null;
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue