2023-11-06 18:28:25 +01:00
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
2019-07-18 05:27:39 +02:00
|
|
|
handleSubmitButtons();
|
2018-07-06 07:18:51 +02:00
|
|
|
|
2019-07-18 05:27:39 +02:00
|
|
|
if (!document.querySelector("body[data-disable-keyboard-shortcuts=true]")) {
|
2024-03-20 23:59:37 +01:00
|
|
|
const keyboardHandler = new KeyboardHandler();
|
2019-07-18 05:27:39 +02:00
|
|
|
keyboardHandler.on("g u", () => goToPage("unread"));
|
|
|
|
keyboardHandler.on("g b", () => goToPage("starred"));
|
|
|
|
keyboardHandler.on("g h", () => goToPage("history"));
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("g f", goToFeedOrFeeds);
|
2019-07-18 05:27:39 +02:00
|
|
|
keyboardHandler.on("g c", () => goToPage("categories"));
|
|
|
|
keyboardHandler.on("g s", () => goToPage("settings"));
|
2024-03-20 03:30:38 +01:00
|
|
|
keyboardHandler.on("g g", () => goToPrevious(TOP));
|
|
|
|
keyboardHandler.on("G", () => goToNext(BOTTOM));
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("ArrowLeft", goToPrevious);
|
|
|
|
keyboardHandler.on("ArrowRight", goToNext);
|
|
|
|
keyboardHandler.on("k", goToPrevious);
|
|
|
|
keyboardHandler.on("p", goToPrevious);
|
|
|
|
keyboardHandler.on("j", goToNext);
|
|
|
|
keyboardHandler.on("n", goToNext);
|
2019-07-18 05:27:39 +02:00
|
|
|
keyboardHandler.on("h", () => goToPage("previous"));
|
|
|
|
keyboardHandler.on("l", () => goToPage("next"));
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("z t", scrollToCurrentItem);
|
|
|
|
keyboardHandler.on("o", openSelectedItem);
|
Add 'Enter' key as a hotkey to open selected item
There are a few things that need to be done, to make this work.
First, we need to register `Enter` as another hotkey that opens the
selected item.
However, by default the `KeyboardHandler` will override all default
actions. That might make sense for any other key, but for the `Enter`
key, we want to keep the default behavior (i.e. follow a selected link
or press a button). So for this single key event, we do not call
`preventDefault()`.
I see this as unproblematic for the following reasons.
1. With the changes from #2348, when we're in a list of items (articles,
categories, feeds), there is no link selected. This is what made the
`Enter` key work _implicitly_ in the past. With nothing selected, the
`Enter` key will do nothing by default.
2. If we have **any** link selected (including when we are in a view
with a list of selectable items), we'll get the default action of
`Enter` (i.e. follow a link), which is exactly what we had before.
Lastly, we need to update the list of keyboard shortcuts displayed when
pressing `?`.
This fixes #2366.
2024-02-20 09:10:51 +01:00
|
|
|
keyboardHandler.on("Enter", () => openSelectedItem());
|
2024-03-16 04:53:46 +01:00
|
|
|
keyboardHandler.on("v", () => openOriginalLink(false));
|
2019-11-29 22:48:56 +01:00
|
|
|
keyboardHandler.on("V", () => openOriginalLink(true));
|
2024-03-16 04:53:46 +01:00
|
|
|
keyboardHandler.on("c", () => openCommentLink(false));
|
2020-01-07 07:02:02 +01:00
|
|
|
keyboardHandler.on("C", () => openCommentLink(true));
|
2022-02-01 04:57:14 +01:00
|
|
|
keyboardHandler.on("m", () => handleEntryStatus("next"));
|
|
|
|
keyboardHandler.on("M", () => handleEntryStatus("previous"));
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("A", markPageAsRead);
|
2024-03-16 04:53:46 +01:00
|
|
|
keyboardHandler.on("s", () => handleSaveEntry());
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("d", handleFetchOriginalContent);
|
2024-03-16 04:53:46 +01:00
|
|
|
keyboardHandler.on("f", () => handleBookmark());
|
2024-03-11 01:01:41 +01:00
|
|
|
keyboardHandler.on("F", goToFeed);
|
|
|
|
keyboardHandler.on("R", handleRefreshAllFeeds);
|
|
|
|
keyboardHandler.on("?", showKeyboardShortcuts);
|
|
|
|
keyboardHandler.on("+", goToAddSubscription);
|
|
|
|
keyboardHandler.on("#", unsubscribeFromFeed);
|
2024-03-02 01:12:17 +01:00
|
|
|
keyboardHandler.on("/", () => goToPage("search"));
|
2023-03-14 04:13:10 +01:00
|
|
|
keyboardHandler.on("a", () => {
|
2024-03-11 01:01:41 +01:00
|
|
|
const enclosureElement = document.querySelector('.entry-enclosures');
|
2023-03-14 04:13:10 +01:00
|
|
|
if (enclosureElement) {
|
|
|
|
enclosureElement.toggleAttribute('open');
|
|
|
|
}
|
|
|
|
});
|
2019-04-29 03:20:46 +02:00
|
|
|
keyboardHandler.on("Escape", () => ModalHandler.close());
|
|
|
|
keyboardHandler.listen();
|
|
|
|
}
|
2018-07-06 07:18:51 +02:00
|
|
|
|
2024-03-20 23:59:37 +01:00
|
|
|
const touchHandler = new TouchHandler();
|
2019-03-09 14:00:26 +01:00
|
|
|
touchHandler.listen();
|
|
|
|
|
2023-11-06 18:28:25 +01:00
|
|
|
if (WebAuthnHandler.isWebAuthnSupported()) {
|
|
|
|
const webauthnHandler = new WebAuthnHandler();
|
|
|
|
|
2024-03-11 04:32:39 +01:00
|
|
|
onClick("#webauthn-delete", () => { webauthnHandler.removeAllCredentials(); });
|
2023-11-06 18:28:25 +01:00
|
|
|
|
2024-03-20 23:59:37 +01:00
|
|
|
const registerButton = document.getElementById("webauthn-register");
|
2023-11-06 18:28:25 +01:00
|
|
|
if (registerButton != null) {
|
|
|
|
registerButton.disabled = false;
|
|
|
|
|
|
|
|
onClick("#webauthn-register", () => {
|
|
|
|
webauthnHandler.register().catch((err) => WebAuthnHandler.showErrorMessage(err));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-03-20 23:59:37 +01:00
|
|
|
const loginButton = document.getElementById("webauthn-login");
|
2023-11-06 18:28:25 +01:00
|
|
|
if (loginButton != null) {
|
|
|
|
const abortController = new AbortController();
|
|
|
|
loginButton.disabled = false;
|
|
|
|
|
|
|
|
onClick("#webauthn-login", () => {
|
2024-03-20 23:59:37 +01:00
|
|
|
const usernameField = document.getElementById("form-username");
|
2023-11-06 18:28:25 +01:00
|
|
|
if (usernameField != null) {
|
|
|
|
abortController.abort();
|
|
|
|
webauthnHandler.login(usernameField.value).catch(err => WebAuthnHandler.showErrorMessage(err));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
webauthnHandler.conditionalLogin(abortController).catch(err => WebAuthnHandler.showErrorMessage(err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Replace link has button role with button tag
# Change HTML tag to button
Replace the link tag with an HTML button to prevent some screen readers from having confusing announcements. By using the HTML button, users can use the Enter and Space keys to activate actions by default, instead of implementing them in JavaScript.
# Differentiate links and buttons visually
When activating the link element, the user may expect the web page to navigate to the URL and the page will refresh; when activating the button element, the user may expect the web page to still be on the same page, so that their current state, such as: input value, won't disappear.
Links and buttons should have different styles visually, so that users can't expect what will happen when they activate a link or a button.
I added the underline to the links, because that is the common pattern. Buttons have border and background color in a common pattern. But I think that will change the current layout drastically. So I added the focus, hover and active classes to the buttons instead.
2024-02-10 02:09:30 +01:00
|
|
|
onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target));
|
|
|
|
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
|
2024-03-11 01:01:41 +01:00
|
|
|
onClick(":is(a, button)[data-fetch-content-entry]", handleFetchOriginalContent);
|
|
|
|
onClick(":is(a, button)[data-share-status]", handleShare);
|
|
|
|
onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, markPageAsRead));
|
Replace link has button role with button tag
# Change HTML tag to button
Replace the link tag with an HTML button to prevent some screen readers from having confusing announcements. By using the HTML button, users can use the Enter and Space keys to activate actions by default, instead of implementing them in JavaScript.
# Differentiate links and buttons visually
When activating the link element, the user may expect the web page to navigate to the URL and the page will refresh; when activating the button element, the user may expect the web page to still be on the same page, so that their current state, such as: input value, won't disappear.
Links and buttons should have different styles visually, so that users can't expect what will happen when they activate a link or a button.
I added the underline to the links, because that is the common pattern. Buttons have border and background color in a common pattern. But I think that will change the current layout drastically. So I added the focus, hover and active classes to the buttons instead.
2024-02-10 02:09:30 +01:00
|
|
|
onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
|
|
|
|
onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
|
2024-03-20 23:59:37 +01:00
|
|
|
const request = new RequestBuilder(url);
|
2019-07-18 06:07:29 +02:00
|
|
|
|
2023-08-08 16:12:41 +02:00
|
|
|
request.withCallback((response) => {
|
2019-07-18 06:07:29 +02:00
|
|
|
if (redirectURL) {
|
|
|
|
window.location.href = redirectURL;
|
2023-08-08 16:12:41 +02:00
|
|
|
} else if (response && response.redirected && response.url) {
|
|
|
|
window.location.href = response.url;
|
2019-07-18 06:07:29 +02:00
|
|
|
} else {
|
|
|
|
window.location.reload();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
request.execute();
|
|
|
|
}));
|
2018-07-06 07:18:51 +02:00
|
|
|
|
2023-03-17 14:56:17 +01:00
|
|
|
onClick("a[data-original-link='true']", (event) => {
|
2022-02-01 04:57:14 +01:00
|
|
|
handleEntryStatus("next", event.target, true);
|
2021-04-27 14:45:05 +02:00
|
|
|
}, true);
|
2023-03-17 14:56:17 +01:00
|
|
|
onAuxClick("a[data-original-link='true']", (event) => {
|
2021-04-27 14:45:05 +02:00
|
|
|
if (event.button == 1) {
|
2022-02-01 04:57:14 +01:00
|
|
|
handleEntryStatus("next", event.target, true);
|
2021-04-27 14:45:05 +02:00
|
|
|
}
|
|
|
|
}, true);
|
|
|
|
|
2024-03-11 04:32:39 +01:00
|
|
|
checkMenuToggleModeByLayout();
|
|
|
|
window.addEventListener("resize", checkMenuToggleModeByLayout, { passive: true });
|
2024-02-07 09:54:11 +01:00
|
|
|
|
2024-03-11 04:32:39 +01:00
|
|
|
fixVoiceOverDetailsSummaryBug();
|
2024-02-14 10:07:56 +01:00
|
|
|
|
2024-03-11 04:32:39 +01:00
|
|
|
const logoElement = document.querySelector(".logo");
|
2024-03-14 05:40:56 +01:00
|
|
|
if (logoElement) {
|
|
|
|
logoElement.addEventListener("click", toggleMainMenu);
|
|
|
|
logoElement.addEventListener("keydown", toggleMainMenu);
|
|
|
|
}
|
2024-02-07 09:54:11 +01:00
|
|
|
|
|
|
|
onClick(".header nav li", (event) => onClickMainMenuListItem(event));
|
2018-07-16 06:51:09 +02:00
|
|
|
|
|
|
|
if ("serviceWorker" in navigator) {
|
2024-03-20 23:59:37 +01:00
|
|
|
const scriptElement = document.getElementById("service-worker-script");
|
2018-07-16 06:51:09 +02:00
|
|
|
if (scriptElement) {
|
2024-03-18 00:45:41 +01:00
|
|
|
navigator.serviceWorker.register(ttpolicy.createScriptURL(scriptElement.src));
|
2018-07-16 06:51:09 +02:00
|
|
|
}
|
|
|
|
}
|
2020-02-09 20:41:00 +01:00
|
|
|
|
|
|
|
window.addEventListener('beforeinstallprompt', (e) => {
|
|
|
|
let deferredPrompt = e;
|
|
|
|
const promptHomeScreen = document.getElementById('prompt-home-screen');
|
|
|
|
if (promptHomeScreen) {
|
|
|
|
promptHomeScreen.style.display = "block";
|
|
|
|
|
|
|
|
const btnAddToHomeScreen = document.getElementById('btn-add-to-home-screen');
|
|
|
|
if (btnAddToHomeScreen) {
|
|
|
|
btnAddToHomeScreen.addEventListener('click', (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
deferredPrompt.prompt();
|
|
|
|
deferredPrompt.userChoice.then(() => {
|
|
|
|
deferredPrompt = null;
|
|
|
|
promptHomeScreen.style.display = "none";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-05-27 06:35:44 +02:00
|
|
|
});
|
Add Media Player and resume to last playback position
In order to ease podcast listening, the player can be put on top of the feed entry as main content.
Use the `Use podcast player` option to enable that. It works on audio and video.
Also, when playing audio or video, progression will be saved in order to be able to resume listening later.
This position saving is done using the original attachement/enclosures player AND podcast player and do not rely on
the podcast player option ti be enabled.
Additionally, I made the player fill the width with the entry container to ease seeking and have a bigger video.
updateEnclosures now keep existing enclosures based on URL
When feeds get updated, enclosures entries are always wiped and re-created. This cause two issue
- enclosure progression get lost in the process
- enclosure ID changes
I used the URL as identifier of an enclosure. Not perfect but hopefully should work.
When an enclosure already exist, I simply do nothing and leave the entry as is in the database.
If anyone is listening/watching to this enclosure during the refresh, the id stay coherent and progression saving still works.
The updateEnclosures function got a bit more complex. I tried to make it the more clear I could.
Some optimisation are possible but would make the function harder to read in my opinion.
I'm not sure if this is often the case, but some feeds may include tracking or simply change the url each
time we update the feed. In those situation, enclosures ids and progression will be lost.
I have no idea how to handle this last situation. Use the size instead/alongside url to define the identity of an enclosure ?
Translation: english as placeholder for every language except French
Aside, I tested a video feed and fixed a few things for it. In fact, the MimeType was not working
at all on my side, and found a pretty old stackoverflow discussion that suggest to use an Apple non-standard MimeType for
m4v video format. I only did one substitution because I only have one feed to test. Any new video feed can make this go away
or evolve depending on the situation. Real video feeds does not tend to be easy to find and test extensively this.
Co-authored-by: toastal
2023-04-13 11:46:43 +02:00
|
|
|
|
2023-11-06 18:28:25 +01:00
|
|
|
// Save and resume media position
|
2024-03-16 14:20:02 +01:00
|
|
|
const lastPositionElements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
|
|
|
|
lastPositionElements.forEach((element) => {
|
2023-11-06 18:28:25 +01:00
|
|
|
if (element.dataset.lastPosition) {
|
|
|
|
element.currentTime = element.dataset.lastPosition;
|
|
|
|
}
|
Add Media Player and resume to last playback position
In order to ease podcast listening, the player can be put on top of the feed entry as main content.
Use the `Use podcast player` option to enable that. It works on audio and video.
Also, when playing audio or video, progression will be saved in order to be able to resume listening later.
This position saving is done using the original attachement/enclosures player AND podcast player and do not rely on
the podcast player option ti be enabled.
Additionally, I made the player fill the width with the entry container to ease seeking and have a bigger video.
updateEnclosures now keep existing enclosures based on URL
When feeds get updated, enclosures entries are always wiped and re-created. This cause two issue
- enclosure progression get lost in the process
- enclosure ID changes
I used the URL as identifier of an enclosure. Not perfect but hopefully should work.
When an enclosure already exist, I simply do nothing and leave the entry as is in the database.
If anyone is listening/watching to this enclosure during the refresh, the id stay coherent and progression saving still works.
The updateEnclosures function got a bit more complex. I tried to make it the more clear I could.
Some optimisation are possible but would make the function harder to read in my opinion.
I'm not sure if this is often the case, but some feeds may include tracking or simply change the url each
time we update the feed. In those situation, enclosures ids and progression will be lost.
I have no idea how to handle this last situation. Use the size instead/alongside url to define the identity of an enclosure ?
Translation: english as placeholder for every language except French
Aside, I tested a video feed and fixed a few things for it. In fact, the MimeType was not working
at all on my side, and found a pretty old stackoverflow discussion that suggest to use an Apple non-standard MimeType for
m4v video format. I only did one substitution because I only have one feed to test. Any new video feed can make this go away
or evolve depending on the situation. Real video feeds does not tend to be easy to find and test extensively this.
Co-authored-by: toastal
2023-04-13 11:46:43 +02:00
|
|
|
element.ontimeupdate = () => handlePlayerProgressionSave(element);
|
|
|
|
});
|
2024-03-16 14:20:02 +01:00
|
|
|
|
|
|
|
// 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;
|
2024-04-10 17:14:38 +02:00
|
|
|
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 = `${element.dataset.playbackRate}x`;
|
|
|
|
});
|
|
|
|
}
|
2024-03-16 14:20:02 +01:00
|
|
|
}
|
|
|
|
});
|
2024-04-10 17:14:38 +02:00
|
|
|
|
|
|
|
// Set enclosure media controls handlers
|
|
|
|
const mediaControlsElements = document.querySelectorAll("button[data-enclosure-action]");
|
|
|
|
mediaControlsElements.forEach((element) => {
|
|
|
|
element.addEventListener("click", () => handleMediaControl(element));
|
|
|
|
});
|
2018-07-06 07:18:51 +02:00
|
|
|
});
|