feat(web): immediate summarize + status poll + ASCII tapir spinner

Clicking "Summarize" now runs the summary in the background (when a Processor is
wired) instead of only queuing it. The handler flips the DB flag, kicks off the
work on a detached context, and returns an animated "processing" card that polls
GET /v/{id}/status every 2s via HTMX. Status returns the summary card once it
lands (no poll → polling stops), the animation while in-flight, or the Queued
card otherwise. Queue-only behaviour is unchanged when no Processor is set.

The spinner is a CSS-only cross-fade of three ASCII tapir frames — no JS, honours
prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 19:48:52 +02:00
co-authored by Claude Opus 4.8
parent 404f74c55c
commit 25215cbcbd
5 changed files with 622 additions and 195 deletions
+58 -4
View File
@@ -91,6 +91,7 @@ func (a *App) Router() http.Handler {
app.HandleFunc("GET /v/{videoId}", a.handleDetail) app.HandleFunc("GET /v/{videoId}", a.handleDetail)
app.HandleFunc("POST /v/{videoId}/action", a.handleAction) app.HandleFunc("POST /v/{videoId}/action", a.handleAction)
app.HandleFunc("POST /v/{videoId}/summarize", a.handleRequestSummarize) app.HandleFunc("POST /v/{videoId}/summarize", a.handleRequestSummarize)
app.HandleFunc("GET /v/{videoId}/status", a.handleStatus)
app.HandleFunc("GET /register", a.handleRegisterForm) app.HandleFunc("GET /register", a.handleRegisterForm)
app.HandleFunc("POST /register", a.handleRegister) app.HandleFunc("POST /register", a.handleRegister)
@@ -215,10 +216,12 @@ func (a *App) handleAction(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/v/"+videoID, http.StatusSeeOther) http.Redirect(w, r, "/v/"+videoID, http.StatusSeeOther)
} }
// handleRequestSummarize queues a video for manual summarization. It does NOT run // handleRequestSummarize handles the "Summarize" button. It always flips the DB
// the engine inline — it only flips summarize_requested; the next `tapir run` // flag (summarize_requested) so the work is durable. With a Processor wired it
// picks it up (the single summarization driver). For HTMX it returns the refreshed // then summarizes immediately in the background and answers with the animated
// card (now showing "Queued"); without JS it redirects back to the list. // processing card that polls /status until done; without one (queue-only) it
// answers with the "Queued" card — the next `tapir run` does the work. Without
// JS it redirects back to the list (POST→redirect→GET).
func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) { func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) {
userID, ok := a.currentUserID(w, r) userID, ok := a.currentUserID(w, r)
if !ok { if !ok {
@@ -245,9 +248,60 @@ func (a *App) handleRequestSummarize(w http.ResponseWriter, r *http.Request) {
a.serverError(w, r, "get video", err) a.serverError(w, r, "get video", err)
return return
} }
if a.Processor != nil {
a.startProcessing(userID, videoID)
a.render(w, r, processingCard(*row))
return
}
a.render(w, r, VideoCard(*row)) a.render(w, r, VideoCard(*row))
} }
// startProcessing marks a video in-flight and summarizes it in the background.
// The goroutine uses a detached context — not the request's, which is cancelled
// when the handler returns — and clears the in-flight mark on completion. On
// error the DB flag stays set, so the video remains queued for the next
// `tapir run`; a successful Processor.ProcessVideo clears it itself.
func (a *App) startProcessing(userID, videoID string) {
key := processingKey(userID, videoID)
a.Processing.Add(key)
go func() {
defer a.Processing.Remove(key)
if err := a.Processor.ProcessVideo(context.Background(), userID, videoID); err != nil {
a.logger().Error("background summarize", "user", userID, "video", videoID, "err", err)
}
}()
}
// handleStatus is the HTMX poll target for an in-flight summarization. It returns
// the card in its current state: the full summary card once the summary exists,
// otherwise the animated processing card while still in-flight (which keeps
// polling), or the queued/button card when neither holds. VideoCard carries no
// polling attributes, so HTMX stops polling once it swaps in.
func (a *App) handleStatus(w http.ResponseWriter, r *http.Request) {
userID, ok := a.currentUserID(w, r)
if !ok {
return
}
videoID := r.PathValue("videoId")
row, err := a.Store.GetVideoRow(r.Context(), userID, videoID)
if errors.Is(err, store.ErrNotFound) {
http.NotFound(w, r)
return
}
if err != nil {
a.serverError(w, r, "get video", err)
return
}
if row.Summarized || !a.Processing.Has(processingKey(userID, videoID)) {
a.render(w, r, VideoCard(*row))
return
}
a.render(w, r, processingCard(*row))
}
// handleSummarizeMode toggles the user's auto/manual summarization mode. The form // handleSummarizeMode toggles the user's auto/manual summarization mode. The form
// submits the desired new value (enabled=true|false). For HTMX it returns the // submits the desired new value (enabled=true|false). For HTMX it returns the
// refreshed mode control; without JS it redirects back to the account page. // refreshed mode control; without JS it redirects back to the account page.
+129
View File
@@ -0,0 +1,129 @@
package web_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/require"
"gitea.d-ma.be/mathias/tapir/internal/web"
)
// fakeProcessor records ProcessVideo calls. With block set it parks until the
// channel is closed, so a test can observe the handler return before the
// background work finishes (proving it ran in a goroutine).
type fakeProcessor struct {
block chan struct{}
done chan struct{}
calls []string
}
func (f *fakeProcessor) ProcessVideo(_ context.Context, _, videoID string) error {
if f.block != nil {
<-f.block
}
f.calls = append(f.calls, videoID)
if f.done != nil {
close(f.done)
}
return nil
}
func TestRequestSummarizeImmediateProcessing(t *testing.T) {
app := newApp(t)
p := rawPool(t)
resetDB(t, p)
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
fp := &fakeProcessor{block: make(chan struct{}), done: make(chan struct{})}
app.Processor = fp
rec := postSummarize(t, app, videoX, true)
require.Equal(t, http.StatusOK, rec.Code)
html := body(t, rec)
// The processing card came back while ProcessVideo is still parked on block:
// the work runs in a goroutine, the handler did not wait for it.
require.Contains(t, html, "Summarizing", "processing card returned")
require.Contains(t, html, "ω", "ascii tapir frame rendered")
require.Contains(t, html, "∪∪", "all tapir frames rendered")
require.Contains(t, html, "/v/"+videoX+"/status", "card polls the status endpoint")
require.Contains(t, html, `hx-trigger="every 2s"`, "card auto-polls every 2s")
require.NotContains(t, html, "Queued", "not the queue-only card")
close(fp.block)
select {
case <-fp.done:
case <-time.After(2 * time.Second):
t.Fatal("ProcessVideo was not called in the background")
}
require.Equal(t, []string{videoX}, fp.calls)
}
func TestStatusProcessingThenDone(t *testing.T) {
ctx := context.Background()
app := newApp(t)
p := rawPool(t)
resetDB(t, p)
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
// Park ProcessVideo so the video stays in-flight while we poll status.
fp := &fakeProcessor{block: make(chan struct{})}
app.Processor = fp
require.Equal(t, http.StatusOK, postSummarize(t, app, videoX, true).Code)
// Processing: status returns the animation card, still polling.
rec := getStatus(t, app, videoX)
require.Equal(t, http.StatusOK, rec.Code)
html := body(t, rec)
require.Contains(t, html, "Summarizing", "in-flight → animation card")
require.Contains(t, html, `hx-trigger="every 2s"`, "still polling")
close(fp.block)
// Done: once a summary exists, status returns the summary card with no poll.
require.NoError(t, deliver(ctx, app, videoX, "the summary body"))
rec = getStatus(t, app, videoX)
require.Equal(t, http.StatusOK, rec.Code)
html = body(t, rec)
require.NotContains(t, html, "Summarizing", "done → no animation")
require.NotContains(t, html, "every 2s", "done card does not poll (polling stops)")
require.Contains(t, html, "/v/"+videoX+"\"", "links to the detail page")
}
func TestStatusQueuedWhenNotInFlight(t *testing.T) {
ctx := context.Background()
app := newApp(t)
p := rawPool(t)
resetDB(t, p)
seedVideo(t, p, videoX, "Pending Title", "https://x", time.Time{})
// Flag set but nothing in-flight (e.g. queue-only, or after a restart).
require.NoError(t, app.Store.RequestSummarize(ctx, userID, videoX))
rec := getStatus(t, app, videoX)
require.Equal(t, http.StatusOK, rec.Code)
html := body(t, rec)
require.Contains(t, html, "Queued", "queued chip card")
require.NotContains(t, html, "Summarizing", "not processing")
require.NotContains(t, html, "every 2s", "queued card does not poll")
}
func TestStatusNotFound(t *testing.T) {
app := newApp(t)
resetDB(t, rawPool(t))
rec := getStatus(t, app, videoX)
require.Equal(t, http.StatusNotFound, rec.Code)
}
func getStatus(t *testing.T, app *web.App, videoID string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodGet, "/v/"+videoID+"/status", nil)
req.Header.Set("HX-Request", "true")
rec := httptest.NewRecorder()
app.Router().ServeHTTP(rec, req)
return rec
}
+40
View File
@@ -176,6 +176,30 @@ func summarizeURL(videoID string) templ.SafeURL {
return templ.SafeURL("/v/" + videoID + "/summarize") return templ.SafeURL("/v/" + videoID + "/summarize")
} }
// statusURL builds the processing-status poll path (GET) for a video id — the
// HTMX poll target while an immediate summarization is in flight.
func statusURL(videoID string) templ.SafeURL {
return templ.SafeURL("/v/" + videoID + "/status")
}
// tapirFrame1..3 are the ASCII tapir frames cycled by the TapirSpinner CSS
// animation. Three near-identical frames (snout and feet shift) read as a stocky,
// big-snouted tapir ambling in place while a summary is generated.
const (
tapirFrame1 = ` /\_____/\
( · ω · )
) ∩∩ (
(__( )__)`
tapirFrame2 = ` /\_____/\
( · ω · )
) ∩∩ (
(__( ^ )__)`
tapirFrame3 = ` /\_____/\
( · ω · )
) ∪∪ (
(__( )__)`
)
// summarizeModeLabel names the current mode for display. // summarizeModeLabel names the current mode for display.
func summarizeModeLabel(auto bool) string { func summarizeModeLabel(auto bool) string {
if auto { if auto {
@@ -381,6 +405,22 @@ main { max-width: 60rem; margin: 0 auto; padding: var(--s4) var(--s3); }
.card-pending { border-style: dashed; } .card-pending { border-style: dashed; }
.card-pending .card-title { color: var(--muted); font-weight: 600; } .card-pending .card-title { color: var(--muted); font-weight: 600; }
/* summarizing animation — a little ASCII tapir ambling in place. Three frames
are stacked absolutely and cross-faded by a stepped keyframe with staggered
delays, so exactly one shows at a time (no JS). */
.card-processing { border-style: dashed; }
.tapir-spin { position: relative; height: 5.2em; margin: var(--s2) 0; }
.tapir-spin pre { position: absolute; inset: 0; margin: 0; opacity: 0; font: .9rem/1.15 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent); animation: tapir-cycle 1.2s steps(1, end) infinite; }
.tapir-spin pre:nth-child(1) { animation-delay: 0s; }
.tapir-spin pre:nth-child(2) { animation-delay: .4s; }
.tapir-spin pre:nth-child(3) { animation-delay: .8s; }
@keyframes tapir-cycle { 0%, 33.32% { opacity: 1; } 33.33%, 100% { opacity: 0; } }
.tapir-label { color: var(--muted); font-size: .9rem; margin: 0; }
@media (prefers-reduced-motion: reduce) {
.tapir-spin pre { animation: none; }
.tapir-spin pre:nth-child(1) { opacity: 1; }
}
/* summarization mode toggle on the account page */ /* summarization mode toggle on the account page */
.summarize-mode { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; } .summarize-mode { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
.summarize-mode p { margin: 0; } .summarize-mode p { margin: 0; }
+33
View File
@@ -140,6 +140,39 @@ templ VideoCard(r store.SummaryRow) {
</li> </li>
} }
// TapirSpinner is the summarizing animation: three ASCII tapir frames stacked
// and cross-faded by CSS keyframes (no JS), with a muted "Summarizing…" label.
// The art is aria-hidden — it is decorative; the label carries the meaning.
templ TapirSpinner() {
<div class="tapir-spin" aria-hidden="true">
<pre>{ tapirFrame1 }</pre>
<pre>{ tapirFrame2 }</pre>
<pre>{ tapirFrame3 }</pre>
</div>
<p class="tapir-label" role="status" aria-live="polite"><em>Summarizing…</em></p>
}
// processingCard is the in-flight summarization card. It replaces the Summarize
// button card and polls /v/{id}/status every 2s, swapping itself (outerHTML, same
// id as VideoCard) for whatever state comes back: it keeps polling while still
// processing, and the summary/queued card it is eventually replaced by carries no
// poll, so polling stops on its own when the fragment changes.
templ processingCard(r store.SummaryRow) {
<li
class="card card-processing"
id={ "video-" + r.VideoID }
hx-get={ string(statusURL(r.VideoID)) }
hx-trigger="every 2s"
hx-swap="outerHTML"
>
<div class="card-title">{ displayTitle(r) }</div>
if cardMeta(r) != "" {
<div class="card-meta">{ cardMeta(r) }</div>
}
@TapirSpinner()
</li>
}
// DetailPage is the full summary view: text, highlights, takeaways, metadata, // DetailPage is the full summary view: text, highlights, takeaways, metadata,
// and the action button group. // and the action button group.
templ DetailPage(r store.SummaryRow) { templ DetailPage(r store.SummaryRow) {
+362 -191
View File
@@ -595,9 +595,10 @@ func VideoCard(r store.SummaryRow) templ.Component {
}) })
} }
// DetailPage is the full summary view: text, highlights, takeaways, metadata, // TapirSpinner is the summarizing animation: three ASCII tapir frames stacked
// and the action button group. // and cross-faded by CSS keyframes (no JS), with a muted "Summarizing…" label.
func DetailPage(r store.SummaryRow) templ.Component { // The art is aria-hidden — it is decorative; the label carries the meaning.
func TapirSpinner() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -618,7 +619,177 @@ func DetailPage(r store.SummaryRow) templ.Component {
templ_7745c5c3_Var28 = templ.NopComponent templ_7745c5c3_Var28 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var29 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"tapir-spin\" aria-hidden=\"true\"><pre>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(tapirFrame1)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 148, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</pre><pre>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(tapirFrame2)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 149, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</pre><pre>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(tapirFrame3)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 150, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</pre></div><p class=\"tapir-label\" role=\"status\" aria-live=\"polite\"><em>Summarizing…</em></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// processingCard is the in-flight summarization card. It replaces the Summarize
// button card and polls /v/{id}/status every 2s, swapping itself (outerHTML, same
// id as VideoCard) for whatever state comes back: it keeps polling while still
// processing, and the summary/queued card it is eventually replaced by carries no
// poll, so polling stops on its own when the fragment changes.
func processingCard(r store.SummaryRow) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var32 := templ.GetChildren(ctx)
if templ_7745c5c3_Var32 == nil {
templ_7745c5c3_Var32 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<li class=\"card card-processing\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue("video-" + r.VideoID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 163, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(statusURL(r.VideoID)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 164, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\" hx-trigger=\"every 2s\" hx-swap=\"outerHTML\"><div class=\"card-title\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 168, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if cardMeta(r) != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<div class=\"card-meta\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(cardMeta(r))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 170, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = TapirSpinner().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
// and the action button group.
func DetailPage(r store.SummaryRow) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var37 := templ.GetChildren(ctx)
if templ_7745c5c3_Var37 == nil {
templ_7745c5c3_Var37 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@@ -630,99 +801,99 @@ func DetailPage(r store.SummaryRow) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<article class=\"detail\"><h1>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<article class=\"detail\"><h1>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var30 string var templ_7745c5c3_Var39 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r)) templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 148, Col: 24} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 181, Col: 24}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</h1><p class=\"meta\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</h1><p class=\"meta\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if detailMeta(r) != "" { if detailMeta(r) != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<span>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var31 string var templ_7745c5c3_Var40 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r)) templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 151, Col: 26} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 184, Col: 26}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</span> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
if r.FallbackUsed { if r.FallbackUsed {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"badge\" title=\"summarized with the fallback model\" aria-label=\"summarized with the fallback model\">fallback</span>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<span class=\"badge\" title=\"summarized with the fallback model\" aria-label=\"summarized with the fallback model\">fallback</span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if url, ok := embedURL(r.ProviderVideoID); ok { if url, ok := embedURL(r.ProviderVideoID); ok {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<div class=\"embed\"><iframe src=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<div class=\"embed\"><iframe src=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var32 string var templ_7745c5c3_Var41 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(url) templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(url)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 160, Col: 15} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 193, Col: 15}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\" title=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\" title=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var33 string var templ_7745c5c3_Var42 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(displayTitle(r)) templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue(displayTitle(r))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 161, Col: 29} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 194, Col: 29}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
if r.URL != "" { if r.URL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<p class=\"source\"><a href=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<p class=\"source\"><a href=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var34 templ.SafeURL var templ_7745c5c3_Var43 templ.SafeURL
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(externalURL(r.URL)) templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinURLErrs(externalURL(r.URL))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 170, Col: 50} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 203, Col: 50}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\" rel=\"noopener noreferrer\">watch on source ↗</a></p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\" rel=\"noopener noreferrer\">watch on source ↗</a></p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -731,88 +902,88 @@ func DetailPage(r store.SummaryRow) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<section><h2>Summary</h2><p class=\"body\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<section><h2>Summary</h2><p class=\"body\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var35 string var templ_7745c5c3_Var44 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary) templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 175, Col: 31} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 208, Col: 31}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</p></section>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</p></section>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if len(r.Highlights) > 0 { if len(r.Highlights) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<section><h2>Highlights</h2><ul>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<section><h2>Highlights</h2><ul>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, h := range r.Highlights { for _, h := range r.Highlights {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<li>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var36 string var templ_7745c5c3_Var45 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(h) templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(h)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 182, Col: 14} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 215, Col: 14}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</li>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</ul></section>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</ul></section>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
if len(r.Takeaways) > 0 { if len(r.Takeaways) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<section><h2>Takeaways</h2><ul>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<section><h2>Takeaways</h2><ul>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, t := range r.Takeaways { for _, t := range r.Takeaways {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<li>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var37 string var templ_7745c5c3_Var46 string
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(t) templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(t)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 192, Col: 14} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 225, Col: 14}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</li>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</ul></section>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</ul></section>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</article>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</article>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil return nil
}) })
templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var29), templ_7745c5c3_Buffer) templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -839,12 +1010,12 @@ func RegisterPage(email, errMsg string) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var38 := templ.GetChildren(ctx) templ_7745c5c3_Var47 := templ.GetChildren(ctx)
if templ_7745c5c3_Var38 == nil { if templ_7745c5c3_Var47 == nil {
templ_7745c5c3_Var38 = templ.NopComponent templ_7745c5c3_Var47 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var39 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_Var48 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@@ -856,59 +1027,59 @@ func RegisterPage(email, errMsg string) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<article class=\"register\"><h1>Complete your registration</h1>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<article class=\"register\"><h1>Complete your registration</h1>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if email != "" { if email != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "<p class=\"meta\">Signed in as ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<p class=\"meta\">Signed in as ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var40 string var templ_7745c5c3_Var49 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(email) templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 209, Col: 40} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 242, Col: 40}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, ".</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, ".</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<p>Choose a display name to finish setting up your Tapir account.</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<p>Choose a display name to finish setting up your Tapir account.</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if errMsg != "" { if errMsg != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"error\" role=\"alert\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<p class=\"error\" role=\"alert\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var41 string var templ_7745c5c3_Var50 string
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg) templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 213, Col: 42} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 246, Col: 42}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<form method=\"post\" action=\"/register\" class=\"register-form\"><label>Display name <input type=\"text\" name=\"display_name\" required autofocus></label> <label class=\"checkbox\"><input type=\"checkbox\" name=\"accept_terms\" value=\"yes\" required> I accept the terms of use</label> <button type=\"submit\" class=\"btn\">Register</button></form></article>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<form method=\"post\" action=\"/register\" class=\"register-form\"><label>Display name <input type=\"text\" name=\"display_name\" required autofocus></label> <label class=\"checkbox\"><input type=\"checkbox\" name=\"accept_terms\" value=\"yes\" required> I accept the terms of use</label> <button type=\"submit\" class=\"btn\">Register</button></form></article>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil return nil
}) })
templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var39), templ_7745c5c3_Buffer) templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var48), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -936,12 +1107,12 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var42 := templ.GetChildren(ctx) templ_7745c5c3_Var51 := templ.GetChildren(ctx)
if templ_7745c5c3_Var42 == nil { if templ_7745c5c3_Var51 == nil {
templ_7745c5c3_Var42 = templ.NopComponent templ_7745c5c3_Var51 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var43 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_Var52 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@@ -957,43 +1128,43 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, " <article class=\"account\"><h1>Account</h1><dl class=\"account-meta\"><dt>Display name</dt><dd>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, " <article class=\"account\"><h1>Account</h1><dl class=\"account-meta\"><dt>Display name</dt><dd>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var44 string var templ_7745c5c3_Var53 string
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName)) templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 241, Col: 36} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 274, Col: 36}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</dd>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</dd>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if email != "" { if email != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<dt>Signed in as</dt><dd>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<dt>Signed in as</dt><dd>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var45 string var templ_7745c5c3_Var54 string
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(email) templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 244, Col: 16} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 277, Col: 16}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</dd>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</dd>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</dl><section><h2>Summarization</h2><p class=\"muted\">Automatic summarizes every new video as it is discovered. Manual lets you pick which videos to summarize — new videos appear in your list with a Summarize button.</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</dl><section><h2>Summarization</h2><p class=\"muted\">Automatic summarizes every new video as it is discovered. Manual lets you pick which videos to summarize — new videos appear in your list with a Summarize button.</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -1001,119 +1172,119 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</section><section><h2>Connected accounts</h2>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</section><section><h2>Connected accounts</h2>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if len(conns) == 0 { if len(conns) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<p class=\"muted\">No connected video accounts yet.</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<p class=\"muted\">No connected video accounts yet.</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<ul class=\"conn-list\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<ul class=\"conn-list\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, c := range conns { for _, c := range conns {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<li class=\"conn\"><div class=\"conn-main\"><span class=\"conn-provider\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<li class=\"conn\"><div class=\"conn-main\"><span class=\"conn-provider\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var46 string var templ_7745c5c3_Var55 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider)) templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 265, Col: 64} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 298, Col: 64}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</span> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if c.ProviderAccount != "" { if c.ProviderAccount != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<span class=\"muted\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<span class=\"muted\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var47 string var templ_7745c5c3_Var56 string
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount) templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 267, Col: 49} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 300, Col: 49}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</span> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<span class=\"chip\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<span class=\"chip\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var48 string var templ_7745c5c3_Var57 string
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status) templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 269, Col: 38} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 302, Col: 38}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</span></div><div class=\"conn-meta muted\">connected ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</span></div><div class=\"conn-meta muted\">connected ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var49 string var templ_7745c5c3_Var58 string
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02")) templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 271, Col: 83} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 304, Col: 83}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</div><form method=\"post\" action=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</div><form method=\"post\" action=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var50 templ.SafeURL var templ_7745c5c3_Var59 templ.SafeURL
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinURLErrs(disconnectURL(c.Provider)) templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinURLErrs(disconnectURL(c.Provider))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 272, Col: 62} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 305, Col: 62}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\"><button type=\"submit\" class=\"btn-secondary\">Disconnect</button></form></li>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "\"><button type=\"submit\" class=\"btn-secondary\">Disconnect</button></form></li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</ul>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</ul>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
if !hasYouTube(conns) { if !hasYouTube(conns) {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<p><a class=\"btn\" href=\"/oauth/youtube/connect\">Connect YouTube</a></p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<p><a class=\"btn\" href=\"/oauth/youtube/connect\">Connect YouTube</a></p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</section><section class=\"danger-zone\"><h2>Delete account</h2><p class=\"muted\">Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.</p><details class=\"confirm-delete\"><summary class=\"btn-danger\">Delete account…</summary><div class=\"confirm-body\"><p>This permanently deletes your account and all data. Are you sure?</p><form method=\"post\" action=\"/account/delete\"><button type=\"submit\" class=\"btn-danger\">Yes, permanently delete my account</button></form></div></details></section></article>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</section><section class=\"danger-zone\"><h2>Delete account</h2><p class=\"muted\">Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.</p><details class=\"confirm-delete\"><summary class=\"btn-danger\">Delete account…</summary><div class=\"confirm-body\"><p>This permanently deletes your account and all data. Are you sure?</p><form method=\"post\" action=\"/account/delete\"><button type=\"submit\" class=\"btn-danger\">Yes, permanently delete my account</button></form></div></details></section></article>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil return nil
}) })
templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var43), templ_7745c5c3_Buffer) templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var52), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -1141,51 +1312,51 @@ func summarizeModeControl(auto bool) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var51 := templ.GetChildren(ctx) templ_7745c5c3_Var60 := templ.GetChildren(ctx)
if templ_7745c5c3_Var51 == nil { if templ_7745c5c3_Var60 == nil {
templ_7745c5c3_Var51 = templ.NopComponent templ_7745c5c3_Var60 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<div id=\"summarize-mode\" class=\"summarize-mode\"><p>Current mode: <strong>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "<div id=\"summarize-mode\" class=\"summarize-mode\"><p>Current mode: <strong>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var52 string var templ_7745c5c3_Var61 string
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto)) templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 309, Col: 53} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 342, Col: 53}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</strong></p><form method=\"post\" action=\"/account/summarize-mode\" hx-post=\"/account/summarize-mode\" hx-target=\"#summarize-mode\" hx-swap=\"outerHTML\"><input type=\"hidden\" name=\"enabled\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</strong></p><form method=\"post\" action=\"/account/summarize-mode\" hx-post=\"/account/summarize-mode\" hx-target=\"#summarize-mode\" hx-swap=\"outerHTML\"><input type=\"hidden\" name=\"enabled\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var53 string var templ_7745c5c3_Var62 string
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolStr(!auto)) templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolStr(!auto))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 317, Col: 61} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 350, Col: 61}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var62)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\"> <button type=\"submit\" class=\"btn-secondary\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "\"> <button type=\"submit\" class=\"btn-secondary\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var54 string var templ_7745c5c3_Var63 string
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto)) templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 318, Col: 79} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 351, Col: 79}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</button></form></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</button></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -1213,117 +1384,117 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var55 := templ.GetChildren(ctx) templ_7745c5c3_Var64 := templ.GetChildren(ctx)
if templ_7745c5c3_Var55 == nil { if templ_7745c5c3_Var64 == nil {
templ_7745c5c3_Var55 = templ.NopComponent templ_7745c5c3_Var64 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<form id=\"action-buttons\" class=\"actions\" method=\"post\" action=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "<form id=\"action-buttons\" class=\"actions\" method=\"post\" action=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var56 templ.SafeURL var templ_7745c5c3_Var65 templ.SafeURL
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinURLErrs(actionURL(videoID)) templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(actionURL(videoID))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 332, Col: 29} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 365, Col: 29}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "\" hx-post=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "\" hx-post=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var57 string var templ_7745c5c3_Var66 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(actionURL(videoID))) templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(actionURL(videoID)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 333, Col: 38} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 366, Col: 38}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, v := range actionVerbs { for _, v := range actionVerbs {
var templ_7745c5c3_Var58 = []any{"action", templ.KV("active", active[v])} var templ_7745c5c3_Var67 = []any{"action", templ.KV("active", active[v])}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var58...) templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var67...)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<button type=\"submit\" name=\"action\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "<button type=\"submit\" name=\"action\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var59 string var templ_7745c5c3_Var68 string
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(v) templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.ResolveAttributeValue(v)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 341, Col: 13} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 374, Col: 13}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var68)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\" class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\" class=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var60 string var templ_7745c5c3_Var69 string
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var58).String()) templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var67).String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 1, Col: 0} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 1, Col: 0}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "\" aria-pressed=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "\" aria-pressed=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var61 string var templ_7745c5c3_Var70 string
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaPressed(active[v])) templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.ResolveAttributeValue(ariaPressed(active[v]))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 343, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 376, Col: 41}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var70)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if active[v] { if active[v] {
var templ_7745c5c3_Var62 string var templ_7745c5c3_Var71 string
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(v)) templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(v))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 346, Col: 30} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 379, Col: 30}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
var templ_7745c5c3_Var63 string var templ_7745c5c3_Var72 string
templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(v)) templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(v))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 348, Col: 21} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 381, Col: 21}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</button>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</button>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</form>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</form>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }