diff --git a/internal/web/static/index.html b/internal/web/static/index.html index 58c902f..ce79cf8 100644 --- a/internal/web/static/index.html +++ b/internal/web/static/index.html @@ -4853,10 +4853,16 @@ renderLatestVersion(); if (data.hasUpdate && data.downloadURL) { if (!manual) { - // Skip silently only if the user explicitly hit "Don't - // show again" for this version (or downloaded it). - var skipKey = 'thefeed_skip_gh_update_' + normalizeVersion(data.latest); - if (localStorage.getItem(skipKey) === '1') return; + // Server-stored skip survives per-port localStorage wipes. + var skipped = ''; + try { + var sx = new XMLHttpRequest(); + sx.open('GET', '/api/settings', false); + sx.send(); + if (sx.status === 200) skipped = JSON.parse(sx.responseText).skipUpdateVersion || ''; + } catch (e) { } + if (!skipped) skipped = localStorage.getItem('thefeed_skip_gh_update_' + normalizeVersion(data.latest)) === '1' ? data.latest : ''; + if (normalizeVersion(skipped) === normalizeVersion(data.latest)) return; } showUpdateDialog(data.latest, data.downloadURL); } else if (manual) { @@ -4887,11 +4893,16 @@ + ''; document.body.appendChild(overlay); var dismiss = function () { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); }; - document.getElementById('updateLater').onclick = dismiss; - document.getElementById('updateSkip').onclick = function () { + var persistSkip = function () { try { localStorage.setItem(skipKey, '1'); } catch (e) { } - dismiss(); + fetch('/api/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ skipUpdateVersion: newVersion }) + }).catch(function () { }); }; + document.getElementById('updateLater').onclick = dismiss; + document.getElementById('updateSkip').onclick = function () { persistSkip(); dismiss(); }; document.getElementById('updateDownload').onclick = function () { try { var a = document.createElement('a'); @@ -4899,7 +4910,7 @@ a.style.display = 'none'; document.body.appendChild(a); a.click(); a.remove(); } catch (e) { } - try { localStorage.setItem(skipKey, '1'); } catch (e) { } + persistSkip(); dismiss(); }; } diff --git a/internal/web/web.go b/internal/web/web.go index 1abdf38..4fe859a 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -99,6 +99,8 @@ type ProfileList struct { Debug bool `json:"debug,omitempty"` Theme string `json:"theme,omitempty"` Lang string `json:"lang,omitempty"` + // SkipUpdateVersion is the latest release the user dismissed. + SkipUpdateVersion string `json:"skipUpdateVersion,omitempty"` // ResolverBank is the shared pool of DNS resolvers used by all profiles. ResolverBank []string `json:"resolverBank,omitempty"` // ResolverScores stores accumulated performance data for bank resolvers. @@ -2832,6 +2834,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { "lang": pl.Lang, "scanPromptOff": pl.ScanPromptOff, "profilePicsEnabled": pl.ProfilePicsEnabled, + "skipUpdateVersion": pl.SkipUpdateVersion, "version": version.Version, "commit": version.Commit, }) @@ -2845,6 +2848,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { Lang *string `json:"lang"` ScanPromptOff *bool `json:"scanPromptOff"` ProfilePicsEnabled *bool `json:"profilePicsEnabled"` + SkipUpdateVersion *string `json:"skipUpdateVersion"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON", 400) @@ -2885,6 +2889,9 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { if req.ProfilePicsEnabled != nil { pl.ProfilePicsEnabled = *req.ProfilePicsEnabled } + if req.SkipUpdateVersion != nil { + pl.SkipUpdateVersion = *req.SkipUpdateVersion + } if err := s.saveProfiles(pl); err != nil { http.Error(w, fmt.Sprintf("save: %v", err), 500) return