Enable adding toasts in full page reloads (and HTMX where it's hx-boost or target = body)
- use toasts to display non-fatal scraping errors for Tweets
This commit is contained in:
parent
91f722b7fa
commit
ee2b287fd9
@ -50,13 +50,16 @@ func (app *Application) ensure_tweet(id scraper.TweetID, is_forced bool, is_conv
|
|||||||
|
|
||||||
if is_needing_scrape && !app.IsScrapingDisabled {
|
if is_needing_scrape && !app.IsScrapingDisabled {
|
||||||
trove, err := scraper.GetTweetFullAPIV2(id, 50) // TODO: parameterizable
|
trove, err := scraper.GetTweetFullAPIV2(id, 50) // TODO: parameterizable
|
||||||
|
|
||||||
|
// Save the trove unless there was an unrecoverable error
|
||||||
if err == nil || errors.Is(err, scraper.END_OF_FEED) || errors.Is(err, scraper.ErrRateLimited) {
|
if err == nil || errors.Is(err, scraper.END_OF_FEED) || errors.Is(err, scraper.ErrRateLimited) {
|
||||||
app.Profile.SaveTweetTrove(trove, false)
|
app.Profile.SaveTweetTrove(trove, false)
|
||||||
go app.Profile.SaveTweetTrove(trove, true) // Download the content in the background
|
go app.Profile.SaveTweetTrove(trove, true) // Download the content in the background
|
||||||
_, is_available = trove.Tweets[id]
|
_, is_available = trove.Tweets[id]
|
||||||
} else {
|
}
|
||||||
app.ErrorLog.Print(err)
|
|
||||||
// TODO: show error in UI
|
if err != nil && !errors.Is(err, scraper.END_OF_FEED) {
|
||||||
|
return scraper.Tweet{}, fmt.Errorf("scraper error: %w", err)
|
||||||
}
|
}
|
||||||
} else if is_needing_scrape {
|
} else if is_needing_scrape {
|
||||||
app.InfoLog.Printf("Would have scraped Tweet: %d", id)
|
app.InfoLog.Printf("Would have scraped Tweet: %d", id)
|
||||||
@ -115,10 +118,31 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
|
|||||||
is_conversation_required := len(parts) <= 2 || (parts[2] != "like" && parts[2] != "unlike")
|
is_conversation_required := len(parts) <= 2 || (parts[2] != "like" && parts[2] != "unlike")
|
||||||
|
|
||||||
tweet, err := app.ensure_tweet(tweet_id, is_scrape_required, is_conversation_required)
|
tweet, err := app.ensure_tweet(tweet_id, is_scrape_required, is_conversation_required)
|
||||||
if errors.Is(err, ErrNotFound) {
|
var toasts []Toast
|
||||||
app.error_404(w)
|
if err != nil {
|
||||||
return
|
app.ErrorLog.Print(fmt.Errorf("TweetDetail (%d): %w", tweet_id, err))
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
// Can't find the tweet; abort
|
||||||
|
app.toast(w, r, Toast{Title: "Not found", Message: "Tweet not found in database", Type: "error"})
|
||||||
|
return
|
||||||
|
} else if errors.Is(err, scraper.ErrSessionInvalidated) {
|
||||||
|
toasts = append(toasts, Toast{
|
||||||
|
Title: "Session invalidated",
|
||||||
|
Message: "Your session has been invalidated by Twitter. You'll have to log in again.",
|
||||||
|
Type: "error",
|
||||||
|
})
|
||||||
|
// TODO: delete the invalidated session
|
||||||
|
} else if errors.Is(err, scraper.ErrRateLimited) {
|
||||||
|
toasts = append(toasts, Toast{
|
||||||
|
Title: "Rate limited",
|
||||||
|
Message: "While scraping, a rate-limit was hit. Results may be incomplete.",
|
||||||
|
Type: "warning",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
panic(err) // Let the 500 handler deal with it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req_with_tweet := r.WithContext(add_tweet_to_context(r.Context(), tweet))
|
req_with_tweet := r.WithContext(add_tweet_to_context(r.Context(), tweet))
|
||||||
|
|
||||||
if len(parts) > 2 && parts[2] == "like" {
|
if len(parts) > 2 && parts[2] == "like" {
|
||||||
@ -137,7 +161,7 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.buffered_render_page(
|
app.buffered_render_page(
|
||||||
w,
|
w,
|
||||||
"tpl/tweet_detail.tpl",
|
"tpl/tweet_detail.tpl",
|
||||||
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID},
|
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID, Toasts: toasts},
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ type PageGlobalData struct {
|
|||||||
SearchText string
|
SearchText string
|
||||||
FocusedTweetID scraper.TweetID
|
FocusedTweetID scraper.TweetID
|
||||||
Notifications
|
Notifications
|
||||||
|
Toasts []Toast
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d PageGlobalData) Tweet(id scraper.TweetID) scraper.Tweet {
|
func (d PageGlobalData) Tweet(id scraper.TweetID) scraper.Tweet {
|
||||||
|
@ -38,7 +38,7 @@ func (app *Application) error_500(w http.ResponseWriter, r *http.Request, err er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) toast(w http.ResponseWriter, r *http.Request, t Toast) {
|
func (app *Application) toast(w http.ResponseWriter, r *http.Request, t Toast) {
|
||||||
// Reset the HTMX response to return an error toast and put it in the
|
// Reset the HTMX response to return an error toast and append it to the Toasts container
|
||||||
w.Header().Set("HX-Reswap", "beforeend")
|
w.Header().Set("HX-Reswap", "beforeend")
|
||||||
w.Header().Set("HX-Retarget", "#toasts")
|
w.Header().Set("HX-Retarget", "#toasts")
|
||||||
w.Header().Set("HX-Push-Url", "false")
|
w.Header().Set("HX-Push-Url", "false")
|
||||||
@ -46,6 +46,12 @@ func (app *Application) toast(w http.ResponseWriter, r *http.Request, t Toast) {
|
|||||||
app.buffered_render_htmx(w, "toast", PageGlobalData{}, t)
|
app.buffered_render_htmx(w, "toast", PageGlobalData{}, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `Type` can be:
|
||||||
|
// - "success" (default)
|
||||||
|
// - "warning"
|
||||||
|
// - "error"
|
||||||
|
//
|
||||||
|
// If "AutoCloseDelay" is not 0, the toast will auto-disappear after that many milliseconds.
|
||||||
type Toast struct {
|
type Toast struct {
|
||||||
Title string
|
Title string
|
||||||
Message string
|
Message string
|
||||||
|
@ -50,6 +50,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
<div class="toasts" id="toasts">
|
<div class="toasts" id="toasts">
|
||||||
|
{{range (global_data).Toasts}}
|
||||||
|
{{template "toast" .}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
hx-on::load="setTimeout(() => this.remove(), {{.AutoCloseDelay}} + 2000); setTimeout(() => this.classList.add('disappearing'), {{.AutoCloseDelay}})"
|
hx-on::load="setTimeout(() => this.remove(), {{.AutoCloseDelay}} + 2000); setTimeout(() => this.classList.add('disappearing'), {{.AutoCloseDelay}})"
|
||||||
{{end}}
|
{{end}}
|
||||||
>
|
>
|
||||||
|
{{if .Title}}
|
||||||
|
<h2 class="toast__title">{{.Title}}</h2>
|
||||||
|
{{end}}
|
||||||
<span class="toast__message">{{.Message}}</span>
|
<span class="toast__message">{{.Message}}</span>
|
||||||
{{if not .AutoCloseDelay}}
|
{{if not .AutoCloseDelay}}
|
||||||
<button class="suicide" onclick="this.parentElement.remove()">X</button>
|
<button class="suicide" onclick="this.parentElement.remove()">X</button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user