refactor(web): one helper shapes the log lines of the page, raw feed and live feed
appendLogLine turns the runner's markers into structure and joins the pieces of a line it cut, for the job page and the live feed (the SSE feed now sends merged lines; a piece that opens a batch is marked and joined by the page). The dead placeholders of actions.go are gone. The live log script moves out of ermite.js into log.js with a testable follow(), covered by jstest/log.test.mjs (append, join, scroll at the end only, close on ci-end).
- commit
- cd99c65322d694fa509569a3eaab1de8f1d5a9d4
- author
- brice · · 2026-10-12 19:20 UTC
- parent
- 7513520
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
43 files · +502 -262
M internal/adapters/web/ASSETS.md +1 -0
| @@ -9,6 +9,7 @@ content-hashed names. Nothing is fetched at runtime. | |||
| 9 | 9 | | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` | | |
| 10 | 10 | | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` | | |
| 11 | 11 | | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | | | |
| 12 | + | | `static/js/log.js` | the live log of a running job (`window.ermiteLog`, tested in Node by `jstest/log.test.mjs`) | written here | | | |
| 12 | 13 | | `static/js/files.js` | the Files view: directories open in place (`window.ermiteTree`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | | | |
| 13 | 14 | | `static/css/56-chroma.css` | syntax classes of the code view, mapped to the `--syn-*` tokens (no colour of its own) | generated by `go run scripts/chroma-css.go` from chroma's token classes | | | |
| 14 | 15 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | |
| @@ -9,6 +9,7 @@ content-hashed names. Nothing is fetched at runtime. | |||||
| 9 | | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` | | 9 | | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` | | ||
| 10 | | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` | | 10 | | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` | | ||
| 11 | | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | | | 11 | | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | | | ||
| 12 | + | | `static/js/log.js` | the live log of a running job (`window.ermiteLog`, tested in Node by `jstest/log.test.mjs`) | written here | | | |||
| 12 | | `static/js/files.js` | the Files view: directories open in place (`window.ermiteTree`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | | | 13 | | `static/js/files.js` | the Files view: directories open in place (`window.ermiteTree`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | | | ||
| 13 | | `static/css/56-chroma.css` | syntax classes of the code view, mapped to the `--syn-*` tokens (no colour of its own) | generated by `go run scripts/chroma-css.go` from chroma's token classes | | | 14 | | `static/css/56-chroma.css` | syntax classes of the code view, mapped to the `--syn-*` tokens (no colour of its own) | generated by `go run scripts/chroma-css.go` from chroma's token classes | | | ||
| 14 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | 15 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | ||
M internal/adapters/web/actions.go +41 -54
| @@ -198,6 +198,9 @@ type LogLineView struct { | |||
| 198 | 198 | // Group marks "##[group]" and "##[endgroup]" lines and Error the | |
| 199 | 199 | // "##[error]" ones; the text is shown without the marker. | |
| 200 | 200 | Group, End, Error bool | |
| 201 | + | // Continued marks a piece of a line whose beginning is in an earlier batch | |
| 202 | + | // of the live feed: the page appends its text to the line before it. | |
| 203 | + | Continued bool | |
| 201 | 204 | } | |
| 202 | 205 | ||
| 203 | 206 | // JobPage is one job with its steps and its log. | |
| @@ -452,6 +455,30 @@ func (s *Server) job(ctx context.Context, r *http.Request) (templ.Component, int | |||
| 452 | 455 | return Job(l, p), http.StatusOK, nil | |
| 453 | 456 | } | |
| 454 | 457 | ||
| 458 | + | // appendLogLine adds the line n of a job log to the lines shown. The markers | |
| 459 | + | // of the runner ("##[group]", "##[endgroup]", "##[error]") become structure and | |
| 460 | + | // are not shown as text. A piece that continues a line too long to send whole | |
| 461 | + | // is joined to the line before it; one that opens the list (its line is in an | |
| 462 | + | // earlier batch) keeps Continued, and the page appends it to that line. | |
| 463 | + | func appendLogLine(out []LogLineView, n int64, l ports.JobLogLine) []LogLineView { | |
| 464 | + | text := l.Text | |
| 465 | + | v := LogLineView{N: n, Step: l.Step, Time: l.Time.UTC().Format("15:04:05"), Continued: l.Continued} | |
| 466 | + | switch { | |
| 467 | + | case strings.HasPrefix(text, "##[group]"): | |
| 468 | + | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |
| 469 | + | case strings.HasPrefix(text, "##[endgroup]"): | |
| 470 | + | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |
| 471 | + | case strings.HasPrefix(text, "##[error]"): | |
| 472 | + | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |
| 473 | + | } | |
| 474 | + | v.Text = visibleText(text) | |
| 475 | + | if l.Continued && len(out) > 0 { | |
| 476 | + | out[len(out)-1].Text += v.Text | |
| 477 | + | return out | |
| 478 | + | } | |
| 479 | + | return append(out, v) | |
| 480 | + | } | |
| 481 | + | ||
| 455 | 482 | // logClient is the key of the visitor for the limit of log streams per client. | |
| 456 | 483 | func (s *Server) logClient(r *http.Request) string { | |
| 457 | 484 | return clientlimit.Key(clientlimit.ClientAddr(r, s.cfg.TrustedProxies)) | |
| @@ -465,40 +492,14 @@ func (s *Server) readLog(ctx context.Context, client string, repo domain.RepoNam | |||
| 465 | 492 | } | |
| 466 | 493 | var out []LogLineView | |
| 467 | 494 | next := from | |
| 468 | - | ranges := j.StepRanges() | |
| 469 | - | stepOf := func(n int64) int32 { | |
| 470 | - | for i, r := range ranges { | |
| 471 | - | if n >= r.Start && n < r.End { | |
| 472 | - | return j.Steps[i].Number | |
| 473 | - | } | |
| 474 | - | } | |
| 475 | - | return 0 | |
| 476 | - | } | |
| 477 | 495 | stop := errors.New("enough") | |
| 478 | 496 | err := s.cfg.Runs.GetJobLog(ctx, principal(ctx), repo, ports.GetJobLogParams{JobID: j.ID, Attempt: j.Attempt, Offset: from, Client: client}, func(b ports.LogBatch) error { | |
| 479 | 497 | for i, l := range b.Lines { | |
| 480 | - | if len(out) >= max { | |
| 481 | - | return stop | |
| 482 | - | } | |
| 483 | - | n := b.Offset + int64(i) | |
| 484 | - | text := l.Text | |
| 485 | - | v := LogLineView{N: n, Step: stepOf(n), Time: l.Time.UTC().Format("15:04:05")} | |
| 486 | - | switch { | |
| 487 | - | case strings.HasPrefix(text, "##[group]"): | |
| 488 | - | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |
| 489 | - | case strings.HasPrefix(text, "##[endgroup]"): | |
| 490 | - | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |
| 491 | - | case strings.HasPrefix(text, "##[error]"): | |
| 492 | - | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |
| 498 | + | if len(out) >= max && !l.Continued { | |
| 499 | + | return stop // the rest of a cut line still belongs to this page | |
| 493 | 500 | } | |
| 494 | - | if l.Continued && len(out) > 0 { | |
| 495 | - | out[len(out)-1].Text += visibleText(text) | |
| 496 | - | next = n + 1 | |
| 497 | - | continue | |
| 498 | - | } | |
| 499 | - | v.Text = visibleText(text) | |
| 500 | - | out = append(out, v) | |
| 501 | - | next = n + 1 | |
| 501 | + | out = appendLogLine(out, b.Offset+int64(i), l) | |
| 502 | + | next = b.Offset + int64(i) + 1 | |
| 502 | 503 | } | |
| 503 | 504 | return nil | |
| 504 | 505 | }) | |
| @@ -533,8 +534,9 @@ func (s *Server) rawLog(w http.ResponseWriter, r *http.Request) { | |||
| 533 | 534 | } | |
| 534 | 535 | attempt = int32(n) | |
| 535 | 536 | } | |
| 536 | - | view, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, attempt) | |
| 537 | - | if err != nil { | |
| 537 | + | // The job is read first: a job that is not there, or not readable, is a 404 | |
| 538 | + | // before any header of the log is sent. | |
| 539 | + | if _, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, attempt); err != nil { | |
| 538 | 540 | s.fail(ctx, w, r, err) | |
| 539 | 541 | return | |
| 540 | 542 | } | |
| @@ -573,7 +575,6 @@ func (s *Server) rawLog(w http.ResponseWriter, r *http.Request) { | |||
| 573 | 575 | _, _ = fmt.Fprintln(w) | |
| 574 | 576 | } | |
| 575 | 577 | if err != nil && !open && errors.Is(err, domain.ErrNotFound) { | |
| 576 | - | _ = view | |
| 577 | 578 | w.WriteHeader(http.StatusNotFound) | |
| 578 | 579 | } | |
| 579 | 580 | } | |
| @@ -714,7 +715,7 @@ func checksSummary(repo domain.RepoName, sha string, checks []ports.CheckView) C | |||
| 714 | 715 | ||
| 715 | 716 | // checksSection is the checks of the rail of a merge request, with the rule of | |
| 716 | 717 | // the repository that applies to them. | |
| 717 | - | func checksSection(repo domain.Repo, checks []ports.CheckView, now time.Time) ChecksSection { | |
| 718 | + | func checksSection(repo domain.Repo, checks []ports.CheckView) ChecksSection { | |
| 718 | 719 | sec := ChecksSection{} | |
| 719 | 720 | for _, c := range checks { | |
| 720 | 721 | row := CheckRow{ | |
| @@ -736,7 +737,6 @@ func checksSection(repo domain.Repo, checks []ports.CheckView, now time.Time) Ch | |||
| 736 | 737 | default: | |
| 737 | 738 | sec.Note = "checks do not block the merge in this repository." | |
| 738 | 739 | } | |
| 739 | - | _ = now | |
| 740 | 740 | return sec | |
| 741 | 741 | } | |
| 742 | 742 | ||
| @@ -770,8 +770,7 @@ func (s *Server) logStream(w http.ResponseWriter, r *http.Request) { | |||
| 770 | 770 | s.denyEvents(ctx, w, errNotFound) | |
| 771 | 771 | return | |
| 772 | 772 | } | |
| 773 | - | view, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, 0) | |
| 774 | - | if err != nil { | |
| 773 | + | if _, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, 0); err != nil { | |
| 775 | 774 | s.denyEvents(ctx, w, err) | |
| 776 | 775 | return | |
| 777 | 776 | } | |
| @@ -786,29 +785,17 @@ func (s *Server) logStream(w http.ResponseWriter, r *http.Request) { | |||
| 786 | 785 | if out.raw(": connected\n\n") != nil { | |
| 787 | 786 | return | |
| 788 | 787 | } | |
| 789 | - | ranges := view.StepRanges() | |
| 790 | - | _ = ranges | |
| 791 | 788 | n := from | |
| 792 | 789 | err = s.cfg.Runs.GetJobLog(ctx, principal(ctx), d.repo.Name, ports.GetJobLogParams{ | |
| 793 | 790 | JobID: jobID, Offset: from, Follow: true, | |
| 794 | 791 | Client: s.logClient(r), | |
| 795 | 792 | }, func(b ports.LogBatch) error { | |
| 796 | - | var html strings.Builder | |
| 793 | + | var views []LogLineView | |
| 797 | 794 | for i, l := range b.Lines { | |
| 798 | - | v := LogLineView{N: b.Offset + int64(i), Time: l.Time.UTC().Format("15:04:05"), Step: l.Step} | |
| 799 | - | text := l.Text | |
| 800 | - | switch { | |
| 801 | - | case strings.HasPrefix(text, "##[group]"): | |
| 802 | - | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |
| 803 | - | case strings.HasPrefix(text, "##[endgroup]"): | |
| 804 | - | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |
| 805 | - | case strings.HasPrefix(text, "##[error]"): | |
| 806 | - | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |
| 807 | - | } | |
| 808 | - | v.Text = visibleText(text) | |
| 809 | - | if l.Continued { | |
| 810 | - | continue // joined to its line by the page reload; rare and harmless | |
| 811 | - | } | |
| 795 | + | views = appendLogLine(views, b.Offset+int64(i), l) | |
| 796 | + | } | |
| 797 | + | var html strings.Builder | |
| 798 | + | for _, v := range views { | |
| 812 | 799 | var b2 strings.Builder | |
| 813 | 800 | if err := LogLine(v).Render(ctx, &b2); err != nil { | |
| 814 | 801 | return err | |
| @@ -198,6 +198,9 @@ type LogLineView struct { | |||||
| 198 | // Group marks "##[group]" and "##[endgroup]" lines and Error the | 198 | // Group marks "##[group]" and "##[endgroup]" lines and Error the | ||
| 199 | // "##[error]" ones; the text is shown without the marker. | 199 | // "##[error]" ones; the text is shown without the marker. | ||
| 200 | Group, End, Error bool | 200 | Group, End, Error bool | ||
| 201 | + | // Continued marks a piece of a line whose beginning is in an earlier batch | |||
| 202 | + | // of the live feed: the page appends its text to the line before it. | |||
| 203 | + | Continued bool | |||
| 201 | } | 204 | } | ||
| 202 | 205 | ||||
| 203 | // JobPage is one job with its steps and its log. | 206 | // JobPage is one job with its steps and its log. | ||
| @@ -452,6 +455,30 @@ func (s *Server) job(ctx context.Context, r *http.Request) (templ.Component, int | |||||
| 452 | return Job(l, p), http.StatusOK, nil | 455 | return Job(l, p), http.StatusOK, nil | ||
| 453 | } | 456 | } | ||
| 454 | 457 | ||||
| 458 | + | // appendLogLine adds the line n of a job log to the lines shown. The markers | |||
| 459 | + | // of the runner ("##[group]", "##[endgroup]", "##[error]") become structure and | |||
| 460 | + | // are not shown as text. A piece that continues a line too long to send whole | |||
| 461 | + | // is joined to the line before it; one that opens the list (its line is in an | |||
| 462 | + | // earlier batch) keeps Continued, and the page appends it to that line. | |||
| 463 | + | func appendLogLine(out []LogLineView, n int64, l ports.JobLogLine) []LogLineView { | |||
| 464 | + | text := l.Text | |||
| 465 | + | v := LogLineView{N: n, Step: l.Step, Time: l.Time.UTC().Format("15:04:05"), Continued: l.Continued} | |||
| 466 | + | switch { | |||
| 467 | + | case strings.HasPrefix(text, "##[group]"): | |||
| 468 | + | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |||
| 469 | + | case strings.HasPrefix(text, "##[endgroup]"): | |||
| 470 | + | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |||
| 471 | + | case strings.HasPrefix(text, "##[error]"): | |||
| 472 | + | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |||
| 473 | + | } | |||
| 474 | + | v.Text = visibleText(text) | |||
| 475 | + | if l.Continued && len(out) > 0 { | |||
| 476 | + | out[len(out)-1].Text += v.Text | |||
| 477 | + | return out | |||
| 478 | + | } | |||
| 479 | + | return append(out, v) | |||
| 480 | + | } | |||
| 481 | + | ||||
| 455 | // logClient is the key of the visitor for the limit of log streams per client. | 482 | // logClient is the key of the visitor for the limit of log streams per client. | ||
| 456 | func (s *Server) logClient(r *http.Request) string { | 483 | func (s *Server) logClient(r *http.Request) string { | ||
| 457 | return clientlimit.Key(clientlimit.ClientAddr(r, s.cfg.TrustedProxies)) | 484 | return clientlimit.Key(clientlimit.ClientAddr(r, s.cfg.TrustedProxies)) | ||
| @@ -465,40 +492,14 @@ func (s *Server) readLog(ctx context.Context, client string, repo domain.RepoNam | |||||
| 465 | } | 492 | } | ||
| 466 | var out []LogLineView | 493 | var out []LogLineView | ||
| 467 | next := from | 494 | next := from | ||
| 468 | - | ranges := j.StepRanges() | |||
| 469 | - | stepOf := func(n int64) int32 { | |||
| 470 | - | for i, r := range ranges { | |||
| 471 | - | if n >= r.Start && n < r.End { | |||
| 472 | - | return j.Steps[i].Number | |||
| 473 | - | } | |||
| 474 | - | } | |||
| 475 | - | return 0 | |||
| 476 | - | } | |||
| 477 | stop := errors.New("enough") | 495 | stop := errors.New("enough") | ||
| 478 | err := s.cfg.Runs.GetJobLog(ctx, principal(ctx), repo, ports.GetJobLogParams{JobID: j.ID, Attempt: j.Attempt, Offset: from, Client: client}, func(b ports.LogBatch) error { | 496 | err := s.cfg.Runs.GetJobLog(ctx, principal(ctx), repo, ports.GetJobLogParams{JobID: j.ID, Attempt: j.Attempt, Offset: from, Client: client}, func(b ports.LogBatch) error { | ||
| 479 | for i, l := range b.Lines { | 497 | for i, l := range b.Lines { | ||
| 480 | - | if len(out) >= max { | 498 | + | if len(out) >= max && !l.Continued { |
| 481 | - | return stop | 499 | + | return stop // the rest of a cut line still belongs to this page |
| 482 | - | } | |||
| 483 | - | n := b.Offset + int64(i) | |||
| 484 | - | text := l.Text | |||
| 485 | - | v := LogLineView{N: n, Step: stepOf(n), Time: l.Time.UTC().Format("15:04:05")} | |||
| 486 | - | switch { | |||
| 487 | - | case strings.HasPrefix(text, "##[group]"): | |||
| 488 | - | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |||
| 489 | - | case strings.HasPrefix(text, "##[endgroup]"): | |||
| 490 | - | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |||
| 491 | - | case strings.HasPrefix(text, "##[error]"): | |||
| 492 | - | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |||
| 493 | } | 500 | } | ||
| 494 | - | if l.Continued && len(out) > 0 { | 501 | + | out = appendLogLine(out, b.Offset+int64(i), l) |
| 495 | - | out[len(out)-1].Text += visibleText(text) | 502 | + | next = b.Offset + int64(i) + 1 |
| 496 | - | next = n + 1 | |||
| 497 | - | continue | |||
| 498 | - | } | |||
| 499 | - | v.Text = visibleText(text) | |||
| 500 | - | out = append(out, v) | |||
| 501 | - | next = n + 1 | |||
| 502 | } | 503 | } | ||
| 503 | return nil | 504 | return nil | ||
| 504 | }) | 505 | }) | ||
| @@ -533,8 +534,9 @@ func (s *Server) rawLog(w http.ResponseWriter, r *http.Request) { | |||||
| 533 | } | 534 | } | ||
| 534 | attempt = int32(n) | 535 | attempt = int32(n) | ||
| 535 | } | 536 | } | ||
| 536 | - | view, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, attempt) | 537 | + | // The job is read first: a job that is not there, or not readable, is a 404 |
| 537 | - | if err != nil { | 538 | + | // before any header of the log is sent. |
| 539 | + | if _, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, attempt); err != nil { | |||
| 538 | s.fail(ctx, w, r, err) | 540 | s.fail(ctx, w, r, err) | ||
| 539 | return | 541 | return | ||
| 540 | } | 542 | } | ||
| @@ -573,7 +575,6 @@ func (s *Server) rawLog(w http.ResponseWriter, r *http.Request) { | |||||
| 573 | _, _ = fmt.Fprintln(w) | 575 | _, _ = fmt.Fprintln(w) | ||
| 574 | } | 576 | } | ||
| 575 | if err != nil && !open && errors.Is(err, domain.ErrNotFound) { | 577 | if err != nil && !open && errors.Is(err, domain.ErrNotFound) { | ||
| 576 | - | _ = view | |||
| 577 | w.WriteHeader(http.StatusNotFound) | 578 | w.WriteHeader(http.StatusNotFound) | ||
| 578 | } | 579 | } | ||
| 579 | } | 580 | } | ||
| @@ -714,7 +715,7 @@ func checksSummary(repo domain.RepoName, sha string, checks []ports.CheckView) C | |||||
| 714 | 715 | ||||
| 715 | // checksSection is the checks of the rail of a merge request, with the rule of | 716 | // checksSection is the checks of the rail of a merge request, with the rule of | ||
| 716 | // the repository that applies to them. | 717 | // the repository that applies to them. | ||
| 717 | - | func checksSection(repo domain.Repo, checks []ports.CheckView, now time.Time) ChecksSection { | 718 | + | func checksSection(repo domain.Repo, checks []ports.CheckView) ChecksSection { |
| 718 | sec := ChecksSection{} | 719 | sec := ChecksSection{} | ||
| 719 | for _, c := range checks { | 720 | for _, c := range checks { | ||
| 720 | row := CheckRow{ | 721 | row := CheckRow{ | ||
| @@ -736,7 +737,6 @@ func checksSection(repo domain.Repo, checks []ports.CheckView, now time.Time) Ch | |||||
| 736 | default: | 737 | default: | ||
| 737 | sec.Note = "checks do not block the merge in this repository." | 738 | sec.Note = "checks do not block the merge in this repository." | ||
| 738 | } | 739 | } | ||
| 739 | - | _ = now | |||
| 740 | return sec | 740 | return sec | ||
| 741 | } | 741 | } | ||
| 742 | 742 | ||||
| @@ -770,8 +770,7 @@ func (s *Server) logStream(w http.ResponseWriter, r *http.Request) { | |||||
| 770 | s.denyEvents(ctx, w, errNotFound) | 770 | s.denyEvents(ctx, w, errNotFound) | ||
| 771 | return | 771 | return | ||
| 772 | } | 772 | } | ||
| 773 | - | view, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, 0) | 773 | + | if _, err := s.cfg.Runs.GetJob(ctx, principal(ctx), d.repo.Name, jobID, 0); err != nil { |
| 774 | - | if err != nil { | |||
| 775 | s.denyEvents(ctx, w, err) | 774 | s.denyEvents(ctx, w, err) | ||
| 776 | return | 775 | return | ||
| 777 | } | 776 | } | ||
| @@ -786,29 +785,17 @@ func (s *Server) logStream(w http.ResponseWriter, r *http.Request) { | |||||
| 786 | if out.raw(": connected\n\n") != nil { | 785 | if out.raw(": connected\n\n") != nil { | ||
| 787 | return | 786 | return | ||
| 788 | } | 787 | } | ||
| 789 | - | ranges := view.StepRanges() | |||
| 790 | - | _ = ranges | |||
| 791 | n := from | 788 | n := from | ||
| 792 | err = s.cfg.Runs.GetJobLog(ctx, principal(ctx), d.repo.Name, ports.GetJobLogParams{ | 789 | err = s.cfg.Runs.GetJobLog(ctx, principal(ctx), d.repo.Name, ports.GetJobLogParams{ | ||
| 793 | JobID: jobID, Offset: from, Follow: true, | 790 | JobID: jobID, Offset: from, Follow: true, | ||
| 794 | Client: s.logClient(r), | 791 | Client: s.logClient(r), | ||
| 795 | }, func(b ports.LogBatch) error { | 792 | }, func(b ports.LogBatch) error { | ||
| 796 | - | var html strings.Builder | 793 | + | var views []LogLineView |
| 797 | for i, l := range b.Lines { | 794 | for i, l := range b.Lines { | ||
| 798 | - | v := LogLineView{N: b.Offset + int64(i), Time: l.Time.UTC().Format("15:04:05"), Step: l.Step} | 795 | + | views = appendLogLine(views, b.Offset+int64(i), l) |
| 799 | - | text := l.Text | 796 | + | } |
| 800 | - | switch { | 797 | + | var html strings.Builder |
| 801 | - | case strings.HasPrefix(text, "##[group]"): | 798 | + | for _, v := range views { |
| 802 | - | v.Group, text = true, strings.TrimPrefix(text, "##[group]") | |||
| 803 | - | case strings.HasPrefix(text, "##[endgroup]"): | |||
| 804 | - | v.End, text = true, strings.TrimPrefix(text, "##[endgroup]") | |||
| 805 | - | case strings.HasPrefix(text, "##[error]"): | |||
| 806 | - | v.Error, text = true, strings.TrimPrefix(text, "##[error]") | |||
| 807 | - | } | |||
| 808 | - | v.Text = visibleText(text) | |||
| 809 | - | if l.Continued { | |||
| 810 | - | continue // joined to its line by the page reload; rare and harmless | |||
| 811 | - | } | |||
| 812 | var b2 strings.Builder | 799 | var b2 strings.Builder | ||
| 813 | if err := LogLine(v).Render(ctx, &b2); err != nil { | 800 | if err := LogLine(v).Render(ctx, &b2); err != nil { | ||
| 814 | return err | 801 | return err | ||
M internal/adapters/web/actions.templ +1 -1
| @@ -208,7 +208,7 @@ templ Job(l Layout, p JobPage) { | |||
| 208 | 208 | // LogLine is a line of the log. Group and error markers of the runner show as | |
| 209 | 209 | // headings and errors, never as raw text. | |
| 210 | 210 | templ LogLine(ln LogLineView) { | |
| 211 | - | <li id={ "L" + itoa64(ln.N) } class={ logClass(ln) } data-step={ itoa(int(ln.Step)) }><time>{ ln.Time }</time><span class="t">{ ln.Text }</span></li> | |
| 211 | + | <li id={ "L" + itoa64(ln.N) } class={ logClass(ln) } data-step={ itoa(int(ln.Step)) } data-cont?={ ln.Continued }><time>{ ln.Time }</time><span class="t">{ ln.Text }</span></li> | |
| 212 | 212 | } | |
| 213 | 213 | ||
| 214 | 214 | templ jobRail(p JobPage) { | |
| @@ -208,7 +208,7 @@ templ Job(l Layout, p JobPage) { | |||||
| 208 | // LogLine is a line of the log. Group and error markers of the runner show as | 208 | // LogLine is a line of the log. Group and error markers of the runner show as | ||
| 209 | // headings and errors, never as raw text. | 209 | // headings and errors, never as raw text. | ||
| 210 | templ LogLine(ln LogLineView) { | 210 | templ LogLine(ln LogLineView) { | ||
| 211 | - | <li id={ "L" + itoa64(ln.N) } class={ logClass(ln) } data-step={ itoa(int(ln.Step)) }><time>{ ln.Time }</time><span class="t">{ ln.Text }</span></li> | 211 | + | <li id={ "L" + itoa64(ln.N) } class={ logClass(ln) } data-step={ itoa(int(ln.Step)) } data-cont?={ ln.Continued }><time>{ ln.Time }</time><span class="t">{ ln.Text }</span></li> |
| 212 | } | 212 | } | ||
| 213 | 213 | ||||
| 214 | templ jobRail(p JobPage) { | 214 | templ jobRail(p JobPage) { | ||
M internal/adapters/web/actions_templ.go +49 -39 generated file: collapsed
M internal/adapters/web/actions_test.go +42 -0
| @@ -68,3 +68,45 @@ func TestLogPagesPassTheClientKey(t *testing.T) { | |||
| 68 | 68 | assert.Equal(t, want, c) | |
| 69 | 69 | } | |
| 70 | 70 | } | |
| 71 | + | ||
| 72 | + | // M4: one helper shapes the lines of the page, the raw feed and the live feed. | |
| 73 | + | func TestAppendLogLine(t *testing.T) { | |
| 74 | + | t.Parallel() | |
| 75 | + | at := time.Date(2026, 10, 12, 10, 11, 12, 0, time.UTC) | |
| 76 | + | line := func(text string, cont bool) ports.JobLogLine { | |
| 77 | + | return ports.JobLogLine{Step: 2, CILogLine: domain.CILogLine{Time: at, Text: text, Continued: cont}} | |
| 78 | + | } | |
| 79 | + | var out []LogLineView | |
| 80 | + | out = appendLogLine(out, 10, line("##[group]Run make", false)) | |
| 81 | + | out = appendLogLine(out, 11, line("plain <b>", false)) | |
| 82 | + | out = appendLogLine(out, 12, line("##[error]Process completed with exit code 1.", false)) | |
| 83 | + | out = appendLogLine(out, 13, line("##[endgroup]", false)) | |
| 84 | + | require.Len(t, out, 4) | |
| 85 | + | assert.True(t, out[0].Group) | |
| 86 | + | assert.Equal(t, "Run make", out[0].Text, "the marker is structure, not text") | |
| 87 | + | assert.EqualValues(t, 10, out[0].N) | |
| 88 | + | assert.Equal(t, "10:11:12", out[0].Time) | |
| 89 | + | assert.EqualValues(t, 2, out[0].Step) | |
| 90 | + | assert.False(t, out[1].Group || out[1].Error || out[1].End) | |
| 91 | + | assert.True(t, out[2].Error) | |
| 92 | + | assert.Equal(t, "Process completed with exit code 1.", out[2].Text) | |
| 93 | + | assert.True(t, out[3].End) | |
| 94 | + | ||
| 95 | + | // A cut line comes back as one line, with the number of its first piece. | |
| 96 | + | var cut []LogLineView | |
| 97 | + | cut = appendLogLine(cut, 20, line("first half, ", false)) | |
| 98 | + | cut = appendLogLine(cut, 21, line("second half", true)) | |
| 99 | + | require.Len(t, cut, 1) | |
| 100 | + | assert.Equal(t, "first half, second half", cut[0].Text) | |
| 101 | + | assert.EqualValues(t, 20, cut[0].N) | |
| 102 | + | ||
| 103 | + | // A piece that opens a batch has no line before it here: it stays, marked, | |
| 104 | + | // for the page to join to the line it already holds. | |
| 105 | + | open := appendLogLine(nil, 30, line("rest", true)) | |
| 106 | + | require.Len(t, open, 1) | |
| 107 | + | assert.True(t, open[0].Continued) | |
| 108 | + | ||
| 109 | + | // Invisible characters of the runner's text are shown, never applied. | |
| 110 | + | hidden := appendLogLine(nil, 40, line("a\u202eb", false)) | |
| 111 | + | assert.NotContains(t, hidden[0].Text, "\u202e") | |
| 112 | + | } | |
| @@ -68,3 +68,45 @@ func TestLogPagesPassTheClientKey(t *testing.T) { | |||||
| 68 | assert.Equal(t, want, c) | 68 | assert.Equal(t, want, c) | ||
| 69 | } | 69 | } | ||
| 70 | } | 70 | } | ||
| 71 | + | ||||
| 72 | + | // M4: one helper shapes the lines of the page, the raw feed and the live feed. | |||
| 73 | + | func TestAppendLogLine(t *testing.T) { | |||
| 74 | + | t.Parallel() | |||
| 75 | + | at := time.Date(2026, 10, 12, 10, 11, 12, 0, time.UTC) | |||
| 76 | + | line := func(text string, cont bool) ports.JobLogLine { | |||
| 77 | + | return ports.JobLogLine{Step: 2, CILogLine: domain.CILogLine{Time: at, Text: text, Continued: cont}} | |||
| 78 | + | } | |||
| 79 | + | var out []LogLineView | |||
| 80 | + | out = appendLogLine(out, 10, line("##[group]Run make", false)) | |||
| 81 | + | out = appendLogLine(out, 11, line("plain <b>", false)) | |||
| 82 | + | out = appendLogLine(out, 12, line("##[error]Process completed with exit code 1.", false)) | |||
| 83 | + | out = appendLogLine(out, 13, line("##[endgroup]", false)) | |||
| 84 | + | require.Len(t, out, 4) | |||
| 85 | + | assert.True(t, out[0].Group) | |||
| 86 | + | assert.Equal(t, "Run make", out[0].Text, "the marker is structure, not text") | |||
| 87 | + | assert.EqualValues(t, 10, out[0].N) | |||
| 88 | + | assert.Equal(t, "10:11:12", out[0].Time) | |||
| 89 | + | assert.EqualValues(t, 2, out[0].Step) | |||
| 90 | + | assert.False(t, out[1].Group || out[1].Error || out[1].End) | |||
| 91 | + | assert.True(t, out[2].Error) | |||
| 92 | + | assert.Equal(t, "Process completed with exit code 1.", out[2].Text) | |||
| 93 | + | assert.True(t, out[3].End) | |||
| 94 | + | ||||
| 95 | + | // A cut line comes back as one line, with the number of its first piece. | |||
| 96 | + | var cut []LogLineView | |||
| 97 | + | cut = appendLogLine(cut, 20, line("first half, ", false)) | |||
| 98 | + | cut = appendLogLine(cut, 21, line("second half", true)) | |||
| 99 | + | require.Len(t, cut, 1) | |||
| 100 | + | assert.Equal(t, "first half, second half", cut[0].Text) | |||
| 101 | + | assert.EqualValues(t, 20, cut[0].N) | |||
| 102 | + | ||||
| 103 | + | // A piece that opens a batch has no line before it here: it stays, marked, | |||
| 104 | + | // for the page to join to the line it already holds. | |||
| 105 | + | open := appendLogLine(nil, 30, line("rest", true)) | |||
| 106 | + | require.Len(t, open, 1) | |||
| 107 | + | assert.True(t, open[0].Continued) | |||
| 108 | + | ||||
| 109 | + | // Invisible characters of the runner's text are shown, never applied. | |||
| 110 | + | hidden := appendLogLine(nil, 40, line("a\u202eb", false)) | |||
| 111 | + | assert.NotContains(t, hidden[0].Text, "\u202e") | |||
| 112 | + | } | |||
M internal/adapters/web/assets.go +2 -1
| @@ -23,7 +23,7 @@ const StaticPrefix = "/-/static/" | |||
| 23 | 23 | // Assets are the URLs of the assets a page loads. Their names carry a hash of | |
| 24 | 24 | // the content, so they can be cached for ever. | |
| 25 | 25 | type Assets struct { | |
| 26 | - | CSS, HTMX, Live, SSE, App, Files, Icon string | |
| 26 | + | CSS, HTMX, Live, SSE, App, Files, Log, Icon string | |
| 27 | 27 | // Font is the UI font, preloaded by every page. | |
| 28 | 28 | Font string | |
| 29 | 29 | // Rules is the speculation rules file named by the Speculation-Rules header. | |
| @@ -56,6 +56,7 @@ var scripts = []struct { | |||
| 56 | 56 | {"hx-sse.min.js", func(a *Assets, u string) { a.SSE = u }}, | |
| 57 | 57 | {"ermite.js", func(a *Assets, u string) { a.App = u }}, | |
| 58 | 58 | {"files.js", func(a *Assets, u string) { a.Files = u }}, | |
| 59 | + | {"log.js", func(a *Assets, u string) { a.Log = u }}, | |
| 59 | 60 | } | |
| 60 | 61 | ||
| 61 | 62 | // sheetAssets are the files the stylesheet references as url(<dir>/<file>): | |
| @@ -23,7 +23,7 @@ const StaticPrefix = "/-/static/" | |||||
| 23 | // Assets are the URLs of the assets a page loads. Their names carry a hash of | 23 | // Assets are the URLs of the assets a page loads. Their names carry a hash of | ||
| 24 | // the content, so they can be cached for ever. | 24 | // the content, so they can be cached for ever. | ||
| 25 | type Assets struct { | 25 | type Assets struct { | ||
| 26 | - | CSS, HTMX, Live, SSE, App, Files, Icon string | 26 | + | CSS, HTMX, Live, SSE, App, Files, Log, Icon string |
| 27 | // Font is the UI font, preloaded by every page. | 27 | // Font is the UI font, preloaded by every page. | ||
| 28 | Font string | 28 | Font string | ||
| 29 | // Rules is the speculation rules file named by the Speculation-Rules header. | 29 | // Rules is the speculation rules file named by the Speculation-Rules header. | ||
| @@ -56,6 +56,7 @@ var scripts = []struct { | |||||
| 56 | {"hx-sse.min.js", func(a *Assets, u string) { a.SSE = u }}, | 56 | {"hx-sse.min.js", func(a *Assets, u string) { a.SSE = u }}, | ||
| 57 | {"ermite.js", func(a *Assets, u string) { a.App = u }}, | 57 | {"ermite.js", func(a *Assets, u string) { a.App = u }}, | ||
| 58 | {"files.js", func(a *Assets, u string) { a.Files = u }}, | 58 | {"files.js", func(a *Assets, u string) { a.Files = u }}, | ||
| 59 | + | {"log.js", func(a *Assets, u string) { a.Log = u }}, | |||
| 59 | } | 60 | } | ||
| 60 | 61 | ||||
| 61 | // sheetAssets are the files the stylesheet references as url(<dir>/<file>): | 62 | // sheetAssets are the files the stylesheet references as url(<dir>/<file>): | ||
A internal/adapters/web/jstest/log.test.mjs +103 -0
| @@ -0,0 +1,103 @@ | |||
| 1 | + | // Tests of the live job log of static/js/log.js (window.ermiteLog) with a stub | |
| 2 | + | // EventSource on the tiny hand-rolled DOM. Run with | |
| 3 | + | // node --test 'internal/adapters/web/jstest/*.test.mjs' | |
| 4 | + | import assert from 'node:assert/strict'; | |
| 5 | + | import { test } from 'node:test'; | |
| 6 | + | import { el, installDom } from './dom.mjs'; | |
| 7 | + | ||
| 8 | + | // A line is "text" or "+text" for a piece that continues the line before it. | |
| 9 | + | function line(text) { | |
| 10 | + | const cont = text.startsWith('+'); | |
| 11 | + | const li = el('li', { cls: 'lg', attrs: cont ? { 'data-cont': '' } : {} }, el('span', { cls: 't', text: cont ? text.slice(1) : text })); | |
| 12 | + | return li; | |
| 13 | + | } | |
| 14 | + | ||
| 15 | + | installDom({ parseHTML: (data) => data.split('|').filter(Boolean).map(line) }); | |
| 16 | + | globalThis.document.addEventListener = () => {}; // log.js starts itself on DOMContentLoaded | |
| 17 | + | ||
| 18 | + | class StubEventSource { | |
| 19 | + | static CLOSED = 2; | |
| 20 | + | static last = null; | |
| 21 | + | constructor(url) { | |
| 22 | + | this.url = url; | |
| 23 | + | this.readyState = 1; | |
| 24 | + | this.handlers = new Map(); | |
| 25 | + | StubEventSource.last = this; | |
| 26 | + | } | |
| 27 | + | addEventListener(name, fn) { this.handlers.set(name, fn); } | |
| 28 | + | close() { this.readyState = StubEventSource.CLOSED; } | |
| 29 | + | emit(name, data) { this.handlers.get(name)?.({ data }); } | |
| 30 | + | } | |
| 31 | + | globalThis.EventSource = StubEventSource; | |
| 32 | + | ||
| 33 | + | let scrolls = []; | |
| 34 | + | globalThis.scrollTo = (x, y) => scrolls.push(y); | |
| 35 | + | Object.assign(globalThis, { innerHeight: 800, scrollY: 0 }); | |
| 36 | + | ||
| 37 | + | await import('../static/js/log.js'); | |
| 38 | + | const { follow } = window.ermiteLog; | |
| 39 | + | ||
| 40 | + | // page makes a job page body: the box with its list and the lines already there. | |
| 41 | + | function page(attrs, ...lines) { | |
| 42 | + | const list = el('ol', { cls: 'log' }, ...lines.map(line)); | |
| 43 | + | const box = el('div', { id: 'job-log', attrs }, list); | |
| 44 | + | const body = document.body; | |
| 45 | + | body.replaceChildren(box); | |
| 46 | + | Object.defineProperty(globalThis.document.body, 'scrollHeight', { value: 1000, configurable: true }); | |
| 47 | + | return { box, list }; | |
| 48 | + | } | |
| 49 | + | const running = { 'data-follow': '', 'data-stream': '/r/job/stream', 'data-next': '3' }; | |
| 50 | + | const texts = (list) => list.children.map((li) => li.querySelector('.t').textContent); | |
| 51 | + | ||
| 52 | + | test('it does nothing for a job that is over, or a page without a feed', () => { | |
| 53 | + | assert.equal(follow(null), null); | |
| 54 | + | assert.equal(follow(page({ 'data-stream': '/s' }).box), null, 'no data-follow: the job ended'); | |
| 55 | + | assert.equal(follow(page({ 'data-follow': '' }).box), null, 'no stream URL'); | |
| 56 | + | }); | |
| 57 | + | ||
| 58 | + | test('it opens the feed after the last line the page holds', () => { | |
| 59 | + | const { box } = page(running, 'a', 'b', 'c'); | |
| 60 | + | const es = follow(box); | |
| 61 | + | assert.equal(es.url, '/r/job/stream?from=3'); | |
| 62 | + | }); | |
| 63 | + | ||
| 64 | + | test('it appends the lines of a batch', () => { | |
| 65 | + | const { box, list } = page(running, 'a'); | |
| 66 | + | const es = follow(box); | |
| 67 | + | es.emit('ci-log', 'b|c'); | |
| 68 | + | es.emit('ci-log', 'd'); | |
| 69 | + | assert.deepEqual(texts(list), ['a', 'b', 'c', 'd']); | |
| 70 | + | }); | |
| 71 | + | ||
| 72 | + | test('a piece that continues a line of an earlier batch joins that line', () => { | |
| 73 | + | const { box, list } = page(running, 'start of a long'); | |
| 74 | + | const es = follow(box); | |
| 75 | + | es.emit('ci-log', '+ line|next'); | |
| 76 | + | assert.deepEqual(texts(list), ['start of a long line', 'next']); | |
| 77 | + | }); | |
| 78 | + | ||
| 79 | + | test('it scrolls with the log only while the reader is at its end', () => { | |
| 80 | + | const { box } = page(running, 'a'); | |
| 81 | + | const es = follow(box); | |
| 82 | + | scrolls = []; | |
| 83 | + | Object.assign(globalThis, { scrollY: 190 }); // 800 + 190 >= 1000 - 80: at the end | |
| 84 | + | es.emit('ci-log', 'b'); | |
| 85 | + | assert.deepEqual(scrolls, [1000]); | |
| 86 | + | scrolls = []; | |
| 87 | + | Object.assign(globalThis, { scrollY: 0 }); // reading above | |
| 88 | + | es.emit('ci-log', 'c'); | |
| 89 | + | assert.deepEqual(scrolls, []); | |
| 90 | + | }); | |
| 91 | + | ||
| 92 | + | test('ci-end closes the feed, and a closed connection stays closed', () => { | |
| 93 | + | const { box } = page(running, 'a'); | |
| 94 | + | const es = follow(box); | |
| 95 | + | assert.equal(es.readyState, 1); | |
| 96 | + | es.emit('ci-end', 'end'); | |
| 97 | + | assert.equal(es.readyState, StubEventSource.CLOSED); | |
| 98 | + | ||
| 99 | + | const again = follow(page(running, 'a').box); | |
| 100 | + | again.readyState = StubEventSource.CLOSED; | |
| 101 | + | again.onerror(); | |
| 102 | + | assert.equal(again.readyState, StubEventSource.CLOSED); | |
| 103 | + | }); | |
| @@ -0,0 +1,103 @@ | |||||
| 1 | + | // Tests of the live job log of static/js/log.js (window.ermiteLog) with a stub | |||
| 2 | + | // EventSource on the tiny hand-rolled DOM. Run with | |||
| 3 | + | // node --test 'internal/adapters/web/jstest/*.test.mjs' | |||
| 4 | + | import assert from 'node:assert/strict'; | |||
| 5 | + | import { test } from 'node:test'; | |||
| 6 | + | import { el, installDom } from './dom.mjs'; | |||
| 7 | + | ||||
| 8 | + | // A line is "text" or "+text" for a piece that continues the line before it. | |||
| 9 | + | function line(text) { | |||
| 10 | + | const cont = text.startsWith('+'); | |||
| 11 | + | const li = el('li', { cls: 'lg', attrs: cont ? { 'data-cont': '' } : {} }, el('span', { cls: 't', text: cont ? text.slice(1) : text })); | |||
| 12 | + | return li; | |||
| 13 | + | } | |||
| 14 | + | ||||
| 15 | + | installDom({ parseHTML: (data) => data.split('|').filter(Boolean).map(line) }); | |||
| 16 | + | globalThis.document.addEventListener = () => {}; // log.js starts itself on DOMContentLoaded | |||
| 17 | + | ||||
| 18 | + | class StubEventSource { | |||
| 19 | + | static CLOSED = 2; | |||
| 20 | + | static last = null; | |||
| 21 | + | constructor(url) { | |||
| 22 | + | this.url = url; | |||
| 23 | + | this.readyState = 1; | |||
| 24 | + | this.handlers = new Map(); | |||
| 25 | + | StubEventSource.last = this; | |||
| 26 | + | } | |||
| 27 | + | addEventListener(name, fn) { this.handlers.set(name, fn); } | |||
| 28 | + | close() { this.readyState = StubEventSource.CLOSED; } | |||
| 29 | + | emit(name, data) { this.handlers.get(name)?.({ data }); } | |||
| 30 | + | } | |||
| 31 | + | globalThis.EventSource = StubEventSource; | |||
| 32 | + | ||||
| 33 | + | let scrolls = []; | |||
| 34 | + | globalThis.scrollTo = (x, y) => scrolls.push(y); | |||
| 35 | + | Object.assign(globalThis, { innerHeight: 800, scrollY: 0 }); | |||
| 36 | + | ||||
| 37 | + | await import('../static/js/log.js'); | |||
| 38 | + | const { follow } = window.ermiteLog; | |||
| 39 | + | ||||
| 40 | + | // page makes a job page body: the box with its list and the lines already there. | |||
| 41 | + | function page(attrs, ...lines) { | |||
| 42 | + | const list = el('ol', { cls: 'log' }, ...lines.map(line)); | |||
| 43 | + | const box = el('div', { id: 'job-log', attrs }, list); | |||
| 44 | + | const body = document.body; | |||
| 45 | + | body.replaceChildren(box); | |||
| 46 | + | Object.defineProperty(globalThis.document.body, 'scrollHeight', { value: 1000, configurable: true }); | |||
| 47 | + | return { box, list }; | |||
| 48 | + | } | |||
| 49 | + | const running = { 'data-follow': '', 'data-stream': '/r/job/stream', 'data-next': '3' }; | |||
| 50 | + | const texts = (list) => list.children.map((li) => li.querySelector('.t').textContent); | |||
| 51 | + | ||||
| 52 | + | test('it does nothing for a job that is over, or a page without a feed', () => { | |||
| 53 | + | assert.equal(follow(null), null); | |||
| 54 | + | assert.equal(follow(page({ 'data-stream': '/s' }).box), null, 'no data-follow: the job ended'); | |||
| 55 | + | assert.equal(follow(page({ 'data-follow': '' }).box), null, 'no stream URL'); | |||
| 56 | + | }); | |||
| 57 | + | ||||
| 58 | + | test('it opens the feed after the last line the page holds', () => { | |||
| 59 | + | const { box } = page(running, 'a', 'b', 'c'); | |||
| 60 | + | const es = follow(box); | |||
| 61 | + | assert.equal(es.url, '/r/job/stream?from=3'); | |||
| 62 | + | }); | |||
| 63 | + | ||||
| 64 | + | test('it appends the lines of a batch', () => { | |||
| 65 | + | const { box, list } = page(running, 'a'); | |||
| 66 | + | const es = follow(box); | |||
| 67 | + | es.emit('ci-log', 'b|c'); | |||
| 68 | + | es.emit('ci-log', 'd'); | |||
| 69 | + | assert.deepEqual(texts(list), ['a', 'b', 'c', 'd']); | |||
| 70 | + | }); | |||
| 71 | + | ||||
| 72 | + | test('a piece that continues a line of an earlier batch joins that line', () => { | |||
| 73 | + | const { box, list } = page(running, 'start of a long'); | |||
| 74 | + | const es = follow(box); | |||
| 75 | + | es.emit('ci-log', '+ line|next'); | |||
| 76 | + | assert.deepEqual(texts(list), ['start of a long line', 'next']); | |||
| 77 | + | }); | |||
| 78 | + | ||||
| 79 | + | test('it scrolls with the log only while the reader is at its end', () => { | |||
| 80 | + | const { box } = page(running, 'a'); | |||
| 81 | + | const es = follow(box); | |||
| 82 | + | scrolls = []; | |||
| 83 | + | Object.assign(globalThis, { scrollY: 190 }); // 800 + 190 >= 1000 - 80: at the end | |||
| 84 | + | es.emit('ci-log', 'b'); | |||
| 85 | + | assert.deepEqual(scrolls, [1000]); | |||
| 86 | + | scrolls = []; | |||
| 87 | + | Object.assign(globalThis, { scrollY: 0 }); // reading above | |||
| 88 | + | es.emit('ci-log', 'c'); | |||
| 89 | + | assert.deepEqual(scrolls, []); | |||
| 90 | + | }); | |||
| 91 | + | ||||
| 92 | + | test('ci-end closes the feed, and a closed connection stays closed', () => { | |||
| 93 | + | const { box } = page(running, 'a'); | |||
| 94 | + | const es = follow(box); | |||
| 95 | + | assert.equal(es.readyState, 1); | |||
| 96 | + | es.emit('ci-end', 'end'); | |||
| 97 | + | assert.equal(es.readyState, StubEventSource.CLOSED); | |||
| 98 | + | ||||
| 99 | + | const again = follow(page(running, 'a').box); | |||
| 100 | + | again.readyState = StubEventSource.CLOSED; | |||
| 101 | + | again.onerror(); | |||
| 102 | + | assert.equal(again.readyState, StubEventSource.CLOSED); | |||
| 103 | + | }); | |||
M internal/adapters/web/mr.go +2 -2
| @@ -459,9 +459,9 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ | |||
| 459 | 459 | } | |
| 460 | 460 | p.ChecksBlock = d.repo.Settings.FailingChecksBlock | |
| 461 | 461 | if open && head != "" { | |
| 462 | - | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head), now) | |
| 462 | + | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head)) | |
| 463 | 463 | } else if head != "" { | |
| 464 | - | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head), now) | |
| 464 | + | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head)) | |
| 465 | 465 | p.Checks.Note = "" | |
| 466 | 466 | } | |
| 467 | 467 | p.Note = diffNote(res.Diff, len(p.Files), p.NFiles) | |
| @@ -459,9 +459,9 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ | |||||
| 459 | } | 459 | } | ||
| 460 | p.ChecksBlock = d.repo.Settings.FailingChecksBlock | 460 | p.ChecksBlock = d.repo.Settings.FailingChecksBlock | ||
| 461 | if open && head != "" { | 461 | if open && head != "" { | ||
| 462 | - | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head), now) | 462 | + | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head)) |
| 463 | } else if head != "" { | 463 | } else if head != "" { | ||
| 464 | - | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head), now) | 464 | + | p.Checks = checksSection(d.repo.Repo, s.checksFor(ctx, d.repo.Name, head)) |
| 465 | p.Checks.Note = "" | 465 | p.Checks.Note = "" | ||
| 466 | } | 466 | } | ||
| 467 | p.Note = diffNote(res.Diff, len(p.Files), p.NFiles) | 467 | p.Note = diffNote(res.Diff, len(p.Files), p.NFiles) | ||
M internal/adapters/web/shell.templ +1 -0
| @@ -22,6 +22,7 @@ templ Shell(l Layout, rail templ.Component) { | |||
| 22 | 22 | <script src={ l.Assets.SSE } defer></script> | |
| 23 | 23 | <script src={ l.Assets.App } defer></script> | |
| 24 | 24 | <script src={ l.Assets.Files } defer></script> | |
| 25 | + | <script src={ l.Assets.Log } defer></script> | |
| 25 | 26 | </head> | |
| 26 | 27 | <body | |
| 27 | 28 | data-ref={ l.RefPath } | |
| @@ -22,6 +22,7 @@ templ Shell(l Layout, rail templ.Component) { | |||||
| 22 | <script src={ l.Assets.SSE } defer></script> | 22 | <script src={ l.Assets.SSE } defer></script> | ||
| 23 | <script src={ l.Assets.App } defer></script> | 23 | <script src={ l.Assets.App } defer></script> | ||
| 24 | <script src={ l.Assets.Files } defer></script> | 24 | <script src={ l.Assets.Files } defer></script> | ||
| 25 | + | <script src={ l.Assets.Log } defer></script> | |||
| 25 | </head> | 26 | </head> | ||
| 26 | <body | 27 | <body | ||
| 27 | data-ref={ l.RefPath } | 28 | data-ref={ l.RefPath } | ||
M internal/adapters/web/shell_templ.go +155 -142
| @@ -151,69 +151,82 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 151 | 151 | if templ_7745c5c3_Err != nil { | |
| 152 | 152 | return templ_7745c5c3_Err | |
| 153 | 153 | } | |
| 154 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" defer></script></head><body data-ref=\"") | |
| 154 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" defer></script><script src=\"") | |
| 155 | 155 | if templ_7745c5c3_Err != nil { | |
| 156 | 156 | return templ_7745c5c3_Err | |
| 157 | 157 | } | |
| 158 | 158 | var templ_7745c5c3_Var11 string | |
| 159 | - | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | |
| 159 | + | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) | |
| 160 | 160 | if templ_7745c5c3_Err != nil { | |
| 161 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 27, Col: 23} | |
| 161 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 29} | |
| 162 | 162 | } | |
| 163 | 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | |
| 164 | 164 | if templ_7745c5c3_Err != nil { | |
| 165 | 165 | return templ_7745c5c3_Err | |
| 166 | 166 | } | |
| 167 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" data-sha=\"") | |
| 167 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" defer></script></head><body data-ref=\"") | |
| 168 | 168 | if templ_7745c5c3_Err != nil { | |
| 169 | 169 | return templ_7745c5c3_Err | |
| 170 | 170 | } | |
| 171 | 171 | var templ_7745c5c3_Var12 string | |
| 172 | - | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |
| 172 | + | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | |
| 173 | 173 | if templ_7745c5c3_Err != nil { | |
| 174 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 19} | |
| 174 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 23} | |
| 175 | 175 | } | |
| 176 | 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | |
| 177 | 177 | if templ_7745c5c3_Err != nil { | |
| 178 | 178 | return templ_7745c5c3_Err | |
| 179 | 179 | } | |
| 180 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-sel=\"\" data-nav=\"\"") | |
| 180 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-sha=\"") | |
| 181 | + | if templ_7745c5c3_Err != nil { | |
| 182 | + | return templ_7745c5c3_Err | |
| 183 | + | } | |
| 184 | + | var templ_7745c5c3_Var13 string | |
| 185 | + | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |
| 186 | + | if templ_7745c5c3_Err != nil { | |
| 187 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 19} | |
| 188 | + | } | |
| 189 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | |
| 190 | + | if templ_7745c5c3_Err != nil { | |
| 191 | + | return templ_7745c5c3_Err | |
| 192 | + | } | |
| 193 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" data-sel=\"\" data-nav=\"\"") | |
| 181 | 194 | if templ_7745c5c3_Err != nil { | |
| 182 | 195 | return templ_7745c5c3_Err | |
| 183 | 196 | } | |
| 184 | 197 | if l.PathsURL != "" { | |
| 185 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " data-paths=\"") | |
| 198 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " data-paths=\"") | |
| 186 | 199 | if templ_7745c5c3_Err != nil { | |
| 187 | 200 | return templ_7745c5c3_Err | |
| 188 | 201 | } | |
| 189 | - | var templ_7745c5c3_Var13 string | |
| 190 | - | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | |
| 202 | + | var templ_7745c5c3_Var14 string | |
| 203 | + | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | |
| 191 | 204 | if templ_7745c5c3_Err != nil { | |
| 192 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 32, Col: 27} | |
| 205 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 33, Col: 27} | |
| 193 | 206 | } | |
| 194 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | |
| 207 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | |
| 195 | 208 | if templ_7745c5c3_Err != nil { | |
| 196 | 209 | return templ_7745c5c3_Err | |
| 197 | 210 | } | |
| 198 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" data-blob=\"") | |
| 211 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-blob=\"") | |
| 199 | 212 | if templ_7745c5c3_Err != nil { | |
| 200 | 213 | return templ_7745c5c3_Err | |
| 201 | 214 | } | |
| 202 | - | var templ_7745c5c3_Var14 string | |
| 203 | - | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | |
| 215 | + | var templ_7745c5c3_Var15 string | |
| 216 | + | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | |
| 204 | 217 | if templ_7745c5c3_Err != nil { | |
| 205 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 33, Col: 26} | |
| 218 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 26} | |
| 206 | 219 | } | |
| 207 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | |
| 220 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) | |
| 208 | 221 | if templ_7745c5c3_Err != nil { | |
| 209 | 222 | return templ_7745c5c3_Err | |
| 210 | 223 | } | |
| 211 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"") | |
| 224 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"") | |
| 212 | 225 | if templ_7745c5c3_Err != nil { | |
| 213 | 226 | return templ_7745c5c3_Err | |
| 214 | 227 | } | |
| 215 | 228 | } | |
| 216 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "><a class=\"skip\" href=\"#main\">skip to content</a>") | |
| 229 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "><a class=\"skip\" href=\"#main\">skip to content</a>") | |
| 217 | 230 | if templ_7745c5c3_Err != nil { | |
| 218 | 231 | return templ_7745c5c3_Err | |
| 219 | 232 | } | |
| @@ -221,48 +234,48 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 221 | 234 | if templ_7745c5c3_Err != nil { | |
| 222 | 235 | return templ_7745c5c3_Err | |
| 223 | 236 | } | |
| 224 | - | var templ_7745c5c3_Var15 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | |
| 225 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...) | |
| 237 | + | var templ_7745c5c3_Var16 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | |
| 238 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...) | |
| 226 | 239 | if templ_7745c5c3_Err != nil { | |
| 227 | 240 | return templ_7745c5c3_Err | |
| 228 | 241 | } | |
| 229 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"") | |
| 242 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"") | |
| 230 | 243 | if templ_7745c5c3_Err != nil { | |
| 231 | 244 | return templ_7745c5c3_Err | |
| 232 | 245 | } | |
| 233 | - | var templ_7745c5c3_Var16 string | |
| 234 | - | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var15).String()) | |
| 246 | + | var templ_7745c5c3_Var17 string | |
| 247 | + | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String()) | |
| 235 | 248 | if templ_7745c5c3_Err != nil { | |
| 236 | 249 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | |
| 237 | 250 | } | |
| 238 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | |
| 251 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) | |
| 239 | 252 | if templ_7745c5c3_Err != nil { | |
| 240 | 253 | return templ_7745c5c3_Err | |
| 241 | 254 | } | |
| 242 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">") | |
| 255 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\">") | |
| 243 | 256 | if templ_7745c5c3_Err != nil { | |
| 244 | 257 | return templ_7745c5c3_Err | |
| 245 | 258 | } | |
| 246 | 259 | if l.Lede != "" { | |
| 247 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | |
| 260 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | |
| 248 | 261 | if templ_7745c5c3_Err != nil { | |
| 249 | 262 | return templ_7745c5c3_Err | |
| 250 | 263 | } | |
| 251 | - | var templ_7745c5c3_Var17 string | |
| 252 | - | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | |
| 264 | + | var templ_7745c5c3_Var18 string | |
| 265 | + | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | |
| 253 | 266 | if templ_7745c5c3_Err != nil { | |
| 254 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 40, Col: 81} | |
| 267 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 41, Col: 81} | |
| 255 | 268 | } | |
| 256 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) | |
| 269 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) | |
| 257 | 270 | if templ_7745c5c3_Err != nil { | |
| 258 | 271 | return templ_7745c5c3_Err | |
| 259 | 272 | } | |
| 260 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</p></section>") | |
| 273 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p></section>") | |
| 261 | 274 | if templ_7745c5c3_Err != nil { | |
| 262 | 275 | return templ_7745c5c3_Err | |
| 263 | 276 | } | |
| 264 | 277 | } | |
| 265 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<main id=\"main\" tabindex=\"-1\">") | |
| 278 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<main id=\"main\" tabindex=\"-1\">") | |
| 266 | 279 | if templ_7745c5c3_Err != nil { | |
| 267 | 280 | return templ_7745c5c3_Err | |
| 268 | 281 | } | |
| @@ -270,12 +283,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 270 | 283 | if templ_7745c5c3_Err != nil { | |
| 271 | 284 | return templ_7745c5c3_Err | |
| 272 | 285 | } | |
| 273 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</main>") | |
| 286 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</main>") | |
| 274 | 287 | if templ_7745c5c3_Err != nil { | |
| 275 | 288 | return templ_7745c5c3_Err | |
| 276 | 289 | } | |
| 277 | 290 | if l.HasRail && rail != nil { | |
| 278 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<aside class=\"rail\" aria-label=\"Context\">") | |
| 291 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<aside class=\"rail\" aria-label=\"Context\">") | |
| 279 | 292 | if templ_7745c5c3_Err != nil { | |
| 280 | 293 | return templ_7745c5c3_Err | |
| 281 | 294 | } | |
| @@ -283,12 +296,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 283 | 296 | if templ_7745c5c3_Err != nil { | |
| 284 | 297 | return templ_7745c5c3_Err | |
| 285 | 298 | } | |
| 286 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</aside>") | |
| 299 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</aside>") | |
| 287 | 300 | if templ_7745c5c3_Err != nil { | |
| 288 | 301 | return templ_7745c5c3_Err | |
| 289 | 302 | } | |
| 290 | 303 | } | |
| 291 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div>") | |
| 304 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div>") | |
| 292 | 305 | if templ_7745c5c3_Err != nil { | |
| 293 | 306 | return templ_7745c5c3_Err | |
| 294 | 307 | } | |
| @@ -316,7 +329,7 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 316 | 329 | if templ_7745c5c3_Err != nil { | |
| 317 | 330 | return templ_7745c5c3_Err | |
| 318 | 331 | } | |
| 319 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</body></html>") | |
| 332 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</body></html>") | |
| 320 | 333 | if templ_7745c5c3_Err != nil { | |
| 321 | 334 | return templ_7745c5c3_Err | |
| 322 | 335 | } | |
| @@ -342,273 +355,273 @@ func Header(l Layout) templ.Component { | |||
| 342 | 355 | }() | |
| 343 | 356 | } | |
| 344 | 357 | ctx = templ.InitializeContext(ctx) | |
| 345 | - | templ_7745c5c3_Var18 := templ.GetChildren(ctx) | |
| 346 | - | if templ_7745c5c3_Var18 == nil { | |
| 347 | - | templ_7745c5c3_Var18 = templ.NopComponent | |
| 358 | + | templ_7745c5c3_Var19 := templ.GetChildren(ctx) | |
| 359 | + | if templ_7745c5c3_Var19 == nil { | |
| 360 | + | templ_7745c5c3_Var19 = templ.NopComponent | |
| 348 | 361 | } | |
| 349 | 362 | ctx = templ.ClearChildren(ctx) | |
| 350 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") | |
| 363 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") | |
| 351 | 364 | if templ_7745c5c3_Err != nil { | |
| 352 | 365 | return templ_7745c5c3_Err | |
| 353 | 366 | } | |
| 354 | - | var templ_7745c5c3_Var19 string | |
| 355 | - | templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) | |
| 367 | + | var templ_7745c5c3_Var20 string | |
| 368 | + | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) | |
| 356 | 369 | if templ_7745c5c3_Err != nil { | |
| 357 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 68, Col: 41} | |
| 370 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 69, Col: 41} | |
| 358 | 371 | } | |
| 359 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) | |
| 372 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) | |
| 360 | 373 | if templ_7745c5c3_Err != nil { | |
| 361 | 374 | return templ_7745c5c3_Err | |
| 362 | 375 | } | |
| 363 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</span></a> ") | |
| 376 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</span></a> ") | |
| 364 | 377 | if templ_7745c5c3_Err != nil { | |
| 365 | 378 | return templ_7745c5c3_Err | |
| 366 | 379 | } | |
| 367 | 380 | if len(l.Crumbs) > 0 { | |
| 368 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | |
| 381 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | |
| 369 | 382 | if templ_7745c5c3_Err != nil { | |
| 370 | 383 | return templ_7745c5c3_Err | |
| 371 | 384 | } | |
| 372 | 385 | for _, c := range l.Crumbs { | |
| 373 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<li>") | |
| 386 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<li>") | |
| 374 | 387 | if templ_7745c5c3_Err != nil { | |
| 375 | 388 | return templ_7745c5c3_Err | |
| 376 | 389 | } | |
| 377 | 390 | if c.Href != "" { | |
| 378 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<a href=\"") | |
| 391 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<a href=\"") | |
| 379 | 392 | if templ_7745c5c3_Err != nil { | |
| 380 | 393 | return templ_7745c5c3_Err | |
| 381 | 394 | } | |
| 382 | - | var templ_7745c5c3_Var20 templ.SafeURL | |
| 383 | - | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | |
| 395 | + | var templ_7745c5c3_Var21 templ.SafeURL | |
| 396 | + | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | |
| 384 | 397 | if templ_7745c5c3_Err != nil { | |
| 385 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 77, Col: 38} | |
| 398 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 78, Col: 38} | |
| 386 | 399 | } | |
| 387 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) | |
| 400 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | |
| 388 | 401 | if templ_7745c5c3_Err != nil { | |
| 389 | 402 | return templ_7745c5c3_Err | |
| 390 | 403 | } | |
| 391 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"") | |
| 404 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"") | |
| 392 | 405 | if templ_7745c5c3_Err != nil { | |
| 393 | 406 | return templ_7745c5c3_Err | |
| 394 | 407 | } | |
| 395 | 408 | if c.Current { | |
| 396 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " aria-current=\"page\"") | |
| 409 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " aria-current=\"page\"") | |
| 397 | 410 | if templ_7745c5c3_Err != nil { | |
| 398 | 411 | return templ_7745c5c3_Err | |
| 399 | 412 | } | |
| 400 | 413 | } | |
| 401 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, ">") | |
| 414 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, ">") | |
| 402 | 415 | if templ_7745c5c3_Err != nil { | |
| 403 | 416 | return templ_7745c5c3_Err | |
| 404 | 417 | } | |
| 405 | - | var templ_7745c5c3_Var21 string | |
| 406 | - | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 418 | + | var templ_7745c5c3_Var22 string | |
| 419 | + | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 407 | 420 | if templ_7745c5c3_Err != nil { | |
| 408 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 81, Col: 19} | |
| 421 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 82, Col: 19} | |
| 409 | 422 | } | |
| 410 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | |
| 423 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | |
| 411 | 424 | if templ_7745c5c3_Err != nil { | |
| 412 | 425 | return templ_7745c5c3_Err | |
| 413 | 426 | } | |
| 414 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</a>") | |
| 427 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</a>") | |
| 415 | 428 | if templ_7745c5c3_Err != nil { | |
| 416 | 429 | return templ_7745c5c3_Err | |
| 417 | 430 | } | |
| 418 | 431 | } else { | |
| 419 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<span") | |
| 432 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<span") | |
| 420 | 433 | if templ_7745c5c3_Err != nil { | |
| 421 | 434 | return templ_7745c5c3_Err | |
| 422 | 435 | } | |
| 423 | 436 | if c.Current { | |
| 424 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " aria-current=\"page\"") | |
| 437 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " aria-current=\"page\"") | |
| 425 | 438 | if templ_7745c5c3_Err != nil { | |
| 426 | 439 | return templ_7745c5c3_Err | |
| 427 | 440 | } | |
| 428 | 441 | } | |
| 429 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, ">") | |
| 442 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, ">") | |
| 430 | 443 | if templ_7745c5c3_Err != nil { | |
| 431 | 444 | return templ_7745c5c3_Err | |
| 432 | 445 | } | |
| 433 | - | var templ_7745c5c3_Var22 string | |
| 434 | - | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 446 | + | var templ_7745c5c3_Var23 string | |
| 447 | + | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 435 | 448 | if templ_7745c5c3_Err != nil { | |
| 436 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 87, Col: 19} | |
| 449 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 88, Col: 19} | |
| 437 | 450 | } | |
| 438 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | |
| 451 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | |
| 439 | 452 | if templ_7745c5c3_Err != nil { | |
| 440 | 453 | return templ_7745c5c3_Err | |
| 441 | 454 | } | |
| 442 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</span>") | |
| 455 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</span>") | |
| 443 | 456 | if templ_7745c5c3_Err != nil { | |
| 444 | 457 | return templ_7745c5c3_Err | |
| 445 | 458 | } | |
| 446 | 459 | } | |
| 447 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</li>") | |
| 460 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</li>") | |
| 448 | 461 | if templ_7745c5c3_Err != nil { | |
| 449 | 462 | return templ_7745c5c3_Err | |
| 450 | 463 | } | |
| 451 | 464 | } | |
| 452 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</ol></nav>") | |
| 465 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</ol></nav>") | |
| 453 | 466 | if templ_7745c5c3_Err != nil { | |
| 454 | 467 | return templ_7745c5c3_Err | |
| 455 | 468 | } | |
| 456 | 469 | } | |
| 457 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"hdr-acts\">") | |
| 470 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<div class=\"hdr-acts\">") | |
| 458 | 471 | if templ_7745c5c3_Err != nil { | |
| 459 | 472 | return templ_7745c5c3_Err | |
| 460 | 473 | } | |
| 461 | 474 | if l.Ref != "" { | |
| 462 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<a class=\"btn ref\" href=\"") | |
| 475 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<a class=\"btn ref\" href=\"") | |
| 463 | 476 | if templ_7745c5c3_Err != nil { | |
| 464 | 477 | return templ_7745c5c3_Err | |
| 465 | 478 | } | |
| 466 | - | var templ_7745c5c3_Var23 templ.SafeURL | |
| 467 | - | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | |
| 479 | + | var templ_7745c5c3_Var24 templ.SafeURL | |
| 480 | + | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | |
| 468 | 481 | if templ_7745c5c3_Err != nil { | |
| 469 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 98, Col: 52} | |
| 482 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 99, Col: 52} | |
| 470 | 483 | } | |
| 471 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | |
| 484 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | |
| 472 | 485 | if templ_7745c5c3_Err != nil { | |
| 473 | 486 | return templ_7745c5c3_Err | |
| 474 | 487 | } | |
| 475 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | |
| 488 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | |
| 476 | 489 | if templ_7745c5c3_Err != nil { | |
| 477 | 490 | return templ_7745c5c3_Err | |
| 478 | 491 | } | |
| 479 | - | var templ_7745c5c3_Var24 string | |
| 480 | - | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | |
| 492 | + | var templ_7745c5c3_Var25 string | |
| 493 | + | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | |
| 481 | 494 | if templ_7745c5c3_Err != nil { | |
| 482 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 104, Col: 36} | |
| 495 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 105, Col: 36} | |
| 483 | 496 | } | |
| 484 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | |
| 497 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | |
| 485 | 498 | if templ_7745c5c3_Err != nil { | |
| 486 | 499 | return templ_7745c5c3_Err | |
| 487 | 500 | } | |
| 488 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</span> <span class=\"sha\">") | |
| 501 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span> <span class=\"sha\">") | |
| 489 | 502 | if templ_7745c5c3_Err != nil { | |
| 490 | 503 | return templ_7745c5c3_Err | |
| 491 | 504 | } | |
| 492 | - | var templ_7745c5c3_Var25 string | |
| 493 | - | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | |
| 505 | + | var templ_7745c5c3_Var26 string | |
| 506 | + | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | |
| 494 | 507 | if templ_7745c5c3_Err != nil { | |
| 495 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 105, Col: 33} | |
| 508 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 106, Col: 33} | |
| 496 | 509 | } | |
| 497 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | |
| 510 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | |
| 498 | 511 | if templ_7745c5c3_Err != nil { | |
| 499 | 512 | return templ_7745c5c3_Err | |
| 500 | 513 | } | |
| 501 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span></a> ") | |
| 514 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span></a> ") | |
| 502 | 515 | if templ_7745c5c3_Err != nil { | |
| 503 | 516 | return templ_7745c5c3_Err | |
| 504 | 517 | } | |
| 505 | 518 | } | |
| 506 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") | |
| 519 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") | |
| 507 | 520 | if templ_7745c5c3_Err != nil { | |
| 508 | 521 | return templ_7745c5c3_Err | |
| 509 | 522 | } | |
| 510 | 523 | if SignedIn(ctx) { | |
| 511 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> <button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") | |
| 524 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> <button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") | |
| 512 | 525 | if templ_7745c5c3_Err != nil { | |
| 513 | 526 | return templ_7745c5c3_Err | |
| 514 | 527 | } | |
| 515 | 528 | } | |
| 516 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</div></div>") | |
| 529 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</div></div>") | |
| 517 | 530 | if templ_7745c5c3_Err != nil { | |
| 518 | 531 | return templ_7745c5c3_Err | |
| 519 | 532 | } | |
| 520 | 533 | if len(l.Sections) > 0 { | |
| 521 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | |
| 534 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | |
| 522 | 535 | if templ_7745c5c3_Err != nil { | |
| 523 | 536 | return templ_7745c5c3_Err | |
| 524 | 537 | } | |
| 525 | 538 | for _, s := range l.Sections { | |
| 526 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<li><a href=\"") | |
| 539 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<li><a href=\"") | |
| 527 | 540 | if templ_7745c5c3_Err != nil { | |
| 528 | 541 | return templ_7745c5c3_Err | |
| 529 | 542 | } | |
| 530 | - | var templ_7745c5c3_Var26 templ.SafeURL | |
| 531 | - | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | |
| 543 | + | var templ_7745c5c3_Var27 templ.SafeURL | |
| 544 | + | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | |
| 532 | 545 | if templ_7745c5c3_Err != nil { | |
| 533 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 137, Col: 36} | |
| 546 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 138, Col: 36} | |
| 534 | 547 | } | |
| 535 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | |
| 548 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | |
| 536 | 549 | if templ_7745c5c3_Err != nil { | |
| 537 | 550 | return templ_7745c5c3_Err | |
| 538 | 551 | } | |
| 539 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" data-key=\"") | |
| 552 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" data-key=\"") | |
| 540 | 553 | if templ_7745c5c3_Err != nil { | |
| 541 | 554 | return templ_7745c5c3_Err | |
| 542 | 555 | } | |
| 543 | - | var templ_7745c5c3_Var27 string | |
| 544 | - | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 556 | + | var templ_7745c5c3_Var28 string | |
| 557 | + | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 545 | 558 | if templ_7745c5c3_Err != nil { | |
| 546 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 138, Col: 24} | |
| 559 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 139, Col: 24} | |
| 547 | 560 | } | |
| 548 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27) | |
| 561 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28) | |
| 549 | 562 | if templ_7745c5c3_Err != nil { | |
| 550 | 563 | return templ_7745c5c3_Err | |
| 551 | 564 | } | |
| 552 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" aria-keyshortcuts=\"") | |
| 565 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\" aria-keyshortcuts=\"") | |
| 553 | 566 | if templ_7745c5c3_Err != nil { | |
| 554 | 567 | return templ_7745c5c3_Err | |
| 555 | 568 | } | |
| 556 | - | var templ_7745c5c3_Var28 string | |
| 557 | - | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 569 | + | var templ_7745c5c3_Var29 string | |
| 570 | + | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 558 | 571 | if templ_7745c5c3_Err != nil { | |
| 559 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 139, Col: 33} | |
| 572 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 140, Col: 33} | |
| 560 | 573 | } | |
| 561 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28) | |
| 574 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | |
| 562 | 575 | if templ_7745c5c3_Err != nil { | |
| 563 | 576 | return templ_7745c5c3_Err | |
| 564 | 577 | } | |
| 565 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"") | |
| 578 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\"") | |
| 566 | 579 | if templ_7745c5c3_Err != nil { | |
| 567 | 580 | return templ_7745c5c3_Err | |
| 568 | 581 | } | |
| 569 | 582 | if s.Current { | |
| 570 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, " aria-current=\"page\"") | |
| 583 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " aria-current=\"page\"") | |
| 571 | 584 | if templ_7745c5c3_Err != nil { | |
| 572 | 585 | return templ_7745c5c3_Err | |
| 573 | 586 | } | |
| 574 | 587 | } | |
| 575 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, ">") | |
| 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, ">") | |
| 576 | 589 | if templ_7745c5c3_Err != nil { | |
| 577 | 590 | return templ_7745c5c3_Err | |
| 578 | 591 | } | |
| 579 | - | var templ_7745c5c3_Var29 string | |
| 580 | - | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | |
| 592 | + | var templ_7745c5c3_Var30 string | |
| 593 | + | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | |
| 581 | 594 | if templ_7745c5c3_Err != nil { | |
| 582 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 143, Col: 17} | |
| 595 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 17} | |
| 583 | 596 | } | |
| 584 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | |
| 597 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | |
| 585 | 598 | if templ_7745c5c3_Err != nil { | |
| 586 | 599 | return templ_7745c5c3_Err | |
| 587 | 600 | } | |
| 588 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<kbd aria-hidden=\"true\">") | |
| 601 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<kbd aria-hidden=\"true\">") | |
| 589 | 602 | if templ_7745c5c3_Err != nil { | |
| 590 | 603 | return templ_7745c5c3_Err | |
| 591 | 604 | } | |
| 592 | - | var templ_7745c5c3_Var30 string | |
| 593 | - | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | |
| 605 | + | var templ_7745c5c3_Var31 string | |
| 606 | + | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | |
| 594 | 607 | if templ_7745c5c3_Err != nil { | |
| 595 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 143, Col: 50} | |
| 608 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 50} | |
| 596 | 609 | } | |
| 597 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | |
| 610 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | |
| 598 | 611 | if templ_7745c5c3_Err != nil { | |
| 599 | 612 | return templ_7745c5c3_Err | |
| 600 | 613 | } | |
| 601 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</kbd></a></li>") | |
| 614 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</kbd></a></li>") | |
| 602 | 615 | if templ_7745c5c3_Err != nil { | |
| 603 | 616 | return templ_7745c5c3_Err | |
| 604 | 617 | } | |
| 605 | 618 | } | |
| 606 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</ul></nav>") | |
| 619 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</ul></nav>") | |
| 607 | 620 | if templ_7745c5c3_Err != nil { | |
| 608 | 621 | return templ_7745c5c3_Err | |
| 609 | 622 | } | |
| 610 | 623 | } | |
| 611 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</header>") | |
| 624 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</header>") | |
| 612 | 625 | if templ_7745c5c3_Err != nil { | |
| 613 | 626 | return templ_7745c5c3_Err | |
| 614 | 627 | } | |
| @@ -634,38 +647,38 @@ func Footer(l Layout) templ.Component { | |||
| 634 | 647 | }() | |
| 635 | 648 | } | |
| 636 | 649 | ctx = templ.InitializeContext(ctx) | |
| 637 | - | templ_7745c5c3_Var31 := templ.GetChildren(ctx) | |
| 638 | - | if templ_7745c5c3_Var31 == nil { | |
| 639 | - | templ_7745c5c3_Var31 = templ.NopComponent | |
| 650 | + | templ_7745c5c3_Var32 := templ.GetChildren(ctx) | |
| 651 | + | if templ_7745c5c3_Var32 == nil { | |
| 652 | + | templ_7745c5c3_Var32 = templ.NopComponent | |
| 640 | 653 | } | |
| 641 | 654 | ctx = templ.ClearChildren(ctx) | |
| 642 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<footer class=\"foot\"><span>ermite ") | |
| 655 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<footer class=\"foot\"><span>ermite ") | |
| 643 | 656 | if templ_7745c5c3_Err != nil { | |
| 644 | 657 | return templ_7745c5c3_Err | |
| 645 | 658 | } | |
| 646 | - | var templ_7745c5c3_Var32 string | |
| 647 | - | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | |
| 659 | + | var templ_7745c5c3_Var33 string | |
| 660 | + | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | |
| 648 | 661 | if templ_7745c5c3_Err != nil { | |
| 649 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 156, Col: 26} | |
| 662 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 157, Col: 26} | |
| 650 | 663 | } | |
| 651 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | |
| 664 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) | |
| 652 | 665 | if templ_7745c5c3_Err != nil { | |
| 653 | 666 | return templ_7745c5c3_Err | |
| 654 | 667 | } | |
| 655 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</span> <a href=\"") | |
| 668 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</span> <a href=\"") | |
| 656 | 669 | if templ_7745c5c3_Err != nil { | |
| 657 | 670 | return templ_7745c5c3_Err | |
| 658 | 671 | } | |
| 659 | - | var templ_7745c5c3_Var33 templ.SafeURL | |
| 660 | - | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | |
| 672 | + | var templ_7745c5c3_Var34 templ.SafeURL | |
| 673 | + | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | |
| 661 | 674 | if templ_7745c5c3_Err != nil { | |
| 662 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 157, Col: 38} | |
| 675 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 158, Col: 38} | |
| 663 | 676 | } | |
| 664 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) | |
| 677 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) | |
| 665 | 678 | if templ_7745c5c3_Err != nil { | |
| 666 | 679 | return templ_7745c5c3_Err | |
| 667 | 680 | } | |
| 668 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") | |
| 681 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") | |
| 669 | 682 | if templ_7745c5c3_Err != nil { | |
| 670 | 683 | return templ_7745c5c3_Err | |
| 671 | 684 | } | |
| @@ -693,12 +706,12 @@ func Hint(l Layout) templ.Component { | |||
| 693 | 706 | }() | |
| 694 | 707 | } | |
| 695 | 708 | ctx = templ.InitializeContext(ctx) | |
| 696 | - | templ_7745c5c3_Var34 := templ.GetChildren(ctx) | |
| 697 | - | if templ_7745c5c3_Var34 == nil { | |
| 698 | - | templ_7745c5c3_Var34 = templ.NopComponent | |
| 709 | + | templ_7745c5c3_Var35 := templ.GetChildren(ctx) | |
| 710 | + | if templ_7745c5c3_Var35 == nil { | |
| 711 | + | templ_7745c5c3_Var35 = templ.NopComponent | |
| 699 | 712 | } | |
| 700 | 713 | ctx = templ.ClearChildren(ctx) | |
| 701 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | |
| 714 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | |
| 702 | 715 | if templ_7745c5c3_Err != nil { | |
| 703 | 716 | return templ_7745c5c3_Err | |
| 704 | 717 | } | |
| @@ -706,7 +719,7 @@ func Hint(l Layout) templ.Component { | |||
| 706 | 719 | if templ_7745c5c3_Err != nil { | |
| 707 | 720 | return templ_7745c5c3_Err | |
| 708 | 721 | } | |
| 709 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</div>") | |
| 722 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</div>") | |
| 710 | 723 | if templ_7745c5c3_Err != nil { | |
| 711 | 724 | return templ_7745c5c3_Err | |
| 712 | 725 | } | |
| @@ -151,69 +151,82 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 151 | if templ_7745c5c3_Err != nil { | 151 | if templ_7745c5c3_Err != nil { | ||
| 152 | return templ_7745c5c3_Err | 152 | return templ_7745c5c3_Err | ||
| 153 | } | 153 | } | ||
| 154 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" defer></script></head><body data-ref=\"") | 154 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" defer></script><script src=\"") |
| 155 | if templ_7745c5c3_Err != nil { | 155 | if templ_7745c5c3_Err != nil { | ||
| 156 | return templ_7745c5c3_Err | 156 | return templ_7745c5c3_Err | ||
| 157 | } | 157 | } | ||
| 158 | var templ_7745c5c3_Var11 string | 158 | var templ_7745c5c3_Var11 string | ||
| 159 | - | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | 159 | + | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) |
| 160 | if templ_7745c5c3_Err != nil { | 160 | if templ_7745c5c3_Err != nil { | ||
| 161 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 27, Col: 23} | 161 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 29} |
| 162 | } | 162 | } | ||
| 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | ||
| 164 | if templ_7745c5c3_Err != nil { | 164 | if templ_7745c5c3_Err != nil { | ||
| 165 | return templ_7745c5c3_Err | 165 | return templ_7745c5c3_Err | ||
| 166 | } | 166 | } | ||
| 167 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" data-sha=\"") | 167 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" defer></script></head><body data-ref=\"") |
| 168 | if templ_7745c5c3_Err != nil { | 168 | if templ_7745c5c3_Err != nil { | ||
| 169 | return templ_7745c5c3_Err | 169 | return templ_7745c5c3_Err | ||
| 170 | } | 170 | } | ||
| 171 | var templ_7745c5c3_Var12 string | 171 | var templ_7745c5c3_Var12 string | ||
| 172 | - | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | 172 | + | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) |
| 173 | if templ_7745c5c3_Err != nil { | 173 | if templ_7745c5c3_Err != nil { | ||
| 174 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 19} | 174 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 23} |
| 175 | } | 175 | } | ||
| 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | ||
| 177 | if templ_7745c5c3_Err != nil { | 177 | if templ_7745c5c3_Err != nil { | ||
| 178 | return templ_7745c5c3_Err | 178 | return templ_7745c5c3_Err | ||
| 179 | } | 179 | } | ||
| 180 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-sel=\"\" data-nav=\"\"") | 180 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-sha=\"") |
| 181 | + | if templ_7745c5c3_Err != nil { | |||
| 182 | + | return templ_7745c5c3_Err | |||
| 183 | + | } | |||
| 184 | + | var templ_7745c5c3_Var13 string | |||
| 185 | + | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |||
| 186 | + | if templ_7745c5c3_Err != nil { | |||
| 187 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 19} | |||
| 188 | + | } | |||
| 189 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | |||
| 190 | + | if templ_7745c5c3_Err != nil { | |||
| 191 | + | return templ_7745c5c3_Err | |||
| 192 | + | } | |||
| 193 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" data-sel=\"\" data-nav=\"\"") | |||
| 181 | if templ_7745c5c3_Err != nil { | 194 | if templ_7745c5c3_Err != nil { | ||
| 182 | return templ_7745c5c3_Err | 195 | return templ_7745c5c3_Err | ||
| 183 | } | 196 | } | ||
| 184 | if l.PathsURL != "" { | 197 | if l.PathsURL != "" { | ||
| 185 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " data-paths=\"") | 198 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " data-paths=\"") |
| 186 | if templ_7745c5c3_Err != nil { | 199 | if templ_7745c5c3_Err != nil { | ||
| 187 | return templ_7745c5c3_Err | 200 | return templ_7745c5c3_Err | ||
| 188 | } | 201 | } | ||
| 189 | - | var templ_7745c5c3_Var13 string | 202 | + | var templ_7745c5c3_Var14 string |
| 190 | - | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | 203 | + | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) |
| 191 | if templ_7745c5c3_Err != nil { | 204 | if templ_7745c5c3_Err != nil { | ||
| 192 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 32, Col: 27} | 205 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 33, Col: 27} |
| 193 | } | 206 | } | ||
| 194 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | 207 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) |
| 195 | if templ_7745c5c3_Err != nil { | 208 | if templ_7745c5c3_Err != nil { | ||
| 196 | return templ_7745c5c3_Err | 209 | return templ_7745c5c3_Err | ||
| 197 | } | 210 | } | ||
| 198 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" data-blob=\"") | 211 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-blob=\"") |
| 199 | if templ_7745c5c3_Err != nil { | 212 | if templ_7745c5c3_Err != nil { | ||
| 200 | return templ_7745c5c3_Err | 213 | return templ_7745c5c3_Err | ||
| 201 | } | 214 | } | ||
| 202 | - | var templ_7745c5c3_Var14 string | 215 | + | var templ_7745c5c3_Var15 string |
| 203 | - | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | 216 | + | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) |
| 204 | if templ_7745c5c3_Err != nil { | 217 | if templ_7745c5c3_Err != nil { | ||
| 205 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 33, Col: 26} | 218 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 26} |
| 206 | } | 219 | } | ||
| 207 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | 220 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) |
| 208 | if templ_7745c5c3_Err != nil { | 221 | if templ_7745c5c3_Err != nil { | ||
| 209 | return templ_7745c5c3_Err | 222 | return templ_7745c5c3_Err | ||
| 210 | } | 223 | } | ||
| 211 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"") | 224 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"") |
| 212 | if templ_7745c5c3_Err != nil { | 225 | if templ_7745c5c3_Err != nil { | ||
| 213 | return templ_7745c5c3_Err | 226 | return templ_7745c5c3_Err | ||
| 214 | } | 227 | } | ||
| 215 | } | 228 | } | ||
| 216 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "><a class=\"skip\" href=\"#main\">skip to content</a>") | 229 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "><a class=\"skip\" href=\"#main\">skip to content</a>") |
| 217 | if templ_7745c5c3_Err != nil { | 230 | if templ_7745c5c3_Err != nil { | ||
| 218 | return templ_7745c5c3_Err | 231 | return templ_7745c5c3_Err | ||
| 219 | } | 232 | } | ||
| @@ -221,48 +234,48 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 221 | if templ_7745c5c3_Err != nil { | 234 | if templ_7745c5c3_Err != nil { | ||
| 222 | return templ_7745c5c3_Err | 235 | return templ_7745c5c3_Err | ||
| 223 | } | 236 | } | ||
| 224 | - | var templ_7745c5c3_Var15 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | 237 | + | var templ_7745c5c3_Var16 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} |
| 225 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...) | 238 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...) |
| 226 | if templ_7745c5c3_Err != nil { | 239 | if templ_7745c5c3_Err != nil { | ||
| 227 | return templ_7745c5c3_Err | 240 | return templ_7745c5c3_Err | ||
| 228 | } | 241 | } | ||
| 229 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"") | 242 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"") |
| 230 | if templ_7745c5c3_Err != nil { | 243 | if templ_7745c5c3_Err != nil { | ||
| 231 | return templ_7745c5c3_Err | 244 | return templ_7745c5c3_Err | ||
| 232 | } | 245 | } | ||
| 233 | - | var templ_7745c5c3_Var16 string | 246 | + | var templ_7745c5c3_Var17 string |
| 234 | - | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var15).String()) | 247 | + | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String()) |
| 235 | if templ_7745c5c3_Err != nil { | 248 | if templ_7745c5c3_Err != nil { | ||
| 236 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | 249 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | ||
| 237 | } | 250 | } | ||
| 238 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | 251 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) |
| 239 | if templ_7745c5c3_Err != nil { | 252 | if templ_7745c5c3_Err != nil { | ||
| 240 | return templ_7745c5c3_Err | 253 | return templ_7745c5c3_Err | ||
| 241 | } | 254 | } | ||
| 242 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">") | 255 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\">") |
| 243 | if templ_7745c5c3_Err != nil { | 256 | if templ_7745c5c3_Err != nil { | ||
| 244 | return templ_7745c5c3_Err | 257 | return templ_7745c5c3_Err | ||
| 245 | } | 258 | } | ||
| 246 | if l.Lede != "" { | 259 | if l.Lede != "" { | ||
| 247 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | 260 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") |
| 248 | if templ_7745c5c3_Err != nil { | 261 | if templ_7745c5c3_Err != nil { | ||
| 249 | return templ_7745c5c3_Err | 262 | return templ_7745c5c3_Err | ||
| 250 | } | 263 | } | ||
| 251 | - | var templ_7745c5c3_Var17 string | 264 | + | var templ_7745c5c3_Var18 string |
| 252 | - | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | 265 | + | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) |
| 253 | if templ_7745c5c3_Err != nil { | 266 | if templ_7745c5c3_Err != nil { | ||
| 254 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 40, Col: 81} | 267 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 41, Col: 81} |
| 255 | } | 268 | } | ||
| 256 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) | 269 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) |
| 257 | if templ_7745c5c3_Err != nil { | 270 | if templ_7745c5c3_Err != nil { | ||
| 258 | return templ_7745c5c3_Err | 271 | return templ_7745c5c3_Err | ||
| 259 | } | 272 | } | ||
| 260 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</p></section>") | 273 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p></section>") |
| 261 | if templ_7745c5c3_Err != nil { | 274 | if templ_7745c5c3_Err != nil { | ||
| 262 | return templ_7745c5c3_Err | 275 | return templ_7745c5c3_Err | ||
| 263 | } | 276 | } | ||
| 264 | } | 277 | } | ||
| 265 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<main id=\"main\" tabindex=\"-1\">") | 278 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<main id=\"main\" tabindex=\"-1\">") |
| 266 | if templ_7745c5c3_Err != nil { | 279 | if templ_7745c5c3_Err != nil { | ||
| 267 | return templ_7745c5c3_Err | 280 | return templ_7745c5c3_Err | ||
| 268 | } | 281 | } | ||
| @@ -270,12 +283,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 270 | if templ_7745c5c3_Err != nil { | 283 | if templ_7745c5c3_Err != nil { | ||
| 271 | return templ_7745c5c3_Err | 284 | return templ_7745c5c3_Err | ||
| 272 | } | 285 | } | ||
| 273 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</main>") | 286 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</main>") |
| 274 | if templ_7745c5c3_Err != nil { | 287 | if templ_7745c5c3_Err != nil { | ||
| 275 | return templ_7745c5c3_Err | 288 | return templ_7745c5c3_Err | ||
| 276 | } | 289 | } | ||
| 277 | if l.HasRail && rail != nil { | 290 | if l.HasRail && rail != nil { | ||
| 278 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<aside class=\"rail\" aria-label=\"Context\">") | 291 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<aside class=\"rail\" aria-label=\"Context\">") |
| 279 | if templ_7745c5c3_Err != nil { | 292 | if templ_7745c5c3_Err != nil { | ||
| 280 | return templ_7745c5c3_Err | 293 | return templ_7745c5c3_Err | ||
| 281 | } | 294 | } | ||
| @@ -283,12 +296,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 283 | if templ_7745c5c3_Err != nil { | 296 | if templ_7745c5c3_Err != nil { | ||
| 284 | return templ_7745c5c3_Err | 297 | return templ_7745c5c3_Err | ||
| 285 | } | 298 | } | ||
| 286 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</aside>") | 299 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</aside>") |
| 287 | if templ_7745c5c3_Err != nil { | 300 | if templ_7745c5c3_Err != nil { | ||
| 288 | return templ_7745c5c3_Err | 301 | return templ_7745c5c3_Err | ||
| 289 | } | 302 | } | ||
| 290 | } | 303 | } | ||
| 291 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div>") | 304 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div>") |
| 292 | if templ_7745c5c3_Err != nil { | 305 | if templ_7745c5c3_Err != nil { | ||
| 293 | return templ_7745c5c3_Err | 306 | return templ_7745c5c3_Err | ||
| 294 | } | 307 | } | ||
| @@ -316,7 +329,7 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 316 | if templ_7745c5c3_Err != nil { | 329 | if templ_7745c5c3_Err != nil { | ||
| 317 | return templ_7745c5c3_Err | 330 | return templ_7745c5c3_Err | ||
| 318 | } | 331 | } | ||
| 319 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</body></html>") | 332 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</body></html>") |
| 320 | if templ_7745c5c3_Err != nil { | 333 | if templ_7745c5c3_Err != nil { | ||
| 321 | return templ_7745c5c3_Err | 334 | return templ_7745c5c3_Err | ||
| 322 | } | 335 | } | ||
| @@ -342,273 +355,273 @@ func Header(l Layout) templ.Component { | |||||
| 342 | }() | 355 | }() | ||
| 343 | } | 356 | } | ||
| 344 | ctx = templ.InitializeContext(ctx) | 357 | ctx = templ.InitializeContext(ctx) | ||
| 345 | - | templ_7745c5c3_Var18 := templ.GetChildren(ctx) | 358 | + | templ_7745c5c3_Var19 := templ.GetChildren(ctx) |
| 346 | - | if templ_7745c5c3_Var18 == nil { | 359 | + | if templ_7745c5c3_Var19 == nil { |
| 347 | - | templ_7745c5c3_Var18 = templ.NopComponent | 360 | + | templ_7745c5c3_Var19 = templ.NopComponent |
| 348 | } | 361 | } | ||
| 349 | ctx = templ.ClearChildren(ctx) | 362 | ctx = templ.ClearChildren(ctx) | ||
| 350 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") | 363 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") |
| 351 | if templ_7745c5c3_Err != nil { | 364 | if templ_7745c5c3_Err != nil { | ||
| 352 | return templ_7745c5c3_Err | 365 | return templ_7745c5c3_Err | ||
| 353 | } | 366 | } | ||
| 354 | - | var templ_7745c5c3_Var19 string | 367 | + | var templ_7745c5c3_Var20 string |
| 355 | - | templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) | 368 | + | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) |
| 356 | if templ_7745c5c3_Err != nil { | 369 | if templ_7745c5c3_Err != nil { | ||
| 357 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 68, Col: 41} | 370 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 69, Col: 41} |
| 358 | } | 371 | } | ||
| 359 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) | 372 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) |
| 360 | if templ_7745c5c3_Err != nil { | 373 | if templ_7745c5c3_Err != nil { | ||
| 361 | return templ_7745c5c3_Err | 374 | return templ_7745c5c3_Err | ||
| 362 | } | 375 | } | ||
| 363 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</span></a> ") | 376 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</span></a> ") |
| 364 | if templ_7745c5c3_Err != nil { | 377 | if templ_7745c5c3_Err != nil { | ||
| 365 | return templ_7745c5c3_Err | 378 | return templ_7745c5c3_Err | ||
| 366 | } | 379 | } | ||
| 367 | if len(l.Crumbs) > 0 { | 380 | if len(l.Crumbs) > 0 { | ||
| 368 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | 381 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") |
| 369 | if templ_7745c5c3_Err != nil { | 382 | if templ_7745c5c3_Err != nil { | ||
| 370 | return templ_7745c5c3_Err | 383 | return templ_7745c5c3_Err | ||
| 371 | } | 384 | } | ||
| 372 | for _, c := range l.Crumbs { | 385 | for _, c := range l.Crumbs { | ||
| 373 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<li>") | 386 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<li>") |
| 374 | if templ_7745c5c3_Err != nil { | 387 | if templ_7745c5c3_Err != nil { | ||
| 375 | return templ_7745c5c3_Err | 388 | return templ_7745c5c3_Err | ||
| 376 | } | 389 | } | ||
| 377 | if c.Href != "" { | 390 | if c.Href != "" { | ||
| 378 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<a href=\"") | 391 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<a href=\"") |
| 379 | if templ_7745c5c3_Err != nil { | 392 | if templ_7745c5c3_Err != nil { | ||
| 380 | return templ_7745c5c3_Err | 393 | return templ_7745c5c3_Err | ||
| 381 | } | 394 | } | ||
| 382 | - | var templ_7745c5c3_Var20 templ.SafeURL | 395 | + | var templ_7745c5c3_Var21 templ.SafeURL |
| 383 | - | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | 396 | + | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) |
| 384 | if templ_7745c5c3_Err != nil { | 397 | if templ_7745c5c3_Err != nil { | ||
| 385 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 77, Col: 38} | 398 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 78, Col: 38} |
| 386 | } | 399 | } | ||
| 387 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) | 400 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) |
| 388 | if templ_7745c5c3_Err != nil { | 401 | if templ_7745c5c3_Err != nil { | ||
| 389 | return templ_7745c5c3_Err | 402 | return templ_7745c5c3_Err | ||
| 390 | } | 403 | } | ||
| 391 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"") | 404 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"") |
| 392 | if templ_7745c5c3_Err != nil { | 405 | if templ_7745c5c3_Err != nil { | ||
| 393 | return templ_7745c5c3_Err | 406 | return templ_7745c5c3_Err | ||
| 394 | } | 407 | } | ||
| 395 | if c.Current { | 408 | if c.Current { | ||
| 396 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " aria-current=\"page\"") | 409 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " aria-current=\"page\"") |
| 397 | if templ_7745c5c3_Err != nil { | 410 | if templ_7745c5c3_Err != nil { | ||
| 398 | return templ_7745c5c3_Err | 411 | return templ_7745c5c3_Err | ||
| 399 | } | 412 | } | ||
| 400 | } | 413 | } | ||
| 401 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, ">") | 414 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, ">") |
| 402 | if templ_7745c5c3_Err != nil { | 415 | if templ_7745c5c3_Err != nil { | ||
| 403 | return templ_7745c5c3_Err | 416 | return templ_7745c5c3_Err | ||
| 404 | } | 417 | } | ||
| 405 | - | var templ_7745c5c3_Var21 string | 418 | + | var templ_7745c5c3_Var22 string |
| 406 | - | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | 419 | + | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) |
| 407 | if templ_7745c5c3_Err != nil { | 420 | if templ_7745c5c3_Err != nil { | ||
| 408 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 81, Col: 19} | 421 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 82, Col: 19} |
| 409 | } | 422 | } | ||
| 410 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | 423 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) |
| 411 | if templ_7745c5c3_Err != nil { | 424 | if templ_7745c5c3_Err != nil { | ||
| 412 | return templ_7745c5c3_Err | 425 | return templ_7745c5c3_Err | ||
| 413 | } | 426 | } | ||
| 414 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</a>") | 427 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</a>") |
| 415 | if templ_7745c5c3_Err != nil { | 428 | if templ_7745c5c3_Err != nil { | ||
| 416 | return templ_7745c5c3_Err | 429 | return templ_7745c5c3_Err | ||
| 417 | } | 430 | } | ||
| 418 | } else { | 431 | } else { | ||
| 419 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<span") | 432 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<span") |
| 420 | if templ_7745c5c3_Err != nil { | 433 | if templ_7745c5c3_Err != nil { | ||
| 421 | return templ_7745c5c3_Err | 434 | return templ_7745c5c3_Err | ||
| 422 | } | 435 | } | ||
| 423 | if c.Current { | 436 | if c.Current { | ||
| 424 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " aria-current=\"page\"") | 437 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " aria-current=\"page\"") |
| 425 | if templ_7745c5c3_Err != nil { | 438 | if templ_7745c5c3_Err != nil { | ||
| 426 | return templ_7745c5c3_Err | 439 | return templ_7745c5c3_Err | ||
| 427 | } | 440 | } | ||
| 428 | } | 441 | } | ||
| 429 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, ">") | 442 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, ">") |
| 430 | if templ_7745c5c3_Err != nil { | 443 | if templ_7745c5c3_Err != nil { | ||
| 431 | return templ_7745c5c3_Err | 444 | return templ_7745c5c3_Err | ||
| 432 | } | 445 | } | ||
| 433 | - | var templ_7745c5c3_Var22 string | 446 | + | var templ_7745c5c3_Var23 string |
| 434 | - | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | 447 | + | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) |
| 435 | if templ_7745c5c3_Err != nil { | 448 | if templ_7745c5c3_Err != nil { | ||
| 436 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 87, Col: 19} | 449 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 88, Col: 19} |
| 437 | } | 450 | } | ||
| 438 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | 451 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) |
| 439 | if templ_7745c5c3_Err != nil { | 452 | if templ_7745c5c3_Err != nil { | ||
| 440 | return templ_7745c5c3_Err | 453 | return templ_7745c5c3_Err | ||
| 441 | } | 454 | } | ||
| 442 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</span>") | 455 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</span>") |
| 443 | if templ_7745c5c3_Err != nil { | 456 | if templ_7745c5c3_Err != nil { | ||
| 444 | return templ_7745c5c3_Err | 457 | return templ_7745c5c3_Err | ||
| 445 | } | 458 | } | ||
| 446 | } | 459 | } | ||
| 447 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</li>") | 460 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</li>") |
| 448 | if templ_7745c5c3_Err != nil { | 461 | if templ_7745c5c3_Err != nil { | ||
| 449 | return templ_7745c5c3_Err | 462 | return templ_7745c5c3_Err | ||
| 450 | } | 463 | } | ||
| 451 | } | 464 | } | ||
| 452 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</ol></nav>") | 465 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</ol></nav>") |
| 453 | if templ_7745c5c3_Err != nil { | 466 | if templ_7745c5c3_Err != nil { | ||
| 454 | return templ_7745c5c3_Err | 467 | return templ_7745c5c3_Err | ||
| 455 | } | 468 | } | ||
| 456 | } | 469 | } | ||
| 457 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"hdr-acts\">") | 470 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<div class=\"hdr-acts\">") |
| 458 | if templ_7745c5c3_Err != nil { | 471 | if templ_7745c5c3_Err != nil { | ||
| 459 | return templ_7745c5c3_Err | 472 | return templ_7745c5c3_Err | ||
| 460 | } | 473 | } | ||
| 461 | if l.Ref != "" { | 474 | if l.Ref != "" { | ||
| 462 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<a class=\"btn ref\" href=\"") | 475 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<a class=\"btn ref\" href=\"") |
| 463 | if templ_7745c5c3_Err != nil { | 476 | if templ_7745c5c3_Err != nil { | ||
| 464 | return templ_7745c5c3_Err | 477 | return templ_7745c5c3_Err | ||
| 465 | } | 478 | } | ||
| 466 | - | var templ_7745c5c3_Var23 templ.SafeURL | 479 | + | var templ_7745c5c3_Var24 templ.SafeURL |
| 467 | - | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | 480 | + | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) |
| 468 | if templ_7745c5c3_Err != nil { | 481 | if templ_7745c5c3_Err != nil { | ||
| 469 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 98, Col: 52} | 482 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 99, Col: 52} |
| 470 | } | 483 | } | ||
| 471 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | 484 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) |
| 472 | if templ_7745c5c3_Err != nil { | 485 | if templ_7745c5c3_Err != nil { | ||
| 473 | return templ_7745c5c3_Err | 486 | return templ_7745c5c3_Err | ||
| 474 | } | 487 | } | ||
| 475 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | 488 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") |
| 476 | if templ_7745c5c3_Err != nil { | 489 | if templ_7745c5c3_Err != nil { | ||
| 477 | return templ_7745c5c3_Err | 490 | return templ_7745c5c3_Err | ||
| 478 | } | 491 | } | ||
| 479 | - | var templ_7745c5c3_Var24 string | 492 | + | var templ_7745c5c3_Var25 string |
| 480 | - | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | 493 | + | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) |
| 481 | if templ_7745c5c3_Err != nil { | 494 | if templ_7745c5c3_Err != nil { | ||
| 482 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 104, Col: 36} | 495 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 105, Col: 36} |
| 483 | } | 496 | } | ||
| 484 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | 497 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) |
| 485 | if templ_7745c5c3_Err != nil { | 498 | if templ_7745c5c3_Err != nil { | ||
| 486 | return templ_7745c5c3_Err | 499 | return templ_7745c5c3_Err | ||
| 487 | } | 500 | } | ||
| 488 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</span> <span class=\"sha\">") | 501 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span> <span class=\"sha\">") |
| 489 | if templ_7745c5c3_Err != nil { | 502 | if templ_7745c5c3_Err != nil { | ||
| 490 | return templ_7745c5c3_Err | 503 | return templ_7745c5c3_Err | ||
| 491 | } | 504 | } | ||
| 492 | - | var templ_7745c5c3_Var25 string | 505 | + | var templ_7745c5c3_Var26 string |
| 493 | - | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | 506 | + | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) |
| 494 | if templ_7745c5c3_Err != nil { | 507 | if templ_7745c5c3_Err != nil { | ||
| 495 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 105, Col: 33} | 508 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 106, Col: 33} |
| 496 | } | 509 | } | ||
| 497 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | 510 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) |
| 498 | if templ_7745c5c3_Err != nil { | 511 | if templ_7745c5c3_Err != nil { | ||
| 499 | return templ_7745c5c3_Err | 512 | return templ_7745c5c3_Err | ||
| 500 | } | 513 | } | ||
| 501 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span></a> ") | 514 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span></a> ") |
| 502 | if templ_7745c5c3_Err != nil { | 515 | if templ_7745c5c3_Err != nil { | ||
| 503 | return templ_7745c5c3_Err | 516 | return templ_7745c5c3_Err | ||
| 504 | } | 517 | } | ||
| 505 | } | 518 | } | ||
| 506 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") | 519 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") |
| 507 | if templ_7745c5c3_Err != nil { | 520 | if templ_7745c5c3_Err != nil { | ||
| 508 | return templ_7745c5c3_Err | 521 | return templ_7745c5c3_Err | ||
| 509 | } | 522 | } | ||
| 510 | if SignedIn(ctx) { | 523 | if SignedIn(ctx) { | ||
| 511 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> <button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") | 524 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> <button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") |
| 512 | if templ_7745c5c3_Err != nil { | 525 | if templ_7745c5c3_Err != nil { | ||
| 513 | return templ_7745c5c3_Err | 526 | return templ_7745c5c3_Err | ||
| 514 | } | 527 | } | ||
| 515 | } | 528 | } | ||
| 516 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</div></div>") | 529 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</div></div>") |
| 517 | if templ_7745c5c3_Err != nil { | 530 | if templ_7745c5c3_Err != nil { | ||
| 518 | return templ_7745c5c3_Err | 531 | return templ_7745c5c3_Err | ||
| 519 | } | 532 | } | ||
| 520 | if len(l.Sections) > 0 { | 533 | if len(l.Sections) > 0 { | ||
| 521 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | 534 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") |
| 522 | if templ_7745c5c3_Err != nil { | 535 | if templ_7745c5c3_Err != nil { | ||
| 523 | return templ_7745c5c3_Err | 536 | return templ_7745c5c3_Err | ||
| 524 | } | 537 | } | ||
| 525 | for _, s := range l.Sections { | 538 | for _, s := range l.Sections { | ||
| 526 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<li><a href=\"") | 539 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<li><a href=\"") |
| 527 | if templ_7745c5c3_Err != nil { | 540 | if templ_7745c5c3_Err != nil { | ||
| 528 | return templ_7745c5c3_Err | 541 | return templ_7745c5c3_Err | ||
| 529 | } | 542 | } | ||
| 530 | - | var templ_7745c5c3_Var26 templ.SafeURL | 543 | + | var templ_7745c5c3_Var27 templ.SafeURL |
| 531 | - | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | 544 | + | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) |
| 532 | if templ_7745c5c3_Err != nil { | 545 | if templ_7745c5c3_Err != nil { | ||
| 533 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 137, Col: 36} | 546 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 138, Col: 36} |
| 534 | } | 547 | } | ||
| 535 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | 548 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) |
| 536 | if templ_7745c5c3_Err != nil { | 549 | if templ_7745c5c3_Err != nil { | ||
| 537 | return templ_7745c5c3_Err | 550 | return templ_7745c5c3_Err | ||
| 538 | } | 551 | } | ||
| 539 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\" data-key=\"") | 552 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" data-key=\"") |
| 540 | if templ_7745c5c3_Err != nil { | 553 | if templ_7745c5c3_Err != nil { | ||
| 541 | return templ_7745c5c3_Err | 554 | return templ_7745c5c3_Err | ||
| 542 | } | 555 | } | ||
| 543 | - | var templ_7745c5c3_Var27 string | 556 | + | var templ_7745c5c3_Var28 string |
| 544 | - | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | 557 | + | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) |
| 545 | if templ_7745c5c3_Err != nil { | 558 | if templ_7745c5c3_Err != nil { | ||
| 546 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 138, Col: 24} | 559 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 139, Col: 24} |
| 547 | } | 560 | } | ||
| 548 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27) | 561 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28) |
| 549 | if templ_7745c5c3_Err != nil { | 562 | if templ_7745c5c3_Err != nil { | ||
| 550 | return templ_7745c5c3_Err | 563 | return templ_7745c5c3_Err | ||
| 551 | } | 564 | } | ||
| 552 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" aria-keyshortcuts=\"") | 565 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\" aria-keyshortcuts=\"") |
| 553 | if templ_7745c5c3_Err != nil { | 566 | if templ_7745c5c3_Err != nil { | ||
| 554 | return templ_7745c5c3_Err | 567 | return templ_7745c5c3_Err | ||
| 555 | } | 568 | } | ||
| 556 | - | var templ_7745c5c3_Var28 string | 569 | + | var templ_7745c5c3_Var29 string |
| 557 | - | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | 570 | + | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) |
| 558 | if templ_7745c5c3_Err != nil { | 571 | if templ_7745c5c3_Err != nil { | ||
| 559 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 139, Col: 33} | 572 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 140, Col: 33} |
| 560 | } | 573 | } | ||
| 561 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28) | 574 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) |
| 562 | if templ_7745c5c3_Err != nil { | 575 | if templ_7745c5c3_Err != nil { | ||
| 563 | return templ_7745c5c3_Err | 576 | return templ_7745c5c3_Err | ||
| 564 | } | 577 | } | ||
| 565 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"") | 578 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\"") |
| 566 | if templ_7745c5c3_Err != nil { | 579 | if templ_7745c5c3_Err != nil { | ||
| 567 | return templ_7745c5c3_Err | 580 | return templ_7745c5c3_Err | ||
| 568 | } | 581 | } | ||
| 569 | if s.Current { | 582 | if s.Current { | ||
| 570 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, " aria-current=\"page\"") | 583 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " aria-current=\"page\"") |
| 571 | if templ_7745c5c3_Err != nil { | 584 | if templ_7745c5c3_Err != nil { | ||
| 572 | return templ_7745c5c3_Err | 585 | return templ_7745c5c3_Err | ||
| 573 | } | 586 | } | ||
| 574 | } | 587 | } | ||
| 575 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, ">") | 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, ">") |
| 576 | if templ_7745c5c3_Err != nil { | 589 | if templ_7745c5c3_Err != nil { | ||
| 577 | return templ_7745c5c3_Err | 590 | return templ_7745c5c3_Err | ||
| 578 | } | 591 | } | ||
| 579 | - | var templ_7745c5c3_Var29 string | 592 | + | var templ_7745c5c3_Var30 string |
| 580 | - | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | 593 | + | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) |
| 581 | if templ_7745c5c3_Err != nil { | 594 | if templ_7745c5c3_Err != nil { | ||
| 582 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 143, Col: 17} | 595 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 17} |
| 583 | } | 596 | } | ||
| 584 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | 597 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) |
| 585 | if templ_7745c5c3_Err != nil { | 598 | if templ_7745c5c3_Err != nil { | ||
| 586 | return templ_7745c5c3_Err | 599 | return templ_7745c5c3_Err | ||
| 587 | } | 600 | } | ||
| 588 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<kbd aria-hidden=\"true\">") | 601 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<kbd aria-hidden=\"true\">") |
| 589 | if templ_7745c5c3_Err != nil { | 602 | if templ_7745c5c3_Err != nil { | ||
| 590 | return templ_7745c5c3_Err | 603 | return templ_7745c5c3_Err | ||
| 591 | } | 604 | } | ||
| 592 | - | var templ_7745c5c3_Var30 string | 605 | + | var templ_7745c5c3_Var31 string |
| 593 | - | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | 606 | + | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) |
| 594 | if templ_7745c5c3_Err != nil { | 607 | if templ_7745c5c3_Err != nil { | ||
| 595 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 143, Col: 50} | 608 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 50} |
| 596 | } | 609 | } | ||
| 597 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | 610 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) |
| 598 | if templ_7745c5c3_Err != nil { | 611 | if templ_7745c5c3_Err != nil { | ||
| 599 | return templ_7745c5c3_Err | 612 | return templ_7745c5c3_Err | ||
| 600 | } | 613 | } | ||
| 601 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</kbd></a></li>") | 614 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</kbd></a></li>") |
| 602 | if templ_7745c5c3_Err != nil { | 615 | if templ_7745c5c3_Err != nil { | ||
| 603 | return templ_7745c5c3_Err | 616 | return templ_7745c5c3_Err | ||
| 604 | } | 617 | } | ||
| 605 | } | 618 | } | ||
| 606 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</ul></nav>") | 619 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</ul></nav>") |
| 607 | if templ_7745c5c3_Err != nil { | 620 | if templ_7745c5c3_Err != nil { | ||
| 608 | return templ_7745c5c3_Err | 621 | return templ_7745c5c3_Err | ||
| 609 | } | 622 | } | ||
| 610 | } | 623 | } | ||
| 611 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</header>") | 624 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</header>") |
| 612 | if templ_7745c5c3_Err != nil { | 625 | if templ_7745c5c3_Err != nil { | ||
| 613 | return templ_7745c5c3_Err | 626 | return templ_7745c5c3_Err | ||
| 614 | } | 627 | } | ||
| @@ -634,38 +647,38 @@ func Footer(l Layout) templ.Component { | |||||
| 634 | }() | 647 | }() | ||
| 635 | } | 648 | } | ||
| 636 | ctx = templ.InitializeContext(ctx) | 649 | ctx = templ.InitializeContext(ctx) | ||
| 637 | - | templ_7745c5c3_Var31 := templ.GetChildren(ctx) | 650 | + | templ_7745c5c3_Var32 := templ.GetChildren(ctx) |
| 638 | - | if templ_7745c5c3_Var31 == nil { | 651 | + | if templ_7745c5c3_Var32 == nil { |
| 639 | - | templ_7745c5c3_Var31 = templ.NopComponent | 652 | + | templ_7745c5c3_Var32 = templ.NopComponent |
| 640 | } | 653 | } | ||
| 641 | ctx = templ.ClearChildren(ctx) | 654 | ctx = templ.ClearChildren(ctx) | ||
| 642 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<footer class=\"foot\"><span>ermite ") | 655 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<footer class=\"foot\"><span>ermite ") |
| 643 | if templ_7745c5c3_Err != nil { | 656 | if templ_7745c5c3_Err != nil { | ||
| 644 | return templ_7745c5c3_Err | 657 | return templ_7745c5c3_Err | ||
| 645 | } | 658 | } | ||
| 646 | - | var templ_7745c5c3_Var32 string | 659 | + | var templ_7745c5c3_Var33 string |
| 647 | - | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | 660 | + | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) |
| 648 | if templ_7745c5c3_Err != nil { | 661 | if templ_7745c5c3_Err != nil { | ||
| 649 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 156, Col: 26} | 662 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 157, Col: 26} |
| 650 | } | 663 | } | ||
| 651 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | 664 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) |
| 652 | if templ_7745c5c3_Err != nil { | 665 | if templ_7745c5c3_Err != nil { | ||
| 653 | return templ_7745c5c3_Err | 666 | return templ_7745c5c3_Err | ||
| 654 | } | 667 | } | ||
| 655 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</span> <a href=\"") | 668 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</span> <a href=\"") |
| 656 | if templ_7745c5c3_Err != nil { | 669 | if templ_7745c5c3_Err != nil { | ||
| 657 | return templ_7745c5c3_Err | 670 | return templ_7745c5c3_Err | ||
| 658 | } | 671 | } | ||
| 659 | - | var templ_7745c5c3_Var33 templ.SafeURL | 672 | + | var templ_7745c5c3_Var34 templ.SafeURL |
| 660 | - | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | 673 | + | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) |
| 661 | if templ_7745c5c3_Err != nil { | 674 | if templ_7745c5c3_Err != nil { | ||
| 662 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 157, Col: 38} | 675 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 158, Col: 38} |
| 663 | } | 676 | } | ||
| 664 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) | 677 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) |
| 665 | if templ_7745c5c3_Err != nil { | 678 | if templ_7745c5c3_Err != nil { | ||
| 666 | return templ_7745c5c3_Err | 679 | return templ_7745c5c3_Err | ||
| 667 | } | 680 | } | ||
| 668 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") | 681 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") |
| 669 | if templ_7745c5c3_Err != nil { | 682 | if templ_7745c5c3_Err != nil { | ||
| 670 | return templ_7745c5c3_Err | 683 | return templ_7745c5c3_Err | ||
| 671 | } | 684 | } | ||
| @@ -693,12 +706,12 @@ func Hint(l Layout) templ.Component { | |||||
| 693 | }() | 706 | }() | ||
| 694 | } | 707 | } | ||
| 695 | ctx = templ.InitializeContext(ctx) | 708 | ctx = templ.InitializeContext(ctx) | ||
| 696 | - | templ_7745c5c3_Var34 := templ.GetChildren(ctx) | 709 | + | templ_7745c5c3_Var35 := templ.GetChildren(ctx) |
| 697 | - | if templ_7745c5c3_Var34 == nil { | 710 | + | if templ_7745c5c3_Var35 == nil { |
| 698 | - | templ_7745c5c3_Var34 = templ.NopComponent | 711 | + | templ_7745c5c3_Var35 = templ.NopComponent |
| 699 | } | 712 | } | ||
| 700 | ctx = templ.ClearChildren(ctx) | 713 | ctx = templ.ClearChildren(ctx) | ||
| 701 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | 714 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") |
| 702 | if templ_7745c5c3_Err != nil { | 715 | if templ_7745c5c3_Err != nil { | ||
| 703 | return templ_7745c5c3_Err | 716 | return templ_7745c5c3_Err | ||
| 704 | } | 717 | } | ||
| @@ -706,7 +719,7 @@ func Hint(l Layout) templ.Component { | |||||
| 706 | if templ_7745c5c3_Err != nil { | 719 | if templ_7745c5c3_Err != nil { | ||
| 707 | return templ_7745c5c3_Err | 720 | return templ_7745c5c3_Err | ||
| 708 | } | 721 | } | ||
| 709 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</div>") | 722 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</div>") |
| 710 | if templ_7745c5c3_Err != nil { | 723 | if templ_7745c5c3_Err != nil { | ||
| 711 | return templ_7745c5c3_Err | 724 | return templ_7745c5c3_Err | ||
| 712 | } | 725 | } | ||
M internal/adapters/web/static/js/ermite.js +0 -23
| @@ -363,26 +363,3 @@ window.ermiteLive = (() => { | |||
| 363 | 363 | }, | |
| 364 | 364 | }; | |
| 365 | 365 | })(); | |
| 366 | - | ||
| 367 | - | // ermiteLog follows the log of a running job on its page: the lines arrive as | |
| 368 | - | // "ci-log" events (internal/adapters/web/actions.go, logStream) and are | |
| 369 | - | // appended to the list. The browser reconnects by itself with Last-Event-ID, | |
| 370 | - | // the number of the next line; "ci-end" closes the feed when the job is over. | |
| 371 | - | // The page scrolls with the log only while the reader is at its end. | |
| 372 | - | (() => { | |
| 373 | - | const box = document.getElementById('job-log'); | |
| 374 | - | if (!box || !box.hasAttribute('data-follow') || !box.dataset.stream || !window.EventSource) return; | |
| 375 | - | const list = box.querySelector('ol.log'); | |
| 376 | - | if (!list) return; | |
| 377 | - | const url = box.dataset.stream + '?from=' + encodeURIComponent(box.dataset.next || '0'); | |
| 378 | - | const es = new EventSource(url); | |
| 379 | - | es.addEventListener('ci-log', (e) => { | |
| 380 | - | const t = document.createElement('template'); | |
| 381 | - | t.innerHTML = e.data; | |
| 382 | - | const atEnd = window.innerHeight + window.scrollY >= document.body.scrollHeight - 80; | |
| 383 | - | list.append(...t.content.children); | |
| 384 | - | if (atEnd) window.scrollTo(0, document.body.scrollHeight); | |
| 385 | - | }); | |
| 386 | - | es.addEventListener('ci-end', () => es.close()); | |
| 387 | - | es.onerror = () => { if (es.readyState === EventSource.CLOSED) es.close(); }; | |
| 388 | - | })(); | |
| @@ -363,26 +363,3 @@ window.ermiteLive = (() => { | |||||
| 363 | }, | 363 | }, | ||
| 364 | }; | 364 | }; | ||
| 365 | })(); | 365 | })(); | ||
| 366 | - | ||||
| 367 | - | // ermiteLog follows the log of a running job on its page: the lines arrive as | |||
| 368 | - | // "ci-log" events (internal/adapters/web/actions.go, logStream) and are | |||
| 369 | - | // appended to the list. The browser reconnects by itself with Last-Event-ID, | |||
| 370 | - | // the number of the next line; "ci-end" closes the feed when the job is over. | |||
| 371 | - | // The page scrolls with the log only while the reader is at its end. | |||
| 372 | - | (() => { | |||
| 373 | - | const box = document.getElementById('job-log'); | |||
| 374 | - | if (!box || !box.hasAttribute('data-follow') || !box.dataset.stream || !window.EventSource) return; | |||
| 375 | - | const list = box.querySelector('ol.log'); | |||
| 376 | - | if (!list) return; | |||
| 377 | - | const url = box.dataset.stream + '?from=' + encodeURIComponent(box.dataset.next || '0'); | |||
| 378 | - | const es = new EventSource(url); | |||
| 379 | - | es.addEventListener('ci-log', (e) => { | |||
| 380 | - | const t = document.createElement('template'); | |||
| 381 | - | t.innerHTML = e.data; | |||
| 382 | - | const atEnd = window.innerHeight + window.scrollY >= document.body.scrollHeight - 80; | |||
| 383 | - | list.append(...t.content.children); | |||
| 384 | - | if (atEnd) window.scrollTo(0, document.body.scrollHeight); | |||
| 385 | - | }); | |||
| 386 | - | es.addEventListener('ci-end', () => es.close()); | |||
| 387 | - | es.onerror = () => { if (es.readyState === EventSource.CLOSED) es.close(); }; | |||
| 388 | - | })(); | |||
A internal/adapters/web/static/js/log.js +43 -0
| @@ -0,0 +1,43 @@ | |||
| 1 | + | // log.js: the live log of a running job (page /<repo>/actions/runs/<id>/job/<id>). | |
| 2 | + | // The lines arrive as "ci-log" events (internal/adapters/web/actions.go, | |
| 3 | + | // logStream), each one the <li> of the lines of a batch, and are appended to the | |
| 4 | + | // list. The browser reconnects by itself with Last-Event-ID, the number of the | |
| 5 | + | // next line; "ci-end" closes the feed when the job is over. The page follows the | |
| 6 | + | // log (scrolls to its end) only while the reader is at the end already. A piece | |
| 7 | + | // that continues a line begun in an earlier batch (data-cont) is joined to the | |
| 8 | + | // last line of the list. | |
| 9 | + | window.ermiteLog = (() => { | |
| 10 | + | const nearEnd = () => window.innerHeight + window.scrollY >= document.body.scrollHeight - 80; | |
| 11 | + | ||
| 12 | + | // follow starts the feed of the box (#job-log) and returns the EventSource, | |
| 13 | + | // or null when the box is not a running job's. | |
| 14 | + | function follow(box) { | |
| 15 | + | if (!box || !box.hasAttribute('data-follow') || !box.dataset.stream || !window.EventSource) return null; | |
| 16 | + | const list = box.querySelector('ol.log'); | |
| 17 | + | if (!list) return null; | |
| 18 | + | const es = new window.EventSource(box.dataset.stream + '?from=' + encodeURIComponent(box.dataset.next || '0')); | |
| 19 | + | es.addEventListener('ci-log', (e) => { | |
| 20 | + | const t = document.createElement('template'); | |
| 21 | + | t.innerHTML = e.data; | |
| 22 | + | const atEnd = nearEnd(); | |
| 23 | + | for (const line of [...t.content.children]) { | |
| 24 | + | const last = list.children[list.children.length - 1]; | |
| 25 | + | if (line.hasAttribute('data-cont') && last) { | |
| 26 | + | const text = line.querySelector('.t'); | |
| 27 | + | const into = last.querySelector('.t'); | |
| 28 | + | if (text && into) into.textContent += text.textContent; | |
| 29 | + | } else { | |
| 30 | + | list.append(line); | |
| 31 | + | } | |
| 32 | + | } | |
| 33 | + | if (atEnd) window.scrollTo(0, document.body.scrollHeight); | |
| 34 | + | }); | |
| 35 | + | es.addEventListener('ci-end', () => es.close()); | |
| 36 | + | es.onerror = () => { if (es.readyState === window.EventSource.CLOSED) es.close(); }; | |
| 37 | + | return es; | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | return { follow }; | |
| 41 | + | })(); | |
| 42 | + | ||
| 43 | + | document.addEventListener('DOMContentLoaded', () => window.ermiteLog.follow(document.getElementById('job-log'))); | |
| @@ -0,0 +1,43 @@ | |||||
| 1 | + | // log.js: the live log of a running job (page /<repo>/actions/runs/<id>/job/<id>). | |||
| 2 | + | // The lines arrive as "ci-log" events (internal/adapters/web/actions.go, | |||
| 3 | + | // logStream), each one the <li> of the lines of a batch, and are appended to the | |||
| 4 | + | // list. The browser reconnects by itself with Last-Event-ID, the number of the | |||
| 5 | + | // next line; "ci-end" closes the feed when the job is over. The page follows the | |||
| 6 | + | // log (scrolls to its end) only while the reader is at the end already. A piece | |||
| 7 | + | // that continues a line begun in an earlier batch (data-cont) is joined to the | |||
| 8 | + | // last line of the list. | |||
| 9 | + | window.ermiteLog = (() => { | |||
| 10 | + | const nearEnd = () => window.innerHeight + window.scrollY >= document.body.scrollHeight - 80; | |||
| 11 | + | ||||
| 12 | + | // follow starts the feed of the box (#job-log) and returns the EventSource, | |||
| 13 | + | // or null when the box is not a running job's. | |||
| 14 | + | function follow(box) { | |||
| 15 | + | if (!box || !box.hasAttribute('data-follow') || !box.dataset.stream || !window.EventSource) return null; | |||
| 16 | + | const list = box.querySelector('ol.log'); | |||
| 17 | + | if (!list) return null; | |||
| 18 | + | const es = new window.EventSource(box.dataset.stream + '?from=' + encodeURIComponent(box.dataset.next || '0')); | |||
| 19 | + | es.addEventListener('ci-log', (e) => { | |||
| 20 | + | const t = document.createElement('template'); | |||
| 21 | + | t.innerHTML = e.data; | |||
| 22 | + | const atEnd = nearEnd(); | |||
| 23 | + | for (const line of [...t.content.children]) { | |||
| 24 | + | const last = list.children[list.children.length - 1]; | |||
| 25 | + | if (line.hasAttribute('data-cont') && last) { | |||
| 26 | + | const text = line.querySelector('.t'); | |||
| 27 | + | const into = last.querySelector('.t'); | |||
| 28 | + | if (text && into) into.textContent += text.textContent; | |||
| 29 | + | } else { | |||
| 30 | + | list.append(line); | |||
| 31 | + | } | |||
| 32 | + | } | |||
| 33 | + | if (atEnd) window.scrollTo(0, document.body.scrollHeight); | |||
| 34 | + | }); | |||
| 35 | + | es.addEventListener('ci-end', () => es.close()); | |||
| 36 | + | es.onerror = () => { if (es.readyState === window.EventSource.CLOSED) es.close(); }; | |||
| 37 | + | return es; | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | return { follow }; | |||
| 41 | + | })(); | |||
| 42 | + | ||||
| 43 | + | document.addEventListener('DOMContentLoaded', () => window.ermiteLog.follow(document.getElementById('job-log'))); | |||
M internal/app/testdata/web/blob-binary.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-code.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-empty.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-image.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-markdown.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-svg.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-text.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-too-large.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/blob-trojan.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-big.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-binary.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-merge.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-modify-split.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-modify.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-rename.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-root.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commit-trojan.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | 25 | <body data-ref="" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/SHA40"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commits-page2.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commits-path.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commits-root.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/long/paths/SHA40" data-blob="/long/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/commits-small.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/diffs/paths/SHA40" data-blob="/diffs/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/empty.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/home.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/not-found-page.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/not-found.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | 25 | <body data-ref="" data-sha="" data-sel="" data-nav=""> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/refs.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/summary.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/demo/paths/SHA40" data-blob="/demo/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/tree-nested.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/tree-root.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/tree-slash-ref.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="feature/x" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/feature/x"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="feature/x" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/feature/x"> | 25 | <body data-ref="feature/x" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/feature/x"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||
M internal/app/testdata/web/tree-subdir.html +2 -0
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | </script> | |
| 20 | 20 | <script src="/-/static/js/files.HASH.js" defer> | |
| 21 | 21 | </script> | |
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |
| 23 | + | </script> | |
| 22 | 24 | </head> | |
| 23 | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | |
| 24 | 26 | <a class="skip" href="#main">skip to content</a> | |
| @@ -19,6 +19,8 @@ | |||||
| 19 | </script> | 19 | </script> | ||
| 20 | <script src="/-/static/js/files.HASH.js" defer> | 20 | <script src="/-/static/js/files.HASH.js" defer> | ||
| 21 | </script> | 21 | </script> | ||
| 22 | + | <script src="/-/static/js/log.HASH.js" defer> | |||
| 23 | + | </script> | |||
| 22 | </head> | 24 | </head> | ||
| 23 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | 25 | <body data-ref="main" data-sha="SHA40" data-sel="" data-nav="" data-paths="/files/paths/SHA40" data-blob="/files/blob/main"> | ||
| 24 | <a class="skip" href="#main">skip to content</a> | 26 | <a class="skip" href="#main">skip to content</a> | ||