mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
PWA: First implementation of offline mode
This commit is contained in:
parent
05f7a34d43
commit
925ea2c082
10 changed files with 1851 additions and 1076 deletions
|
@ -42,6 +42,7 @@ type User struct {
|
|||
DefaultHomePage string `json:"default_home_page"`
|
||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||
CacheForOffline bool `json:"cache_for_offline"`
|
||||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||
|
|
|
@ -970,6 +970,7 @@ var migrations = []func(tx *sql.Tx, driver string) error{
|
|||
return err
|
||||
},
|
||||
func(tx *sql.Tx, _ string) (err error) {
|
||||
<<<<<<< HEAD
|
||||
sql := `
|
||||
ALTER TABLE integrations ADD COLUMN discord_enabled bool default 'f';
|
||||
ALTER TABLE integrations ADD COLUMN discord_webhook_link text default '';
|
||||
|
@ -1015,4 +1016,9 @@ var migrations = []func(tx *sql.Tx, driver string) error{
|
|||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx, _ string) (err error) {
|
||||
sql := `ALTER TABLE users ADD COLUMN cache_for_offline boolean default 'f'`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -41,6 +41,7 @@ type User struct {
|
|||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||
CacheForOffline bool `json:"cache_for_offline"`
|
||||
}
|
||||
|
||||
// UserCreationRequest represents the request to create a user.
|
||||
|
@ -82,6 +83,7 @@ type UserModificationRequest struct {
|
|||
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||
BlockFilterEntryRules *string `json:"block_filter_entry_rules"`
|
||||
KeepFilterEntryRules *string `json:"keep_filter_entry_rules"`
|
||||
CacheForOffline *bool `json:"cache_for_offline"`
|
||||
}
|
||||
|
||||
// Patch updates the User object with the modification request.
|
||||
|
@ -197,6 +199,9 @@ func (u *UserModificationRequest) Patch(user *User) {
|
|||
if u.KeepFilterEntryRules != nil {
|
||||
user.KeepFilterEntryRules = *u.KeepFilterEntryRules
|
||||
}
|
||||
if u.CacheForOffline != nil {
|
||||
user.CacheForOffline = *u.CacheForOffline
|
||||
}
|
||||
}
|
||||
|
||||
// UseTimezone converts last login date to the given timezone.
|
||||
|
|
|
@ -96,7 +96,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
|||
mark_read_on_view,
|
||||
media_playback_rate,
|
||||
block_filter_entry_rules,
|
||||
keep_filter_entry_rules
|
||||
keep_filter_entry_rules,
|
||||
cache_for_offline
|
||||
`
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
|
@ -140,6 +141,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
|||
&user.MediaPlaybackRate,
|
||||
&user.BlockFilterEntryRules,
|
||||
&user.KeepFilterEntryRules,
|
||||
&user.CacheForOffline,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
@ -204,9 +206,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
mark_read_on_media_player_completion=$25,
|
||||
media_playback_rate=$26,
|
||||
block_filter_entry_rules=$27,
|
||||
keep_filter_entry_rules=$28
|
||||
keep_filter_entry_rules=$28,
|
||||
cache_for_offline=$29
|
||||
WHERE
|
||||
id=$29
|
||||
id=$30
|
||||
`
|
||||
|
||||
_, err = s.db.Exec(
|
||||
|
@ -239,6 +242,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.MediaPlaybackRate,
|
||||
user.BlockFilterEntryRules,
|
||||
user.KeepFilterEntryRules,
|
||||
user.CacheForOffline,
|
||||
user.ID,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -273,9 +277,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
mark_read_on_media_player_completion=$24,
|
||||
media_playback_rate=$25,
|
||||
block_filter_entry_rules=$26,
|
||||
keep_filter_entry_rules=$27
|
||||
keep_filter_entry_rules=$27,
|
||||
cache_for_offline=$28
|
||||
WHERE
|
||||
id=$28
|
||||
id=$29
|
||||
`
|
||||
|
||||
_, err := s.db.Exec(
|
||||
|
@ -307,6 +312,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.MediaPlaybackRate,
|
||||
user.BlockFilterEntryRules,
|
||||
user.KeepFilterEntryRules,
|
||||
user.CacheForOffline,
|
||||
user.ID,
|
||||
)
|
||||
|
||||
|
@ -360,7 +366,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
|||
mark_read_on_media_player_completion,
|
||||
media_playback_rate,
|
||||
block_filter_entry_rules,
|
||||
keep_filter_entry_rules
|
||||
keep_filter_entry_rules,
|
||||
cache_for_offline
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -401,7 +408,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
|||
mark_read_on_media_player_completion,
|
||||
media_playback_rate,
|
||||
block_filter_entry_rules,
|
||||
keep_filter_entry_rules
|
||||
keep_filter_entry_rules,
|
||||
cache_for_offline
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -442,7 +450,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
|||
mark_read_on_media_player_completion,
|
||||
media_playback_rate,
|
||||
block_filter_entry_rules,
|
||||
keep_filter_entry_rules
|
||||
keep_filter_entry_rules,
|
||||
cache_for_offline
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -490,7 +499,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
|||
u.mark_read_on_media_player_completion,
|
||||
media_playback_rate,
|
||||
u.block_filter_entry_rules,
|
||||
u.keep_filter_entry_rules
|
||||
u.keep_filter_entry_rules,
|
||||
u.cache_for_offline
|
||||
FROM
|
||||
users u
|
||||
LEFT JOIN
|
||||
|
@ -533,6 +543,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
|||
&user.MediaPlaybackRate,
|
||||
&user.BlockFilterEntryRules,
|
||||
&user.KeepFilterEntryRules,
|
||||
&user.CacheForOffline,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -646,7 +657,9 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
mark_read_on_media_player_completion,
|
||||
media_playback_rate,
|
||||
block_filter_entry_rules,
|
||||
keep_filter_entry_rules
|
||||
keep_filter_entry_rules,
|
||||
media_playback_rate,
|
||||
cache_for_offline
|
||||
FROM
|
||||
users
|
||||
ORDER BY username ASC
|
||||
|
@ -690,6 +703,7 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
&user.MediaPlaybackRate,
|
||||
&user.BlockFilterEntryRules,
|
||||
&user.KeepFilterEntryRules,
|
||||
&user.CacheForOffline,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -52,6 +52,7 @@ type SettingsForm struct {
|
|||
MediaPlaybackRate float64
|
||||
BlockFilterEntryRules string
|
||||
KeepFilterEntryRules string
|
||||
CacheForOffline bool
|
||||
}
|
||||
|
||||
// MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
|
||||
|
@ -119,6 +120,8 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
|||
user.MarkReadOnView = MarkReadOnView
|
||||
user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
|
||||
|
||||
user.CacheForOffline = s.CacheForOffline
|
||||
|
||||
if s.Password != "" {
|
||||
user.Password = s.Password
|
||||
}
|
||||
|
@ -205,5 +208,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
|||
MediaPlaybackRate: mediaPlaybackRate,
|
||||
BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
|
||||
KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
|
||||
CacheForOffline: r.FormValue("cache_for_offline") == "1",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
|||
MediaPlaybackRate: user.MediaPlaybackRate,
|
||||
BlockFilterEntryRules: user.BlockFilterEntryRules,
|
||||
KeepFilterEntryRules: user.KeepFilterEntryRules,
|
||||
CacheForOffline: user.CacheForOffline,
|
||||
}
|
||||
|
||||
timezones, err := h.store.Timezones()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,44 +1,66 @@
|
|||
|
||||
// Incrementing OFFLINE_VERSION will kick off the install event and force
|
||||
// previously cached resources to be updated from the network.
|
||||
const OFFLINE_VERSION = 1;
|
||||
const OFFLINE_VERSION = 2;
|
||||
const CACHE_NAME = "offline";
|
||||
|
||||
console.log(USE_CACHE);
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
|
||||
// Setting {cache: 'reload'} in the new request will ensure that the
|
||||
// response isn't fulfilled from the HTTP cache; i.e., it will be from
|
||||
// the network.
|
||||
await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
|
||||
})()
|
||||
);
|
||||
if (USE_CACHE) {
|
||||
await cache.addAll(["/", "/unread", OFFLINE_URL]);
|
||||
} else {
|
||||
// Setting {cache: 'reload'} in the new request will ensure that the
|
||||
// response isn't fulfilled from the HTTP cache; i.e., it will be from
|
||||
// the network.
|
||||
await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
|
||||
}
|
||||
})(),
|
||||
);
|
||||
|
||||
// Force the waiting service worker to become the active service worker.
|
||||
self.skipWaiting();
|
||||
// Force the waiting service worker to become the active service worker.
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
// We proxify requests through fetch() only if we are offline because it's slower.
|
||||
if (navigator.onLine === false && event.request.mode === "navigate") {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
try {
|
||||
// Always try the network first.
|
||||
const networkResponse = await fetch(event.request);
|
||||
return networkResponse;
|
||||
} catch (error) {
|
||||
// catch is only triggered if an exception is thrown, which is likely
|
||||
// due to a network error.
|
||||
// If fetch() returns a valid HTTP response with a response code in
|
||||
// the 4xx or 5xx range, the catch() will NOT be called.
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const cachedResponse = await cache.match(OFFLINE_URL);
|
||||
return cachedResponse;
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
// We proxify requests through fetch() only if we are offline because it's slower.
|
||||
if (
|
||||
USE_CACHE ||
|
||||
(navigator.onLine === false && event.request.mode === "navigate")
|
||||
) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
try {
|
||||
// Always try the network first.
|
||||
const networkResponse = 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 is only triggered if an exception is thrown, which is likely
|
||||
// due to a network error.
|
||||
// If fetch() returns a valid HTTP response with a response code in
|
||||
// the 4xx or 5xx range, the catch() will NOT be called.
|
||||
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);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,7 +31,19 @@ func (h *handler) showJavascript(w http.ResponseWriter, r *http.Request) {
|
|||
contents := static.JavascriptBundles[filename]
|
||||
|
||||
if filename == "service-worker" {
|
||||
variables := fmt.Sprintf(`const OFFLINE_URL=%q;`, route.Path(h.router, "offline"))
|
||||
user, err := h.store.UserByID(request.UserID(r))
|
||||
if err != nil {
|
||||
html.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
cacheForOffline := 0
|
||||
if user.CacheForOffline {
|
||||
cacheForOffline = 1
|
||||
}
|
||||
|
||||
variables := fmt.Sprintf(`const OFFLINE_URL=%q;const USE_CACHE=%d;`, route.Path(h.router, "offline"), cacheForOffline)
|
||||
|
||||
contents = append([]byte(variables), contents...)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue