mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
Merge d88b8a1d3b
into b583de88f3
This commit is contained in:
commit
2729a76ce4
17 changed files with 2555 additions and 1679 deletions
|
@ -46,6 +46,7 @@ type User struct {
|
||||||
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||||
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||||
ExternalFontHosts string `json:"external_font_hosts"`
|
ExternalFontHosts string `json:"external_font_hosts"`
|
||||||
|
CacheForOffline bool `json:"cache_for_offline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) String() string {
|
func (u User) String() string {
|
||||||
|
|
|
@ -970,6 +970,7 @@ var migrations = []func(tx *sql.Tx, driver string) error{
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
func(tx *sql.Tx, _ string) (err error) {
|
func(tx *sql.Tx, _ string) (err error) {
|
||||||
|
<<<<<<< HEAD
|
||||||
sql := `
|
sql := `
|
||||||
ALTER TABLE integrations ADD COLUMN discord_enabled bool default 'f';
|
ALTER TABLE integrations ADD COLUMN discord_enabled bool default 'f';
|
||||||
ALTER TABLE integrations ADD COLUMN discord_webhook_link text default '';
|
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)
|
_, err = tx.Exec(sql)
|
||||||
return err
|
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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,11 @@ func LastForceRefresh(r *http.Request) int64 {
|
||||||
return timestamp
|
return timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if the request is from a service worker.
|
||||||
|
func IsServiceWorker(r *http.Request) bool {
|
||||||
|
return r.Header.Get("Client-Type") == "service-worker"
|
||||||
|
}
|
||||||
|
|
||||||
// ClientIP returns the client IP address stored in the context.
|
// ClientIP returns the client IP address stored in the context.
|
||||||
func ClientIP(r *http.Request) string {
|
func ClientIP(r *http.Request) string {
|
||||||
return getContextStringValue(r, ClientIPContextKey)
|
return getContextStringValue(r, ClientIPContextKey)
|
||||||
|
|
|
@ -100,7 +100,7 @@ func (b *Builder) Write() {
|
||||||
func (b *Builder) writeHeaders() {
|
func (b *Builder) writeHeaders() {
|
||||||
b.headers["X-Content-Type-Options"] = "nosniff"
|
b.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
b.headers["X-Frame-Options"] = "DENY"
|
b.headers["X-Frame-Options"] = "DENY"
|
||||||
b.headers["Referrer-Policy"] = "no-referrer"
|
b.headers["Referrer-Policy"] = "strict-origin"
|
||||||
|
|
||||||
for key, value := range b.headers {
|
for key, value := range b.headers {
|
||||||
b.w.Header().Set(key, value)
|
b.w.Header().Set(key, value)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -41,6 +41,7 @@ type User struct {
|
||||||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||||
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||||
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||||
|
CacheForOffline bool `json:"cache_for_offline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCreationRequest represents the request to create a user.
|
// UserCreationRequest represents the request to create a user.
|
||||||
|
@ -82,6 +83,7 @@ type UserModificationRequest struct {
|
||||||
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||||
BlockFilterEntryRules *string `json:"block_filter_entry_rules"`
|
BlockFilterEntryRules *string `json:"block_filter_entry_rules"`
|
||||||
KeepFilterEntryRules *string `json:"keep_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.
|
// Patch updates the User object with the modification request.
|
||||||
|
@ -197,6 +199,9 @@ func (u *UserModificationRequest) Patch(user *User) {
|
||||||
if u.KeepFilterEntryRules != nil {
|
if u.KeepFilterEntryRules != nil {
|
||||||
user.KeepFilterEntryRules = *u.KeepFilterEntryRules
|
user.KeepFilterEntryRules = *u.KeepFilterEntryRules
|
||||||
}
|
}
|
||||||
|
if u.CacheForOffline != nil {
|
||||||
|
user.CacheForOffline = *u.CacheForOffline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseTimezone converts last login date to the given timezone.
|
// UseTimezone converts last login date to the given timezone.
|
||||||
|
|
|
@ -241,7 +241,7 @@ func sanitizeAttributes(baseURL, tagName string, attributes []html.Attribute) ([
|
||||||
func getExtraAttributes(tagName string) ([]string, []string) {
|
func getExtraAttributes(tagName string) ([]string, []string) {
|
||||||
switch tagName {
|
switch tagName {
|
||||||
case "a":
|
case "a":
|
||||||
return []string{"rel", "target", "referrerpolicy"}, []string{`rel="noopener noreferrer"`, `target="_blank"`, `referrerpolicy="no-referrer"`}
|
return []string{"rel", "target", "referrerpolicy"}, []string{`rel="noopener noreferrer"`, `target="_blank"`, `referrerpolicy="strict-origin"`}
|
||||||
case "video", "audio":
|
case "video", "audio":
|
||||||
return []string{"controls"}, []string{"controls"}
|
return []string{"controls"}, []string{"controls"}
|
||||||
case "iframe":
|
case "iframe":
|
||||||
|
|
|
@ -96,7 +96,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
block_filter_entry_rules,
|
block_filter_entry_rules,
|
||||||
keep_filter_entry_rules
|
keep_filter_entry_rules,
|
||||||
|
cache_for_offline
|
||||||
`
|
`
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
|
@ -140,6 +141,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
&user.BlockFilterEntryRules,
|
&user.BlockFilterEntryRules,
|
||||||
&user.KeepFilterEntryRules,
|
&user.KeepFilterEntryRules,
|
||||||
|
&user.CacheForOffline,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
@ -204,9 +206,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
mark_read_on_media_player_completion=$25,
|
mark_read_on_media_player_completion=$25,
|
||||||
media_playback_rate=$26,
|
media_playback_rate=$26,
|
||||||
block_filter_entry_rules=$27,
|
block_filter_entry_rules=$27,
|
||||||
keep_filter_entry_rules=$28
|
keep_filter_entry_rules=$28,
|
||||||
|
cache_for_offline=$29
|
||||||
WHERE
|
WHERE
|
||||||
id=$29
|
id=$30
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = s.db.Exec(
|
_, err = s.db.Exec(
|
||||||
|
@ -239,6 +242,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.MediaPlaybackRate,
|
user.MediaPlaybackRate,
|
||||||
user.BlockFilterEntryRules,
|
user.BlockFilterEntryRules,
|
||||||
user.KeepFilterEntryRules,
|
user.KeepFilterEntryRules,
|
||||||
|
user.CacheForOffline,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -273,9 +277,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
mark_read_on_media_player_completion=$24,
|
mark_read_on_media_player_completion=$24,
|
||||||
media_playback_rate=$25,
|
media_playback_rate=$25,
|
||||||
block_filter_entry_rules=$26,
|
block_filter_entry_rules=$26,
|
||||||
keep_filter_entry_rules=$27
|
keep_filter_entry_rules=$27,
|
||||||
|
cache_for_offline=$28
|
||||||
WHERE
|
WHERE
|
||||||
id=$28
|
id=$29
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
|
@ -307,6 +312,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.MediaPlaybackRate,
|
user.MediaPlaybackRate,
|
||||||
user.BlockFilterEntryRules,
|
user.BlockFilterEntryRules,
|
||||||
user.KeepFilterEntryRules,
|
user.KeepFilterEntryRules,
|
||||||
|
user.CacheForOffline,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -360,7 +366,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
||||||
mark_read_on_media_player_completion,
|
mark_read_on_media_player_completion,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
block_filter_entry_rules,
|
block_filter_entry_rules,
|
||||||
keep_filter_entry_rules
|
keep_filter_entry_rules,
|
||||||
|
cache_for_offline
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -401,7 +408,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
||||||
mark_read_on_media_player_completion,
|
mark_read_on_media_player_completion,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
block_filter_entry_rules,
|
block_filter_entry_rules,
|
||||||
keep_filter_entry_rules
|
keep_filter_entry_rules,
|
||||||
|
cache_for_offline
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -442,7 +450,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
||||||
mark_read_on_media_player_completion,
|
mark_read_on_media_player_completion,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
block_filter_entry_rules,
|
block_filter_entry_rules,
|
||||||
keep_filter_entry_rules
|
keep_filter_entry_rules,
|
||||||
|
cache_for_offline
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -490,7 +499,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
||||||
u.mark_read_on_media_player_completion,
|
u.mark_read_on_media_player_completion,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
u.block_filter_entry_rules,
|
u.block_filter_entry_rules,
|
||||||
u.keep_filter_entry_rules
|
u.keep_filter_entry_rules,
|
||||||
|
u.cache_for_offline
|
||||||
FROM
|
FROM
|
||||||
users u
|
users u
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -533,6 +543,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
&user.BlockFilterEntryRules,
|
&user.BlockFilterEntryRules,
|
||||||
&user.KeepFilterEntryRules,
|
&user.KeepFilterEntryRules,
|
||||||
|
&user.CacheForOffline,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
@ -646,7 +657,9 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
mark_read_on_media_player_completion,
|
mark_read_on_media_player_completion,
|
||||||
media_playback_rate,
|
media_playback_rate,
|
||||||
block_filter_entry_rules,
|
block_filter_entry_rules,
|
||||||
keep_filter_entry_rules
|
keep_filter_entry_rules,
|
||||||
|
media_playback_rate,
|
||||||
|
cache_for_offline
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
ORDER BY username ASC
|
ORDER BY username ASC
|
||||||
|
@ -690,6 +703,7 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
&user.BlockFilterEntryRules,
|
&user.BlockFilterEntryRules,
|
||||||
&user.KeepFilterEntryRules,
|
&user.KeepFilterEntryRules,
|
||||||
|
&user.CacheForOffline,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -132,6 +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 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" .}}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<section class="entry" data-id="{{ .entry.ID }}" aria-labelledby="page-header-title">
|
<section class="entry" data-id="{{ .entry.ID }}" aria-labelledby="page-header-title">
|
||||||
<header class="entry-header">
|
<header class="entry-header">
|
||||||
<h1 id="page-header-title" dir="auto">
|
<h1 id="page-header-title" dir="auto">
|
||||||
<a href="{{ .entry.URL | safeURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">{{ .entry.Title }}</a>
|
<a href="{{ .entry.URL | safeURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="strict-origin">{{ .entry.Title }}</a>
|
||||||
</h1>
|
</h1>
|
||||||
{{ if .user }}
|
{{ if .user }}
|
||||||
<div class="entry-actions">
|
<div class="entry-actions">
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
class="page-link"
|
class="page-link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="strict-origin"
|
||||||
data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
|
data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
title="{{ t "entry.comments.title" }}"
|
title="{{ t "entry.comments.title" }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="strict-origin"
|
||||||
data-comments-link="true"
|
data-comments-link="true"
|
||||||
>{{ icon "comment" }}<span class="icon-label">{{ t "entry.comments.label" }}</span></a>
|
>{{ icon "comment" }}<span class="icon-label">{{ t "entry.comments.label" }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -232,7 +232,7 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<div class="entry-enclosure-download">
|
<div class="entry-enclosure-download">
|
||||||
<a href="{{ .URL | safeURL }}" title="{{ t "action.download" }}{{ if gt .Size 0 }} - {{ formatFileSize .Size }}{{ end }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">{{ .URL | safeURL }}</a>
|
<a href="{{ .URL | safeURL }}" title="{{ t "action.download" }}{{ if gt .Size 0 }} - {{ formatFileSize .Size }}{{ end }}" target="_blank" rel="noopener noreferrer" referrerpolicy="strict-origin">{{ .URL | safeURL }}</a>
|
||||||
<small>{{ if gt .Size 0 }} - <strong>{{ formatFileSize .Size }}</strong>{{ end }}</small>
|
<small>{{ if gt .Size 0 }} - <strong>{{ formatFileSize .Size }}</strong>{{ end }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (h *handler) showUnreadEntryPage(w http.ResponseWriter, r *http.Request) {
|
||||||
prevEntryRoute = route.Path(h.router, "unreadEntry", "entryID", prevEntry.ID)
|
prevEntryRoute = route.Path(h.router, "unreadEntry", "entryID", prevEntry.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.ShouldMarkAsReadOnView(user) {
|
if entry.ShouldMarkAsReadOnView(user) && !request.IsServiceWorker(r) {
|
||||||
entry.Status = model.EntryStatusRead
|
entry.Status = model.EntryStatusRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
|
|
@ -52,6 +52,7 @@ type SettingsForm struct {
|
||||||
MediaPlaybackRate float64
|
MediaPlaybackRate float64
|
||||||
BlockFilterEntryRules string
|
BlockFilterEntryRules string
|
||||||
KeepFilterEntryRules string
|
KeepFilterEntryRules string
|
||||||
|
CacheForOffline bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
|
// 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.MarkReadOnView = MarkReadOnView
|
||||||
user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
|
user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
|
||||||
|
|
||||||
|
user.CacheForOffline = s.CacheForOffline
|
||||||
|
|
||||||
if s.Password != "" {
|
if s.Password != "" {
|
||||||
user.Password = s.Password
|
user.Password = s.Password
|
||||||
}
|
}
|
||||||
|
@ -205,5 +208,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
||||||
MediaPlaybackRate: mediaPlaybackRate,
|
MediaPlaybackRate: mediaPlaybackRate,
|
||||||
BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
|
BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
|
||||||
KeepFilterEntryRules: r.FormValue("keep_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,
|
MediaPlaybackRate: user.MediaPlaybackRate,
|
||||||
BlockFilterEntryRules: user.BlockFilterEntryRules,
|
BlockFilterEntryRules: user.BlockFilterEntryRules,
|
||||||
KeepFilterEntryRules: user.KeepFilterEntryRules,
|
KeepFilterEntryRules: user.KeepFilterEntryRules,
|
||||||
|
CacheForOffline: user.CacheForOffline,
|
||||||
}
|
}
|
||||||
|
|
||||||
timezones, err := h.store.Timezones()
|
timezones, err := h.store.Timezones()
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,44 +1,126 @@
|
||||||
|
|
||||||
// Incrementing OFFLINE_VERSION will kick off the install event and force
|
// Incrementing OFFLINE_VERSION will kick off the install event and force
|
||||||
// previously cached resources to be updated from the network.
|
// previously cached resources to be updated from the network.
|
||||||
const OFFLINE_VERSION = 1;
|
const OFFLINE_VERSION = 2;
|
||||||
const CACHE_NAME = "offline";
|
const CACHE_NAME = "offline";
|
||||||
|
|
||||||
|
const cachedPages = [
|
||||||
|
"/unread",
|
||||||
|
"/starred",
|
||||||
|
"/stylesheets",
|
||||||
|
"/app",
|
||||||
|
"/service-worker",
|
||||||
|
"/manifest.json",
|
||||||
|
"/feed/icon",
|
||||||
|
"/icon",
|
||||||
|
];
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
(async () => {
|
(async () => {
|
||||||
const cache = await caches.open(CACHE_NAME);
|
const cache = await caches.open(CACHE_NAME);
|
||||||
|
|
||||||
// Setting {cache: 'reload'} in the new request will ensure that the
|
if (USE_CACHE) {
|
||||||
// response isn't fulfilled from the HTTP cache; i.e., it will be from
|
await cache.addAll(["/", "/unread", "/starred", OFFLINE_URL]);
|
||||||
// the network.
|
} else {
|
||||||
await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
|
// 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.
|
// Force the waiting service worker to become the active service worker.
|
||||||
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (await cache.match(request)) || (await fetchResponsePromise);
|
||||||
|
} catch (error) {
|
||||||
|
const cache = await caches.open(CACHE_NAME);
|
||||||
|
return await cache.match(OFFLINE_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.addEventListener("fetch", (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
// We proxify requests through fetch() only if we are offline because it's slower.
|
if (USE_CACHE) {
|
||||||
if (navigator.onLine === false && event.request.mode === "navigate") {
|
const url = new URL(event.request.url);
|
||||||
event.respondWith(
|
if (cachedPages.some((page) => url.pathname.startsWith(page))) {
|
||||||
(async () => {
|
return event.respondWith(cacheFirstWithRefresh(event.request));
|
||||||
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 (navigator.onLine === false && event.request.mode === "navigate") {
|
||||||
|
event.respondWith(
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
// Always try the network first.
|
||||||
|
return await fetch(event.request);
|
||||||
|
} 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);
|
||||||
|
return await cache.match(OFFLINE_URL);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("load", async (event) => {
|
||||||
|
if (
|
||||||
|
navigator.onLine === true &&
|
||||||
|
event.target.location.pathname === "/unread" &&
|
||||||
|
USE_CACHE
|
||||||
|
) {
|
||||||
|
const cache = await caches.open(CACHE_NAME);
|
||||||
|
|
||||||
|
for (let article of document.getElementsByTagName("article")) {
|
||||||
|
const as = article.getElementsByTagName("a");
|
||||||
|
if (as.length > 0) {
|
||||||
|
const a = as[0];
|
||||||
|
const href = a.href;
|
||||||
|
cache
|
||||||
|
.add(
|
||||||
|
new Request(href, {
|
||||||
|
headers: new Headers({
|
||||||
|
"Client-Type": "service-worker",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
article;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,7 +31,19 @@ func (h *handler) showJavascript(w http.ResponseWriter, r *http.Request) {
|
||||||
contents := static.JavascriptBundles[filename]
|
contents := static.JavascriptBundles[filename]
|
||||||
|
|
||||||
if filename == "service-worker" {
|
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...)
|
contents = append([]byte(variables), contents...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue