feat(web): move the inbox button of the home page next to the logo, with a mailbox icon and a darker count badge
- commit
- 4fc291e7aec41ccf4f7b95936bd72ed5a7b060da
- author
- brice · · 2026-10-19 15:37 UTC
- parent
- 5f6d2c6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
9 files · +545 -539
M internal/adapters/web/home.go +2 -3
| @@ -121,11 +121,10 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in | |||
| 121 | 121 | p.Sorts = append(p.Sorts, sl) | |
| 122 | 122 | } | |
| 123 | 123 | p.Rows = s.homeRows(ctx, repos, now) | |
| 124 | + | l := s.base() | |
| 124 | 125 | if n, show := s.inboxCount(ctx); show { | |
| 125 | - | p.Inbox = &InboxButton{Href: inboxPath, Count: n} | |
| 126 | + | l.Inbox = &InboxButton{Href: inboxPath, Count: n} | |
| 126 | 127 | } | |
| 127 | - | ||
| 128 | - | l := s.base() | |
| 129 | 128 | l.Title = s.instance | |
| 130 | 129 | l.Live, l.LiveURL = true, liveURL("home", "") | |
| 131 | 130 | l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...) | |
| @@ -121,11 +121,10 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in | |||||
| 121 | p.Sorts = append(p.Sorts, sl) | 121 | p.Sorts = append(p.Sorts, sl) | ||
| 122 | } | 122 | } | ||
| 123 | p.Rows = s.homeRows(ctx, repos, now) | 123 | p.Rows = s.homeRows(ctx, repos, now) | ||
| 124 | + | l := s.base() | |||
| 124 | if n, show := s.inboxCount(ctx); show { | 125 | if n, show := s.inboxCount(ctx); show { | ||
| 125 | - | p.Inbox = &InboxButton{Href: inboxPath, Count: n} | 126 | + | l.Inbox = &InboxButton{Href: inboxPath, Count: n} |
| 126 | } | 127 | } | ||
| 127 | - | ||||
| 128 | - | l := s.base() | |||
| 129 | l.Title = s.instance | 128 | l.Title = s.instance | ||
| 130 | l.Live, l.LiveURL = true, liveURL("home", "") | 129 | l.Live, l.LiveURL = true, liveURL("home", "") | ||
| 131 | l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...) | 130 | l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...) | ||
M internal/adapters/web/pages.templ +0 -8
| @@ -9,14 +9,6 @@ templ Home(l Layout, p HomePage) { | |||
| 9 | 9 | <div class="home" data-layout="cards"> | |
| 10 | 10 | <section class="home-repos" aria-label="Repositories"> | |
| 11 | 11 | <div class="home-bar"> | |
| 12 | - | if p.Inbox != nil { | |
| 13 | - | <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox"> | |
| 14 | - | Inbox | |
| 15 | - | if p.Inbox.Count >= 0 { | |
| 16 | - | <span class="count">{ itoa(p.Inbox.Count) }</span> | |
| 17 | - | } | |
| 18 | - | </a> | |
| 19 | - | } | |
| 20 | 12 | <div class="filter"> | |
| 21 | 13 | <label for="home-filter" class="sr">filter</label> | |
| 22 | 14 | <input | |
| @@ -9,14 +9,6 @@ templ Home(l Layout, p HomePage) { | |||||
| 9 | <div class="home" data-layout="cards"> | 9 | <div class="home" data-layout="cards"> | ||
| 10 | <section class="home-repos" aria-label="Repositories"> | 10 | <section class="home-repos" aria-label="Repositories"> | ||
| 11 | <div class="home-bar"> | 11 | <div class="home-bar"> | ||
| 12 | - | if p.Inbox != nil { | |||
| 13 | - | <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox"> | |||
| 14 | - | Inbox | |||
| 15 | - | if p.Inbox.Count >= 0 { | |||
| 16 | - | <span class="count">{ itoa(p.Inbox.Count) }</span> | |||
| 17 | - | } | |||
| 18 | - | </a> | |||
| 19 | - | } | |||
| 20 | <div class="filter"> | 12 | <div class="filter"> | ||
| 21 | <label for="home-filter" class="sr">filter</label> | 13 | <label for="home-filter" class="sr">filter</label> | ||
| 22 | <input | 14 | <input | ||
M internal/adapters/web/pages_templ.go +363 -409 generated file: collapsed
M internal/adapters/web/shell.templ +13 -0
| @@ -80,6 +80,19 @@ templ Header(l Layout) { | |||
| 80 | 80 | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 81 | 81 | <span class="brand-mark" aria-hidden="true"></span> | |
| 82 | 82 | </a> | |
| 83 | + | if l.Inbox != nil { | |
| 84 | + | <a | |
| 85 | + | class="btn inbox-btn" | |
| 86 | + | href={ templ.SafeURL(l.Inbox.Href) } | |
| 87 | + | title="inbox" | |
| 88 | + | > | |
| 89 | + | <span class="ico i-mailbox" aria-hidden="true"></span> | |
| 90 | + | Inbox | |
| 91 | + | if l.Inbox.Count >= 0 { | |
| 92 | + | <span class="count">{ itoa(l.Inbox.Count) }</span> | |
| 93 | + | } | |
| 94 | + | </a> | |
| 95 | + | } | |
| 83 | 96 | if len(l.Crumbs) > 0 { | |
| 84 | 97 | <nav class="crumbs" aria-label="Path"> | |
| 85 | 98 | <ol> | |
| @@ -80,6 +80,19 @@ templ Header(l Layout) { | |||||
| 80 | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | 80 | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | ||
| 81 | <span class="brand-mark" aria-hidden="true"></span> | 81 | <span class="brand-mark" aria-hidden="true"></span> | ||
| 82 | </a> | 82 | </a> | ||
| 83 | + | if l.Inbox != nil { | |||
| 84 | + | <a | |||
| 85 | + | class="btn inbox-btn" | |||
| 86 | + | href={ templ.SafeURL(l.Inbox.Href) } | |||
| 87 | + | title="inbox" | |||
| 88 | + | > | |||
| 89 | + | <span class="ico i-mailbox" aria-hidden="true"></span> | |||
| 90 | + | Inbox | |||
| 91 | + | if l.Inbox.Count >= 0 { | |||
| 92 | + | <span class="count">{ itoa(l.Inbox.Count) }</span> | |||
| 93 | + | } | |||
| 94 | + | </a> | |||
| 95 | + | } | |||
| 83 | if len(l.Crumbs) > 0 { | 96 | if len(l.Crumbs) > 0 { | ||
| 84 | <nav class="crumbs" aria-label="Path"> | 97 | <nav class="crumbs" aria-label="Path"> | ||
| 85 | <ol> | 98 | <ol> | ||
M internal/adapters/web/shell_templ.go +157 -115 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +6 -0
| @@ -29,6 +29,7 @@ | |||
| 29 | 29 | .i-cards { --i: url(icons/cards.svg); } | |
| 30 | 30 | .i-split { --i: url(icons/split.svg); } | |
| 31 | 31 | .i-compact { --i: url(icons/compact.svg); } | |
| 32 | + | .i-mailbox { --i: url(icons/mailbox.svg); } | |
| 32 | 33 | ||
| 33 | 34 | .pill { | |
| 34 | 35 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | |
| @@ -77,6 +78,11 @@ | |||
| 77 | 78 | } | |
| 78 | 79 | /* the hermit by his fire: a grey picture in its own rounded square */ | |
| 79 | 80 | .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; } | |
| 81 | + | /* the inbox, right of the logo on the home page: its number in a darker badge */ | |
| 82 | + | .inbox-btn { | |
| 83 | + | flex: none; | |
| 84 | + | & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); } | |
| 85 | + | } | |
| 80 | 86 | .crumbs { | |
| 81 | 87 | min-width: 0; flex: 1 1 auto; overflow: hidden; | |
| 82 | 88 | & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); } | |
| @@ -29,6 +29,7 @@ | |||||
| 29 | .i-cards { --i: url(icons/cards.svg); } | 29 | .i-cards { --i: url(icons/cards.svg); } | ||
| 30 | .i-split { --i: url(icons/split.svg); } | 30 | .i-split { --i: url(icons/split.svg); } | ||
| 31 | .i-compact { --i: url(icons/compact.svg); } | 31 | .i-compact { --i: url(icons/compact.svg); } | ||
| 32 | + | .i-mailbox { --i: url(icons/mailbox.svg); } | |||
| 32 | 33 | ||||
| 33 | .pill { | 34 | .pill { | ||
| 34 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | 35 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | ||
| @@ -77,6 +78,11 @@ | |||||
| 77 | } | 78 | } | ||
| 78 | /* the hermit by his fire: a grey picture in its own rounded square */ | 79 | /* the hermit by his fire: a grey picture in its own rounded square */ | ||
| 79 | .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; } | 80 | .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; } | ||
| 81 | + | /* the inbox, right of the logo on the home page: its number in a darker badge */ | |||
| 82 | + | .inbox-btn { | |||
| 83 | + | flex: none; | |||
| 84 | + | & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); } | |||
| 85 | + | } | |||
| 80 | .crumbs { | 86 | .crumbs { | ||
| 81 | min-width: 0; flex: 1 1 auto; overflow: hidden; | 87 | min-width: 0; flex: 1 1 auto; overflow: hidden; | ||
| 82 | & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); } | 88 | & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); } | ||
M internal/adapters/web/static/css/60-mr.css +0 -1
| @@ -222,7 +222,6 @@ | |||
| 222 | 222 | & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); } | |
| 223 | 223 | & .since { align-self: flex-start; max-width: 100%; } | |
| 224 | 224 | } | |
| 225 | - | .inbox-btn { gap: var(--s2); } | |
| 226 | 225 | ||
| 227 | 226 | @media (max-width: 700px) { | |
| 228 | 227 | .list.mrs .c-who { display: none; } | |
| @@ -222,7 +222,6 @@ | |||||
| 222 | & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); } | 222 | & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); } | ||
| 223 | & .since { align-self: flex-start; max-width: 100%; } | 223 | & .since { align-self: flex-start; max-width: 100%; } | ||
| 224 | } | 224 | } | ||
| 225 | - | .inbox-btn { gap: var(--s2); } | |||
| 226 | 225 | ||||
| 227 | @media (max-width: 700px) { | 226 | @media (max-width: 700px) { | ||
| 228 | .list.mrs .c-who { display: none; } | 227 | .list.mrs .c-who { display: none; } | ||
A internal/adapters/web/static/icons/mailbox.svg +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg> | |
| @@ -0,0 +1,1 @@ | |||||
| 1 | + | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg> | |||
M internal/adapters/web/view.go +3 -3
| @@ -71,6 +71,9 @@ type Layout struct { | |||
| 71 | 71 | MR bool | |
| 72 | 72 | // IssueForm loads the script of the public issue form (issue-form.js). | |
| 73 | 73 | IssueForm bool | |
| 74 | + | // Inbox is the button to the owner's inbox, next to the logo; set on the | |
| 75 | + | // home page only, for the signed-in owner. | |
| 76 | + | Inbox *InboxButton | |
| 74 | 77 | } | |
| 75 | 78 | ||
| 76 | 79 | // Link is a plain label + href pair. | |
| @@ -137,9 +140,6 @@ type SortLink struct { | |||
| 137 | 140 | type HomePage struct { | |
| 138 | 141 | Sorts []SortLink | |
| 139 | 142 | Rows []HomeRow | |
| 140 | - | // Inbox is the button to the owner's inbox; nil unless the owner is signed | |
| 141 | - | // in. | |
| 142 | - | Inbox *InboxButton | |
| 143 | 143 | } | |
| 144 | 144 | ||
| 145 | 145 | // InboxButton is the link to the inbox with the number of open items; Count is | |
| @@ -71,6 +71,9 @@ type Layout struct { | |||||
| 71 | MR bool | 71 | MR bool | ||
| 72 | // IssueForm loads the script of the public issue form (issue-form.js). | 72 | // IssueForm loads the script of the public issue form (issue-form.js). | ||
| 73 | IssueForm bool | 73 | IssueForm bool | ||
| 74 | + | // Inbox is the button to the owner's inbox, next to the logo; set on the | |||
| 75 | + | // home page only, for the signed-in owner. | |||
| 76 | + | Inbox *InboxButton | |||
| 74 | } | 77 | } | ||
| 75 | 78 | ||||
| 76 | // Link is a plain label + href pair. | 79 | // Link is a plain label + href pair. | ||
| @@ -137,9 +140,6 @@ type SortLink struct { | |||||
| 137 | type HomePage struct { | 140 | type HomePage struct { | ||
| 138 | Sorts []SortLink | 141 | Sorts []SortLink | ||
| 139 | Rows []HomeRow | 142 | Rows []HomeRow | ||
| 140 | - | // Inbox is the button to the owner's inbox; nil unless the owner is signed | |||
| 141 | - | // in. | |||
| 142 | - | Inbox *InboxButton | |||
| 143 | } | 143 | } | ||
| 144 | 144 | ||||
| 145 | // InboxButton is the link to the inbox with the number of open items; Count is | 145 | // InboxButton is the link to the inbox with the number of open items; Count is | ||