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:
parent
c50edd735a
commit
fbc3294f30
5 changed files with 668 additions and 624 deletions
|
@ -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.",
|
||||||
|
|
|
@ -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" .}}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue