1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-07-22 17:18:37 +00:00

Fix cache strategy and warn user

This commit is contained in:
Brieuc Dubois 2025-01-04 18:49:47 +01:00
parent c50edd735a
commit fbc3294f30
5 changed files with 668 additions and 624 deletions

View file

@ -784,6 +784,7 @@
"page.offline.message": "You are offline", "page.offline.message": "You are offline",
"page.offline.refresh_page": "Try to refresh the page", "page.offline.refresh_page": "Try to refresh the page",
"page.webauthn_rename.title": "Rename Passkey", "page.webauthn_rename.title": "Rename Passkey",
"page.cache.warning": "You're viewing a cached version. Refresh to see the latest one.",
"alert.no_shared_entry": "There is no shared entry.", "alert.no_shared_entry": "There is no shared entry.",
"alert.no_bookmark": "There are no starred entries.", "alert.no_bookmark": "There are no starred entries.",
"alert.no_category": "There is no category.", "alert.no_category": "There is no category.",

View file

@ -132,7 +132,7 @@
{{ if .flashErrorMessage }} {{ if .flashErrorMessage }}
<div role="alert" class="flash-error-message alert alert-error">{{ .flashErrorMessage }}</div> <div role="alert" class="flash-error-message alert alert-error">{{ .flashErrorMessage }}</div>
{{ end }} {{ end }}
<div id="offline-flag" role="alert" aria-live="assertive" aria-atomic="true" class="flash-message alert alert-warning hidden">{{ t "page.offline.warning" }}</div> <div role="alert" aria-live="assertive" aria-atomic="true" class="flash-message alert alert-warning offline-hidden">{{ t "page.cache.warning" }}</div>
{{template "page_header" .}} {{template "page_header" .}}

View file

@ -90,6 +90,7 @@ func (h *handler) showUnreadEntryPage(w http.ResponseWriter, r *http.Request) {
view.Set("user", user) view.Set("user", user)
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID)) view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID)) view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
view.Set("useCachedVersion", r.Header.Get("X-Cache-Hit") != "")
// Fetching the counter here avoid to be off by one. // Fetching the counter here avoid to be off by one.
view.Set("countUnread", h.store.CountUnreadEntries(user.ID)) view.Set("countUnread", h.store.CountUnreadEntries(user.ID))

View file

