mirror of
https://github.com/miniflux/v2.git
synced 2025-08-16 18:01:37 +00:00
refactor(js): code cleanup and add jshint comments
This commit is contained in:
parent
3e1a7e411c
commit
62410659d5
2 changed files with 248 additions and 132 deletions
|
@ -129,6 +129,20 @@ function findEntry(element) {
|
|||
return document.querySelector(".entry");
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an icon label element into the parent element.
|
||||
*
|
||||
* @param {Element} parentElement The parent element to insert the icon label into.
|
||||
* @param {string} iconLabelText The text to display in the icon label.
|
||||
* @returns {void}
|
||||
*/
|
||||
function insertIconLabelElement(parentElement, iconLabelText) {
|
||||
const span = document.createElement('span');
|
||||
span.classList.add('icon-label');
|
||||
span.textContent = iconLabelText;
|
||||
parentElement.appendChild(span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific page.
|
||||
*
|
||||
|
@ -477,15 +491,12 @@ function handleEntryStatus(navigationDirection, element, setToRead) {
|
|||
}
|
||||
}
|
||||
|
||||
// Add an icon-label span element.
|
||||
function appendIconLabel(element, labelTextContent) {
|
||||
const span = document.createElement('span');
|
||||
span.classList.add('icon-label');
|
||||
span.textContent = labelTextContent;
|
||||
element.appendChild(span);
|
||||
}
|
||||
|
||||
// Change the entry status to the opposite value.
|
||||
/**
|
||||
* Toggle the entry status between "read" and "unread".
|
||||
*
|
||||
* @param {Element} element The entry element to toggle the status for.
|
||||
* @param {boolean} toasting If true, show a toast notification after toggling the status.
|
||||
*/
|
||||
function toggleEntryStatus(element, toasting) {
|
||||
const entryID = parseInt(element.dataset.id, 10);
|
||||
const link = element.querySelector(":is(a, button)[data-toggle-status]");
|
||||
|
@ -515,7 +526,7 @@ function toggleEntryStatus(element, toasting) {
|
|||
}
|
||||
|
||||
link.replaceChildren(iconElement.content.cloneNode(true));
|
||||
appendIconLabel(link, label);
|
||||
insertIconLabelElement(link, label);
|
||||
link.dataset.value = newStatus;
|
||||
|
||||
if (element.classList.contains("item-status-" + currentStatus)) {
|
||||
|
@ -529,7 +540,11 @@ function toggleEntryStatus(element, toasting) {
|
|||
});
|
||||
}
|
||||
|
||||
// Mark a single entry as read.
|
||||
/**
|
||||
* Mark the entry as read if it is currently unread.
|
||||
*
|
||||
* @param {Element} element The entry element to mark as read.
|
||||
*/
|
||||
function markEntryAsRead(element) {
|
||||
if (element.classList.contains("item-status-unread")) {
|
||||
element.classList.remove("item-status-unread");
|
||||
|
@ -540,15 +555,24 @@ function markEntryAsRead(element) {
|
|||
}
|
||||
}
|
||||
|
||||
// Send the Ajax request to refresh all feeds in the background
|
||||
/**
|
||||
* Handle the refresh of all feeds.
|
||||
*
|
||||
* This function redirects the user to the URL specified in the data-refresh-all-feeds-url attribute of the body element.
|
||||
*/
|
||||
function handleRefreshAllFeeds() {
|
||||
const url = document.body.dataset.refreshAllFeedsUrl;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
const refreshAllFeedsUrl = document.body.dataset.refreshAllFeedsUrl;
|
||||
if (refreshAllFeedsUrl) {
|
||||
window.location.href = refreshAllFeedsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the Ajax request to change entries statuses.
|
||||
/**
|
||||
* Update the status of multiple entries.
|
||||
*
|
||||
* @param {Array<number>} entryIDs - The IDs of the entries to update.
|
||||
* @param {string} status - The new status to set for the entries (e.g., "read", "unread").
|
||||
*/
|
||||
function updateEntriesStatus(entryIDs, status, callback) {
|
||||
const url = document.body.dataset.entriesStatusUrl;
|
||||
const request = new RequestBuilder(url);
|
||||
|
@ -569,7 +593,11 @@ function updateEntriesStatus(entryIDs, status, callback) {
|
|||
request.execute();
|
||||
}
|
||||
|
||||
// Handle save entry from list view and entry view.
|
||||
/**
|
||||
* Handle save entry from list view and entry view.
|
||||
*
|
||||
* @param {Element} element
|
||||
*/
|
||||
function handleSaveEntry(element) {
|
||||
const toasting = !element;
|
||||
const currentEntry = findEntry(element);
|
||||
|
@ -578,19 +606,25 @@ function handleSaveEntry(element) {
|
|||
}
|
||||
}
|
||||
|
||||
// Send the Ajax request to save an entry.
|
||||
/**
|
||||
* Save the entry by sending an Ajax request to the server.
|
||||
*
|
||||
* @param {Element} element The element that triggered the save action.
|
||||
* @param {boolean} toasting If true, show a toast notification after saving the entry.
|
||||
* @return {void}
|
||||
*/
|
||||
function saveEntry(element, toasting) {
|
||||
if (!element || element.dataset.completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.textContent = "";
|
||||
appendIconLabel(element, element.dataset.labelLoading);
|
||||
insertIconLabelElement(element, element.dataset.labelLoading);
|
||||
|
||||
const request = new RequestBuilder(element.dataset.saveUrl);
|
||||
request.withCallback(() => {
|
||||
element.textContent = "";
|
||||
appendIconLabel(element, element.dataset.labelDone);
|
||||
insertIconLabelElement(element, element.dataset.labelDone);
|
||||
element.dataset.completed = "true";
|
||||
if (toasting) {
|
||||
const iconElement = document.querySelector("template#icon-save");
|
||||
|
@ -600,7 +634,11 @@ function saveEntry(element, toasting) {
|
|||
request.execute();
|
||||
}
|
||||
|
||||
// Handle bookmark from the list view and entry view.
|
||||
/**
|
||||
* Handle bookmarking an entry.
|
||||
*
|
||||
* @param {Element} element - The element that triggered the bookmark action.
|
||||
*/
|
||||
function handleBookmark(element) {
|
||||
const toasting = !element;
|
||||
const currentEntry = findEntry(element);
|
||||
|
@ -609,7 +647,13 @@ function handleBookmark(element) {
|
|||
}
|
||||
}
|
||||
|
||||
// Send the Ajax request and change the icon when bookmarking an entry.
|
||||
/**
|
||||
* Toggle the bookmark status of an entry.
|
||||
*
|
||||
* @param {Element} parentElement - The parent element containing the bookmark button.
|
||||
* @param {boolean} toasting - Whether to show a toast notification.
|
||||
* @returns {void}
|
||||
*/
|
||||
function toggleBookmark(parentElement, toasting) {
|
||||
const buttonElement = parentElement.querySelector(":is(a, button)[data-toggle-bookmark]");
|
||||
if (!buttonElement) {
|
||||
|
@ -617,7 +661,7 @@ function toggleBookmark(parentElement, toasting) {
|
|||
}
|
||||
|
||||
buttonElement.textContent = "";
|
||||
appendIconLabel(buttonElement, buttonElement.dataset.labelLoading);
|
||||
insertIconLabelElement(buttonElement, buttonElement.dataset.labelLoading);
|
||||
|
||||
const request = new RequestBuilder(buttonElement.dataset.bookmarkUrl);
|
||||
request.withCallback(() => {
|
||||
|
@ -640,13 +684,17 @@ function toggleBookmark(parentElement, toasting) {
|
|||
}
|
||||
|
||||
buttonElement.replaceChildren(iconElement.content.cloneNode(true));
|
||||
appendIconLabel(buttonElement, label);
|
||||
insertIconLabelElement(buttonElement, label);
|
||||
buttonElement.dataset.value = newStarStatus;
|
||||
});
|
||||
request.execute();
|
||||
}
|
||||
|
||||
// Send the Ajax request to download the original web page.
|
||||
/**
|
||||
* Handle fetching the original content of an entry.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleFetchOriginalContent() {
|
||||
if (isListView()) {
|
||||
return;
|
||||
|
@ -660,7 +708,7 @@ function handleFetchOriginalContent() {
|
|||
const previousElement = buttonElement.cloneNode(true);
|
||||
|
||||
buttonElement.textContent = "";
|
||||
appendIconLabel(buttonElement, buttonElement.dataset.labelLoading);
|
||||
insertIconLabelElement(buttonElement, buttonElement.dataset.labelLoading);
|
||||
|
||||
const request = new RequestBuilder(buttonElement.dataset.fetchContentUrl);
|
||||
request.withCallback((response) => {
|
||||
|
@ -680,6 +728,12 @@ function handleFetchOriginalContent() {
|
|||
request.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the original link of an entry.
|
||||
*
|
||||
* @param {boolean} openLinkInCurrentTab - Whether to open the link in the current tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
function openOriginalLink(openLinkInCurrentTab) {
|
||||
const entryLink = document.querySelector(".entry h1 a");
|
||||
if (entryLink !== null) {
|
||||
|
@ -704,6 +758,12 @@ function openOriginalLink(openLinkInCurrentTab) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the comments link of an entry.
|
||||
*
|
||||
* @param {boolean} openLinkInCurrentTab - Whether to open the link in the current tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
function openCommentLink(openLinkInCurrentTab) {
|
||||
if (!isListView()) {
|
||||
const entryLink = document.querySelector(":is(a, button)[data-comments-link]");
|
||||
|
@ -722,6 +782,12 @@ function openCommentLink(openLinkInCurrentTab) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the selected item in the current view.
|
||||
*
|
||||
* If the current view is a list view, it will navigate to the link of the currently selected item.
|
||||
* If the current view is an entry view, it will navigate to the link of the entry.
|
||||
*/
|
||||
function openSelectedItem() {
|
||||
const currentItemLink = document.querySelector(".current-item .item-title a");
|
||||
if (currentItemLink !== null) {
|
||||
|
@ -729,6 +795,9 @@ function openSelectedItem() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the feed of the currently selected item.
|
||||
*/
|
||||
function unsubscribeFromFeed() {
|
||||
const unsubscribeLinks = document.querySelectorAll("[data-action=remove-feed]");
|
||||
if (unsubscribeLinks.length === 1) {
|
||||
|
@ -746,6 +815,9 @@ function unsubscribeFromFeed() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the page to the currently selected item.
|
||||
*/
|
||||
function scrollToCurrentItem() {
|
||||
const currentItem = document.querySelector(".current-item");
|
||||
if (currentItem !== null) {
|
||||
|
@ -753,18 +825,33 @@ function scrollToCurrentItem() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the unread counter by a specified amount.
|
||||
*
|
||||
* @param {number} n - The amount to decrement the counter by.
|
||||
*/
|
||||
function decrementUnreadCounter(n) {
|
||||
updateUnreadCounterValue((current) => {
|
||||
return current - n;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the unread counter by a specified amount.
|
||||
*
|
||||
* @param {number} n - The amount to increment the counter by.
|
||||
*/
|
||||
function incrementUnreadCounter(n) {
|
||||
updateUnreadCounterValue((current) => {
|
||||
return current + n;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the unread counter value.
|
||||
*
|
||||
* @param {function} callback - The function to call with the old value.
|
||||
*/
|
||||
function updateUnreadCounterValue(callback) {
|
||||
document.querySelectorAll("span.unread-counter").forEach((element) => {
|
||||
const oldValue = parseInt(element.textContent, 10);
|
||||
|
@ -784,6 +871,17 @@ function updateUnreadCounterValue(callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle confirmation messages for actions that require user confirmation.
|
||||
*
|
||||
* This function modifies the link element to show a confirmation question with "Yes" and "No" buttons.
|
||||
* If the user clicks "Yes", it calls the provided callback with the URL and redirect URL.
|
||||
* If the user clicks "No", it either redirects to a no-action URL or restores the link element.
|
||||
*
|
||||
* @param {Element} linkElement - The link or button element that triggered the confirmation.
|
||||
* @param {function} callback - The callback function to execute if the user confirms the action.
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleConfirmationMessage(linkElement, callback) {
|
||||
if (linkElement.tagName !== 'A' && linkElement.tagName !== "BUTTON") {
|
||||
linkElement = linkElement.parentNode;
|
||||
|
@ -838,14 +936,21 @@ function handleConfirmationMessage(linkElement, callback) {
|
|||
containerElement.appendChild(questionElement);
|
||||
}
|
||||
|
||||
function showToast(label, iconElement) {
|
||||
if (!label || !iconElement) {
|
||||
/**
|
||||
* Show a toast notification.
|
||||
*
|
||||
* @param {string} toastMessage - The label to display in the toast.
|
||||
* @param {Element} iconElement - The icon element to display in the toast.
|
||||
* @returns {void}
|
||||
*/
|
||||
function showToast(toastMessage, iconElement) {
|
||||
if (!toastMessage || !iconElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toastMsgElement = document.getElementById("toast-msg");
|
||||
toastMsgElement.replaceChildren(iconElement.content.cloneNode(true));
|
||||
appendIconLabel(toastMsgElement, label);
|
||||
insertIconLabelElement(toastMsgElement, toastMessage);
|
||||
|
||||
const toastElementWrapper = document.getElementById("toast-wrapper");
|
||||
toastElementWrapper.classList.remove('toast-animate');
|
||||
|
@ -855,26 +960,47 @@ function showToast(label, iconElement) {
|
|||
}
|
||||
|
||||
/**
|
||||
* save player position to allow to resume playback later
|
||||
* @param {Element} playerElement
|
||||
* Check if the player is actually playing a media
|
||||
*
|
||||
* @param mediaElement the player element itself
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPlayerPlaying(mediaElement) {
|
||||
return mediaElement &&
|
||||
mediaElement.currentTime > 0 &&
|
||||
!mediaElement.paused &&
|
||||
!mediaElement.ended &&
|
||||
mediaElement.readyState > 2; // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle player progression save and mark as read on completion.
|
||||
*
|
||||
* This function is triggered on the `timeupdate` event of the media player.
|
||||
* It saves the current playback position and marks the entry as read if the completion percentage is reached.
|
||||
*
|
||||
* @param {Element} playerElement The media player element (audio or video).
|
||||
*/
|
||||
function handlePlayerProgressionSaveAndMarkAsReadOnCompletion(playerElement) {
|
||||
if (!isPlayerPlaying(playerElement)) {
|
||||
return; //If the player is not playing, we do not want to save the progression and mark as read on completion
|
||||
return;
|
||||
}
|
||||
const currentPositionInSeconds = Math.floor(playerElement.currentTime); // we do not need a precise value
|
||||
|
||||
const currentPositionInSeconds = Math.floor(playerElement.currentTime);
|
||||
const lastKnownPositionInSeconds = parseInt(playerElement.dataset.lastPosition, 10);
|
||||
const markAsReadOnCompletion = parseFloat(playerElement.dataset.markReadOnCompletion); //completion percentage to mark as read
|
||||
const markAsReadOnCompletion = parseFloat(playerElement.dataset.markReadOnCompletion);
|
||||
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) ||
|
||||
currentPositionInSeconds <= (lastKnownPositionInSeconds - recordInterval)
|
||||
) {
|
||||
playerElement.dataset.lastPosition = currentPositionInSeconds.toString();
|
||||
|
||||
const request = new RequestBuilder(playerElement.dataset.saveUrl);
|
||||
request.withBody({ progression: currentPositionInSeconds });
|
||||
request.execute();
|
||||
|
||||
// Handle the mark as read on completion
|
||||
if (markAsReadOnCompletion >= 0 && playerElement.duration > 0) {
|
||||
const completion = currentPositionInSeconds / playerElement.duration;
|
||||
|
@ -886,54 +1012,69 @@ function handlePlayerProgressionSaveAndMarkAsReadOnCompletion(playerElement) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if the player is actually playing a media
|
||||
* @param element the player element itself
|
||||
* @returns {boolean}
|
||||
* Handle media control actions like seeking and changing playback speed.
|
||||
*
|
||||
* This function is triggered by clicking on media control buttons.
|
||||
* It adjusts the playback position or speed of media elements with the same enclosure ID.
|
||||
*
|
||||
* @param {Element} mediaPlayerButtonElement
|
||||
*/
|
||||
function isPlayerPlaying(element) {
|
||||
return element &&
|
||||
element.currentTime > 0 &&
|
||||
!element.paused &&
|
||||
!element.ended &&
|
||||
element.readyState > 2; //https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all clicks on media player controls button on enclosures.
|
||||
* Will change the current speed and position of the player accordingly.
|
||||
* Will not save anything, all is done client-side, however, changing the position
|
||||
* will trigger the handlePlayerProgressionSave and save the new position backends side.
|
||||
* @param {Element} button
|
||||
*/
|
||||
function handleMediaControl(button) {
|
||||
const action = button.dataset.enclosureAction;
|
||||
const value = parseFloat(button.dataset.actionValue);
|
||||
const targetEnclosureId = button.dataset.enclosureId;
|
||||
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}"]`);
|
||||
enclosures.forEach((enclosure) => {
|
||||
switch (action) {
|
||||
function handleMediaControlButtonClick(mediaPlayerButtonElement) {
|
||||
const actionType = mediaPlayerButtonElement.dataset.enclosureAction;
|
||||
const actionValue = parseFloat(mediaPlayerButtonElement.dataset.actionValue);
|
||||
const enclosureID = mediaPlayerButtonElement.dataset.enclosureId;
|
||||
const mediaElements = document.querySelectorAll(`audio[data-enclosure-id="${enclosureID}"],video[data-enclosure-id="${enclosureID}"]`);
|
||||
const speedIndicatorElements = document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${enclosureID}"]`);
|
||||
mediaElements.forEach((mediaElement) => {
|
||||
switch (actionType) {
|
||||
case "seek":
|
||||
enclosure.currentTime = Math.max(enclosure.currentTime + value, 0);
|
||||
mediaElement.currentTime = Math.max(mediaElement.currentTime + actionValue, 0);
|
||||
break;
|
||||
case "speed":
|
||||
// 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.
|
||||
enclosure.playbackRate = Math.max(0.25, enclosure.playbackRate + value);
|
||||
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.
|
||||
// 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`;
|
||||
// 0.25 was chosen because it will allow to get back to 1x in two "faster" clicks.
|
||||
// A lower value would result in a playback rate of 0, effectively pausing playback.
|
||||
mediaElement.playbackRate = Math.max(0.25, mediaElement.playbackRate + actionValue);
|
||||
speedIndicatorElements.forEach((speedIndicatorElement) => {
|
||||
speedIndicatorElement.innerText = `${mediaElement.playbackRate.toFixed(2)}x`;
|
||||
});
|
||||
break;
|
||||
case "speed-reset":
|
||||
enclosure.playbackRate = value ;
|
||||
speedIndicator.forEach((speedI) => {
|
||||
mediaElement.playbackRate = actionValue ;
|
||||
speedIndicatorElements.forEach((speedIndicatorElement) => {
|
||||
// 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
|
||||
speedI.innerText = `${enclosure.playbackRate.toFixed(2)}x`;
|
||||
// The trick only works on rates less than 10, but it feels an acceptable trade-off considering the feature
|
||||
speedIndicatorElement.innerText = `${mediaElement.playbackRate.toFixed(2)}x`;
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize media player event handlers.
|
||||
*/
|
||||
function initializeMediaPlayerHandlers() {
|
||||
document.querySelectorAll("button[data-enclosure-action]").forEach((element) => {
|
||||
element.addEventListener("click", () => handleMediaControlButtonClick(element));
|
||||
});
|
||||
|
||||
// Set playback from the last position if available
|
||||
document.querySelectorAll("audio[data-last-position],video[data-last-position]").forEach((element) => {
|
||||
if (element.dataset.lastPosition) {
|
||||
element.currentTime = element.dataset.lastPosition;
|
||||
}
|
||||
element.ontimeupdate = () => handlePlayerProgressionSaveAndMarkAsReadOnCompletion(element);
|
||||
});
|
||||
|
||||
// Set playback speed from the data attribute if available
|
||||
document.querySelectorAll("audio[data-playback-rate],video[data-playback-rate]").forEach((element) => {
|
||||
if (element.dataset.playbackRate) {
|
||||
element.playbackRate = element.dataset.playbackRate;
|
||||
if (element.dataset.enclosureId) {
|
||||
document.querySelectorAll(`span.speed-indicator[data-enclosure-id="${element.dataset.enclosureId}"]`).forEach((speedIndicatorElement) => {
|
||||
speedIndicatorElement.innerText = `${parseFloat(element.dataset.playbackRate).toFixed(2)}x`;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
85
internal/ui/static/js/bootstrap.js
vendored
85
internal/ui/static/js/bootstrap.js
vendored
|
@ -1,5 +1,7 @@
|
|||
disableSubmitButtonsOnFormSubmit();
|
||||
initializeMediaPlayerHandlers();
|
||||
|
||||
// Initialize the keyboard shortcuts if enabled.
|
||||
if (!document.querySelector("body[data-disable-keyboard-shortcuts=true]")) {
|
||||
const keyboardHandler = new KeyboardHandler();
|
||||
keyboardHandler.on("g u", () => goToPage("unread"));
|
||||
|
@ -47,40 +49,11 @@ if (!document.querySelector("body[data-disable-keyboard-shortcuts=true]")) {
|
|||
keyboardHandler.listen();
|
||||
}
|
||||
|
||||
// Initialize the touch handler for mobile devices.
|
||||
const touchHandler = new TouchHandler();
|
||||
touchHandler.listen();
|
||||
|
||||
if (WebAuthnHandler.isWebAuthnSupported()) {
|
||||
const webauthnHandler = new WebAuthnHandler();
|
||||
|
||||
onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); });
|
||||
|
||||
const registerButton = document.getElementById("webauthn-register");
|
||||
if (registerButton !== null) {
|
||||
registerButton.disabled = false;
|
||||
|
||||
onClick("#webauthn-register", () => {
|
||||
webauthnHandler.register().catch((err) => WebAuthnHandler.showErrorMessage(err));
|
||||
});
|
||||
}
|
||||
|
||||
const loginButton = document.getElementById("webauthn-login");
|
||||
if (loginButton !== null) {
|
||||
const abortController = new AbortController();
|
||||
loginButton.disabled = false;
|
||||
|
||||
onClick("#webauthn-login", () => {
|
||||
const usernameField = document.getElementById("form-username");
|
||||
if (usernameField !== null) {
|
||||
abortController.abort();
|
||||
webauthnHandler.login(usernameField.value).catch(err => WebAuthnHandler.showErrorMessage(err));
|
||||
}
|
||||
});
|
||||
|
||||
webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize click handlers.
|
||||
onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target));
|
||||
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
|
||||
onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent);
|
||||
|
@ -137,6 +110,7 @@ if ("serviceWorker" in navigator) {
|
|||
}
|
||||
}
|
||||
|
||||
// PWA install prompt handling.
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
let deferredPrompt = e;
|
||||
const promptHomeScreen = document.getElementById('prompt-home-screen');
|
||||
|
@ -157,33 +131,34 @@ window.addEventListener('beforeinstallprompt', (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Save and resume media position
|
||||
const lastPositionElements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
|
||||
lastPositionElements.forEach((element) => {
|
||||
if (element.dataset.lastPosition) {
|
||||
element.currentTime = element.dataset.lastPosition;
|
||||
}
|
||||
element.ontimeupdate = () => handlePlayerProgressionSaveAndMarkAsReadOnCompletion(element);
|
||||
});
|
||||
// PassKey handling.
|
||||
if (WebAuthnHandler.isWebAuthnSupported()) {
|
||||
const webauthnHandler = new WebAuthnHandler();
|
||||
|
||||
// Set media playback rate
|
||||
const playbackRateElements = document.querySelectorAll("audio[data-playback-rate],video[data-playback-rate]");
|
||||
playbackRateElements.forEach((element) => {
|
||||
if (element.dataset.playbackRate) {
|
||||
element.playbackRate = element.dataset.playbackRate;
|
||||
if (element.dataset.enclosureId){
|
||||
// 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
|
||||
// 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)=>{
|
||||
speedI.innerText = `${parseFloat(element.dataset.playbackRate).toFixed(2)}x`;
|
||||
onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); });
|
||||
|
||||
const registerButton = document.getElementById("webauthn-register");
|
||||
if (registerButton !== null) {
|
||||
registerButton.disabled = false;
|
||||
|
||||
onClick("#webauthn-register", () => {
|
||||
webauthnHandler.register().catch((err) => WebAuthnHandler.showErrorMessage(err));
|
||||
});
|
||||
}
|
||||
|
||||
const loginButton = document.getElementById("webauthn-login");
|
||||
if (loginButton !== null) {
|
||||
const abortController = new AbortController();
|
||||
loginButton.disabled = false;
|
||||
|
||||
onClick("#webauthn-login", () => {
|
||||
const usernameField = document.getElementById("form-username");
|
||||
if (usernameField !== null) {
|
||||
abortController.abort();
|
||||
webauthnHandler.login(usernameField.value).catch(err => WebAuthnHandler.showErrorMessage(err));
|
||||
}
|
||||
});
|
||||
|
||||
// Set enclosure media controls handlers
|
||||
const mediaControlsElements = document.querySelectorAll("button[data-enclosure-action]");
|
||||
mediaControlsElements.forEach((element) => {
|
||||
element.addEventListener("click", () => handleMediaControl(element));
|
||||
});
|
||||
webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue