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 {
|
||||
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) {
|
||||
app.Profile.SaveTweetTrove(trove, false)
|
||||
go app.Profile.SaveTweetTrove(trove, true) // Download the content in the background
|
||||
_, 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 {
|
||||
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")
|
||||
|
||||
tweet, err := app.ensure_tweet(tweet_id, is_scrape_required, is_conversation_required)
|
||||
var toasts []Toast
|
||||
if err != nil {
|
||||
app.ErrorLog.Print(fmt.Errorf("TweetDetail (%d): %w", tweet_id, err))
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
app.error_404(w)
|
||||
// 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))
|
||||
|
||||
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(
|
||||
w,
|
||||
"tpl/tweet_detail.tpl",
|
||||
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID},
|
||||
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID, Toasts: toasts},
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ type PageGlobalData struct {
|
||||
SearchText string
|
||||
FocusedTweetID scraper.TweetID
|
||||
Notifications
|
||||
Toasts []Toast
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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-Retarget", "#toasts")
|
||||
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)
|
||||
}
|
||||
|
||||
// `Type` can be:
|
||||
// - "success" (default)
|
||||
// - "warning"
|
||||
// - "error"
|
||||
//
|
||||
// If "AutoCloseDelay" is not 0, the toast will auto-disappear after that many milliseconds.
|
||||
type Toast struct {
|
||||
Title string
|
||||
Message string
|
||||
|
@ -50,6 +50,9 @@
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="toasts" id="toasts">
|
||||
{{range (global_data).Toasts}}
|
||||
{{template "toast" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,6 +5,9 @@
|
||||
hx-on::load="setTimeout(() => this.remove(), {{.AutoCloseDelay}} + 2000); setTimeout(() => this.classList.add('disappearing'), {{.AutoCloseDelay}})"
|
||||
{{end}}
|
||||
>
|
||||
{{if .Title}}
|
||||
<h2 class="toast__title">{{.Title}}</h2>
|
||||
{{end}}
|
||||
<span class="toast__message">{{.Message}}</span>
|
||||
{{if not .AutoCloseDelay}}
|
||||
<button class="suicide" onclick="this.parentElement.remove()">X</button>
|
||||
|
Loading…
x
Reference in New Issue
Block a user