Fix mark as read request showing popup due to onbeforeunload (#7554)

* Prevent onbeforeunload from showing a popup before leaving

* Send mark as read request when leaving and revert interval

* Use visibilitychange event instead of onbeforeunload, and refactor send_mark_read_queue to use fetch

* Move removed code to the new `catch` block

* Refactor with async fetch
This commit is contained in:
Inverle
2025-07-16 16:59:06 +02:00
committed by GitHub
parent edda6f0159
commit 6744a2fdce

View File

@@ -212,85 +212,88 @@ function removeArticle(div) {
const pending_entries = {};
let mark_read_queue = [];
function send_mark_read_queue(queue, asRead, callback) {
async function send_mark_read_queue(queue, asRead, callback) {
if (!queue || queue.length === 0) {
if (callback) {
callback();
}
return;
}
const req = new XMLHttpRequest();
req.open('POST', '.?c=entry&a=read' + (asRead ? '' : '&is_read=0'), true);
req.responseType = 'json';
req.onerror = function (e) {
for (let i = queue.length - 1; i >= 0; i--) {
const div = document.getElementById('flux_' + queue[i]);
div.querySelectorAll('a.read > .icon').forEach(icon => {
icon.outerHTML = div.classList.contains('not_read') ? context.icons.unread : context.icons.read;
let json;
try {
const resp = await fetch('.?c=entry&a=read' + (asRead ? '' : '&is_read=0'), {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
},
body: JSON.stringify({
ajax: true,
_csrf: context.csrf,
id: queue,
}),
keepalive: true
});
if (!resp.ok) {
for (let i = queue.length - 1; i >= 0; i--) {
const div = document.getElementById('flux_' + queue[i]);
div.querySelectorAll('a.read > .icon').forEach(icon => {
icon.outerHTML = div.classList.contains('not_read') ? context.icons.unread : context.icons.read;
});
delete pending_entries['flux_' + queue[i]];
}
badAjax(resp.status == 403);
return;
}
json = await resp.json();
} catch (e) {
console.error(e.message);
badAjax();
return;
}
for (let i = queue.length - 1; i >= 0; i--) {
const div = document.getElementById('flux_' + queue[i]);
const myIcons = context.icons;
let inc = 0;
if (div.classList.contains('not_read')) {
div.classList.remove('not_read');
div.querySelectorAll('a.read').forEach(function (a) {
a.href = a.href.replace('&is_read=0', '') + '&is_read=1';
});
delete pending_entries['flux_' + queue[i]];
}
badAjax(this.status == 403);
};
req.onload = function (e) {
if (this.status != 200) {
return req.onerror(e);
}
const json = xmlHttpRequestJson(this);
if (!json) {
return req.onerror(e);
}
for (let i = queue.length - 1; i >= 0; i--) {
const div = document.getElementById('flux_' + queue[i]);
const myIcons = context.icons;
let inc = 0;
if (div.classList.contains('not_read')) {
div.classList.remove('not_read');
div.querySelectorAll('a.read').forEach(function (a) {
a.href = a.href.replace('&is_read=0', '') + '&is_read=1';
});
div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.read; });
inc--;
if (context.auto_remove_article) {
removeArticle(div);
}
} else {
div.classList.add('not_read');
div.classList.add('keep_unread'); // Split for IE11
div.querySelectorAll('a.read').forEach(function (a) {
a.href = a.href.replace('&is_read=1', '');
});
div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.unread; });
inc++;
div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.read; });
inc--;
if (context.auto_remove_article) {
removeArticle(div);
}
const feed_link = div.querySelector('.website > a, a.website');
if (feed_link) {
const feed_url = feed_link.href;
const feed_id = feed_url.substr(feed_url.lastIndexOf('f_'));
incUnreadsFeed(div, feed_id, inc);
}
delete pending_entries['flux_' + queue[i]];
} else {
div.classList.add('not_read');
div.classList.add('keep_unread'); // Split for IE11
div.querySelectorAll('a.read').forEach(function (a) {
a.href = a.href.replace('&is_read=1', '');
});
div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.unread; });
inc++;
}
faviconNbUnread();
if (json.tags) {
const tagIds = Object.keys(json.tags);
for (let i = tagIds.length - 1; i >= 0; i--) {
const tagId = tagIds[i];
incUnreadsTag(tagId, (asRead ? -1 : 1) * json.tags[tagId].length);
}
const feed_link = div.querySelector('.website > a, a.website');
if (feed_link) {
const feed_url = feed_link.href;
const feed_id = feed_url.substr(feed_url.lastIndexOf('f_'));
incUnreadsFeed(div, feed_id, inc);
}
toggle_bigMarkAsRead_button();
onScroll();
if (callback) {
callback();
delete pending_entries['flux_' + queue[i]];
}
faviconNbUnread();
if (json.tags) {
const tagIds = Object.keys(json.tags);
for (let i = tagIds.length - 1; i >= 0; i--) {
const tagId = tagIds[i];
incUnreadsTag(tagId, (asRead ? -1 : 1) * json.tags[tagId].length);
}
};
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
req.send(JSON.stringify({
ajax: true,
_csrf: context.csrf,
id: queue,
}));
}
toggle_bigMarkAsRead_button();
onScroll();
if (callback) {
callback();
}
}
let send_mark_read_queue_timeout = 0;
@@ -2096,15 +2099,18 @@ function init_normal() {
init_actualize();
faviconNbUnread();
window.onbeforeunload = function (e) {
const sidebar = document.getElementById('sidebar');
if (sidebar) { // Save sidebar scroll position
sessionStorage.setItem('FreshRSS_sidebar_scrollTop', sidebar.scrollTop);
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
const sidebar = document.getElementById('sidebar');
if (sidebar) { // Save sidebar scroll position
sessionStorage.setItem('FreshRSS_sidebar_scrollTop', sidebar.scrollTop);
}
if (mark_read_queue && mark_read_queue.length > 0) {
clearTimeout(send_mark_read_queue_timeout);
send_mark_queue_tick(null);
}
}
if (mark_read_queue && mark_read_queue.length > 0) {
return false;
}
};
});
}
function init_main_beforeDOM() {