SSR: Repost ohne JS, return_to in Formularen, Werbebanner entfernt

Letzter vollständiger SSR-Stand vor dem Rückbau auf reine SPA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-09-26 21:23:09 +02:00
co-authored by Claude Opus 5.5
parent c66df283d1
commit eccddcebe6
9 changed files with 194 additions and 45 deletions
+44 -4
View File
@@ -80,6 +80,31 @@ type entryView struct {
DeleteURL string
}
// bumpRemaining ist die Restsperre des Reposts in Sekunden (0 = wieder
// möglich). Spiegelt die Bedingung im UPDATE von handleBump; die Vorlage
// braucht den Wert, um den Repost-Knopf zu sperren und die Wartezeit
// anzuzeigen -- das Gegenstück zu setupBump in web/js/feed.js.
func (v entryView) bumpRemaining() int64 {
rem := v.LastBump + v.BumpCount*bumpStep - time.Now().Unix()
if rem < 0 {
return 0
}
return rem
}
// BumpLocked meldet, ob der Repost dieses Beitrags gerade im Cooldown steckt.
func (v entryView) BumpLocked() bool { return v.bumpRemaining() > 0 }
// BumpWaitHours ist die Restsperre auf volle Stunden aufgerundet (mindestens
// 1, solange überhaupt Zeit übrig ist) -- wie die Anzeige im JS-Frontend.
func (v entryView) BumpWaitHours() int64 {
h := (v.bumpRemaining() + 3599) / 3600
if h < 1 {
return 1
}
return h
}
// composeForm hält die Eingaben des Beitragsformulars über einen
// fehlgeschlagenen Versuch hinweg. Die angehängte Datei fehlt dabei
// zwangsläufig: ein <input type="file"> lässt sich aus Sicherheitsgründen
@@ -97,6 +122,13 @@ type feedView struct {
PrevURL string
NextURL string
// ReturnTo ist der Rücksprungort dieser Seite (beim GET die Seite selbst).
// Das Beitragsformular trägt ihn als verstecktes Feld, damit er einen
// abgelehnten Post übersteht -- sonst fiele das neu gerenderte Formular
// samt seiner Interaktions-Knöpfe auf den Default "/" zurück (die noch per
// JS bediente Startseite) statt auf "/feed".
ReturnTo string
// CanPost steuert, ob das Beitragsformular erscheint. Ohne Anmeldung gibt
// es nichts zu schreiben; über TOR wird das Schreiben abgelehnt (blockTOR),
// und ein Formular, das beim Absenden immer scheitert, wäre schlechter als
@@ -178,11 +210,13 @@ func buildFeedView(r *http.Request, uid int64, rootsOnly bool) (feedView, error)
return feedView{}, Internal(err)
}
back := returnTo(r)
v := feedView{
Entries: toEntryViews(entries, tallies, returnTo(r), viewer),
Page: page,
Pages: pages,
IsTOR: isTOR(r),
Entries: toEntryViews(entries, tallies, back, viewer),
Page: page,
Pages: pages,
IsTOR: isTOR(r),
ReturnTo: back,
}
v.CanPost = viewer != 0 && !v.IsTOR
if page > 0 {
@@ -203,6 +237,11 @@ type threadView struct {
CanPost bool
IsTOR bool
Compose composeForm
// ReturnTo wie bei feedView: das Antwortformular trägt ihn versteckt mit,
// damit eine abgelehnte Antwort die Interaktions-Knöpfe nicht auf "/"
// zurückwirft statt auf den Thread.
ReturnTo string
}
// toEntryViews reichert Beiträge mit Abstimmungsstand, Rücksprungziel und
@@ -272,6 +311,7 @@ func buildThreadView(r *http.Request, pid int64) (threadView, error) {
Entry: toEntryViews([]Entry{t.Entry}, tallies, back, viewer)[0],
Replies: toEntryViews(t.Replies, tallies, back, viewer),
IsTOR: isTOR(r),
ReturnTo: back,
}
// Auf einen gelöschten Beitrag lässt sich nicht antworten -- er hat keinen
// Autor mehr und der Platzhalter soll den Thread nur zusammenhalten.