5 Commits

Author SHA1 Message Date
nkanaev
4b3a278679 Update changelog.md 2025-09-23 21:59:32 +01:00
nkanaev
aa06e65c59 redesign auto-refresh UI 2025-09-23 21:59:32 +01:00
nkanaev
dd57abefdd Update .gitignore 2025-09-23 21:59:32 +01:00
nkanaev
be8ba62bb1 make manifest.json public 2025-09-23 21:59:32 +01:00
nkanaev
b7895f6743 auth cookie directives 2025-09-23 21:59:32 +01:00
8 changed files with 48 additions and 13 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@
*.db-wal *.db-wal
*.syso *.syso
versioninfo.rc versioninfo.rc
.DS_Store

View File

@@ -1,7 +1,9 @@
# upcoming # upcoming
- (new) serve on unix socket (thanks to @rvighne) - (new) serve on unix socket (thanks to @rvighne)
- (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion)
- (fix) smooth scrolling on iOS (thanks to gatheraled) - (fix) smooth scrolling on iOS (thanks to gatheraled)
- (etc) cookie security measures (thanks to Tom Fitzhenry)
# v2.5 (2025-03-26) # v2.5 (2025-03-26)

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>

After

Width:  |  Height:  |  Size: 268 B

View File

@@ -78,12 +78,20 @@
<header class="dropdown-header" role="heading" aria-level="2">Auto Refresh</header> <header class="dropdown-header" role="heading" aria-level="2">Auto Refresh</header>
<div class="row text-center m-0"> <div class="row text-center m-0">
<button class="dropdown-item col-4 px-0" :aria-pressed="!refreshRate" :class="{active: !refreshRate}" @click.stop="refreshRate = 0">0</button> <button class="dropdown-item col-4 px-0"
<button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 10" :class="{active: refreshRate == 10}" @click.stop="refreshRate = 10">10m</button> @click.stop="changeRefreshRate(-1)"
<button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 30" :class="{active: refreshRate == 30}" @click.stop="refreshRate = 30">30m</button> :disabled="!refreshRate">
<button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 60" :class="{active: refreshRate == 60}" @click.stop="refreshRate = 60">1h</button> <span class="icon">
<button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 120" :class="{active: refreshRate == 120}" @click.stop="refreshRate = 120">2h</button> {% inline "chevron-down.svg" %}
<button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 240" :class="{active: refreshRate == 240}" @click.stop="refreshRate = 240">4h</button> </span>
</button>
<div class="col-4 d-flex align-items-center justify-content-center">{{ refreshRateTitle }}</div>
<button class="dropdown-item col-4 px-0"
@click.stop="changeRefreshRate(1)" :disabled="refreshRate === refreshRateOptions.at(-1).value">
<span class="icon">
{% inline "chevron-up.svg" %}
</span>
</button>
</div> </div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>

View File

@@ -252,6 +252,17 @@ var vm = new Vue({
'refreshRate': s.refresh_rate, 'refreshRate': s.refresh_rate,
'authenticated': app.authenticated, 'authenticated': app.authenticated,
'feed_errors': {}, 'feed_errors': {},
'refreshRateOptions': [
{ title: "0", value: 0 },
{ title: "10m", value: 10 },
{ title: "30m", value: 30 },
{ title: "1h", value: 60 },
{ title: "2h", value: 120 },
{ title: "4h", value: 240 },
{ title: "12h", value: 720 },
{ title: "24h", value: 1440 },
],
} }
}, },
computed: { computed: {
@@ -309,7 +320,11 @@ var vm = new Vue({
contentVideos: function() { contentVideos: function() {
if (!this.itemSelectedDetails) return [] if (!this.itemSelectedDetails) return []
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'video') return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'video')
} },
refreshRateTitle: function () {
const entry = this.refreshRateOptions.find(o => o.value === this.refreshRate)
return entry ? entry.title : '0'
},
}, },
watch: { watch: {
'theme': { 'theme': {
@@ -778,6 +793,12 @@ var vm = new Vue({
if (target && scroll) scrollto(target, scroll) if (target && scroll) scrollto(target, scroll)
}) })
}, },
changeRefreshRate: function(offset) {
const curIdx = this.refreshRateOptions.findIndex(o => o.value === this.refreshRate)
if (curIdx <= 0 && offset < 0) return
if (curIdx >= (this.refreshRateOptions.length - 1) && offset > 0) return
this.refreshRate = this.refreshRateOptions[curIdx + offset].value
},
} }
}) })

View File

@@ -7,7 +7,6 @@ import (
"encoding/hex" "encoding/hex"
"net/http" "net/http"
"strings" "strings"
"time"
) )
func IsAuthenticated(req *http.Request, username, password string) bool { func IsAuthenticated(req *http.Request, username, password string) bool {
@@ -24,10 +23,12 @@ func IsAuthenticated(req *http.Request, username, password string) bool {
func Authenticate(rw http.ResponseWriter, username, password, basepath string) { func Authenticate(rw http.ResponseWriter, username, password, basepath string) {
http.SetCookie(rw, &http.Cookie{ http.SetCookie(rw, &http.Cookie{
Name: "auth", Name: "auth",
Value: username + ":" + secret(username, password), Value: username + ":" + secret(username, password),
Expires: time.Now().Add(time.Hour * 24 * 7), // 1 week, MaxAge: 604800, // 1 week
Path: basepath, Path: basepath,
Secure: true,
SameSite: http.SameSiteLaxMode,
}) })
} }

View File

@@ -34,7 +34,7 @@ func (s *Server) handler() http.Handler {
BasePath: s.BasePath, BasePath: s.BasePath,
Username: s.Username, Username: s.Username,
Password: s.Password, Password: s.Password,
Public: []string{"/static", "/fever"}, Public: []string{"/static", "/fever", "/manifest.json"},
DB: s.db, DB: s.db,
} }
r.Use(a.Handler) r.Use(a.Handler)