@ -24,7 +24,9 @@ hr {
padding-bottom: 10px; padding-bottom: 10px;
} }
h1, h2, h3 { h1,
h2,
h3 {
color: var(--title-color); color: var(--title-color);
} }
@ -75,7 +77,8 @@ a:hover {
padding: var(--padding-size); padding: var(--padding-size);
position: absolute; position: absolute;
transition: translate 0.3s; transition: translate 0.3s;
translate: -50% calc(-100% - calc(var(--padding-size) * 2) - calc(var(--border-size) * 2)); translate: -50%
calc(-100% - calc(var(--padding-size) * 2) - calc(var(--border-size) * 2));
} }
.skip-to-content-link:focus { .skip-to-content-link:focus {
@ -262,7 +265,7 @@ a:hover {
} }
#toast-msg { #toast-msg {
background-color: rgba(0,0,0,0.7); background-color: rgba(0, 0, 0, 0.7);
padding-bottom: 4px; padding-bottom: 4px;
padding-left: 4px; padding-left: 4px;
padding-right: 5px; padding-right: 5px;
@ -274,11 +277,30 @@ a:hover {
} }
@keyframes toastKeyFrames { @keyframes toastKeyFrames {
0% {visibility: hidden; opacity: 0;} 0% {
25% {visibility: visible; opacity: 1; z-index: 9999} visibility: hidden;
50% {visibility: visible; opacity: 1; z-index: 9999} opacity: 0;
75% {visibility: visible; opacity: 1; z-index: 9999} }
100% {visibility: hidden; opacity: 0; z-index: 0} 25% {
visibility: visible;
opacity: 1;
z-index: 9999;
}
50% {
visibility: visible;
opacity: 1;
z-index: 9999;
}
75% {
visibility: visible;
opacity: 1;
z-index: 9999;
}
100% {
visibility: hidden;
opacity: 0;
z-index: 0;
}
} }
/* Hide the logo when there is not enough space to display menus when using languages more verbose than English */ /* Hide the logo when there is not enough space to display menus when using languages more verbose than English */
@ -309,7 +331,7 @@ a:hover {
padding: 0 12px 0 0; padding: 0 12px 0 0;
line-height: normal; line-height: normal;
border: none; border: none;
font-size: 1.0em; font-size: 1em;
} }
.header nav { .header nav {
@ -321,7 +343,8 @@ a:hover {
display: none; display: none;
} }
.header ul:not(.js-menu-show), .header ul.js-menu-show { .header ul:not(.js-menu-show),
.header ul.js-menu-show {
display: revert; display: revert;
} }
@ -347,11 +370,14 @@ table {
border-collapse: collapse; border-collapse: collapse;
} }
table, th, td { table,
th,
td {
border: 1px solid var(--table-border-color); border: 1px solid var(--table-border-color);
} }
th, td { th,
td {
padding: 5px; padding: 5px;
text-align: left; text-align: left;
} }
@ -642,7 +668,7 @@ template {
right: 0; right: 0;
font-size: 1.7em; font-size: 1.7em;
color: #ccc; color: #ccc;
padding:0 .2em; padding: 0 0.2em;
margin: 10px; margin: 10px;
text-decoration: none; text-decoration: none;
background-color: transparent; background-color: transparent;
@ -706,7 +732,6 @@ template {
color: var(--category-link-hover-color); color: var(--category-link-hover-color);
} }
.category-item-total { .category-item-total {
color: var(--body-color); color: var(--body-color);
} }
@ -746,7 +771,8 @@ template {
padding-left: 15px; padding-left: 15px;
} }
.pagination-next, .pagination-last { .pagination-next,
.pagination-last {
text-align: right; text-align: right;
} }
@ -784,7 +810,8 @@ template {
} }
.item.current-item { .item.current-item {
border: var(--current-item-border-width) solid var(--current-item-border-color); border: var(--current-item-border-width) solid
var(--current-item-border-color);
padding: 3px; padding: 3px;
box-shadow: var(--current-item-box-shadow); box-shadow: var(--current-item-box-shadow);
} }
@ -793,7 +820,6 @@ template {
outline: none; outline: none;
} }
.item-header { .item-header {
font-size: 1rem; font-size: 1rem;
} }
@ -948,7 +974,7 @@ article.category-has-unread {
} }
.entry header h1 { .entry header h1 {
font-size: 2.0em; font-size: 2em;
line-height: 1.25em; line-height: 1.25em;
margin: 5px 0 30px 0; margin: 5px 0 30px 0;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -1026,7 +1052,12 @@ article.category-has-unread {
touch-action: pan-y pinch-zoom; touch-action: pan-y pinch-zoom;
} }
.entry-content h1, h2, h3, h4, h5, h6 { .entry-content h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 15px; margin-top: 15px;
margin-bottom: 10px; margin-bottom: 10px;
} }
@ -1242,37 +1273,38 @@ details.entry-enclosures {
opacity: 20%; opacity: 20%;
} }
audio, video { audio,
video {
width: 100%; width: 100%;
} }
.media-controls{ .media-controls {
font-size: .9em; font-size: 0.9em;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.media-controls .media-control-label{ .media-controls .media-control-label {
line-height: 1em; line-height: 1em;
} }
.media-controls>div{ .media-controls > div {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content:center; justify-content: center;
min-width: 50%; min-width: 50%;
align-items: center; align-items: center;
} }
.media-controls>div>*{ .media-controls > div > * {
padding-left:12px; padding-left: 12px;
} }
.media-controls>div>*:first-child{ .media-controls > div > *:first-child {
padding-left:0; padding-left: 0;
} }
.media-controls span.speed-indicator{ .media-controls span.speed-indicator {
/*monospace to ensure constant width even when value change. JS ensure the value is always on 4 characters (in most cases) /*monospace to ensure constant width even when value change. JS ensure the value is always on 4 characters (in most cases)
This reduce ui flickering due to element moving around a bit This reduce ui flickering due to element moving around a bit
*/ */
@ -1291,6 +1323,7 @@ audio, video {
margin-top: 15px; margin-top: 15px;
} }
.hidden { .hidden,
.offline-hidden {
display: none; display: none;
} }

View file

@ -23,39 +23,53 @@ self.addEventListener("install", (event) => {
self.skipWaiting(); self.skipWaiting();
}); });
async function cacheFirstWithRefresh(request) {
const cache = await caches.open(CACHE_NAME);
const fetchResponsePromise = fetch(request).then(async (networkResponse) => {
if (!networkResponse.ok) return networkResponse;
const contentType = networkResponse.headers.get("Content-Type");
if (!contentType || !contentType.includes("text/html")) {
cache.put(request, networkResponse.clone());
return networkResponse;
}
const text = await networkResponse.clone().text();
const modifiedHtml = text.replace(/offline-hidden/g, "offline-visibe");
const clonedResponse = new Response(modifiedHtml, {
status: networkResponse.status,
statusText: networkResponse.statusText,
headers: networkResponse.headers,
});
cache.put(request, clonedResponse.clone());
return networkResponse;
});
return (await cache.match(request)) || (await fetchResponsePromise);
}
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
if (USE_CACHE) {
return event.respondWith(cacheFirstWithRefresh(event.request));
}
// We proxify requests through fetch() only if we are offline because it's slower. // We proxify requests through fetch() only if we are offline because it's slower.
if ( if (navigator.onLine === false && event.request.mode === "navigate") {
USE_CACHE ||
(navigator.onLine === false && event.request.mode === "navigate")
) {
event.respondWith( event.respondWith(
(async () => { (async () => {
try { try {
// Always try the network first. // Always try the network first.
const networkResponse = await fetch(event.request); return await fetch(event.request);
if (USE_CACHE) {
const cache = await caches.open(CACHE_NAME);
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
} catch (error) { } catch (error) {
// catch is only triggered if an exception is thrown, which is likely // catch is only triggered if an exception is thrown, which is likely
// due to a network error. // due to a network error.
// If fetch() returns a valid HTTP response with a response code in // If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called. // the 4xx or 5xx range, the catch() will NOT be called.
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
if (!USE_CACHE) {
return await cache.match(OFFLINE_URL);
}
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
return cachedResponse;
}
return await cache.match(OFFLINE_URL); return await cache.match(OFFLINE_URL);
} }
})(), })(),
@ -91,8 +105,3 @@ self.addEventListener("load", async (event) => {
} }
} }
}); });
self.addEventListener("DOMContentLoaded", function () {
const offlineFlag = document.getElementById("offline-flag");
offlineFlag.classList.toggle("hidden", navigator.onLine);
});