feat(web): one merge request card, a diff view menu, a wide diff mode and quieter hunk rows
- commit
- fcc77036ea838fc565f571e1c6afe37cd933ecc7
- author
- brice · · 2026-10-19 15:05 UTC
- committer
- brice · · 2026-10-19 15:05 UTC
- parent
- d8e7d5b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
16 files · +866 -681
M internal/adapters/web/diff.templ +30 -3
| @@ -72,9 +72,8 @@ templ Commit(l Layout, p CommitPage) { | |||
| 72 | 72 | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 73 | 73 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 74 | 74 | <span class="grow"></span> | |
| 75 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 76 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 77 | - | @collapseButton() | |
| 75 | + | @diffViewMenu(p.DiffView) | |
| 76 | + | @wideButton() | |
| 78 | 77 | </div> | |
| 79 | 78 | if p.Invisible { | |
| 80 | 79 | <p class="notice"> | |
| @@ -112,6 +111,34 @@ templ Commit(l Layout, p CommitPage) { | |||
| 112 | 111 | } | |
| 113 | 112 | } | |
| 114 | 113 | ||
| 114 | + | // diffViewMenu is the layout of the diff (unified, the default, or split) and | |
| 115 | + | // the fold of its files, in one menu: a <details> that works without scripts; | |
| 116 | + | // u, d and x still act without opening it. Its button names the layout shown. | |
| 117 | + | templ diffViewMenu(p DiffView) { | |
| 118 | + | <details class="menu vmenu"> | |
| 119 | + | <summary class="btn" aria-label="Diff view"> | |
| 120 | + | if p.Both { | |
| 121 | + | <span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">{ viewLabel(p.View) }</span> | |
| 122 | + | } else { | |
| 123 | + | { viewLabel(p.View) } | |
| 124 | + | } | |
| 125 | + | </summary> | |
| 126 | + | <div class="menu-pop" role="group" aria-label="Diff view"> | |
| 127 | + | @viewLink(p, "unified", "Unified", "u", p.UnifiedHref) | |
| 128 | + | @viewLink(p, "split", "Split", "d", p.SplitHref) | |
| 129 | + | @collapseButton() | |
| 130 | + | </div> | |
| 131 | + | </details> | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | // wideButton gives the diff the whole width of the screen: the rail goes to | |
| 135 | + | // the right edge and the code takes the rest. ermite.js keeps the choice. | |
| 136 | + | templ wideButton() { | |
| 137 | + | <button type="button" class="btn" data-js data-fire="wide" data-wide-toggle aria-pressed="false" aria-keyshortcuts="w" title="use the whole width of the screen"> | |
| 138 | + | Wide<kbd aria-hidden="true">w</kbd> | |
| 139 | + | </button> | |
| 140 | + | } | |
| 141 | + | ||
| 115 | 142 | // viewLink is a button of the diff tools. It is a link, so that the split | |
| 116 | 143 | // layout works without JavaScript; with it, and when both layouts are in the | |
| 117 | 144 | // page, a click only flips the layout. | |
| @@ -72,9 +72,8 @@ templ Commit(l Layout, p CommitPage) { | |||||
| 72 | <div class="diff-tools" role="group" aria-label="Diff view"> | 72 | <div class="diff-tools" role="group" aria-label="Diff view"> | ||
| 73 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 73 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | ||
| 74 | <span class="grow"></span> | 74 | <span class="grow"></span> | ||
| 75 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 75 | + | @diffViewMenu(p.DiffView) |
| 76 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 76 | + | @wideButton() |
| 77 | - | @collapseButton() | |||
| 78 | </div> | 77 | </div> | ||
| 79 | if p.Invisible { | 78 | if p.Invisible { | ||
| 80 | <p class="notice"> | 79 | <p class="notice"> | ||
| @@ -112,6 +111,34 @@ templ Commit(l Layout, p CommitPage) { | |||||
| 112 | } | 111 | } | ||
| 113 | } | 112 | } | ||
| 114 | 113 | ||||
| 114 | + | // diffViewMenu is the layout of the diff (unified, the default, or split) and | |||
| 115 | + | // the fold of its files, in one menu: a <details> that works without scripts; | |||
| 116 | + | // u, d and x still act without opening it. Its button names the layout shown. | |||
| 117 | + | templ diffViewMenu(p DiffView) { | |||
| 118 | + | <details class="menu vmenu"> | |||
| 119 | + | <summary class="btn" aria-label="Diff view"> | |||
| 120 | + | if p.Both { | |||
| 121 | + | <span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">{ viewLabel(p.View) }</span> | |||
| 122 | + | } else { | |||
| 123 | + | { viewLabel(p.View) } | |||
| 124 | + | } | |||
| 125 | + | </summary> | |||
| 126 | + | <div class="menu-pop" role="group" aria-label="Diff view"> | |||
| 127 | + | @viewLink(p, "unified", "Unified", "u", p.UnifiedHref) | |||
| 128 | + | @viewLink(p, "split", "Split", "d", p.SplitHref) | |||
| 129 | + | @collapseButton() | |||
| 130 | + | </div> | |||
| 131 | + | </details> | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | // wideButton gives the diff the whole width of the screen: the rail goes to | |||
| 135 | + | // the right edge and the code takes the rest. ermite.js keeps the choice. | |||
| 136 | + | templ wideButton() { | |||
| 137 | + | <button type="button" class="btn" data-js data-fire="wide" data-wide-toggle aria-pressed="false" aria-keyshortcuts="w" title="use the whole width of the screen"> | |||
| 138 | + | Wide<kbd aria-hidden="true">w</kbd> | |||
| 139 | + | </button> | |||
| 140 | + | } | |||
| 141 | + | ||||
| 115 | // viewLink is a button of the diff tools. It is a link, so that the split | 142 | // viewLink is a button of the diff tools. It is a link, so that the split | ||
| 116 | // layout works without JavaScript; with it, and when both layouts are in the | 143 | // layout works without JavaScript; with it, and when both layouts are in the | ||
| 117 | // page, a click only flips the layout. | 144 | // page, a click only flips the layout. | ||
M internal/adapters/web/diff_templ.go +542 -434 generated file: collapsed
M internal/adapters/web/format.go +8 -0
| @@ -61,6 +61,14 @@ func grouped(n int) string { | |||
| 61 | 61 | return string(b) | |
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | + | // viewLabel names a diff layout on the button of the view menu. | |
| 65 | + | func viewLabel(view string) string { | |
| 66 | + | if view == "split" { | |
| 67 | + | return "Split" | |
| 68 | + | } | |
| 69 | + | return "Unified" | |
| 70 | + | } | |
| 71 | + | ||
| 64 | 72 | func plural(n int, one, many string) string { | |
| 65 | 73 | if n == 1 { | |
| 66 | 74 | return "1 " + one | |
| @@ -61,6 +61,14 @@ func grouped(n int) string { | |||||
| 61 | return string(b) | 61 | return string(b) | ||
| 62 | } | 62 | } | ||
| 63 | 63 | ||||
| 64 | + | // viewLabel names a diff layout on the button of the view menu. | |||
| 65 | + | func viewLabel(view string) string { | |||
| 66 | + | if view == "split" { | |||
| 67 | + | return "Split" | |||
| 68 | + | } | |||
| 69 | + | return "Unified" | |||
| 70 | + | } | |||
| 71 | + | ||||
| 64 | func plural(n int, one, many string) string { | 72 | func plural(n int, one, many string) string { | ||
| 65 | if n == 1 { | 73 | if n == 1 { | ||
| 66 | return "1 " + one | 74 | return "1 " + one | ||
M internal/adapters/web/issues.templ +24 -22
| @@ -136,32 +136,34 @@ templ issueNewRail() { | |||
| 136 | 136 | // owner, the form to comment and the button that closes or reopens it. | |
| 137 | 137 | templ IssueShow(l Layout, p IssuePage) { | |
| 138 | 138 | @Shell(l, issueRail(p)) { | |
| 139 | - | <header class="show mr-h"> | |
| 140 | - | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | |
| 141 | - | <dl class="kv show-kv"> | |
| 142 | - | <dt>state</dt> | |
| 143 | - | <dd class="mr-state" data-state={ p.State }>{ p.State }</dd> | |
| 144 | - | <dt>opened</dt> | |
| 145 | - | <dd> | |
| 146 | - | { p.Opened.Name } | |
| 147 | - | if p.Opened.WhenAt != "" { | |
| 148 | - | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |
| 139 | + | <section class="mr-card"> | |
| 140 | + | <header class="show mr-h"> | |
| 141 | + | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | |
| 142 | + | <dl class="kv show-kv"> | |
| 143 | + | <dt>state</dt> | |
| 144 | + | <dd class="mr-state" data-state={ p.State }>{ p.State }</dd> | |
| 145 | + | <dt>opened</dt> | |
| 146 | + | <dd> | |
| 147 | + | { p.Opened.Name } | |
| 148 | + | if p.Opened.WhenAt != "" { | |
| 149 | + | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |
| 150 | + | } | |
| 151 | + | </dd> | |
| 152 | + | if p.UpdatedAt != "" { | |
| 153 | + | <dt>updated</dt> | |
| 154 | + | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |
| 149 | 155 | } | |
| 150 | - | </dd> | |
| 151 | - | if p.UpdatedAt != "" { | |
| 152 | - | <dt>updated</dt> | |
| 153 | - | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |
| 154 | - | } | |
| 155 | - | </dl> | |
| 156 | - | </header> | |
| 156 | + | </dl> | |
| 157 | + | </header> | |
| 158 | + | if p.Desc != "" { | |
| 159 | + | <article class="mr-desc prose" aria-label="description" hx-ignore> | |
| 160 | + | @templ.Raw(string(p.Desc)) | |
| 161 | + | </article> | |
| 162 | + | } | |
| 163 | + | </section> | |
| 157 | 164 | if p.Live { | |
| 158 | 165 | @LiveSlot("mr-notice") | |
| 159 | 166 | } | |
| 160 | - | if p.Desc != "" { | |
| 161 | - | <article class="mr-desc prose" aria-label="description" hx-ignore> | |
| 162 | - | @templ.Raw(string(p.Desc)) | |
| 163 | - | </article> | |
| 164 | - | } | |
| 165 | 167 | <h2 class="rule-h" id="comments"><span>Comments</span><span class="count">{ itoa(len(p.Comments)) }</span></h2> | |
| 166 | 168 | for _, c := range p.Comments { | |
| 167 | 169 | @commentView(c, "") | |
| @@ -136,32 +136,34 @@ templ issueNewRail() { | |||||
| 136 | // owner, the form to comment and the button that closes or reopens it. | 136 | // owner, the form to comment and the button that closes or reopens it. | ||
| 137 | templ IssueShow(l Layout, p IssuePage) { | 137 | templ IssueShow(l Layout, p IssuePage) { | ||
| 138 | @Shell(l, issueRail(p)) { | 138 | @Shell(l, issueRail(p)) { | ||
| 139 | - | <header class="show mr-h"> | 139 | + | <section class="mr-card"> |
| 140 | - | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | 140 | + | <header class="show mr-h"> |
| 141 | - | <dl class="kv show-kv"> | 141 | + | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> |
| 142 | - | <dt>state</dt> | 142 | + | <dl class="kv show-kv"> |
| 143 | - | <dd class="mr-state" data-state={ p.State }>{ p.State }</dd> | 143 | + | <dt>state</dt> |
| 144 | - | <dt>opened</dt> | 144 | + | <dd class="mr-state" data-state={ p.State }>{ p.State }</dd> |
| 145 | - | <dd> | 145 | + | <dt>opened</dt> |
| 146 | - | { p.Opened.Name } | 146 | + | <dd> |
| 147 | - | if p.Opened.WhenAt != "" { | 147 | + | { p.Opened.Name } |
| 148 | - | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | 148 | + | if p.Opened.WhenAt != "" { |
| 149 | + | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |||
| 150 | + | } | |||
| 151 | + | </dd> | |||
| 152 | + | if p.UpdatedAt != "" { | |||
| 153 | + | <dt>updated</dt> | |||
| 154 | + | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |||
| 149 | } | 155 | } | ||
| 150 | - | </dd> | 156 | + | </dl> |
| 151 | - | if p.UpdatedAt != "" { | 157 | + | </header> |
| 152 | - | <dt>updated</dt> | 158 | + | if p.Desc != "" { |
| 153 | - | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | 159 | + | <article class="mr-desc prose" aria-label="description" hx-ignore> |
| 154 | - | } | 160 | + | @templ.Raw(string(p.Desc)) |
| 155 | - | </dl> | 161 | + | </article> |
| 156 | - | </header> | 162 | + | } |
| 163 | + | </section> | |||
| 157 | if p.Live { | 164 | if p.Live { | ||
| 158 | @LiveSlot("mr-notice") | 165 | @LiveSlot("mr-notice") | ||
| 159 | } | 166 | } | ||
| 160 | - | if p.Desc != "" { | |||
| 161 | - | <article class="mr-desc prose" aria-label="description" hx-ignore> | |||
| 162 | - | @templ.Raw(string(p.Desc)) | |||
| 163 | - | </article> | |||
| 164 | - | } | |||
| 165 | <h2 class="rule-h" id="comments"><span>Comments</span><span class="count">{ itoa(len(p.Comments)) }</span></h2> | 167 | <h2 class="rule-h" id="comments"><span>Comments</span><span class="count">{ itoa(len(p.Comments)) }</span></h2> | ||
| 166 | for _, c := range p.Comments { | 168 | for _, c := range p.Comments { | ||
| 167 | @commentView(c, "") | 169 | @commentView(c, "") | ||
M internal/adapters/web/issues_templ.go +26 -26
| @@ -585,14 +585,14 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 585 | 585 | }() | |
| 586 | 586 | } | |
| 587 | 587 | ctx = templ.InitializeContext(ctx) | |
| 588 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<header class=\"show mr-h\"><h1><span class=\"mute\">") | |
| 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<section class=\"mr-card\"><header class=\"show mr-h\"><h1><span class=\"mute\">") | |
| 589 | 589 | if templ_7745c5c3_Err != nil { | |
| 590 | 590 | return templ_7745c5c3_Err | |
| 591 | 591 | } | |
| 592 | 592 | var templ_7745c5c3_Var27 string | |
| 593 | 593 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Number) | |
| 594 | 594 | if templ_7745c5c3_Err != nil { | |
| 595 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 140, Col: 36} | |
| 595 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 141, Col: 37} | |
| 596 | 596 | } | |
| 597 | 597 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | |
| 598 | 598 | if templ_7745c5c3_Err != nil { | |
| @@ -605,7 +605,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 605 | 605 | var templ_7745c5c3_Var28 string | |
| 606 | 606 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | |
| 607 | 607 | if templ_7745c5c3_Err != nil { | |
| 608 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 140, Col: 55} | |
| 608 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 141, Col: 56} | |
| 609 | 609 | } | |
| 610 | 610 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | |
| 611 | 611 | if templ_7745c5c3_Err != nil { | |
| @@ -618,7 +618,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 618 | 618 | var templ_7745c5c3_Var29 string | |
| 619 | 619 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.State) | |
| 620 | 620 | if templ_7745c5c3_Err != nil { | |
| 621 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 143, Col: 45} | |
| 621 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 144, Col: 46} | |
| 622 | 622 | } | |
| 623 | 623 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | |
| 624 | 624 | if templ_7745c5c3_Err != nil { | |
| @@ -631,7 +631,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 631 | 631 | var templ_7745c5c3_Var30 string | |
| 632 | 632 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(p.State) | |
| 633 | 633 | if templ_7745c5c3_Err != nil { | |
| 634 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 143, Col: 57} | |
| 634 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 144, Col: 58} | |
| 635 | 635 | } | |
| 636 | 636 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | |
| 637 | 637 | if templ_7745c5c3_Err != nil { | |
| @@ -644,7 +644,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 644 | 644 | var templ_7745c5c3_Var31 string | |
| 645 | 645 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.Name) | |
| 646 | 646 | if templ_7745c5c3_Err != nil { | |
| 647 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 146, Col: 20} | |
| 647 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 147, Col: 21} | |
| 648 | 648 | } | |
| 649 | 649 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | |
| 650 | 650 | if templ_7745c5c3_Err != nil { | |
| @@ -658,7 +658,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 658 | 658 | var templ_7745c5c3_Var32 string | |
| 659 | 659 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(" · ") | |
| 660 | 660 | if templ_7745c5c3_Err != nil { | |
| 661 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 14} | |
| 661 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 15} | |
| 662 | 662 | } | |
| 663 | 663 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | |
| 664 | 664 | if templ_7745c5c3_Err != nil { | |
| @@ -671,7 +671,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 671 | 671 | var templ_7745c5c3_Var33 string | |
| 672 | 672 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.WhenAt) | |
| 673 | 673 | if templ_7745c5c3_Err != nil { | |
| 674 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 48} | |
| 674 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 49} | |
| 675 | 675 | } | |
| 676 | 676 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | |
| 677 | 677 | if templ_7745c5c3_Err != nil { | |
| @@ -684,7 +684,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 684 | 684 | var templ_7745c5c3_Var34 string | |
| 685 | 685 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.Abs) | |
| 686 | 686 | if templ_7745c5c3_Err != nil { | |
| 687 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 71} | |
| 687 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 72} | |
| 688 | 688 | } | |
| 689 | 689 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | |
| 690 | 690 | if templ_7745c5c3_Err != nil { | |
| @@ -697,7 +697,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 697 | 697 | var templ_7745c5c3_Var35 string | |
| 698 | 698 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.When) | |
| 699 | 699 | if templ_7745c5c3_Err != nil { | |
| 700 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 89} | |
| 700 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 90} | |
| 701 | 701 | } | |
| 702 | 702 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | |
| 703 | 703 | if templ_7745c5c3_Err != nil { | |
| @@ -720,7 +720,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 720 | 720 | var templ_7745c5c3_Var36 string | |
| 721 | 721 | templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.UpdatedAt) | |
| 722 | 722 | if templ_7745c5c3_Err != nil { | |
| 723 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 153, Col: 37} | |
| 723 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 154, Col: 38} | |
| 724 | 724 | } | |
| 725 | 725 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36) | |
| 726 | 726 | if templ_7745c5c3_Err != nil { | |
| @@ -733,7 +733,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 733 | 733 | var templ_7745c5c3_Var37 string | |
| 734 | 734 | templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(p.Updated) | |
| 735 | 735 | if templ_7745c5c3_Err != nil { | |
| 736 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 153, Col: 51} | |
| 736 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 154, Col: 52} | |
| 737 | 737 | } | |
| 738 | 738 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) | |
| 739 | 739 | if templ_7745c5c3_Err != nil { | |
| @@ -748,26 +748,26 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 748 | 748 | if templ_7745c5c3_Err != nil { | |
| 749 | 749 | return templ_7745c5c3_Err | |
| 750 | 750 | } | |
| 751 | - | if p.Live { | |
| 752 | - | templ_7745c5c3_Err = LiveSlot("mr-notice").Render(ctx, templ_7745c5c3_Buffer) | |
| 751 | + | if p.Desc != "" { | |
| 752 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<article class=\"mr-desc prose\" aria-label=\"description\" hx-ignore>") | |
| 753 | 753 | if templ_7745c5c3_Err != nil { | |
| 754 | 754 | return templ_7745c5c3_Err | |
| 755 | 755 | } | |
| 756 | - | } | |
| 757 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, " ") | |
| 758 | - | if templ_7745c5c3_Err != nil { | |
| 759 | - | return templ_7745c5c3_Err | |
| 760 | - | } | |
| 761 | - | if p.Desc != "" { | |
| 762 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<article class=\"mr-desc prose\" aria-label=\"description\" hx-ignore>") | |
| 756 | + | templ_7745c5c3_Err = templ.Raw(string(p.Desc)).Render(ctx, templ_7745c5c3_Buffer) | |
| 763 | 757 | if templ_7745c5c3_Err != nil { | |
| 764 | 758 | return templ_7745c5c3_Err | |
| 765 | 759 | } | |
| 766 | - | templ_7745c5c3_Err = templ.Raw(string(p.Desc)).Render(ctx, templ_7745c5c3_Buffer) | |
| 760 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</article>") | |
| 767 | 761 | if templ_7745c5c3_Err != nil { | |
| 768 | 762 | return templ_7745c5c3_Err | |
| 769 | 763 | } | |
| 770 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</article>") | |
| 764 | + | } | |
| 765 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>") | |
| 766 | + | if templ_7745c5c3_Err != nil { | |
| 767 | + | return templ_7745c5c3_Err | |
| 768 | + | } | |
| 769 | + | if p.Live { | |
| 770 | + | templ_7745c5c3_Err = LiveSlot("mr-notice").Render(ctx, templ_7745c5c3_Buffer) | |
| 771 | 771 | if templ_7745c5c3_Err != nil { | |
| 772 | 772 | return templ_7745c5c3_Err | |
| 773 | 773 | } | |
| @@ -779,7 +779,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||
| 779 | 779 | var templ_7745c5c3_Var38 string | |
| 780 | 780 | templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Comments))) | |
| 781 | 781 | if templ_7745c5c3_Err != nil { | |
| 782 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 165, Col: 99} | |
| 782 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 167, Col: 99} | |
| 783 | 783 | } | |
| 784 | 784 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) | |
| 785 | 785 | if templ_7745c5c3_Err != nil { | |
| @@ -854,7 +854,7 @@ func issueRail(p IssuePage) templ.Component { | |||
| 854 | 854 | var templ_7745c5c3_Var40 templ.SafeURL | |
| 855 | 855 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Transition.Action)) | |
| 856 | 856 | if templ_7745c5c3_Err != nil { | |
| 857 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 182, Col: 66} | |
| 857 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 184, Col: 66} | |
| 858 | 858 | } | |
| 859 | 859 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | |
| 860 | 860 | if templ_7745c5c3_Err != nil { | |
| @@ -867,7 +867,7 @@ func issueRail(p IssuePage) templ.Component { | |||
| 867 | 867 | var templ_7745c5c3_Var41 string | |
| 868 | 868 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(p.Transition.Label) | |
| 869 | 869 | if templ_7745c5c3_Err != nil { | |
| 870 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 183, Col: 58} | |
| 870 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 185, Col: 58} | |
| 871 | 871 | } | |
| 872 | 872 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | |
| 873 | 873 | if templ_7745c5c3_Err != nil { | |
| @@ -585,14 +585,14 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 585 | }() | 585 | }() | ||
| 586 | } | 586 | } | ||
| 587 | ctx = templ.InitializeContext(ctx) | 587 | ctx = templ.InitializeContext(ctx) | ||
| 588 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<header class=\"show mr-h\"><h1><span class=\"mute\">") | 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<section class=\"mr-card\"><header class=\"show mr-h\"><h1><span class=\"mute\">") |
| 589 | if templ_7745c5c3_Err != nil { | 589 | if templ_7745c5c3_Err != nil { | ||
| 590 | return templ_7745c5c3_Err | 590 | return templ_7745c5c3_Err | ||
| 591 | } | 591 | } | ||
| 592 | var templ_7745c5c3_Var27 string | 592 | var templ_7745c5c3_Var27 string | ||
| 593 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Number) | 593 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Number) | ||
| 594 | if templ_7745c5c3_Err != nil { | 594 | if templ_7745c5c3_Err != nil { | ||
| 595 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 140, Col: 36} | 595 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 141, Col: 37} |
| 596 | } | 596 | } | ||
| 597 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | 597 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | ||
| 598 | if templ_7745c5c3_Err != nil { | 598 | if templ_7745c5c3_Err != nil { | ||
| @@ -605,7 +605,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 605 | var templ_7745c5c3_Var28 string | 605 | var templ_7745c5c3_Var28 string | ||
| 606 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | 606 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | ||
| 607 | if templ_7745c5c3_Err != nil { | 607 | if templ_7745c5c3_Err != nil { | ||
| 608 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 140, Col: 55} | 608 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 141, Col: 56} |
| 609 | } | 609 | } | ||
| 610 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | 610 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | ||
| 611 | if templ_7745c5c3_Err != nil { | 611 | if templ_7745c5c3_Err != nil { | ||
| @@ -618,7 +618,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 618 | var templ_7745c5c3_Var29 string | 618 | var templ_7745c5c3_Var29 string | ||
| 619 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.State) | 619 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.State) | ||
| 620 | if templ_7745c5c3_Err != nil { | 620 | if templ_7745c5c3_Err != nil { | ||
| 621 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 143, Col: 45} | 621 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 144, Col: 46} |
| 622 | } | 622 | } | ||
| 623 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | 623 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | ||
| 624 | if templ_7745c5c3_Err != nil { | 624 | if templ_7745c5c3_Err != nil { | ||
| @@ -631,7 +631,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 631 | var templ_7745c5c3_Var30 string | 631 | var templ_7745c5c3_Var30 string | ||
| 632 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(p.State) | 632 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(p.State) | ||
| 633 | if templ_7745c5c3_Err != nil { | 633 | if templ_7745c5c3_Err != nil { | ||
| 634 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 143, Col: 57} | 634 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 144, Col: 58} |
| 635 | } | 635 | } | ||
| 636 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | 636 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | ||
| 637 | if templ_7745c5c3_Err != nil { | 637 | if templ_7745c5c3_Err != nil { | ||
| @@ -644,7 +644,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 644 | var templ_7745c5c3_Var31 string | 644 | var templ_7745c5c3_Var31 string | ||
| 645 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.Name) | 645 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.Name) | ||
| 646 | if templ_7745c5c3_Err != nil { | 646 | if templ_7745c5c3_Err != nil { | ||
| 647 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 146, Col: 20} | 647 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 147, Col: 21} |
| 648 | } | 648 | } | ||
| 649 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | 649 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | ||
| 650 | if templ_7745c5c3_Err != nil { | 650 | if templ_7745c5c3_Err != nil { | ||
| @@ -658,7 +658,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 658 | var templ_7745c5c3_Var32 string | 658 | var templ_7745c5c3_Var32 string | ||
| 659 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(" · ") | 659 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(" · ") | ||
| 660 | if templ_7745c5c3_Err != nil { | 660 | if templ_7745c5c3_Err != nil { | ||
| 661 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 14} | 661 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 15} |
| 662 | } | 662 | } | ||
| 663 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | 663 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | ||
| 664 | if templ_7745c5c3_Err != nil { | 664 | if templ_7745c5c3_Err != nil { | ||
| @@ -671,7 +671,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 671 | var templ_7745c5c3_Var33 string | 671 | var templ_7745c5c3_Var33 string | ||
| 672 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.WhenAt) | 672 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.WhenAt) | ||
| 673 | if templ_7745c5c3_Err != nil { | 673 | if templ_7745c5c3_Err != nil { | ||
| 674 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 48} | 674 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 49} |
| 675 | } | 675 | } | ||
| 676 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | 676 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | ||
| 677 | if templ_7745c5c3_Err != nil { | 677 | if templ_7745c5c3_Err != nil { | ||
| @@ -684,7 +684,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 684 | var templ_7745c5c3_Var34 string | 684 | var templ_7745c5c3_Var34 string | ||
| 685 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.Abs) | 685 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Opened.Abs) | ||
| 686 | if templ_7745c5c3_Err != nil { | 686 | if templ_7745c5c3_Err != nil { | ||
| 687 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 71} | 687 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 72} |
| 688 | } | 688 | } | ||
| 689 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | 689 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | ||
| 690 | if templ_7745c5c3_Err != nil { | 690 | if templ_7745c5c3_Err != nil { | ||
| @@ -697,7 +697,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 697 | var templ_7745c5c3_Var35 string | 697 | var templ_7745c5c3_Var35 string | ||
| 698 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.When) | 698 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(p.Opened.When) | ||
| 699 | if templ_7745c5c3_Err != nil { | 699 | if templ_7745c5c3_Err != nil { | ||
| 700 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 148, Col: 89} | 700 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 149, Col: 90} |
| 701 | } | 701 | } | ||
| 702 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | 702 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | ||
| 703 | if templ_7745c5c3_Err != nil { | 703 | if templ_7745c5c3_Err != nil { | ||
| @@ -720,7 +720,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 720 | var templ_7745c5c3_Var36 string | 720 | var templ_7745c5c3_Var36 string | ||
| 721 | templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.UpdatedAt) | 721 | templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.UpdatedAt) | ||
| 722 | if templ_7745c5c3_Err != nil { | 722 | if templ_7745c5c3_Err != nil { | ||
| 723 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 153, Col: 37} | 723 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 154, Col: 38} |
| 724 | } | 724 | } | ||
| 725 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36) | 725 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36) | ||
| 726 | if templ_7745c5c3_Err != nil { | 726 | if templ_7745c5c3_Err != nil { | ||
| @@ -733,7 +733,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 733 | var templ_7745c5c3_Var37 string | 733 | var templ_7745c5c3_Var37 string | ||
| 734 | templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(p.Updated) | 734 | templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(p.Updated) | ||
| 735 | if templ_7745c5c3_Err != nil { | 735 | if templ_7745c5c3_Err != nil { | ||
| 736 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 153, Col: 51} | 736 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 154, Col: 52} |
| 737 | } | 737 | } | ||
| 738 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) | 738 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) | ||
| 739 | if templ_7745c5c3_Err != nil { | 739 | if templ_7745c5c3_Err != nil { | ||
| @@ -748,26 +748,26 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 748 | if templ_7745c5c3_Err != nil { | 748 | if templ_7745c5c3_Err != nil { | ||
| 749 | return templ_7745c5c3_Err | 749 | return templ_7745c5c3_Err | ||
| 750 | } | 750 | } | ||
| 751 | - | if p.Live { | 751 | + | if p.Desc != "" { |
| 752 | - | templ_7745c5c3_Err = LiveSlot("mr-notice").Render(ctx, templ_7745c5c3_Buffer) | 752 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<article class=\"mr-desc prose\" aria-label=\"description\" hx-ignore>") |
| 753 | if templ_7745c5c3_Err != nil { | 753 | if templ_7745c5c3_Err != nil { | ||
| 754 | return templ_7745c5c3_Err | 754 | return templ_7745c5c3_Err | ||
| 755 | } | 755 | } | ||
| 756 | - | } | 756 | + | templ_7745c5c3_Err = templ.Raw(string(p.Desc)).Render(ctx, templ_7745c5c3_Buffer) |
| 757 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, " ") | |||
| 758 | - | if templ_7745c5c3_Err != nil { | |||
| 759 | - | return templ_7745c5c3_Err | |||
| 760 | - | } | |||
| 761 | - | if p.Desc != "" { | |||
| 762 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<article class=\"mr-desc prose\" aria-label=\"description\" hx-ignore>") | |||
| 763 | if templ_7745c5c3_Err != nil { | 757 | if templ_7745c5c3_Err != nil { | ||
| 764 | return templ_7745c5c3_Err | 758 | return templ_7745c5c3_Err | ||
| 765 | } | 759 | } | ||
| 766 | - | templ_7745c5c3_Err = templ.Raw(string(p.Desc)).Render(ctx, templ_7745c5c3_Buffer) | 760 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</article>") |
| 767 | if templ_7745c5c3_Err != nil { | 761 | if templ_7745c5c3_Err != nil { | ||
| 768 | return templ_7745c5c3_Err | 762 | return templ_7745c5c3_Err | ||
| 769 | } | 763 | } | ||
| 770 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</article>") | 764 | + | } |
| 765 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>") | |||
| 766 | + | if templ_7745c5c3_Err != nil { | |||
| 767 | + | return templ_7745c5c3_Err | |||
| 768 | + | } | |||
| 769 | + | if p.Live { | |||
| 770 | + | templ_7745c5c3_Err = LiveSlot("mr-notice").Render(ctx, templ_7745c5c3_Buffer) | |||
| 771 | if templ_7745c5c3_Err != nil { | 771 | if templ_7745c5c3_Err != nil { | ||
| 772 | return templ_7745c5c3_Err | 772 | return templ_7745c5c3_Err | ||
| 773 | } | 773 | } | ||
| @@ -779,7 +779,7 @@ func IssueShow(l Layout, p IssuePage) templ.Component { | |||||
| 779 | var templ_7745c5c3_Var38 string | 779 | var templ_7745c5c3_Var38 string | ||
| 780 | templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Comments))) | 780 | templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Comments))) | ||
| 781 | if templ_7745c5c3_Err != nil { | 781 | if templ_7745c5c3_Err != nil { | ||
| 782 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 165, Col: 99} | 782 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 167, Col: 99} |
| 783 | } | 783 | } | ||
| 784 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) | 784 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) | ||
| 785 | if templ_7745c5c3_Err != nil { | 785 | if templ_7745c5c3_Err != nil { | ||
| @@ -854,7 +854,7 @@ func issueRail(p IssuePage) templ.Component { | |||||
| 854 | var templ_7745c5c3_Var40 templ.SafeURL | 854 | var templ_7745c5c3_Var40 templ.SafeURL | ||
| 855 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Transition.Action)) | 855 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Transition.Action)) | ||
| 856 | if templ_7745c5c3_Err != nil { | 856 | if templ_7745c5c3_Err != nil { | ||
| 857 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 182, Col: 66} | 857 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 184, Col: 66} |
| 858 | } | 858 | } | ||
| 859 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | 859 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | ||
| 860 | if templ_7745c5c3_Err != nil { | 860 | if templ_7745c5c3_Err != nil { | ||
| @@ -867,7 +867,7 @@ func issueRail(p IssuePage) templ.Component { | |||||
| 867 | var templ_7745c5c3_Var41 string | 867 | var templ_7745c5c3_Var41 string | ||
| 868 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(p.Transition.Label) | 868 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(p.Transition.Label) | ||
| 869 | if templ_7745c5c3_Err != nil { | 869 | if templ_7745c5c3_Err != nil { | ||
| 870 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 183, Col: 58} | 870 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/issues.templ`, Line: 185, Col: 58} |
| 871 | } | 871 | } | ||
| 872 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | 872 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | ||
| 873 | if templ_7745c5c3_Err != nil { | 873 | if templ_7745c5c3_Err != nil { | ||
M internal/adapters/web/keys.templ +1 -0
| @@ -32,6 +32,7 @@ templ Keys() { | |||
| 32 | 32 | k-split from:body -> ermite.layout('split'); | |
| 33 | 33 | k-unified from:body -> ermite.layout('unified'); | |
| 34 | 34 | k-collapse from:body -> ermite.collapse(); | |
| 35 | + | k-wide from:body -> window.ermiteWide.toggle(); | |
| 35 | 36 | k-expand from:body -> ermite.expandFile(); | |
| 36 | 37 | k-expandAll from:body -> ermite.expandAll(); | |
| 37 | 38 | k-permalink from:body -> ermite.permalink(); | |
| @@ -32,6 +32,7 @@ templ Keys() { | |||||
| 32 | k-split from:body -> ermite.layout('split'); | 32 | k-split from:body -> ermite.layout('split'); | ||
| 33 | k-unified from:body -> ermite.layout('unified'); | 33 | k-unified from:body -> ermite.layout('unified'); | ||
| 34 | k-collapse from:body -> ermite.collapse(); | 34 | k-collapse from:body -> ermite.collapse(); | ||
| 35 | + | k-wide from:body -> window.ermiteWide.toggle(); | |||
| 35 | k-expand from:body -> ermite.expandFile(); | 36 | k-expand from:body -> ermite.expandFile(); | ||
| 36 | k-expandAll from:body -> ermite.expandAll(); | 37 | k-expandAll from:body -> ermite.expandAll(); | ||
| 37 | k-permalink from:body -> ermite.permalink(); | 38 | k-permalink from:body -> ermite.permalink(); | ||
M internal/adapters/web/keys_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/mr.templ +39 -39
| @@ -109,41 +109,43 @@ templ mrListRail(p MRListPage) { | |||
| 109 | 109 | // form, the checks, the settings, the commits and the files. | |
| 110 | 110 | templ MergeRequest(l Layout, p MRPage) { | |
| 111 | 111 | @Shell(l, mrRail(p)) { | |
| 112 | - | <header class="show mr-h"> | |
| 113 | - | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | |
| 114 | - | <dl class="kv show-kv"> | |
| 115 | - | <dt>state</dt> | |
| 116 | - | <dd class="mr-state" data-state={ p.State }> | |
| 117 | - | { p.State } | |
| 118 | - | if p.MergedShort != "" { | |
| 119 | - | { " · " }<a href={ templ.SafeURL(p.MergedHref) }>{ p.MergedShort }</a>{ " · " + p.MergeMode } | |
| 120 | - | } | |
| 121 | - | </dd> | |
| 122 | - | <dt>branch</dt> | |
| 123 | - | <dd> | |
| 124 | - | @branchLink(p.Source, p.SourceHref) | |
| 125 | - | { " → " } | |
| 126 | - | @branchLink(p.Target, p.TargetHref) | |
| 127 | - | </dd> | |
| 128 | - | <dt>opened</dt> | |
| 129 | - | <dd> | |
| 130 | - | { p.Opened.Name } | |
| 131 | - | if p.Opened.WhenAt != "" { | |
| 132 | - | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |
| 112 | + | <section class="mr-card"> | |
| 113 | + | <header class="show mr-h"> | |
| 114 | + | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | |
| 115 | + | <dl class="kv show-kv"> | |
| 116 | + | <dt>state</dt> | |
| 117 | + | <dd class="mr-state" data-state={ p.State }> | |
| 118 | + | { p.State } | |
| 119 | + | if p.MergedShort != "" { | |
| 120 | + | { " · " }<a href={ templ.SafeURL(p.MergedHref) }>{ p.MergedShort }</a>{ " · " + p.MergeMode } | |
| 121 | + | } | |
| 122 | + | </dd> | |
| 123 | + | <dt>branch</dt> | |
| 124 | + | <dd> | |
| 125 | + | @branchLink(p.Source, p.SourceHref) | |
| 126 | + | { " → " } | |
| 127 | + | @branchLink(p.Target, p.TargetHref) | |
| 128 | + | </dd> | |
| 129 | + | <dt>opened</dt> | |
| 130 | + | <dd> | |
| 131 | + | { p.Opened.Name } | |
| 132 | + | if p.Opened.WhenAt != "" { | |
| 133 | + | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |
| 134 | + | } | |
| 135 | + | </dd> | |
| 136 | + | if p.UpdatedAt != "" { | |
| 137 | + | <dt>updated</dt> | |
| 138 | + | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |
| 133 | 139 | } | |
| 134 | - | </dd> | |
| 135 | - | if p.UpdatedAt != "" { | |
| 136 | - | <dt>updated</dt> | |
| 137 | - | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |
| 138 | - | } | |
| 139 | - | </dl> | |
| 140 | - | </header> | |
| 140 | + | </dl> | |
| 141 | + | </header> | |
| 142 | + | if p.Desc != "" { | |
| 143 | + | <article class="mr-desc prose" aria-label="description" hx-ignore> | |
| 144 | + | @templ.Raw(string(p.Desc)) | |
| 145 | + | </article> | |
| 146 | + | } | |
| 147 | + | </section> | |
| 141 | 148 | @LiveSlot("mr-notice") | |
| 142 | - | if p.Desc != "" { | |
| 143 | - | <article class="mr-desc prose" aria-label="description" hx-ignore> | |
| 144 | - | @templ.Raw(string(p.Desc)) | |
| 145 | - | </article> | |
| 146 | - | } | |
| 147 | 149 | if p.Since != nil { | |
| 148 | 150 | <nav class="since" aria-label="Diff range"> | |
| 149 | 151 | <a | |
| @@ -431,9 +433,8 @@ templ mrRail(p MRPage) { | |||
| 431 | 433 | templ mrBar(p MRPage) { | |
| 432 | 434 | <div class="diff-tools mr-bar" role="group" aria-label="Diff view and merge"> | |
| 433 | 435 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 434 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 435 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 436 | - | @collapseButton() | |
| 436 | + | @diffViewMenu(p.DiffView) | |
| 437 | + | @wideButton() | |
| 437 | 438 | <span class="grow"></span> | |
| 438 | 439 | @MRChecksSum(p.ChecksSum) | |
| 439 | 440 | if p.Box != nil { | |
| @@ -562,9 +563,8 @@ templ Compare(l Layout, p ComparePage) { | |||
| 562 | 563 | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 563 | 564 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 564 | 565 | <span class="grow"></span> | |
| 565 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 566 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 567 | - | @collapseButton() | |
| 566 | + | @diffViewMenu(p.DiffView) | |
| 567 | + | @wideButton() | |
| 568 | 568 | </div> | |
| 569 | 569 | if p.Invisible { | |
| 570 | 570 | <p class="notice"> | |
| @@ -109,41 +109,43 @@ templ mrListRail(p MRListPage) { | |||||
| 109 | // form, the checks, the settings, the commits and the files. | 109 | // form, the checks, the settings, the commits and the files. | ||
| 110 | templ MergeRequest(l Layout, p MRPage) { | 110 | templ MergeRequest(l Layout, p MRPage) { | ||
| 111 | @Shell(l, mrRail(p)) { | 111 | @Shell(l, mrRail(p)) { | ||
| 112 | - | <header class="show mr-h"> | 112 | + | <section class="mr-card"> |
| 113 | - | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> | 113 | + | <header class="show mr-h"> |
| 114 | - | <dl class="kv show-kv"> | 114 | + | <h1><span class="mute">{ p.Number }</span> { p.Title }</h1> |
| 115 | - | <dt>state</dt> | 115 | + | <dl class="kv show-kv"> |
| 116 | - | <dd class="mr-state" data-state={ p.State }> | 116 | + | <dt>state</dt> |
| 117 | - | { p.State } | 117 | + | <dd class="mr-state" data-state={ p.State }> |
| 118 | - | if p.MergedShort != "" { | 118 | + | { p.State } |
| 119 | - | { " · " }<a href={ templ.SafeURL(p.MergedHref) }>{ p.MergedShort }</a>{ " · " + p.MergeMode } | 119 | + | if p.MergedShort != "" { |
| 120 | - | } | 120 | + | { " · " }<a href={ templ.SafeURL(p.MergedHref) }>{ p.MergedShort }</a>{ " · " + p.MergeMode } |
| 121 | - | </dd> | 121 | + | } |
| 122 | - | <dt>branch</dt> | 122 | + | </dd> |
| 123 | - | <dd> | 123 | + | <dt>branch</dt> |
| 124 | - | @branchLink(p.Source, p.SourceHref) | 124 | + | <dd> |
| 125 | - | { " → " } | 125 | + | @branchLink(p.Source, p.SourceHref) |
| 126 | - | @branchLink(p.Target, p.TargetHref) | 126 | + | { " → " } |
| 127 | - | </dd> | 127 | + | @branchLink(p.Target, p.TargetHref) |
| 128 | - | <dt>opened</dt> | 128 | + | </dd> |
| 129 | - | <dd> | 129 | + | <dt>opened</dt> |
| 130 | - | { p.Opened.Name } | 130 | + | <dd> |
| 131 | - | if p.Opened.WhenAt != "" { | 131 | + | { p.Opened.Name } |
| 132 | - | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | 132 | + | if p.Opened.WhenAt != "" { |
| 133 | + | { " · " }<time datetime={ p.Opened.WhenAt } title={ p.Opened.Abs }>{ p.Opened.When }</time> | |||
| 134 | + | } | |||
| 135 | + | </dd> | |||
| 136 | + | if p.UpdatedAt != "" { | |||
| 137 | + | <dt>updated</dt> | |||
| 138 | + | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | |||
| 133 | } | 139 | } | ||
| 134 | - | </dd> | 140 | + | </dl> |
| 135 | - | if p.UpdatedAt != "" { | 141 | + | </header> |
| 136 | - | <dt>updated</dt> | 142 | + | if p.Desc != "" { |
| 137 | - | <dd><time datetime={ p.UpdatedAt }>{ p.Updated }</time></dd> | 143 | + | <article class="mr-desc prose" aria-label="description" hx-ignore> |
| 138 | - | } | 144 | + | @templ.Raw(string(p.Desc)) |
| 139 | - | </dl> | 145 | + | </article> |
| 140 | - | </header> | 146 | + | } |
| 147 | + | </section> | |||
| 141 | @LiveSlot("mr-notice") | 148 | @LiveSlot("mr-notice") | ||
| 142 | - | if p.Desc != "" { | |||
| 143 | - | <article class="mr-desc prose" aria-label="description" hx-ignore> | |||
| 144 | - | @templ.Raw(string(p.Desc)) | |||
| 145 | - | </article> | |||
| 146 | - | } | |||
| 147 | if p.Since != nil { | 149 | if p.Since != nil { | ||
| 148 | <nav class="since" aria-label="Diff range"> | 150 | <nav class="since" aria-label="Diff range"> | ||
| 149 | <a | 151 | <a | ||
| @@ -431,9 +433,8 @@ templ mrRail(p MRPage) { | |||||
| 431 | templ mrBar(p MRPage) { | 433 | templ mrBar(p MRPage) { | ||
| 432 | <div class="diff-tools mr-bar" role="group" aria-label="Diff view and merge"> | 434 | <div class="diff-tools mr-bar" role="group" aria-label="Diff view and merge"> | ||
| 433 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 435 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | ||
| 434 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 436 | + | @diffViewMenu(p.DiffView) |
| 435 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 437 | + | @wideButton() |
| 436 | - | @collapseButton() | |||
| 437 | <span class="grow"></span> | 438 | <span class="grow"></span> | ||
| 438 | @MRChecksSum(p.ChecksSum) | 439 | @MRChecksSum(p.ChecksSum) | ||
| 439 | if p.Box != nil { | 440 | if p.Box != nil { | ||
| @@ -562,9 +563,8 @@ templ Compare(l Layout, p ComparePage) { | |||||
| 562 | <div class="diff-tools" role="group" aria-label="Diff view"> | 563 | <div class="diff-tools" role="group" aria-label="Diff view"> | ||
| 563 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 564 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | ||
| 564 | <span class="grow"></span> | 565 | <span class="grow"></span> | ||
| 565 | - | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 566 | + | @diffViewMenu(p.DiffView) |
| 566 | - | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 567 | + | @wideButton() |
| 567 | - | @collapseButton() | |||
| 568 | </div> | 568 | </div> | ||
| 569 | if p.Invisible { | 569 | if p.Invisible { | ||
| 570 | <p class="notice"> | 570 | <p class="notice"> | ||
M internal/adapters/web/mr_templ.go +127 -135 generated file: collapsed
M internal/adapters/web/overlays.templ +1 -0
| @@ -30,6 +30,7 @@ templ OverlayHelp(mr bool) { | |||
| 30 | 30 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | |
| 31 | 31 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | |
| 32 | 32 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | |
| 33 | + | <div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div> | |
| 33 | 34 | <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> | |
| 34 | 35 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | |
| 35 | 36 | if mr { | |
| @@ -30,6 +30,7 @@ templ OverlayHelp(mr bool) { | |||||
| 30 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | 30 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | ||
| 31 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | 31 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | ||
| 32 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | 32 | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | ||
| 33 | + | <div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div> | |||
| 33 | <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> | 34 | <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> | ||
| 34 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | 35 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | ||
| 35 | if mr { | 36 | if mr { | ||
M internal/adapters/web/overlays_templ.go +12 -12 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +3 -0
| @@ -133,6 +133,9 @@ | |||
| 133 | 133 | align-content: start; | |
| 134 | 134 | } | |
| 135 | 135 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | |
| 136 | + | /* the wide diff (w, ermiteWide): a page with a diff takes the whole width | |
| 137 | + | of the screen; the rail goes to the right edge, the code takes the rest */ | |
| 138 | + | html.wide body:has(#diffs) :is(.work, .hdr-top, .tabs, .foot) { max-width: none; } | |
| 136 | 139 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | |
| 137 | 140 | .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); } | |
| 138 | 141 | main > * + * { margin-top: var(--s4); } | |
| @@ -133,6 +133,9 @@ | |||||
| 133 | align-content: start; | 133 | align-content: start; | ||
| 134 | } | 134 | } | ||
| 135 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | 135 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | ||
| 136 | + | /* the wide diff (w, ermiteWide): a page with a diff takes the whole width | |||
| 137 | + | of the screen; the rail goes to the right edge, the code takes the rest */ | |||
| 138 | + | html.wide body:has(#diffs) :is(.work, .hdr-top, .tabs, .foot) { max-width: none; } | |||
| 136 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | 139 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | ||
| 137 | .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); } | 140 | .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); } | ||
| 138 | main > * + * { margin-top: var(--s4); } | 141 | main > * + * { margin-top: var(--s4); } | ||
M internal/adapters/web/static/css/50-code.css +7 -4
| @@ -79,13 +79,16 @@ | |||
| 79 | 79 | & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; } | |
| 80 | 80 | & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); } | |
| 81 | 81 | & .tx { white-space: pre; } | |
| 82 | - | & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; } | |
| 82 | + | /* the hunk headers and the hidden lines stay quiet: a faint band between | |
| 83 | + | the code, so that the eye keeps reading */ | |
| 84 | + | & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); } | |
| 85 | + | & .hunk td { color: color-mix(in srgb, var(--ink-mute) 70%, transparent); padding: 0 1ch; } | |
| 83 | 86 | /* a run of hidden lines: the whole row loads them */ | |
| 84 | - | & .gap td { padding: 0; background: var(--paper-alt); } | |
| 87 | + | & .gap td { padding: 0; } | |
| 85 | 88 | & .gap button { | |
| 86 | 89 | 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); | |
| 90 | + | font: inherit; color: var(--ink-mute); background: none; border: 0; | |
| 91 | + | border-block: 1px dashed color-mix(in srgb, var(--border) 60%, transparent); | |
| 89 | 92 | } | |
| 90 | 93 | & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); } | |
| 91 | 94 | & .gap button:disabled { cursor: progress; opacity: .6; } | |
| @@ -79,13 +79,16 @@ | |||||
| 79 | & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; } | 79 | & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; } | ||
| 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 | + | /* the hunk headers and the hidden lines stay quiet: a faint band between |
| 83 | + | the code, so that the eye keeps reading */ | |||
| 84 | + | & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); } | |||
| 85 | + | & .hunk td { color: color-mix(in srgb, var(--ink-mute) 70%, transparent); padding: 0 1ch; } | |||
| 83 | /* a run of hidden lines: the whole row loads them */ | 86 | /* a run of hidden lines: the whole row loads them */ | ||
| 84 | - | & .gap td { padding: 0; background: var(--paper-alt); } | 87 | + | & .gap td { padding: 0; } |
| 85 | & .gap button { | 88 | & .gap button { | ||
| 86 | display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer; | 89 | display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer; | ||
| 87 | - | font: inherit; color: var(--ink-dim); background: none; border: 0; | 90 | + | font: inherit; color: var(--ink-mute); background: none; border: 0; |
| 88 | - | border-block: 1px dashed var(--border); | 91 | + | border-block: 1px dashed color-mix(in srgb, var(--border) 60%, transparent); |
| 89 | } | 92 | } | ||
| 90 | & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); } | 93 | & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); } | ||
| 91 | & .gap button:disabled { cursor: progress; opacity: .6; } | 94 | & .gap button:disabled { cursor: progress; opacity: .6; } | ||
M internal/adapters/web/static/css/60-mr.css +15 -0
| @@ -1,6 +1,12 @@ | |||
| 1 | 1 | @layer components { | |
| 2 | 2 | /* merge requests: the review page. Hue only where it carries a verdict. */ | |
| 3 | 3 | .mr-h .show-kv { margin-top: 0; } | |
| 4 | + | /* one card: the header (title, facts) a shade lighter, the description below */ | |
| 5 | + | .mr-card { | |
| 6 | + | background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; | |
| 7 | + | & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; } | |
| 8 | + | & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; } | |
| 9 | + | } | |
| 4 | 10 | ||
| 5 | 11 | .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); } | |
| 6 | 12 | .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); } | |
| @@ -49,6 +55,15 @@ | |||
| 49 | 55 | } | |
| 50 | 56 | & .kv { margin: 0; } | |
| 51 | 57 | } | |
| 58 | + | /* the view menu of a diff: the layout and the fold, one row each */ | |
| 59 | + | .vmenu { | |
| 60 | + | & > summary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); } | |
| 61 | + | & .menu-pop { left: 0; right: auto; width: 220px; gap: var(--s1); padding: var(--s2); } | |
| 62 | + | & .menu-pop .btn { width: 100%; justify-content: flex-start; border-color: transparent; background: none; } | |
| 63 | + | & .menu-pop .btn:hover { background: var(--bg-raised); } | |
| 64 | + | & .menu-pop .btn[aria-current="true"] { background: var(--bg-raised); color: var(--ink-hi); } | |
| 65 | + | & .menu-pop .btn kbd { margin-left: auto; } | |
| 66 | + | } | |
| 52 | 67 | .mmenu { | |
| 53 | 68 | & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; } | |
| 54 | 69 | & .d-modes label, & .d-check { | |
| @@ -1,6 +1,12 @@ | |||||
| 1 | @layer components { | 1 | @layer components { | ||
| 2 | /* merge requests: the review page. Hue only where it carries a verdict. */ | 2 | /* merge requests: the review page. Hue only where it carries a verdict. */ | ||
| 3 | .mr-h .show-kv { margin-top: 0; } | 3 | .mr-h .show-kv { margin-top: 0; } | ||
| 4 | + | /* one card: the header (title, facts) a shade lighter, the description below */ | |||
| 5 | + | .mr-card { | |||
| 6 | + | background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; | |||
| 7 | + | & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; } | |||
| 8 | + | & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; } | |||
| 9 | + | } | |||
| 4 | 10 | ||||
| 5 | .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); } | 11 | .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); } | ||
| 6 | .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); } | 12 | .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); } | ||
| @@ -49,6 +55,15 @@ | |||||
| 49 | } | 55 | } | ||
| 50 | & .kv { margin: 0; } | 56 | & .kv { margin: 0; } | ||
| 51 | } | 57 | } | ||
| 58 | + | /* the view menu of a diff: the layout and the fold, one row each */ | |||
| 59 | + | .vmenu { | |||
| 60 | + | & > summary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); } | |||
| 61 | + | & .menu-pop { left: 0; right: auto; width: 220px; gap: var(--s1); padding: var(--s2); } | |||
| 62 | + | & .menu-pop .btn { width: 100%; justify-content: flex-start; border-color: transparent; background: none; } | |||
| 63 | + | & .menu-pop .btn:hover { background: var(--bg-raised); } | |||
| 64 | + | & .menu-pop .btn[aria-current="true"] { background: var(--bg-raised); color: var(--ink-hi); } | |||
| 65 | + | & .menu-pop .btn kbd { margin-left: auto; } | |||
| 66 | + | } | |||
| 52 | .mmenu { | 67 | .mmenu { | ||
| 53 | & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; } | 68 | & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; } | ||
| 54 | & .d-modes label, & .d-check { | 69 | & .d-modes label, & .d-check { | ||
M internal/adapters/web/static/js/ermite.js +30 -1
| @@ -14,6 +14,7 @@ | |||
| 14 | 14 | // 7. The service worker (ermiteInstall). | |
| 15 | 15 | // 8. Chip lists kept on one line, with a … chip (ermiteFit). | |
| 16 | 16 | // 9. The layout of the home page, remembered in the browser (ermiteLayout). | |
| 17 | + | // 10. The wide diff: the page takes the whole width (ermiteWide). | |
| 17 | 18 | // | |
| 18 | 19 | // Preferences are kept by ermiteStore (store.js, loaded first). | |
| 19 | 20 | (() => { | |
| @@ -553,9 +554,30 @@ | |||
| 553 | 554 | return { get: pref.get, set, apply, values }; | |
| 554 | 555 | })(); | |
| 555 | 556 | ||
| 557 | + | // The wide diff: on the pages with a diff, the work area takes the whole | |
| 558 | + | // width of the screen, the rail at the right edge and the code in the rest. | |
| 559 | + | // The choice is kept by ermiteStore ("diff-wide": '1' when wide) and shown | |
| 560 | + | // as html.wide, which the styles follow, and as aria-pressed on the | |
| 561 | + | // buttons [data-wide-toggle]. Another tab's change applies here at once. | |
| 562 | + | window.ermiteWide = (() => { | |
| 563 | + | const pref = window.ermiteStore.pref('diff-wide', { values: ['1', ''], fallback: '' }); | |
| 564 | + | function apply() { | |
| 565 | + | const wide = pref.get() === '1'; | |
| 566 | + | document.documentElement.classList.toggle?.('wide', wide); | |
| 567 | + | for (const b of document.querySelectorAll('[data-wide-toggle]')) b.setAttribute('aria-pressed', String(wide)); | |
| 568 | + | } | |
| 569 | + | function toggle() { | |
| 570 | + | pref.set(pref.get() === '1' ? '' : '1'); | |
| 571 | + | apply(); | |
| 572 | + | } | |
| 573 | + | pref.watch(apply); | |
| 574 | + | apply(); | |
| 575 | + | return { toggle, apply }; | |
| 576 | + | })(); | |
| 577 | + | ||
| 556 | 578 | // 1. Keys. | |
| 557 | 579 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | |
| 558 | - | s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', | |
| 580 | + | s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', | |
| 559 | 581 | // the merge request page (mr.js): the merge menu, mark read | |
| 560 | 582 | M: 'merge', R: 'read' }; | |
| 561 | 583 | // "g t" is the key "t": the tabs have one event each. | |
| @@ -611,6 +633,13 @@ | |||
| 611 | 633 | if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return; | |
| 612 | 634 | e.preventDefault(); | |
| 613 | 635 | ermite.fire(el.dataset.fire); | |
| 636 | + | // A choice in a menu closes it. | |
| 637 | + | const menu = el.closest('details.menu'); | |
| 638 | + | if (menu) menu.open = false; | |
| 639 | + | }); | |
| 640 | + | // A click outside an open menu closes it. | |
| 641 | + | addEventListener('click', (e) => { | |
| 642 | + | for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false; | |
| 614 | 643 | }); | |
| 615 | 644 | for (const a of $$('a[data-fire="refs"]')) { | |
| 616 | 645 | a.setAttribute('aria-haspopup', 'dialog'); | |
| @@ -14,6 +14,7 @@ | |||||
| 14 | // 7. The service worker (ermiteInstall). | 14 | // 7. The service worker (ermiteInstall). | ||
| 15 | // 8. Chip lists kept on one line, with a … chip (ermiteFit). | 15 | // 8. Chip lists kept on one line, with a … chip (ermiteFit). | ||
| 16 | // 9. The layout of the home page, remembered in the browser (ermiteLayout). | 16 | // 9. The layout of the home page, remembered in the browser (ermiteLayout). | ||
| 17 | + | // 10. The wide diff: the page takes the whole width (ermiteWide). | |||
| 17 | // | 18 | // | ||
| 18 | // Preferences are kept by ermiteStore (store.js, loaded first). | 19 | // Preferences are kept by ermiteStore (store.js, loaded first). | ||
| 19 | (() => { | 20 | (() => { | ||
| @@ -553,9 +554,30 @@ | |||||
| 553 | return { get: pref.get, set, apply, values }; | 554 | return { get: pref.get, set, apply, values }; | ||
| 554 | })(); | 555 | })(); | ||
| 555 | 556 | ||||
| 557 | + | // The wide diff: on the pages with a diff, the work area takes the whole | |||
| 558 | + | // width of the screen, the rail at the right edge and the code in the rest. | |||
| 559 | + | // The choice is kept by ermiteStore ("diff-wide": '1' when wide) and shown | |||
| 560 | + | // as html.wide, which the styles follow, and as aria-pressed on the | |||
| 561 | + | // buttons [data-wide-toggle]. Another tab's change applies here at once. | |||
| 562 | + | window.ermiteWide = (() => { | |||
| 563 | + | const pref = window.ermiteStore.pref('diff-wide', { values: ['1', ''], fallback: '' }); | |||
| 564 | + | function apply() { | |||
| 565 | + | const wide = pref.get() === '1'; | |||
| 566 | + | document.documentElement.classList.toggle?.('wide', wide); | |||
| 567 | + | for (const b of document.querySelectorAll('[data-wide-toggle]')) b.setAttribute('aria-pressed', String(wide)); | |||
| 568 | + | } | |||
| 569 | + | function toggle() { | |||
| 570 | + | pref.set(pref.get() === '1' ? '' : '1'); | |||
| 571 | + | apply(); | |||
| 572 | + | } | |||
| 573 | + | pref.watch(apply); | |||
| 574 | + | apply(); | |||
| 575 | + | return { toggle, apply }; | |||
| 576 | + | })(); | |||
| 577 | + | ||||
| 556 | // 1. Keys. | 578 | // 1. Keys. | ||
| 557 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | 579 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | ||
| 558 | - | s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', | 580 | + | s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', |
| 559 | // the merge request page (mr.js): the merge menu, mark read | 581 | // the merge request page (mr.js): the merge menu, mark read | ||
| 560 | M: 'merge', R: 'read' }; | 582 | M: 'merge', R: 'read' }; | ||
| 561 | // "g t" is the key "t": the tabs have one event each. | 583 | // "g t" is the key "t": the tabs have one event each. | ||
| @@ -611,6 +633,13 @@ | |||||
| 611 | if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return; | 633 | if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return; | ||
| 612 | e.preventDefault(); | 634 | e.preventDefault(); | ||
| 613 | ermite.fire(el.dataset.fire); | 635 | ermite.fire(el.dataset.fire); | ||
| 636 | + | // A choice in a menu closes it. | |||
| 637 | + | const menu = el.closest('details.menu'); | |||
| 638 | + | if (menu) menu.open = false; | |||
| 639 | + | }); | |||
| 640 | + | // A click outside an open menu closes it. | |||
| 641 | + | addEventListener('click', (e) => { | |||
| 642 | + | for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false; | |||
| 614 | }); | 643 | }); | ||
| 615 | for (const a of $$('a[data-fire="refs"]')) { | 644 | for (const a of $$('a[data-fire="refs"]')) { | ||
| 616 | a.setAttribute('aria-haspopup', 'dialog'); | 645 | a.setAttribute('aria-haspopup', 'dialog'); | ||
M internal/adapters/web/static/js/mr.js +0 -4
| @@ -311,10 +311,6 @@ window.ermiteMR = (() => { | |||
| 311 | 311 | try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ } | |
| 312 | 312 | if (next) { e.preventDefault(); location.assign(next); } | |
| 313 | 313 | }); | |
| 314 | - | // A click outside an open menu closes it. | |
| 315 | - | addEventListener('click', (e) => { | |
| 316 | - | for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false; | |
| 317 | - | }); | |
| 318 | 314 | } | |
| 319 | 315 | ||
| 320 | 316 | return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile }; | |
| @@ -311,10 +311,6 @@ window.ermiteMR = (() => { | |||||
| 311 | try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ } | 311 | try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ } | ||
| 312 | if (next) { e.preventDefault(); location.assign(next); } | 312 | if (next) { e.preventDefault(); location.assign(next); } | ||
| 313 | }); | 313 | }); | ||
| 314 | - | // A click outside an open menu closes it. | |||
| 315 | - | addEventListener('click', (e) => { | |||
| 316 | - | for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false; | |||
| 317 | - | }); | |||
| 318 | } | 314 | } | ||
| 319 | 315 | ||||
| 320 | return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile }; | 316 | return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile }; | ||