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 generated file: collapsed
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
| @@ -51,7 +51,7 @@ func OverlayHelp(mr bool) templ.Component { | |||
| 51 | 51 | if templ_7745c5c3_Err != nil { | |
| 52 | 52 | return templ_7745c5c3_Err | |
| 53 | 53 | } | |
| 54 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " branch or tag</dd></div><div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests, issues</dd></div><div><dt><kbd>g h</kbd></dt><dd>home</dd></div><div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div><div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div><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><div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>") | |
| 54 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " branch or tag</dd></div><div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests, issues</dd></div><div><dt><kbd>g h</kbd></dt><dd>home</dd></div><div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div><div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div><div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div><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><div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>") | |
| 55 | 55 | if templ_7745c5c3_Err != nil { | |
| 56 | 56 | return templ_7745c5c3_Err | |
| 57 | 57 | } | |
| @@ -99,7 +99,7 @@ func OverlayRefs(l Layout) templ.Component { | |||
| 99 | 99 | var templ_7745c5c3_Var4 string | |
| 100 | 100 | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Heads))) | |
| 101 | 101 | if templ_7745c5c3_Err != nil { | |
| 102 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 63, Col: 58} | |
| 102 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 64, Col: 58} | |
| 103 | 103 | } | |
| 104 | 104 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | |
| 105 | 105 | if templ_7745c5c3_Err != nil { | |
| @@ -122,7 +122,7 @@ func OverlayRefs(l Layout) templ.Component { | |||
| 122 | 122 | var templ_7745c5c3_Var5 string | |
| 123 | 123 | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Tags))) | |
| 124 | 124 | if templ_7745c5c3_Err != nil { | |
| 125 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 71, Col: 53} | |
| 125 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 72, Col: 53} | |
| 126 | 126 | } | |
| 127 | 127 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | |
| 128 | 128 | if templ_7745c5c3_Err != nil { | |
| @@ -175,7 +175,7 @@ func refItem(r RefItem) templ.Component { | |||
| 175 | 175 | var templ_7745c5c3_Var7 templ.SafeURL | |
| 176 | 176 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | |
| 177 | 177 | if templ_7745c5c3_Err != nil { | |
| 178 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 86, Col: 31} | |
| 178 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 87, Col: 31} | |
| 179 | 179 | } | |
| 180 | 180 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | |
| 181 | 181 | if templ_7745c5c3_Err != nil { | |
| @@ -213,7 +213,7 @@ func refItem(r RefItem) templ.Component { | |||
| 213 | 213 | var templ_7745c5c3_Var8 string | |
| 214 | 214 | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | |
| 215 | 215 | if templ_7745c5c3_Err != nil { | |
| 216 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 97, Col: 18} | |
| 216 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 98, Col: 18} | |
| 217 | 217 | } | |
| 218 | 218 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | |
| 219 | 219 | if templ_7745c5c3_Err != nil { | |
| @@ -262,7 +262,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 262 | 262 | var templ_7745c5c3_Var10 string | |
| 263 | 263 | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(i)) | |
| 264 | 264 | if templ_7745c5c3_Err != nil { | |
| 265 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 30} | |
| 265 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 30} | |
| 266 | 266 | } | |
| 267 | 267 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | |
| 268 | 268 | if templ_7745c5c3_Err != nil { | |
| @@ -275,7 +275,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 275 | 275 | var templ_7745c5c3_Var11 string | |
| 276 | 276 | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatBool(i == 0)) | |
| 277 | 277 | if templ_7745c5c3_Err != nil { | |
| 278 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 89} | |
| 278 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 89} | |
| 279 | 279 | } | |
| 280 | 280 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | |
| 281 | 281 | if templ_7745c5c3_Err != nil { | |
| @@ -288,7 +288,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 288 | 288 | var templ_7745c5c3_Var12 string | |
| 289 | 289 | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(strings.ToLower(c.Kind + " " + c.Label)) | |
| 290 | 290 | if templ_7745c5c3_Err != nil { | |
| 291 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 140} | |
| 291 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 140} | |
| 292 | 292 | } | |
| 293 | 293 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | |
| 294 | 294 | if templ_7745c5c3_Err != nil { | |
| @@ -311,7 +311,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 311 | 311 | var templ_7745c5c3_Var13 templ.SafeURL | |
| 312 | 312 | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | |
| 313 | 313 | if templ_7745c5c3_Err != nil { | |
| 314 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 198} | |
| 314 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 198} | |
| 315 | 315 | } | |
| 316 | 316 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) | |
| 317 | 317 | if templ_7745c5c3_Err != nil { | |
| @@ -324,7 +324,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 324 | 324 | var templ_7745c5c3_Var14 string | |
| 325 | 325 | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 326 | 326 | if templ_7745c5c3_Err != nil { | |
| 327 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 242} | |
| 327 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 242} | |
| 328 | 328 | } | |
| 329 | 329 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) | |
| 330 | 330 | if templ_7745c5c3_Err != nil { | |
| @@ -337,7 +337,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 337 | 337 | var templ_7745c5c3_Var15 string | |
| 338 | 338 | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(c.Kind) | |
| 339 | 339 | if templ_7745c5c3_Err != nil { | |
| 340 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 275} | |
| 340 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 275} | |
| 341 | 341 | } | |
| 342 | 342 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) | |
| 343 | 343 | if templ_7745c5c3_Err != nil { | |
| @@ -355,7 +355,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||
| 355 | 355 | var templ_7745c5c3_Var16 string | |
| 356 | 356 | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(len(items))) | |
| 357 | 357 | if templ_7745c5c3_Err != nil { | |
| 358 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 128, Col: 38} | |
| 358 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 129, Col: 38} | |
| 359 | 359 | } | |
| 360 | 360 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | |
| 361 | 361 | if templ_7745c5c3_Err != nil { | |
| @@ -51,7 +51,7 @@ func OverlayHelp(mr bool) templ.Component { | |||||
| 51 | if templ_7745c5c3_Err != nil { | 51 | if templ_7745c5c3_Err != nil { | ||
| 52 | return templ_7745c5c3_Err | 52 | return templ_7745c5c3_Err | ||
| 53 | } | 53 | } | ||
| 54 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " branch or tag</dd></div><div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests, issues</dd></div><div><dt><kbd>g h</kbd></dt><dd>home</dd></div><div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div><div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div><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><div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>") | 54 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " branch or tag</dd></div><div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd> <kbd>i</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests, issues</dd></div><div><dt><kbd>g h</kbd></dt><dd>home</dd></div><div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div><div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div><div><dt><kbd>w</kbd></dt><dd>diff: use the whole width of the screen, or not</dd></div><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><div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div>") |
| 55 | if templ_7745c5c3_Err != nil { | 55 | if templ_7745c5c3_Err != nil { | ||
| 56 | return templ_7745c5c3_Err | 56 | return templ_7745c5c3_Err | ||
| 57 | } | 57 | } | ||
| @@ -99,7 +99,7 @@ func OverlayRefs(l Layout) templ.Component { | |||||
| 99 | var templ_7745c5c3_Var4 string | 99 | var templ_7745c5c3_Var4 string | ||
| 100 | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Heads))) | 100 | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Heads))) | ||
| 101 | if templ_7745c5c3_Err != nil { | 101 | if templ_7745c5c3_Err != nil { | ||
| 102 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 63, Col: 58} | 102 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 64, Col: 58} |
| 103 | } | 103 | } | ||
| 104 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | 104 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | ||
| 105 | if templ_7745c5c3_Err != nil { | 105 | if templ_7745c5c3_Err != nil { | ||
| @@ -122,7 +122,7 @@ func OverlayRefs(l Layout) templ.Component { | |||||
| 122 | var templ_7745c5c3_Var5 string | 122 | var templ_7745c5c3_Var5 string | ||
| 123 | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Tags))) | 123 | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(l.Tags))) | ||
| 124 | if templ_7745c5c3_Err != nil { | 124 | if templ_7745c5c3_Err != nil { | ||
| 125 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 71, Col: 53} | 125 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 72, Col: 53} |
| 126 | } | 126 | } | ||
| 127 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | 127 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | ||
| 128 | if templ_7745c5c3_Err != nil { | 128 | if templ_7745c5c3_Err != nil { | ||
| @@ -175,7 +175,7 @@ func refItem(r RefItem) templ.Component { | |||||
| 175 | var templ_7745c5c3_Var7 templ.SafeURL | 175 | var templ_7745c5c3_Var7 templ.SafeURL | ||
| 176 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | 176 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | ||
| 177 | if templ_7745c5c3_Err != nil { | 177 | if templ_7745c5c3_Err != nil { | ||
| 178 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 86, Col: 31} | 178 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 87, Col: 31} |
| 179 | } | 179 | } | ||
| 180 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | 180 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | ||
| 181 | if templ_7745c5c3_Err != nil { | 181 | if templ_7745c5c3_Err != nil { | ||
| @@ -213,7 +213,7 @@ func refItem(r RefItem) templ.Component { | |||||
| 213 | var templ_7745c5c3_Var8 string | 213 | var templ_7745c5c3_Var8 string | ||
| 214 | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | 214 | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | ||
| 215 | if templ_7745c5c3_Err != nil { | 215 | if templ_7745c5c3_Err != nil { | ||
| 216 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 97, Col: 18} | 216 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 98, Col: 18} |
| 217 | } | 217 | } | ||
| 218 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | 218 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | ||
| 219 | if templ_7745c5c3_Err != nil { | 219 | if templ_7745c5c3_Err != nil { | ||
| @@ -262,7 +262,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 262 | var templ_7745c5c3_Var10 string | 262 | var templ_7745c5c3_Var10 string | ||
| 263 | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(i)) | 263 | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(i)) | ||
| 264 | if templ_7745c5c3_Err != nil { | 264 | if templ_7745c5c3_Err != nil { | ||
| 265 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 30} | 265 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 30} |
| 266 | } | 266 | } | ||
| 267 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | 267 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | ||
| 268 | if templ_7745c5c3_Err != nil { | 268 | if templ_7745c5c3_Err != nil { | ||
| @@ -275,7 +275,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 275 | var templ_7745c5c3_Var11 string | 275 | var templ_7745c5c3_Var11 string | ||
| 276 | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatBool(i == 0)) | 276 | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatBool(i == 0)) | ||
| 277 | if templ_7745c5c3_Err != nil { | 277 | if templ_7745c5c3_Err != nil { | ||
| 278 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 89} | 278 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 89} |
| 279 | } | 279 | } | ||
| 280 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | 280 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | ||
| 281 | if templ_7745c5c3_Err != nil { | 281 | if templ_7745c5c3_Err != nil { | ||
| @@ -288,7 +288,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 288 | var templ_7745c5c3_Var12 string | 288 | var templ_7745c5c3_Var12 string | ||
| 289 | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(strings.ToLower(c.Kind + " " + c.Label)) | 289 | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(strings.ToLower(c.Kind + " " + c.Label)) | ||
| 290 | if templ_7745c5c3_Err != nil { | 290 | if templ_7745c5c3_Err != nil { | ||
| 291 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 140} | 291 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 140} |
| 292 | } | 292 | } | ||
| 293 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | 293 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | ||
| 294 | if templ_7745c5c3_Err != nil { | 294 | if templ_7745c5c3_Err != nil { | ||
| @@ -311,7 +311,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 311 | var templ_7745c5c3_Var13 templ.SafeURL | 311 | var templ_7745c5c3_Var13 templ.SafeURL | ||
| 312 | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | 312 | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | ||
| 313 | if templ_7745c5c3_Err != nil { | 313 | if templ_7745c5c3_Err != nil { | ||
| 314 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 198} | 314 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 198} |
| 315 | } | 315 | } | ||
| 316 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) | 316 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) | ||
| 317 | if templ_7745c5c3_Err != nil { | 317 | if templ_7745c5c3_Err != nil { | ||
| @@ -324,7 +324,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 324 | var templ_7745c5c3_Var14 string | 324 | var templ_7745c5c3_Var14 string | ||
| 325 | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | 325 | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | ||
| 326 | if templ_7745c5c3_Err != nil { | 326 | if templ_7745c5c3_Err != nil { | ||
| 327 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 242} | 327 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 242} |
| 328 | } | 328 | } | ||
| 329 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) | 329 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) | ||
| 330 | if templ_7745c5c3_Err != nil { | 330 | if templ_7745c5c3_Err != nil { | ||
| @@ -337,7 +337,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 337 | var templ_7745c5c3_Var15 string | 337 | var templ_7745c5c3_Var15 string | ||
| 338 | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(c.Kind) | 338 | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(c.Kind) | ||
| 339 | if templ_7745c5c3_Err != nil { | 339 | if templ_7745c5c3_Err != nil { | ||
| 340 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 126, Col: 275} | 340 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 127, Col: 275} |
| 341 | } | 341 | } | ||
| 342 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) | 342 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) | ||
| 343 | if templ_7745c5c3_Err != nil { | 343 | if templ_7745c5c3_Err != nil { | ||
| @@ -355,7 +355,7 @@ func OverlayCmd(items []CmdItem) templ.Component { | |||||
| 355 | var templ_7745c5c3_Var16 string | 355 | var templ_7745c5c3_Var16 string | ||
| 356 | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(len(items))) | 356 | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue("cmd-o" + itoa(len(items))) | ||
| 357 | if templ_7745c5c3_Err != nil { | 357 | if templ_7745c5c3_Err != nil { | ||
| 358 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 128, Col: 38} | 358 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/overlays.templ`, Line: 129, Col: 38} |
| 359 | } | 359 | } | ||
| 360 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | 360 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | ||
| 361 | if templ_7745c5c3_Err != nil { | 361 | if templ_7745c5c3_Err != nil { | ||
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 }; | ||