mirror of
https://github.com/miniflux/v2.git
synced 2025-09-30 19:22:11 +00:00
chore(js): format JavaScript code with Prettier
This commit is contained in:
parent
cce0e7bd29
commit
f0575d64bc
10 changed files with 162 additions and 128 deletions
4
internal/ui/static/js/.prettierrc.json
Normal file
4
internal/ui/static/js/.prettierrc.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a new tab with the given URL.
|
* Open a new tab with the given URL.
|
||||||
*
|
*
|
||||||
|
@ -34,7 +33,11 @@ function scrollPageTo(element, evenIfOnScreen) {
|
||||||
const viewportPosition = windowScrollPosition + windowHeight;
|
const viewportPosition = windowScrollPosition + windowHeight;
|
||||||
const itemBottomPosition = element.offsetTop + element.offsetHeight;
|
const itemBottomPosition = element.offsetTop + element.offsetHeight;
|
||||||
|
|
||||||
if (evenIfOnScreen || viewportPosition - itemBottomPosition < 0 || viewportPosition - element.offsetTop > windowHeight) {
|
if (
|
||||||
|
evenIfOnScreen ||
|
||||||
|
viewportPosition - itemBottomPosition < 0 ||
|
||||||
|
viewportPosition - element.offsetTop > windowHeight
|
||||||
|
) {
|
||||||
window.scrollTo(0, element.offsetTop - 10);
|
window.scrollTo(0, element.offsetTop - 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +79,7 @@ function checkMenuToggleModeByLayout() {
|
||||||
logoElement.setAttribute("role", "button");
|
logoElement.setAttribute("role", "button");
|
||||||
logoElement.setAttribute("tabindex", "0");
|
logoElement.setAttribute("tabindex", "0");
|
||||||
logoElement.setAttribute("aria-label", logoToggleButtonLabel);
|
logoElement.setAttribute("aria-label", logoToggleButtonLabel);
|
||||||
logoElement.setAttribute("aria-expanded", navMenuElementIsExpanded?"true":"false");
|
logoElement.setAttribute("aria-expanded", navMenuElementIsExpanded ? "true" : "false");
|
||||||
homePageLinkElement.setAttribute("tabindex", "-1");
|
homePageLinkElement.setAttribute("tabindex", "-1");
|
||||||
} else {
|
} else {
|
||||||
logoElement.removeAttribute("role");
|
logoElement.removeAttribute("role");
|
||||||
|
@ -91,10 +94,10 @@ function fixVoiceOverDetailsSummaryBug() {
|
||||||
document.querySelectorAll("details").forEach((details) => {
|
document.querySelectorAll("details").forEach((details) => {
|
||||||
const summaryElement = details.querySelector("summary");
|
const summaryElement = details.querySelector("summary");
|
||||||
summaryElement.setAttribute("role", "button");
|
summaryElement.setAttribute("role", "button");
|
||||||
summaryElement.setAttribute("aria-expanded", details.open? "true": "false");
|
summaryElement.setAttribute("aria-expanded", details.open ? "true" : "false");
|
||||||
|
|
||||||
details.addEventListener("toggle", () => {
|
details.addEventListener("toggle", () => {
|
||||||
summaryElement.setAttribute("aria-expanded", details.open? "true": "false");
|
summaryElement.setAttribute("aria-expanded", details.open ? "true" : "false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -182,6 +185,7 @@ function markPageAsRead() {
|
||||||
/**
|
/**
|
||||||
* Handle entry status changes from the list view and entry view.
|
* Handle entry status changes from the list view and entry view.
|
||||||
* Focus the next or the previous entry if it exists.
|
* Focus the next or the previous entry if it exists.
|
||||||
|
*
|
||||||
* @param {string} item Item to focus: "previous" or "next".
|
* @param {string} item Item to focus: "previous" or "next".
|
||||||
* @param {Element} element
|
* @param {Element} element
|
||||||
* @param {boolean} setToRead
|
* @param {boolean} setToRead
|
||||||
|
@ -193,14 +197,14 @@ function handleEntryStatus(item, element, setToRead) {
|
||||||
if (!setToRead || currentEntry.querySelector(":is(a, button)[data-toggle-status]").dataset.value === "unread") {
|
if (!setToRead || currentEntry.querySelector(":is(a, button)[data-toggle-status]").dataset.value === "unread") {
|
||||||
toggleEntryStatus(currentEntry, toasting);
|
toggleEntryStatus(currentEntry, toasting);
|
||||||
}
|
}
|
||||||
if (isListView() && currentEntry.classList.contains('current-item')) {
|
if (isListView() && currentEntry.classList.contains("current-item")) {
|
||||||
switch (item) {
|
switch (item) {
|
||||||
case "previous":
|
case "previous":
|
||||||
goToListItem(-1);
|
goToListItem(-1);
|
||||||
break;
|
break;
|
||||||
case "next":
|
case "next":
|
||||||
goToListItem(1);
|
goToListItem(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,8 +212,8 @@ function handleEntryStatus(item, element, setToRead) {
|
||||||
|
|
||||||
// Add an icon-label span element.
|
// Add an icon-label span element.
|
||||||
function appendIconLabel(element, labelTextContent) {
|
function appendIconLabel(element, labelTextContent) {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement("span");
|
||||||
span.classList.add('icon-label');
|
span.classList.add("icon-label");
|
||||||
span.textContent = labelTextContent;
|
span.textContent = labelTextContent;
|
||||||
element.appendChild(span);
|
element.appendChild(span);
|
||||||
}
|
}
|
||||||
|
@ -276,7 +280,7 @@ function updateEntriesStatus(entryIDs, status, callback) {
|
||||||
const request = new RequestBuilder(url);
|
const request = new RequestBuilder(url);
|
||||||
request.withBody({ entry_ids: entryIDs, status: status });
|
request.withBody({ entry_ids: entryIDs, status: status });
|
||||||
request.withCallback((resp) => {
|
request.withCallback((resp) => {
|
||||||
resp.json().then(count => {
|
resp.json().then((count) => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(resp);
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
@ -386,7 +390,7 @@ function handleFetchOriginalContent() {
|
||||||
|
|
||||||
const request = new RequestBuilder(buttonElement.dataset.fetchContentUrl);
|
const request = new RequestBuilder(buttonElement.dataset.fetchContentUrl);
|
||||||
request.withCallback((response) => {
|
request.withCallback((response) => {
|
||||||
buttonElement.textContent = '';
|
buttonElement.textContent = "";
|
||||||
buttonElement.appendChild(previousElement);
|
buttonElement.appendChild(previousElement);
|
||||||
|
|
||||||
response.json().then((data) => {
|
response.json().then((data) => {
|
||||||
|
@ -419,7 +423,7 @@ function openOriginalLink(openLinkInCurrentTab) {
|
||||||
|
|
||||||
const currentItem = document.querySelector(".current-item");
|
const currentItem = document.querySelector(".current-item");
|
||||||
// If we are not on the list of starred items, move to the next item
|
// If we are not on the list of starred items, move to the next item
|
||||||
if (document.location.href !== document.querySelector(':is(a, button)[data-page=starred]').href) {
|
if (document.location.href !== document.querySelector(":is(a, button)[data-page=starred]").href) {
|
||||||
goToListItem(1);
|
goToListItem(1);
|
||||||
}
|
}
|
||||||
markEntryAsRead(currentItem);
|
markEntryAsRead(currentItem);
|
||||||
|
@ -516,7 +520,7 @@ function goToFeedOrFeeds() {
|
||||||
if (isEntry()) {
|
if (isEntry()) {
|
||||||
goToFeed();
|
goToFeed();
|
||||||
} else {
|
} else {
|
||||||
goToPage('feeds');
|
goToPage("feeds");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,16 +605,13 @@ function updateUnreadCounterValue(callback) {
|
||||||
element.textContent = callback(oldValue);
|
element.textContent = callback(oldValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.location.href.endsWith('/unread')) {
|
if (window.location.href.endsWith("/unread")) {
|
||||||
const oldValue = parseInt(document.title.split('(')[1], 10);
|
const oldValue = parseInt(document.title.split("(")[1], 10);
|
||||||
const newValue = callback(oldValue);
|
const newValue = callback(oldValue);
|
||||||
|
|
||||||
document.title = document.title.replace(
|
document.title = document.title.replace(/(.*?)\(\d+\)(.*?)/, function (match, prefix, suffix, offset, string) {
|
||||||
/(.*?)\(\d+\)(.*?)/,
|
return prefix + "(" + newValue + ")" + suffix;
|
||||||
function (match, prefix, suffix, offset, string) {
|
});
|
||||||
return prefix + '(' + newValue + ')' + suffix;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,7 +634,7 @@ function findEntry(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirmationMessage(linkElement, callback) {
|
function handleConfirmationMessage(linkElement, callback) {
|
||||||
if (linkElement.tagName !== 'A' && linkElement.tagName !== "BUTTON") {
|
if (linkElement.tagName !== "A" && linkElement.tagName !== "BUTTON") {
|
||||||
linkElement = linkElement.parentNode;
|
linkElement = linkElement.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,9 +697,9 @@ function showToast(label, iconElement) {
|
||||||
appendIconLabel(toastMsgElement, label);
|
appendIconLabel(toastMsgElement, label);
|
||||||
|
|
||||||
const toastElementWrapper = document.getElementById("toast-wrapper");
|
const toastElementWrapper = document.getElementById("toast-wrapper");
|
||||||
toastElementWrapper.classList.remove('toast-animate');
|
toastElementWrapper.classList.remove("toast-animate");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toastElementWrapper.classList.add('toast-animate');
|
toastElementWrapper.classList.add("toast-animate");
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,8 +722,9 @@ function handlePlayerProgressionSaveAndMarkAsReadOnCompletion(playerElement) {
|
||||||
const recordInterval = 10;
|
const recordInterval = 10;
|
||||||
|
|
||||||
// we limit the number of update to only one by interval. Otherwise, we would have multiple update per seconds
|
// we limit the number of update to only one by interval. Otherwise, we would have multiple update per seconds
|
||||||
if (currentPositionInSeconds >= (lastKnownPositionInSeconds + recordInterval) ||
|
if (
|
||||||
currentPositionInSeconds <= (lastKnownPositionInSeconds - recordInterval)
|
currentPositionInSeconds >= lastKnownPositionInSeconds + recordInterval ||
|
||||||
|
currentPositionInSeconds <= lastKnownPositionInSeconds - recordInterval
|
||||||
) {
|
) {
|
||||||
playerElement.dataset.lastPosition = currentPositionInSeconds.toString();
|
playerElement.dataset.lastPosition = currentPositionInSeconds.toString();
|
||||||
const request = new RequestBuilder(playerElement.dataset.saveUrl);
|
const request = new RequestBuilder(playerElement.dataset.saveUrl);
|
||||||
|
@ -730,7 +732,7 @@ function handlePlayerProgressionSaveAndMarkAsReadOnCompletion(playerElement) {
|
||||||
request.execute();
|
request.execute();
|
||||||
// Handle the mark as read on completion
|
// Handle the mark as read on completion
|
||||||
if (markAsReadOnCompletion >= 0 && playerElement.duration > 0) {
|
if (markAsReadOnCompletion >= 0 && playerElement.duration > 0) {
|
||||||
const completion = currentPositionInSeconds / playerElement.duration;
|
const completion = currentPositionInSeconds / playerElement.duration;
|
||||||
if (completion >= markAsReadOnCompletion) {
|
if (completion >= markAsReadOnCompletion) {
|
||||||
handleEntryStatus("none", document.querySelector(":is(a, button)[data-toggle-status]"), true);
|
handleEntryStatus("none", document.querySelector(":is(a, button)[data-toggle-status]"), true);
|
||||||
}
|
}
|
||||||
|
@ -744,18 +746,14 @@ function handlePlayerProgressionSaveAndMarkAsReadOnCompletion(playerElement) {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function isPlayerPlaying(element) {
|
function isPlayerPlaying(element) {
|
||||||
return element &&
|
return element && element.currentTime > 0 && !element.paused && !element.ended && element.readyState > 2; //https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
|
||||||
element.currentTime > 0 &&
|
|
||||||
!element.paused &&
|
|
||||||
!element.ended &&
|
|
||||||
element.readyState > 2; //https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle new share entires and already shared entries
|
* handle new share entires and already shared entries
|
||||||
*/
|
*/
|
||||||
async function handleShare() {
|
async function handleShare() {
|
||||||
const link = document.querySelector(':is(a, button)[data-share-status]');
|
const link = document.querySelector(":is(a, button)[data-share-status]");
|
||||||
const title = document.querySelector(".entry-header > h1 > a");
|
const title = document.querySelector(".entry-header > h1 > a");
|
||||||
if (link.dataset.shareStatus === "shared") {
|
if (link.dataset.shareStatus === "shared") {
|
||||||
await checkShareAPI(title, link.href);
|
await checkShareAPI(title, link.href);
|
||||||
|
@ -771,8 +769,8 @@ async function handleShare() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wrapper for Web Share API
|
* wrapper for Web Share API
|
||||||
*/
|
*/
|
||||||
async function checkShareAPI(title, url) {
|
async function checkShareAPI(title, url) {
|
||||||
if (!navigator.canShare) {
|
if (!navigator.canShare) {
|
||||||
console.error("Your browser doesn't support the Web Share API.");
|
console.error("Your browser doesn't support the Web Share API.");
|
||||||
|
@ -782,7 +780,7 @@ async function checkShareAPI(title, url) {
|
||||||
try {
|
try {
|
||||||
await navigator.share({
|
await navigator.share({
|
||||||
title: title ? title.textContent : url,
|
title: title ? title.textContent : url,
|
||||||
url: url
|
url: url,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -810,31 +808,33 @@ function handleMediaControl(button) {
|
||||||
const action = button.dataset.enclosureAction;
|
const action = button.dataset.enclosureAction;
|
||||||
const value = parseFloat(button.dataset.actionValue);
|
const value = parseFloat(button.dataset.actionValue);
|
||||||
const targetEnclosureId = button.dataset.enclosureId;
|
const targetEnclosureId = button.dataset.enclosureId;
|
||||||
const enclosures = document.querySelectorAll(`audio[data-enclosure-id="${targetEnclosureId}"],video[data-enclosure-id="${targetEnclosureId}"]`);
|
const enclosures = document.querySelectorAll(
|
||||||
|
`audio[data-enclosure-id="${targetEnclosureId}"],video[data-enclosure-id="${targetEnclosureId}"]`
|
||||||
|
);
|
||||||
const speedIndicator = document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${targetEnclosureId}"]`);
|
const speedIndicator = document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${targetEnclosureId}"]`);
|
||||||
enclosures.forEach((enclosure) => {
|
enclosures.forEach((enclosure) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "seek":
|
case "seek":
|
||||||
enclosure.currentTime = Math.max(enclosure.currentTime + value, 0);
|
enclosure.currentTime = Math.max(enclosure.currentTime + value, 0);
|
||||||
break;
|
break;
|
||||||
case "speed":
|
case "speed":
|
||||||
// I set a floor speed of 0.25 to avoid too slow speed where it gives the impression it stopped.
|
// I set a floor speed of 0.25 to avoid too slow speed where it gives the impression it stopped.
|
||||||
// 0.25 was chosen because it will allow to get back to 1x in two "faster" click, and lower value with same property would be 0.
|
// 0.25 was chosen because it will allow to get back to 1x in two "faster" click, and lower value with same property would be 0.
|
||||||
enclosure.playbackRate = Math.max(0.25, enclosure.playbackRate + value);
|
enclosure.playbackRate = Math.max(0.25, enclosure.playbackRate + value);
|
||||||
speedIndicator.forEach((speedI) => {
|
speedIndicator.forEach((speedI) => {
|
||||||
// Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters.
|
// Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters.
|
||||||
// The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature
|
// The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature
|
||||||
speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`;
|
speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "speed-reset":
|
case "speed-reset":
|
||||||
enclosure.playbackRate = value ;
|
enclosure.playbackRate = value;
|
||||||
speedIndicator.forEach((speedI) => {
|
speedIndicator.forEach((speedI) => {
|
||||||
// Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters.
|
// Two digit precision to ensure we always have the same number of characters (4) to avoid controls moving when clicking buttons because of more or less characters.
|
||||||
// The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature
|
// The trick only work on rate less than 10, but it feels an acceptable tread of considering the feature
|
||||||
speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`;
|
speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
86
internal/ui/static/js/bootstrap.js
vendored
86
internal/ui/static/js/bootstrap.js
vendored
|
@ -39,9 +39,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
keyboardHandler.on("#", unsubscribeFromFeed);
|
keyboardHandler.on("#", unsubscribeFromFeed);
|
||||||
keyboardHandler.on("/", () => goToPage("search"));
|
keyboardHandler.on("/", () => goToPage("search"));
|
||||||
keyboardHandler.on("a", () => {
|
keyboardHandler.on("a", () => {
|
||||||
const enclosureElement = document.querySelector('.entry-enclosures');
|
const enclosureElement = document.querySelector(".entry-enclosures");
|
||||||
if (enclosureElement) {
|
if (enclosureElement) {
|
||||||
enclosureElement.toggleAttribute('open');
|
enclosureElement.toggleAttribute("open");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
keyboardHandler.on("Escape", () => ModalHandler.close());
|
keyboardHandler.on("Escape", () => ModalHandler.close());
|
||||||
|
@ -54,7 +54,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (WebAuthnHandler.isWebAuthnSupported()) {
|
if (WebAuthnHandler.isWebAuthnSupported()) {
|
||||||
const webauthnHandler = new WebAuthnHandler();
|
const webauthnHandler = new WebAuthnHandler();
|
||||||
|
|
||||||
onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); });
|
onClick("#webauthn-delete", () => {
|
||||||
|
webauthnHandler.removeAllCredentials();
|
||||||
|
});
|
||||||
|
|
||||||
const registerButton = document.getElementById("webauthn-register");
|
const registerButton = document.getElementById("webauthn-register");
|
||||||
if (registerButton !== null) {
|
if (registerButton !== null) {
|
||||||
|
@ -74,11 +76,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
const usernameField = document.getElementById("form-username");
|
const usernameField = document.getElementById("form-username");
|
||||||
if (usernameField !== null) {
|
if (usernameField !== null) {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
webauthnHandler.login(usernameField.value).catch(err => WebAuthnHandler.showErrorMessage(err));
|
webauthnHandler.login(usernameField.value).catch((err) => WebAuthnHandler.showErrorMessage(err));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err));
|
webauthnHandler.conditionalLogin(abortController).catch((err) => WebAuthnHandler.showErrorMessage(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,32 +88,44 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
|
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
|
||||||
onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent);
|
onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent);
|
||||||
onClick(":is(a, button)[data-share-status]", handleShare);
|
onClick(":is(a, button)[data-share-status]", handleShare);
|
||||||
onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, markPageAsRead));
|
onClick(":is(a, button)[data-action=markPageAsRead]", (event) =>
|
||||||
|
handleConfirmationMessage(event.target, markPageAsRead)
|
||||||
|
);
|
||||||
onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
|
onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
|
||||||
onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
|
onClick(":is(a, button)[data-confirm]", (event) =>
|
||||||
const request = new RequestBuilder(url);
|
handleConfirmationMessage(event.target, (url, redirectURL) => {
|
||||||
|
const request = new RequestBuilder(url);
|
||||||
|
|
||||||
request.withCallback((response) => {
|
request.withCallback((response) => {
|
||||||
if (redirectURL) {
|
if (redirectURL) {
|
||||||
window.location.href = redirectURL;
|
window.location.href = redirectURL;
|
||||||
} else if (response && response.redirected && response.url) {
|
} else if (response && response.redirected && response.url) {
|
||||||
window.location.href = response.url;
|
window.location.href = response.url;
|
||||||
} else {
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
onClick("a[data-original-link='true']", (event) => {
|
onClick(
|
||||||
handleEntryStatus("next", event.target, true);
|
"a[data-original-link='true']",
|
||||||
}, true);
|
(event) => {
|
||||||
onAuxClick("a[data-original-link='true']", (event) => {
|
|
||||||
if (event.button === 1) {
|
|
||||||
handleEntryStatus("next", event.target, true);
|
handleEntryStatus("next", event.target, true);
|
||||||
}
|
},
|
||||||
}, true);
|
true
|
||||||
|
);
|
||||||
|
onAuxClick(
|
||||||
|
"a[data-original-link='true']",
|
||||||
|
(event) => {
|
||||||
|
if (event.button === 1) {
|
||||||
|
handleEntryStatus("next", event.target, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
checkMenuToggleModeByLayout();
|
checkMenuToggleModeByLayout();
|
||||||
window.addEventListener("resize", checkMenuToggleModeByLayout, { passive: true });
|
window.addEventListener("resize", checkMenuToggleModeByLayout, { passive: true });
|
||||||
|
@ -129,19 +143,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
const scriptElement = document.getElementById("service-worker-script");
|
const scriptElement = document.getElementById("service-worker-script");
|
||||||
if (scriptElement) {
|
if (scriptElement) {
|
||||||
navigator.serviceWorker.register(ttpolicy.createScriptURL(scriptElement.src));
|
navigator.serviceWorker.register(ttpolicy.createScriptURL(scriptElement.src));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('beforeinstallprompt', (e) => {
|
window.addEventListener("beforeinstallprompt", (e) => {
|
||||||
let deferredPrompt = e;
|
let deferredPrompt = e;
|
||||||
const promptHomeScreen = document.getElementById('prompt-home-screen');
|
const promptHomeScreen = document.getElementById("prompt-home-screen");
|
||||||
if (promptHomeScreen) {
|
if (promptHomeScreen) {
|
||||||
promptHomeScreen.style.display = "block";
|
promptHomeScreen.style.display = "block";
|
||||||
|
|
||||||
const btnAddToHomeScreen = document.getElementById('btn-add-to-home-screen');
|
const btnAddToHomeScreen = document.getElementById("btn-add-to-home-screen");
|
||||||
if (btnAddToHomeScreen) {
|
if (btnAddToHomeScreen) {
|
||||||
btnAddToHomeScreen.addEventListener('click', (e) => {
|
btnAddToHomeScreen.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
deferredPrompt.prompt();
|
deferredPrompt.prompt();
|
||||||
deferredPrompt.userChoice.then(() => {
|
deferredPrompt.userChoice.then(() => {
|
||||||
|
@ -167,13 +181,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
playbackRateElements.forEach((element) => {
|
playbackRateElements.forEach((element) => {
|
||||||
if (element.dataset.playbackRate) {
|
if (element.dataset.playbackRate) {
|
||||||
element.playbackRate = element.dataset.playbackRate;
|
element.playbackRate = element.dataset.playbackRate;
|
||||||
if (element.dataset.enclosureId){
|
if (element.dataset.enclosureId) {
|
||||||
// In order to display properly the speed we need to do it on bootstrap.
|
// In order to display properly the speed we need to do it on bootstrap.
|
||||||
// Could not do it backend side because I didn't know how to do it because of the template inclusion and
|
// Could not do it backend side because I didn't know how to do it because of the template inclusion and
|
||||||
// the way the initial playback speed is handled. See enclosure_media_controls.html if you want to try to fix this
|
// the way the initial playback speed is handled. See enclosure_media_controls.html if you want to try to fix this
|
||||||
document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${element.dataset.enclosureId}"]`).forEach((speedI)=>{
|
document
|
||||||
speedI.innerText = `${parseFloat(element.dataset.playbackRate).toFixed(2)}x`;
|
.querySelectorAll(`span.speed-indicator[data-enclosure-id="${element.dataset.enclosureId}"]`)
|
||||||
});
|
.forEach((speedI) => {
|
||||||
|
speedI.innerText = `${parseFloat(element.dataset.playbackRate).toFixed(2)}x`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,9 +46,11 @@ class KeyboardHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
isEventIgnored(event, key) {
|
isEventIgnored(event, key) {
|
||||||
return event.target.tagName === "INPUT" ||
|
return (
|
||||||
|
event.target.tagName === "INPUT" ||
|
||||||
event.target.tagName === "TEXTAREA" ||
|
event.target.tagName === "TEXTAREA" ||
|
||||||
(this.queue.length < 1 && !this.triggers.has(key));
|
(this.queue.length < 1 && !this.triggers.has(key))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static isModifierKeyDown(event) {
|
static isModifierKeyDown(event) {
|
||||||
|
@ -57,12 +59,18 @@ class KeyboardHandler {
|
||||||
|
|
||||||
static getKey(event) {
|
static getKey(event) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Esc': return 'Escape';
|
case "Esc":
|
||||||
case 'Up': return 'ArrowUp';
|
return "Escape";
|
||||||
case 'Down': return 'ArrowDown';
|
case "Up":
|
||||||
case 'Left': return 'ArrowLeft';
|
return "ArrowUp";
|
||||||
case 'Right': return 'ArrowRight';
|
case "Down":
|
||||||
default: return event.key;
|
return "ArrowDown";
|
||||||
|
case "Left":
|
||||||
|
return "ArrowLeft";
|
||||||
|
case "Right":
|
||||||
|
return "ArrowRight";
|
||||||
|
default:
|
||||||
|
return event.key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ class ModalHandler {
|
||||||
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
||||||
|
|
||||||
this.getModalContainer().onkeydown = (e) => {
|
this.getModalContainer().onkeydown = (e) => {
|
||||||
if (e.key !== 'Tab') {
|
if (e.key !== "Tab") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ class RequestBuilder {
|
||||||
body: null,
|
body: null,
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Csrf-Token": getCsrfToken()
|
"X-Csrf-Token": getCsrfToken(),
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
// Incrementing OFFLINE_VERSION will kick off the install event and force
|
// Incrementing OFFLINE_VERSION will kick off the install event and force
|
||||||
// previously cached resources to be updated from the network.
|
// previously cached resources to be updated from the network.
|
||||||
const OFFLINE_VERSION = 1;
|
const OFFLINE_VERSION = 1;
|
||||||
|
|
|
@ -9,7 +9,7 @@ class TouchHandler {
|
||||||
move: { x: -1, y: -1 },
|
move: { x: -1, y: -1 },
|
||||||
moved: false,
|
moved: false,
|
||||||
time: 0,
|
time: 0,
|
||||||
element: null
|
element: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class TouchHandler {
|
||||||
const horizontalDistance = Math.abs(this.touch.move.x - this.touch.start.x);
|
const horizontalDistance = Math.abs(this.touch.move.x - this.touch.start.x);
|
||||||
const verticalDistance = Math.abs(this.touch.move.y - this.touch.start.y);
|
const verticalDistance = Math.abs(this.touch.move.y - this.touch.start.y);
|
||||||
|
|
||||||
if (horizontalDistance > 30 && verticalDistance < 70 || this.touch.moved) {
|
if ((horizontalDistance > 30 && verticalDistance < 70) || this.touch.moved) {
|
||||||
return this.touch.move.x - this.touch.start.x;
|
return this.touch.move.x - this.touch.start.x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ let ttpolicy;
|
||||||
if (window.trustedTypes && trustedTypes.createPolicy) {
|
if (window.trustedTypes && trustedTypes.createPolicy) {
|
||||||
//TODO: use an allow-list for `createScriptURL`
|
//TODO: use an allow-list for `createScriptURL`
|
||||||
if (!ttpolicy) {
|
if (!ttpolicy) {
|
||||||
ttpolicy = trustedTypes.createPolicy('ttpolicy', {
|
ttpolicy = trustedTypes.createPolicy("ttpolicy", {
|
||||||
createScriptURL: src => src,
|
createScriptURL: (src) => src,
|
||||||
createHTML: html => html,
|
createHTML: (html) => html,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ttpolicy = {
|
ttpolicy = {
|
||||||
createScriptURL: src => src,
|
createScriptURL: (src) => src,
|
||||||
createHTML: html => html,
|
createHTML: (html) => html,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,11 @@ class WebAuthnHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async isConditionalLoginSupported() {
|
async isConditionalLoginSupported() {
|
||||||
return WebAuthnHandler.isWebAuthnSupported() &&
|
return (
|
||||||
|
WebAuthnHandler.isWebAuthnSupported() &&
|
||||||
window.PublicKeyCredential.isConditionalMediationAvailable &&
|
window.PublicKeyCredential.isConditionalMediationAvailable &&
|
||||||
window.PublicKeyCredential.isConditionalMediationAvailable();
|
window.PublicKeyCredential.isConditionalMediationAvailable()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async conditionalLogin(abortController) {
|
async conditionalLogin(abortController) {
|
||||||
|
@ -35,7 +37,7 @@ class WebAuthnHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeBuffer(value) {
|
decodeBuffer(value) {
|
||||||
return Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0));
|
return Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeBuffer(value) {
|
encodeBuffer(value) {
|
||||||
|
@ -55,7 +57,7 @@ class WebAuthnHandler {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Csrf-Token": getCsrfToken()
|
"X-Csrf-Token": getCsrfToken(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
@ -90,10 +92,14 @@ class WebAuthnHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentialCreationOptions = await registerBeginResponse.json();
|
const credentialCreationOptions = await registerBeginResponse.json();
|
||||||
credentialCreationOptions.publicKey.challenge = this.decodeBuffer(credentialCreationOptions.publicKey.challenge);
|
credentialCreationOptions.publicKey.challenge = this.decodeBuffer(
|
||||||
|
credentialCreationOptions.publicKey.challenge
|
||||||
|
);
|
||||||
credentialCreationOptions.publicKey.user.id = this.decodeBuffer(credentialCreationOptions.publicKey.user.id);
|
credentialCreationOptions.publicKey.user.id = this.decodeBuffer(credentialCreationOptions.publicKey.user.id);
|
||||||
if (Object.hasOwn(credentialCreationOptions.publicKey, 'excludeCredentials')) {
|
if (Object.hasOwn(credentialCreationOptions.publicKey, "excludeCredentials")) {
|
||||||
credentialCreationOptions.publicKey.excludeCredentials.forEach((credential) => credential.id = this.decodeBuffer(credential.id));
|
credentialCreationOptions.publicKey.excludeCredentials.forEach(
|
||||||
|
(credential) => (credential.id = this.decodeBuffer(credential.id))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attestation = await navigator.credentials.create(credentialCreationOptions);
|
const attestation = await navigator.credentials.create(credentialCreationOptions);
|
||||||
|
@ -134,8 +140,10 @@ class WebAuthnHandler {
|
||||||
const credentialRequestOptions = await loginBeginResponse.json();
|
const credentialRequestOptions = await loginBeginResponse.json();
|
||||||
credentialRequestOptions.publicKey.challenge = this.decodeBuffer(credentialRequestOptions.publicKey.challenge);
|
credentialRequestOptions.publicKey.challenge = this.decodeBuffer(credentialRequestOptions.publicKey.challenge);
|
||||||
|
|
||||||
if (Object.hasOwn(credentialRequestOptions.publicKey, 'allowCredentials')) {
|
if (Object.hasOwn(credentialRequestOptions.publicKey, "allowCredentials")) {
|
||||||
credentialRequestOptions.publicKey.allowCredentials.forEach((credential) => credential.id = this.decodeBuffer(credential.id));
|
credentialRequestOptions.publicKey.allowCredentials.forEach(
|
||||||
|
(credential) => (credential.id = this.decodeBuffer(credential.id))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
|
@ -146,8 +154,7 @@ class WebAuthnHandler {
|
||||||
let assertion;
|
let assertion;
|
||||||
try {
|
try {
|
||||||
assertion = await navigator.credentials.get(credentialRequestOptions);
|
assertion = await navigator.credentials.get(credentialRequestOptions);
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
// Swallow aborted conditional logins
|
// Swallow aborted conditional logins
|
||||||
if (err instanceof DOMException && err.name === "AbortError") {
|
if (err instanceof DOMException && err.name === "AbortError") {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue