feat(web): show the lines a diff hides (e / E) (#54)
- commit
- 032b9efee6dcade40d71e6fd7bf5c696373fad80
- author
- brice · · 2026-10-17 23:52 UTC
- committer
- bnema · · 2026-10-17 23:52 UTC
- parent
- 7ecd377
- signature
- SSH Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown - refs
- tag: v0.20.0
48 files · +1136 -96
M internal/adapters/web/commit.go +1 -0
| @@ -122,6 +122,7 @@ func (s *Server) commit(ctx context.Context, r *http.Request) (templ.Component, | |||
| 122 | 122 | Dels: res.Stats.Deletions, | |
| 123 | 123 | Files: diffFiles(res.Diff.Files), | |
| 124 | 124 | } | |
| 125 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", cid)) | |
| 125 | 126 | if cm := person(c.Committer, now); cm != p.Author || c.Committer.Email != c.Author.Email { | |
| 126 | 127 | p.Committer = &cm | |
| 127 | 128 | } | |
| @@ -122,6 +122,7 @@ func (s *Server) commit(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 122 | Dels: res.Stats.Deletions, | 122 | Dels: res.Stats.Deletions, | ||
| 123 | Files: diffFiles(res.Diff.Files), | 123 | Files: diffFiles(res.Diff.Files), | ||
| 124 | } | 124 | } | ||
| 125 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", cid)) | |||
| 125 | if cm := person(c.Committer, now); cm != p.Author || c.Committer.Email != c.Author.Email { | 126 | if cm := person(c.Committer, now); cm != p.Author || c.Committer.Email != c.Author.Email { | ||
| 126 | p.Committer = &cm | 127 | p.Committer = &cm | ||
| 127 | } | 128 | } | ||
M internal/adapters/web/compare.go +1 -0
| @@ -87,6 +87,7 @@ func (s *Server) compare(ctx context.Context, r *http.Request) (templ.Component, | |||
| 87 | 87 | } | |
| 88 | 88 | p.HasDiff = true | |
| 89 | 89 | p.Files = diffFiles(res.Diff.Files) | |
| 90 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", res.Commit.ID.String())) | |
| 90 | 91 | p.NFiles, p.Adds, p.Dels = res.Stats.FilesChanged, res.Stats.Additions, res.Stats.Deletions | |
| 91 | 92 | if n := diffNote(res.Diff, len(p.Files), p.NFiles); n != "" { | |
| 92 | 93 | p.Note = n | |
| @@ -87,6 +87,7 @@ func (s *Server) compare(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 87 | } | 87 | } | ||
| 88 | p.HasDiff = true | 88 | p.HasDiff = true | ||
| 89 | p.Files = diffFiles(res.Diff.Files) | 89 | p.Files = diffFiles(res.Diff.Files) | ||
| 90 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", res.Commit.ID.String())) | |||
| 90 | p.NFiles, p.Adds, p.Dels = res.Stats.FilesChanged, res.Stats.Additions, res.Stats.Deletions | 91 | p.NFiles, p.Adds, p.Dels = res.Stats.FilesChanged, res.Stats.Additions, res.Stats.Deletions | ||
| 91 | if n := diffNote(res.Diff, len(p.Files), p.NFiles); n != "" { | 92 | if n := diffNote(res.Diff, len(p.Files), p.NFiles); n != "" { | ||
| 92 | p.Note = n | 93 | p.Note = n | ||
A internal/adapters/web/context.go +111 -0
| @@ -0,0 +1,111 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package web | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "net/http" | |
| 8 | + | "strconv" | |
| 9 | + | "strings" | |
| 10 | + | ||
| 11 | + | "github.com/a-h/templ" | |
| 12 | + | ||
| 13 | + | "bnema.dev/ermite/internal/domain" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // maxContextLines bounds the lines one gap loads: the rest stays behind a | |
| 17 | + | // smaller gap that loads the next lines. | |
| 18 | + | const maxContextLines = 2000 | |
| 19 | + | ||
| 20 | + | // ContextRows is the hidden lines of a gap, for both layouts: they replace the | |
| 21 | + | // rows of the gap. More is the gap that is left, when the file goes on. | |
| 22 | + | type ContextRows struct { | |
| 23 | + | Lines []DiffLine | |
| 24 | + | More *DiffGap | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // diffContext is GET /<repo>/context/<commit>?path=&old=&new=&n=: the lines of | |
| 28 | + | // a file of a diff that its hunks hide, as rows of the diff tables. commit is | |
| 29 | + | // the full id of the commit of the new side; path the file there; old and new | |
| 30 | + | // the numbers of the first hidden line on each side, which move together (the | |
| 31 | + | // lines are unchanged); n the number of lines, 0 for the rest of the file. | |
| 32 | + | // | |
| 33 | + | //hexcheck:ignore suspicious-business-logic-in-adapter cuts a file the use case opened into rows of the diff; no rule is decided here | |
| 34 | + | func (s *Server) diffContext(ctx context.Context, r *http.Request) (templ.Component, int, error) { | |
| 35 | + | sha := r.PathValue("sha") | |
| 36 | + | if !isFullID(sha) { | |
| 37 | + | return nil, 0, errNotFound | |
| 38 | + | } | |
| 39 | + | q := r.URL.Query() | |
| 40 | + | num := func(k string, least int) (int, bool) { | |
| 41 | + | v, err := strconv.Atoi(q.Get(k)) | |
| 42 | + | return v, err == nil && v >= least && strconv.Itoa(v) == q.Get(k) | |
| 43 | + | } | |
| 44 | + | oldFrom, ok1 := num("old", 1) | |
| 45 | + | newFrom, ok2 := num("new", 1) | |
| 46 | + | n, ok3 := num("n", 0) | |
| 47 | + | path := q.Get("path") | |
| 48 | + | if !ok1 || !ok2 || !ok3 || path == "" { | |
| 49 | + | return nil, 0, errNotFound | |
| 50 | + | } | |
| 51 | + | repo, err := domain.ParseRepoName(r.PathValue("repo")) | |
| 52 | + | if err != nil { | |
| 53 | + | return nil, 0, err | |
| 54 | + | } | |
| 55 | + | info, body, err := s.openBlobAt(ctx, repo, sha, path) | |
| 56 | + | if err != nil { | |
| 57 | + | return nil, 0, err | |
| 58 | + | } | |
| 59 | + | b := &blobStream{body: body, info: info} | |
| 60 | + | defer b.close() | |
| 61 | + | if info.Binary || info.Size > maxCodeBytes { | |
| 62 | + | return nil, 0, errNotFound | |
| 63 | + | } | |
| 64 | + | if err := b.fill(int(info.Size)); err != nil { | |
| 65 | + | return nil, 0, err | |
| 66 | + | } | |
| 67 | + | // Lines as Git numbers them: split on LF only (a lone CR is a character of | |
| 68 | + | // its line, shown as a marker; the CR of a CRLF is dropped by segments). | |
| 69 | + | text := strings.ToValidUTF8(string(b.buf), "\uFFFD") | |
| 70 | + | lines := strings.Split(text, "\n") | |
| 71 | + | noEOL := text != "" && !strings.HasSuffix(text, "\n") | |
| 72 | + | if !noEOL { | |
| 73 | + | lines = lines[:len(lines)-1] | |
| 74 | + | } | |
| 75 | + | if newFrom > len(lines)+1 { | |
| 76 | + | return nil, 0, errNotFound | |
| 77 | + | } | |
| 78 | + | end := len(lines) | |
| 79 | + | if n > 0 { | |
| 80 | + | end = min(end, newFrom-1+n) | |
| 81 | + | } | |
| 82 | + | cut := min(end, newFrom-1+maxContextLines) | |
| 83 | + | ||
| 84 | + | var out ContextRows | |
| 85 | + | for i := newFrom - 1; i < cut; i++ { | |
| 86 | + | o, nw := oldFrom+i-(newFrom-1), i+1 | |
| 87 | + | out.Lines = append(out.Lines, DiffLine{ | |
| 88 | + | Kind: "ctx", Mark: " ", Segs: segments(lines[i]), | |
| 89 | + | Old: itoa(o), New: itoa(nw), oldN: o, newN: nw, | |
| 90 | + | NoEOL: noEOL && i == len(lines)-1, | |
| 91 | + | }) | |
| 92 | + | } | |
| 93 | + | if cut < end { | |
| 94 | + | rest := 0 | |
| 95 | + | if n > 0 { | |
| 96 | + | rest = end - cut | |
| 97 | + | } | |
| 98 | + | next := gapHref(r, oldFrom+cut-(newFrom-1), cut+1, rest) | |
| 99 | + | out.More = &DiffGap{Key: q.Get("key"), Lines: rest, Src: next} | |
| 100 | + | } | |
| 101 | + | return DiffContext(out), http.StatusOK, nil | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | // gapHref is the address of the gap that starts at the lines old and new. | |
| 105 | + | func gapHref(r *http.Request, old, new, n int) string { | |
| 106 | + | q := r.URL.Query() | |
| 107 | + | q.Set("old", itoa(old)) | |
| 108 | + | q.Set("new", itoa(new)) | |
| 109 | + | q.Set("n", itoa(n)) | |
| 110 | + | return r.URL.Path + "?" + q.Encode() | |
| 111 | + | } | |
| @@ -0,0 +1,111 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package web | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "net/http" | |||
| 8 | + | "strconv" | |||
| 9 | + | "strings" | |||
| 10 | + | ||||
| 11 | + | "github.com/a-h/templ" | |||
| 12 | + | ||||
| 13 | + | "bnema.dev/ermite/internal/domain" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // maxContextLines bounds the lines one gap loads: the rest stays behind a | |||
| 17 | + | // smaller gap that loads the next lines. | |||
| 18 | + | const maxContextLines = 2000 | |||
| 19 | + | ||||
| 20 | + | // ContextRows is the hidden lines of a gap, for both layouts: they replace the | |||
| 21 | + | // rows of the gap. More is the gap that is left, when the file goes on. | |||
| 22 | + | type ContextRows struct { | |||
| 23 | + | Lines []DiffLine | |||
| 24 | + | More *DiffGap | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | // diffContext is GET /<repo>/context/<commit>?path=&old=&new=&n=: the lines of | |||
| 28 | + | // a file of a diff that its hunks hide, as rows of the diff tables. commit is | |||
| 29 | + | // the full id of the commit of the new side; path the file there; old and new | |||
| 30 | + | // the numbers of the first hidden line on each side, which move together (the | |||
| 31 | + | // lines are unchanged); n the number of lines, 0 for the rest of the file. | |||
| 32 | + | // | |||
| 33 | + | //hexcheck:ignore suspicious-business-logic-in-adapter cuts a file the use case opened into rows of the diff; no rule is decided here | |||
| 34 | + | func (s *Server) diffContext(ctx context.Context, r *http.Request) (templ.Component, int, error) { | |||
| 35 | + | sha := r.PathValue("sha") | |||
| 36 | + | if !isFullID(sha) { | |||
| 37 | + | return nil, 0, errNotFound | |||
| 38 | + | } | |||
| 39 | + | q := r.URL.Query() | |||
| 40 | + | num := func(k string, least int) (int, bool) { | |||
| 41 | + | v, err := strconv.Atoi(q.Get(k)) | |||
| 42 | + | return v, err == nil && v >= least && strconv.Itoa(v) == q.Get(k) | |||
| 43 | + | } | |||
| 44 | + | oldFrom, ok1 := num("old", 1) | |||
| 45 | + | newFrom, ok2 := num("new", 1) | |||
| 46 | + | n, ok3 := num("n", 0) | |||
| 47 | + | path := q.Get("path") | |||
| 48 | + | if !ok1 || !ok2 || !ok3 || path == "" { | |||
| 49 | + | return nil, 0, errNotFound | |||
| 50 | + | } | |||
| 51 | + | repo, err := domain.ParseRepoName(r.PathValue("repo")) | |||
| 52 | + | if err != nil { | |||
| 53 | + | return nil, 0, err | |||
| 54 | + | } | |||
| 55 | + | info, body, err := s.openBlobAt(ctx, repo, sha, path) | |||
| 56 | + | if err != nil { | |||
| 57 | + | return nil, 0, err | |||
| 58 | + | } | |||
| 59 | + | b := &blobStream{body: body, info: info} | |||
| 60 | + | defer b.close() | |||
| 61 | + | if info.Binary || info.Size > maxCodeBytes { | |||
| 62 | + | return nil, 0, errNotFound | |||
| 63 | + | } | |||
| 64 | + | if err := b.fill(int(info.Size)); err != nil { | |||
| 65 | + | return nil, 0, err | |||
| 66 | + | } | |||
| 67 | + | // Lines as Git numbers them: split on LF only (a lone CR is a character of | |||
| 68 | + | // its line, shown as a marker; the CR of a CRLF is dropped by segments). | |||
| 69 | + | text := strings.ToValidUTF8(string(b.buf), "\uFFFD") | |||
| 70 | + | lines := strings.Split(text, "\n") | |||
| 71 | + | noEOL := text != "" && !strings.HasSuffix(text, "\n") | |||
| 72 | + | if !noEOL { | |||
| 73 | + | lines = lines[:len(lines)-1] | |||
| 74 | + | } | |||
| 75 | + | if newFrom > len(lines)+1 { | |||
| 76 | + | return nil, 0, errNotFound | |||
| 77 | + | } | |||
| 78 | + | end := len(lines) | |||
| 79 | + | if n > 0 { | |||
| 80 | + | end = min(end, newFrom-1+n) | |||
| 81 | + | } | |||
| 82 | + | cut := min(end, newFrom-1+maxContextLines) | |||
| 83 | + | ||||
| 84 | + | var out ContextRows | |||
| 85 | + | for i := newFrom - 1; i < cut; i++ { | |||
| 86 | + | o, nw := oldFrom+i-(newFrom-1), i+1 | |||
| 87 | + | out.Lines = append(out.Lines, DiffLine{ | |||
| 88 | + | Kind: "ctx", Mark: " ", Segs: segments(lines[i]), | |||
| 89 | + | Old: itoa(o), New: itoa(nw), oldN: o, newN: nw, | |||
| 90 | + | NoEOL: noEOL && i == len(lines)-1, | |||
| 91 | + | }) | |||
| 92 | + | } | |||
| 93 | + | if cut < end { | |||
| 94 | + | rest := 0 | |||
| 95 | + | if n > 0 { | |||
| 96 | + | rest = end - cut | |||
| 97 | + | } | |||
| 98 | + | next := gapHref(r, oldFrom+cut-(newFrom-1), cut+1, rest) | |||
| 99 | + | out.More = &DiffGap{Key: q.Get("key"), Lines: rest, Src: next} | |||
| 100 | + | } | |||
| 101 | + | return DiffContext(out), http.StatusOK, nil | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | // gapHref is the address of the gap that starts at the lines old and new. | |||
| 105 | + | func gapHref(r *http.Request, old, new, n int) string { | |||
| 106 | + | q := r.URL.Query() | |||
| 107 | + | q.Set("old", itoa(old)) | |||
| 108 | + | q.Set("new", itoa(new)) | |||
| 109 | + | q.Set("n", itoa(n)) | |||
| 110 | + | return r.URL.Path + "?" + q.Encode() | |||
| 111 | + | } | |||
M internal/adapters/web/diff.go +83 -0
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "fmt" | |
| 8 | 8 | "html" | |
| 9 | 9 | "io" | |
| 10 | + | "net/url" | |
| 10 | 11 | "slices" | |
| 11 | 12 | "strings" | |
| 12 | 13 | "unicode/utf8" | |
| @@ -78,6 +79,28 @@ type DiffHunk struct { | |||
| 78 | 79 | Header string | |
| 79 | 80 | Lines []DiffLine | |
| 80 | 81 | Split []SplitRow | |
| 82 | + | // Gap is the unchanged lines before the hunk that the diff does not show, | |
| 83 | + | // nil when there are none or they cannot be loaded. | |
| 84 | + | Gap *DiffGap | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | // DiffGap is a run of unchanged lines a diff hides: a row of the diff that | |
| 88 | + | // loads them (diffContext) and takes their place. | |
| 89 | + | type DiffGap struct { | |
| 90 | + | // Key tells the gap apart within its file: the unified and the split | |
| 91 | + | // tables hold one row each for it. | |
| 92 | + | Key string | |
| 93 | + | // Lines is the number of hidden lines, 0 for the rest of the file. | |
| 94 | + | Lines int | |
| 95 | + | Src string | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // Label is the text of the row of the gap. | |
| 99 | + | func (g DiffGap) Label() string { | |
| 100 | + | if g.Lines == 0 { | |
| 101 | + | return "show the rest of the file" | |
| 102 | + | } | |
| 103 | + | return plural(g.Lines, "hidden line", "hidden lines") | |
| 81 | 104 | } | |
| 82 | 105 | ||
| 83 | 106 | // DiffFile is one changed file. | |
| @@ -121,6 +144,66 @@ type DiffFile struct { | |||
| 121 | 144 | // Loose are the comments of a file loaded with its window that are on a | |
| 122 | 145 | // line its diff does not show: listed at the end of the file. | |
| 123 | 146 | Loose []CommentView | |
| 147 | + | // Tail is the rest of the file after its last hunk, nil when it cannot be | |
| 148 | + | // loaded. Its length is not known until it is. | |
| 149 | + | Tail *DiffGap | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | // diffContextLines is the number of unchanged lines Git shows around a change | |
| 153 | + | // (the -U3 of the diffs of the Git adapter, Git's default). | |
| 154 | + | const diffContextLines = 3 | |
| 155 | + | ||
| 156 | + | // contextGaps gives the files of a diff the gaps around their hunks, which | |
| 157 | + | // load the hidden lines of the new side from base (/<repo>/context/<commit>, | |
| 158 | + | // the commit of the new side). Only a file whose two sides hold text has | |
| 159 | + | // them: an added or a deleted file shows every line already. | |
| 160 | + | func contextGaps(out []DiffFile, files []domain.FileDiff, base string) { | |
| 161 | + | for i, f := range files { | |
| 162 | + | if i >= len(out) || f.Binary || f.Truncated || len(f.Hunks) == 0 || len(out[i].Hunks) != len(f.Hunks) { | |
| 163 | + | continue | |
| 164 | + | } | |
| 165 | + | switch f.Status { | |
| 166 | + | case domain.FileModified, domain.FileRenamed, domain.FileCopied: | |
| 167 | + | default: | |
| 168 | + | continue | |
| 169 | + | } | |
| 170 | + | gap := func(key string, oldFrom, newFrom, n int) *DiffGap { | |
| 171 | + | q := url.Values{} | |
| 172 | + | q.Set("path", f.NewPath) | |
| 173 | + | q.Set("old", itoa(oldFrom)) | |
| 174 | + | q.Set("new", itoa(newFrom)) | |
| 175 | + | q.Set("n", itoa(n)) | |
| 176 | + | q.Set("key", key) | |
| 177 | + | return &DiffGap{Key: key, Lines: n, Src: base + "?" + q.Encode()} | |
| 178 | + | } | |
| 179 | + | // first is the first line of a side of a hunk: a side without lines | |
| 180 | + | // names the line before the hunk ("-5,0"). | |
| 181 | + | first := func(start, lines int) int { | |
| 182 | + | if lines == 0 { | |
| 183 | + | return start + 1 | |
| 184 | + | } | |
| 185 | + | return start | |
| 186 | + | } | |
| 187 | + | oldNext, newNext := 1, 1 | |
| 188 | + | for j, h := range f.Hunks { | |
| 189 | + | oldFirst, newFirst := first(h.OldStart, h.OldLines), first(h.NewStart, h.NewLines) | |
| 190 | + | if n := newFirst - newNext; n > 0 && n == oldFirst-oldNext { | |
| 191 | + | out[i].Hunks[j].Gap = gap("g"+itoa(j), oldNext, newNext, n) | |
| 192 | + | } | |
| 193 | + | oldNext, newNext = oldFirst+h.OldLines, newFirst+h.NewLines | |
| 194 | + | } | |
| 195 | + | // Git ends a hunk diffContextLines lines after its last change, or at | |
| 196 | + | // the end of the file: fewer lines after the last change mean the | |
| 197 | + | // file ends there. | |
| 198 | + | last := f.Hunks[len(f.Hunks)-1].Lines | |
| 199 | + | trailing := 0 | |
| 200 | + | for k := len(last) - 1; k >= 0 && last[k].Kind == domain.LineContext && !last[k].NoNewlineAtEOF; k-- { | |
| 201 | + | trailing++ | |
| 202 | + | } | |
| 203 | + | if trailing >= diffContextLines { | |
| 204 | + | out[i].Tail = gap("tail", oldNext, newNext, 0) | |
| 205 | + | } | |
| 206 | + | } | |
| 124 | 207 | } | |
| 125 | 208 | ||
| 126 | 209 | // gutterDigits is the number of digits of the largest line number of the | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | "html" | 8 | "html" | ||
| 9 | "io" | 9 | "io" | ||
| 10 | + | "net/url" | |||
| 10 | "slices" | 11 | "slices" | ||
| 11 | "strings" | 12 | "strings" | ||
| 12 | "unicode/utf8" | 13 | "unicode/utf8" | ||
| @@ -78,6 +79,28 @@ type DiffHunk struct { | |||||
| 78 | Header string | 79 | Header string | ||
| 79 | Lines []DiffLine | 80 | Lines []DiffLine | ||
| 80 | Split []SplitRow | 81 | Split []SplitRow | ||
| 82 | + | // Gap is the unchanged lines before the hunk that the diff does not show, | |||
| 83 | + | // nil when there are none or they cannot be loaded. | |||
| 84 | + | Gap *DiffGap | |||
| 85 | + | } | |||
| 86 | + | ||||
| 87 | + | // DiffGap is a run of unchanged lines a diff hides: a row of the diff that | |||
| 88 | + | // loads them (diffContext) and takes their place. | |||
| 89 | + | type DiffGap struct { | |||
| 90 | + | // Key tells the gap apart within its file: the unified and the split | |||
| 91 | + | // tables hold one row each for it. | |||
| 92 | + | Key string | |||
| 93 | + | // Lines is the number of hidden lines, 0 for the rest of the file. | |||
| 94 | + | Lines int | |||
| 95 | + | Src string | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // Label is the text of the row of the gap. | |||
| 99 | + | func (g DiffGap) Label() string { | |||
| 100 | + | if g.Lines == 0 { | |||
| 101 | + | return "show the rest of the file" | |||
| 102 | + | } | |||
| 103 | + | return plural(g.Lines, "hidden line", "hidden lines") | |||
| 81 | } | 104 | } | ||
| 82 | 105 | ||||
| 83 | // DiffFile is one changed file. | 106 | // DiffFile is one changed file. | ||
| @@ -121,6 +144,66 @@ type DiffFile struct { | |||||
| 121 | // Loose are the comments of a file loaded with its window that are on a | 144 | // Loose are the comments of a file loaded with its window that are on a | ||
| 122 | // line its diff does not show: listed at the end of the file. | 145 | // line its diff does not show: listed at the end of the file. | ||
| 123 | Loose []CommentView | 146 | Loose []CommentView | ||
| 147 | + | // Tail is the rest of the file after its last hunk, nil when it cannot be | |||
| 148 | + | // loaded. Its length is not known until it is. | |||
| 149 | + | Tail *DiffGap | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | // diffContextLines is the number of unchanged lines Git shows around a change | |||
| 153 | + | // (the -U3 of the diffs of the Git adapter, Git's default). | |||
| 154 | + | const diffContextLines = 3 | |||
| 155 | + | ||||
| 156 | + | // contextGaps gives the files of a diff the gaps around their hunks, which | |||
| 157 | + | // load the hidden lines of the new side from base (/<repo>/context/<commit>, | |||
| 158 | + | // the commit of the new side). Only a file whose two sides hold text has | |||
| 159 | + | // them: an added or a deleted file shows every line already. | |||
| 160 | + | func contextGaps(out []DiffFile, files []domain.FileDiff, base string) { | |||
| 161 | + | for i, f := range files { | |||
| 162 | + | if i >= len(out) || f.Binary || f.Truncated || len(f.Hunks) == 0 || len(out[i].Hunks) != len(f.Hunks) { | |||
| 163 | + | continue | |||
| 164 | + | } | |||
| 165 | + | switch f.Status { | |||
| 166 | + | case domain.FileModified, domain.FileRenamed, domain.FileCopied: | |||
| 167 | + | default: | |||
| 168 | + | continue | |||
| 169 | + | } | |||
| 170 | + | gap := func(key string, oldFrom, newFrom, n int) *DiffGap { | |||
| 171 | + | q := url.Values{} | |||
| 172 | + | q.Set("path", f.NewPath) | |||
| 173 | + | q.Set("old", itoa(oldFrom)) | |||
| 174 | + | q.Set("new", itoa(newFrom)) | |||
| 175 | + | q.Set("n", itoa(n)) | |||
| 176 | + | q.Set("key", key) | |||
| 177 | + | return &DiffGap{Key: key, Lines: n, Src: base + "?" + q.Encode()} | |||
| 178 | + | } | |||
| 179 | + | // first is the first line of a side of a hunk: a side without lines | |||
| 180 | + | // names the line before the hunk ("-5,0"). | |||
| 181 | + | first := func(start, lines int) int { | |||
| 182 | + | if lines == 0 { | |||
| 183 | + | return start + 1 | |||
| 184 | + | } | |||
| 185 | + | return start | |||
| 186 | + | } | |||
| 187 | + | oldNext, newNext := 1, 1 | |||
| 188 | + | for j, h := range f.Hunks { | |||
| 189 | + | oldFirst, newFirst := first(h.OldStart, h.OldLines), first(h.NewStart, h.NewLines) | |||
| 190 | + | if n := newFirst - newNext; n > 0 && n == oldFirst-oldNext { | |||
| 191 | + | out[i].Hunks[j].Gap = gap("g"+itoa(j), oldNext, newNext, n) | |||
| 192 | + | } | |||
| 193 | + | oldNext, newNext = oldFirst+h.OldLines, newFirst+h.NewLines | |||
| 194 | + | } | |||
| 195 | + | // Git ends a hunk diffContextLines lines after its last change, or at | |||
| 196 | + | // the end of the file: fewer lines after the last change mean the | |||
| 197 | + | // file ends there. | |||
| 198 | + | last := f.Hunks[len(f.Hunks)-1].Lines | |||
| 199 | + | trailing := 0 | |||
| 200 | + | for k := len(last) - 1; k >= 0 && last[k].Kind == domain.LineContext && !last[k].NoNewlineAtEOF; k-- { | |||
| 201 | + | trailing++ | |||
| 202 | + | } | |||
| 203 | + | if trailing >= diffContextLines { | |||
| 204 | + | out[i].Tail = gap("tail", oldNext, newNext, 0) | |||
| 205 | + | } | |||
| 206 | + | } | |||
| 124 | } | 207 | } | ||
| 125 | 208 | ||||
| 126 | // gutterDigits is the number of digits of the largest line number of the | 209 | // gutterDigits is the number of digits of the largest line number of the | ||
M internal/adapters/web/diff.templ +60 -0
| @@ -212,6 +212,9 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||
| 212 | 212 | <table class="dt uni" aria-label={ f.Path + ", unified" }> | |
| 213 | 213 | for _, h := range f.Hunks { | |
| 214 | 214 | <tbody> | |
| 215 | + | if h.Gap != nil { | |
| 216 | + | @gapRow(*h.Gap, 4) | |
| 217 | + | } | |
| 215 | 218 | <tr class="hunk"><td colspan="4">{ h.Header }</td></tr> | |
| 216 | 219 | for _, ln := range h.Lines { | |
| 217 | 220 | <tr class={ ln.Kind }> | |
| @@ -229,6 +232,11 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||
| 229 | 232 | } | |
| 230 | 233 | </tbody> | |
| 231 | 234 | } | |
| 235 | + | if f.Tail != nil { | |
| 236 | + | <tbody> | |
| 237 | + | @gapRow(*f.Tail, 4) | |
| 238 | + | </tbody> | |
| 239 | + | } | |
| 232 | 240 | </table> | |
| 233 | 241 | } | |
| 234 | 242 | if p.ShowSplit { | |
| @@ -236,6 +244,9 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||
| 236 | 244 | <colgroup><col class="c-ln"/><col class="c-m"/><col/><col class="c-ln"/><col class="c-m"/><col/></colgroup> | |
| 237 | 245 | for _, h := range f.Hunks { | |
| 238 | 246 | <tbody> | |
| 247 | + | if h.Gap != nil { | |
| 248 | + | @gapRow(*h.Gap, 6) | |
| 249 | + | } | |
| 239 | 250 | <tr class="hunk"><td colspan="6">{ h.Header }</td></tr> | |
| 240 | 251 | for _, row := range h.Split { | |
| 241 | 252 | <tr> | |
| @@ -250,6 +261,11 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||
| 250 | 261 | } | |
| 251 | 262 | </tbody> | |
| 252 | 263 | } | |
| 264 | + | if f.Tail != nil { | |
| 265 | + | <tbody> | |
| 266 | + | @gapRow(*f.Tail, 6) | |
| 267 | + | </tbody> | |
| 268 | + | } | |
| 253 | 269 | </table> | |
| 254 | 270 | } | |
| 255 | 271 | </div> | |
| @@ -286,6 +302,50 @@ templ MRFilesMoved() { | |||
| 286 | 302 | <p class="notice" data-moved>this merge request changed since the page was loaded — <a href="">reload</a></p> | |
| 287 | 303 | } | |
| 288 | 304 | ||
| 305 | + | // gapRow is the row of the hidden lines before a hunk, or of the rest of the | |
| 306 | + | // file: a button that loads them in their place (ermite.js, ermite.expand). | |
| 307 | + | // Without scripts nothing loads them: hidden. | |
| 308 | + | templ gapRow(g DiffGap, cols int) { | |
| 309 | + | <tr class="gap" data-js data-gap={ g.Key }> | |
| 310 | + | <td colspan={ itoa(cols) }> | |
| 311 | + | <button type="button" data-src={ g.Src }> | |
| 312 | + | <span aria-hidden="true">⋯</span> { g.Label() } | |
| 313 | + | </button> | |
| 314 | + | </td> | |
| 315 | + | </tr> | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | // DiffContext is the hidden lines of a gap, for /context: one body a layout, | |
| 319 | + | // whose rows replace the rows of the gap in the table of that layout. | |
| 320 | + | templ DiffContext(c ContextRows) { | |
| 321 | + | <table> | |
| 322 | + | <tbody data-for="uni"> | |
| 323 | + | for _, ln := range c.Lines { | |
| 324 | + | <tr class="ctx"> | |
| 325 | + | <td class="ln">{ ln.Old }</td> | |
| 326 | + | <td class="ln">{ ln.New }</td> | |
| 327 | + | <td class="m">{ ln.Mark }</td> | |
| 328 | + | <td class="tx">@codeLine(ln)</td> | |
| 329 | + | </tr> | |
| 330 | + | } | |
| 331 | + | if c.More != nil { | |
| 332 | + | @gapRow(*c.More, 4) | |
| 333 | + | } | |
| 334 | + | </tbody> | |
| 335 | + | <tbody data-for="spl"> | |
| 336 | + | for i := range c.Lines { | |
| 337 | + | <tr> | |
| 338 | + | @splitSide(&c.Lines[i], true) | |
| 339 | + | @splitSide(&c.Lines[i], false) | |
| 340 | + | </tr> | |
| 341 | + | } | |
| 342 | + | if c.More != nil { | |
| 343 | + | @gapRow(*c.More, 6) | |
| 344 | + | } | |
| 345 | + | </tbody> | |
| 346 | + | </table> | |
| 347 | + | } | |
| 348 | + | ||
| 289 | 349 | // splitSide is one half of a row of the split view; a side without a line is | |
| 290 | 350 | // an empty cell. | |
| 291 | 351 | templ splitSide(ln *DiffLine, old bool) { | |
| @@ -212,6 +212,9 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||||
| 212 | <table class="dt uni" aria-label={ f.Path + ", unified" }> | 212 | <table class="dt uni" aria-label={ f.Path + ", unified" }> | ||
| 213 | for _, h := range f.Hunks { | 213 | for _, h := range f.Hunks { | ||
| 214 | <tbody> | 214 | <tbody> | ||
| 215 | + | if h.Gap != nil { | |||
| 216 | + | @gapRow(*h.Gap, 4) | |||
| 217 | + | } | |||
| 215 | <tr class="hunk"><td colspan="4">{ h.Header }</td></tr> | 218 | <tr class="hunk"><td colspan="4">{ h.Header }</td></tr> | ||
| 216 | for _, ln := range h.Lines { | 219 | for _, ln := range h.Lines { | ||
| 217 | <tr class={ ln.Kind }> | 220 | <tr class={ ln.Kind }> | ||
| @@ -229,6 +232,11 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||||
| 229 | } | 232 | } | ||
| 230 | </tbody> | 233 | </tbody> | ||
| 231 | } | 234 | } | ||
| 235 | + | if f.Tail != nil { | |||
| 236 | + | <tbody> | |||
| 237 | + | @gapRow(*f.Tail, 4) | |||
| 238 | + | </tbody> | |||
| 239 | + | } | |||
| 232 | </table> | 240 | </table> | ||
| 233 | } | 241 | } | ||
| 234 | if p.ShowSplit { | 242 | if p.ShowSplit { | ||
| @@ -236,6 +244,9 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||||
| 236 | <colgroup><col class="c-ln"/><col class="c-m"/><col/><col class="c-ln"/><col class="c-m"/><col/></colgroup> | 244 | <colgroup><col class="c-ln"/><col class="c-m"/><col/><col class="c-ln"/><col class="c-m"/><col/></colgroup> | ||
| 237 | for _, h := range f.Hunks { | 245 | for _, h := range f.Hunks { | ||
| 238 | <tbody> | 246 | <tbody> | ||
| 247 | + | if h.Gap != nil { | |||
| 248 | + | @gapRow(*h.Gap, 6) | |||
| 249 | + | } | |||
| 239 | <tr class="hunk"><td colspan="6">{ h.Header }</td></tr> | 250 | <tr class="hunk"><td colspan="6">{ h.Header }</td></tr> | ||
| 240 | for _, row := range h.Split { | 251 | for _, row := range h.Split { | ||
| 241 | <tr> | 252 | <tr> | ||
| @@ -250,6 +261,11 @@ templ DiffFileView(f DiffFile, p DiffView) { | |||||
| 250 | } | 261 | } | ||
| 251 | </tbody> | 262 | </tbody> | ||
| 252 | } | 263 | } | ||
| 264 | + | if f.Tail != nil { | |||
| 265 | + | <tbody> | |||
| 266 | + | @gapRow(*f.Tail, 6) | |||
| 267 | + | </tbody> | |||
| 268 | + | } | |||
| 253 | </table> | 269 | </table> | ||
| 254 | } | 270 | } | ||
| 255 | </div> | 271 | </div> | ||
| @@ -286,6 +302,50 @@ templ MRFilesMoved() { | |||||
| 286 | <p class="notice" data-moved>this merge request changed since the page was loaded — <a href="">reload</a></p> | 302 | <p class="notice" data-moved>this merge request changed since the page was loaded — <a href="">reload</a></p> | ||
| 287 | } | 303 | } | ||
| 288 | 304 | ||||
| 305 | + | // gapRow is the row of the hidden lines before a hunk, or of the rest of the | |||
| 306 | + | // file: a button that loads them in their place (ermite.js, ermite.expand). | |||
| 307 | + | // Without scripts nothing loads them: hidden. | |||
| 308 | + | templ gapRow(g DiffGap, cols int) { | |||
| 309 | + | <tr class="gap" data-js data-gap={ g.Key }> | |||
| 310 | + | <td colspan={ itoa(cols) }> | |||
| 311 | + | <button type="button" data-src={ g.Src }> | |||
| 312 | + | <span aria-hidden="true">⋯</span> { g.Label() } | |||
| 313 | + | </button> | |||
| 314 | + | </td> | |||
| 315 | + | </tr> | |||
| 316 | + | } | |||
| 317 | + | ||||
| 318 | + | // DiffContext is the hidden lines of a gap, for /context: one body a layout, | |||
| 319 | + | // whose rows replace the rows of the gap in the table of that layout. | |||
| 320 | + | templ DiffContext(c ContextRows) { | |||
| 321 | + | <table> | |||
| 322 | + | <tbody data-for="uni"> | |||
| 323 | + | for _, ln := range c.Lines { | |||
| 324 | + | <tr class="ctx"> | |||
| 325 | + | <td class="ln">{ ln.Old }</td> | |||
| 326 | + | <td class="ln">{ ln.New }</td> | |||
| 327 | + | <td class="m">{ ln.Mark }</td> | |||
| 328 | + | <td class="tx">@codeLine(ln)</td> | |||
| 329 | + | </tr> | |||
| 330 | + | } | |||
| 331 | + | if c.More != nil { | |||
| 332 | + | @gapRow(*c.More, 4) | |||
| 333 | + | } | |||
| 334 | + | </tbody> | |||
| 335 | + | <tbody data-for="spl"> | |||
| 336 | + | for i := range c.Lines { | |||
| 337 | + | <tr> | |||
| 338 | + | @splitSide(&c.Lines[i], true) | |||
| 339 | + | @splitSide(&c.Lines[i], false) | |||
| 340 | + | </tr> | |||
| 341 | + | } | |||
| 342 | + | if c.More != nil { | |||
| 343 | + | @gapRow(*c.More, 6) | |||
| 344 | + | } | |||
| 345 | + | </tbody> | |||
| 346 | + | </table> | |||
| 347 | + | } | |||
| 348 | + | ||||
| 289 | // splitSide is one half of a row of the split view; a side without a line is | 349 | // splitSide is one half of a row of the split view; a side without a line is | ||
| 290 | // an empty cell. | 350 | // an empty cell. | ||
| 291 | templ splitSide(ln *DiffLine, old bool) { | 351 | templ splitSide(ln *DiffLine, old bool) { | ||
M internal/adapters/web/diff_templ.go +339 -85
| @@ -1284,20 +1284,30 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1284 | 1284 | return templ_7745c5c3_Err | |
| 1285 | 1285 | } | |
| 1286 | 1286 | for _, h := range f.Hunks { | |
| 1287 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<tbody><tr class=\"hunk\"><td colspan=\"4\">") | |
| 1287 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<tbody>") | |
| 1288 | + | if templ_7745c5c3_Err != nil { | |
| 1289 | + | return templ_7745c5c3_Err | |
| 1290 | + | } | |
| 1291 | + | if h.Gap != nil { | |
| 1292 | + | templ_7745c5c3_Err = gapRow(*h.Gap, 4).Render(ctx, templ_7745c5c3_Buffer) | |
| 1293 | + | if templ_7745c5c3_Err != nil { | |
| 1294 | + | return templ_7745c5c3_Err | |
| 1295 | + | } | |
| 1296 | + | } | |
| 1297 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "<tr class=\"hunk\"><td colspan=\"4\">") | |
| 1288 | 1298 | if templ_7745c5c3_Err != nil { | |
| 1289 | 1299 | return templ_7745c5c3_Err | |
| 1290 | 1300 | } | |
| 1291 | 1301 | var templ_7745c5c3_Var69 string | |
| 1292 | 1302 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | |
| 1293 | 1303 | if templ_7745c5c3_Err != nil { | |
| 1294 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 215, Col: 51} | |
| 1304 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 218, Col: 51} | |
| 1295 | 1305 | } | |
| 1296 | 1306 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) | |
| 1297 | 1307 | if templ_7745c5c3_Err != nil { | |
| 1298 | 1308 | return templ_7745c5c3_Err | |
| 1299 | 1309 | } | |
| 1300 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td></tr>") | |
| 1310 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td></tr>") | |
| 1301 | 1311 | if templ_7745c5c3_Err != nil { | |
| 1302 | 1312 | return templ_7745c5c3_Err | |
| 1303 | 1313 | } | |
| @@ -1307,7 +1317,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1307 | 1317 | if templ_7745c5c3_Err != nil { | |
| 1308 | 1318 | return templ_7745c5c3_Err | |
| 1309 | 1319 | } | |
| 1310 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<tr class=\"") | |
| 1320 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "<tr class=\"") | |
| 1311 | 1321 | if templ_7745c5c3_Err != nil { | |
| 1312 | 1322 | return templ_7745c5c3_Err | |
| 1313 | 1323 | } | |
| @@ -1320,7 +1330,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1320 | 1330 | if templ_7745c5c3_Err != nil { | |
| 1321 | 1331 | return templ_7745c5c3_Err | |
| 1322 | 1332 | } | |
| 1323 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\"><td class=\"ln\">") | |
| 1333 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\"><td class=\"ln\">") | |
| 1324 | 1334 | if templ_7745c5c3_Err != nil { | |
| 1325 | 1335 | return templ_7745c5c3_Err | |
| 1326 | 1336 | } | |
| @@ -1328,7 +1338,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1328 | 1338 | if templ_7745c5c3_Err != nil { | |
| 1329 | 1339 | return templ_7745c5c3_Err | |
| 1330 | 1340 | } | |
| 1331 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "</td><td class=\"ln\">") | |
| 1341 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</td><td class=\"ln\">") | |
| 1332 | 1342 | if templ_7745c5c3_Err != nil { | |
| 1333 | 1343 | return templ_7745c5c3_Err | |
| 1334 | 1344 | } | |
| @@ -1336,20 +1346,20 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1336 | 1346 | if templ_7745c5c3_Err != nil { | |
| 1337 | 1347 | return templ_7745c5c3_Err | |
| 1338 | 1348 | } | |
| 1339 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</td><td class=\"m\">") | |
| 1349 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</td><td class=\"m\">") | |
| 1340 | 1350 | if templ_7745c5c3_Err != nil { | |
| 1341 | 1351 | return templ_7745c5c3_Err | |
| 1342 | 1352 | } | |
| 1343 | 1353 | var templ_7745c5c3_Var72 string | |
| 1344 | 1354 | templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | |
| 1345 | 1355 | if templ_7745c5c3_Err != nil { | |
| 1346 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 220, Col: 33} | |
| 1356 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 223, Col: 33} | |
| 1347 | 1357 | } | |
| 1348 | 1358 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) | |
| 1349 | 1359 | if templ_7745c5c3_Err != nil { | |
| 1350 | 1360 | return templ_7745c5c3_Err | |
| 1351 | 1361 | } | |
| 1352 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</td><td class=\"tx\">") | |
| 1362 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</td><td class=\"tx\">") | |
| 1353 | 1363 | if templ_7745c5c3_Err != nil { | |
| 1354 | 1364 | return templ_7745c5c3_Err | |
| 1355 | 1365 | } | |
| @@ -1357,12 +1367,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1357 | 1367 | if templ_7745c5c3_Err != nil { | |
| 1358 | 1368 | return templ_7745c5c3_Err | |
| 1359 | 1369 | } | |
| 1360 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</td></tr>") | |
| 1370 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</td></tr>") | |
| 1361 | 1371 | if templ_7745c5c3_Err != nil { | |
| 1362 | 1372 | return templ_7745c5c3_Err | |
| 1363 | 1373 | } | |
| 1364 | 1374 | if len(ln.Notes) > 0 || ln.Form != nil { | |
| 1365 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<tr class=\"fnote\"><td colspan=\"3\"></td><td>") | |
| 1375 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "<tr class=\"fnote\"><td colspan=\"3\"></td><td>") | |
| 1366 | 1376 | if templ_7745c5c3_Err != nil { | |
| 1367 | 1377 | return templ_7745c5c3_Err | |
| 1368 | 1378 | } | |
| @@ -1370,60 +1380,84 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1370 | 1380 | if templ_7745c5c3_Err != nil { | |
| 1371 | 1381 | return templ_7745c5c3_Err | |
| 1372 | 1382 | } | |
| 1373 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</td></tr>") | |
| 1383 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</td></tr>") | |
| 1374 | 1384 | if templ_7745c5c3_Err != nil { | |
| 1375 | 1385 | return templ_7745c5c3_Err | |
| 1376 | 1386 | } | |
| 1377 | 1387 | } | |
| 1378 | 1388 | } | |
| 1379 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</tbody>") | |
| 1389 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</tbody> ") | |
| 1390 | + | if templ_7745c5c3_Err != nil { | |
| 1391 | + | return templ_7745c5c3_Err | |
| 1392 | + | } | |
| 1393 | + | } | |
| 1394 | + | if f.Tail != nil { | |
| 1395 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<tbody>") | |
| 1396 | + | if templ_7745c5c3_Err != nil { | |
| 1397 | + | return templ_7745c5c3_Err | |
| 1398 | + | } | |
| 1399 | + | templ_7745c5c3_Err = gapRow(*f.Tail, 4).Render(ctx, templ_7745c5c3_Buffer) | |
| 1400 | + | if templ_7745c5c3_Err != nil { | |
| 1401 | + | return templ_7745c5c3_Err | |
| 1402 | + | } | |
| 1403 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</tbody>") | |
| 1380 | 1404 | if templ_7745c5c3_Err != nil { | |
| 1381 | 1405 | return templ_7745c5c3_Err | |
| 1382 | 1406 | } | |
| 1383 | 1407 | } | |
| 1384 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</table>") | |
| 1408 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</table>") | |
| 1385 | 1409 | if templ_7745c5c3_Err != nil { | |
| 1386 | 1410 | return templ_7745c5c3_Err | |
| 1387 | 1411 | } | |
| 1388 | 1412 | } | |
| 1389 | 1413 | if p.ShowSplit { | |
| 1390 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<table class=\"dt spl\" aria-label=\"") | |
| 1414 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<table class=\"dt spl\" aria-label=\"") | |
| 1391 | 1415 | if templ_7745c5c3_Err != nil { | |
| 1392 | 1416 | return templ_7745c5c3_Err | |
| 1393 | 1417 | } | |
| 1394 | 1418 | var templ_7745c5c3_Var73 string | |
| 1395 | 1419 | templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path + ", split") | |
| 1396 | 1420 | if templ_7745c5c3_Err != nil { | |
| 1397 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 235, Col: 58} | |
| 1421 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 243, Col: 58} | |
| 1398 | 1422 | } | |
| 1399 | 1423 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var73) | |
| 1400 | 1424 | if templ_7745c5c3_Err != nil { | |
| 1401 | 1425 | return templ_7745c5c3_Err | |
| 1402 | 1426 | } | |
| 1403 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "\"><colgroup><col class=\"c-ln\"><col class=\"c-m\"><col><col class=\"c-ln\"><col class=\"c-m\"><col></colgroup> ") | |
| 1427 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "\"><colgroup><col class=\"c-ln\"><col class=\"c-m\"><col><col class=\"c-ln\"><col class=\"c-m\"><col></colgroup> ") | |
| 1404 | 1428 | if templ_7745c5c3_Err != nil { | |
| 1405 | 1429 | return templ_7745c5c3_Err | |
| 1406 | 1430 | } | |
| 1407 | 1431 | for _, h := range f.Hunks { | |
| 1408 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<tbody><tr class=\"hunk\"><td colspan=\"6\">") | |
| 1432 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<tbody>") | |
| 1433 | + | if templ_7745c5c3_Err != nil { | |
| 1434 | + | return templ_7745c5c3_Err | |
| 1435 | + | } | |
| 1436 | + | if h.Gap != nil { | |
| 1437 | + | templ_7745c5c3_Err = gapRow(*h.Gap, 6).Render(ctx, templ_7745c5c3_Buffer) | |
| 1438 | + | if templ_7745c5c3_Err != nil { | |
| 1439 | + | return templ_7745c5c3_Err | |
| 1440 | + | } | |
| 1441 | + | } | |
| 1442 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<tr class=\"hunk\"><td colspan=\"6\">") | |
| 1409 | 1443 | if templ_7745c5c3_Err != nil { | |
| 1410 | 1444 | return templ_7745c5c3_Err | |
| 1411 | 1445 | } | |
| 1412 | 1446 | var templ_7745c5c3_Var74 string | |
| 1413 | 1447 | templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | |
| 1414 | 1448 | if templ_7745c5c3_Err != nil { | |
| 1415 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 239, Col: 51} | |
| 1449 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 250, Col: 51} | |
| 1416 | 1450 | } | |
| 1417 | 1451 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) | |
| 1418 | 1452 | if templ_7745c5c3_Err != nil { | |
| 1419 | 1453 | return templ_7745c5c3_Err | |
| 1420 | 1454 | } | |
| 1421 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "</td></tr>") | |
| 1455 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "</td></tr>") | |
| 1422 | 1456 | if templ_7745c5c3_Err != nil { | |
| 1423 | 1457 | return templ_7745c5c3_Err | |
| 1424 | 1458 | } | |
| 1425 | 1459 | for _, row := range h.Split { | |
| 1426 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "<tr>") | |
| 1460 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "<tr>") | |
| 1427 | 1461 | if templ_7745c5c3_Err != nil { | |
| 1428 | 1462 | return templ_7745c5c3_Err | |
| 1429 | 1463 | } | |
| @@ -1435,12 +1469,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1435 | 1469 | if templ_7745c5c3_Err != nil { | |
| 1436 | 1470 | return templ_7745c5c3_Err | |
| 1437 | 1471 | } | |
| 1438 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "</tr>") | |
| 1472 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</tr>") | |
| 1439 | 1473 | if templ_7745c5c3_Err != nil { | |
| 1440 | 1474 | return templ_7745c5c3_Err | |
| 1441 | 1475 | } | |
| 1442 | 1476 | if notes, form := row.Notes(), row.Form(); len(notes) > 0 || form != nil { | |
| 1443 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<tr class=\"fnote\"><td colspan=\"6\">") | |
| 1477 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "<tr class=\"fnote\"><td colspan=\"6\">") | |
| 1444 | 1478 | if templ_7745c5c3_Err != nil { | |
| 1445 | 1479 | return templ_7745c5c3_Err | |
| 1446 | 1480 | } | |
| @@ -1448,28 +1482,42 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1448 | 1482 | if templ_7745c5c3_Err != nil { | |
| 1449 | 1483 | return templ_7745c5c3_Err | |
| 1450 | 1484 | } | |
| 1451 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "</td></tr>") | |
| 1485 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</td></tr>") | |
| 1452 | 1486 | if templ_7745c5c3_Err != nil { | |
| 1453 | 1487 | return templ_7745c5c3_Err | |
| 1454 | 1488 | } | |
| 1455 | 1489 | } | |
| 1456 | 1490 | } | |
| 1457 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "</tbody>") | |
| 1491 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</tbody> ") | |
| 1458 | 1492 | if templ_7745c5c3_Err != nil { | |
| 1459 | 1493 | return templ_7745c5c3_Err | |
| 1460 | 1494 | } | |
| 1461 | 1495 | } | |
| 1462 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</table>") | |
| 1496 | + | if f.Tail != nil { | |
| 1497 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<tbody>") | |
| 1498 | + | if templ_7745c5c3_Err != nil { | |
| 1499 | + | return templ_7745c5c3_Err | |
| 1500 | + | } | |
| 1501 | + | templ_7745c5c3_Err = gapRow(*f.Tail, 6).Render(ctx, templ_7745c5c3_Buffer) | |
| 1502 | + | if templ_7745c5c3_Err != nil { | |
| 1503 | + | return templ_7745c5c3_Err | |
| 1504 | + | } | |
| 1505 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "</tbody>") | |
| 1506 | + | if templ_7745c5c3_Err != nil { | |
| 1507 | + | return templ_7745c5c3_Err | |
| 1508 | + | } | |
| 1509 | + | } | |
| 1510 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "</table>") | |
| 1463 | 1511 | if templ_7745c5c3_Err != nil { | |
| 1464 | 1512 | return templ_7745c5c3_Err | |
| 1465 | 1513 | } | |
| 1466 | 1514 | } | |
| 1467 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "</div>") | |
| 1515 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "</div>") | |
| 1468 | 1516 | if templ_7745c5c3_Err != nil { | |
| 1469 | 1517 | return templ_7745c5c3_Err | |
| 1470 | 1518 | } | |
| 1471 | 1519 | } else { | |
| 1472 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "<p class=\"note pad\">") | |
| 1520 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<p class=\"note pad\">") | |
| 1473 | 1521 | if templ_7745c5c3_Err != nil { | |
| 1474 | 1522 | return templ_7745c5c3_Err | |
| 1475 | 1523 | } | |
| @@ -1477,25 +1525,25 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1477 | 1525 | var templ_7745c5c3_Var75 string | |
| 1478 | 1526 | templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(f.Note) | |
| 1479 | 1527 | if templ_7745c5c3_Err != nil { | |
| 1480 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 259, Col: 13} | |
| 1528 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 275, Col: 13} | |
| 1481 | 1529 | } | |
| 1482 | 1530 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) | |
| 1483 | 1531 | if templ_7745c5c3_Err != nil { | |
| 1484 | 1532 | return templ_7745c5c3_Err | |
| 1485 | 1533 | } | |
| 1486 | 1534 | } else { | |
| 1487 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "no textual change.") | |
| 1535 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "no textual change.") | |
| 1488 | 1536 | if templ_7745c5c3_Err != nil { | |
| 1489 | 1537 | return templ_7745c5c3_Err | |
| 1490 | 1538 | } | |
| 1491 | 1539 | } | |
| 1492 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "</p>") | |
| 1540 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "</p>") | |
| 1493 | 1541 | if templ_7745c5c3_Err != nil { | |
| 1494 | 1542 | return templ_7745c5c3_Err | |
| 1495 | 1543 | } | |
| 1496 | 1544 | } | |
| 1497 | 1545 | if len(f.Loose) > 0 { | |
| 1498 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "<div class=\"dloose\">") | |
| 1546 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<div class=\"dloose\">") | |
| 1499 | 1547 | if templ_7745c5c3_Err != nil { | |
| 1500 | 1548 | return templ_7745c5c3_Err | |
| 1501 | 1549 | } | |
| @@ -1505,12 +1553,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||
| 1505 | 1553 | return templ_7745c5c3_Err | |
| 1506 | 1554 | } | |
| 1507 | 1555 | } | |
| 1508 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "</div>") | |
| 1556 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "</div>") | |
| 1509 | 1557 | if templ_7745c5c3_Err != nil { | |
| 1510 | 1558 | return templ_7745c5c3_Err | |
| 1511 | 1559 | } | |
| 1512 | 1560 | } | |
| 1513 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "</details>") | |
| 1561 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</details>") | |
| 1514 | 1562 | if templ_7745c5c3_Err != nil { | |
| 1515 | 1563 | return templ_7745c5c3_Err | |
| 1516 | 1564 | } | |
| @@ -1574,7 +1622,7 @@ func MRFilesMoved() templ.Component { | |||
| 1574 | 1622 | templ_7745c5c3_Var77 = templ.NopComponent | |
| 1575 | 1623 | } | |
| 1576 | 1624 | ctx = templ.ClearChildren(ctx) | |
| 1577 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<p class=\"notice\" data-moved>this merge request changed since the page was loaded — <a href=\"\">reload</a></p>") | |
| 1625 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<p class=\"notice\" data-moved>this merge request changed since the page was loaded — <a href=\"\">reload</a></p>") | |
| 1578 | 1626 | if templ_7745c5c3_Err != nil { | |
| 1579 | 1627 | return templ_7745c5c3_Err | |
| 1580 | 1628 | } | |
| @@ -1582,9 +1630,10 @@ func MRFilesMoved() templ.Component { | |||
| 1582 | 1630 | }) | |
| 1583 | 1631 | } | |
| 1584 | 1632 | ||
| 1585 | - | // splitSide is one half of a row of the split view; a side without a line is | |
| 1586 | - | // an empty cell. | |
| 1587 | - | func splitSide(ln *DiffLine, old bool) templ.Component { | |
| 1633 | + | // gapRow is the row of the hidden lines before a hunk, or of the rest of the | |
| 1634 | + | // file: a button that loads them in their place (ermite.js, ermite.expand). | |
| 1635 | + | // Without scripts nothing loads them: hidden. | |
| 1636 | + | func gapRow(g DiffGap, cols int) templ.Component { | |
| 1588 | 1637 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |
| 1589 | 1638 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |
| 1590 | 1639 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | |
| @@ -1605,31 +1654,236 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||
| 1605 | 1654 | templ_7745c5c3_Var78 = templ.NopComponent | |
| 1606 | 1655 | } | |
| 1607 | 1656 | ctx = templ.ClearChildren(ctx) | |
| 1657 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "<tr class=\"gap\" data-js data-gap=\"") | |
| 1658 | + | if templ_7745c5c3_Err != nil { | |
| 1659 | + | return templ_7745c5c3_Err | |
| 1660 | + | } | |
| 1661 | + | var templ_7745c5c3_Var79 string | |
| 1662 | + | templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(g.Key) | |
| 1663 | + | if templ_7745c5c3_Err != nil { | |
| 1664 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 309, Col: 41} | |
| 1665 | + | } | |
| 1666 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79) | |
| 1667 | + | if templ_7745c5c3_Err != nil { | |
| 1668 | + | return templ_7745c5c3_Err | |
| 1669 | + | } | |
| 1670 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "\"><td colspan=\"") | |
| 1671 | + | if templ_7745c5c3_Err != nil { | |
| 1672 | + | return templ_7745c5c3_Err | |
| 1673 | + | } | |
| 1674 | + | var templ_7745c5c3_Var80 string | |
| 1675 | + | templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(cols)) | |
| 1676 | + | if templ_7745c5c3_Err != nil { | |
| 1677 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 310, Col: 26} | |
| 1678 | + | } | |
| 1679 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80) | |
| 1680 | + | if templ_7745c5c3_Err != nil { | |
| 1681 | + | return templ_7745c5c3_Err | |
| 1682 | + | } | |
| 1683 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "\"><button type=\"button\" data-src=\"") | |
| 1684 | + | if templ_7745c5c3_Err != nil { | |
| 1685 | + | return templ_7745c5c3_Err | |
| 1686 | + | } | |
| 1687 | + | var templ_7745c5c3_Var81 string | |
| 1688 | + | templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.ResolveAttributeValue(g.Src) | |
| 1689 | + | if templ_7745c5c3_Err != nil { | |
| 1690 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 311, Col: 41} | |
| 1691 | + | } | |
| 1692 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var81) | |
| 1693 | + | if templ_7745c5c3_Err != nil { | |
| 1694 | + | return templ_7745c5c3_Err | |
| 1695 | + | } | |
| 1696 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "\"><span aria-hidden=\"true\">⋯</span> ") | |
| 1697 | + | if templ_7745c5c3_Err != nil { | |
| 1698 | + | return templ_7745c5c3_Err | |
| 1699 | + | } | |
| 1700 | + | var templ_7745c5c3_Var82 string | |
| 1701 | + | templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(g.Label()) | |
| 1702 | + | if templ_7745c5c3_Err != nil { | |
| 1703 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 312, Col: 51} | |
| 1704 | + | } | |
| 1705 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82)) | |
| 1706 | + | if templ_7745c5c3_Err != nil { | |
| 1707 | + | return templ_7745c5c3_Err | |
| 1708 | + | } | |
| 1709 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "</button></td></tr>") | |
| 1710 | + | if templ_7745c5c3_Err != nil { | |
| 1711 | + | return templ_7745c5c3_Err | |
| 1712 | + | } | |
| 1713 | + | return nil | |
| 1714 | + | }) | |
| 1715 | + | } | |
| 1716 | + | ||
| 1717 | + | // DiffContext is the hidden lines of a gap, for /context: one body a layout, | |
| 1718 | + | // whose rows replace the rows of the gap in the table of that layout. | |
| 1719 | + | func DiffContext(c ContextRows) templ.Component { | |
| 1720 | + | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |
| 1721 | + | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |
| 1722 | + | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | |
| 1723 | + | return templ_7745c5c3_CtxErr | |
| 1724 | + | } | |
| 1725 | + | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | |
| 1726 | + | if !templ_7745c5c3_IsBuffer { | |
| 1727 | + | defer func() { | |
| 1728 | + | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | |
| 1729 | + | if templ_7745c5c3_Err == nil { | |
| 1730 | + | templ_7745c5c3_Err = templ_7745c5c3_BufErr | |
| 1731 | + | } | |
| 1732 | + | }() | |
| 1733 | + | } | |
| 1734 | + | ctx = templ.InitializeContext(ctx) | |
| 1735 | + | templ_7745c5c3_Var83 := templ.GetChildren(ctx) | |
| 1736 | + | if templ_7745c5c3_Var83 == nil { | |
| 1737 | + | templ_7745c5c3_Var83 = templ.NopComponent | |
| 1738 | + | } | |
| 1739 | + | ctx = templ.ClearChildren(ctx) | |
| 1740 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "<table><tbody data-for=\"uni\">") | |
| 1741 | + | if templ_7745c5c3_Err != nil { | |
| 1742 | + | return templ_7745c5c3_Err | |
| 1743 | + | } | |
| 1744 | + | for _, ln := range c.Lines { | |
| 1745 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "<tr class=\"ctx\"><td class=\"ln\">") | |
| 1746 | + | if templ_7745c5c3_Err != nil { | |
| 1747 | + | return templ_7745c5c3_Err | |
| 1748 | + | } | |
| 1749 | + | var templ_7745c5c3_Var84 string | |
| 1750 | + | templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Old) | |
| 1751 | + | if templ_7745c5c3_Err != nil { | |
| 1752 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 325, Col: 28} | |
| 1753 | + | } | |
| 1754 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84)) | |
| 1755 | + | if templ_7745c5c3_Err != nil { | |
| 1756 | + | return templ_7745c5c3_Err | |
| 1757 | + | } | |
| 1758 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "</td><td class=\"ln\">") | |
| 1759 | + | if templ_7745c5c3_Err != nil { | |
| 1760 | + | return templ_7745c5c3_Err | |
| 1761 | + | } | |
| 1762 | + | var templ_7745c5c3_Var85 string | |
| 1763 | + | templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(ln.New) | |
| 1764 | + | if templ_7745c5c3_Err != nil { | |
| 1765 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 326, Col: 28} | |
| 1766 | + | } | |
| 1767 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85)) | |
| 1768 | + | if templ_7745c5c3_Err != nil { | |
| 1769 | + | return templ_7745c5c3_Err | |
| 1770 | + | } | |
| 1771 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "</td><td class=\"m\">") | |
| 1772 | + | if templ_7745c5c3_Err != nil { | |
| 1773 | + | return templ_7745c5c3_Err | |
| 1774 | + | } | |
| 1775 | + | var templ_7745c5c3_Var86 string | |
| 1776 | + | templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | |
| 1777 | + | if templ_7745c5c3_Err != nil { | |
| 1778 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 327, Col: 28} | |
| 1779 | + | } | |
| 1780 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) | |
| 1781 | + | if templ_7745c5c3_Err != nil { | |
| 1782 | + | return templ_7745c5c3_Err | |
| 1783 | + | } | |
| 1784 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</td><td class=\"tx\">") | |
| 1785 | + | if templ_7745c5c3_Err != nil { | |
| 1786 | + | return templ_7745c5c3_Err | |
| 1787 | + | } | |
| 1788 | + | templ_7745c5c3_Err = codeLine(ln).Render(ctx, templ_7745c5c3_Buffer) | |
| 1789 | + | if templ_7745c5c3_Err != nil { | |
| 1790 | + | return templ_7745c5c3_Err | |
| 1791 | + | } | |
| 1792 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "</td></tr>") | |
| 1793 | + | if templ_7745c5c3_Err != nil { | |
| 1794 | + | return templ_7745c5c3_Err | |
| 1795 | + | } | |
| 1796 | + | } | |
| 1797 | + | if c.More != nil { | |
| 1798 | + | templ_7745c5c3_Err = gapRow(*c.More, 4).Render(ctx, templ_7745c5c3_Buffer) | |
| 1799 | + | if templ_7745c5c3_Err != nil { | |
| 1800 | + | return templ_7745c5c3_Err | |
| 1801 | + | } | |
| 1802 | + | } | |
| 1803 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</tbody> <tbody data-for=\"spl\">") | |
| 1804 | + | if templ_7745c5c3_Err != nil { | |
| 1805 | + | return templ_7745c5c3_Err | |
| 1806 | + | } | |
| 1807 | + | for i := range c.Lines { | |
| 1808 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<tr>") | |
| 1809 | + | if templ_7745c5c3_Err != nil { | |
| 1810 | + | return templ_7745c5c3_Err | |
| 1811 | + | } | |
| 1812 | + | templ_7745c5c3_Err = splitSide(&c.Lines[i], true).Render(ctx, templ_7745c5c3_Buffer) | |
| 1813 | + | if templ_7745c5c3_Err != nil { | |
| 1814 | + | return templ_7745c5c3_Err | |
| 1815 | + | } | |
| 1816 | + | templ_7745c5c3_Err = splitSide(&c.Lines[i], false).Render(ctx, templ_7745c5c3_Buffer) | |
| 1817 | + | if templ_7745c5c3_Err != nil { | |
| 1818 | + | return templ_7745c5c3_Err | |
| 1819 | + | } | |
| 1820 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</tr>") | |
| 1821 | + | if templ_7745c5c3_Err != nil { | |
| 1822 | + | return templ_7745c5c3_Err | |
| 1823 | + | } | |
| 1824 | + | } | |
| 1825 | + | if c.More != nil { | |
| 1826 | + | templ_7745c5c3_Err = gapRow(*c.More, 6).Render(ctx, templ_7745c5c3_Buffer) | |
| 1827 | + | if templ_7745c5c3_Err != nil { | |
| 1828 | + | return templ_7745c5c3_Err | |
| 1829 | + | } | |
| 1830 | + | } | |
| 1831 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</tbody></table>") | |
| 1832 | + | if templ_7745c5c3_Err != nil { | |
| 1833 | + | return templ_7745c5c3_Err | |
| 1834 | + | } | |
| 1835 | + | return nil | |
| 1836 | + | }) | |
| 1837 | + | } | |
| 1838 | + | ||
| 1839 | + | // splitSide is one half of a row of the split view; a side without a line is | |
| 1840 | + | // an empty cell. | |
| 1841 | + | func splitSide(ln *DiffLine, old bool) templ.Component { | |
| 1842 | + | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |
| 1843 | + | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |
| 1844 | + | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | |
| 1845 | + | return templ_7745c5c3_CtxErr | |
| 1846 | + | } | |
| 1847 | + | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | |
| 1848 | + | if !templ_7745c5c3_IsBuffer { | |
| 1849 | + | defer func() { | |
| 1850 | + | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | |
| 1851 | + | if templ_7745c5c3_Err == nil { | |
| 1852 | + | templ_7745c5c3_Err = templ_7745c5c3_BufErr | |
| 1853 | + | } | |
| 1854 | + | }() | |
| 1855 | + | } | |
| 1856 | + | ctx = templ.InitializeContext(ctx) | |
| 1857 | + | templ_7745c5c3_Var87 := templ.GetChildren(ctx) | |
| 1858 | + | if templ_7745c5c3_Var87 == nil { | |
| 1859 | + | templ_7745c5c3_Var87 = templ.NopComponent | |
| 1860 | + | } | |
| 1861 | + | ctx = templ.ClearChildren(ctx) | |
| 1608 | 1862 | if ln == nil { | |
| 1609 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<td class=\"ln none\"></td><td class=\"m none\"></td><td class=\"tx none\"></td>") | |
| 1863 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "<td class=\"ln none\"></td><td class=\"m none\"></td><td class=\"tx none\"></td>") | |
| 1610 | 1864 | if templ_7745c5c3_Err != nil { | |
| 1611 | 1865 | return templ_7745c5c3_Err | |
| 1612 | 1866 | } | |
| 1613 | 1867 | } else { | |
| 1614 | - | var templ_7745c5c3_Var79 = []any{"ln", ln.Kind} | |
| 1615 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var79...) | |
| 1868 | + | var templ_7745c5c3_Var88 = []any{"ln", ln.Kind} | |
| 1869 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var88...) | |
| 1616 | 1870 | if templ_7745c5c3_Err != nil { | |
| 1617 | 1871 | return templ_7745c5c3_Err | |
| 1618 | 1872 | } | |
| 1619 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "<td class=\"") | |
| 1873 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "<td class=\"") | |
| 1620 | 1874 | if templ_7745c5c3_Err != nil { | |
| 1621 | 1875 | return templ_7745c5c3_Err | |
| 1622 | 1876 | } | |
| 1623 | - | var templ_7745c5c3_Var80 string | |
| 1624 | - | templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var79).String()) | |
| 1877 | + | var templ_7745c5c3_Var89 string | |
| 1878 | + | templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var88).String()) | |
| 1625 | 1879 | if templ_7745c5c3_Err != nil { | |
| 1626 | 1880 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | |
| 1627 | 1881 | } | |
| 1628 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80) | |
| 1882 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var89) | |
| 1629 | 1883 | if templ_7745c5c3_Err != nil { | |
| 1630 | 1884 | return templ_7745c5c3_Err | |
| 1631 | 1885 | } | |
| 1632 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "\">") | |
| 1886 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "\">") | |
| 1633 | 1887 | if templ_7745c5c3_Err != nil { | |
| 1634 | 1888 | return templ_7745c5c3_Err | |
| 1635 | 1889 | } | |
| @@ -1644,64 +1898,64 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||
| 1644 | 1898 | return templ_7745c5c3_Err | |
| 1645 | 1899 | } | |
| 1646 | 1900 | } | |
| 1647 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "</td>") | |
| 1901 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "</td>") | |
| 1648 | 1902 | if templ_7745c5c3_Err != nil { | |
| 1649 | 1903 | return templ_7745c5c3_Err | |
| 1650 | 1904 | } | |
| 1651 | - | var templ_7745c5c3_Var81 = []any{"m", ln.Kind} | |
| 1652 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var81...) | |
| 1905 | + | var templ_7745c5c3_Var90 = []any{"m", ln.Kind} | |
| 1906 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var90...) | |
| 1653 | 1907 | if templ_7745c5c3_Err != nil { | |
| 1654 | 1908 | return templ_7745c5c3_Err | |
| 1655 | 1909 | } | |
| 1656 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "<td class=\"") | |
| 1910 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "<td class=\"") | |
| 1657 | 1911 | if templ_7745c5c3_Err != nil { | |
| 1658 | 1912 | return templ_7745c5c3_Err | |
| 1659 | 1913 | } | |
| 1660 | - | var templ_7745c5c3_Var82 string | |
| 1661 | - | templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var81).String()) | |
| 1914 | + | var templ_7745c5c3_Var91 string | |
| 1915 | + | templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var90).String()) | |
| 1662 | 1916 | if templ_7745c5c3_Err != nil { | |
| 1663 | 1917 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | |
| 1664 | 1918 | } | |
| 1665 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var82) | |
| 1919 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var91) | |
| 1666 | 1920 | if templ_7745c5c3_Err != nil { | |
| 1667 | 1921 | return templ_7745c5c3_Err | |
| 1668 | 1922 | } | |
| 1669 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "\">") | |
| 1923 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "\">") | |
| 1670 | 1924 | if templ_7745c5c3_Err != nil { | |
| 1671 | 1925 | return templ_7745c5c3_Err | |
| 1672 | 1926 | } | |
| 1673 | - | var templ_7745c5c3_Var83 string | |
| 1674 | - | templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | |
| 1927 | + | var templ_7745c5c3_Var92 string | |
| 1928 | + | templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | |
| 1675 | 1929 | if templ_7745c5c3_Err != nil { | |
| 1676 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 304, Col: 38} | |
| 1930 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 364, Col: 38} | |
| 1677 | 1931 | } | |
| 1678 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83)) | |
| 1932 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var92)) | |
| 1679 | 1933 | if templ_7745c5c3_Err != nil { | |
| 1680 | 1934 | return templ_7745c5c3_Err | |
| 1681 | 1935 | } | |
| 1682 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</td>") | |
| 1936 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "</td>") | |
| 1683 | 1937 | if templ_7745c5c3_Err != nil { | |
| 1684 | 1938 | return templ_7745c5c3_Err | |
| 1685 | 1939 | } | |
| 1686 | - | var templ_7745c5c3_Var84 = []any{"tx", ln.Kind} | |
| 1687 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var84...) | |
| 1940 | + | var templ_7745c5c3_Var93 = []any{"tx", ln.Kind} | |
| 1941 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var93...) | |
| 1688 | 1942 | if templ_7745c5c3_Err != nil { | |
| 1689 | 1943 | return templ_7745c5c3_Err | |
| 1690 | 1944 | } | |
| 1691 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "<td class=\"") | |
| 1945 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "<td class=\"") | |
| 1692 | 1946 | if templ_7745c5c3_Err != nil { | |
| 1693 | 1947 | return templ_7745c5c3_Err | |
| 1694 | 1948 | } | |
| 1695 | - | var templ_7745c5c3_Var85 string | |
| 1696 | - | templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var84).String()) | |
| 1949 | + | var templ_7745c5c3_Var94 string | |
| 1950 | + | templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var93).String()) | |
| 1697 | 1951 | if templ_7745c5c3_Err != nil { | |
| 1698 | 1952 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | |
| 1699 | 1953 | } | |
| 1700 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var85) | |
| 1954 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var94) | |
| 1701 | 1955 | if templ_7745c5c3_Err != nil { | |
| 1702 | 1956 | return templ_7745c5c3_Err | |
| 1703 | 1957 | } | |
| 1704 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "\">") | |
| 1958 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "\">") | |
| 1705 | 1959 | if templ_7745c5c3_Err != nil { | |
| 1706 | 1960 | return templ_7745c5c3_Err | |
| 1707 | 1961 | } | |
| @@ -1709,7 +1963,7 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||
| 1709 | 1963 | if templ_7745c5c3_Err != nil { | |
| 1710 | 1964 | return templ_7745c5c3_Err | |
| 1711 | 1965 | } | |
| 1712 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</td>") | |
| 1966 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</td>") | |
| 1713 | 1967 | if templ_7745c5c3_Err != nil { | |
| 1714 | 1968 | return templ_7745c5c3_Err | |
| 1715 | 1969 | } | |
| @@ -1736,49 +1990,49 @@ func gutter(n, href string) templ.Component { | |||
| 1736 | 1990 | }() | |
| 1737 | 1991 | } | |
| 1738 | 1992 | ctx = templ.InitializeContext(ctx) | |
| 1739 | - | templ_7745c5c3_Var86 := templ.GetChildren(ctx) | |
| 1740 | - | if templ_7745c5c3_Var86 == nil { | |
| 1741 | - | templ_7745c5c3_Var86 = templ.NopComponent | |
| 1993 | + | templ_7745c5c3_Var95 := templ.GetChildren(ctx) | |
| 1994 | + | if templ_7745c5c3_Var95 == nil { | |
| 1995 | + | templ_7745c5c3_Var95 = templ.NopComponent | |
| 1742 | 1996 | } | |
| 1743 | 1997 | ctx = templ.ClearChildren(ctx) | |
| 1744 | 1998 | if href != "" { | |
| 1745 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<a href=\"") | |
| 1999 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<a href=\"") | |
| 1746 | 2000 | if templ_7745c5c3_Err != nil { | |
| 1747 | 2001 | return templ_7745c5c3_Err | |
| 1748 | 2002 | } | |
| 1749 | - | var templ_7745c5c3_Var87 templ.SafeURL | |
| 1750 | - | templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href)) | |
| 2003 | + | var templ_7745c5c3_Var96 templ.SafeURL | |
| 2004 | + | templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href)) | |
| 1751 | 2005 | if templ_7745c5c3_Err != nil { | |
| 1752 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 313, Col: 31} | |
| 2006 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 373, Col: 31} | |
| 1753 | 2007 | } | |
| 1754 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) | |
| 2008 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96)) | |
| 1755 | 2009 | if templ_7745c5c3_Err != nil { | |
| 1756 | 2010 | return templ_7745c5c3_Err | |
| 1757 | 2011 | } | |
| 1758 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "\" title=\"comment on this line\">") | |
| 2012 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "\" title=\"comment on this line\">") | |
| 1759 | 2013 | if templ_7745c5c3_Err != nil { | |
| 1760 | 2014 | return templ_7745c5c3_Err | |
| 1761 | 2015 | } | |
| 1762 | - | var templ_7745c5c3_Var88 string | |
| 1763 | - | templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(n) | |
| 2016 | + | var templ_7745c5c3_Var97 string | |
| 2017 | + | templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(n) | |
| 1764 | 2018 | if templ_7745c5c3_Err != nil { | |
| 1765 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 313, Col: 66} | |
| 2019 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 373, Col: 66} | |
| 1766 | 2020 | } | |
| 1767 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88)) | |
| 2021 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97)) | |
| 1768 | 2022 | if templ_7745c5c3_Err != nil { | |
| 1769 | 2023 | return templ_7745c5c3_Err | |
| 1770 | 2024 | } | |
| 1771 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "</a>") | |
| 2025 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "</a>") | |
| 1772 | 2026 | if templ_7745c5c3_Err != nil { | |
| 1773 | 2027 | return templ_7745c5c3_Err | |
| 1774 | 2028 | } | |
| 1775 | 2029 | } else { | |
| 1776 | - | var templ_7745c5c3_Var89 string | |
| 1777 | - | templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(n) | |
| 2030 | + | var templ_7745c5c3_Var98 string | |
| 2031 | + | templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(n) | |
| 1778 | 2032 | if templ_7745c5c3_Err != nil { | |
| 1779 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 315, Col: 5} | |
| 2033 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 375, Col: 5} | |
| 1780 | 2034 | } | |
| 1781 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89)) | |
| 2035 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98)) | |
| 1782 | 2036 | if templ_7745c5c3_Err != nil { | |
| 1783 | 2037 | return templ_7745c5c3_Err | |
| 1784 | 2038 | } | |
| @@ -1284,20 +1284,30 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1284 | return templ_7745c5c3_Err | 1284 | return templ_7745c5c3_Err | ||
| 1285 | } | 1285 | } | ||
| 1286 | for _, h := range f.Hunks { | 1286 | for _, h := range f.Hunks { | ||
| 1287 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<tbody><tr class=\"hunk\"><td colspan=\"4\">") | 1287 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<tbody>") |
| 1288 | + | if templ_7745c5c3_Err != nil { | |||
| 1289 | + | return templ_7745c5c3_Err | |||
| 1290 | + | } | |||
| 1291 | + | if h.Gap != nil { | |||
| 1292 | + | templ_7745c5c3_Err = gapRow(*h.Gap, 4).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1293 | + | if templ_7745c5c3_Err != nil { | |||
| 1294 | + | return templ_7745c5c3_Err | |||
| 1295 | + | } | |||
| 1296 | + | } | |||
| 1297 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "<tr class=\"hunk\"><td colspan=\"4\">") | |||
| 1288 | if templ_7745c5c3_Err != nil { | 1298 | if templ_7745c5c3_Err != nil { | ||
| 1289 | return templ_7745c5c3_Err | 1299 | return templ_7745c5c3_Err | ||
| 1290 | } | 1300 | } | ||
| 1291 | var templ_7745c5c3_Var69 string | 1301 | var templ_7745c5c3_Var69 string | ||
| 1292 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | 1302 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | ||
| 1293 | if templ_7745c5c3_Err != nil { | 1303 | if templ_7745c5c3_Err != nil { | ||
| 1294 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 215, Col: 51} | 1304 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 218, Col: 51} |
| 1295 | } | 1305 | } | ||
| 1296 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) | 1306 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) | ||
| 1297 | if templ_7745c5c3_Err != nil { | 1307 | if templ_7745c5c3_Err != nil { | ||
| 1298 | return templ_7745c5c3_Err | 1308 | return templ_7745c5c3_Err | ||
| 1299 | } | 1309 | } | ||
| 1300 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td></tr>") | 1310 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td></tr>") |
| 1301 | if templ_7745c5c3_Err != nil { | 1311 | if templ_7745c5c3_Err != nil { | ||
| 1302 | return templ_7745c5c3_Err | 1312 | return templ_7745c5c3_Err | ||
| 1303 | } | 1313 | } | ||
| @@ -1307,7 +1317,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1307 | if templ_7745c5c3_Err != nil { | 1317 | if templ_7745c5c3_Err != nil { | ||
| 1308 | return templ_7745c5c3_Err | 1318 | return templ_7745c5c3_Err | ||
| 1309 | } | 1319 | } | ||
| 1310 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<tr class=\"") | 1320 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "<tr class=\"") |
| 1311 | if templ_7745c5c3_Err != nil { | 1321 | if templ_7745c5c3_Err != nil { | ||
| 1312 | return templ_7745c5c3_Err | 1322 | return templ_7745c5c3_Err | ||
| 1313 | } | 1323 | } | ||
| @@ -1320,7 +1330,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1320 | if templ_7745c5c3_Err != nil { | 1330 | if templ_7745c5c3_Err != nil { | ||
| 1321 | return templ_7745c5c3_Err | 1331 | return templ_7745c5c3_Err | ||
| 1322 | } | 1332 | } | ||
| 1323 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\"><td class=\"ln\">") | 1333 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\"><td class=\"ln\">") |
| 1324 | if templ_7745c5c3_Err != nil { | 1334 | if templ_7745c5c3_Err != nil { | ||
| 1325 | return templ_7745c5c3_Err | 1335 | return templ_7745c5c3_Err | ||
| 1326 | } | 1336 | } | ||
| @@ -1328,7 +1338,7 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1328 | if templ_7745c5c3_Err != nil { | 1338 | if templ_7745c5c3_Err != nil { | ||
| 1329 | return templ_7745c5c3_Err | 1339 | return templ_7745c5c3_Err | ||
| 1330 | } | 1340 | } | ||
| 1331 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "</td><td class=\"ln\">") | 1341 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</td><td class=\"ln\">") |
| 1332 | if templ_7745c5c3_Err != nil { | 1342 | if templ_7745c5c3_Err != nil { | ||
| 1333 | return templ_7745c5c3_Err | 1343 | return templ_7745c5c3_Err | ||
| 1334 | } | 1344 | } | ||
| @@ -1336,20 +1346,20 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1336 | if templ_7745c5c3_Err != nil { | 1346 | if templ_7745c5c3_Err != nil { | ||
| 1337 | return templ_7745c5c3_Err | 1347 | return templ_7745c5c3_Err | ||
| 1338 | } | 1348 | } | ||
| 1339 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</td><td class=\"m\">") | 1349 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</td><td class=\"m\">") |
| 1340 | if templ_7745c5c3_Err != nil { | 1350 | if templ_7745c5c3_Err != nil { | ||
| 1341 | return templ_7745c5c3_Err | 1351 | return templ_7745c5c3_Err | ||
| 1342 | } | 1352 | } | ||
| 1343 | var templ_7745c5c3_Var72 string | 1353 | var templ_7745c5c3_Var72 string | ||
| 1344 | templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | 1354 | templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | ||
| 1345 | if templ_7745c5c3_Err != nil { | 1355 | if templ_7745c5c3_Err != nil { | ||
| 1346 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 220, Col: 33} | 1356 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 223, Col: 33} |
| 1347 | } | 1357 | } | ||
| 1348 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) | 1358 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) | ||
| 1349 | if templ_7745c5c3_Err != nil { | 1359 | if templ_7745c5c3_Err != nil { | ||
| 1350 | return templ_7745c5c3_Err | 1360 | return templ_7745c5c3_Err | ||
| 1351 | } | 1361 | } | ||
| 1352 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</td><td class=\"tx\">") | 1362 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</td><td class=\"tx\">") |
| 1353 | if templ_7745c5c3_Err != nil { | 1363 | if templ_7745c5c3_Err != nil { | ||
| 1354 | return templ_7745c5c3_Err | 1364 | return templ_7745c5c3_Err | ||
| 1355 | } | 1365 | } | ||
| @@ -1357,12 +1367,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1357 | if templ_7745c5c3_Err != nil { | 1367 | if templ_7745c5c3_Err != nil { | ||
| 1358 | return templ_7745c5c3_Err | 1368 | return templ_7745c5c3_Err | ||
| 1359 | } | 1369 | } | ||
| 1360 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</td></tr>") | 1370 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</td></tr>") |
| 1361 | if templ_7745c5c3_Err != nil { | 1371 | if templ_7745c5c3_Err != nil { | ||
| 1362 | return templ_7745c5c3_Err | 1372 | return templ_7745c5c3_Err | ||
| 1363 | } | 1373 | } | ||
| 1364 | if len(ln.Notes) > 0 || ln.Form != nil { | 1374 | if len(ln.Notes) > 0 || ln.Form != nil { | ||
| 1365 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<tr class=\"fnote\"><td colspan=\"3\"></td><td>") | 1375 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "<tr class=\"fnote\"><td colspan=\"3\"></td><td>") |
| 1366 | if templ_7745c5c3_Err != nil { | 1376 | if templ_7745c5c3_Err != nil { | ||
| 1367 | return templ_7745c5c3_Err | 1377 | return templ_7745c5c3_Err | ||
| 1368 | } | 1378 | } | ||
| @@ -1370,60 +1380,84 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1370 | if templ_7745c5c3_Err != nil { | 1380 | if templ_7745c5c3_Err != nil { | ||
| 1371 | return templ_7745c5c3_Err | 1381 | return templ_7745c5c3_Err | ||
| 1372 | } | 1382 | } | ||
| 1373 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</td></tr>") | 1383 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</td></tr>") |
| 1374 | if templ_7745c5c3_Err != nil { | 1384 | if templ_7745c5c3_Err != nil { | ||
| 1375 | return templ_7745c5c3_Err | 1385 | return templ_7745c5c3_Err | ||
| 1376 | } | 1386 | } | ||
| 1377 | } | 1387 | } | ||
| 1378 | } | 1388 | } | ||
| 1379 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</tbody>") | 1389 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</tbody> ") |
| 1390 | + | if templ_7745c5c3_Err != nil { | |||
| 1391 | + | return templ_7745c5c3_Err | |||
| 1392 | + | } | |||
| 1393 | + | } | |||
| 1394 | + | if f.Tail != nil { | |||
| 1395 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<tbody>") | |||
| 1396 | + | if templ_7745c5c3_Err != nil { | |||
| 1397 | + | return templ_7745c5c3_Err | |||
| 1398 | + | } | |||
| 1399 | + | templ_7745c5c3_Err = gapRow(*f.Tail, 4).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1400 | + | if templ_7745c5c3_Err != nil { | |||
| 1401 | + | return templ_7745c5c3_Err | |||
| 1402 | + | } | |||
| 1403 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</tbody>") | |||
| 1380 | if templ_7745c5c3_Err != nil { | 1404 | if templ_7745c5c3_Err != nil { | ||
| 1381 | return templ_7745c5c3_Err | 1405 | return templ_7745c5c3_Err | ||
| 1382 | } | 1406 | } | ||
| 1383 | } | 1407 | } | ||
| 1384 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</table>") | 1408 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</table>") |
| 1385 | if templ_7745c5c3_Err != nil { | 1409 | if templ_7745c5c3_Err != nil { | ||
| 1386 | return templ_7745c5c3_Err | 1410 | return templ_7745c5c3_Err | ||
| 1387 | } | 1411 | } | ||
| 1388 | } | 1412 | } | ||
| 1389 | if p.ShowSplit { | 1413 | if p.ShowSplit { | ||
| 1390 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<table class=\"dt spl\" aria-label=\"") | 1414 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<table class=\"dt spl\" aria-label=\"") |
| 1391 | if templ_7745c5c3_Err != nil { | 1415 | if templ_7745c5c3_Err != nil { | ||
| 1392 | return templ_7745c5c3_Err | 1416 | return templ_7745c5c3_Err | ||
| 1393 | } | 1417 | } | ||
| 1394 | var templ_7745c5c3_Var73 string | 1418 | var templ_7745c5c3_Var73 string | ||
| 1395 | templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path + ", split") | 1419 | templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path + ", split") | ||
| 1396 | if templ_7745c5c3_Err != nil { | 1420 | if templ_7745c5c3_Err != nil { | ||
| 1397 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 235, Col: 58} | 1421 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 243, Col: 58} |
| 1398 | } | 1422 | } | ||
| 1399 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var73) | 1423 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var73) | ||
| 1400 | if templ_7745c5c3_Err != nil { | 1424 | if templ_7745c5c3_Err != nil { | ||
| 1401 | return templ_7745c5c3_Err | 1425 | return templ_7745c5c3_Err | ||
| 1402 | } | 1426 | } | ||
| 1403 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "\"><colgroup><col class=\"c-ln\"><col class=\"c-m\"><col><col class=\"c-ln\"><col class=\"c-m\"><col></colgroup> ") | 1427 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "\"><colgroup><col class=\"c-ln\"><col class=\"c-m\"><col><col class=\"c-ln\"><col class=\"c-m\"><col></colgroup> ") |
| 1404 | if templ_7745c5c3_Err != nil { | 1428 | if templ_7745c5c3_Err != nil { | ||
| 1405 | return templ_7745c5c3_Err | 1429 | return templ_7745c5c3_Err | ||
| 1406 | } | 1430 | } | ||
| 1407 | for _, h := range f.Hunks { | 1431 | for _, h := range f.Hunks { | ||
| 1408 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<tbody><tr class=\"hunk\"><td colspan=\"6\">") | 1432 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<tbody>") |
| 1433 | + | if templ_7745c5c3_Err != nil { | |||
| 1434 | + | return templ_7745c5c3_Err | |||
| 1435 | + | } | |||
| 1436 | + | if h.Gap != nil { | |||
| 1437 | + | templ_7745c5c3_Err = gapRow(*h.Gap, 6).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1438 | + | if templ_7745c5c3_Err != nil { | |||
| 1439 | + | return templ_7745c5c3_Err | |||
| 1440 | + | } | |||
| 1441 | + | } | |||
| 1442 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<tr class=\"hunk\"><td colspan=\"6\">") | |||
| 1409 | if templ_7745c5c3_Err != nil { | 1443 | if templ_7745c5c3_Err != nil { | ||
| 1410 | return templ_7745c5c3_Err | 1444 | return templ_7745c5c3_Err | ||
| 1411 | } | 1445 | } | ||
| 1412 | var templ_7745c5c3_Var74 string | 1446 | var templ_7745c5c3_Var74 string | ||
| 1413 | templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | 1447 | templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(h.Header) | ||
| 1414 | if templ_7745c5c3_Err != nil { | 1448 | if templ_7745c5c3_Err != nil { | ||
| 1415 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 239, Col: 51} | 1449 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 250, Col: 51} |
| 1416 | } | 1450 | } | ||
| 1417 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) | 1451 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) | ||
| 1418 | if templ_7745c5c3_Err != nil { | 1452 | if templ_7745c5c3_Err != nil { | ||
| 1419 | return templ_7745c5c3_Err | 1453 | return templ_7745c5c3_Err | ||
| 1420 | } | 1454 | } | ||
| 1421 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "</td></tr>") | 1455 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "</td></tr>") |
| 1422 | if templ_7745c5c3_Err != nil { | 1456 | if templ_7745c5c3_Err != nil { | ||
| 1423 | return templ_7745c5c3_Err | 1457 | return templ_7745c5c3_Err | ||
| 1424 | } | 1458 | } | ||
| 1425 | for _, row := range h.Split { | 1459 | for _, row := range h.Split { | ||
| 1426 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "<tr>") | 1460 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "<tr>") |
| 1427 | if templ_7745c5c3_Err != nil { | 1461 | if templ_7745c5c3_Err != nil { | ||
| 1428 | return templ_7745c5c3_Err | 1462 | return templ_7745c5c3_Err | ||
| 1429 | } | 1463 | } | ||
| @@ -1435,12 +1469,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1435 | if templ_7745c5c3_Err != nil { | 1469 | if templ_7745c5c3_Err != nil { | ||
| 1436 | return templ_7745c5c3_Err | 1470 | return templ_7745c5c3_Err | ||
| 1437 | } | 1471 | } | ||
| 1438 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "</tr>") | 1472 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</tr>") |
| 1439 | if templ_7745c5c3_Err != nil { | 1473 | if templ_7745c5c3_Err != nil { | ||
| 1440 | return templ_7745c5c3_Err | 1474 | return templ_7745c5c3_Err | ||
| 1441 | } | 1475 | } | ||
| 1442 | if notes, form := row.Notes(), row.Form(); len(notes) > 0 || form != nil { | 1476 | if notes, form := row.Notes(), row.Form(); len(notes) > 0 || form != nil { | ||
| 1443 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<tr class=\"fnote\"><td colspan=\"6\">") | 1477 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "<tr class=\"fnote\"><td colspan=\"6\">") |
| 1444 | if templ_7745c5c3_Err != nil { | 1478 | if templ_7745c5c3_Err != nil { | ||
| 1445 | return templ_7745c5c3_Err | 1479 | return templ_7745c5c3_Err | ||
| 1446 | } | 1480 | } | ||
| @@ -1448,28 +1482,42 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1448 | if templ_7745c5c3_Err != nil { | 1482 | if templ_7745c5c3_Err != nil { | ||
| 1449 | return templ_7745c5c3_Err | 1483 | return templ_7745c5c3_Err | ||
| 1450 | } | 1484 | } | ||
| 1451 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "</td></tr>") | 1485 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</td></tr>") |
| 1452 | if templ_7745c5c3_Err != nil { | 1486 | if templ_7745c5c3_Err != nil { | ||
| 1453 | return templ_7745c5c3_Err | 1487 | return templ_7745c5c3_Err | ||
| 1454 | } | 1488 | } | ||
| 1455 | } | 1489 | } | ||
| 1456 | } | 1490 | } | ||
| 1457 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "</tbody>") | 1491 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</tbody> ") |
| 1458 | if templ_7745c5c3_Err != nil { | 1492 | if templ_7745c5c3_Err != nil { | ||
| 1459 | return templ_7745c5c3_Err | 1493 | return templ_7745c5c3_Err | ||
| 1460 | } | 1494 | } | ||
| 1461 | } | 1495 | } | ||
| 1462 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</table>") | 1496 | + | if f.Tail != nil { |
| 1497 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<tbody>") | |||
| 1498 | + | if templ_7745c5c3_Err != nil { | |||
| 1499 | + | return templ_7745c5c3_Err | |||
| 1500 | + | } | |||
| 1501 | + | templ_7745c5c3_Err = gapRow(*f.Tail, 6).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1502 | + | if templ_7745c5c3_Err != nil { | |||
| 1503 | + | return templ_7745c5c3_Err | |||
| 1504 | + | } | |||
| 1505 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "</tbody>") | |||
| 1506 | + | if templ_7745c5c3_Err != nil { | |||
| 1507 | + | return templ_7745c5c3_Err | |||
| 1508 | + | } | |||
| 1509 | + | } | |||
| 1510 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "</table>") | |||
| 1463 | if templ_7745c5c3_Err != nil { | 1511 | if templ_7745c5c3_Err != nil { | ||
| 1464 | return templ_7745c5c3_Err | 1512 | return templ_7745c5c3_Err | ||
| 1465 | } | 1513 | } | ||
| 1466 | } | 1514 | } | ||
| 1467 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "</div>") | 1515 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "</div>") |
| 1468 | if templ_7745c5c3_Err != nil { | 1516 | if templ_7745c5c3_Err != nil { | ||
| 1469 | return templ_7745c5c3_Err | 1517 | return templ_7745c5c3_Err | ||
| 1470 | } | 1518 | } | ||
| 1471 | } else { | 1519 | } else { | ||
| 1472 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "<p class=\"note pad\">") | 1520 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<p class=\"note pad\">") |
| 1473 | if templ_7745c5c3_Err != nil { | 1521 | if templ_7745c5c3_Err != nil { | ||
| 1474 | return templ_7745c5c3_Err | 1522 | return templ_7745c5c3_Err | ||
| 1475 | } | 1523 | } | ||
| @@ -1477,25 +1525,25 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1477 | var templ_7745c5c3_Var75 string | 1525 | var templ_7745c5c3_Var75 string | ||
| 1478 | templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(f.Note) | 1526 | templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(f.Note) | ||
| 1479 | if templ_7745c5c3_Err != nil { | 1527 | if templ_7745c5c3_Err != nil { | ||
| 1480 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 259, Col: 13} | 1528 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 275, Col: 13} |
| 1481 | } | 1529 | } | ||
| 1482 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) | 1530 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) | ||
| 1483 | if templ_7745c5c3_Err != nil { | 1531 | if templ_7745c5c3_Err != nil { | ||
| 1484 | return templ_7745c5c3_Err | 1532 | return templ_7745c5c3_Err | ||
| 1485 | } | 1533 | } | ||
| 1486 | } else { | 1534 | } else { | ||
| 1487 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "no textual change.") | 1535 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "no textual change.") |
| 1488 | if templ_7745c5c3_Err != nil { | 1536 | if templ_7745c5c3_Err != nil { | ||
| 1489 | return templ_7745c5c3_Err | 1537 | return templ_7745c5c3_Err | ||
| 1490 | } | 1538 | } | ||
| 1491 | } | 1539 | } | ||
| 1492 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "</p>") | 1540 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "</p>") |
| 1493 | if templ_7745c5c3_Err != nil { | 1541 | if templ_7745c5c3_Err != nil { | ||
| 1494 | return templ_7745c5c3_Err | 1542 | return templ_7745c5c3_Err | ||
| 1495 | } | 1543 | } | ||
| 1496 | } | 1544 | } | ||
| 1497 | if len(f.Loose) > 0 { | 1545 | if len(f.Loose) > 0 { | ||
| 1498 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "<div class=\"dloose\">") | 1546 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<div class=\"dloose\">") |
| 1499 | if templ_7745c5c3_Err != nil { | 1547 | if templ_7745c5c3_Err != nil { | ||
| 1500 | return templ_7745c5c3_Err | 1548 | return templ_7745c5c3_Err | ||
| 1501 | } | 1549 | } | ||
| @@ -1505,12 +1553,12 @@ func DiffFileView(f DiffFile, p DiffView) templ.Component { | |||||
| 1505 | return templ_7745c5c3_Err | 1553 | return templ_7745c5c3_Err | ||
| 1506 | } | 1554 | } | ||
| 1507 | } | 1555 | } | ||
| 1508 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "</div>") | 1556 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "</div>") |
| 1509 | if templ_7745c5c3_Err != nil { | 1557 | if templ_7745c5c3_Err != nil { | ||
| 1510 | return templ_7745c5c3_Err | 1558 | return templ_7745c5c3_Err | ||
| 1511 | } | 1559 | } | ||
| 1512 | } | 1560 | } | ||
| 1513 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "</details>") | 1561 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</details>") |
| 1514 | if templ_7745c5c3_Err != nil { | 1562 | if templ_7745c5c3_Err != nil { | ||
| 1515 | return templ_7745c5c3_Err | 1563 | return templ_7745c5c3_Err | ||
| 1516 | } | 1564 | } | ||
| @@ -1574,7 +1622,7 @@ func MRFilesMoved() templ.Component { | |||||
| 1574 | templ_7745c5c3_Var77 = templ.NopComponent | 1622 | templ_7745c5c3_Var77 = templ.NopComponent | ||
| 1575 | } | 1623 | } | ||
| 1576 | ctx = templ.ClearChildren(ctx) | 1624 | ctx = templ.ClearChildren(ctx) | ||
| 1577 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<p class=\"notice\" data-moved>this merge request changed since the page was loaded — <a href=\"\">reload</a></p>") | 1625 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<p class=\"notice\" data-moved>this merge request changed since the page was loaded — <a href=\"\">reload</a></p>") |
| 1578 | if templ_7745c5c3_Err != nil { | 1626 | if templ_7745c5c3_Err != nil { | ||
| 1579 | return templ_7745c5c3_Err | 1627 | return templ_7745c5c3_Err | ||
| 1580 | } | 1628 | } | ||
| @@ -1582,9 +1630,10 @@ func MRFilesMoved() templ.Component { | |||||
| 1582 | }) | 1630 | }) | ||
| 1583 | } | 1631 | } | ||
| 1584 | 1632 | ||||
| 1585 | - | // splitSide is one half of a row of the split view; a side without a line is | 1633 | + | // gapRow is the row of the hidden lines before a hunk, or of the rest of the |
| 1586 | - | // an empty cell. | 1634 | + | // file: a button that loads them in their place (ermite.js, ermite.expand). |
| 1587 | - | func splitSide(ln *DiffLine, old bool) templ.Component { | 1635 | + | // Without scripts nothing loads them: hidden. |
| 1636 | + | func gapRow(g DiffGap, cols int) templ.Component { | |||
| 1588 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | 1637 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||
| 1589 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | 1638 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||
| 1590 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | 1639 | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | ||
| @@ -1605,31 +1654,236 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||||
| 1605 | templ_7745c5c3_Var78 = templ.NopComponent | 1654 | templ_7745c5c3_Var78 = templ.NopComponent | ||
| 1606 | } | 1655 | } | ||
| 1607 | ctx = templ.ClearChildren(ctx) | 1656 | ctx = templ.ClearChildren(ctx) | ||
| 1657 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "<tr class=\"gap\" data-js data-gap=\"") | |||
| 1658 | + | if templ_7745c5c3_Err != nil { | |||
| 1659 | + | return templ_7745c5c3_Err | |||
| 1660 | + | } | |||
| 1661 | + | var templ_7745c5c3_Var79 string | |||
| 1662 | + | templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(g.Key) | |||
| 1663 | + | if templ_7745c5c3_Err != nil { | |||
| 1664 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 309, Col: 41} | |||
| 1665 | + | } | |||
| 1666 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79) | |||
| 1667 | + | if templ_7745c5c3_Err != nil { | |||
| 1668 | + | return templ_7745c5c3_Err | |||
| 1669 | + | } | |||
| 1670 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "\"><td colspan=\"") | |||
| 1671 | + | if templ_7745c5c3_Err != nil { | |||
| 1672 | + | return templ_7745c5c3_Err | |||
| 1673 | + | } | |||
| 1674 | + | var templ_7745c5c3_Var80 string | |||
| 1675 | + | templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(cols)) | |||
| 1676 | + | if templ_7745c5c3_Err != nil { | |||
| 1677 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 310, Col: 26} | |||
| 1678 | + | } | |||
| 1679 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80) | |||
| 1680 | + | if templ_7745c5c3_Err != nil { | |||
| 1681 | + | return templ_7745c5c3_Err | |||
| 1682 | + | } | |||
| 1683 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "\"><button type=\"button\" data-src=\"") | |||
| 1684 | + | if templ_7745c5c3_Err != nil { | |||
| 1685 | + | return templ_7745c5c3_Err | |||
| 1686 | + | } | |||
| 1687 | + | var templ_7745c5c3_Var81 string | |||
| 1688 | + | templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.ResolveAttributeValue(g.Src) | |||
| 1689 | + | if templ_7745c5c3_Err != nil { | |||
| 1690 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 311, Col: 41} | |||
| 1691 | + | } | |||
| 1692 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var81) | |||
| 1693 | + | if templ_7745c5c3_Err != nil { | |||
| 1694 | + | return templ_7745c5c3_Err | |||
| 1695 | + | } | |||
| 1696 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "\"><span aria-hidden=\"true\">⋯</span> ") | |||
| 1697 | + | if templ_7745c5c3_Err != nil { | |||
| 1698 | + | return templ_7745c5c3_Err | |||
| 1699 | + | } | |||
| 1700 | + | var templ_7745c5c3_Var82 string | |||
| 1701 | + | templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(g.Label()) | |||
| 1702 | + | if templ_7745c5c3_Err != nil { | |||
| 1703 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 312, Col: 51} | |||
| 1704 | + | } | |||
| 1705 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82)) | |||
| 1706 | + | if templ_7745c5c3_Err != nil { | |||
| 1707 | + | return templ_7745c5c3_Err | |||
| 1708 | + | } | |||
| 1709 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "</button></td></tr>") | |||
| 1710 | + | if templ_7745c5c3_Err != nil { | |||
| 1711 | + | return templ_7745c5c3_Err | |||
| 1712 | + | } | |||
| 1713 | + | return nil | |||
| 1714 | + | }) | |||
| 1715 | + | } | |||
| 1716 | + | ||||
| 1717 | + | // DiffContext is the hidden lines of a gap, for /context: one body a layout, | |||
| 1718 | + | // whose rows replace the rows of the gap in the table of that layout. | |||
| 1719 | + | func DiffContext(c ContextRows) templ.Component { | |||
| 1720 | + | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |||
| 1721 | + | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |||
| 1722 | + | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | |||
| 1723 | + | return templ_7745c5c3_CtxErr | |||
| 1724 | + | } | |||
| 1725 | + | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | |||
| 1726 | + | if !templ_7745c5c3_IsBuffer { | |||
| 1727 | + | defer func() { | |||
| 1728 | + | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | |||
| 1729 | + | if templ_7745c5c3_Err == nil { | |||
| 1730 | + | templ_7745c5c3_Err = templ_7745c5c3_BufErr | |||
| 1731 | + | } | |||
| 1732 | + | }() | |||
| 1733 | + | } | |||
| 1734 | + | ctx = templ.InitializeContext(ctx) | |||
| 1735 | + | templ_7745c5c3_Var83 := templ.GetChildren(ctx) | |||
| 1736 | + | if templ_7745c5c3_Var83 == nil { | |||
| 1737 | + | templ_7745c5c3_Var83 = templ.NopComponent | |||
| 1738 | + | } | |||
| 1739 | + | ctx = templ.ClearChildren(ctx) | |||
| 1740 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "<table><tbody data-for=\"uni\">") | |||
| 1741 | + | if templ_7745c5c3_Err != nil { | |||
| 1742 | + | return templ_7745c5c3_Err | |||
| 1743 | + | } | |||
| 1744 | + | for _, ln := range c.Lines { | |||
| 1745 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "<tr class=\"ctx\"><td class=\"ln\">") | |||
| 1746 | + | if templ_7745c5c3_Err != nil { | |||
| 1747 | + | return templ_7745c5c3_Err | |||
| 1748 | + | } | |||
| 1749 | + | var templ_7745c5c3_Var84 string | |||
| 1750 | + | templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Old) | |||
| 1751 | + | if templ_7745c5c3_Err != nil { | |||
| 1752 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 325, Col: 28} | |||
| 1753 | + | } | |||
| 1754 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84)) | |||
| 1755 | + | if templ_7745c5c3_Err != nil { | |||
| 1756 | + | return templ_7745c5c3_Err | |||
| 1757 | + | } | |||
| 1758 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "</td><td class=\"ln\">") | |||
| 1759 | + | if templ_7745c5c3_Err != nil { | |||
| 1760 | + | return templ_7745c5c3_Err | |||
| 1761 | + | } | |||
| 1762 | + | var templ_7745c5c3_Var85 string | |||
| 1763 | + | templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(ln.New) | |||
| 1764 | + | if templ_7745c5c3_Err != nil { | |||
| 1765 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 326, Col: 28} | |||
| 1766 | + | } | |||
| 1767 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85)) | |||
| 1768 | + | if templ_7745c5c3_Err != nil { | |||
| 1769 | + | return templ_7745c5c3_Err | |||
| 1770 | + | } | |||
| 1771 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "</td><td class=\"m\">") | |||
| 1772 | + | if templ_7745c5c3_Err != nil { | |||
| 1773 | + | return templ_7745c5c3_Err | |||
| 1774 | + | } | |||
| 1775 | + | var templ_7745c5c3_Var86 string | |||
| 1776 | + | templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | |||
| 1777 | + | if templ_7745c5c3_Err != nil { | |||
| 1778 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 327, Col: 28} | |||
| 1779 | + | } | |||
| 1780 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) | |||
| 1781 | + | if templ_7745c5c3_Err != nil { | |||
| 1782 | + | return templ_7745c5c3_Err | |||
| 1783 | + | } | |||
| 1784 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</td><td class=\"tx\">") | |||
| 1785 | + | if templ_7745c5c3_Err != nil { | |||
| 1786 | + | return templ_7745c5c3_Err | |||
| 1787 | + | } | |||
| 1788 | + | templ_7745c5c3_Err = codeLine(ln).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1789 | + | if templ_7745c5c3_Err != nil { | |||
| 1790 | + | return templ_7745c5c3_Err | |||
| 1791 | + | } | |||
| 1792 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "</td></tr>") | |||
| 1793 | + | if templ_7745c5c3_Err != nil { | |||
| 1794 | + | return templ_7745c5c3_Err | |||
| 1795 | + | } | |||
| 1796 | + | } | |||
| 1797 | + | if c.More != nil { | |||
| 1798 | + | templ_7745c5c3_Err = gapRow(*c.More, 4).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1799 | + | if templ_7745c5c3_Err != nil { | |||
| 1800 | + | return templ_7745c5c3_Err | |||
| 1801 | + | } | |||
| 1802 | + | } | |||
| 1803 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</tbody> <tbody data-for=\"spl\">") | |||
| 1804 | + | if templ_7745c5c3_Err != nil { | |||
| 1805 | + | return templ_7745c5c3_Err | |||
| 1806 | + | } | |||
| 1807 | + | for i := range c.Lines { | |||
| 1808 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<tr>") | |||
| 1809 | + | if templ_7745c5c3_Err != nil { | |||
| 1810 | + | return templ_7745c5c3_Err | |||
| 1811 | + | } | |||
| 1812 | + | templ_7745c5c3_Err = splitSide(&c.Lines[i], true).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1813 | + | if templ_7745c5c3_Err != nil { | |||
| 1814 | + | return templ_7745c5c3_Err | |||
| 1815 | + | } | |||
| 1816 | + | templ_7745c5c3_Err = splitSide(&c.Lines[i], false).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1817 | + | if templ_7745c5c3_Err != nil { | |||
| 1818 | + | return templ_7745c5c3_Err | |||
| 1819 | + | } | |||
| 1820 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</tr>") | |||
| 1821 | + | if templ_7745c5c3_Err != nil { | |||
| 1822 | + | return templ_7745c5c3_Err | |||
| 1823 | + | } | |||
| 1824 | + | } | |||
| 1825 | + | if c.More != nil { | |||
| 1826 | + | templ_7745c5c3_Err = gapRow(*c.More, 6).Render(ctx, templ_7745c5c3_Buffer) | |||
| 1827 | + | if templ_7745c5c3_Err != nil { | |||
| 1828 | + | return templ_7745c5c3_Err | |||
| 1829 | + | } | |||
| 1830 | + | } | |||
| 1831 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</tbody></table>") | |||
| 1832 | + | if templ_7745c5c3_Err != nil { | |||
| 1833 | + | return templ_7745c5c3_Err | |||
| 1834 | + | } | |||
| 1835 | + | return nil | |||
| 1836 | + | }) | |||
| 1837 | + | } | |||
| 1838 | + | ||||
| 1839 | + | // splitSide is one half of a row of the split view; a side without a line is | |||
| 1840 | + | // an empty cell. | |||
| 1841 | + | func splitSide(ln *DiffLine, old bool) templ.Component { | |||
| 1842 | + | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |||
| 1843 | + | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |||
| 1844 | + | if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | |||
| 1845 | + | return templ_7745c5c3_CtxErr | |||
| 1846 | + | } | |||
| 1847 | + | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | |||
| 1848 | + | if !templ_7745c5c3_IsBuffer { | |||
| 1849 | + | defer func() { | |||
| 1850 | + | templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | |||
| 1851 | + | if templ_7745c5c3_Err == nil { | |||
| 1852 | + | templ_7745c5c3_Err = templ_7745c5c3_BufErr | |||
| 1853 | + | } | |||
| 1854 | + | }() | |||
| 1855 | + | } | |||
| 1856 | + | ctx = templ.InitializeContext(ctx) | |||
| 1857 | + | templ_7745c5c3_Var87 := templ.GetChildren(ctx) | |||
| 1858 | + | if templ_7745c5c3_Var87 == nil { | |||
| 1859 | + | templ_7745c5c3_Var87 = templ.NopComponent | |||
| 1860 | + | } | |||
| 1861 | + | ctx = templ.ClearChildren(ctx) | |||
| 1608 | if ln == nil { | 1862 | if ln == nil { | ||
| 1609 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<td class=\"ln none\"></td><td class=\"m none\"></td><td class=\"tx none\"></td>") | 1863 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "<td class=\"ln none\"></td><td class=\"m none\"></td><td class=\"tx none\"></td>") |
| 1610 | if templ_7745c5c3_Err != nil { | 1864 | if templ_7745c5c3_Err != nil { | ||
| 1611 | return templ_7745c5c3_Err | 1865 | return templ_7745c5c3_Err | ||
| 1612 | } | 1866 | } | ||
| 1613 | } else { | 1867 | } else { | ||
| 1614 | - | var templ_7745c5c3_Var79 = []any{"ln", ln.Kind} | 1868 | + | var templ_7745c5c3_Var88 = []any{"ln", ln.Kind} |
| 1615 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var79...) | 1869 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var88...) |
| 1616 | if templ_7745c5c3_Err != nil { | 1870 | if templ_7745c5c3_Err != nil { | ||
| 1617 | return templ_7745c5c3_Err | 1871 | return templ_7745c5c3_Err | ||
| 1618 | } | 1872 | } | ||
| 1619 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "<td class=\"") | 1873 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "<td class=\"") |
| 1620 | if templ_7745c5c3_Err != nil { | 1874 | if templ_7745c5c3_Err != nil { | ||
| 1621 | return templ_7745c5c3_Err | 1875 | return templ_7745c5c3_Err | ||
| 1622 | } | 1876 | } | ||
| 1623 | - | var templ_7745c5c3_Var80 string | 1877 | + | var templ_7745c5c3_Var89 string |
| 1624 | - | templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var79).String()) | 1878 | + | templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var88).String()) |
| 1625 | if templ_7745c5c3_Err != nil { | 1879 | if templ_7745c5c3_Err != nil { | ||
| 1626 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | 1880 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | ||
| 1627 | } | 1881 | } | ||
| 1628 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80) | 1882 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var89) |
| 1629 | if templ_7745c5c3_Err != nil { | 1883 | if templ_7745c5c3_Err != nil { | ||
| 1630 | return templ_7745c5c3_Err | 1884 | return templ_7745c5c3_Err | ||
| 1631 | } | 1885 | } | ||
| 1632 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "\">") | 1886 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "\">") |
| 1633 | if templ_7745c5c3_Err != nil { | 1887 | if templ_7745c5c3_Err != nil { | ||
| 1634 | return templ_7745c5c3_Err | 1888 | return templ_7745c5c3_Err | ||
| 1635 | } | 1889 | } | ||
| @@ -1644,64 +1898,64 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||||
| 1644 | return templ_7745c5c3_Err | 1898 | return templ_7745c5c3_Err | ||
| 1645 | } | 1899 | } | ||
| 1646 | } | 1900 | } | ||
| 1647 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "</td>") | 1901 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "</td>") |
| 1648 | if templ_7745c5c3_Err != nil { | 1902 | if templ_7745c5c3_Err != nil { | ||
| 1649 | return templ_7745c5c3_Err | 1903 | return templ_7745c5c3_Err | ||
| 1650 | } | 1904 | } | ||
| 1651 | - | var templ_7745c5c3_Var81 = []any{"m", ln.Kind} | 1905 | + | var templ_7745c5c3_Var90 = []any{"m", ln.Kind} |
| 1652 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var81...) | 1906 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var90...) |
| 1653 | if templ_7745c5c3_Err != nil { | 1907 | if templ_7745c5c3_Err != nil { | ||
| 1654 | return templ_7745c5c3_Err | 1908 | return templ_7745c5c3_Err | ||
| 1655 | } | 1909 | } | ||
| 1656 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "<td class=\"") | 1910 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "<td class=\"") |
| 1657 | if templ_7745c5c3_Err != nil { | 1911 | if templ_7745c5c3_Err != nil { | ||
| 1658 | return templ_7745c5c3_Err | 1912 | return templ_7745c5c3_Err | ||
| 1659 | } | 1913 | } | ||
| 1660 | - | var templ_7745c5c3_Var82 string | 1914 | + | var templ_7745c5c3_Var91 string |
| 1661 | - | templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var81).String()) | 1915 | + | templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var90).String()) |
| 1662 | if templ_7745c5c3_Err != nil { | 1916 | if templ_7745c5c3_Err != nil { | ||
| 1663 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | 1917 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | ||
| 1664 | } | 1918 | } | ||
| 1665 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var82) | 1919 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var91) |
| 1666 | if templ_7745c5c3_Err != nil { | 1920 | if templ_7745c5c3_Err != nil { | ||
| 1667 | return templ_7745c5c3_Err | 1921 | return templ_7745c5c3_Err | ||
| 1668 | } | 1922 | } | ||
| 1669 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "\">") | 1923 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "\">") |
| 1670 | if templ_7745c5c3_Err != nil { | 1924 | if templ_7745c5c3_Err != nil { | ||
| 1671 | return templ_7745c5c3_Err | 1925 | return templ_7745c5c3_Err | ||
| 1672 | } | 1926 | } | ||
| 1673 | - | var templ_7745c5c3_Var83 string | 1927 | + | var templ_7745c5c3_Var92 string |
| 1674 | - | templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) | 1928 | + | templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Mark) |
| 1675 | if templ_7745c5c3_Err != nil { | 1929 | if templ_7745c5c3_Err != nil { | ||
| 1676 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 304, Col: 38} | 1930 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 364, Col: 38} |
| 1677 | } | 1931 | } | ||
| 1678 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83)) | 1932 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var92)) |
| 1679 | if templ_7745c5c3_Err != nil { | 1933 | if templ_7745c5c3_Err != nil { | ||
| 1680 | return templ_7745c5c3_Err | 1934 | return templ_7745c5c3_Err | ||
| 1681 | } | 1935 | } | ||
| 1682 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</td>") | 1936 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "</td>") |
| 1683 | if templ_7745c5c3_Err != nil { | 1937 | if templ_7745c5c3_Err != nil { | ||
| 1684 | return templ_7745c5c3_Err | 1938 | return templ_7745c5c3_Err | ||
| 1685 | } | 1939 | } | ||
| 1686 | - | var templ_7745c5c3_Var84 = []any{"tx", ln.Kind} | 1940 | + | var templ_7745c5c3_Var93 = []any{"tx", ln.Kind} |
| 1687 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var84...) | 1941 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var93...) |
| 1688 | if templ_7745c5c3_Err != nil { | 1942 | if templ_7745c5c3_Err != nil { | ||
| 1689 | return templ_7745c5c3_Err | 1943 | return templ_7745c5c3_Err | ||
| 1690 | } | 1944 | } | ||
| 1691 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "<td class=\"") | 1945 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "<td class=\"") |
| 1692 | if templ_7745c5c3_Err != nil { | 1946 | if templ_7745c5c3_Err != nil { | ||
| 1693 | return templ_7745c5c3_Err | 1947 | return templ_7745c5c3_Err | ||
| 1694 | } | 1948 | } | ||
| 1695 | - | var templ_7745c5c3_Var85 string | 1949 | + | var templ_7745c5c3_Var94 string |
| 1696 | - | templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var84).String()) | 1950 | + | templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var93).String()) |
| 1697 | if templ_7745c5c3_Err != nil { | 1951 | if templ_7745c5c3_Err != nil { | ||
| 1698 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | 1952 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 1, Col: 0} | ||
| 1699 | } | 1953 | } | ||
| 1700 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var85) | 1954 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var94) |
| 1701 | if templ_7745c5c3_Err != nil { | 1955 | if templ_7745c5c3_Err != nil { | ||
| 1702 | return templ_7745c5c3_Err | 1956 | return templ_7745c5c3_Err | ||
| 1703 | } | 1957 | } | ||
| 1704 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "\">") | 1958 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "\">") |
| 1705 | if templ_7745c5c3_Err != nil { | 1959 | if templ_7745c5c3_Err != nil { | ||
| 1706 | return templ_7745c5c3_Err | 1960 | return templ_7745c5c3_Err | ||
| 1707 | } | 1961 | } | ||
| @@ -1709,7 +1963,7 @@ func splitSide(ln *DiffLine, old bool) templ.Component { | |||||
| 1709 | if templ_7745c5c3_Err != nil { | 1963 | if templ_7745c5c3_Err != nil { | ||
| 1710 | return templ_7745c5c3_Err | 1964 | return templ_7745c5c3_Err | ||
| 1711 | } | 1965 | } | ||
| 1712 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</td>") | 1966 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</td>") |
| 1713 | if templ_7745c5c3_Err != nil { | 1967 | if templ_7745c5c3_Err != nil { | ||
| 1714 | return templ_7745c5c3_Err | 1968 | return templ_7745c5c3_Err | ||
| 1715 | } | 1969 | } | ||
| @@ -1736,49 +1990,49 @@ func gutter(n, href string) templ.Component { | |||||
| 1736 | }() | 1990 | }() | ||
| 1737 | } | 1991 | } | ||
| 1738 | ctx = templ.InitializeContext(ctx) | 1992 | ctx = templ.InitializeContext(ctx) | ||
| 1739 | - | templ_7745c5c3_Var86 := templ.GetChildren(ctx) | 1993 | + | templ_7745c5c3_Var95 := templ.GetChildren(ctx) |
| 1740 | - | if templ_7745c5c3_Var86 == nil { | 1994 | + | if templ_7745c5c3_Var95 == nil { |
| 1741 | - | templ_7745c5c3_Var86 = templ.NopComponent | 1995 | + | templ_7745c5c3_Var95 = templ.NopComponent |
| 1742 | } | 1996 | } | ||
| 1743 | ctx = templ.ClearChildren(ctx) | 1997 | ctx = templ.ClearChildren(ctx) | ||
| 1744 | if href != "" { | 1998 | if href != "" { | ||
| 1745 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<a href=\"") | 1999 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<a href=\"") |
| 1746 | if templ_7745c5c3_Err != nil { | 2000 | if templ_7745c5c3_Err != nil { | ||
| 1747 | return templ_7745c5c3_Err | 2001 | return templ_7745c5c3_Err | ||
| 1748 | } | 2002 | } | ||
| 1749 | - | var templ_7745c5c3_Var87 templ.SafeURL | 2003 | + | var templ_7745c5c3_Var96 templ.SafeURL |
| 1750 | - | templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href)) | 2004 | + | templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href)) |
| 1751 | if templ_7745c5c3_Err != nil { | 2005 | if templ_7745c5c3_Err != nil { | ||
| 1752 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 313, Col: 31} | 2006 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 373, Col: 31} |
| 1753 | } | 2007 | } | ||
| 1754 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) | 2008 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96)) |
| 1755 | if templ_7745c5c3_Err != nil { | 2009 | if templ_7745c5c3_Err != nil { | ||
| 1756 | return templ_7745c5c3_Err | 2010 | return templ_7745c5c3_Err | ||
| 1757 | } | 2011 | } | ||
| 1758 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "\" title=\"comment on this line\">") | 2012 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "\" title=\"comment on this line\">") |
| 1759 | if templ_7745c5c3_Err != nil { | 2013 | if templ_7745c5c3_Err != nil { | ||
| 1760 | return templ_7745c5c3_Err | 2014 | return templ_7745c5c3_Err | ||
| 1761 | } | 2015 | } | ||
| 1762 | - | var templ_7745c5c3_Var88 string | 2016 | + | var templ_7745c5c3_Var97 string |
| 1763 | - | templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(n) | 2017 | + | templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(n) |
| 1764 | if templ_7745c5c3_Err != nil { | 2018 | if templ_7745c5c3_Err != nil { | ||
| 1765 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 313, Col: 66} | 2019 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 373, Col: 66} |
| 1766 | } | 2020 | } | ||
| 1767 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88)) | 2021 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97)) |
| 1768 | if templ_7745c5c3_Err != nil { | 2022 | if templ_7745c5c3_Err != nil { | ||
| 1769 | return templ_7745c5c3_Err | 2023 | return templ_7745c5c3_Err | ||
| 1770 | } | 2024 | } | ||
| 1771 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "</a>") | 2025 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "</a>") |
| 1772 | if templ_7745c5c3_Err != nil { | 2026 | if templ_7745c5c3_Err != nil { | ||
| 1773 | return templ_7745c5c3_Err | 2027 | return templ_7745c5c3_Err | ||
| 1774 | } | 2028 | } | ||
| 1775 | } else { | 2029 | } else { | ||
| 1776 | - | var templ_7745c5c3_Var89 string | 2030 | + | var templ_7745c5c3_Var98 string |
| 1777 | - | templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(n) | 2031 | + | templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(n) |
| 1778 | if templ_7745c5c3_Err != nil { | 2032 | if templ_7745c5c3_Err != nil { | ||
| 1779 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 315, Col: 5} | 2033 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/diff.templ`, Line: 375, Col: 5} |
| 1780 | } | 2034 | } | ||
| 1781 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89)) | 2035 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98)) |
| 1782 | if templ_7745c5c3_Err != nil { | 2036 | if templ_7745c5c3_Err != nil { | ||
| 1783 | return templ_7745c5c3_Err | 2037 | return templ_7745c5c3_Err | ||
| 1784 | } | 2038 | } | ||
M internal/adapters/web/diff_test.go +50 -0
| @@ -141,3 +141,53 @@ func TestCodeLine(t *testing.T) { | |||
| 141 | 141 | assert.Equal(t, `a <b> <span class="inv" title="invisible character"><U+202E></span> c d`+ | |
| 142 | 142 | `<span class="eol" title="no newline at end of file">`+noEOL+`</span>`, b.String()) | |
| 143 | 143 | } | |
| 144 | + | ||
| 145 | + | // The gaps of a file: before a hunk the unchanged lines it does not show | |
| 146 | + | // (the same number on each side), after the last hunk the rest of the file; | |
| 147 | + | // none for a file whose lines are all in the diff. | |
| 148 | + | func TestContextGaps(t *testing.T) { | |
| 149 | + | t.Parallel() | |
| 150 | + | ctx := func(o, n int) domain.Line { return line(domain.LineContext, o, n, "x") } | |
| 151 | + | files := []domain.FileDiff{ | |
| 152 | + | {Status: domain.FileModified, NewPath: "a b.go", Hunks: []domain.Hunk{ | |
| 153 | + | {OldStart: 1, OldLines: 2, NewStart: 1, NewLines: 2, Lines: []domain.Line{ctx(1, 1), ctx(2, 2)}}, | |
| 154 | + | {OldStart: 10, OldLines: 3, NewStart: 10, NewLines: 3, Lines: []domain.Line{ctx(10, 10), ctx(11, 11), ctx(12, 12)}}, | |
| 155 | + | }}, | |
| 156 | + | {Status: domain.FileModified, NewPath: "b.go", Hunks: []domain.Hunk{ | |
| 157 | + | // a pure deletion: the new side has no line, "+4,0" names line 4 | |
| 158 | + | {OldStart: 5, OldLines: 1, NewStart: 4, NewLines: 0, Lines: []domain.Line{line(domain.LineDeletion, 5, 0, "x")}}, | |
| 159 | + | }}, | |
| 160 | + | {Status: domain.FileAdded, NewPath: "c.go", Hunks: []domain.Hunk{{OldStart: 0, NewStart: 1, NewLines: 1, Lines: []domain.Line{line(domain.LineAddition, 0, 1, "x")}}}}, | |
| 161 | + | {Status: domain.FileModified, NewPath: "d.go", Hunks: []domain.Hunk{ | |
| 162 | + | {OldStart: 3, OldLines: 1, NewStart: 3, NewLines: 1, Lines: []domain.Line{{Kind: domain.LineAddition, NewNumber: 3, Text: "end", NoNewlineAtEOF: true}}}, | |
| 163 | + | }}, | |
| 164 | + | } | |
| 165 | + | out := diffFiles(files) | |
| 166 | + | contextGaps(out, files, "/r/context/c") | |
| 167 | + | ||
| 168 | + | a := out[0] | |
| 169 | + | assert.Nil(t, a.Hunks[0].Gap, "the first hunk starts at line 1") | |
| 170 | + | require.NotNil(t, a.Hunks[1].Gap) | |
| 171 | + | assert.Equal(t, 7, a.Hunks[1].Gap.Lines, "lines 3 to 9 on both sides") | |
| 172 | + | assert.Equal(t, "/r/context/c?key=g1&n=7&new=3&old=3&path=a+b.go", a.Hunks[1].Gap.Src) | |
| 173 | + | require.NotNil(t, a.Tail) | |
| 174 | + | assert.Equal(t, "/r/context/c?key=tail&n=0&new=13&old=13&path=a+b.go", a.Tail.Src) | |
| 175 | + | ||
| 176 | + | b := out[1] | |
| 177 | + | require.NotNil(t, b.Hunks[0].Gap) | |
| 178 | + | assert.Equal(t, 4, b.Hunks[0].Gap.Lines, "lines 1 to 4 on both sides before the deleted line 5") | |
| 179 | + | assert.Nil(t, b.Tail, "a hunk that ends on a change ends the file") | |
| 180 | + | ||
| 181 | + | assert.Nil(t, out[2].Tail, "an added file shows every line") | |
| 182 | + | assert.Nil(t, out[3].Tail, "the last hunk holds the end of the file") | |
| 183 | + | assert.NotNil(t, a.Tail, "three unchanged lines after the change: the file may go on") | |
| 184 | + | assert.NotNil(t, out[3].Hunks[0].Gap) | |
| 185 | + | ||
| 186 | + | // Sides that do not move together (a parse the page cannot trust): no gap. | |
| 187 | + | odd := []domain.FileDiff{{Status: domain.FileModified, NewPath: "e.go", Hunks: []domain.Hunk{ | |
| 188 | + | {OldStart: 10, OldLines: 1, NewStart: 12, NewLines: 1, Lines: []domain.Line{ctx(10, 12)}}, | |
| 189 | + | }}} | |
| 190 | + | o := diffFiles(odd) | |
| 191 | + | contextGaps(o, odd, "/r/context/c") | |
| 192 | + | assert.Nil(t, o[0].Hunks[0].Gap) | |
| 193 | + | } | |
| @@ -141,3 +141,53 @@ func TestCodeLine(t *testing.T) { | |||||
| 141 | assert.Equal(t, `a <b> <span class="inv" title="invisible character"><U+202E></span> c d`+ | 141 | assert.Equal(t, `a <b> <span class="inv" title="invisible character"><U+202E></span> c d`+ | ||
| 142 | `<span class="eol" title="no newline at end of file">`+noEOL+`</span>`, b.String()) | 142 | `<span class="eol" title="no newline at end of file">`+noEOL+`</span>`, b.String()) | ||
| 143 | } | 143 | } | ||
| 144 | + | ||||
| 145 | + | // The gaps of a file: before a hunk the unchanged lines it does not show | |||
| 146 | + | // (the same number on each side), after the last hunk the rest of the file; | |||
| 147 | + | // none for a file whose lines are all in the diff. | |||
| 148 | + | func TestContextGaps(t *testing.T) { | |||
| 149 | + | t.Parallel() | |||
| 150 | + | ctx := func(o, n int) domain.Line { return line(domain.LineContext, o, n, "x") } | |||
| 151 | + | files := []domain.FileDiff{ | |||
| 152 | + | {Status: domain.FileModified, NewPath: "a b.go", Hunks: []domain.Hunk{ | |||
| 153 | + | {OldStart: 1, OldLines: 2, NewStart: 1, NewLines: 2, Lines: []domain.Line{ctx(1, 1), ctx(2, 2)}}, | |||
| 154 | + | {OldStart: 10, OldLines: 3, NewStart: 10, NewLines: 3, Lines: []domain.Line{ctx(10, 10), ctx(11, 11), ctx(12, 12)}}, | |||
| 155 | + | }}, | |||
| 156 | + | {Status: domain.FileModified, NewPath: "b.go", Hunks: []domain.Hunk{ | |||
| 157 | + | // a pure deletion: the new side has no line, "+4,0" names line 4 | |||
| 158 | + | {OldStart: 5, OldLines: 1, NewStart: 4, NewLines: 0, Lines: []domain.Line{line(domain.LineDeletion, 5, 0, "x")}}, | |||
| 159 | + | }}, | |||
| 160 | + | {Status: domain.FileAdded, NewPath: "c.go", Hunks: []domain.Hunk{{OldStart: 0, NewStart: 1, NewLines: 1, Lines: []domain.Line{line(domain.LineAddition, 0, 1, "x")}}}}, | |||
| 161 | + | {Status: domain.FileModified, NewPath: "d.go", Hunks: []domain.Hunk{ | |||
| 162 | + | {OldStart: 3, OldLines: 1, NewStart: 3, NewLines: 1, Lines: []domain.Line{{Kind: domain.LineAddition, NewNumber: 3, Text: "end", NoNewlineAtEOF: true}}}, | |||
| 163 | + | }}, | |||
| 164 | + | } | |||
| 165 | + | out := diffFiles(files) | |||
| 166 | + | contextGaps(out, files, "/r/context/c") | |||
| 167 | + | ||||
| 168 | + | a := out[0] | |||
| 169 | + | assert.Nil(t, a.Hunks[0].Gap, "the first hunk starts at line 1") | |||
| 170 | + | require.NotNil(t, a.Hunks[1].Gap) | |||
| 171 | + | assert.Equal(t, 7, a.Hunks[1].Gap.Lines, "lines 3 to 9 on both sides") | |||
| 172 | + | assert.Equal(t, "/r/context/c?key=g1&n=7&new=3&old=3&path=a+b.go", a.Hunks[1].Gap.Src) | |||
| 173 | + | require.NotNil(t, a.Tail) | |||
| 174 | + | assert.Equal(t, "/r/context/c?key=tail&n=0&new=13&old=13&path=a+b.go", a.Tail.Src) | |||
| 175 | + | ||||
| 176 | + | b := out[1] | |||
| 177 | + | require.NotNil(t, b.Hunks[0].Gap) | |||
| 178 | + | assert.Equal(t, 4, b.Hunks[0].Gap.Lines, "lines 1 to 4 on both sides before the deleted line 5") | |||
| 179 | + | assert.Nil(t, b.Tail, "a hunk that ends on a change ends the file") | |||
| 180 | + | ||||
| 181 | + | assert.Nil(t, out[2].Tail, "an added file shows every line") | |||
| 182 | + | assert.Nil(t, out[3].Tail, "the last hunk holds the end of the file") | |||
| 183 | + | assert.NotNil(t, a.Tail, "three unchanged lines after the change: the file may go on") | |||
| 184 | + | assert.NotNil(t, out[3].Hunks[0].Gap) | |||
| 185 | + | ||||
| 186 | + | // Sides that do not move together (a parse the page cannot trust): no gap. | |||
| 187 | + | odd := []domain.FileDiff{{Status: domain.FileModified, NewPath: "e.go", Hunks: []domain.Hunk{ | |||
| 188 | + | {OldStart: 10, OldLines: 1, NewStart: 12, NewLines: 1, Lines: []domain.Line{ctx(10, 12)}}, | |||
| 189 | + | }}} | |||
| 190 | + | o := diffFiles(odd) | |||
| 191 | + | contextGaps(o, odd, "/r/context/c") | |||
| 192 | + | assert.Nil(t, o[0].Hunks[0].Gap) | |||
| 193 | + | } | |||
M internal/adapters/web/keys.templ +2 -0
| @@ -31,6 +31,8 @@ templ Keys() { | |||
| 31 | 31 | k-split from:body -> ermite.layout('split'); | |
| 32 | 32 | k-unified from:body -> ermite.layout('unified'); | |
| 33 | 33 | k-collapse from:body -> ermite.collapse(); | |
| 34 | + | k-expand from:body -> ermite.expandFile(); | |
| 35 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 34 | 36 | k-permalink from:body -> ermite.permalink(); | |
| 35 | 37 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 36 | 38 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -31,6 +31,8 @@ templ Keys() { | |||||
| 31 | k-split from:body -> ermite.layout('split'); | 31 | k-split from:body -> ermite.layout('split'); | ||
| 32 | k-unified from:body -> ermite.layout('unified'); | 32 | k-unified from:body -> ermite.layout('unified'); | ||
| 33 | k-collapse from:body -> ermite.collapse(); | 33 | k-collapse from:body -> ermite.collapse(); | ||
| 34 | + | k-expand from:body -> ermite.expandFile(); | |||
| 35 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 34 | k-permalink from:body -> ermite.permalink(); | 36 | k-permalink from:body -> ermite.permalink(); | ||
| 35 | k-merge from:body -> window.ermiteMR?.openMerge(); | 37 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 36 | k-read from:body -> window.ermiteMR?.markRead(); | 38 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/adapters/web/keys_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/mr.go +1 -0
| @@ -476,6 +476,7 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ | |||
| 476 | 476 | Dels: res.Stats.Deletions, | |
| 477 | 477 | Files: diffFiles(res.Diff.Files), | |
| 478 | 478 | } | |
| 479 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head)) | |
| 479 | 480 | p.Opened.Name = p.Author | |
| 480 | 481 | p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt) | |
| 481 | 482 | if !m.CreatedAt.IsZero() { | |
| @@ -476,6 +476,7 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ | |||||
| 476 | Dels: res.Stats.Deletions, | 476 | Dels: res.Stats.Deletions, | ||
| 477 | Files: diffFiles(res.Diff.Files), | 477 | Files: diffFiles(res.Diff.Files), | ||
| 478 | } | 478 | } | ||
| 479 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head)) | |||
| 479 | p.Opened.Name = p.Author | 480 | p.Opened.Name = p.Author | ||
| 480 | p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt) | 481 | p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt) | ||
| 481 | if !m.CreatedAt.IsZero() { | 482 | if !m.CreatedAt.IsZero() { | ||
M internal/adapters/web/mr_files.go +1 -0
| @@ -197,6 +197,7 @@ func (s *Server) mrFiles(ctx context.Context, r *http.Request) (templ.Component, | |||
| 197 | 197 | signed := SignedIn(ctx) | |
| 198 | 198 | canWrite := signed && m.State == domain.MRStateOpen && !since | |
| 199 | 199 | p := MRPage{Files: diffFiles(res.Diff.Files), LooseInFiles: true} | |
| 200 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head.String())) | |
| 200 | 201 | s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil) | |
| 201 | 202 | window := p.Files[from:min(from+mrWindow, len(p.Files))] | |
| 202 | 203 | // The layouts follow the page: it sends ?view= (the layout shown) and | |
| @@ -197,6 +197,7 @@ func (s *Server) mrFiles(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 197 | signed := SignedIn(ctx) | 197 | signed := SignedIn(ctx) | ||
| 198 | canWrite := signed && m.State == domain.MRStateOpen && !since | 198 | canWrite := signed && m.State == domain.MRStateOpen && !since | ||
| 199 | p := MRPage{Files: diffFiles(res.Diff.Files), LooseInFiles: true} | 199 | p := MRPage{Files: diffFiles(res.Diff.Files), LooseInFiles: true} | ||
| 200 | + | contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head.String())) | |||
| 200 | s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil) | 201 | s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil) | ||
| 201 | window := p.Files[from:min(from+mrWindow, len(p.Files))] | 202 | window := p.Files[from:min(from+mrWindow, len(p.Files))] | ||
| 202 | // The layouts follow the page: it sends ?view= (the layout shown) and | 203 | // The layouts follow the page: it sends ?view= (the layout shown) and | ||
M internal/adapters/web/overlays.templ +1 -0
| @@ -22,6 +22,7 @@ templ OverlayHelp(mr bool) { | |||
| 22 | 22 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | |
| 23 | 23 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | |
| 24 | 24 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | |
| 25 | + | <div><dt><kbd>e</kbd> <kbd>E</kbd></dt><dd>diff: the whole file in view / every file, with the lines the hunks hide</dd></div> | |
| 25 | 26 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | |
| 26 | 27 | if mr { | |
| 27 | 28 | <div><dt><kbd>g f</kbd></dt><dd>merge request: the list of files (j k move, enter opens, esc back)</dd></div> | |
| @@ -22,6 +22,7 @@ templ OverlayHelp(mr bool) { | |||||
| 22 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | 22 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | ||
| 23 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | 23 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | ||
| 24 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | 24 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | ||
| 25 | + | <div><dt><kbd>e</kbd> <kbd>E</kbd></dt><dd>diff: the whole file in view / every file, with the lines the hunks hide</dd></div> | |||
| 25 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | 26 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | ||
| 26 | if mr { | 27 | if mr { | ||
| 27 | <div><dt><kbd>g f</kbd></dt><dd>merge request: the list of files (j k move, enter opens, esc back)</dd></div> | 28 | <div><dt><kbd>g f</kbd></dt><dd>merge request: the list of files (j k move, enter opens, esc back)</dd></div> | ||
M internal/adapters/web/overlays_templ.go +9 -9 generated file: collapsed
M internal/adapters/web/static/css/50-code.css +9 -0
| @@ -80,6 +80,15 @@ | |||
| 80 | 80 | & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); } | |
| 81 | 81 | & .tx { white-space: pre; } | |
| 82 | 82 | & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; } | |
| 83 | + | /* a run of hidden lines: the whole row loads them */ | |
| 84 | + | & .gap td { padding: 0; background: var(--paper-alt); } | |
| 85 | + | & .gap button { | |
| 86 | + | display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer; | |
| 87 | + | font: inherit; color: var(--ink-dim); background: none; border: 0; | |
| 88 | + | border-block: 1px dashed var(--border); | |
| 89 | + | } | |
| 90 | + | & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); } | |
| 91 | + | & .gap button:disabled { cursor: progress; opacity: .6; } | |
| 83 | 92 | & .add, & tr.add td { background: var(--add); } | |
| 84 | 93 | & .del, & tr.del td { background: var(--del); } | |
| 85 | 94 | & .add.m, & tr.add .m { color: var(--teal); font-weight: bold; } | |
| @@ -80,6 +80,15 @@ | |||||
| 80 | & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); } | 80 | & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); } | ||
| 81 | & .tx { white-space: pre; } | 81 | & .tx { white-space: pre; } | ||
| 82 | & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; } | 82 | & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; } | ||
| 83 | + | /* a run of hidden lines: the whole row loads them */ | |||
| 84 | + | & .gap td { padding: 0; background: var(--paper-alt); } | |||
| 85 | + | & .gap button { | |||
| 86 | + | display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer; | |||
| 87 | + | font: inherit; color: var(--ink-dim); background: none; border: 0; | |||
| 88 | + | border-block: 1px dashed var(--border); | |||
| 89 | + | } | |||
| 90 | + | & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); } | |||
| 91 | + | & .gap button:disabled { cursor: progress; opacity: .6; } | |||
| 83 | & .add, & tr.add td { background: var(--add); } | 92 | & .add, & tr.add td { background: var(--add); } | ||
| 84 | & .del, & tr.del td { background: var(--del); } | 93 | & .del, & tr.del td { background: var(--del); } | ||
| 85 | & .add.m, & tr.add .m { color: var(--teal); font-weight: bold; } | 94 | & .add.m, & tr.add .m { color: var(--teal); font-weight: bold; } | ||
M internal/adapters/web/static/js/ermite.js +109 -1
| @@ -10,6 +10,7 @@ | |||
| 10 | 10 | // 3. ermiteLive applies the fragments of the event stream (see events.go). | |
| 11 | 11 | // 4. The file of the diff in view is marked in the table of contents. | |
| 12 | 12 | // 5. A handler that fails is shown in the hint badge. | |
| 13 | + | // 6. The lines a diff hides load in place (ermiteGaps; e and E). | |
| 13 | 14 | (() => { | |
| 14 | 15 | // Marks the page as scripted: what does nothing without scripts (the filters, | |
| 15 | 16 | // the "go to" and help buttons) is hidden until this class is set (see the | |
| @@ -179,6 +180,26 @@ | |||
| 179 | 180 | const open = !files.some((d) => d.open); | |
| 180 | 181 | for (const d of files) d.open = open; | |
| 181 | 182 | }, | |
| 183 | + | // e / E: the whole file in view, or every file of the diff, with every | |
| 184 | + | // line its hunks hide. | |
| 185 | + | expandFile() { | |
| 186 | + | const f = window.ermiteGaps.current(); | |
| 187 | + | if (!f) return flash('no file in view'); | |
| 188 | + | f.open = true; | |
| 189 | + | window.ermiteGaps.file(f).then((r) => { | |
| 190 | + | if (!r.done) return; // load said what failed | |
| 191 | + | flash(r.lines ? 'whole file' : 'nothing hidden in this file'); | |
| 192 | + | }); | |
| 193 | + | }, | |
| 194 | + | async expandAll() { | |
| 195 | + | const files = $$('[data-file]').filter((f) => f.querySelector('tr.gap')); | |
| 196 | + | if (!files.length) return flash('nothing hidden in the loaded files'); | |
| 197 | + | flash('loading every line…', 10000); | |
| 198 | + | for (const f of files) { | |
| 199 | + | if (!(await window.ermiteGaps.file(f)).done) return; // load said what failed | |
| 200 | + | } | |
| 201 | + | flash('every line of the loaded files'); | |
| 202 | + | }, | |
| 182 | 203 | nextNote() { | |
| 183 | 204 | const notes = $$('#diffs .cmt'); | |
| 184 | 205 | if (!notes.length) return; | |
| @@ -280,9 +301,96 @@ | |||
| 280 | 301 | }; | |
| 281 | 302 | })(); | |
| 282 | 303 | ||
| 304 | + | // The hidden lines of a diff: a row .gap holds a button that loads them | |
| 305 | + | // (/context); the rows that come back replace the gap in the table of each | |
| 306 | + | // layout (the unified and the split tables hold one row each for it). A | |
| 307 | + | // file whose lines are all loaded has no gap left. | |
| 308 | + | window.ermiteGaps = (() => { | |
| 309 | + | const pending = new Map(); | |
| 310 | + | // load fills the gap the button is in, in both tables of its file. | |
| 311 | + | function load(btn) { | |
| 312 | + | const src = btn.dataset.src; | |
| 313 | + | const row = btn.closest('tr.gap'); | |
| 314 | + | const file = row?.closest('[data-file]'); | |
| 315 | + | if (!src || !file) return Promise.resolve(0); | |
| 316 | + | if (pending.has(src)) return pending.get(src); | |
| 317 | + | const key = row.dataset.gap; | |
| 318 | + | const table = btn.closest('table'); | |
| 319 | + | const focused = document.activeElement === btn; | |
| 320 | + | btn.disabled = true; | |
| 321 | + | const p = fetch(src, { headers: { Accept: 'text/html' }, credentials: 'same-origin' }) | |
| 322 | + | .then(async (res) => { | |
| 323 | + | if (!res.ok) throw new Error('loading the hidden lines: ' + res.status); | |
| 324 | + | const t = document.createElement('template'); | |
| 325 | + | t.innerHTML = await res.text(); | |
| 326 | + | let n = 0; | |
| 327 | + | for (const table of file.querySelectorAll('table.dt')) { | |
| 328 | + | const kind = table.classList.contains('spl') ? 'spl' : 'uni'; | |
| 329 | + | const from = t.content.querySelector('tbody[data-for="' + kind + '"]'); | |
| 330 | + | const at = [...table.querySelectorAll('tr.gap')].find((r) => r.dataset.gap === key); | |
| 331 | + | if (!from || !at) continue; | |
| 332 | + | const rows = [...from.children].map((r) => r.cloneNode(true)); | |
| 333 | + | n = Math.max(n, rows.filter((r) => !r.classList.contains('gap')).length); | |
| 334 | + | at.replaceWith(...rows); | |
| 335 | + | } | |
| 336 | + | // The focus was on the button, which is gone: on the gap that is | |
| 337 | + | // left in the same table, else on the content. | |
| 338 | + | if (focused && (document.activeElement === btn || document.activeElement === document.body)) { | |
| 339 | + | const next = [...(table?.querySelectorAll('tr.gap') || [])].find((r) => r.dataset.gap === key)?.querySelector('button'); | |
| 340 | + | if (next) next.focus({ preventScroll: true }); | |
| 341 | + | else document.getElementById('main')?.focus({ preventScroll: true }); | |
| 342 | + | } | |
| 343 | + | return n; | |
| 344 | + | }) | |
| 345 | + | .catch((err) => { | |
| 346 | + | btn.disabled = false; | |
| 347 | + | window.ermite?.flash?.(err.message, 4000); | |
| 348 | + | return -1; | |
| 349 | + | }) | |
| 350 | + | .finally(() => pending.delete(src)); | |
| 351 | + | pending.set(src, p); | |
| 352 | + | return p; | |
| 353 | + | } | |
| 354 | + | // file loads every gap of a file, until none is left (a long gap comes | |
| 355 | + | // back with a gap for the rest). A gap already loading is waited for. | |
| 356 | + | // done is false when a load failed (it said why) or the file never ran | |
| 357 | + | // out of gaps. | |
| 358 | + | async function file(f) { | |
| 359 | + | let lines = 0; | |
| 360 | + | for (let guard = 0; guard < 1000; guard++) { | |
| 361 | + | const btn = f.querySelector('tr.gap button[data-src]'); | |
| 362 | + | if (!btn) return { done: true, lines }; | |
| 363 | + | const n = await load(btn); | |
| 364 | + | if (n < 0) return { done: false, lines }; | |
| 365 | + | if (btn.isConnected) { | |
| 366 | + | // an answer that did not take the gap's place: stop, not loop | |
| 367 | + | window.ermite?.flash?.('some hidden lines could not be shown', 4000); | |
| 368 | + | return { done: false, lines }; | |
| 369 | + | } | |
| 370 | + | lines += n; | |
| 371 | + | } | |
| 372 | + | window.ermite?.flash?.('the file is too long to show every line at once', 4000); | |
| 373 | + | return { done: false, lines }; | |
| 374 | + | } | |
| 375 | + | // current is the file in view: the one the table of contents marks, else | |
| 376 | + | // the last whose top is above the top third of the window. | |
| 377 | + | function current() { | |
| 378 | + | const a = document.querySelector('.files a[aria-current="true"]'); | |
| 379 | + | const marked = a && document.getElementById(a.hash.slice(1)); | |
| 380 | + | if (marked?.matches('[data-file]')) return marked; | |
| 381 | + | const files = [...document.querySelectorAll('[data-file]')]; | |
| 382 | + | return files.findLast((f) => f.getBoundingClientRect().top < innerHeight / 3) ?? files[0] ?? null; | |
| 383 | + | } | |
| 384 | + | addEventListener('click', (e) => { | |
| 385 | + | const btn = e.target.closest?.('tr.gap button[data-src]'); | |
| 386 | + | if (btn) load(btn); | |
| 387 | + | }); | |
| 388 | + | return { load, file, current }; | |
| 389 | + | })(); | |
| 390 | + | ||
| 283 | 391 | // 1. Keys. | |
| 284 | 392 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | |
| 285 | - | s: 's', t: 't', c: 'c', r: 'r', m: 'm', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', Enter: 'open', Escape: 'esc', | |
| 393 | + | s: 's', t: 't', c: 'c', r: 'r', m: 'm', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', | |
| 286 | 394 | // the merge request page (mr.js): the merge menu, mark read | |
| 287 | 395 | M: 'merge', R: 'read' }; | |
| 288 | 396 | // "g t" is the key "t": the tabs have one event each. | |
| @@ -10,6 +10,7 @@ | |||||
| 10 | // 3. ermiteLive applies the fragments of the event stream (see events.go). | 10 | // 3. ermiteLive applies the fragments of the event stream (see events.go). | ||
| 11 | // 4. The file of the diff in view is marked in the table of contents. | 11 | // 4. The file of the diff in view is marked in the table of contents. | ||
| 12 | // 5. A handler that fails is shown in the hint badge. | 12 | // 5. A handler that fails is shown in the hint badge. | ||
| 13 | + | // 6. The lines a diff hides load in place (ermiteGaps; e and E). | |||
| 13 | (() => { | 14 | (() => { | ||
| 14 | // Marks the page as scripted: what does nothing without scripts (the filters, | 15 | // Marks the page as scripted: what does nothing without scripts (the filters, | ||
| 15 | // the "go to" and help buttons) is hidden until this class is set (see the | 16 | // the "go to" and help buttons) is hidden until this class is set (see the | ||
| @@ -179,6 +180,26 @@ | |||||
| 179 | const open = !files.some((d) => d.open); | 180 | const open = !files.some((d) => d.open); | ||
| 180 | for (const d of files) d.open = open; | 181 | for (const d of files) d.open = open; | ||
| 181 | }, | 182 | }, | ||
| 183 | + | // e / E: the whole file in view, or every file of the diff, with every | |||
| 184 | + | // line its hunks hide. | |||
| 185 | + | expandFile() { | |||
| 186 | + | const f = window.ermiteGaps.current(); | |||
| 187 | + | if (!f) return flash('no file in view'); | |||
| 188 | + | f.open = true; | |||
| 189 | + | window.ermiteGaps.file(f).then((r) => { | |||
| 190 | + | if (!r.done) return; // load said what failed | |||
| 191 | + | flash(r.lines ? 'whole file' : 'nothing hidden in this file'); | |||
| 192 | + | }); | |||
| 193 | + | }, | |||
| 194 | + | async expandAll() { | |||
| 195 | + | const files = $$('[data-file]').filter((f) => f.querySelector('tr.gap')); | |||
| 196 | + | if (!files.length) return flash('nothing hidden in the loaded files'); | |||
| 197 | + | flash('loading every line…', 10000); | |||
| 198 | + | for (const f of files) { | |||
| 199 | + | if (!(await window.ermiteGaps.file(f)).done) return; // load said what failed | |||
| 200 | + | } | |||
| 201 | + | flash('every line of the loaded files'); | |||
| 202 | + | }, | |||
| 182 | nextNote() { | 203 | nextNote() { | ||
| 183 | const notes = $$('#diffs .cmt'); | 204 | const notes = $$('#diffs .cmt'); | ||
| 184 | if (!notes.length) return; | 205 | if (!notes.length) return; | ||
| @@ -280,9 +301,96 @@ | |||||
| 280 | }; | 301 | }; | ||
| 281 | })(); | 302 | })(); | ||
| 282 | 303 | ||||
| 304 | + | // The hidden lines of a diff: a row .gap holds a button that loads them | |||
| 305 | + | // (/context); the rows that come back replace the gap in the table of each | |||
| 306 | + | // layout (the unified and the split tables hold one row each for it). A | |||
| 307 | + | // file whose lines are all loaded has no gap left. | |||
| 308 | + | window.ermiteGaps = (() => { | |||
| 309 | + | const pending = new Map(); | |||
| 310 | + | // load fills the gap the button is in, in both tables of its file. | |||
| 311 | + | function load(btn) { | |||
| 312 | + | const src = btn.dataset.src; | |||
| 313 | + | const row = btn.closest('tr.gap'); | |||
| 314 | + | const file = row?.closest('[data-file]'); | |||
| 315 | + | if (!src || !file) return Promise.resolve(0); | |||
| 316 | + | if (pending.has(src)) return pending.get(src); | |||
| 317 | + | const key = row.dataset.gap; | |||
| 318 | + | const table = btn.closest('table'); | |||
| 319 | + | const focused = document.activeElement === btn; | |||
| 320 | + | btn.disabled = true; | |||
| 321 | + | const p = fetch(src, { headers: { Accept: 'text/html' }, credentials: 'same-origin' }) | |||
| 322 | + | .then(async (res) => { | |||
| 323 | + | if (!res.ok) throw new Error('loading the hidden lines: ' + res.status); | |||
| 324 | + | const t = document.createElement('template'); | |||
| 325 | + | t.innerHTML = await res.text(); | |||
| 326 | + | let n = 0; | |||
| 327 | + | for (const table of file.querySelectorAll('table.dt')) { | |||
| 328 | + | const kind = table.classList.contains('spl') ? 'spl' : 'uni'; | |||
| 329 | + | const from = t.content.querySelector('tbody[data-for="' + kind + '"]'); | |||
| 330 | + | const at = [...table.querySelectorAll('tr.gap')].find((r) => r.dataset.gap === key); | |||
| 331 | + | if (!from || !at) continue; | |||
| 332 | + | const rows = [...from.children].map((r) => r.cloneNode(true)); | |||
| 333 | + | n = Math.max(n, rows.filter((r) => !r.classList.contains('gap')).length); | |||
| 334 | + | at.replaceWith(...rows); | |||
| 335 | + | } | |||
| 336 | + | // The focus was on the button, which is gone: on the gap that is | |||
| 337 | + | // left in the same table, else on the content. | |||
| 338 | + | if (focused && (document.activeElement === btn || document.activeElement === document.body)) { | |||
| 339 | + | const next = [...(table?.querySelectorAll('tr.gap') || [])].find((r) => r.dataset.gap === key)?.querySelector('button'); | |||
| 340 | + | if (next) next.focus({ preventScroll: true }); | |||
| 341 | + | else document.getElementById('main')?.focus({ preventScroll: true }); | |||
| 342 | + | } | |||
| 343 | + | return n; | |||
| 344 | + | }) | |||
| 345 | + | .catch((err) => { | |||
| 346 | + | btn.disabled = false; | |||
| 347 | + | window.ermite?.flash?.(err.message, 4000); | |||
| 348 | + | return -1; | |||
| 349 | + | }) | |||
| 350 | + | .finally(() => pending.delete(src)); | |||
| 351 | + | pending.set(src, p); | |||
| 352 | + | return p; | |||
| 353 | + | } | |||
| 354 | + | // file loads every gap of a file, until none is left (a long gap comes | |||
| 355 | + | // back with a gap for the rest). A gap already loading is waited for. | |||
| 356 | + | // done is false when a load failed (it said why) or the file never ran | |||
| 357 | + | // out of gaps. | |||
| 358 | + | async function file(f) { | |||
| 359 | + | let lines = 0; | |||
| 360 | + | for (let guard = 0; guard < 1000; guard++) { | |||
| 361 | + | const btn = f.querySelector('tr.gap button[data-src]'); | |||
| 362 | + | if (!btn) return { done: true, lines }; | |||
| 363 | + | const n = await load(btn); | |||
| 364 | + | if (n < 0) return { done: false, lines }; | |||
| 365 | + | if (btn.isConnected) { | |||
| 366 | + | // an answer that did not take the gap's place: stop, not loop | |||
| 367 | + | window.ermite?.flash?.('some hidden lines could not be shown', 4000); | |||
| 368 | + | return { done: false, lines }; | |||
| 369 | + | } | |||
| 370 | + | lines += n; | |||
| 371 | + | } | |||
| 372 | + | window.ermite?.flash?.('the file is too long to show every line at once', 4000); | |||
| 373 | + | return { done: false, lines }; | |||
| 374 | + | } | |||
| 375 | + | // current is the file in view: the one the table of contents marks, else | |||
| 376 | + | // the last whose top is above the top third of the window. | |||
| 377 | + | function current() { | |||
| 378 | + | const a = document.querySelector('.files a[aria-current="true"]'); | |||
| 379 | + | const marked = a && document.getElementById(a.hash.slice(1)); | |||
| 380 | + | if (marked?.matches('[data-file]')) return marked; | |||
| 381 | + | const files = [...document.querySelectorAll('[data-file]')]; | |||
| 382 | + | return files.findLast((f) => f.getBoundingClientRect().top < innerHeight / 3) ?? files[0] ?? null; | |||
| 383 | + | } | |||
| 384 | + | addEventListener('click', (e) => { | |||
| 385 | + | const btn = e.target.closest?.('tr.gap button[data-src]'); | |||
| 386 | + | if (btn) load(btn); | |||
| 387 | + | }); | |||
| 388 | + | return { load, file, current }; | |||
| 389 | + | })(); | |||
| 390 | + | ||||
| 283 | // 1. Keys. | 391 | // 1. Keys. | ||
| 284 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | 392 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | ||
| 285 | - | s: 's', t: 't', c: 'c', r: 'r', m: 'm', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', Enter: 'open', Escape: 'esc', | 393 | + | s: 's', t: 't', c: 'c', r: 'r', m: 'm', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', |
| 286 | // the merge request page (mr.js): the merge menu, mark read | 394 | // the merge request page (mr.js): the merge menu, mark read | ||
| 287 | M: 'merge', R: 'read' }; | 395 | M: 'merge', R: 'read' }; | ||
| 288 | // "g t" is the key "t": the tabs have one event each. | 396 | // "g t" is the key "t": the tabs have one event each. | ||
M internal/adapters/web/web.go +1 -0
| @@ -172,6 +172,7 @@ func (s *Server) routes() *http.ServeMux { | |||
| 172 | 172 | mux.HandleFunc("GET /{repo}/blob/{rest...}", s.page(s.blob)) | |
| 173 | 173 | mux.HandleFunc("GET /{repo}/raw/{rest...}", s.raw) | |
| 174 | 174 | mux.HandleFunc("GET /{repo}/paths/{sha}", s.paths) | |
| 175 | + | mux.HandleFunc("GET /{repo}/context/{sha}", s.page(s.diffContext)) | |
| 175 | 176 | if s.cfg.MergeRequests != nil { | |
| 176 | 177 | mux.HandleFunc("GET /{repo}/merge-requests", s.page(s.mrList)) | |
| 177 | 178 | mux.HandleFunc("GET /{repo}/merge-requests/{$}", s.page(s.mrList)) | |
| @@ -172,6 +172,7 @@ func (s *Server) routes() *http.ServeMux { | |||||
| 172 | mux.HandleFunc("GET /{repo}/blob/{rest...}", s.page(s.blob)) | 172 | mux.HandleFunc("GET /{repo}/blob/{rest...}", s.page(s.blob)) | ||
| 173 | mux.HandleFunc("GET /{repo}/raw/{rest...}", s.raw) | 173 | mux.HandleFunc("GET /{repo}/raw/{rest...}", s.raw) | ||
| 174 | mux.HandleFunc("GET /{repo}/paths/{sha}", s.paths) | 174 | mux.HandleFunc("GET /{repo}/paths/{sha}", s.paths) | ||
| 175 | + | mux.HandleFunc("GET /{repo}/context/{sha}", s.page(s.diffContext)) | |||
| 175 | if s.cfg.MergeRequests != nil { | 176 | if s.cfg.MergeRequests != nil { | ||
| 176 | mux.HandleFunc("GET /{repo}/merge-requests", s.page(s.mrList)) | 177 | mux.HandleFunc("GET /{repo}/merge-requests", s.page(s.mrList)) | ||
| 177 | mux.HandleFunc("GET /{repo}/merge-requests/{$}", s.page(s.mrList)) | 178 | mux.HandleFunc("GET /{repo}/merge-requests/{$}", s.page(s.mrList)) | ||
M internal/app/testdata/web/blob-binary.html +9 -0
| @@ -234,6 +234,13 @@ | |||
| 234 | 234 | </div> | |
| 235 | 235 | <div> | |
| 236 | 236 | <dt> | |
| 237 | + | <kbd>e</kbd> | |
| 238 | + | <kbd>E</kbd> | |
| 239 | + | </dt> | |
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 241 | + | </div> | |
| 242 | + | <div> | |
| 243 | + | <dt> | |
| 237 | 244 | <kbd>n</kbd> | |
| 238 | 245 | </dt> | |
| 239 | 246 | <dd>merge request: next note in the diff</dd> | |
| @@ -383,6 +390,8 @@ | |||
| 383 | 390 | k-split from:body -> ermite.layout('split'); | |
| 384 | 391 | k-unified from:body -> ermite.layout('unified'); | |
| 385 | 392 | k-collapse from:body -> ermite.collapse(); | |
| 393 | + | k-expand from:body -> ermite.expandFile(); | |
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 386 | 395 | k-permalink from:body -> ermite.permalink(); | |
| 387 | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 388 | 397 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -234,6 +234,13 @@ | |||||
| 234 | </div> | 234 | </div> | ||
| 235 | <div> | 235 | <div> | ||
| 236 | <dt> | 236 | <dt> | ||
| 237 | + | <kbd>e</kbd> | |||
| 238 | + | <kbd>E</kbd> | |||
| 239 | + | </dt> | |||
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 241 | + | </div> | |||
| 242 | + | <div> | |||
| 243 | + | <dt> | |||
| 237 | <kbd>n</kbd> | 244 | <kbd>n</kbd> | ||
| 238 | </dt> | 245 | </dt> | ||
| 239 | <dd>merge request: next note in the diff</dd> | 246 | <dd>merge request: next note in the diff</dd> | ||
| @@ -383,6 +390,8 @@ | |||||
| 383 | k-split from:body -> ermite.layout('split'); | 390 | k-split from:body -> ermite.layout('split'); | ||
| 384 | k-unified from:body -> ermite.layout('unified'); | 391 | k-unified from:body -> ermite.layout('unified'); | ||
| 385 | k-collapse from:body -> ermite.collapse(); | 392 | k-collapse from:body -> ermite.collapse(); | ||
| 393 | + | k-expand from:body -> ermite.expandFile(); | |||
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 386 | k-permalink from:body -> ermite.permalink(); | 395 | k-permalink from:body -> ermite.permalink(); | ||
| 387 | k-merge from:body -> window.ermiteMR?.openMerge(); | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 388 | k-read from:body -> window.ermiteMR?.markRead(); | 397 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-code.html +9 -0
| @@ -304,6 +304,13 @@ | |||
| 304 | 304 | </div> | |
| 305 | 305 | <div> | |
| 306 | 306 | <dt> | |
| 307 | + | <kbd>e</kbd> | |
| 308 | + | <kbd>E</kbd> | |
| 309 | + | </dt> | |
| 310 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 311 | + | </div> | |
| 312 | + | <div> | |
| 313 | + | <dt> | |
| 307 | 314 | <kbd>n</kbd> | |
| 308 | 315 | </dt> | |
| 309 | 316 | <dd>merge request: next note in the diff</dd> | |
| @@ -453,6 +460,8 @@ | |||
| 453 | 460 | k-split from:body -> ermite.layout('split'); | |
| 454 | 461 | k-unified from:body -> ermite.layout('unified'); | |
| 455 | 462 | k-collapse from:body -> ermite.collapse(); | |
| 463 | + | k-expand from:body -> ermite.expandFile(); | |
| 464 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 456 | 465 | k-permalink from:body -> ermite.permalink(); | |
| 457 | 466 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 458 | 467 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -304,6 +304,13 @@ | |||||
| 304 | </div> | 304 | </div> | ||
| 305 | <div> | 305 | <div> | ||
| 306 | <dt> | 306 | <dt> | ||
| 307 | + | <kbd>e</kbd> | |||
| 308 | + | <kbd>E</kbd> | |||
| 309 | + | </dt> | |||
| 310 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 311 | + | </div> | |||
| 312 | + | <div> | |||
| 313 | + | <dt> | |||
| 307 | <kbd>n</kbd> | 314 | <kbd>n</kbd> | ||
| 308 | </dt> | 315 | </dt> | ||
| 309 | <dd>merge request: next note in the diff</dd> | 316 | <dd>merge request: next note in the diff</dd> | ||
| @@ -453,6 +460,8 @@ | |||||
| 453 | k-split from:body -> ermite.layout('split'); | 460 | k-split from:body -> ermite.layout('split'); | ||
| 454 | k-unified from:body -> ermite.layout('unified'); | 461 | k-unified from:body -> ermite.layout('unified'); | ||
| 455 | k-collapse from:body -> ermite.collapse(); | 462 | k-collapse from:body -> ermite.collapse(); | ||
| 463 | + | k-expand from:body -> ermite.expandFile(); | |||
| 464 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 456 | k-permalink from:body -> ermite.permalink(); | 465 | k-permalink from:body -> ermite.permalink(); | ||
| 457 | k-merge from:body -> window.ermiteMR?.openMerge(); | 466 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 458 | k-read from:body -> window.ermiteMR?.markRead(); | 467 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-empty.html +9 -0
| @@ -234,6 +234,13 @@ | |||
| 234 | 234 | </div> | |
| 235 | 235 | <div> | |
| 236 | 236 | <dt> | |
| 237 | + | <kbd>e</kbd> | |
| 238 | + | <kbd>E</kbd> | |
| 239 | + | </dt> | |
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 241 | + | </div> | |
| 242 | + | <div> | |
| 243 | + | <dt> | |
| 237 | 244 | <kbd>n</kbd> | |
| 238 | 245 | </dt> | |
| 239 | 246 | <dd>merge request: next note in the diff</dd> | |
| @@ -383,6 +390,8 @@ | |||
| 383 | 390 | k-split from:body -> ermite.layout('split'); | |
| 384 | 391 | k-unified from:body -> ermite.layout('unified'); | |
| 385 | 392 | k-collapse from:body -> ermite.collapse(); | |
| 393 | + | k-expand from:body -> ermite.expandFile(); | |
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 386 | 395 | k-permalink from:body -> ermite.permalink(); | |
| 387 | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 388 | 397 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -234,6 +234,13 @@ | |||||
| 234 | </div> | 234 | </div> | ||
| 235 | <div> | 235 | <div> | ||
| 236 | <dt> | 236 | <dt> | ||
| 237 | + | <kbd>e</kbd> | |||
| 238 | + | <kbd>E</kbd> | |||
| 239 | + | </dt> | |||
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 241 | + | </div> | |||
| 242 | + | <div> | |||
| 243 | + | <dt> | |||
| 237 | <kbd>n</kbd> | 244 | <kbd>n</kbd> | ||
| 238 | </dt> | 245 | </dt> | ||
| 239 | <dd>merge request: next note in the diff</dd> | 246 | <dd>merge request: next note in the diff</dd> | ||
| @@ -383,6 +390,8 @@ | |||||
| 383 | k-split from:body -> ermite.layout('split'); | 390 | k-split from:body -> ermite.layout('split'); | ||
| 384 | k-unified from:body -> ermite.layout('unified'); | 391 | k-unified from:body -> ermite.layout('unified'); | ||
| 385 | k-collapse from:body -> ermite.collapse(); | 392 | k-collapse from:body -> ermite.collapse(); | ||
| 393 | + | k-expand from:body -> ermite.expandFile(); | |||
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 386 | k-permalink from:body -> ermite.permalink(); | 395 | k-permalink from:body -> ermite.permalink(); | ||
| 387 | k-merge from:body -> window.ermiteMR?.openMerge(); | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 388 | k-read from:body -> window.ermiteMR?.markRead(); | 397 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-image.html +9 -0
| @@ -236,6 +236,13 @@ | |||
| 236 | 236 | </div> | |
| 237 | 237 | <div> | |
| 238 | 238 | <dt> | |
| 239 | + | <kbd>e</kbd> | |
| 240 | + | <kbd>E</kbd> | |
| 241 | + | </dt> | |
| 242 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 243 | + | </div> | |
| 244 | + | <div> | |
| 245 | + | <dt> | |
| 239 | 246 | <kbd>n</kbd> | |
| 240 | 247 | </dt> | |
| 241 | 248 | <dd>merge request: next note in the diff</dd> | |
| @@ -385,6 +392,8 @@ | |||
| 385 | 392 | k-split from:body -> ermite.layout('split'); | |
| 386 | 393 | k-unified from:body -> ermite.layout('unified'); | |
| 387 | 394 | k-collapse from:body -> ermite.collapse(); | |
| 395 | + | k-expand from:body -> ermite.expandFile(); | |
| 396 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 388 | 397 | k-permalink from:body -> ermite.permalink(); | |
| 389 | 398 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 390 | 399 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -236,6 +236,13 @@ | |||||
| 236 | </div> | 236 | </div> | ||
| 237 | <div> | 237 | <div> | ||
| 238 | <dt> | 238 | <dt> | ||
| 239 | + | <kbd>e</kbd> | |||
| 240 | + | <kbd>E</kbd> | |||
| 241 | + | </dt> | |||
| 242 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 243 | + | </div> | |||
| 244 | + | <div> | |||
| 245 | + | <dt> | |||
| 239 | <kbd>n</kbd> | 246 | <kbd>n</kbd> | ||
| 240 | </dt> | 247 | </dt> | ||
| 241 | <dd>merge request: next note in the diff</dd> | 248 | <dd>merge request: next note in the diff</dd> | ||
| @@ -385,6 +392,8 @@ | |||||
| 385 | k-split from:body -> ermite.layout('split'); | 392 | k-split from:body -> ermite.layout('split'); | ||
| 386 | k-unified from:body -> ermite.layout('unified'); | 393 | k-unified from:body -> ermite.layout('unified'); | ||
| 387 | k-collapse from:body -> ermite.collapse(); | 394 | k-collapse from:body -> ermite.collapse(); | ||
| 395 | + | k-expand from:body -> ermite.expandFile(); | |||
| 396 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 388 | k-permalink from:body -> ermite.permalink(); | 397 | k-permalink from:body -> ermite.permalink(); | ||
| 389 | k-merge from:body -> window.ermiteMR?.openMerge(); | 398 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 390 | k-read from:body -> window.ermiteMR?.markRead(); | 399 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-markdown.html +9 -0
| @@ -301,6 +301,13 @@ | |||
| 301 | 301 | </div> | |
| 302 | 302 | <div> | |
| 303 | 303 | <dt> | |
| 304 | + | <kbd>e</kbd> | |
| 305 | + | <kbd>E</kbd> | |
| 306 | + | </dt> | |
| 307 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 308 | + | </div> | |
| 309 | + | <div> | |
| 310 | + | <dt> | |
| 304 | 311 | <kbd>n</kbd> | |
| 305 | 312 | </dt> | |
| 306 | 313 | <dd>merge request: next note in the diff</dd> | |
| @@ -450,6 +457,8 @@ | |||
| 450 | 457 | k-split from:body -> ermite.layout('split'); | |
| 451 | 458 | k-unified from:body -> ermite.layout('unified'); | |
| 452 | 459 | k-collapse from:body -> ermite.collapse(); | |
| 460 | + | k-expand from:body -> ermite.expandFile(); | |
| 461 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 453 | 462 | k-permalink from:body -> ermite.permalink(); | |
| 454 | 463 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 455 | 464 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -301,6 +301,13 @@ | |||||
| 301 | </div> | 301 | </div> | ||
| 302 | <div> | 302 | <div> | ||
| 303 | <dt> | 303 | <dt> | ||
| 304 | + | <kbd>e</kbd> | |||
| 305 | + | <kbd>E</kbd> | |||
| 306 | + | </dt> | |||
| 307 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 308 | + | </div> | |||
| 309 | + | <div> | |||
| 310 | + | <dt> | |||
| 304 | <kbd>n</kbd> | 311 | <kbd>n</kbd> | ||
| 305 | </dt> | 312 | </dt> | ||
| 306 | <dd>merge request: next note in the diff</dd> | 313 | <dd>merge request: next note in the diff</dd> | ||
| @@ -450,6 +457,8 @@ | |||||
| 450 | k-split from:body -> ermite.layout('split'); | 457 | k-split from:body -> ermite.layout('split'); | ||
| 451 | k-unified from:body -> ermite.layout('unified'); | 458 | k-unified from:body -> ermite.layout('unified'); | ||
| 452 | k-collapse from:body -> ermite.collapse(); | 459 | k-collapse from:body -> ermite.collapse(); | ||
| 460 | + | k-expand from:body -> ermite.expandFile(); | |||
| 461 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 453 | k-permalink from:body -> ermite.permalink(); | 462 | k-permalink from:body -> ermite.permalink(); | ||
| 454 | k-merge from:body -> window.ermiteMR?.openMerge(); | 463 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 455 | k-read from:body -> window.ermiteMR?.markRead(); | 464 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-svg.html +9 -0
| @@ -259,6 +259,13 @@ | |||
| 259 | 259 | </div> | |
| 260 | 260 | <div> | |
| 261 | 261 | <dt> | |
| 262 | + | <kbd>e</kbd> | |
| 263 | + | <kbd>E</kbd> | |
| 264 | + | </dt> | |
| 265 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 266 | + | </div> | |
| 267 | + | <div> | |
| 268 | + | <dt> | |
| 262 | 269 | <kbd>n</kbd> | |
| 263 | 270 | </dt> | |
| 264 | 271 | <dd>merge request: next note in the diff</dd> | |
| @@ -408,6 +415,8 @@ | |||
| 408 | 415 | k-split from:body -> ermite.layout('split'); | |
| 409 | 416 | k-unified from:body -> ermite.layout('unified'); | |
| 410 | 417 | k-collapse from:body -> ermite.collapse(); | |
| 418 | + | k-expand from:body -> ermite.expandFile(); | |
| 419 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 411 | 420 | k-permalink from:body -> ermite.permalink(); | |
| 412 | 421 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 413 | 422 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -259,6 +259,13 @@ | |||||
| 259 | </div> | 259 | </div> | ||
| 260 | <div> | 260 | <div> | ||
| 261 | <dt> | 261 | <dt> | ||
| 262 | + | <kbd>e</kbd> | |||
| 263 | + | <kbd>E</kbd> | |||
| 264 | + | </dt> | |||
| 265 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 266 | + | </div> | |||
| 267 | + | <div> | |||
| 268 | + | <dt> | |||
| 262 | <kbd>n</kbd> | 269 | <kbd>n</kbd> | ||
| 263 | </dt> | 270 | </dt> | ||
| 264 | <dd>merge request: next note in the diff</dd> | 271 | <dd>merge request: next note in the diff</dd> | ||
| @@ -408,6 +415,8 @@ | |||||
| 408 | k-split from:body -> ermite.layout('split'); | 415 | k-split from:body -> ermite.layout('split'); | ||
| 409 | k-unified from:body -> ermite.layout('unified'); | 416 | k-unified from:body -> ermite.layout('unified'); | ||
| 410 | k-collapse from:body -> ermite.collapse(); | 417 | k-collapse from:body -> ermite.collapse(); | ||
| 418 | + | k-expand from:body -> ermite.expandFile(); | |||
| 419 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 411 | k-permalink from:body -> ermite.permalink(); | 420 | k-permalink from:body -> ermite.permalink(); | ||
| 412 | k-merge from:body -> window.ermiteMR?.openMerge(); | 421 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 413 | k-read from:body -> window.ermiteMR?.markRead(); | 422 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-text.html +9 -0
| @@ -254,6 +254,13 @@ | |||
| 254 | 254 | </div> | |
| 255 | 255 | <div> | |
| 256 | 256 | <dt> | |
| 257 | + | <kbd>e</kbd> | |
| 258 | + | <kbd>E</kbd> | |
| 259 | + | </dt> | |
| 260 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 261 | + | </div> | |
| 262 | + | <div> | |
| 263 | + | <dt> | |
| 257 | 264 | <kbd>n</kbd> | |
| 258 | 265 | </dt> | |
| 259 | 266 | <dd>merge request: next note in the diff</dd> | |
| @@ -403,6 +410,8 @@ | |||
| 403 | 410 | k-split from:body -> ermite.layout('split'); | |
| 404 | 411 | k-unified from:body -> ermite.layout('unified'); | |
| 405 | 412 | k-collapse from:body -> ermite.collapse(); | |
| 413 | + | k-expand from:body -> ermite.expandFile(); | |
| 414 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 406 | 415 | k-permalink from:body -> ermite.permalink(); | |
| 407 | 416 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 408 | 417 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -254,6 +254,13 @@ | |||||
| 254 | </div> | 254 | </div> | ||
| 255 | <div> | 255 | <div> | ||
| 256 | <dt> | 256 | <dt> | ||
| 257 | + | <kbd>e</kbd> | |||
| 258 | + | <kbd>E</kbd> | |||
| 259 | + | </dt> | |||
| 260 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 261 | + | </div> | |||
| 262 | + | <div> | |||
| 263 | + | <dt> | |||
| 257 | <kbd>n</kbd> | 264 | <kbd>n</kbd> | ||
| 258 | </dt> | 265 | </dt> | ||
| 259 | <dd>merge request: next note in the diff</dd> | 266 | <dd>merge request: next note in the diff</dd> | ||
| @@ -403,6 +410,8 @@ | |||||
| 403 | k-split from:body -> ermite.layout('split'); | 410 | k-split from:body -> ermite.layout('split'); | ||
| 404 | k-unified from:body -> ermite.layout('unified'); | 411 | k-unified from:body -> ermite.layout('unified'); | ||
| 405 | k-collapse from:body -> ermite.collapse(); | 412 | k-collapse from:body -> ermite.collapse(); | ||
| 413 | + | k-expand from:body -> ermite.expandFile(); | |||
| 414 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 406 | k-permalink from:body -> ermite.permalink(); | 415 | k-permalink from:body -> ermite.permalink(); | ||
| 407 | k-merge from:body -> window.ermiteMR?.openMerge(); | 416 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 408 | k-read from:body -> window.ermiteMR?.markRead(); | 417 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-too-large.html +9 -0
| @@ -234,6 +234,13 @@ | |||
| 234 | 234 | </div> | |
| 235 | 235 | <div> | |
| 236 | 236 | <dt> | |
| 237 | + | <kbd>e</kbd> | |
| 238 | + | <kbd>E</kbd> | |
| 239 | + | </dt> | |
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 241 | + | </div> | |
| 242 | + | <div> | |
| 243 | + | <dt> | |
| 237 | 244 | <kbd>n</kbd> | |
| 238 | 245 | </dt> | |
| 239 | 246 | <dd>merge request: next note in the diff</dd> | |
| @@ -383,6 +390,8 @@ | |||
| 383 | 390 | k-split from:body -> ermite.layout('split'); | |
| 384 | 391 | k-unified from:body -> ermite.layout('unified'); | |
| 385 | 392 | k-collapse from:body -> ermite.collapse(); | |
| 393 | + | k-expand from:body -> ermite.expandFile(); | |
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 386 | 395 | k-permalink from:body -> ermite.permalink(); | |
| 387 | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 388 | 397 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -234,6 +234,13 @@ | |||||
| 234 | </div> | 234 | </div> | ||
| 235 | <div> | 235 | <div> | ||
| 236 | <dt> | 236 | <dt> | ||
| 237 | + | <kbd>e</kbd> | |||
| 238 | + | <kbd>E</kbd> | |||
| 239 | + | </dt> | |||
| 240 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 241 | + | </div> | |||
| 242 | + | <div> | |||
| 243 | + | <dt> | |||
| 237 | <kbd>n</kbd> | 244 | <kbd>n</kbd> | ||
| 238 | </dt> | 245 | </dt> | ||
| 239 | <dd>merge request: next note in the diff</dd> | 246 | <dd>merge request: next note in the diff</dd> | ||
| @@ -383,6 +390,8 @@ | |||||
| 383 | k-split from:body -> ermite.layout('split'); | 390 | k-split from:body -> ermite.layout('split'); | ||
| 384 | k-unified from:body -> ermite.layout('unified'); | 391 | k-unified from:body -> ermite.layout('unified'); | ||
| 385 | k-collapse from:body -> ermite.collapse(); | 392 | k-collapse from:body -> ermite.collapse(); | ||
| 393 | + | k-expand from:body -> ermite.expandFile(); | |||
| 394 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 386 | k-permalink from:body -> ermite.permalink(); | 395 | k-permalink from:body -> ermite.permalink(); | ||
| 387 | k-merge from:body -> window.ermiteMR?.openMerge(); | 396 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 388 | k-read from:body -> window.ermiteMR?.markRead(); | 397 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/blob-trojan.html +9 -0
| @@ -301,6 +301,13 @@ | |||
| 301 | 301 | </div> | |
| 302 | 302 | <div> | |
| 303 | 303 | <dt> | |
| 304 | + | <kbd>e</kbd> | |
| 305 | + | <kbd>E</kbd> | |
| 306 | + | </dt> | |
| 307 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 308 | + | </div> | |
| 309 | + | <div> | |
| 310 | + | <dt> | |
| 304 | 311 | <kbd>n</kbd> | |
| 305 | 312 | </dt> | |
| 306 | 313 | <dd>merge request: next note in the diff</dd> | |
| @@ -450,6 +457,8 @@ | |||
| 450 | 457 | k-split from:body -> ermite.layout('split'); | |
| 451 | 458 | k-unified from:body -> ermite.layout('unified'); | |
| 452 | 459 | k-collapse from:body -> ermite.collapse(); | |
| 460 | + | k-expand from:body -> ermite.expandFile(); | |
| 461 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 453 | 462 | k-permalink from:body -> ermite.permalink(); | |
| 454 | 463 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 455 | 464 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -301,6 +301,13 @@ | |||||
| 301 | </div> | 301 | </div> | ||
| 302 | <div> | 302 | <div> | ||
| 303 | <dt> | 303 | <dt> | ||
| 304 | + | <kbd>e</kbd> | |||
| 305 | + | <kbd>E</kbd> | |||
| 306 | + | </dt> | |||
| 307 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 308 | + | </div> | |||
| 309 | + | <div> | |||
| 310 | + | <dt> | |||
| 304 | <kbd>n</kbd> | 311 | <kbd>n</kbd> | ||
| 305 | </dt> | 312 | </dt> | ||
| 306 | <dd>merge request: next note in the diff</dd> | 313 | <dd>merge request: next note in the diff</dd> | ||
| @@ -450,6 +457,8 @@ | |||||
| 450 | k-split from:body -> ermite.layout('split'); | 457 | k-split from:body -> ermite.layout('split'); | ||
| 451 | k-unified from:body -> ermite.layout('unified'); | 458 | k-unified from:body -> ermite.layout('unified'); | ||
| 452 | k-collapse from:body -> ermite.collapse(); | 459 | k-collapse from:body -> ermite.collapse(); | ||
| 460 | + | k-expand from:body -> ermite.expandFile(); | |||
| 461 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 453 | k-permalink from:body -> ermite.permalink(); | 462 | k-permalink from:body -> ermite.permalink(); | ||
| 454 | k-merge from:body -> window.ermiteMR?.openMerge(); | 463 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 455 | k-read from:body -> window.ermiteMR?.markRead(); | 464 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-big.html +9 -0
| @@ -272,6 +272,13 @@ | |||
| 272 | 272 | </div> | |
| 273 | 273 | <div> | |
| 274 | 274 | <dt> | |
| 275 | + | <kbd>e</kbd> | |
| 276 | + | <kbd>E</kbd> | |
| 277 | + | </dt> | |
| 278 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 279 | + | </div> | |
| 280 | + | <div> | |
| 281 | + | <dt> | |
| 275 | 282 | <kbd>n</kbd> | |
| 276 | 283 | </dt> | |
| 277 | 284 | <dd>merge request: next note in the diff</dd> | |
| @@ -421,6 +428,8 @@ | |||
| 421 | 428 | k-split from:body -> ermite.layout('split'); | |
| 422 | 429 | k-unified from:body -> ermite.layout('unified'); | |
| 423 | 430 | k-collapse from:body -> ermite.collapse(); | |
| 431 | + | k-expand from:body -> ermite.expandFile(); | |
| 432 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 424 | 433 | k-permalink from:body -> ermite.permalink(); | |
| 425 | 434 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 426 | 435 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -272,6 +272,13 @@ | |||||
| 272 | </div> | 272 | </div> | ||
| 273 | <div> | 273 | <div> | ||
| 274 | <dt> | 274 | <dt> | ||
| 275 | + | <kbd>e</kbd> | |||
| 276 | + | <kbd>E</kbd> | |||
| 277 | + | </dt> | |||
| 278 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 279 | + | </div> | |||
| 280 | + | <div> | |||
| 281 | + | <dt> | |||
| 275 | <kbd>n</kbd> | 282 | <kbd>n</kbd> | ||
| 276 | </dt> | 283 | </dt> | ||
| 277 | <dd>merge request: next note in the diff</dd> | 284 | <dd>merge request: next note in the diff</dd> | ||
| @@ -421,6 +428,8 @@ | |||||
| 421 | k-split from:body -> ermite.layout('split'); | 428 | k-split from:body -> ermite.layout('split'); | ||
| 422 | k-unified from:body -> ermite.layout('unified'); | 429 | k-unified from:body -> ermite.layout('unified'); | ||
| 423 | k-collapse from:body -> ermite.collapse(); | 430 | k-collapse from:body -> ermite.collapse(); | ||
| 431 | + | k-expand from:body -> ermite.expandFile(); | |||
| 432 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 424 | k-permalink from:body -> ermite.permalink(); | 433 | k-permalink from:body -> ermite.permalink(); | ||
| 425 | k-merge from:body -> window.ermiteMR?.openMerge(); | 434 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 426 | k-read from:body -> window.ermiteMR?.markRead(); | 435 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-binary.html +9 -0
| @@ -264,6 +264,13 @@ | |||
| 264 | 264 | </div> | |
| 265 | 265 | <div> | |
| 266 | 266 | <dt> | |
| 267 | + | <kbd>e</kbd> | |
| 268 | + | <kbd>E</kbd> | |
| 269 | + | </dt> | |
| 270 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 271 | + | </div> | |
| 272 | + | <div> | |
| 273 | + | <dt> | |
| 267 | 274 | <kbd>n</kbd> | |
| 268 | 275 | </dt> | |
| 269 | 276 | <dd>merge request: next note in the diff</dd> | |
| @@ -413,6 +420,8 @@ | |||
| 413 | 420 | k-split from:body -> ermite.layout('split'); | |
| 414 | 421 | k-unified from:body -> ermite.layout('unified'); | |
| 415 | 422 | k-collapse from:body -> ermite.collapse(); | |
| 423 | + | k-expand from:body -> ermite.expandFile(); | |
| 424 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 416 | 425 | k-permalink from:body -> ermite.permalink(); | |
| 417 | 426 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 418 | 427 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -264,6 +264,13 @@ | |||||
| 264 | </div> | 264 | </div> | ||
| 265 | <div> | 265 | <div> | ||
| 266 | <dt> | 266 | <dt> | ||
| 267 | + | <kbd>e</kbd> | |||
| 268 | + | <kbd>E</kbd> | |||
| 269 | + | </dt> | |||
| 270 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 271 | + | </div> | |||
| 272 | + | <div> | |||
| 273 | + | <dt> | |||
| 267 | <kbd>n</kbd> | 274 | <kbd>n</kbd> | ||
| 268 | </dt> | 275 | </dt> | ||
| 269 | <dd>merge request: next note in the diff</dd> | 276 | <dd>merge request: next note in the diff</dd> | ||
| @@ -413,6 +420,8 @@ | |||||
| 413 | k-split from:body -> ermite.layout('split'); | 420 | k-split from:body -> ermite.layout('split'); | ||
| 414 | k-unified from:body -> ermite.layout('unified'); | 421 | k-unified from:body -> ermite.layout('unified'); | ||
| 415 | k-collapse from:body -> ermite.collapse(); | 422 | k-collapse from:body -> ermite.collapse(); | ||
| 423 | + | k-expand from:body -> ermite.expandFile(); | |||
| 424 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 416 | k-permalink from:body -> ermite.permalink(); | 425 | k-permalink from:body -> ermite.permalink(); | ||
| 417 | k-merge from:body -> window.ermiteMR?.openMerge(); | 426 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 418 | k-read from:body -> window.ermiteMR?.markRead(); | 427 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-merge.html +9 -0
| @@ -306,6 +306,13 @@ | |||
| 306 | 306 | </div> | |
| 307 | 307 | <div> | |
| 308 | 308 | <dt> | |
| 309 | + | <kbd>e</kbd> | |
| 310 | + | <kbd>E</kbd> | |
| 311 | + | </dt> | |
| 312 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 313 | + | </div> | |
| 314 | + | <div> | |
| 315 | + | <dt> | |
| 309 | 316 | <kbd>n</kbd> | |
| 310 | 317 | </dt> | |
| 311 | 318 | <dd>merge request: next note in the diff</dd> | |
| @@ -455,6 +462,8 @@ | |||
| 455 | 462 | k-split from:body -> ermite.layout('split'); | |
| 456 | 463 | k-unified from:body -> ermite.layout('unified'); | |
| 457 | 464 | k-collapse from:body -> ermite.collapse(); | |
| 465 | + | k-expand from:body -> ermite.expandFile(); | |
| 466 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 458 | 467 | k-permalink from:body -> ermite.permalink(); | |
| 459 | 468 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 460 | 469 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -306,6 +306,13 @@ | |||||
| 306 | </div> | 306 | </div> | ||
| 307 | <div> | 307 | <div> | ||
| 308 | <dt> | 308 | <dt> | ||
| 309 | + | <kbd>e</kbd> | |||
| 310 | + | <kbd>E</kbd> | |||
| 311 | + | </dt> | |||
| 312 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 313 | + | </div> | |||
| 314 | + | <div> | |||
| 315 | + | <dt> | |||
| 309 | <kbd>n</kbd> | 316 | <kbd>n</kbd> | ||
| 310 | </dt> | 317 | </dt> | ||
| 311 | <dd>merge request: next note in the diff</dd> | 318 | <dd>merge request: next note in the diff</dd> | ||
| @@ -455,6 +462,8 @@ | |||||
| 455 | k-split from:body -> ermite.layout('split'); | 462 | k-split from:body -> ermite.layout('split'); | ||
| 456 | k-unified from:body -> ermite.layout('unified'); | 463 | k-unified from:body -> ermite.layout('unified'); | ||
| 457 | k-collapse from:body -> ermite.collapse(); | 464 | k-collapse from:body -> ermite.collapse(); | ||
| 465 | + | k-expand from:body -> ermite.expandFile(); | |||
| 466 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 458 | k-permalink from:body -> ermite.permalink(); | 467 | k-permalink from:body -> ermite.permalink(); | ||
| 459 | k-merge from:body -> window.ermiteMR?.openMerge(); | 468 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 460 | k-read from:body -> window.ermiteMR?.markRead(); | 469 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-modify-split.html +9 -0
| @@ -429,6 +429,13 @@ And <b>html</b> & "quotes".</pre> | |||
| 429 | 429 | </div> | |
| 430 | 430 | <div> | |
| 431 | 431 | <dt> | |
| 432 | + | <kbd>e</kbd> | |
| 433 | + | <kbd>E</kbd> | |
| 434 | + | </dt> | |
| 435 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 436 | + | </div> | |
| 437 | + | <div> | |
| 438 | + | <dt> | |
| 432 | 439 | <kbd>n</kbd> | |
| 433 | 440 | </dt> | |
| 434 | 441 | <dd>merge request: next note in the diff</dd> | |
| @@ -578,6 +585,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 578 | 585 | k-split from:body -> ermite.layout('split'); | |
| 579 | 586 | k-unified from:body -> ermite.layout('unified'); | |
| 580 | 587 | k-collapse from:body -> ermite.collapse(); | |
| 588 | + | k-expand from:body -> ermite.expandFile(); | |
| 589 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 581 | 590 | k-permalink from:body -> ermite.permalink(); | |
| 582 | 591 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 583 | 592 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -429,6 +429,13 @@ And <b>html</b> & "quotes".</pre> | |||||
| 429 | </div> | 429 | </div> | ||
| 430 | <div> | 430 | <div> | ||
| 431 | <dt> | 431 | <dt> | ||
| 432 | + | <kbd>e</kbd> | |||
| 433 | + | <kbd>E</kbd> | |||
| 434 | + | </dt> | |||
| 435 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 436 | + | </div> | |||
| 437 | + | <div> | |||
| 438 | + | <dt> | |||
| 432 | <kbd>n</kbd> | 439 | <kbd>n</kbd> | ||
| 433 | </dt> | 440 | </dt> | ||
| 434 | <dd>merge request: next note in the diff</dd> | 441 | <dd>merge request: next note in the diff</dd> | ||
| @@ -578,6 +585,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 578 | k-split from:body -> ermite.layout('split'); | 585 | k-split from:body -> ermite.layout('split'); | ||
| 579 | k-unified from:body -> ermite.layout('unified'); | 586 | k-unified from:body -> ermite.layout('unified'); | ||
| 580 | k-collapse from:body -> ermite.collapse(); | 587 | k-collapse from:body -> ermite.collapse(); | ||
| 588 | + | k-expand from:body -> ermite.expandFile(); | |||
| 589 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 581 | k-permalink from:body -> ermite.permalink(); | 590 | k-permalink from:body -> ermite.permalink(); | ||
| 582 | k-merge from:body -> window.ermiteMR?.openMerge(); | 591 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 583 | k-read from:body -> window.ermiteMR?.markRead(); | 592 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-modify.html +9 -0
| @@ -429,6 +429,13 @@ And <b>html</b> & "quotes".</pre> | |||
| 429 | 429 | </div> | |
| 430 | 430 | <div> | |
| 431 | 431 | <dt> | |
| 432 | + | <kbd>e</kbd> | |
| 433 | + | <kbd>E</kbd> | |
| 434 | + | </dt> | |
| 435 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 436 | + | </div> | |
| 437 | + | <div> | |
| 438 | + | <dt> | |
| 432 | 439 | <kbd>n</kbd> | |
| 433 | 440 | </dt> | |
| 434 | 441 | <dd>merge request: next note in the diff</dd> | |
| @@ -578,6 +585,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 578 | 585 | k-split from:body -> ermite.layout('split'); | |
| 579 | 586 | k-unified from:body -> ermite.layout('unified'); | |
| 580 | 587 | k-collapse from:body -> ermite.collapse(); | |
| 588 | + | k-expand from:body -> ermite.expandFile(); | |
| 589 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 581 | 590 | k-permalink from:body -> ermite.permalink(); | |
| 582 | 591 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 583 | 592 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -429,6 +429,13 @@ And <b>html</b> & "quotes".</pre> | |||||
| 429 | </div> | 429 | </div> | ||
| 430 | <div> | 430 | <div> | ||
| 431 | <dt> | 431 | <dt> | ||
| 432 | + | <kbd>e</kbd> | |||
| 433 | + | <kbd>E</kbd> | |||
| 434 | + | </dt> | |||
| 435 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 436 | + | </div> | |||
| 437 | + | <div> | |||
| 438 | + | <dt> | |||
| 432 | <kbd>n</kbd> | 439 | <kbd>n</kbd> | ||
| 433 | </dt> | 440 | </dt> | ||
| 434 | <dd>merge request: next note in the diff</dd> | 441 | <dd>merge request: next note in the diff</dd> | ||
| @@ -578,6 +585,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 578 | k-split from:body -> ermite.layout('split'); | 585 | k-split from:body -> ermite.layout('split'); | ||
| 579 | k-unified from:body -> ermite.layout('unified'); | 586 | k-unified from:body -> ermite.layout('unified'); | ||
| 580 | k-collapse from:body -> ermite.collapse(); | 587 | k-collapse from:body -> ermite.collapse(); | ||
| 588 | + | k-expand from:body -> ermite.expandFile(); | |||
| 589 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 581 | k-permalink from:body -> ermite.permalink(); | 590 | k-permalink from:body -> ermite.permalink(); | ||
| 582 | k-merge from:body -> window.ermiteMR?.openMerge(); | 591 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 583 | k-read from:body -> window.ermiteMR?.markRead(); | 592 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-rename.html +9 -0
| @@ -333,6 +333,13 @@ | |||
| 333 | 333 | </div> | |
| 334 | 334 | <div> | |
| 335 | 335 | <dt> | |
| 336 | + | <kbd>e</kbd> | |
| 337 | + | <kbd>E</kbd> | |
| 338 | + | </dt> | |
| 339 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 340 | + | </div> | |
| 341 | + | <div> | |
| 342 | + | <dt> | |
| 336 | 343 | <kbd>n</kbd> | |
| 337 | 344 | </dt> | |
| 338 | 345 | <dd>merge request: next note in the diff</dd> | |
| @@ -482,6 +489,8 @@ | |||
| 482 | 489 | k-split from:body -> ermite.layout('split'); | |
| 483 | 490 | k-unified from:body -> ermite.layout('unified'); | |
| 484 | 491 | k-collapse from:body -> ermite.collapse(); | |
| 492 | + | k-expand from:body -> ermite.expandFile(); | |
| 493 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 485 | 494 | k-permalink from:body -> ermite.permalink(); | |
| 486 | 495 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 487 | 496 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -333,6 +333,13 @@ | |||||
| 333 | </div> | 333 | </div> | ||
| 334 | <div> | 334 | <div> | ||
| 335 | <dt> | 335 | <dt> | ||
| 336 | + | <kbd>e</kbd> | |||
| 337 | + | <kbd>E</kbd> | |||
| 338 | + | </dt> | |||
| 339 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 340 | + | </div> | |||
| 341 | + | <div> | |||
| 342 | + | <dt> | |||
| 336 | <kbd>n</kbd> | 343 | <kbd>n</kbd> | ||
| 337 | </dt> | 344 | </dt> | ||
| 338 | <dd>merge request: next note in the diff</dd> | 345 | <dd>merge request: next note in the diff</dd> | ||
| @@ -482,6 +489,8 @@ | |||||
| 482 | k-split from:body -> ermite.layout('split'); | 489 | k-split from:body -> ermite.layout('split'); | ||
| 483 | k-unified from:body -> ermite.layout('unified'); | 490 | k-unified from:body -> ermite.layout('unified'); | ||
| 484 | k-collapse from:body -> ermite.collapse(); | 491 | k-collapse from:body -> ermite.collapse(); | ||
| 492 | + | k-expand from:body -> ermite.expandFile(); | |||
| 493 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 485 | k-permalink from:body -> ermite.permalink(); | 494 | k-permalink from:body -> ermite.permalink(); | ||
| 486 | k-merge from:body -> window.ermiteMR?.openMerge(); | 495 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 487 | k-read from:body -> window.ermiteMR?.markRead(); | 496 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-root.html +9 -0
| @@ -652,6 +652,13 @@ | |||
| 652 | 652 | </div> | |
| 653 | 653 | <div> | |
| 654 | 654 | <dt> | |
| 655 | + | <kbd>e</kbd> | |
| 656 | + | <kbd>E</kbd> | |
| 657 | + | </dt> | |
| 658 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 659 | + | </div> | |
| 660 | + | <div> | |
| 661 | + | <dt> | |
| 655 | 662 | <kbd>n</kbd> | |
| 656 | 663 | </dt> | |
| 657 | 664 | <dd>merge request: next note in the diff</dd> | |
| @@ -801,6 +808,8 @@ | |||
| 801 | 808 | k-split from:body -> ermite.layout('split'); | |
| 802 | 809 | k-unified from:body -> ermite.layout('unified'); | |
| 803 | 810 | k-collapse from:body -> ermite.collapse(); | |
| 811 | + | k-expand from:body -> ermite.expandFile(); | |
| 812 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 804 | 813 | k-permalink from:body -> ermite.permalink(); | |
| 805 | 814 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 806 | 815 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -652,6 +652,13 @@ | |||||
| 652 | </div> | 652 | </div> | ||
| 653 | <div> | 653 | <div> | ||
| 654 | <dt> | 654 | <dt> | ||
| 655 | + | <kbd>e</kbd> | |||
| 656 | + | <kbd>E</kbd> | |||
| 657 | + | </dt> | |||
| 658 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 659 | + | </div> | |||
| 660 | + | <div> | |||
| 661 | + | <dt> | |||
| 655 | <kbd>n</kbd> | 662 | <kbd>n</kbd> | ||
| 656 | </dt> | 663 | </dt> | ||
| 657 | <dd>merge request: next note in the diff</dd> | 664 | <dd>merge request: next note in the diff</dd> | ||
| @@ -801,6 +808,8 @@ | |||||
| 801 | k-split from:body -> ermite.layout('split'); | 808 | k-split from:body -> ermite.layout('split'); | ||
| 802 | k-unified from:body -> ermite.layout('unified'); | 809 | k-unified from:body -> ermite.layout('unified'); | ||
| 803 | k-collapse from:body -> ermite.collapse(); | 810 | k-collapse from:body -> ermite.collapse(); | ||
| 811 | + | k-expand from:body -> ermite.expandFile(); | |||
| 812 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 804 | k-permalink from:body -> ermite.permalink(); | 813 | k-permalink from:body -> ermite.permalink(); | ||
| 805 | k-merge from:body -> window.ermiteMR?.openMerge(); | 814 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 806 | k-read from:body -> window.ermiteMR?.markRead(); | 815 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commit-trojan.html +9 -0
| @@ -441,6 +441,13 @@ | |||
| 441 | 441 | </div> | |
| 442 | 442 | <div> | |
| 443 | 443 | <dt> | |
| 444 | + | <kbd>e</kbd> | |
| 445 | + | <kbd>E</kbd> | |
| 446 | + | </dt> | |
| 447 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 448 | + | </div> | |
| 449 | + | <div> | |
| 450 | + | <dt> | |
| 444 | 451 | <kbd>n</kbd> | |
| 445 | 452 | </dt> | |
| 446 | 453 | <dd>merge request: next note in the diff</dd> | |
| @@ -590,6 +597,8 @@ | |||
| 590 | 597 | k-split from:body -> ermite.layout('split'); | |
| 591 | 598 | k-unified from:body -> ermite.layout('unified'); | |
| 592 | 599 | k-collapse from:body -> ermite.collapse(); | |
| 600 | + | k-expand from:body -> ermite.expandFile(); | |
| 601 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 593 | 602 | k-permalink from:body -> ermite.permalink(); | |
| 594 | 603 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 595 | 604 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -441,6 +441,13 @@ | |||||
| 441 | </div> | 441 | </div> | ||
| 442 | <div> | 442 | <div> | ||
| 443 | <dt> | 443 | <dt> | ||
| 444 | + | <kbd>e</kbd> | |||
| 445 | + | <kbd>E</kbd> | |||
| 446 | + | </dt> | |||
| 447 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 448 | + | </div> | |||
| 449 | + | <div> | |||
| 450 | + | <dt> | |||
| 444 | <kbd>n</kbd> | 451 | <kbd>n</kbd> | ||
| 445 | </dt> | 452 | </dt> | ||
| 446 | <dd>merge request: next note in the diff</dd> | 453 | <dd>merge request: next note in the diff</dd> | ||
| @@ -590,6 +597,8 @@ | |||||
| 590 | k-split from:body -> ermite.layout('split'); | 597 | k-split from:body -> ermite.layout('split'); | ||
| 591 | k-unified from:body -> ermite.layout('unified'); | 598 | k-unified from:body -> ermite.layout('unified'); | ||
| 592 | k-collapse from:body -> ermite.collapse(); | 599 | k-collapse from:body -> ermite.collapse(); | ||
| 600 | + | k-expand from:body -> ermite.expandFile(); | |||
| 601 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 593 | k-permalink from:body -> ermite.permalink(); | 602 | k-permalink from:body -> ermite.permalink(); | ||
| 594 | k-merge from:body -> window.ermiteMR?.openMerge(); | 603 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 595 | k-read from:body -> window.ermiteMR?.markRead(); | 604 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commits-page2.html +9 -0
| @@ -265,6 +265,13 @@ | |||
| 265 | 265 | </div> | |
| 266 | 266 | <div> | |
| 267 | 267 | <dt> | |
| 268 | + | <kbd>e</kbd> | |
| 269 | + | <kbd>E</kbd> | |
| 270 | + | </dt> | |
| 271 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 272 | + | </div> | |
| 273 | + | <div> | |
| 274 | + | <dt> | |
| 268 | 275 | <kbd>n</kbd> | |
| 269 | 276 | </dt> | |
| 270 | 277 | <dd>merge request: next note in the diff</dd> | |
| @@ -400,6 +407,8 @@ | |||
| 400 | 407 | k-split from:body -> ermite.layout('split'); | |
| 401 | 408 | k-unified from:body -> ermite.layout('unified'); | |
| 402 | 409 | k-collapse from:body -> ermite.collapse(); | |
| 410 | + | k-expand from:body -> ermite.expandFile(); | |
| 411 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 403 | 412 | k-permalink from:body -> ermite.permalink(); | |
| 404 | 413 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 405 | 414 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -265,6 +265,13 @@ | |||||
| 265 | </div> | 265 | </div> | ||
| 266 | <div> | 266 | <div> | ||
| 267 | <dt> | 267 | <dt> | ||
| 268 | + | <kbd>e</kbd> | |||
| 269 | + | <kbd>E</kbd> | |||
| 270 | + | </dt> | |||
| 271 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 272 | + | </div> | |||
| 273 | + | <div> | |||
| 274 | + | <dt> | |||
| 268 | <kbd>n</kbd> | 275 | <kbd>n</kbd> | ||
| 269 | </dt> | 276 | </dt> | ||
| 270 | <dd>merge request: next note in the diff</dd> | 277 | <dd>merge request: next note in the diff</dd> | ||
| @@ -400,6 +407,8 @@ | |||||
| 400 | k-split from:body -> ermite.layout('split'); | 407 | k-split from:body -> ermite.layout('split'); | ||
| 401 | k-unified from:body -> ermite.layout('unified'); | 408 | k-unified from:body -> ermite.layout('unified'); | ||
| 402 | k-collapse from:body -> ermite.collapse(); | 409 | k-collapse from:body -> ermite.collapse(); | ||
| 410 | + | k-expand from:body -> ermite.expandFile(); | |||
| 411 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 403 | k-permalink from:body -> ermite.permalink(); | 412 | k-permalink from:body -> ermite.permalink(); | ||
| 404 | k-merge from:body -> window.ermiteMR?.openMerge(); | 413 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 405 | k-read from:body -> window.ermiteMR?.markRead(); | 414 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commits-path.html +9 -0
| @@ -280,6 +280,13 @@ | |||
| 280 | 280 | </div> | |
| 281 | 281 | <div> | |
| 282 | 282 | <dt> | |
| 283 | + | <kbd>e</kbd> | |
| 284 | + | <kbd>E</kbd> | |
| 285 | + | </dt> | |
| 286 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 287 | + | </div> | |
| 288 | + | <div> | |
| 289 | + | <dt> | |
| 283 | 290 | <kbd>n</kbd> | |
| 284 | 291 | </dt> | |
| 285 | 292 | <dd>merge request: next note in the diff</dd> | |
| @@ -415,6 +422,8 @@ | |||
| 415 | 422 | k-split from:body -> ermite.layout('split'); | |
| 416 | 423 | k-unified from:body -> ermite.layout('unified'); | |
| 417 | 424 | k-collapse from:body -> ermite.collapse(); | |
| 425 | + | k-expand from:body -> ermite.expandFile(); | |
| 426 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 418 | 427 | k-permalink from:body -> ermite.permalink(); | |
| 419 | 428 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 420 | 429 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -280,6 +280,13 @@ | |||||
| 280 | </div> | 280 | </div> | ||
| 281 | <div> | 281 | <div> | ||
| 282 | <dt> | 282 | <dt> | ||
| 283 | + | <kbd>e</kbd> | |||
| 284 | + | <kbd>E</kbd> | |||
| 285 | + | </dt> | |||
| 286 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 287 | + | </div> | |||
| 288 | + | <div> | |||
| 289 | + | <dt> | |||
| 283 | <kbd>n</kbd> | 290 | <kbd>n</kbd> | ||
| 284 | </dt> | 291 | </dt> | ||
| 285 | <dd>merge request: next note in the diff</dd> | 292 | <dd>merge request: next note in the diff</dd> | ||
| @@ -415,6 +422,8 @@ | |||||
| 415 | k-split from:body -> ermite.layout('split'); | 422 | k-split from:body -> ermite.layout('split'); | ||
| 416 | k-unified from:body -> ermite.layout('unified'); | 423 | k-unified from:body -> ermite.layout('unified'); | ||
| 417 | k-collapse from:body -> ermite.collapse(); | 424 | k-collapse from:body -> ermite.collapse(); | ||
| 425 | + | k-expand from:body -> ermite.expandFile(); | |||
| 426 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 418 | k-permalink from:body -> ermite.permalink(); | 427 | k-permalink from:body -> ermite.permalink(); | ||
| 419 | k-merge from:body -> window.ermiteMR?.openMerge(); | 428 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 420 | k-read from:body -> window.ermiteMR?.markRead(); | 429 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commits-root.html +9 -0
| @@ -1165,6 +1165,13 @@ | |||
| 1165 | 1165 | </div> | |
| 1166 | 1166 | <div> | |
| 1167 | 1167 | <dt> | |
| 1168 | + | <kbd>e</kbd> | |
| 1169 | + | <kbd>E</kbd> | |
| 1170 | + | </dt> | |
| 1171 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 1172 | + | </div> | |
| 1173 | + | <div> | |
| 1174 | + | <dt> | |
| 1168 | 1175 | <kbd>n</kbd> | |
| 1169 | 1176 | </dt> | |
| 1170 | 1177 | <dd>merge request: next note in the diff</dd> | |
| @@ -1300,6 +1307,8 @@ | |||
| 1300 | 1307 | k-split from:body -> ermite.layout('split'); | |
| 1301 | 1308 | k-unified from:body -> ermite.layout('unified'); | |
| 1302 | 1309 | k-collapse from:body -> ermite.collapse(); | |
| 1310 | + | k-expand from:body -> ermite.expandFile(); | |
| 1311 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 1303 | 1312 | k-permalink from:body -> ermite.permalink(); | |
| 1304 | 1313 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 1305 | 1314 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -1165,6 +1165,13 @@ | |||||
| 1165 | </div> | 1165 | </div> | ||
| 1166 | <div> | 1166 | <div> | ||
| 1167 | <dt> | 1167 | <dt> | ||
| 1168 | + | <kbd>e</kbd> | |||
| 1169 | + | <kbd>E</kbd> | |||
| 1170 | + | </dt> | |||
| 1171 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 1172 | + | </div> | |||
| 1173 | + | <div> | |||
| 1174 | + | <dt> | |||
| 1168 | <kbd>n</kbd> | 1175 | <kbd>n</kbd> | ||
| 1169 | </dt> | 1176 | </dt> | ||
| 1170 | <dd>merge request: next note in the diff</dd> | 1177 | <dd>merge request: next note in the diff</dd> | ||
| @@ -1300,6 +1307,8 @@ | |||||
| 1300 | k-split from:body -> ermite.layout('split'); | 1307 | k-split from:body -> ermite.layout('split'); | ||
| 1301 | k-unified from:body -> ermite.layout('unified'); | 1308 | k-unified from:body -> ermite.layout('unified'); | ||
| 1302 | k-collapse from:body -> ermite.collapse(); | 1309 | k-collapse from:body -> ermite.collapse(); | ||
| 1310 | + | k-expand from:body -> ermite.expandFile(); | |||
| 1311 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 1303 | k-permalink from:body -> ermite.permalink(); | 1312 | k-permalink from:body -> ermite.permalink(); | ||
| 1304 | k-merge from:body -> window.ermiteMR?.openMerge(); | 1313 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 1305 | k-read from:body -> window.ermiteMR?.markRead(); | 1314 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/commits-small.html +9 -0
| @@ -426,6 +426,13 @@ | |||
| 426 | 426 | </div> | |
| 427 | 427 | <div> | |
| 428 | 428 | <dt> | |
| 429 | + | <kbd>e</kbd> | |
| 430 | + | <kbd>E</kbd> | |
| 431 | + | </dt> | |
| 432 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 433 | + | </div> | |
| 434 | + | <div> | |
| 435 | + | <dt> | |
| 429 | 436 | <kbd>n</kbd> | |
| 430 | 437 | </dt> | |
| 431 | 438 | <dd>merge request: next note in the diff</dd> | |
| @@ -581,6 +588,8 @@ | |||
| 581 | 588 | k-split from:body -> ermite.layout('split'); | |
| 582 | 589 | k-unified from:body -> ermite.layout('unified'); | |
| 583 | 590 | k-collapse from:body -> ermite.collapse(); | |
| 591 | + | k-expand from:body -> ermite.expandFile(); | |
| 592 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 584 | 593 | k-permalink from:body -> ermite.permalink(); | |
| 585 | 594 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 586 | 595 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -426,6 +426,13 @@ | |||||
| 426 | </div> | 426 | </div> | ||
| 427 | <div> | 427 | <div> | ||
| 428 | <dt> | 428 | <dt> | ||
| 429 | + | <kbd>e</kbd> | |||
| 430 | + | <kbd>E</kbd> | |||
| 431 | + | </dt> | |||
| 432 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 433 | + | </div> | |||
| 434 | + | <div> | |||
| 435 | + | <dt> | |||
| 429 | <kbd>n</kbd> | 436 | <kbd>n</kbd> | ||
| 430 | </dt> | 437 | </dt> | ||
| 431 | <dd>merge request: next note in the diff</dd> | 438 | <dd>merge request: next note in the diff</dd> | ||
| @@ -581,6 +588,8 @@ | |||||
| 581 | k-split from:body -> ermite.layout('split'); | 588 | k-split from:body -> ermite.layout('split'); | ||
| 582 | k-unified from:body -> ermite.layout('unified'); | 589 | k-unified from:body -> ermite.layout('unified'); | ||
| 583 | k-collapse from:body -> ermite.collapse(); | 590 | k-collapse from:body -> ermite.collapse(); | ||
| 591 | + | k-expand from:body -> ermite.expandFile(); | |||
| 592 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 584 | k-permalink from:body -> ermite.permalink(); | 593 | k-permalink from:body -> ermite.permalink(); | ||
| 585 | k-merge from:body -> window.ermiteMR?.openMerge(); | 594 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 586 | k-read from:body -> window.ermiteMR?.markRead(); | 595 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/empty.html +9 -0
| @@ -183,6 +183,13 @@ | |||
| 183 | 183 | </div> | |
| 184 | 184 | <div> | |
| 185 | 185 | <dt> | |
| 186 | + | <kbd>e</kbd> | |
| 187 | + | <kbd>E</kbd> | |
| 188 | + | </dt> | |
| 189 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 190 | + | </div> | |
| 191 | + | <div> | |
| 192 | + | <dt> | |
| 186 | 193 | <kbd>n</kbd> | |
| 187 | 194 | </dt> | |
| 188 | 195 | <dd>merge request: next note in the diff</dd> | |
| @@ -249,6 +256,8 @@ | |||
| 249 | 256 | k-split from:body -> ermite.layout('split'); | |
| 250 | 257 | k-unified from:body -> ermite.layout('unified'); | |
| 251 | 258 | k-collapse from:body -> ermite.collapse(); | |
| 259 | + | k-expand from:body -> ermite.expandFile(); | |
| 260 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 252 | 261 | k-permalink from:body -> ermite.permalink(); | |
| 253 | 262 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 254 | 263 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -183,6 +183,13 @@ | |||||
| 183 | </div> | 183 | </div> | ||
| 184 | <div> | 184 | <div> | ||
| 185 | <dt> | 185 | <dt> | ||
| 186 | + | <kbd>e</kbd> | |||
| 187 | + | <kbd>E</kbd> | |||
| 188 | + | </dt> | |||
| 189 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 190 | + | </div> | |||
| 191 | + | <div> | |||
| 192 | + | <dt> | |||
| 186 | <kbd>n</kbd> | 193 | <kbd>n</kbd> | ||
| 187 | </dt> | 194 | </dt> | ||
| 188 | <dd>merge request: next note in the diff</dd> | 195 | <dd>merge request: next note in the diff</dd> | ||
| @@ -249,6 +256,8 @@ | |||||
| 249 | k-split from:body -> ermite.layout('split'); | 256 | k-split from:body -> ermite.layout('split'); | ||
| 250 | k-unified from:body -> ermite.layout('unified'); | 257 | k-unified from:body -> ermite.layout('unified'); | ||
| 251 | k-collapse from:body -> ermite.collapse(); | 258 | k-collapse from:body -> ermite.collapse(); | ||
| 259 | + | k-expand from:body -> ermite.expandFile(); | |||
| 260 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 252 | k-permalink from:body -> ermite.permalink(); | 261 | k-permalink from:body -> ermite.permalink(); | ||
| 253 | k-merge from:body -> window.ermiteMR?.openMerge(); | 262 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 254 | k-read from:body -> window.ermiteMR?.markRead(); | 263 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/home.html +9 -0
| @@ -215,6 +215,13 @@ | |||
| 215 | 215 | </div> | |
| 216 | 216 | <div> | |
| 217 | 217 | <dt> | |
| 218 | + | <kbd>e</kbd> | |
| 219 | + | <kbd>E</kbd> | |
| 220 | + | </dt> | |
| 221 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 222 | + | </div> | |
| 223 | + | <div> | |
| 224 | + | <dt> | |
| 218 | 225 | <kbd>n</kbd> | |
| 219 | 226 | </dt> | |
| 220 | 227 | <dd>merge request: next note in the diff</dd> | |
| @@ -287,6 +294,8 @@ | |||
| 287 | 294 | k-split from:body -> ermite.layout('split'); | |
| 288 | 295 | k-unified from:body -> ermite.layout('unified'); | |
| 289 | 296 | k-collapse from:body -> ermite.collapse(); | |
| 297 | + | k-expand from:body -> ermite.expandFile(); | |
| 298 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 290 | 299 | k-permalink from:body -> ermite.permalink(); | |
| 291 | 300 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 292 | 301 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -215,6 +215,13 @@ | |||||
| 215 | </div> | 215 | </div> | ||
| 216 | <div> | 216 | <div> | ||
| 217 | <dt> | 217 | <dt> | ||
| 218 | + | <kbd>e</kbd> | |||
| 219 | + | <kbd>E</kbd> | |||
| 220 | + | </dt> | |||
| 221 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 222 | + | </div> | |||
| 223 | + | <div> | |||
| 224 | + | <dt> | |||
| 218 | <kbd>n</kbd> | 225 | <kbd>n</kbd> | ||
| 219 | </dt> | 226 | </dt> | ||
| 220 | <dd>merge request: next note in the diff</dd> | 227 | <dd>merge request: next note in the diff</dd> | ||
| @@ -287,6 +294,8 @@ | |||||
| 287 | k-split from:body -> ermite.layout('split'); | 294 | k-split from:body -> ermite.layout('split'); | ||
| 288 | k-unified from:body -> ermite.layout('unified'); | 295 | k-unified from:body -> ermite.layout('unified'); | ||
| 289 | k-collapse from:body -> ermite.collapse(); | 296 | k-collapse from:body -> ermite.collapse(); | ||
| 297 | + | k-expand from:body -> ermite.expandFile(); | |||
| 298 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 290 | k-permalink from:body -> ermite.permalink(); | 299 | k-permalink from:body -> ermite.permalink(); | ||
| 291 | k-merge from:body -> window.ermiteMR?.openMerge(); | 300 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 292 | k-read from:body -> window.ermiteMR?.markRead(); | 301 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/not-found-page.html +9 -0
| @@ -172,6 +172,13 @@ | |||
| 172 | 172 | </div> | |
| 173 | 173 | <div> | |
| 174 | 174 | <dt> | |
| 175 | + | <kbd>e</kbd> | |
| 176 | + | <kbd>E</kbd> | |
| 177 | + | </dt> | |
| 178 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 179 | + | </div> | |
| 180 | + | <div> | |
| 181 | + | <dt> | |
| 175 | 182 | <kbd>n</kbd> | |
| 176 | 183 | </dt> | |
| 177 | 184 | <dd>merge request: next note in the diff</dd> | |
| @@ -232,6 +239,8 @@ | |||
| 232 | 239 | k-split from:body -> ermite.layout('split'); | |
| 233 | 240 | k-unified from:body -> ermite.layout('unified'); | |
| 234 | 241 | k-collapse from:body -> ermite.collapse(); | |
| 242 | + | k-expand from:body -> ermite.expandFile(); | |
| 243 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 235 | 244 | k-permalink from:body -> ermite.permalink(); | |
| 236 | 245 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 237 | 246 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -172,6 +172,13 @@ | |||||
| 172 | </div> | 172 | </div> | ||
| 173 | <div> | 173 | <div> | ||
| 174 | <dt> | 174 | <dt> | ||
| 175 | + | <kbd>e</kbd> | |||
| 176 | + | <kbd>E</kbd> | |||
| 177 | + | </dt> | |||
| 178 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 179 | + | </div> | |||
| 180 | + | <div> | |||
| 181 | + | <dt> | |||
| 175 | <kbd>n</kbd> | 182 | <kbd>n</kbd> | ||
| 176 | </dt> | 183 | </dt> | ||
| 177 | <dd>merge request: next note in the diff</dd> | 184 | <dd>merge request: next note in the diff</dd> | ||
| @@ -232,6 +239,8 @@ | |||||
| 232 | k-split from:body -> ermite.layout('split'); | 239 | k-split from:body -> ermite.layout('split'); | ||
| 233 | k-unified from:body -> ermite.layout('unified'); | 240 | k-unified from:body -> ermite.layout('unified'); | ||
| 234 | k-collapse from:body -> ermite.collapse(); | 241 | k-collapse from:body -> ermite.collapse(); | ||
| 242 | + | k-expand from:body -> ermite.expandFile(); | |||
| 243 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 235 | k-permalink from:body -> ermite.permalink(); | 244 | k-permalink from:body -> ermite.permalink(); | ||
| 236 | k-merge from:body -> window.ermiteMR?.openMerge(); | 245 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 237 | k-read from:body -> window.ermiteMR?.markRead(); | 246 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/not-found.html +9 -0
| @@ -172,6 +172,13 @@ | |||
| 172 | 172 | </div> | |
| 173 | 173 | <div> | |
| 174 | 174 | <dt> | |
| 175 | + | <kbd>e</kbd> | |
| 176 | + | <kbd>E</kbd> | |
| 177 | + | </dt> | |
| 178 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 179 | + | </div> | |
| 180 | + | <div> | |
| 181 | + | <dt> | |
| 175 | 182 | <kbd>n</kbd> | |
| 176 | 183 | </dt> | |
| 177 | 184 | <dd>merge request: next note in the diff</dd> | |
| @@ -232,6 +239,8 @@ | |||
| 232 | 239 | k-split from:body -> ermite.layout('split'); | |
| 233 | 240 | k-unified from:body -> ermite.layout('unified'); | |
| 234 | 241 | k-collapse from:body -> ermite.collapse(); | |
| 242 | + | k-expand from:body -> ermite.expandFile(); | |
| 243 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 235 | 244 | k-permalink from:body -> ermite.permalink(); | |
| 236 | 245 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 237 | 246 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -172,6 +172,13 @@ | |||||
| 172 | </div> | 172 | </div> | ||
| 173 | <div> | 173 | <div> | ||
| 174 | <dt> | 174 | <dt> | ||
| 175 | + | <kbd>e</kbd> | |||
| 176 | + | <kbd>E</kbd> | |||
| 177 | + | </dt> | |||
| 178 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 179 | + | </div> | |||
| 180 | + | <div> | |||
| 181 | + | <dt> | |||
| 175 | <kbd>n</kbd> | 182 | <kbd>n</kbd> | ||
| 176 | </dt> | 183 | </dt> | ||
| 177 | <dd>merge request: next note in the diff</dd> | 184 | <dd>merge request: next note in the diff</dd> | ||
| @@ -232,6 +239,8 @@ | |||||
| 232 | k-split from:body -> ermite.layout('split'); | 239 | k-split from:body -> ermite.layout('split'); | ||
| 233 | k-unified from:body -> ermite.layout('unified'); | 240 | k-unified from:body -> ermite.layout('unified'); | ||
| 234 | k-collapse from:body -> ermite.collapse(); | 241 | k-collapse from:body -> ermite.collapse(); | ||
| 242 | + | k-expand from:body -> ermite.expandFile(); | |||
| 243 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 235 | k-permalink from:body -> ermite.permalink(); | 244 | k-permalink from:body -> ermite.permalink(); | ||
| 236 | k-merge from:body -> window.ermiteMR?.openMerge(); | 245 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 237 | k-read from:body -> window.ermiteMR?.markRead(); | 246 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/refs.html +9 -0
| @@ -302,6 +302,13 @@ | |||
| 302 | 302 | </div> | |
| 303 | 303 | <div> | |
| 304 | 304 | <dt> | |
| 305 | + | <kbd>e</kbd> | |
| 306 | + | <kbd>E</kbd> | |
| 307 | + | </dt> | |
| 308 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 309 | + | </div> | |
| 310 | + | <div> | |
| 311 | + | <dt> | |
| 305 | 312 | <kbd>n</kbd> | |
| 306 | 313 | </dt> | |
| 307 | 314 | <dd>merge request: next note in the diff</dd> | |
| @@ -467,6 +474,8 @@ | |||
| 467 | 474 | k-split from:body -> ermite.layout('split'); | |
| 468 | 475 | k-unified from:body -> ermite.layout('unified'); | |
| 469 | 476 | k-collapse from:body -> ermite.collapse(); | |
| 477 | + | k-expand from:body -> ermite.expandFile(); | |
| 478 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 470 | 479 | k-permalink from:body -> ermite.permalink(); | |
| 471 | 480 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 472 | 481 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -302,6 +302,13 @@ | |||||
| 302 | </div> | 302 | </div> | ||
| 303 | <div> | 303 | <div> | ||
| 304 | <dt> | 304 | <dt> | ||
| 305 | + | <kbd>e</kbd> | |||
| 306 | + | <kbd>E</kbd> | |||
| 307 | + | </dt> | |||
| 308 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 309 | + | </div> | |||
| 310 | + | <div> | |||
| 311 | + | <dt> | |||
| 305 | <kbd>n</kbd> | 312 | <kbd>n</kbd> | ||
| 306 | </dt> | 313 | </dt> | ||
| 307 | <dd>merge request: next note in the diff</dd> | 314 | <dd>merge request: next note in the diff</dd> | ||
| @@ -467,6 +474,8 @@ | |||||
| 467 | k-split from:body -> ermite.layout('split'); | 474 | k-split from:body -> ermite.layout('split'); | ||
| 468 | k-unified from:body -> ermite.layout('unified'); | 475 | k-unified from:body -> ermite.layout('unified'); | ||
| 469 | k-collapse from:body -> ermite.collapse(); | 476 | k-collapse from:body -> ermite.collapse(); | ||
| 477 | + | k-expand from:body -> ermite.expandFile(); | |||
| 478 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 470 | k-permalink from:body -> ermite.permalink(); | 479 | k-permalink from:body -> ermite.permalink(); | ||
| 471 | k-merge from:body -> window.ermiteMR?.openMerge(); | 480 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 472 | k-read from:body -> window.ermiteMR?.markRead(); | 481 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/summary.html +9 -0
| @@ -331,6 +331,13 @@ | |||
| 331 | 331 | </div> | |
| 332 | 332 | <div> | |
| 333 | 333 | <dt> | |
| 334 | + | <kbd>e</kbd> | |
| 335 | + | <kbd>E</kbd> | |
| 336 | + | </dt> | |
| 337 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 338 | + | </div> | |
| 339 | + | <div> | |
| 340 | + | <dt> | |
| 334 | 341 | <kbd>n</kbd> | |
| 335 | 342 | </dt> | |
| 336 | 343 | <dd>merge request: next note in the diff</dd> | |
| @@ -496,6 +503,8 @@ | |||
| 496 | 503 | k-split from:body -> ermite.layout('split'); | |
| 497 | 504 | k-unified from:body -> ermite.layout('unified'); | |
| 498 | 505 | k-collapse from:body -> ermite.collapse(); | |
| 506 | + | k-expand from:body -> ermite.expandFile(); | |
| 507 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 499 | 508 | k-permalink from:body -> ermite.permalink(); | |
| 500 | 509 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 501 | 510 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -331,6 +331,13 @@ | |||||
| 331 | </div> | 331 | </div> | ||
| 332 | <div> | 332 | <div> | ||
| 333 | <dt> | 333 | <dt> | ||
| 334 | + | <kbd>e</kbd> | |||
| 335 | + | <kbd>E</kbd> | |||
| 336 | + | </dt> | |||
| 337 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 338 | + | </div> | |||
| 339 | + | <div> | |||
| 340 | + | <dt> | |||
| 334 | <kbd>n</kbd> | 341 | <kbd>n</kbd> | ||
| 335 | </dt> | 342 | </dt> | ||
| 336 | <dd>merge request: next note in the diff</dd> | 343 | <dd>merge request: next note in the diff</dd> | ||
| @@ -496,6 +503,8 @@ | |||||
| 496 | k-split from:body -> ermite.layout('split'); | 503 | k-split from:body -> ermite.layout('split'); | ||
| 497 | k-unified from:body -> ermite.layout('unified'); | 504 | k-unified from:body -> ermite.layout('unified'); | ||
| 498 | k-collapse from:body -> ermite.collapse(); | 505 | k-collapse from:body -> ermite.collapse(); | ||
| 506 | + | k-expand from:body -> ermite.expandFile(); | |||
| 507 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 499 | k-permalink from:body -> ermite.permalink(); | 508 | k-permalink from:body -> ermite.permalink(); | ||
| 500 | k-merge from:body -> window.ermiteMR?.openMerge(); | 509 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 501 | k-read from:body -> window.ermiteMR?.markRead(); | 510 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/tree-nested.html +9 -0
| @@ -254,6 +254,13 @@ | |||
| 254 | 254 | </div> | |
| 255 | 255 | <div> | |
| 256 | 256 | <dt> | |
| 257 | + | <kbd>e</kbd> | |
| 258 | + | <kbd>E</kbd> | |
| 259 | + | </dt> | |
| 260 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 261 | + | </div> | |
| 262 | + | <div> | |
| 263 | + | <dt> | |
| 257 | 264 | <kbd>n</kbd> | |
| 258 | 265 | </dt> | |
| 259 | 266 | <dd>merge request: next note in the diff</dd> | |
| @@ -403,6 +410,8 @@ | |||
| 403 | 410 | k-split from:body -> ermite.layout('split'); | |
| 404 | 411 | k-unified from:body -> ermite.layout('unified'); | |
| 405 | 412 | k-collapse from:body -> ermite.collapse(); | |
| 413 | + | k-expand from:body -> ermite.expandFile(); | |
| 414 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 406 | 415 | k-permalink from:body -> ermite.permalink(); | |
| 407 | 416 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 408 | 417 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -254,6 +254,13 @@ | |||||
| 254 | </div> | 254 | </div> | ||
| 255 | <div> | 255 | <div> | ||
| 256 | <dt> | 256 | <dt> | ||
| 257 | + | <kbd>e</kbd> | |||
| 258 | + | <kbd>E</kbd> | |||
| 259 | + | </dt> | |||
| 260 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 261 | + | </div> | |||
| 262 | + | <div> | |||
| 263 | + | <dt> | |||
| 257 | <kbd>n</kbd> | 264 | <kbd>n</kbd> | ||
| 258 | </dt> | 265 | </dt> | ||
| 259 | <dd>merge request: next note in the diff</dd> | 266 | <dd>merge request: next note in the diff</dd> | ||
| @@ -403,6 +410,8 @@ | |||||
| 403 | k-split from:body -> ermite.layout('split'); | 410 | k-split from:body -> ermite.layout('split'); | ||
| 404 | k-unified from:body -> ermite.layout('unified'); | 411 | k-unified from:body -> ermite.layout('unified'); | ||
| 405 | k-collapse from:body -> ermite.collapse(); | 412 | k-collapse from:body -> ermite.collapse(); | ||
| 413 | + | k-expand from:body -> ermite.expandFile(); | |||
| 414 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 406 | k-permalink from:body -> ermite.permalink(); | 415 | k-permalink from:body -> ermite.permalink(); | ||
| 407 | k-merge from:body -> window.ermiteMR?.openMerge(); | 416 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 408 | k-read from:body -> window.ermiteMR?.markRead(); | 417 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/tree-root.html +9 -0
| @@ -481,6 +481,13 @@ | |||
| 481 | 481 | </div> | |
| 482 | 482 | <div> | |
| 483 | 483 | <dt> | |
| 484 | + | <kbd>e</kbd> | |
| 485 | + | <kbd>E</kbd> | |
| 486 | + | </dt> | |
| 487 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 488 | + | </div> | |
| 489 | + | <div> | |
| 490 | + | <dt> | |
| 484 | 491 | <kbd>n</kbd> | |
| 485 | 492 | </dt> | |
| 486 | 493 | <dd>merge request: next note in the diff</dd> | |
| @@ -630,6 +637,8 @@ | |||
| 630 | 637 | k-split from:body -> ermite.layout('split'); | |
| 631 | 638 | k-unified from:body -> ermite.layout('unified'); | |
| 632 | 639 | k-collapse from:body -> ermite.collapse(); | |
| 640 | + | k-expand from:body -> ermite.expandFile(); | |
| 641 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 633 | 642 | k-permalink from:body -> ermite.permalink(); | |
| 634 | 643 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 635 | 644 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -481,6 +481,13 @@ | |||||
| 481 | </div> | 481 | </div> | ||
| 482 | <div> | 482 | <div> | ||
| 483 | <dt> | 483 | <dt> | ||
| 484 | + | <kbd>e</kbd> | |||
| 485 | + | <kbd>E</kbd> | |||
| 486 | + | </dt> | |||
| 487 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 488 | + | </div> | |||
| 489 | + | <div> | |||
| 490 | + | <dt> | |||
| 484 | <kbd>n</kbd> | 491 | <kbd>n</kbd> | ||
| 485 | </dt> | 492 | </dt> | ||
| 486 | <dd>merge request: next note in the diff</dd> | 493 | <dd>merge request: next note in the diff</dd> | ||
| @@ -630,6 +637,8 @@ | |||||
| 630 | k-split from:body -> ermite.layout('split'); | 637 | k-split from:body -> ermite.layout('split'); | ||
| 631 | k-unified from:body -> ermite.layout('unified'); | 638 | k-unified from:body -> ermite.layout('unified'); | ||
| 632 | k-collapse from:body -> ermite.collapse(); | 639 | k-collapse from:body -> ermite.collapse(); | ||
| 640 | + | k-expand from:body -> ermite.expandFile(); | |||
| 641 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 633 | k-permalink from:body -> ermite.permalink(); | 642 | k-permalink from:body -> ermite.permalink(); | ||
| 634 | k-merge from:body -> window.ermiteMR?.openMerge(); | 643 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 635 | k-read from:body -> window.ermiteMR?.markRead(); | 644 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/tree-slash-ref.html +9 -0
| @@ -251,6 +251,13 @@ | |||
| 251 | 251 | </div> | |
| 252 | 252 | <div> | |
| 253 | 253 | <dt> | |
| 254 | + | <kbd>e</kbd> | |
| 255 | + | <kbd>E</kbd> | |
| 256 | + | </dt> | |
| 257 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 258 | + | </div> | |
| 259 | + | <div> | |
| 260 | + | <dt> | |
| 254 | 261 | <kbd>n</kbd> | |
| 255 | 262 | </dt> | |
| 256 | 263 | <dd>merge request: next note in the diff</dd> | |
| @@ -400,6 +407,8 @@ | |||
| 400 | 407 | k-split from:body -> ermite.layout('split'); | |
| 401 | 408 | k-unified from:body -> ermite.layout('unified'); | |
| 402 | 409 | k-collapse from:body -> ermite.collapse(); | |
| 410 | + | k-expand from:body -> ermite.expandFile(); | |
| 411 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 403 | 412 | k-permalink from:body -> ermite.permalink(); | |
| 404 | 413 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 405 | 414 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -251,6 +251,13 @@ | |||||
| 251 | </div> | 251 | </div> | ||
| 252 | <div> | 252 | <div> | ||
| 253 | <dt> | 253 | <dt> | ||
| 254 | + | <kbd>e</kbd> | |||
| 255 | + | <kbd>E</kbd> | |||
| 256 | + | </dt> | |||
| 257 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 258 | + | </div> | |||
| 259 | + | <div> | |||
| 260 | + | <dt> | |||
| 254 | <kbd>n</kbd> | 261 | <kbd>n</kbd> | ||
| 255 | </dt> | 262 | </dt> | ||
| 256 | <dd>merge request: next note in the diff</dd> | 263 | <dd>merge request: next note in the diff</dd> | ||
| @@ -400,6 +407,8 @@ | |||||
| 400 | k-split from:body -> ermite.layout('split'); | 407 | k-split from:body -> ermite.layout('split'); | ||
| 401 | k-unified from:body -> ermite.layout('unified'); | 408 | k-unified from:body -> ermite.layout('unified'); | ||
| 402 | k-collapse from:body -> ermite.collapse(); | 409 | k-collapse from:body -> ermite.collapse(); | ||
| 410 | + | k-expand from:body -> ermite.expandFile(); | |||
| 411 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 403 | k-permalink from:body -> ermite.permalink(); | 412 | k-permalink from:body -> ermite.permalink(); | ||
| 404 | k-merge from:body -> window.ermiteMR?.openMerge(); | 413 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 405 | k-read from:body -> window.ermiteMR?.markRead(); | 414 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/testdata/web/tree-subdir.html +9 -0
| @@ -276,6 +276,13 @@ | |||
| 276 | 276 | </div> | |
| 277 | 277 | <div> | |
| 278 | 278 | <dt> | |
| 279 | + | <kbd>e</kbd> | |
| 280 | + | <kbd>E</kbd> | |
| 281 | + | </dt> | |
| 282 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |
| 283 | + | </div> | |
| 284 | + | <div> | |
| 285 | + | <dt> | |
| 279 | 286 | <kbd>n</kbd> | |
| 280 | 287 | </dt> | |
| 281 | 288 | <dd>merge request: next note in the diff</dd> | |
| @@ -425,6 +432,8 @@ | |||
| 425 | 432 | k-split from:body -> ermite.layout('split'); | |
| 426 | 433 | k-unified from:body -> ermite.layout('unified'); | |
| 427 | 434 | k-collapse from:body -> ermite.collapse(); | |
| 435 | + | k-expand from:body -> ermite.expandFile(); | |
| 436 | + | k-expandAll from:body -> ermite.expandAll(); | |
| 428 | 437 | k-permalink from:body -> ermite.permalink(); | |
| 429 | 438 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 430 | 439 | k-read from:body -> window.ermiteMR?.markRead(); | |
| @@ -276,6 +276,13 @@ | |||||
| 276 | </div> | 276 | </div> | ||
| 277 | <div> | 277 | <div> | ||
| 278 | <dt> | 278 | <dt> | ||
| 279 | + | <kbd>e</kbd> | |||
| 280 | + | <kbd>E</kbd> | |||
| 281 | + | </dt> | |||
| 282 | + | <dd>diff: the whole file in view / every file, with the lines the hunks hide</dd> | |||
| 283 | + | </div> | |||
| 284 | + | <div> | |||
| 285 | + | <dt> | |||
| 279 | <kbd>n</kbd> | 286 | <kbd>n</kbd> | ||
| 280 | </dt> | 287 | </dt> | ||
| 281 | <dd>merge request: next note in the diff</dd> | 288 | <dd>merge request: next note in the diff</dd> | ||
| @@ -425,6 +432,8 @@ | |||||
| 425 | k-split from:body -> ermite.layout('split'); | 432 | k-split from:body -> ermite.layout('split'); | ||
| 426 | k-unified from:body -> ermite.layout('unified'); | 433 | k-unified from:body -> ermite.layout('unified'); | ||
| 427 | k-collapse from:body -> ermite.collapse(); | 434 | k-collapse from:body -> ermite.collapse(); | ||
| 435 | + | k-expand from:body -> ermite.expandFile(); | |||
| 436 | + | k-expandAll from:body -> ermite.expandAll(); | |||
| 428 | k-permalink from:body -> ermite.permalink(); | 437 | k-permalink from:body -> ermite.permalink(); | ||
| 429 | k-merge from:body -> window.ermiteMR?.openMerge(); | 438 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 430 | k-read from:body -> window.ermiteMR?.markRead(); | 439 | k-read from:body -> window.ermiteMR?.markRead(); | ||
M internal/app/web_commit_test.go +78 -0
| @@ -185,3 +185,81 @@ func TestWebCommitPrivate(t *testing.T) { | |||
| 185 | 185 | assert.Equal(t, normalise(missing), normalise(body), p) | |
| 186 | 186 | } | |
| 187 | 187 | } | |
| 188 | + | ||
| 189 | + | // The hidden lines of a diff: the page has a gap before a hunk and after the | |
| 190 | + | // last one, and /context serves their rows for both layouts, numbered on both | |
| 191 | + | // sides; a private repository's are the 404 page. | |
| 192 | + | func TestWebDiffContext(t *testing.T) { | |
| 193 | + | h := newHarness(t, harnessOpts{}) | |
| 194 | + | wt := t.TempDir() | |
| 195 | + | h.mustGit(wt, "init", "-q", "--initial-branch=main") | |
| 196 | + | var src strings.Builder | |
| 197 | + | for i := 1; i <= 40; i++ { | |
| 198 | + | fmt.Fprintf(&src, "line %d <b>\n", i) | |
| 199 | + | } | |
| 200 | + | h.put(wt, "f.txt", []byte(src.String()), 0o644) | |
| 201 | + | h.commitAt(wt, "2026-02-01T10:00:00Z", "forty lines") | |
| 202 | + | h.put(wt, "f.txt", []byte(strings.Replace(src.String(), "line 20 <b>", "line twenty", 1)), 0o644) | |
| 203 | + | sha := h.commitAt(wt, "2026-02-02T10:00:00Z", "change line 20") | |
| 204 | + | h.createRepo("ctx", domain.VisibilityPublic) | |
| 205 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx.git"), "main") | |
| 206 | + | ||
| 207 | + | _, _, page := h.get("/ctx/commit/" + sha) | |
| 208 | + | gap := "/ctx/context/" + sha + "?key=g0&n=16&new=1&old=1&path=f.txt" | |
| 209 | + | assert.Contains(t, page, gap, "lines 1 to 16 before the hunk (17 to 23)") | |
| 210 | + | assert.Contains(t, page, "16 hidden lines") | |
| 211 | + | assert.Contains(t, page, "/ctx/context/"+sha+"?key=tail&n=0&new=24&old=24&path=f.txt") | |
| 212 | + | ||
| 213 | + | st, _, body := h.get("/ctx/context/" + sha + "?key=g0&n=16&new=1&old=1&path=f.txt") | |
| 214 | + | require.Equal(t, 200, st) | |
| 215 | + | assert.Equal(t, 3, strings.Count(body, "line 16 <b>"), "unified once, split on both sides, escaped") | |
| 216 | + | assert.NotContains(t, body, "line 17") | |
| 217 | + | assert.NotContains(t, body, `class="gap"`, "nothing left") | |
| 218 | + | ||
| 219 | + | st, _, body = h.get("/ctx/context/" + sha + "?key=tail&n=0&new=24&old=24&path=f.txt") | |
| 220 | + | require.Equal(t, 200, st) | |
| 221 | + | assert.Contains(t, body, "line 40 <b>") | |
| 222 | + | assert.NotContains(t, body, "line 23 ") | |
| 223 | + | ||
| 224 | + | for _, bad := range []string{ | |
| 225 | + | "/ctx/context/" + sha + "?n=1&new=1&old=1", // no path | |
| 226 | + | "/ctx/context/" + sha + "?n=1&new=0&old=1&path=f.txt", // line 0 | |
| 227 | + | "/ctx/context/" + sha + "?n=-1&new=1&old=1&path=f.txt", // negative | |
| 228 | + | "/ctx/context/" + sha + "?n=1&new=99&old=99&path=f.txt", // past the end | |
| 229 | + | "/ctx/context/" + sha + "?n=1&new=1&old=1&path=nope.txt", // no such file | |
| 230 | + | "/ctx/context/" + sha[:7] + "?n=1&new=1&old=1&path=f.txt", // not a full id | |
| 231 | + | "/ctx/context/main?n=1&new=1&old=1&path=f.txt", // a ref | |
| 232 | + | } { | |
| 233 | + | st, _, _ := h.get(bad) | |
| 234 | + | assert.Equal(t, 404, st, bad) | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | // Lines as Git numbers them: a lone CR is not a line break; the last line | |
| 238 | + | // of a file without a final newline keeps its marker. | |
| 239 | + | var cr strings.Builder | |
| 240 | + | cr.WriteString("a\rb\n") | |
| 241 | + | for i := 2; i <= 30; i++ { | |
| 242 | + | fmt.Fprintf(&cr, "row %d\n", i) | |
| 243 | + | } | |
| 244 | + | h.put(wt, "cr.txt", []byte(cr.String()+"last"), 0o644) | |
| 245 | + | h.commitAt(wt, "2026-02-03T10:00:00Z", "cr") | |
| 246 | + | h.put(wt, "cr.txt", []byte(strings.Replace(cr.String(), "row 15\n", "row fifteen\n", 1)+"last"), 0o644) | |
| 247 | + | crSHA := h.commitAt(wt, "2026-02-04T10:00:00Z", "change row 15") | |
| 248 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx.git"), "main") | |
| 249 | + | _, _, crPage := h.get("/ctx/commit/" + crSHA) | |
| 250 | + | assert.Contains(t, crPage, "11 hidden lines", "lines 1 to 11 before the hunk at 12") | |
| 251 | + | st, _, body = h.get("/ctx/context/" + crSHA + "?n=11&new=1&old=1&path=cr.txt") | |
| 252 | + | require.Equal(t, 200, st) | |
| 253 | + | assert.Contains(t, body, "row 11") | |
| 254 | + | assert.NotContains(t, body, "row 12", "a lone CR does not shift the numbers") | |
| 255 | + | st, _, body = h.get("/ctx/context/" + crSHA + "?n=0&new=19&old=19&path=cr.txt") | |
| 256 | + | require.Equal(t, 200, st) | |
| 257 | + | assert.Contains(t, body, "last<span class=\"eol\"", "no newline at the end of the file") | |
| 258 | + | ||
| 259 | + | // A private repository: the 404 page, nothing of the file. | |
| 260 | + | h.createRepo("ctx-hidden", domain.VisibilityPrivate) | |
| 261 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx-hidden.git"), "main") | |
| 262 | + | st, _, body = h.get("/ctx-hidden/context/" + sha + "?n=0&new=1&old=1&path=f.txt") | |
| 263 | + | assert.Equal(t, 404, st) | |
| 264 | + | assert.NotContains(t, body, "line 1 ") | |
| 265 | + | } | |
| @@ -185,3 +185,81 @@ func TestWebCommitPrivate(t *testing.T) { | |||||
| 185 | assert.Equal(t, normalise(missing), normalise(body), p) | 185 | assert.Equal(t, normalise(missing), normalise(body), p) | ||
| 186 | } | 186 | } | ||
| 187 | } | 187 | } | ||
| 188 | + | ||||
| 189 | + | // The hidden lines of a diff: the page has a gap before a hunk and after the | |||
| 190 | + | // last one, and /context serves their rows for both layouts, numbered on both | |||
| 191 | + | // sides; a private repository's are the 404 page. | |||
| 192 | + | func TestWebDiffContext(t *testing.T) { | |||
| 193 | + | h := newHarness(t, harnessOpts{}) | |||
| 194 | + | wt := t.TempDir() | |||
| 195 | + | h.mustGit(wt, "init", "-q", "--initial-branch=main") | |||
| 196 | + | var src strings.Builder | |||
| 197 | + | for i := 1; i <= 40; i++ { | |||
| 198 | + | fmt.Fprintf(&src, "line %d <b>\n", i) | |||
| 199 | + | } | |||
| 200 | + | h.put(wt, "f.txt", []byte(src.String()), 0o644) | |||
| 201 | + | h.commitAt(wt, "2026-02-01T10:00:00Z", "forty lines") | |||
| 202 | + | h.put(wt, "f.txt", []byte(strings.Replace(src.String(), "line 20 <b>", "line twenty", 1)), 0o644) | |||
| 203 | + | sha := h.commitAt(wt, "2026-02-02T10:00:00Z", "change line 20") | |||
| 204 | + | h.createRepo("ctx", domain.VisibilityPublic) | |||
| 205 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx.git"), "main") | |||
| 206 | + | ||||
| 207 | + | _, _, page := h.get("/ctx/commit/" + sha) | |||
| 208 | + | gap := "/ctx/context/" + sha + "?key=g0&n=16&new=1&old=1&path=f.txt" | |||
| 209 | + | assert.Contains(t, page, gap, "lines 1 to 16 before the hunk (17 to 23)") | |||
| 210 | + | assert.Contains(t, page, "16 hidden lines") | |||
| 211 | + | assert.Contains(t, page, "/ctx/context/"+sha+"?key=tail&n=0&new=24&old=24&path=f.txt") | |||
| 212 | + | ||||
| 213 | + | st, _, body := h.get("/ctx/context/" + sha + "?key=g0&n=16&new=1&old=1&path=f.txt") | |||
| 214 | + | require.Equal(t, 200, st) | |||
| 215 | + | assert.Equal(t, 3, strings.Count(body, "line 16 <b>"), "unified once, split on both sides, escaped") | |||
| 216 | + | assert.NotContains(t, body, "line 17") | |||
| 217 | + | assert.NotContains(t, body, `class="gap"`, "nothing left") | |||
| 218 | + | ||||
| 219 | + | st, _, body = h.get("/ctx/context/" + sha + "?key=tail&n=0&new=24&old=24&path=f.txt") | |||
| 220 | + | require.Equal(t, 200, st) | |||
| 221 | + | assert.Contains(t, body, "line 40 <b>") | |||
| 222 | + | assert.NotContains(t, body, "line 23 ") | |||
| 223 | + | ||||
| 224 | + | for _, bad := range []string{ | |||
| 225 | + | "/ctx/context/" + sha + "?n=1&new=1&old=1", // no path | |||
| 226 | + | "/ctx/context/" + sha + "?n=1&new=0&old=1&path=f.txt", // line 0 | |||
| 227 | + | "/ctx/context/" + sha + "?n=-1&new=1&old=1&path=f.txt", // negative | |||
| 228 | + | "/ctx/context/" + sha + "?n=1&new=99&old=99&path=f.txt", // past the end | |||
| 229 | + | "/ctx/context/" + sha + "?n=1&new=1&old=1&path=nope.txt", // no such file | |||
| 230 | + | "/ctx/context/" + sha[:7] + "?n=1&new=1&old=1&path=f.txt", // not a full id | |||
| 231 | + | "/ctx/context/main?n=1&new=1&old=1&path=f.txt", // a ref | |||
| 232 | + | } { | |||
| 233 | + | st, _, _ := h.get(bad) | |||
| 234 | + | assert.Equal(t, 404, st, bad) | |||
| 235 | + | } | |||
| 236 | + | ||||
| 237 | + | // Lines as Git numbers them: a lone CR is not a line break; the last line | |||
| 238 | + | // of a file without a final newline keeps its marker. | |||
| 239 | + | var cr strings.Builder | |||
| 240 | + | cr.WriteString("a\rb\n") | |||
| 241 | + | for i := 2; i <= 30; i++ { | |||
| 242 | + | fmt.Fprintf(&cr, "row %d\n", i) | |||
| 243 | + | } | |||
| 244 | + | h.put(wt, "cr.txt", []byte(cr.String()+"last"), 0o644) | |||
| 245 | + | h.commitAt(wt, "2026-02-03T10:00:00Z", "cr") | |||
| 246 | + | h.put(wt, "cr.txt", []byte(strings.Replace(cr.String(), "row 15\n", "row fifteen\n", 1)+"last"), 0o644) | |||
| 247 | + | crSHA := h.commitAt(wt, "2026-02-04T10:00:00Z", "change row 15") | |||
| 248 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx.git"), "main") | |||
| 249 | + | _, _, crPage := h.get("/ctx/commit/" + crSHA) | |||
| 250 | + | assert.Contains(t, crPage, "11 hidden lines", "lines 1 to 11 before the hunk at 12") | |||
| 251 | + | st, _, body = h.get("/ctx/context/" + crSHA + "?n=11&new=1&old=1&path=cr.txt") | |||
| 252 | + | require.Equal(t, 200, st) | |||
| 253 | + | assert.Contains(t, body, "row 11") | |||
| 254 | + | assert.NotContains(t, body, "row 12", "a lone CR does not shift the numbers") | |||
| 255 | + | st, _, body = h.get("/ctx/context/" + crSHA + "?n=0&new=19&old=19&path=cr.txt") | |||
| 256 | + | require.Equal(t, 200, st) | |||
| 257 | + | assert.Contains(t, body, "last<span class=\"eol\"", "no newline at the end of the file") | |||
| 258 | + | ||||
| 259 | + | // A private repository: the 404 page, nothing of the file. | |||
| 260 | + | h.createRepo("ctx-hidden", domain.VisibilityPrivate) | |||
| 261 | + | h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/ctx-hidden.git"), "main") | |||
| 262 | + | st, _, body = h.get("/ctx-hidden/context/" + sha + "?n=0&new=1&old=1&path=f.txt") | |||
| 263 | + | assert.Equal(t, 404, st) | |||
| 264 | + | assert.NotContains(t, body, "line 1 ") | |||
| 265 | + | } | |||