feat(web): UI polish, auto sign-in from the link, installable app (#46)
- commit
- ab8e17813f0cb8c0a644ebf460cdb8a21db87fb5
- author
- brice · · 2026-10-17 14:02 UTC
- committer
- bnema · · 2026-10-17 14:02 UTC
- parent
- 924eaca
- signature
- SSH Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown - refs
- tag: v0.17.0
66 files · +776 -451
M README.md +2 -2
| @@ -27,7 +27,7 @@ ermite serve | |||
| 27 | 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | |
| 28 | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | |
| 29 | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | |
| 30 | - | | `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | | |
| 30 | + | | `ERMITE_OWNER` | `owner` | owner login: shown in the web header when signed in, named in new passkeys, and the one CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | | |
| 31 | 31 | | `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour | | |
| 32 | 32 | | `ERMITE_SECRETS_KEY` | empty | 32 random bytes in base64 (`head -c 32 /dev/urandom \| base64`) that encrypt CI secrets at rest (AES-256-GCM); keep it in the deployment secret store like `ERMITE_ADMIN_TOKEN`. Without it `ermite secret set` fails and jobs receive no secrets (variables and environments still work). The server refuses to start with another key than the one the stored secrets were sealed with; losing the key loses every secret (set them again), a backup without the key reveals none | | |
| 33 | 33 | | `ERMITE_CI_ARTIFACTS` | `1` | `0` turns the CI artifacts off: no artifact service, and artifact actions fail as on a GitHub Enterprise Server without artifacts v4 | | |
| @@ -168,7 +168,7 @@ The cache (`actions/cache`, `setup-go`/`setup-node` `cache:`) is served by the r | |||
| 168 | 168 | Two ways in, both for the owner, both giving the same session cookie for 12 hours. Neither works over plain `http` except on localhost: the cookie is `Secure`. | |
| 169 | 169 | ||
| 170 | 170 | - **Passkey** (a phone, a laptop, a security key): open `/-/login` and press "Sign in with a passkey". Nothing to type, no terminal. A passkey is added once from `/-/passkeys` while signed in. | |
| 171 | - | - **Link**: `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming signs the browser in (never longer than the token that asked for the link). It is also how to sign in the first time, before any passkey exists. | |
| 171 | + | - **Link**: `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it signs the browser in (without scripts, click the button on the page) (never longer than the token that asked for the link). It is also how to sign in the first time, before any passkey exists. | |
| 172 | 172 | ||
| 173 | 173 | Passkeys need `ERMITE_PUBLIC_URL` to be `https` (or `http://localhost`) with a **host name, not an IP address**: WebAuthn ties a passkey to that name, and a passkey registered for one name does not work on another. On `http://127.0.0.1:…` the server starts without passkeys (the log says `passkeys disabled`, the routes and links are absent, `ermite version` does not list `passkeys`). Up to 20 passkeys can be registered; each asks for user verification (a fingerprint, a face or a PIN), and the server stores only the public key. A security key that reports a signature counter is checked for cloning; synced passkeys (iCloud, Google, a password manager) report none, so they are not. | |
| 174 | 174 | ||
| @@ -27,7 +27,7 @@ ermite serve | |||||
| 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | ||
| 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | ||
| 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | ||
| 30 | - | | `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | | 30 | + | | `ERMITE_OWNER` | `owner` | owner login: shown in the web header when signed in, named in new passkeys, and the one CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | |
| 31 | | `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour | | 31 | | `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour | | ||
| 32 | | `ERMITE_SECRETS_KEY` | empty | 32 random bytes in base64 (`head -c 32 /dev/urandom \| base64`) that encrypt CI secrets at rest (AES-256-GCM); keep it in the deployment secret store like `ERMITE_ADMIN_TOKEN`. Without it `ermite secret set` fails and jobs receive no secrets (variables and environments still work). The server refuses to start with another key than the one the stored secrets were sealed with; losing the key loses every secret (set them again), a backup without the key reveals none | | 32 | | `ERMITE_SECRETS_KEY` | empty | 32 random bytes in base64 (`head -c 32 /dev/urandom \| base64`) that encrypt CI secrets at rest (AES-256-GCM); keep it in the deployment secret store like `ERMITE_ADMIN_TOKEN`. Without it `ermite secret set` fails and jobs receive no secrets (variables and environments still work). The server refuses to start with another key than the one the stored secrets were sealed with; losing the key loses every secret (set them again), a backup without the key reveals none | | ||
| 33 | | `ERMITE_CI_ARTIFACTS` | `1` | `0` turns the CI artifacts off: no artifact service, and artifact actions fail as on a GitHub Enterprise Server without artifacts v4 | | 33 | | `ERMITE_CI_ARTIFACTS` | `1` | `0` turns the CI artifacts off: no artifact service, and artifact actions fail as on a GitHub Enterprise Server without artifacts v4 | | ||
| @@ -168,7 +168,7 @@ The cache (`actions/cache`, `setup-go`/`setup-node` `cache:`) is served by the r | |||||
| 168 | Two ways in, both for the owner, both giving the same session cookie for 12 hours. Neither works over plain `http` except on localhost: the cookie is `Secure`. | 168 | Two ways in, both for the owner, both giving the same session cookie for 12 hours. Neither works over plain `http` except on localhost: the cookie is `Secure`. | ||
| 169 | 169 | ||||
| 170 | - **Passkey** (a phone, a laptop, a security key): open `/-/login` and press "Sign in with a passkey". Nothing to type, no terminal. A passkey is added once from `/-/passkeys` while signed in. | 170 | - **Passkey** (a phone, a laptop, a security key): open `/-/login` and press "Sign in with a passkey". Nothing to type, no terminal. A passkey is added once from `/-/passkeys` while signed in. | ||
| 171 | - | - **Link**: `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming signs the browser in (never longer than the token that asked for the link). It is also how to sign in the first time, before any passkey exists. | 171 | + | - **Link**: `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it signs the browser in (without scripts, click the button on the page) (never longer than the token that asked for the link). It is also how to sign in the first time, before any passkey exists. |
| 172 | 172 | ||||
| 173 | Passkeys need `ERMITE_PUBLIC_URL` to be `https` (or `http://localhost`) with a **host name, not an IP address**: WebAuthn ties a passkey to that name, and a passkey registered for one name does not work on another. On `http://127.0.0.1:…` the server starts without passkeys (the log says `passkeys disabled`, the routes and links are absent, `ermite version` does not list `passkeys`). Up to 20 passkeys can be registered; each asks for user verification (a fingerprint, a face or a PIN), and the server stores only the public key. A security key that reports a signature counter is checked for cloning; synced passkeys (iCloud, Google, a password manager) report none, so they are not. | 173 | Passkeys need `ERMITE_PUBLIC_URL` to be `https` (or `http://localhost`) with a **host name, not an IP address**: WebAuthn ties a passkey to that name, and a passkey registered for one name does not work on another. On `http://127.0.0.1:…` the server starts without passkeys (the log says `passkeys disabled`, the routes and links are absent, `ermite version` does not list `passkeys`). Up to 20 passkeys can be registered; each asks for user verification (a fingerprint, a face or a PIN), and the server stores only the public key. A security key that reports a signature counter is checked for cloning; synced passkeys (iCloud, Google, a password manager) report none, so they are not. | ||
| 174 | 174 | ||||
M internal/adapters/web/ASSETS.md +2 -1
| @@ -16,7 +16,8 @@ content-hashed names. Nothing is fetched at runtime. | |||
| 16 | 16 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | |
| 17 | 17 | | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` | | |
| 18 | 18 | | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` | | |
| 19 | - | | `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is | written here | | | |
| 19 | + | | `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `user`, `key`, `log-in`, `log-out` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `user-round`, `key-round`, `log-in`, `log-out`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | | | |
| 20 | + | | `static/app/*.png` | icons of the installed web app (manifest, `apple-touch-icon`), rendered from `brand.svg` with resvg; the maskable and Apple icons have the picture in the safe zone on `#181818` | generated here | | | |
| 20 | 21 | | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | | | |
| 21 | 22 | ||
| 22 | 23 | The three htmx files are the exact files the approved mockup was built with. | |
| @@ -16,7 +16,8 @@ content-hashed names. Nothing is fetched at runtime. | |||||
| 16 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | 16 | | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | | | ||
| 17 | | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` | | 17 | | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` | | ||
| 18 | | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` | | 18 | | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` | | ||
| 19 | - | | `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is | written here | | | 19 | + | | `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `user`, `key`, `log-in`, `log-out` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `user-round`, `key-round`, `log-in`, `log-out`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | | |
| 20 | + | | `static/app/*.png` | icons of the installed web app (manifest, `apple-touch-icon`), rendered from `brand.svg` with resvg; the maskable and Apple icons have the picture in the safe zone on `#181818` | generated here | | | |||
| 20 | | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | | | 21 | | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | | | ||
| 21 | 22 | ||||
| 22 | The three htmx files are the exact files the approved mockup was built with. | 23 | The three htmx files are the exact files the approved mockup was built with. | ||
M internal/adapters/web/assets.go +68 -0
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "crypto/sha256" | |
| 7 | 7 | "embed" | |
| 8 | 8 | "encoding/hex" | |
| 9 | + | "encoding/json" | |
| 9 | 10 | "errors" | |
| 10 | 11 | "io/fs" | |
| 11 | 12 | "net/http" | |
| @@ -24,6 +25,9 @@ const StaticPrefix = "/-/static/" | |||
| 24 | 25 | // the content, so they can be cached for ever. | |
| 25 | 26 | type Assets struct { | |
| 26 | 27 | CSS, HTMX, Live, SSE, App, Files, Passkey, Log, Icon string | |
| 28 | + | // Manifest is the web app manifest (install on a phone's home screen), | |
| 29 | + | // AppleIcon the home screen icon iOS reads from a link instead. | |
| 30 | + | Manifest, AppleIcon string | |
| 27 | 31 | // Font is the UI font, preloaded by every page. | |
| 28 | 32 | Font string | |
| 29 | 33 | // Rules is the speculation rules file named by the Speculation-Rules header. | |
| @@ -146,9 +150,73 @@ func loadAssets() (*assetSet, error) { | |||
| 146 | 150 | return nil, err | |
| 147 | 151 | } | |
| 148 | 152 | s.urls.Icon = put("img", "icon.svg", icon, "image/svg+xml") | |
| 153 | + | ||
| 154 | + | if err := s.loadApp(put); err != nil { | |
| 155 | + | return nil, err | |
| 156 | + | } | |
| 149 | 157 | return s, nil | |
| 150 | 158 | } | |
| 151 | 159 | ||
| 160 | + | // appIcons are the PNG icons of the installed app (static/app), in the | |
| 161 | + | // manifest; purpose "maskable" has the picture inside the safe zone on a | |
| 162 | + | // full-bleed background, for launchers that crop to a circle or a squircle. | |
| 163 | + | var appIcons = []struct{ file, sizes, purpose string }{ | |
| 164 | + | {"icon-192.png", "192x192", "any"}, | |
| 165 | + | {"icon-512.png", "512x512", "any"}, | |
| 166 | + | {"icon-maskable-512.png", "512x512", "maskable"}, | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | // webManifest is the shape of the web app manifest: the minimum a browser | |
| 170 | + | // asks for to offer "install" / "add to home screen". | |
| 171 | + | type webManifest struct { | |
| 172 | + | Name string `json:"name"` | |
| 173 | + | ShortName string `json:"short_name"` | |
| 174 | + | StartURL string `json:"start_url"` | |
| 175 | + | Scope string `json:"scope"` | |
| 176 | + | Display string `json:"display"` | |
| 177 | + | BackgroundColor string `json:"background_color"` | |
| 178 | + | ThemeColor string `json:"theme_color"` | |
| 179 | + | Icons []manifestIcon `json:"icons"` | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | type manifestIcon struct { | |
| 183 | + | Src string `json:"src"` | |
| 184 | + | Sizes string `json:"sizes"` | |
| 185 | + | Type string `json:"type"` | |
| 186 | + | Purpose string `json:"purpose"` | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | // appColor is the dark canvas: the splash screen and the bar of the installed | |
| 190 | + | // app (the pages follow the system theme; the manifest takes one colour). | |
| 191 | + | const appColor = "#050505" | |
| 192 | + | ||
| 193 | + | // loadApp adds the app icons and the manifest, which names them by their | |
| 194 | + | // hashed URLs. | |
| 195 | + | func (s *assetSet) loadApp(put func(dir, name string, b []byte, typ string) string) error { | |
| 196 | + | m := webManifest{ | |
| 197 | + | Name: "ermite", ShortName: "ermite", StartURL: "/", Scope: "/", Display: "standalone", | |
| 198 | + | BackgroundColor: appColor, ThemeColor: appColor, | |
| 199 | + | } | |
| 200 | + | for _, ic := range appIcons { | |
| 201 | + | b, err := staticFS.ReadFile("static/app/" + ic.file) | |
| 202 | + | if err != nil { | |
| 203 | + | return err | |
| 204 | + | } | |
| 205 | + | m.Icons = append(m.Icons, manifestIcon{Src: put("app", ic.file, b, "image/png"), Sizes: ic.sizes, Type: "image/png", Purpose: ic.purpose}) | |
| 206 | + | } | |
| 207 | + | apple, err := staticFS.ReadFile("static/app/apple-touch-icon.png") | |
| 208 | + | if err != nil { | |
| 209 | + | return err | |
| 210 | + | } | |
| 211 | + | s.urls.AppleIcon = put("app", "apple-touch-icon.png", apple, "image/png") | |
| 212 | + | b, err := json.Marshal(m) | |
| 213 | + | if err != nil { | |
| 214 | + | return err | |
| 215 | + | } | |
| 216 | + | s.urls.Manifest = put("app", "manifest.webmanifest", b, "application/manifest+json") | |
| 217 | + | return nil | |
| 218 | + | } | |
| 219 | + | ||
| 152 | 220 | // serve answers GET /-/static/<hashed name>. A name that does not match a | |
| 153 | 221 | // current asset is a 404, so a stale hash never serves the wrong content. | |
| 154 | 222 | func (s *assetSet) serve(w http.ResponseWriter, r *http.Request) { | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "crypto/sha256" | 6 | "crypto/sha256" | ||
| 7 | "embed" | 7 | "embed" | ||
| 8 | "encoding/hex" | 8 | "encoding/hex" | ||
| 9 | + | "encoding/json" | |||
| 9 | "errors" | 10 | "errors" | ||
| 10 | "io/fs" | 11 | "io/fs" | ||
| 11 | "net/http" | 12 | "net/http" | ||
| @@ -24,6 +25,9 @@ const StaticPrefix = "/-/static/" | |||||
| 24 | // the content, so they can be cached for ever. | 25 | // the content, so they can be cached for ever. | ||
| 25 | type Assets struct { | 26 | type Assets struct { | ||
| 26 | CSS, HTMX, Live, SSE, App, Files, Passkey, Log, Icon string | 27 | CSS, HTMX, Live, SSE, App, Files, Passkey, Log, Icon string | ||
| 28 | + | // Manifest is the web app manifest (install on a phone's home screen), | |||
| 29 | + | // AppleIcon the home screen icon iOS reads from a link instead. | |||
| 30 | + | Manifest, AppleIcon string | |||
| 27 | // Font is the UI font, preloaded by every page. | 31 | // Font is the UI font, preloaded by every page. | ||
| 28 | Font string | 32 | Font string | ||
| 29 | // Rules is the speculation rules file named by the Speculation-Rules header. | 33 | // Rules is the speculation rules file named by the Speculation-Rules header. | ||
| @@ -146,9 +150,73 @@ func loadAssets() (*assetSet, error) { | |||||
| 146 | return nil, err | 150 | return nil, err | ||
| 147 | } | 151 | } | ||
| 148 | s.urls.Icon = put("img", "icon.svg", icon, "image/svg+xml") | 152 | s.urls.Icon = put("img", "icon.svg", icon, "image/svg+xml") | ||
| 153 | + | ||||
| 154 | + | if err := s.loadApp(put); err != nil { | |||
| 155 | + | return nil, err | |||
| 156 | + | } | |||
| 149 | return s, nil | 157 | return s, nil | ||
| 150 | } | 158 | } | ||
| 151 | 159 | ||||
| 160 | + | // appIcons are the PNG icons of the installed app (static/app), in the | |||
| 161 | + | // manifest; purpose "maskable" has the picture inside the safe zone on a | |||
| 162 | + | // full-bleed background, for launchers that crop to a circle or a squircle. | |||
| 163 | + | var appIcons = []struct{ file, sizes, purpose string }{ | |||
| 164 | + | {"icon-192.png", "192x192", "any"}, | |||
| 165 | + | {"icon-512.png", "512x512", "any"}, | |||
| 166 | + | {"icon-maskable-512.png", "512x512", "maskable"}, | |||
| 167 | + | } | |||
| 168 | + | ||||
| 169 | + | // webManifest is the shape of the web app manifest: the minimum a browser | |||
| 170 | + | // asks for to offer "install" / "add to home screen". | |||
| 171 | + | type webManifest struct { | |||
| 172 | + | Name string `json:"name"` | |||
| 173 | + | ShortName string `json:"short_name"` | |||
| 174 | + | StartURL string `json:"start_url"` | |||
| 175 | + | Scope string `json:"scope"` | |||
| 176 | + | Display string `json:"display"` | |||
| 177 | + | BackgroundColor string `json:"background_color"` | |||
| 178 | + | ThemeColor string `json:"theme_color"` | |||
| 179 | + | Icons []manifestIcon `json:"icons"` | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | type manifestIcon struct { | |||
| 183 | + | Src string `json:"src"` | |||
| 184 | + | Sizes string `json:"sizes"` | |||
| 185 | + | Type string `json:"type"` | |||
| 186 | + | Purpose string `json:"purpose"` | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | // appColor is the dark canvas: the splash screen and the bar of the installed | |||
| 190 | + | // app (the pages follow the system theme; the manifest takes one colour). | |||
| 191 | + | const appColor = "#050505" | |||
| 192 | + | ||||
| 193 | + | // loadApp adds the app icons and the manifest, which names them by their | |||
| 194 | + | // hashed URLs. | |||
| 195 | + | func (s *assetSet) loadApp(put func(dir, name string, b []byte, typ string) string) error { | |||
| 196 | + | m := webManifest{ | |||
| 197 | + | Name: "ermite", ShortName: "ermite", StartURL: "/", Scope: "/", Display: "standalone", | |||
| 198 | + | BackgroundColor: appColor, ThemeColor: appColor, | |||
| 199 | + | } | |||
| 200 | + | for _, ic := range appIcons { | |||
| 201 | + | b, err := staticFS.ReadFile("static/app/" + ic.file) | |||
| 202 | + | if err != nil { | |||
| 203 | + | return err | |||
| 204 | + | } | |||
| 205 | + | m.Icons = append(m.Icons, manifestIcon{Src: put("app", ic.file, b, "image/png"), Sizes: ic.sizes, Type: "image/png", Purpose: ic.purpose}) | |||
| 206 | + | } | |||
| 207 | + | apple, err := staticFS.ReadFile("static/app/apple-touch-icon.png") | |||
| 208 | + | if err != nil { | |||
| 209 | + | return err | |||
| 210 | + | } | |||
| 211 | + | s.urls.AppleIcon = put("app", "apple-touch-icon.png", apple, "image/png") | |||
| 212 | + | b, err := json.Marshal(m) | |||
| 213 | + | if err != nil { | |||
| 214 | + | return err | |||
| 215 | + | } | |||
| 216 | + | s.urls.Manifest = put("app", "manifest.webmanifest", b, "application/manifest+json") | |||
| 217 | + | return nil | |||
| 218 | + | } | |||
| 219 | + | ||||
| 152 | // serve answers GET /-/static/<hashed name>. A name that does not match a | 220 | // serve answers GET /-/static/<hashed name>. A name that does not match a | ||
| 153 | // current asset is a 404, so a stale hash never serves the wrong content. | 221 | // current asset is a 404, so a stale hash never serves the wrong content. | ||
| 154 | func (s *assetSet) serve(w http.ResponseWriter, r *http.Request) { | 222 | func (s *assetSet) serve(w http.ResponseWriter, r *http.Request) { | ||
M internal/adapters/web/fragments.templ +5 -4
| @@ -82,8 +82,9 @@ templ MRNotice(text, href string) { | |||
| 82 | 82 | } | |
| 83 | 83 | ||
| 84 | 84 | // Live is the connection to the event stream, in the hint badge. It shows | |
| 85 | - | // only once a connection was lost: a page that never connected, or that is | |
| 86 | - | // live, says nothing. The events of the stream are fragments that ermiteLive | |
| 85 | + | // only once a connection stays lost for a few seconds (ermiteLive.lost): a | |
| 86 | + | // page that never connected, that is live, or whose stream comes back right | |
| 87 | + | // away (a proxy closing an idle stream) says nothing. The events of the stream are fragments that ermiteLive | |
| 87 | 88 | // (ermite.js) swaps in the page. A page without a stream renders nothing. | |
| 88 | 89 | // | |
| 89 | 90 | // hx-sse retries a lost connection for ever, with a growing delay. A refusal | |
| @@ -97,8 +98,8 @@ templ Live(l Layout) { | |||
| 97 | 98 | hx-sse:connect={ l.LiveURL } | |
| 98 | 99 | hx-swap="none" | |
| 99 | 100 | :text="data.state === 'offline' ? 'live updates paused' : ''" | |
| 100 | - | hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 101 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 101 | + | hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 102 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 102 | 103 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 103 | 104 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 104 | 105 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -82,8 +82,9 @@ templ MRNotice(text, href string) { | |||||
| 82 | } | 82 | } | ||
| 83 | 83 | ||||
| 84 | // Live is the connection to the event stream, in the hint badge. It shows | 84 | // Live is the connection to the event stream, in the hint badge. It shows | ||
| 85 | - | // only once a connection was lost: a page that never connected, or that is | 85 | + | // only once a connection stays lost for a few seconds (ermiteLive.lost): a |
| 86 | - | // live, says nothing. The events of the stream are fragments that ermiteLive | 86 | + | // page that never connected, that is live, or whose stream comes back right |
| 87 | + | // away (a proxy closing an idle stream) says nothing. The events of the stream are fragments that ermiteLive | |||
| 87 | // (ermite.js) swaps in the page. A page without a stream renders nothing. | 88 | // (ermite.js) swaps in the page. A page without a stream renders nothing. | ||
| 88 | // | 89 | // | ||
| 89 | // hx-sse retries a lost connection for ever, with a growing delay. A refusal | 90 | // hx-sse retries a lost connection for ever, with a growing delay. A refusal | ||
| @@ -97,8 +98,8 @@ templ Live(l Layout) { | |||||
| 97 | hx-sse:connect={ l.LiveURL } | 98 | hx-sse:connect={ l.LiveURL } | ||
| 98 | hx-swap="none" | 99 | hx-swap="none" | ||
| 99 | :text="data.state === 'offline' ? 'live updates paused' : ''" | 100 | :text="data.state === 'offline' ? 'live updates paused' : ''" | ||
| 100 | - | hx-on="htmx:sse:after:connection -> data.state = 'live'; | 101 | + | hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 101 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 102 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 102 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 103 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 103 | htmx:sse:before:connection -> ermiteLive.gate(event); | 104 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 104 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 105 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/adapters/web/fragments_templ.go +5 -4 generated file: collapsed
M internal/adapters/web/fragments_test.go +3 -3
| @@ -58,9 +58,9 @@ func TestShellDegradesWithoutScripts(t *testing.T) { | |||
| 58 | 58 | got := renderToString(t, Header(l)) | |
| 59 | 59 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | |
| 60 | 60 | assert.NotContains(t, got, "hx-on:click", "no handler is inlined in the header") | |
| 61 | - | for _, want := range []string{`class="btn search" data-js`, `class="btn icon" data-js data-fire="help"`} { | |
| 62 | - | assert.Contains(t, got, want) | |
| 63 | - | } | |
| 61 | + | assert.Contains(t, got, `class="btn search" data-js`) | |
| 62 | + | assert.NotContains(t, got, `data-fire="help"`, "the help button is in the footer only") | |
| 63 | + | assert.Contains(t, renderToString(t, Footer(l)), `class="foot-keys" data-js data-fire="help"`) | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | 66 | // Data reaches the scripts through data-* attributes, never spliced into an | |
| @@ -58,9 +58,9 @@ func TestShellDegradesWithoutScripts(t *testing.T) { | |||||
| 58 | got := renderToString(t, Header(l)) | 58 | got := renderToString(t, Header(l)) | ||
| 59 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | 59 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | ||
| 60 | assert.NotContains(t, got, "hx-on:click", "no handler is inlined in the header") | 60 | assert.NotContains(t, got, "hx-on:click", "no handler is inlined in the header") | ||
| 61 | - | for _, want := range []string{`class="btn search" data-js`, `class="btn icon" data-js data-fire="help"`} { | 61 | + | assert.Contains(t, got, `class="btn search" data-js`) |
| 62 | - | assert.Contains(t, got, want) | 62 | + | assert.NotContains(t, got, `data-fire="help"`, "the help button is in the footer only") |
| 63 | - | } | 63 | + | assert.Contains(t, renderToString(t, Footer(l)), `class="foot-keys" data-js data-fire="help"`) |
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | // Data reaches the scripts through data-* attributes, never spliced into an | 66 | // Data reaches the scripts through data-* attributes, never spliced into an | ||
A internal/adapters/web/jstest/live.test.mjs +40 -0
| @@ -0,0 +1,40 @@ | |||
| 1 | + | // Tests of the "live updates paused" delay of static/js/ermite.js | |
| 2 | + | // (window.ermiteLive.lost/back) with mocked timers. Only the ermiteLive part of | |
| 3 | + | // the file is evaluated: the rest needs a page. Run with | |
| 4 | + | // node --test 'internal/adapters/web/jstest/*.test.mjs' | |
| 5 | + | import assert from 'node:assert/strict'; | |
| 6 | + | import { readFileSync } from 'node:fs'; | |
| 7 | + | import { test } from 'node:test'; | |
| 8 | + | ||
| 9 | + | const src = readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8'); | |
| 10 | + | const live = src.slice(src.indexOf('window.ermiteLive = ')); | |
| 11 | + | ||
| 12 | + | function setup(t) { | |
| 13 | + | t.mock.timers.enable({ apis: ['setTimeout'] }); | |
| 14 | + | const hint = { dataset: { state: 'connecting' } }; | |
| 15 | + | globalThis.window = {}; | |
| 16 | + | globalThis.document = { querySelector: (sel) => (sel === '.hint-live' ? hint : null) }; | |
| 17 | + | new Function(live)(); | |
| 18 | + | return { live: globalThis.window.ermiteLive, hint }; | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | test('a drop shorter than the delay says nothing', (t) => { | |
| 22 | + | const { live, hint } = setup(t); | |
| 23 | + | live.back(); | |
| 24 | + | live.lost(); | |
| 25 | + | t.mock.timers.tick(4000); | |
| 26 | + | live.back(); | |
| 27 | + | t.mock.timers.tick(10000); | |
| 28 | + | assert.equal(hint.dataset.state, 'live'); | |
| 29 | + | }); | |
| 30 | + | ||
| 31 | + | test('a drop that lasts shows offline, once, until the stream is back', (t) => { | |
| 32 | + | const { live, hint } = setup(t); | |
| 33 | + | live.lost(); | |
| 34 | + | t.mock.timers.tick(3000); | |
| 35 | + | live.lost(); // a second error does not restart the delay | |
| 36 | + | t.mock.timers.tick(2000); | |
| 37 | + | assert.equal(hint.dataset.state, 'offline'); | |
| 38 | + | live.back(); | |
| 39 | + | assert.equal(hint.dataset.state, 'live'); | |
| 40 | + | }); | |
| @@ -0,0 +1,40 @@ | |||||
| 1 | + | // Tests of the "live updates paused" delay of static/js/ermite.js | |||
| 2 | + | // (window.ermiteLive.lost/back) with mocked timers. Only the ermiteLive part of | |||
| 3 | + | // the file is evaluated: the rest needs a page. Run with | |||
| 4 | + | // node --test 'internal/adapters/web/jstest/*.test.mjs' | |||
| 5 | + | import assert from 'node:assert/strict'; | |||
| 6 | + | import { readFileSync } from 'node:fs'; | |||
| 7 | + | import { test } from 'node:test'; | |||
| 8 | + | ||||
| 9 | + | const src = readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8'); | |||
| 10 | + | const live = src.slice(src.indexOf('window.ermiteLive = ')); | |||
| 11 | + | ||||
| 12 | + | function setup(t) { | |||
| 13 | + | t.mock.timers.enable({ apis: ['setTimeout'] }); | |||
| 14 | + | const hint = { dataset: { state: 'connecting' } }; | |||
| 15 | + | globalThis.window = {}; | |||
| 16 | + | globalThis.document = { querySelector: (sel) => (sel === '.hint-live' ? hint : null) }; | |||
| 17 | + | new Function(live)(); | |||
| 18 | + | return { live: globalThis.window.ermiteLive, hint }; | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | test('a drop shorter than the delay says nothing', (t) => { | |||
| 22 | + | const { live, hint } = setup(t); | |||
| 23 | + | live.back(); | |||
| 24 | + | live.lost(); | |||
| 25 | + | t.mock.timers.tick(4000); | |||
| 26 | + | live.back(); | |||
| 27 | + | t.mock.timers.tick(10000); | |||
| 28 | + | assert.equal(hint.dataset.state, 'live'); | |||
| 29 | + | }); | |||
| 30 | + | ||||
| 31 | + | test('a drop that lasts shows offline, once, until the stream is back', (t) => { | |||
| 32 | + | const { live, hint } = setup(t); | |||
| 33 | + | live.lost(); | |||
| 34 | + | t.mock.timers.tick(3000); | |||
| 35 | + | live.lost(); // a second error does not restart the delay | |||
| 36 | + | t.mock.timers.tick(2000); | |||
| 37 | + | assert.equal(hint.dataset.state, 'offline'); | |||
| 38 | + | live.back(); | |||
| 39 | + | assert.equal(hint.dataset.state, 'live'); | |||
| 40 | + | }); | |||
A internal/adapters/web/manifest_test.go +40 -0
| @@ -0,0 +1,40 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package web | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "encoding/json" | |
| 7 | + | "strings" | |
| 8 | + | "testing" | |
| 9 | + | ||
| 10 | + | "github.com/stretchr/testify/assert" | |
| 11 | + | "github.com/stretchr/testify/require" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // The web app manifest makes the site installable: every page links it, and | |
| 15 | + | // its icons are served assets. | |
| 16 | + | func TestWebAppManifest(t *testing.T) { | |
| 17 | + | t.Parallel() | |
| 18 | + | srv := newBareServer(t) | |
| 19 | + | u := srv.assets.urls | |
| 20 | + | man, ok := srv.assets.byName[strings.TrimPrefix(u.Manifest, StaticPrefix)] | |
| 21 | + | require.True(t, ok) | |
| 22 | + | assert.Equal(t, "application/manifest+json", man.typ) | |
| 23 | + | ||
| 24 | + | var m webManifest | |
| 25 | + | require.NoError(t, json.Unmarshal(man.body, &m)) | |
| 26 | + | assert.Equal(t, "standalone", m.Display) | |
| 27 | + | assert.Equal(t, "/", m.StartURL) | |
| 28 | + | require.Len(t, m.Icons, len(appIcons)) | |
| 29 | + | for _, ic := range m.Icons { | |
| 30 | + | a, ok := srv.assets.byName[strings.TrimPrefix(ic.Src, StaticPrefix)] | |
| 31 | + | require.True(t, ok, ic.Src) | |
| 32 | + | assert.Equal(t, "image/png", a.typ) | |
| 33 | + | } | |
| 34 | + | _, ok = srv.assets.byName[strings.TrimPrefix(u.AppleIcon, StaticPrefix)] | |
| 35 | + | assert.True(t, ok) | |
| 36 | + | ||
| 37 | + | page := renderToString(t, Shell(Layout{Assets: u}, nil)) | |
| 38 | + | assert.Contains(t, page, `<link rel="manifest" href="`+u.Manifest+`">`) | |
| 39 | + | assert.Contains(t, page, `<link rel="apple-touch-icon" href="`+u.AppleIcon+`">`) | |
| 40 | + | } | |
| @@ -0,0 +1,40 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package web | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "encoding/json" | |||
| 7 | + | "strings" | |||
| 8 | + | "testing" | |||
| 9 | + | ||||
| 10 | + | "github.com/stretchr/testify/assert" | |||
| 11 | + | "github.com/stretchr/testify/require" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // The web app manifest makes the site installable: every page links it, and | |||
| 15 | + | // its icons are served assets. | |||
| 16 | + | func TestWebAppManifest(t *testing.T) { | |||
| 17 | + | t.Parallel() | |||
| 18 | + | srv := newBareServer(t) | |||
| 19 | + | u := srv.assets.urls | |||
| 20 | + | man, ok := srv.assets.byName[strings.TrimPrefix(u.Manifest, StaticPrefix)] | |||
| 21 | + | require.True(t, ok) | |||
| 22 | + | assert.Equal(t, "application/manifest+json", man.typ) | |||
| 23 | + | ||||
| 24 | + | var m webManifest | |||
| 25 | + | require.NoError(t, json.Unmarshal(man.body, &m)) | |||
| 26 | + | assert.Equal(t, "standalone", m.Display) | |||
| 27 | + | assert.Equal(t, "/", m.StartURL) | |||
| 28 | + | require.Len(t, m.Icons, len(appIcons)) | |||
| 29 | + | for _, ic := range m.Icons { | |||
| 30 | + | a, ok := srv.assets.byName[strings.TrimPrefix(ic.Src, StaticPrefix)] | |||
| 31 | + | require.True(t, ok, ic.Src) | |||
| 32 | + | assert.Equal(t, "image/png", a.typ) | |||
| 33 | + | } | |||
| 34 | + | _, ok = srv.assets.byName[strings.TrimPrefix(u.AppleIcon, StaticPrefix)] | |||
| 35 | + | assert.True(t, ok) | |||
| 36 | + | ||||
| 37 | + | page := renderToString(t, Shell(Layout{Assets: u}, nil)) | |||
| 38 | + | assert.Contains(t, page, `<link rel="manifest" href="`+u.Manifest+`">`) | |||
| 39 | + | assert.Contains(t, page, `<link rel="apple-touch-icon" href="`+u.AppleIcon+`">`) | |||
| 40 | + | } | |||
M internal/adapters/web/pages.templ +6 -3
| @@ -170,8 +170,11 @@ templ State(l Layout, p StatePage) { | |||
| 170 | 170 | } | |
| 171 | 171 | } | |
| 172 | 172 | ||
| 173 | - | // Login asks the owner to confirm a login link. Opening the link consumes | |
| 174 | - | // nothing; only this form does. It is a state page with a button. | |
| 173 | + | // Login signs the browser in with a login link. Opening the link consumes | |
| 174 | + | // nothing; only this form does. ermite.js submits it as soon as a browser | |
| 175 | + | // shows the page (data-autosubmit): link previews and prefetchers run no | |
| 176 | + | // script, so they still cannot use the link. Without scripts the owner | |
| 177 | + | // clicks the button. | |
| 175 | 178 | templ Login(l Layout, p LoginPage) { | |
| 176 | 179 | @Shell(l, nil) { | |
| 177 | 180 | <section class="state" aria-labelledby="state-title"> | |
| @@ -179,7 +182,7 @@ templ Login(l Layout, p LoginPage) { | |||
| 179 | 182 | for _, line := range p.Lines { | |
| 180 | 183 | <p class="dim">{ line }</p> | |
| 181 | 184 | } | |
| 182 | - | <form class="state-act" method="post" action={ templ.SafeURL(p.Action) }> | |
| 185 | + | <form class="state-act" method="post" action={ templ.SafeURL(p.Action) } data-autosubmit> | |
| 183 | 186 | <button type="submit" class="btn primary">{ p.Button }</button> | |
| 184 | 187 | </form> | |
| 185 | 188 | <p class="state-back"><a href="/">Back to home</a></p> | |
| @@ -170,8 +170,11 @@ templ State(l Layout, p StatePage) { | |||||
| 170 | } | 170 | } | ||
| 171 | } | 171 | } | ||
| 172 | 172 | ||||
| 173 | - | // Login asks the owner to confirm a login link. Opening the link consumes | 173 | + | // Login signs the browser in with a login link. Opening the link consumes |
| 174 | - | // nothing; only this form does. It is a state page with a button. | 174 | + | // nothing; only this form does. ermite.js submits it as soon as a browser |
| 175 | + | // shows the page (data-autosubmit): link previews and prefetchers run no | |||
| 176 | + | // script, so they still cannot use the link. Without scripts the owner | |||
| 177 | + | // clicks the button. | |||
| 175 | templ Login(l Layout, p LoginPage) { | 178 | templ Login(l Layout, p LoginPage) { | ||
| 176 | @Shell(l, nil) { | 179 | @Shell(l, nil) { | ||
| 177 | <section class="state" aria-labelledby="state-title"> | 180 | <section class="state" aria-labelledby="state-title"> | ||
| @@ -179,7 +182,7 @@ templ Login(l Layout, p LoginPage) { | |||||
| 179 | for _, line := range p.Lines { | 182 | for _, line := range p.Lines { | ||
| 180 | <p class="dim">{ line }</p> | 183 | <p class="dim">{ line }</p> | ||
| 181 | } | 184 | } | ||
| 182 | - | <form class="state-act" method="post" action={ templ.SafeURL(p.Action) }> | 185 | + | <form class="state-act" method="post" action={ templ.SafeURL(p.Action) } data-autosubmit> |
| 183 | <button type="submit" class="btn primary">{ p.Button }</button> | 186 | <button type="submit" class="btn primary">{ p.Button }</button> | ||
| 184 | </form> | 187 | </form> | ||
| 185 | <p class="state-back"><a href="/">Back to home</a></p> | 188 | <p class="state-back"><a href="/">Back to home</a></p> | ||
M internal/adapters/web/pages_templ.go +26 -23 generated file: collapsed
M internal/adapters/web/passkeys_test.go +6 -4
| @@ -484,19 +484,21 @@ func TestPasskeyLoginPage(t *testing.T) { | |||
| 484 | 484 | } | |
| 485 | 485 | ||
| 486 | 486 | // The header links: "Sign in" for a visitor, "Passkeys" for the owner. | |
| 487 | + | // They are icons with an accessible name. | |
| 487 | 488 | func TestPasskeyHeaderLinks(t *testing.T) { | |
| 488 | 489 | t.Parallel() | |
| 489 | 490 | f := newPasskeyServer(t) | |
| 490 | 491 | rec := f.do(t, req{method: "GET", target: "/-/login"}) | |
| 491 | - | assert.Contains(t, rec.Body.String(), `href="/-/login"`) | |
| 492 | + | assert.Contains(t, rec.Body.String(), `href="/-/login" aria-label="Sign in"`) | |
| 492 | 493 | assert.NotContains(t, rec.Body.String(), `href="/-/passkeys"`) | |
| 493 | 494 | assert.Contains(t, rec.Body.String(), "passkey.", "the script is loaded") | |
| 494 | 495 | ||
| 495 | 496 | f.passkeys.EXPECT().List(mock.Anything, mock.Anything).Return(nil, nil).Once() | |
| 496 | 497 | rec = f.do(t, req{method: "GET", target: "/-/passkeys", cookie: "tok"}) | |
| 497 | - | assert.Contains(t, rec.Body.String(), `href="/-/passkeys"`) | |
| 498 | + | assert.Contains(t, rec.Body.String(), `href="/-/passkeys" aria-label="Passkeys"`) | |
| 498 | 499 | assert.Contains(t, rec.Body.String(), `action="/-/logout"`) | |
| 499 | - | assert.NotContains(t, rec.Body.String(), `>Sign in</a>`) | |
| 500 | + | assert.Contains(t, rec.Body.String(), `aria-label="Sign out"`) | |
| 501 | + | assert.NotContains(t, rec.Body.String(), `aria-label="Sign in"`) | |
| 500 | 502 | } | |
| 501 | 503 | ||
| 502 | 504 | // Without the passkey service there is no route, no link and no script | |
| @@ -564,7 +566,7 @@ func TestPasskeyRoutesLeaveLoginLinksAlone(t *testing.T) { | |||
| 564 | 566 | f := newPasskeyServer(t) | |
| 565 | 567 | rec := f.do(t, req{method: "GET", target: "/-/login/" + nonceA}) | |
| 566 | 568 | assert.Equal(t, http.StatusOK, rec.Code) | |
| 567 | - | assert.Contains(t, rec.Body.String(), "sign in as the owner") | |
| 569 | + | assert.Contains(t, rec.Body.String(), "signing in as the owner") | |
| 568 | 570 | rec = f.do(t, req{method: "POST", target: "/-/login/" + nonceA, origin: forgeOrigin}) | |
| 569 | 571 | assert.Equal(t, http.StatusSeeOther, rec.Code) | |
| 570 | 572 | } | |
| @@ -484,19 +484,21 @@ func TestPasskeyLoginPage(t *testing.T) { | |||||
| 484 | } | 484 | } | ||
| 485 | 485 | ||||
| 486 | // The header links: "Sign in" for a visitor, "Passkeys" for the owner. | 486 | // The header links: "Sign in" for a visitor, "Passkeys" for the owner. | ||
| 487 | + | // They are icons with an accessible name. | |||
| 487 | func TestPasskeyHeaderLinks(t *testing.T) { | 488 | func TestPasskeyHeaderLinks(t *testing.T) { | ||
| 488 | t.Parallel() | 489 | t.Parallel() | ||
| 489 | f := newPasskeyServer(t) | 490 | f := newPasskeyServer(t) | ||
| 490 | rec := f.do(t, req{method: "GET", target: "/-/login"}) | 491 | rec := f.do(t, req{method: "GET", target: "/-/login"}) | ||
| 491 | - | assert.Contains(t, rec.Body.String(), `href="/-/login"`) | 492 | + | assert.Contains(t, rec.Body.String(), `href="/-/login" aria-label="Sign in"`) |
| 492 | assert.NotContains(t, rec.Body.String(), `href="/-/passkeys"`) | 493 | assert.NotContains(t, rec.Body.String(), `href="/-/passkeys"`) | ||
| 493 | assert.Contains(t, rec.Body.String(), "passkey.", "the script is loaded") | 494 | assert.Contains(t, rec.Body.String(), "passkey.", "the script is loaded") | ||
| 494 | 495 | ||||
| 495 | f.passkeys.EXPECT().List(mock.Anything, mock.Anything).Return(nil, nil).Once() | 496 | f.passkeys.EXPECT().List(mock.Anything, mock.Anything).Return(nil, nil).Once() | ||
| 496 | rec = f.do(t, req{method: "GET", target: "/-/passkeys", cookie: "tok"}) | 497 | rec = f.do(t, req{method: "GET", target: "/-/passkeys", cookie: "tok"}) | ||
| 497 | - | assert.Contains(t, rec.Body.String(), `href="/-/passkeys"`) | 498 | + | assert.Contains(t, rec.Body.String(), `href="/-/passkeys" aria-label="Passkeys"`) |
| 498 | assert.Contains(t, rec.Body.String(), `action="/-/logout"`) | 499 | assert.Contains(t, rec.Body.String(), `action="/-/logout"`) | ||
| 499 | - | assert.NotContains(t, rec.Body.String(), `>Sign in</a>`) | 500 | + | assert.Contains(t, rec.Body.String(), `aria-label="Sign out"`) |
| 501 | + | assert.NotContains(t, rec.Body.String(), `aria-label="Sign in"`) | |||
| 500 | } | 502 | } | ||
| 501 | 503 | ||||
| 502 | // Without the passkey service there is no route, no link and no script | 504 | // Without the passkey service there is no route, no link and no script | ||
| @@ -564,7 +566,7 @@ func TestPasskeyRoutesLeaveLoginLinksAlone(t *testing.T) { | |||||
| 564 | f := newPasskeyServer(t) | 566 | f := newPasskeyServer(t) | ||
| 565 | rec := f.do(t, req{method: "GET", target: "/-/login/" + nonceA}) | 567 | rec := f.do(t, req{method: "GET", target: "/-/login/" + nonceA}) | ||
| 566 | assert.Equal(t, http.StatusOK, rec.Code) | 568 | assert.Equal(t, http.StatusOK, rec.Code) | ||
| 567 | - | assert.Contains(t, rec.Body.String(), "sign in as the owner") | 569 | + | assert.Contains(t, rec.Body.String(), "signing in as the owner") |
| 568 | rec = f.do(t, req{method: "POST", target: "/-/login/" + nonceA, origin: forgeOrigin}) | 570 | rec = f.do(t, req{method: "POST", target: "/-/login/" + nonceA, origin: forgeOrigin}) | ||
| 569 | assert.Equal(t, http.StatusSeeOther, rec.Code) | 571 | assert.Equal(t, http.StatusSeeOther, rec.Code) | ||
| 570 | } | 572 | } | ||
M internal/adapters/web/session.go +8 -7
| @@ -23,9 +23,10 @@ import ( | |||
| 23 | 23 | // The owner's session on the web (ADR 0018). | |
| 24 | 24 | // | |
| 25 | 25 | // A browser signs in by opening the one-time link `ermite web login` printed: | |
| 26 | - | // GET /-/login/<nonce> shows a confirmation and consumes nothing, so link | |
| 27 | - | // previews and prefetchers cannot use it; POST /-/login/<nonce> consumes the | |
| 28 | - | // link and sets the cookie. The cookie holds a signed token; every request | |
| 26 | + | // GET /-/login/<nonce> shows a page whose script posts the link at once and | |
| 27 | + | // consumes nothing itself, so link previews and prefetchers, which run no | |
| 28 | + | // script, cannot use it; POST /-/login/<nonce> consumes the link and sets the | |
| 29 | + | // cookie. The cookie holds a signed token; every request | |
| 29 | 30 | // that carries a valid one runs as the owner, every other as anonymous. | |
| 30 | 31 | // | |
| 31 | 32 | // State changes are POST only and pass one gate (ServeHTTP): an Origin equal to | |
| @@ -243,8 +244,8 @@ func (s *Server) secretPage(w http.ResponseWriter, r *http.Request, status int, | |||
| 243 | 244 | s.write(r.Context(), w, r, status, c) | |
| 244 | 245 | } | |
| 245 | 246 | ||
| 246 | - | // loginConfirm is GET /-/login/<nonce>: it shows the confirmation and does not | |
| 247 | - | // use the link. | |
| 247 | + | // loginConfirm is GET /-/login/<nonce>: it shows the page that signs in (its | |
| 248 | + | // script posts the form, see Login) and does not use the link. | |
| 248 | 249 | func (s *Server) loginConfirm(w http.ResponseWriter, r *http.Request) { | |
| 249 | 250 | nonce := r.PathValue("nonce") | |
| 250 | 251 | if !validNonce(nonce) || !s.cfg.Sessions.LoginLinkValid(nonce) { | |
| @@ -253,8 +254,8 @@ func (s *Server) loginConfirm(w http.ResponseWriter, r *http.Request) { | |||
| 253 | 254 | } | |
| 254 | 255 | l := s.stateLayout("sign in", []Crumb{{Label: "sign in", Current: true}}) | |
| 255 | 256 | s.secretPage(w, r, http.StatusOK, Login(l, LoginPage{ | |
| 256 | - | Title: "sign in as the owner", | |
| 257 | - | Lines: []string{"this browser will be signed in for 12 hours.", "only confirm if you ran `ermite web login` just now."}, | |
| 257 | + | Title: "signing in as the owner…", | |
| 258 | + | Lines: []string{"this browser will be signed in for 12 hours.", "if nothing happens, click the button."}, | |
| 258 | 259 | Action: loginPrefix + nonce, | |
| 259 | 260 | Button: "Sign in", | |
| 260 | 261 | })) | |
| @@ -23,9 +23,10 @@ import ( | |||||
| 23 | // The owner's session on the web (ADR 0018). | 23 | // The owner's session on the web (ADR 0018). | ||
| 24 | // | 24 | // | ||
| 25 | // A browser signs in by opening the one-time link `ermite web login` printed: | 25 | // A browser signs in by opening the one-time link `ermite web login` printed: | ||
| 26 | - | // GET /-/login/<nonce> shows a confirmation and consumes nothing, so link | 26 | + | // GET /-/login/<nonce> shows a page whose script posts the link at once and |
| 27 | - | // previews and prefetchers cannot use it; POST /-/login/<nonce> consumes the | 27 | + | // consumes nothing itself, so link previews and prefetchers, which run no |
| 28 | - | // link and sets the cookie. The cookie holds a signed token; every request | 28 | + | // script, cannot use it; POST /-/login/<nonce> consumes the link and sets the |
| 29 | + | // cookie. The cookie holds a signed token; every request | |||
| 29 | // that carries a valid one runs as the owner, every other as anonymous. | 30 | // that carries a valid one runs as the owner, every other as anonymous. | ||
| 30 | // | 31 | // | ||
| 31 | // State changes are POST only and pass one gate (ServeHTTP): an Origin equal to | 32 | // State changes are POST only and pass one gate (ServeHTTP): an Origin equal to | ||
| @@ -243,8 +244,8 @@ func (s *Server) secretPage(w http.ResponseWriter, r *http.Request, status int, | |||||
| 243 | s.write(r.Context(), w, r, status, c) | 244 | s.write(r.Context(), w, r, status, c) | ||
| 244 | } | 245 | } | ||
| 245 | 246 | ||||
| 246 | - | // loginConfirm is GET /-/login/<nonce>: it shows the confirmation and does not | 247 | + | // loginConfirm is GET /-/login/<nonce>: it shows the page that signs in (its |
| 247 | - | // use the link. | 248 | + | // script posts the form, see Login) and does not use the link. |
| 248 | func (s *Server) loginConfirm(w http.ResponseWriter, r *http.Request) { | 249 | func (s *Server) loginConfirm(w http.ResponseWriter, r *http.Request) { | ||
| 249 | nonce := r.PathValue("nonce") | 250 | nonce := r.PathValue("nonce") | ||
| 250 | if !validNonce(nonce) || !s.cfg.Sessions.LoginLinkValid(nonce) { | 251 | if !validNonce(nonce) || !s.cfg.Sessions.LoginLinkValid(nonce) { | ||
| @@ -253,8 +254,8 @@ func (s *Server) loginConfirm(w http.ResponseWriter, r *http.Request) { | |||||
| 253 | } | 254 | } | ||
| 254 | l := s.stateLayout("sign in", []Crumb{{Label: "sign in", Current: true}}) | 255 | l := s.stateLayout("sign in", []Crumb{{Label: "sign in", Current: true}}) | ||
| 255 | s.secretPage(w, r, http.StatusOK, Login(l, LoginPage{ | 256 | s.secretPage(w, r, http.StatusOK, Login(l, LoginPage{ | ||
| 256 | - | Title: "sign in as the owner", | 257 | + | Title: "signing in as the owner…", |
| 257 | - | Lines: []string{"this browser will be signed in for 12 hours.", "only confirm if you ran `ermite web login` just now."}, | 258 | + | Lines: []string{"this browser will be signed in for 12 hours.", "if nothing happens, click the button."}, |
| 258 | Action: loginPrefix + nonce, | 259 | Action: loginPrefix + nonce, | ||
| 259 | Button: "Sign in", | 260 | Button: "Sign in", | ||
| 260 | })) | 261 | })) | ||
M internal/adapters/web/shell.templ +13 -10
| @@ -13,7 +13,12 @@ templ Shell(l Layout, rail templ.Component) { | |||
| 13 | 13 | <meta name="viewport" content="width=device-width, initial-scale=1"/> | |
| 14 | 14 | <meta name="color-scheme" content="light dark"/> | |
| 15 | 15 | <title>{ l.Title }</title> | |
| 16 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"/> | |
| 17 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"/> | |
| 18 | + | <meta name="apple-mobile-web-app-title" content="ermite"/> | |
| 16 | 19 | <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/> | |
| 20 | + | <link rel="apple-touch-icon" href={ templ.SafeURL(l.Assets.AppleIcon) }/> | |
| 21 | + | <link rel="manifest" href={ templ.SafeURL(l.Assets.Manifest) }/> | |
| 17 | 22 | <link rel="preload" href={ templ.SafeURL(l.Assets.Font) } as="font" type="font/woff2" crossorigin/> | |
| 18 | 23 | <link rel="stylesheet" href={ templ.SafeURL(l.Assets.CSS) }/> | |
| 19 | 24 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'/> | |
| @@ -60,14 +65,13 @@ templ Shell(l Layout, rail templ.Component) { | |||
| 60 | 65 | </html> | |
| 61 | 66 | } | |
| 62 | 67 | ||
| 63 | - | // Header is the page header: the instance, the path, the ref switcher, the | |
| 64 | - | // "go to" palette and the owner; inside a repository, the tabs below. | |
| 68 | + | // Header is the page header: the logo (home), the path, the ref switcher, | |
| 69 | + | // the "go to" palette and the owner; inside a repository, the tabs below. | |
| 65 | 70 | templ Header(l Layout) { | |
| 66 | 71 | <header class="hdr"> | |
| 67 | 72 | <div class="hdr-top"> | |
| 68 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 73 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 69 | 74 | <span class="brand-mark" aria-hidden="true"></span> | |
| 70 | - | <span class="brand-name">{ l.Instance }</span> | |
| 71 | 75 | </a> | |
| 72 | 76 | if len(l.Crumbs) > 0 { | |
| 73 | 77 | <nav class="crumbs" aria-label="Path"> | |
| @@ -121,17 +125,16 @@ templ Header(l Layout) { | |||
| 121 | 125 | <span class="search-label">Go to…</span> | |
| 122 | 126 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 123 | 127 | </button> | |
| 124 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 125 | 128 | if SignedIn(ctx) { | |
| 126 | 129 | <form class="owner" method="post" action="/-/logout"> | |
| 127 | - | <span class="owner-badge" title="signed in as the owner">owner</span> | |
| 130 | + | <span class="owner-badge" title="signed in as the owner"><span class="ico i-user" aria-hidden="true"></span>{ l.Owner }</span> | |
| 128 | 131 | if l.Passkeys { | |
| 129 | - | <a class="btn" href="/-/passkeys" title="add or remove the passkeys that sign you in">Passkeys</a> | |
| 132 | + | <a class="btn icon" href="/-/passkeys" aria-label="Passkeys" title="passkeys: add or remove the passkeys that sign you in"><span class="ico i-key" aria-hidden="true"></span></a> | |
| 130 | 133 | } | |
| 131 | - | <button type="submit" class="btn" title="sign out of this browser">Sign out</button> | |
| 134 | + | <button type="submit" class="btn icon" aria-label="Sign out" title="sign out of this browser"><span class="ico i-log-out" aria-hidden="true"></span></button> | |
| 132 | 135 | </form> | |
| 133 | 136 | } else if l.Passkeys { | |
| 134 | - | <a class="btn" href="/-/login" title="sign in with a passkey">Sign in</a> | |
| 137 | + | <a class="btn icon" href="/-/login" aria-label="Sign in" title="sign in with a passkey"><span class="ico i-log-in" aria-hidden="true"></span></a> | |
| 135 | 138 | } | |
| 136 | 139 | </div> | |
| 137 | 140 | </div> | |
| @@ -162,7 +165,7 @@ templ Footer(l Layout) { | |||
| 162 | 165 | <footer class="foot"> | |
| 163 | 166 | <span>ermite { l.Version }</span> | |
| 164 | 167 | <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 165 | - | <span class="foot-keys"><kbd>?</kbd> keyboard shortcuts</span> | |
| 168 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"><kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 166 | 169 | </footer> | |
| 167 | 170 | } | |
| 168 | 171 | ||
| @@ -13,7 +13,12 @@ templ Shell(l Layout, rail templ.Component) { | |||||
| 13 | <meta name="viewport" content="width=device-width, initial-scale=1"/> | 13 | <meta name="viewport" content="width=device-width, initial-scale=1"/> | ||
| 14 | <meta name="color-scheme" content="light dark"/> | 14 | <meta name="color-scheme" content="light dark"/> | ||
| 15 | <title>{ l.Title }</title> | 15 | <title>{ l.Title }</title> | ||
| 16 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"/> | |||
| 17 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"/> | |||
| 18 | + | <meta name="apple-mobile-web-app-title" content="ermite"/> | |||
| 16 | <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/> | 19 | <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/> | ||
| 20 | + | <link rel="apple-touch-icon" href={ templ.SafeURL(l.Assets.AppleIcon) }/> | |||
| 21 | + | <link rel="manifest" href={ templ.SafeURL(l.Assets.Manifest) }/> | |||
| 17 | <link rel="preload" href={ templ.SafeURL(l.Assets.Font) } as="font" type="font/woff2" crossorigin/> | 22 | <link rel="preload" href={ templ.SafeURL(l.Assets.Font) } as="font" type="font/woff2" crossorigin/> | ||
| 18 | <link rel="stylesheet" href={ templ.SafeURL(l.Assets.CSS) }/> | 23 | <link rel="stylesheet" href={ templ.SafeURL(l.Assets.CSS) }/> | ||
| 19 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'/> | 24 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'/> | ||
| @@ -60,14 +65,13 @@ templ Shell(l Layout, rail templ.Component) { | |||||
| 60 | </html> | 65 | </html> | ||
| 61 | } | 66 | } | ||
| 62 | 67 | ||||
| 63 | - | // Header is the page header: the instance, the path, the ref switcher, the | 68 | + | // Header is the page header: the logo (home), the path, the ref switcher, |
| 64 | - | // "go to" palette and the owner; inside a repository, the tabs below. | 69 | + | // the "go to" palette and the owner; inside a repository, the tabs below. |
| 65 | templ Header(l Layout) { | 70 | templ Header(l Layout) { | ||
| 66 | <header class="hdr"> | 71 | <header class="hdr"> | ||
| 67 | <div class="hdr-top"> | 72 | <div class="hdr-top"> | ||
| 68 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 73 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 69 | <span class="brand-mark" aria-hidden="true"></span> | 74 | <span class="brand-mark" aria-hidden="true"></span> | ||
| 70 | - | <span class="brand-name">{ l.Instance }</span> | |||
| 71 | </a> | 75 | </a> | ||
| 72 | if len(l.Crumbs) > 0 { | 76 | if len(l.Crumbs) > 0 { | ||
| 73 | <nav class="crumbs" aria-label="Path"> | 77 | <nav class="crumbs" aria-label="Path"> | ||
| @@ -121,17 +125,16 @@ templ Header(l Layout) { | |||||
| 121 | <span class="search-label">Go to…</span> | 125 | <span class="search-label">Go to…</span> | ||
| 122 | <kbd aria-hidden="true">ctrl k</kbd> | 126 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 123 | </button> | 127 | </button> | ||
| 124 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 125 | if SignedIn(ctx) { | 128 | if SignedIn(ctx) { | ||
| 126 | <form class="owner" method="post" action="/-/logout"> | 129 | <form class="owner" method="post" action="/-/logout"> | ||
| 127 | - | <span class="owner-badge" title="signed in as the owner">owner</span> | 130 | + | <span class="owner-badge" title="signed in as the owner"><span class="ico i-user" aria-hidden="true"></span>{ l.Owner }</span> |
| 128 | if l.Passkeys { | 131 | if l.Passkeys { | ||
| 129 | - | <a class="btn" href="/-/passkeys" title="add or remove the passkeys that sign you in">Passkeys</a> | 132 | + | <a class="btn icon" href="/-/passkeys" aria-label="Passkeys" title="passkeys: add or remove the passkeys that sign you in"><span class="ico i-key" aria-hidden="true"></span></a> |
| 130 | } | 133 | } | ||
| 131 | - | <button type="submit" class="btn" title="sign out of this browser">Sign out</button> | 134 | + | <button type="submit" class="btn icon" aria-label="Sign out" title="sign out of this browser"><span class="ico i-log-out" aria-hidden="true"></span></button> |
| 132 | </form> | 135 | </form> | ||
| 133 | } else if l.Passkeys { | 136 | } else if l.Passkeys { | ||
| 134 | - | <a class="btn" href="/-/login" title="sign in with a passkey">Sign in</a> | 137 | + | <a class="btn icon" href="/-/login" aria-label="Sign in" title="sign in with a passkey"><span class="ico i-log-in" aria-hidden="true"></span></a> |
| 135 | } | 138 | } | ||
| 136 | </div> | 139 | </div> | ||
| 137 | </div> | 140 | </div> | ||
| @@ -162,7 +165,7 @@ templ Footer(l Layout) { | |||||
| 162 | <footer class="foot"> | 165 | <footer class="foot"> | ||
| 163 | <span>ermite { l.Version }</span> | 166 | <span>ermite { l.Version }</span> | ||
| 164 | <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 167 | <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 165 | - | <span class="foot-keys"><kbd>?</kbd> keyboard shortcuts</span> | 168 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"><kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 166 | </footer> | 169 | </footer> | ||
| 167 | } | 170 | } | ||
| 168 | 171 | ||||
M internal/adapters/web/shell_templ.go +210 -184
| @@ -47,79 +47,79 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 47 | 47 | if templ_7745c5c3_Err != nil { | |
| 48 | 48 | return templ_7745c5c3_Err | |
| 49 | 49 | } | |
| 50 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"icon\" type=\"image/svg+xml\" href=\"") | |
| 50 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"") | |
| 51 | 51 | if templ_7745c5c3_Err != nil { | |
| 52 | 52 | return templ_7745c5c3_Err | |
| 53 | 53 | } | |
| 54 | 54 | var templ_7745c5c3_Var3 templ.SafeURL | |
| 55 | 55 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Icon)) | |
| 56 | 56 | if templ_7745c5c3_Err != nil { | |
| 57 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 16, Col: 76} | |
| 57 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 19, Col: 76} | |
| 58 | 58 | } | |
| 59 | 59 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | |
| 60 | 60 | if templ_7745c5c3_Err != nil { | |
| 61 | 61 | return templ_7745c5c3_Err | |
| 62 | 62 | } | |
| 63 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><link rel=\"preload\" href=\"") | |
| 63 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><link rel=\"apple-touch-icon\" href=\"") | |
| 64 | 64 | if templ_7745c5c3_Err != nil { | |
| 65 | 65 | return templ_7745c5c3_Err | |
| 66 | 66 | } | |
| 67 | 67 | var templ_7745c5c3_Var4 templ.SafeURL | |
| 68 | - | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Font)) | |
| 68 | + | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.AppleIcon)) | |
| 69 | 69 | if templ_7745c5c3_Err != nil { | |
| 70 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 17, Col: 58} | |
| 70 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 20, Col: 72} | |
| 71 | 71 | } | |
| 72 | 72 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | |
| 73 | 73 | if templ_7745c5c3_Err != nil { | |
| 74 | 74 | return templ_7745c5c3_Err | |
| 75 | 75 | } | |
| 76 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"stylesheet\" href=\"") | |
| 76 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><link rel=\"manifest\" href=\"") | |
| 77 | 77 | if templ_7745c5c3_Err != nil { | |
| 78 | 78 | return templ_7745c5c3_Err | |
| 79 | 79 | } | |
| 80 | 80 | var templ_7745c5c3_Var5 templ.SafeURL | |
| 81 | - | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.CSS)) | |
| 81 | + | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Manifest)) | |
| 82 | 82 | if templ_7745c5c3_Err != nil { | |
| 83 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 18, Col: 60} | |
| 83 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 21, Col: 63} | |
| 84 | 84 | } | |
| 85 | 85 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | |
| 86 | 86 | if templ_7745c5c3_Err != nil { | |
| 87 | 87 | return templ_7745c5c3_Err | |
| 88 | 88 | } | |
| 89 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta name=\"htmx-config\" content='{\"history\":false,\"includeIndicatorCSS\":false,\"extensions\":\"sse, hx-live\"}'><script src=\"") | |
| 89 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><link rel=\"preload\" href=\"") | |
| 90 | 90 | if templ_7745c5c3_Err != nil { | |
| 91 | 91 | return templ_7745c5c3_Err | |
| 92 | 92 | } | |
| 93 | - | var templ_7745c5c3_Var6 string | |
| 94 | - | templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.HTMX) | |
| 93 | + | var templ_7745c5c3_Var6 templ.SafeURL | |
| 94 | + | templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Font)) | |
| 95 | 95 | if templ_7745c5c3_Err != nil { | |
| 96 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 20, Col: 30} | |
| 96 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 22, Col: 58} | |
| 97 | 97 | } | |
| 98 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6) | |
| 98 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) | |
| 99 | 99 | if templ_7745c5c3_Err != nil { | |
| 100 | 100 | return templ_7745c5c3_Err | |
| 101 | 101 | } | |
| 102 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" defer></script><script src=\"") | |
| 102 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"stylesheet\" href=\"") | |
| 103 | 103 | if templ_7745c5c3_Err != nil { | |
| 104 | 104 | return templ_7745c5c3_Err | |
| 105 | 105 | } | |
| 106 | - | var templ_7745c5c3_Var7 string | |
| 107 | - | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Live) | |
| 106 | + | var templ_7745c5c3_Var7 templ.SafeURL | |
| 107 | + | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.CSS)) | |
| 108 | 108 | if templ_7745c5c3_Err != nil { | |
| 109 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 21, Col: 30} | |
| 109 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 23, Col: 60} | |
| 110 | 110 | } | |
| 111 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7) | |
| 111 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | |
| 112 | 112 | if templ_7745c5c3_Err != nil { | |
| 113 | 113 | return templ_7745c5c3_Err | |
| 114 | 114 | } | |
| 115 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" defer></script><script src=\"") | |
| 115 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><meta name=\"htmx-config\" content='{\"history\":false,\"includeIndicatorCSS\":false,\"extensions\":\"sse, hx-live\"}'><script src=\"") | |
| 116 | 116 | if templ_7745c5c3_Err != nil { | |
| 117 | 117 | return templ_7745c5c3_Err | |
| 118 | 118 | } | |
| 119 | 119 | var templ_7745c5c3_Var8 string | |
| 120 | - | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.SSE) | |
| 120 | + | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.HTMX) | |
| 121 | 121 | if templ_7745c5c3_Err != nil { | |
| 122 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 22, Col: 29} | |
| 122 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 30} | |
| 123 | 123 | } | |
| 124 | 124 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) | |
| 125 | 125 | if templ_7745c5c3_Err != nil { | |
| @@ -130,9 +130,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 130 | 130 | return templ_7745c5c3_Err | |
| 131 | 131 | } | |
| 132 | 132 | var templ_7745c5c3_Var9 string | |
| 133 | - | templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.App) | |
| 133 | + | templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Live) | |
| 134 | 134 | if templ_7745c5c3_Err != nil { | |
| 135 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 23, Col: 29} | |
| 135 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 26, Col: 30} | |
| 136 | 136 | } | |
| 137 | 137 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) | |
| 138 | 138 | if templ_7745c5c3_Err != nil { | |
| @@ -143,9 +143,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 143 | 143 | return templ_7745c5c3_Err | |
| 144 | 144 | } | |
| 145 | 145 | var templ_7745c5c3_Var10 string | |
| 146 | - | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Files) | |
| 146 | + | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.SSE) | |
| 147 | 147 | if templ_7745c5c3_Err != nil { | |
| 148 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 24, Col: 31} | |
| 148 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 27, Col: 29} | |
| 149 | 149 | } | |
| 150 | 150 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | |
| 151 | 151 | if templ_7745c5c3_Err != nil { | |
| @@ -156,9 +156,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 156 | 156 | return templ_7745c5c3_Err | |
| 157 | 157 | } | |
| 158 | 158 | var templ_7745c5c3_Var11 string | |
| 159 | - | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Passkey) | |
| 159 | + | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.App) | |
| 160 | 160 | if templ_7745c5c3_Err != nil { | |
| 161 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 33} | |
| 161 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 29} | |
| 162 | 162 | } | |
| 163 | 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | |
| 164 | 164 | if templ_7745c5c3_Err != nil { | |
| @@ -169,77 +169,103 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 169 | 169 | return templ_7745c5c3_Err | |
| 170 | 170 | } | |
| 171 | 171 | var templ_7745c5c3_Var12 string | |
| 172 | - | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) | |
| 172 | + | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Files) | |
| 173 | 173 | if templ_7745c5c3_Err != nil { | |
| 174 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 26, Col: 29} | |
| 174 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 31} | |
| 175 | 175 | } | |
| 176 | 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | |
| 177 | 177 | if templ_7745c5c3_Err != nil { | |
| 178 | 178 | return templ_7745c5c3_Err | |
| 179 | 179 | } | |
| 180 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" defer></script></head><body data-ref=\"") | |
| 180 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" defer></script><script src=\"") | |
| 181 | 181 | if templ_7745c5c3_Err != nil { | |
| 182 | 182 | return templ_7745c5c3_Err | |
| 183 | 183 | } | |
| 184 | 184 | var templ_7745c5c3_Var13 string | |
| 185 | - | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | |
| 185 | + | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Passkey) | |
| 186 | 186 | if templ_7745c5c3_Err != nil { | |
| 187 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 23} | |
| 187 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 30, Col: 33} | |
| 188 | 188 | } | |
| 189 | 189 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | |
| 190 | 190 | if templ_7745c5c3_Err != nil { | |
| 191 | 191 | return templ_7745c5c3_Err | |
| 192 | 192 | } | |
| 193 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" data-sha=\"") | |
| 193 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" defer></script><script src=\"") | |
| 194 | 194 | if templ_7745c5c3_Err != nil { | |
| 195 | 195 | return templ_7745c5c3_Err | |
| 196 | 196 | } | |
| 197 | 197 | var templ_7745c5c3_Var14 string | |
| 198 | - | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |
| 198 | + | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) | |
| 199 | 199 | if templ_7745c5c3_Err != nil { | |
| 200 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 30, Col: 19} | |
| 200 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 31, Col: 29} | |
| 201 | 201 | } | |
| 202 | 202 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | |
| 203 | 203 | if templ_7745c5c3_Err != nil { | |
| 204 | 204 | return templ_7745c5c3_Err | |
| 205 | 205 | } | |
| 206 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" data-sel=\"\" data-nav=\"\"") | |
| 206 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" defer></script></head><body data-ref=\"") | |
| 207 | + | if templ_7745c5c3_Err != nil { | |
| 208 | + | return templ_7745c5c3_Err | |
| 209 | + | } | |
| 210 | + | var templ_7745c5c3_Var15 string | |
| 211 | + | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | |
| 212 | + | if templ_7745c5c3_Err != nil { | |
| 213 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 23} | |
| 214 | + | } | |
| 215 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) | |
| 216 | + | if templ_7745c5c3_Err != nil { | |
| 217 | + | return templ_7745c5c3_Err | |
| 218 | + | } | |
| 219 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-sha=\"") | |
| 220 | + | if templ_7745c5c3_Err != nil { | |
| 221 | + | return templ_7745c5c3_Err | |
| 222 | + | } | |
| 223 | + | var templ_7745c5c3_Var16 string | |
| 224 | + | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |
| 225 | + | if templ_7745c5c3_Err != nil { | |
| 226 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 35, Col: 19} | |
| 227 | + | } | |
| 228 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | |
| 229 | + | if templ_7745c5c3_Err != nil { | |
| 230 | + | return templ_7745c5c3_Err | |
| 231 | + | } | |
| 232 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" data-sel=\"\" data-nav=\"\"") | |
| 207 | 233 | if templ_7745c5c3_Err != nil { | |
| 208 | 234 | return templ_7745c5c3_Err | |
| 209 | 235 | } | |
| 210 | 236 | if l.PathsURL != "" { | |
| 211 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " data-paths=\"") | |
| 237 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " data-paths=\"") | |
| 212 | 238 | if templ_7745c5c3_Err != nil { | |
| 213 | 239 | return templ_7745c5c3_Err | |
| 214 | 240 | } | |
| 215 | - | var templ_7745c5c3_Var15 string | |
| 216 | - | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | |
| 241 | + | var templ_7745c5c3_Var17 string | |
| 242 | + | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | |
| 217 | 243 | if templ_7745c5c3_Err != nil { | |
| 218 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 27} | |
| 244 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 39, Col: 27} | |
| 219 | 245 | } | |
| 220 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) | |
| 246 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) | |
| 221 | 247 | if templ_7745c5c3_Err != nil { | |
| 222 | 248 | return templ_7745c5c3_Err | |
| 223 | 249 | } | |
| 224 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" data-blob=\"") | |
| 250 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" data-blob=\"") | |
| 225 | 251 | if templ_7745c5c3_Err != nil { | |
| 226 | 252 | return templ_7745c5c3_Err | |
| 227 | 253 | } | |
| 228 | - | var templ_7745c5c3_Var16 string | |
| 229 | - | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | |
| 254 | + | var templ_7745c5c3_Var18 string | |
| 255 | + | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | |
| 230 | 256 | if templ_7745c5c3_Err != nil { | |
| 231 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 35, Col: 26} | |
| 257 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 40, Col: 26} | |
| 232 | 258 | } | |
| 233 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | |
| 259 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18) | |
| 234 | 260 | if templ_7745c5c3_Err != nil { | |
| 235 | 261 | return templ_7745c5c3_Err | |
| 236 | 262 | } | |
| 237 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"") | |
| 263 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"") | |
| 238 | 264 | if templ_7745c5c3_Err != nil { | |
| 239 | 265 | return templ_7745c5c3_Err | |
| 240 | 266 | } | |
| 241 | 267 | } | |
| 242 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "><a class=\"skip\" href=\"#main\">skip to content</a>") | |
| 268 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "><a class=\"skip\" href=\"#main\">skip to content</a>") | |
| 243 | 269 | if templ_7745c5c3_Err != nil { | |
| 244 | 270 | return templ_7745c5c3_Err | |
| 245 | 271 | } | |
| @@ -247,48 +273,48 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 247 | 273 | if templ_7745c5c3_Err != nil { | |
| 248 | 274 | return templ_7745c5c3_Err | |
| 249 | 275 | } | |
| 250 | - | var templ_7745c5c3_Var17 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | |
| 251 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) | |
| 276 | + | var templ_7745c5c3_Var19 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | |
| 277 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) | |
| 252 | 278 | if templ_7745c5c3_Err != nil { | |
| 253 | 279 | return templ_7745c5c3_Err | |
| 254 | 280 | } | |
| 255 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"") | |
| 281 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"") | |
| 256 | 282 | if templ_7745c5c3_Err != nil { | |
| 257 | 283 | return templ_7745c5c3_Err | |
| 258 | 284 | } | |
| 259 | - | var templ_7745c5c3_Var18 string | |
| 260 | - | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String()) | |
| 285 | + | var templ_7745c5c3_Var20 string | |
| 286 | + | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var19).String()) | |
| 261 | 287 | if templ_7745c5c3_Err != nil { | |
| 262 | 288 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | |
| 263 | 289 | } | |
| 264 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18) | |
| 290 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20) | |
| 265 | 291 | if templ_7745c5c3_Err != nil { | |
| 266 | 292 | return templ_7745c5c3_Err | |
| 267 | 293 | } | |
| 268 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">") | |
| 294 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">") | |
| 269 | 295 | if templ_7745c5c3_Err != nil { | |
| 270 | 296 | return templ_7745c5c3_Err | |
| 271 | 297 | } | |
| 272 | 298 | if l.Lede != "" { | |
| 273 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | |
| 299 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | |
| 274 | 300 | if templ_7745c5c3_Err != nil { | |
| 275 | 301 | return templ_7745c5c3_Err | |
| 276 | 302 | } | |
| 277 | - | var templ_7745c5c3_Var19 string | |
| 278 | - | templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | |
| 303 | + | var templ_7745c5c3_Var21 string | |
| 304 | + | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | |
| 279 | 305 | if templ_7745c5c3_Err != nil { | |
| 280 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 42, Col: 81} | |
| 306 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 47, Col: 81} | |
| 281 | 307 | } | |
| 282 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) | |
| 308 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | |
| 283 | 309 | if templ_7745c5c3_Err != nil { | |
| 284 | 310 | return templ_7745c5c3_Err | |
| 285 | 311 | } | |
| 286 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p></section>") | |
| 312 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p></section>") | |
| 287 | 313 | if templ_7745c5c3_Err != nil { | |
| 288 | 314 | return templ_7745c5c3_Err | |
| 289 | 315 | } | |
| 290 | 316 | } | |
| 291 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<main id=\"main\" tabindex=\"-1\">") | |
| 317 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<main id=\"main\" tabindex=\"-1\">") | |
| 292 | 318 | if templ_7745c5c3_Err != nil { | |
| 293 | 319 | return templ_7745c5c3_Err | |
| 294 | 320 | } | |
| @@ -296,12 +322,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 296 | 322 | if templ_7745c5c3_Err != nil { | |
| 297 | 323 | return templ_7745c5c3_Err | |
| 298 | 324 | } | |
| 299 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</main>") | |
| 325 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</main>") | |
| 300 | 326 | if templ_7745c5c3_Err != nil { | |
| 301 | 327 | return templ_7745c5c3_Err | |
| 302 | 328 | } | |
| 303 | 329 | if l.HasRail && rail != nil { | |
| 304 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<aside class=\"rail\" aria-label=\"Context\">") | |
| 330 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<aside class=\"rail\" aria-label=\"Context\">") | |
| 305 | 331 | if templ_7745c5c3_Err != nil { | |
| 306 | 332 | return templ_7745c5c3_Err | |
| 307 | 333 | } | |
| @@ -309,12 +335,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 309 | 335 | if templ_7745c5c3_Err != nil { | |
| 310 | 336 | return templ_7745c5c3_Err | |
| 311 | 337 | } | |
| 312 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</aside>") | |
| 338 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</aside>") | |
| 313 | 339 | if templ_7745c5c3_Err != nil { | |
| 314 | 340 | return templ_7745c5c3_Err | |
| 315 | 341 | } | |
| 316 | 342 | } | |
| 317 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>") | |
| 343 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</div>") | |
| 318 | 344 | if templ_7745c5c3_Err != nil { | |
| 319 | 345 | return templ_7745c5c3_Err | |
| 320 | 346 | } | |
| @@ -342,7 +368,7 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 342 | 368 | if templ_7745c5c3_Err != nil { | |
| 343 | 369 | return templ_7745c5c3_Err | |
| 344 | 370 | } | |
| 345 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</body></html>") | |
| 371 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</body></html>") | |
| 346 | 372 | if templ_7745c5c3_Err != nil { | |
| 347 | 373 | return templ_7745c5c3_Err | |
| 348 | 374 | } | |
| @@ -350,8 +376,8 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||
| 350 | 376 | }) | |
| 351 | 377 | } | |
| 352 | 378 | ||
| 353 | - | // Header is the page header: the instance, the path, the ref switcher, the | |
| 354 | - | // "go to" palette and the owner; inside a repository, the tabs below. | |
| 379 | + | // Header is the page header: the logo (home), the path, the ref switcher, | |
| 380 | + | // the "go to" palette and the owner; inside a repository, the tabs below. | |
| 355 | 381 | func Header(l Layout) templ.Component { | |
| 356 | 382 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | |
| 357 | 383 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | |
| @@ -368,288 +394,288 @@ func Header(l Layout) templ.Component { | |||
| 368 | 394 | }() | |
| 369 | 395 | } | |
| 370 | 396 | ctx = templ.InitializeContext(ctx) | |
| 371 | - | templ_7745c5c3_Var20 := templ.GetChildren(ctx) | |
| 372 | - | if templ_7745c5c3_Var20 == nil { | |
| 373 | - | templ_7745c5c3_Var20 = templ.NopComponent | |
| 397 | + | templ_7745c5c3_Var22 := templ.GetChildren(ctx) | |
| 398 | + | if templ_7745c5c3_Var22 == nil { | |
| 399 | + | templ_7745c5c3_Var22 = templ.NopComponent | |
| 374 | 400 | } | |
| 375 | 401 | ctx = templ.ClearChildren(ctx) | |
| 376 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") | |
| 377 | - | if templ_7745c5c3_Err != nil { | |
| 378 | - | return templ_7745c5c3_Err | |
| 379 | - | } | |
| 380 | - | var templ_7745c5c3_Var21 string | |
| 381 | - | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) | |
| 382 | - | if templ_7745c5c3_Err != nil { | |
| 383 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 70, Col: 41} | |
| 384 | - | } | |
| 385 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | |
| 386 | - | if templ_7745c5c3_Err != nil { | |
| 387 | - | return templ_7745c5c3_Err | |
| 388 | - | } | |
| 389 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</span></a> ") | |
| 402 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" aria-label=\"home\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span></a> ") | |
| 390 | 403 | if templ_7745c5c3_Err != nil { | |
| 391 | 404 | return templ_7745c5c3_Err | |
| 392 | 405 | } | |
| 393 | 406 | if len(l.Crumbs) > 0 { | |
| 394 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | |
| 407 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | |
| 395 | 408 | if templ_7745c5c3_Err != nil { | |
| 396 | 409 | return templ_7745c5c3_Err | |
| 397 | 410 | } | |
| 398 | 411 | for _, c := range l.Crumbs { | |
| 399 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<li>") | |
| 412 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<li>") | |
| 400 | 413 | if templ_7745c5c3_Err != nil { | |
| 401 | 414 | return templ_7745c5c3_Err | |
| 402 | 415 | } | |
| 403 | 416 | if c.Href != "" { | |
| 404 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a href=\"") | |
| 417 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<a href=\"") | |
| 405 | 418 | if templ_7745c5c3_Err != nil { | |
| 406 | 419 | return templ_7745c5c3_Err | |
| 407 | 420 | } | |
| 408 | - | var templ_7745c5c3_Var22 templ.SafeURL | |
| 409 | - | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | |
| 421 | + | var templ_7745c5c3_Var23 templ.SafeURL | |
| 422 | + | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | |
| 410 | 423 | if templ_7745c5c3_Err != nil { | |
| 411 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 79, Col: 38} | |
| 424 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 83, Col: 38} | |
| 412 | 425 | } | |
| 413 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | |
| 426 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | |
| 414 | 427 | if templ_7745c5c3_Err != nil { | |
| 415 | 428 | return templ_7745c5c3_Err | |
| 416 | 429 | } | |
| 417 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\"") | |
| 430 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"") | |
| 418 | 431 | if templ_7745c5c3_Err != nil { | |
| 419 | 432 | return templ_7745c5c3_Err | |
| 420 | 433 | } | |
| 421 | 434 | if c.Current { | |
| 422 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " aria-current=\"page\"") | |
| 435 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " aria-current=\"page\"") | |
| 423 | 436 | if templ_7745c5c3_Err != nil { | |
| 424 | 437 | return templ_7745c5c3_Err | |
| 425 | 438 | } | |
| 426 | 439 | } | |
| 427 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, ">") | |
| 440 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, ">") | |
| 428 | 441 | if templ_7745c5c3_Err != nil { | |
| 429 | 442 | return templ_7745c5c3_Err | |
| 430 | 443 | } | |
| 431 | - | var templ_7745c5c3_Var23 string | |
| 432 | - | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 444 | + | var templ_7745c5c3_Var24 string | |
| 445 | + | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 433 | 446 | if templ_7745c5c3_Err != nil { | |
| 434 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 83, Col: 19} | |
| 447 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 87, Col: 19} | |
| 435 | 448 | } | |
| 436 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | |
| 449 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | |
| 437 | 450 | if templ_7745c5c3_Err != nil { | |
| 438 | 451 | return templ_7745c5c3_Err | |
| 439 | 452 | } | |
| 440 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</a>") | |
| 453 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</a>") | |
| 441 | 454 | if templ_7745c5c3_Err != nil { | |
| 442 | 455 | return templ_7745c5c3_Err | |
| 443 | 456 | } | |
| 444 | 457 | } else { | |
| 445 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<span") | |
| 458 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<span") | |
| 446 | 459 | if templ_7745c5c3_Err != nil { | |
| 447 | 460 | return templ_7745c5c3_Err | |
| 448 | 461 | } | |
| 449 | 462 | if c.Current { | |
| 450 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " aria-current=\"page\"") | |
| 463 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " aria-current=\"page\"") | |
| 451 | 464 | if templ_7745c5c3_Err != nil { | |
| 452 | 465 | return templ_7745c5c3_Err | |
| 453 | 466 | } | |
| 454 | 467 | } | |
| 455 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, ">") | |
| 468 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ">") | |
| 456 | 469 | if templ_7745c5c3_Err != nil { | |
| 457 | 470 | return templ_7745c5c3_Err | |
| 458 | 471 | } | |
| 459 | - | var templ_7745c5c3_Var24 string | |
| 460 | - | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 472 | + | var templ_7745c5c3_Var25 string | |
| 473 | + | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | |
| 461 | 474 | if templ_7745c5c3_Err != nil { | |
| 462 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 89, Col: 19} | |
| 475 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 93, Col: 19} | |
| 463 | 476 | } | |
| 464 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | |
| 477 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | |
| 465 | 478 | if templ_7745c5c3_Err != nil { | |
| 466 | 479 | return templ_7745c5c3_Err | |
| 467 | 480 | } | |
| 468 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</span>") | |
| 481 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</span>") | |
| 469 | 482 | if templ_7745c5c3_Err != nil { | |
| 470 | 483 | return templ_7745c5c3_Err | |
| 471 | 484 | } | |
| 472 | 485 | } | |
| 473 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</li>") | |
| 486 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</li>") | |
| 474 | 487 | if templ_7745c5c3_Err != nil { | |
| 475 | 488 | return templ_7745c5c3_Err | |
| 476 | 489 | } | |
| 477 | 490 | } | |
| 478 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</ol></nav>") | |
| 491 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</ol></nav>") | |
| 479 | 492 | if templ_7745c5c3_Err != nil { | |
| 480 | 493 | return templ_7745c5c3_Err | |
| 481 | 494 | } | |
| 482 | 495 | } | |
| 483 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"hdr-acts\">") | |
| 496 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<div class=\"hdr-acts\">") | |
| 484 | 497 | if templ_7745c5c3_Err != nil { | |
| 485 | 498 | return templ_7745c5c3_Err | |
| 486 | 499 | } | |
| 487 | 500 | if l.Ref != "" { | |
| 488 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<a class=\"btn ref\" href=\"") | |
| 501 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<a class=\"btn ref\" href=\"") | |
| 489 | 502 | if templ_7745c5c3_Err != nil { | |
| 490 | 503 | return templ_7745c5c3_Err | |
| 491 | 504 | } | |
| 492 | - | var templ_7745c5c3_Var25 templ.SafeURL | |
| 493 | - | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | |
| 505 | + | var templ_7745c5c3_Var26 templ.SafeURL | |
| 506 | + | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | |
| 494 | 507 | if templ_7745c5c3_Err != nil { | |
| 495 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 100, Col: 52} | |
| 508 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 104, Col: 52} | |
| 496 | 509 | } | |
| 497 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | |
| 510 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | |
| 498 | 511 | if templ_7745c5c3_Err != nil { | |
| 499 | 512 | return templ_7745c5c3_Err | |
| 500 | 513 | } | |
| 501 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | |
| 514 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | |
| 502 | 515 | if templ_7745c5c3_Err != nil { | |
| 503 | 516 | return templ_7745c5c3_Err | |
| 504 | 517 | } | |
| 505 | - | var templ_7745c5c3_Var26 string | |
| 506 | - | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | |
| 518 | + | var templ_7745c5c3_Var27 string | |
| 519 | + | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | |
| 507 | 520 | if templ_7745c5c3_Err != nil { | |
| 508 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 106, Col: 36} | |
| 521 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 110, Col: 36} | |
| 509 | 522 | } | |
| 510 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | |
| 523 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | |
| 511 | 524 | if templ_7745c5c3_Err != nil { | |
| 512 | 525 | return templ_7745c5c3_Err | |
| 513 | 526 | } | |
| 514 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span> <span class=\"sha\">") | |
| 527 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span> <span class=\"sha\">") | |
| 515 | 528 | if templ_7745c5c3_Err != nil { | |
| 516 | 529 | return templ_7745c5c3_Err | |
| 517 | 530 | } | |
| 518 | - | var templ_7745c5c3_Var27 string | |
| 519 | - | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | |
| 531 | + | var templ_7745c5c3_Var28 string | |
| 532 | + | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | |
| 520 | 533 | if templ_7745c5c3_Err != nil { | |
| 521 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 107, Col: 33} | |
| 534 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 111, Col: 33} | |
| 522 | 535 | } | |
| 523 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | |
| 536 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | |
| 524 | 537 | if templ_7745c5c3_Err != nil { | |
| 525 | 538 | return templ_7745c5c3_Err | |
| 526 | 539 | } | |
| 527 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span></a> ") | |
| 540 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</span></a> ") | |
| 528 | 541 | if templ_7745c5c3_Err != nil { | |
| 529 | 542 | return templ_7745c5c3_Err | |
| 530 | 543 | } | |
| 531 | 544 | } | |
| 532 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") | |
| 545 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> ") | |
| 533 | 546 | if templ_7745c5c3_Err != nil { | |
| 534 | 547 | return templ_7745c5c3_Err | |
| 535 | 548 | } | |
| 536 | 549 | if SignedIn(ctx) { | |
| 537 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> ") | |
| 550 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\"><span class=\"ico i-user\" aria-hidden=\"true\"></span>") | |
| 551 | + | if templ_7745c5c3_Err != nil { | |
| 552 | + | return templ_7745c5c3_Err | |
| 553 | + | } | |
| 554 | + | var templ_7745c5c3_Var29 string | |
| 555 | + | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(l.Owner) | |
| 556 | + | if templ_7745c5c3_Err != nil { | |
| 557 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 130, Col: 123} | |
| 558 | + | } | |
| 559 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | |
| 560 | + | if templ_7745c5c3_Err != nil { | |
| 561 | + | return templ_7745c5c3_Err | |
| 562 | + | } | |
| 563 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span> ") | |
| 538 | 564 | if templ_7745c5c3_Err != nil { | |
| 539 | 565 | return templ_7745c5c3_Err | |
| 540 | 566 | } | |
| 541 | 567 | if l.Passkeys { | |
| 542 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a class=\"btn\" href=\"/-/passkeys\" title=\"add or remove the passkeys that sign you in\">Passkeys</a> ") | |
| 568 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a class=\"btn icon\" href=\"/-/passkeys\" aria-label=\"Passkeys\" title=\"passkeys: add or remove the passkeys that sign you in\"><span class=\"ico i-key\" aria-hidden=\"true\"></span></a> ") | |
| 543 | 569 | if templ_7745c5c3_Err != nil { | |
| 544 | 570 | return templ_7745c5c3_Err | |
| 545 | 571 | } | |
| 546 | 572 | } | |
| 547 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") | |
| 573 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<button type=\"submit\" class=\"btn icon\" aria-label=\"Sign out\" title=\"sign out of this browser\"><span class=\"ico i-log-out\" aria-hidden=\"true\"></span></button></form>") | |
| 548 | 574 | if templ_7745c5c3_Err != nil { | |
| 549 | 575 | return templ_7745c5c3_Err | |
| 550 | 576 | } | |
| 551 | 577 | } else if l.Passkeys { | |
| 552 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a class=\"btn\" href=\"/-/login\" title=\"sign in with a passkey\">Sign in</a>") | |
| 578 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<a class=\"btn icon\" href=\"/-/login\" aria-label=\"Sign in\" title=\"sign in with a passkey\"><span class=\"ico i-log-in\" aria-hidden=\"true\"></span></a>") | |
| 553 | 579 | if templ_7745c5c3_Err != nil { | |
| 554 | 580 | return templ_7745c5c3_Err | |
| 555 | 581 | } | |
| 556 | 582 | } | |
| 557 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</div></div>") | |
| 583 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</div></div>") | |
| 558 | 584 | if templ_7745c5c3_Err != nil { | |
| 559 | 585 | return templ_7745c5c3_Err | |
| 560 | 586 | } | |
| 561 | 587 | if len(l.Sections) > 0 { | |
| 562 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | |
| 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | |
| 563 | 589 | if templ_7745c5c3_Err != nil { | |
| 564 | 590 | return templ_7745c5c3_Err | |
| 565 | 591 | } | |
| 566 | 592 | for _, s := range l.Sections { | |
| 567 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<li><a href=\"") | |
| 593 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<li><a href=\"") | |
| 568 | 594 | if templ_7745c5c3_Err != nil { | |
| 569 | 595 | return templ_7745c5c3_Err | |
| 570 | 596 | } | |
| 571 | - | var templ_7745c5c3_Var28 templ.SafeURL | |
| 572 | - | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | |
| 597 | + | var templ_7745c5c3_Var30 templ.SafeURL | |
| 598 | + | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | |
| 573 | 599 | if templ_7745c5c3_Err != nil { | |
| 574 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 36} | |
| 600 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 147, Col: 36} | |
| 575 | 601 | } | |
| 576 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | |
| 602 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | |
| 577 | 603 | if templ_7745c5c3_Err != nil { | |
| 578 | 604 | return templ_7745c5c3_Err | |
| 579 | 605 | } | |
| 580 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\" data-key=\"") | |
| 606 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" data-key=\"") | |
| 581 | 607 | if templ_7745c5c3_Err != nil { | |
| 582 | 608 | return templ_7745c5c3_Err | |
| 583 | 609 | } | |
| 584 | - | var templ_7745c5c3_Var29 string | |
| 585 | - | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 610 | + | var templ_7745c5c3_Var31 string | |
| 611 | + | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 586 | 612 | if templ_7745c5c3_Err != nil { | |
| 587 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 145, Col: 24} | |
| 613 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 148, Col: 24} | |
| 588 | 614 | } | |
| 589 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | |
| 615 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31) | |
| 590 | 616 | if templ_7745c5c3_Err != nil { | |
| 591 | 617 | return templ_7745c5c3_Err | |
| 592 | 618 | } | |
| 593 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\" aria-keyshortcuts=\"") | |
| 619 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\" aria-keyshortcuts=\"") | |
| 594 | 620 | if templ_7745c5c3_Err != nil { | |
| 595 | 621 | return templ_7745c5c3_Err | |
| 596 | 622 | } | |
| 597 | - | var templ_7745c5c3_Var30 string | |
| 598 | - | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 623 | + | var templ_7745c5c3_Var32 string | |
| 624 | + | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | |
| 599 | 625 | if templ_7745c5c3_Err != nil { | |
| 600 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 146, Col: 33} | |
| 626 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 149, Col: 33} | |
| 601 | 627 | } | |
| 602 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30) | |
| 628 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32) | |
| 603 | 629 | if templ_7745c5c3_Err != nil { | |
| 604 | 630 | return templ_7745c5c3_Err | |
| 605 | 631 | } | |
| 606 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\"") | |
| 632 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\"") | |
| 607 | 633 | if templ_7745c5c3_Err != nil { | |
| 608 | 634 | return templ_7745c5c3_Err | |
| 609 | 635 | } | |
| 610 | 636 | if s.Current { | |
| 611 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " aria-current=\"page\"") | |
| 637 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, " aria-current=\"page\"") | |
| 612 | 638 | if templ_7745c5c3_Err != nil { | |
| 613 | 639 | return templ_7745c5c3_Err | |
| 614 | 640 | } | |
| 615 | 641 | } | |
| 616 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, ">") | |
| 642 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, ">") | |
| 617 | 643 | if templ_7745c5c3_Err != nil { | |
| 618 | 644 | return templ_7745c5c3_Err | |
| 619 | 645 | } | |
| 620 | - | var templ_7745c5c3_Var31 string | |
| 621 | - | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | |
| 646 | + | var templ_7745c5c3_Var33 string | |
| 647 | + | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | |
| 622 | 648 | if templ_7745c5c3_Err != nil { | |
| 623 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 150, Col: 17} | |
| 649 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 153, Col: 17} | |
| 624 | 650 | } | |
| 625 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | |
| 651 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) | |
| 626 | 652 | if templ_7745c5c3_Err != nil { | |
| 627 | 653 | return templ_7745c5c3_Err | |
| 628 | 654 | } | |
| 629 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<kbd aria-hidden=\"true\">") | |
| 655 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<kbd aria-hidden=\"true\">") | |
| 630 | 656 | if templ_7745c5c3_Err != nil { | |
| 631 | 657 | return templ_7745c5c3_Err | |
| 632 | 658 | } | |
| 633 | - | var templ_7745c5c3_Var32 string | |
| 634 | - | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | |
| 659 | + | var templ_7745c5c3_Var34 string | |
| 660 | + | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | |
| 635 | 661 | if templ_7745c5c3_Err != nil { | |
| 636 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 150, Col: 50} | |
| 662 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 153, Col: 50} | |
| 637 | 663 | } | |
| 638 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | |
| 664 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) | |
| 639 | 665 | if templ_7745c5c3_Err != nil { | |
| 640 | 666 | return templ_7745c5c3_Err | |
| 641 | 667 | } | |
| 642 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</kbd></a></li>") | |
| 668 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</kbd></a></li>") | |
| 643 | 669 | if templ_7745c5c3_Err != nil { | |
| 644 | 670 | return templ_7745c5c3_Err | |
| 645 | 671 | } | |
| 646 | 672 | } | |
| 647 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</ul></nav>") | |
| 673 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</ul></nav>") | |
| 648 | 674 | if templ_7745c5c3_Err != nil { | |
| 649 | 675 | return templ_7745c5c3_Err | |
| 650 | 676 | } | |
| 651 | 677 | } | |
| 652 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</header>") | |
| 678 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</header>") | |
| 653 | 679 | if templ_7745c5c3_Err != nil { | |
| 654 | 680 | return templ_7745c5c3_Err | |
| 655 | 681 | } | |
| @@ -675,38 +701,38 @@ func Footer(l Layout) templ.Component { | |||
| 675 | 701 | }() | |
| 676 | 702 | } | |
| 677 | 703 | ctx = templ.InitializeContext(ctx) | |
| 678 | - | templ_7745c5c3_Var33 := templ.GetChildren(ctx) | |
| 679 | - | if templ_7745c5c3_Var33 == nil { | |
| 680 | - | templ_7745c5c3_Var33 = templ.NopComponent | |
| 704 | + | templ_7745c5c3_Var35 := templ.GetChildren(ctx) | |
| 705 | + | if templ_7745c5c3_Var35 == nil { | |
| 706 | + | templ_7745c5c3_Var35 = templ.NopComponent | |
| 681 | 707 | } | |
| 682 | 708 | ctx = templ.ClearChildren(ctx) | |
| 683 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<footer class=\"foot\"><span>ermite ") | |
| 709 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<footer class=\"foot\"><span>ermite ") | |
| 684 | 710 | if templ_7745c5c3_Err != nil { | |
| 685 | 711 | return templ_7745c5c3_Err | |
| 686 | 712 | } | |
| 687 | - | var templ_7745c5c3_Var34 string | |
| 688 | - | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | |
| 713 | + | var templ_7745c5c3_Var36 string | |
| 714 | + | templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | |
| 689 | 715 | if templ_7745c5c3_Err != nil { | |
| 690 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 163, Col: 26} | |
| 716 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 166, Col: 26} | |
| 691 | 717 | } | |
| 692 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) | |
| 718 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) | |
| 693 | 719 | if templ_7745c5c3_Err != nil { | |
| 694 | 720 | return templ_7745c5c3_Err | |
| 695 | 721 | } | |
| 696 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span> <a href=\"") | |
| 722 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</span> <a href=\"") | |
| 697 | 723 | if templ_7745c5c3_Err != nil { | |
| 698 | 724 | return templ_7745c5c3_Err | |
| 699 | 725 | } | |
| 700 | - | var templ_7745c5c3_Var35 templ.SafeURL | |
| 701 | - | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | |
| 726 | + | var templ_7745c5c3_Var37 templ.SafeURL | |
| 727 | + | templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | |
| 702 | 728 | if templ_7745c5c3_Err != nil { | |
| 703 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 164, Col: 38} | |
| 729 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 167, Col: 38} | |
| 704 | 730 | } | |
| 705 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | |
| 731 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) | |
| 706 | 732 | if templ_7745c5c3_Err != nil { | |
| 707 | 733 | return templ_7745c5c3_Err | |
| 708 | 734 | } | |
| 709 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") | |
| 735 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button></footer>") | |
| 710 | 736 | if templ_7745c5c3_Err != nil { | |
| 711 | 737 | return templ_7745c5c3_Err | |
| 712 | 738 | } | |
| @@ -734,12 +760,12 @@ func Hint(l Layout) templ.Component { | |||
| 734 | 760 | }() | |
| 735 | 761 | } | |
| 736 | 762 | ctx = templ.InitializeContext(ctx) | |
| 737 | - | templ_7745c5c3_Var36 := templ.GetChildren(ctx) | |
| 738 | - | if templ_7745c5c3_Var36 == nil { | |
| 739 | - | templ_7745c5c3_Var36 = templ.NopComponent | |
| 763 | + | templ_7745c5c3_Var38 := templ.GetChildren(ctx) | |
| 764 | + | if templ_7745c5c3_Var38 == nil { | |
| 765 | + | templ_7745c5c3_Var38 = templ.NopComponent | |
| 740 | 766 | } | |
| 741 | 767 | ctx = templ.ClearChildren(ctx) | |
| 742 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | |
| 768 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | |
| 743 | 769 | if templ_7745c5c3_Err != nil { | |
| 744 | 770 | return templ_7745c5c3_Err | |
| 745 | 771 | } | |
| @@ -747,7 +773,7 @@ func Hint(l Layout) templ.Component { | |||
| 747 | 773 | if templ_7745c5c3_Err != nil { | |
| 748 | 774 | return templ_7745c5c3_Err | |
| 749 | 775 | } | |
| 750 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</div>") | |
| 776 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</div>") | |
| 751 | 777 | if templ_7745c5c3_Err != nil { | |
| 752 | 778 | return templ_7745c5c3_Err | |
| 753 | 779 | } | |
| @@ -47,79 +47,79 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 47 | if templ_7745c5c3_Err != nil { | 47 | if templ_7745c5c3_Err != nil { | ||
| 48 | return templ_7745c5c3_Err | 48 | return templ_7745c5c3_Err | ||
| 49 | } | 49 | } | ||
| 50 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"icon\" type=\"image/svg+xml\" href=\"") | 50 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"") |
| 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 | var templ_7745c5c3_Var3 templ.SafeURL | 54 | var templ_7745c5c3_Var3 templ.SafeURL | ||
| 55 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Icon)) | 55 | templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Icon)) | ||
| 56 | if templ_7745c5c3_Err != nil { | 56 | if templ_7745c5c3_Err != nil { | ||
| 57 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 16, Col: 76} | 57 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 19, Col: 76} |
| 58 | } | 58 | } | ||
| 59 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | 59 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | ||
| 60 | if templ_7745c5c3_Err != nil { | 60 | if templ_7745c5c3_Err != nil { | ||
| 61 | return templ_7745c5c3_Err | 61 | return templ_7745c5c3_Err | ||
| 62 | } | 62 | } | ||
| 63 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><link rel=\"preload\" href=\"") | 63 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><link rel=\"apple-touch-icon\" href=\"") |
| 64 | if templ_7745c5c3_Err != nil { | 64 | if templ_7745c5c3_Err != nil { | ||
| 65 | return templ_7745c5c3_Err | 65 | return templ_7745c5c3_Err | ||
| 66 | } | 66 | } | ||
| 67 | var templ_7745c5c3_Var4 templ.SafeURL | 67 | var templ_7745c5c3_Var4 templ.SafeURL | ||
| 68 | - | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Font)) | 68 | + | templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.AppleIcon)) |
| 69 | if templ_7745c5c3_Err != nil { | 69 | if templ_7745c5c3_Err != nil { | ||
| 70 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 17, Col: 58} | 70 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 20, Col: 72} |
| 71 | } | 71 | } | ||
| 72 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | 72 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | ||
| 73 | if templ_7745c5c3_Err != nil { | 73 | if templ_7745c5c3_Err != nil { | ||
| 74 | return templ_7745c5c3_Err | 74 | return templ_7745c5c3_Err | ||
| 75 | } | 75 | } | ||
| 76 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"stylesheet\" href=\"") | 76 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><link rel=\"manifest\" href=\"") |
| 77 | if templ_7745c5c3_Err != nil { | 77 | if templ_7745c5c3_Err != nil { | ||
| 78 | return templ_7745c5c3_Err | 78 | return templ_7745c5c3_Err | ||
| 79 | } | 79 | } | ||
| 80 | var templ_7745c5c3_Var5 templ.SafeURL | 80 | var templ_7745c5c3_Var5 templ.SafeURL | ||
| 81 | - | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.CSS)) | 81 | + | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Manifest)) |
| 82 | if templ_7745c5c3_Err != nil { | 82 | if templ_7745c5c3_Err != nil { | ||
| 83 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 18, Col: 60} | 83 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 21, Col: 63} |
| 84 | } | 84 | } | ||
| 85 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | 85 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | ||
| 86 | if templ_7745c5c3_Err != nil { | 86 | if templ_7745c5c3_Err != nil { | ||
| 87 | return templ_7745c5c3_Err | 87 | return templ_7745c5c3_Err | ||
| 88 | } | 88 | } | ||
| 89 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta name=\"htmx-config\" content='{\"history\":false,\"includeIndicatorCSS\":false,\"extensions\":\"sse, hx-live\"}'><script src=\"") | 89 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><link rel=\"preload\" href=\"") |
| 90 | if templ_7745c5c3_Err != nil { | 90 | if templ_7745c5c3_Err != nil { | ||
| 91 | return templ_7745c5c3_Err | 91 | return templ_7745c5c3_Err | ||
| 92 | } | 92 | } | ||
| 93 | - | var templ_7745c5c3_Var6 string | 93 | + | var templ_7745c5c3_Var6 templ.SafeURL |
| 94 | - | templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.HTMX) | 94 | + | templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.Font)) |
| 95 | if templ_7745c5c3_Err != nil { | 95 | if templ_7745c5c3_Err != nil { | ||
| 96 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 20, Col: 30} | 96 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 22, Col: 58} |
| 97 | } | 97 | } | ||
| 98 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6) | 98 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) |
| 99 | if templ_7745c5c3_Err != nil { | 99 | if templ_7745c5c3_Err != nil { | ||
| 100 | return templ_7745c5c3_Err | 100 | return templ_7745c5c3_Err | ||
| 101 | } | 101 | } | ||
| 102 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" defer></script><script src=\"") | 102 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"stylesheet\" href=\"") |
| 103 | if templ_7745c5c3_Err != nil { | 103 | if templ_7745c5c3_Err != nil { | ||
| 104 | return templ_7745c5c3_Err | 104 | return templ_7745c5c3_Err | ||
| 105 | } | 105 | } | ||
| 106 | - | var templ_7745c5c3_Var7 string | 106 | + | var templ_7745c5c3_Var7 templ.SafeURL |
| 107 | - | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Live) | 107 | + | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.Assets.CSS)) |
| 108 | if templ_7745c5c3_Err != nil { | 108 | if templ_7745c5c3_Err != nil { | ||
| 109 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 21, Col: 30} | 109 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 23, Col: 60} |
| 110 | } | 110 | } | ||
| 111 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7) | 111 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) |
| 112 | if templ_7745c5c3_Err != nil { | 112 | if templ_7745c5c3_Err != nil { | ||
| 113 | return templ_7745c5c3_Err | 113 | return templ_7745c5c3_Err | ||
| 114 | } | 114 | } | ||
| 115 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" defer></script><script src=\"") | 115 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><meta name=\"htmx-config\" content='{\"history\":false,\"includeIndicatorCSS\":false,\"extensions\":\"sse, hx-live\"}'><script src=\"") |
| 116 | if templ_7745c5c3_Err != nil { | 116 | if templ_7745c5c3_Err != nil { | ||
| 117 | return templ_7745c5c3_Err | 117 | return templ_7745c5c3_Err | ||
| 118 | } | 118 | } | ||
| 119 | var templ_7745c5c3_Var8 string | 119 | var templ_7745c5c3_Var8 string | ||
| 120 | - | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.SSE) | 120 | + | templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.HTMX) |
| 121 | if templ_7745c5c3_Err != nil { | 121 | if templ_7745c5c3_Err != nil { | ||
| 122 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 22, Col: 29} | 122 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 30} |
| 123 | } | 123 | } | ||
| 124 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) | 124 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) | ||
| 125 | if templ_7745c5c3_Err != nil { | 125 | if templ_7745c5c3_Err != nil { | ||
| @@ -130,9 +130,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 130 | return templ_7745c5c3_Err | 130 | return templ_7745c5c3_Err | ||
| 131 | } | 131 | } | ||
| 132 | var templ_7745c5c3_Var9 string | 132 | var templ_7745c5c3_Var9 string | ||
| 133 | - | templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.App) | 133 | + | templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Live) |
| 134 | if templ_7745c5c3_Err != nil { | 134 | if templ_7745c5c3_Err != nil { | ||
| 135 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 23, Col: 29} | 135 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 26, Col: 30} |
| 136 | } | 136 | } | ||
| 137 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) | 137 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) | ||
| 138 | if templ_7745c5c3_Err != nil { | 138 | if templ_7745c5c3_Err != nil { | ||
| @@ -143,9 +143,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 143 | return templ_7745c5c3_Err | 143 | return templ_7745c5c3_Err | ||
| 144 | } | 144 | } | ||
| 145 | var templ_7745c5c3_Var10 string | 145 | var templ_7745c5c3_Var10 string | ||
| 146 | - | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Files) | 146 | + | templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.SSE) |
| 147 | if templ_7745c5c3_Err != nil { | 147 | if templ_7745c5c3_Err != nil { | ||
| 148 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 24, Col: 31} | 148 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 27, Col: 29} |
| 149 | } | 149 | } | ||
| 150 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | 150 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10) | ||
| 151 | if templ_7745c5c3_Err != nil { | 151 | if templ_7745c5c3_Err != nil { | ||
| @@ -156,9 +156,9 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 156 | return templ_7745c5c3_Err | 156 | return templ_7745c5c3_Err | ||
| 157 | } | 157 | } | ||
| 158 | var templ_7745c5c3_Var11 string | 158 | var templ_7745c5c3_Var11 string | ||
| 159 | - | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Passkey) | 159 | + | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.App) |
| 160 | if templ_7745c5c3_Err != nil { | 160 | if templ_7745c5c3_Err != nil { | ||
| 161 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 25, Col: 33} | 161 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 28, Col: 29} |
| 162 | } | 162 | } | ||
| 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | 163 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) | ||
| 164 | if templ_7745c5c3_Err != nil { | 164 | if templ_7745c5c3_Err != nil { | ||
| @@ -169,77 +169,103 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 169 | return templ_7745c5c3_Err | 169 | return templ_7745c5c3_Err | ||
| 170 | } | 170 | } | ||
| 171 | var templ_7745c5c3_Var12 string | 171 | var templ_7745c5c3_Var12 string | ||
| 172 | - | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) | 172 | + | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Files) |
| 173 | if templ_7745c5c3_Err != nil { | 173 | if templ_7745c5c3_Err != nil { | ||
| 174 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 26, Col: 29} | 174 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 31} |
| 175 | } | 175 | } | ||
| 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | 176 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) | ||
| 177 | if templ_7745c5c3_Err != nil { | 177 | if templ_7745c5c3_Err != nil { | ||
| 178 | return templ_7745c5c3_Err | 178 | return templ_7745c5c3_Err | ||
| 179 | } | 179 | } | ||
| 180 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" defer></script></head><body data-ref=\"") | 180 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" defer></script><script src=\"") |
| 181 | if templ_7745c5c3_Err != nil { | 181 | if templ_7745c5c3_Err != nil { | ||
| 182 | return templ_7745c5c3_Err | 182 | return templ_7745c5c3_Err | ||
| 183 | } | 183 | } | ||
| 184 | var templ_7745c5c3_Var13 string | 184 | var templ_7745c5c3_Var13 string | ||
| 185 | - | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | 185 | + | templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Passkey) |
| 186 | if templ_7745c5c3_Err != nil { | 186 | if templ_7745c5c3_Err != nil { | ||
| 187 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 29, Col: 23} | 187 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 30, Col: 33} |
| 188 | } | 188 | } | ||
| 189 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | 189 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) | ||
| 190 | if templ_7745c5c3_Err != nil { | 190 | if templ_7745c5c3_Err != nil { | ||
| 191 | return templ_7745c5c3_Err | 191 | return templ_7745c5c3_Err | ||
| 192 | } | 192 | } | ||
| 193 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" data-sha=\"") | 193 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" defer></script><script src=\"") |
| 194 | if templ_7745c5c3_Err != nil { | 194 | if templ_7745c5c3_Err != nil { | ||
| 195 | return templ_7745c5c3_Err | 195 | return templ_7745c5c3_Err | ||
| 196 | } | 196 | } | ||
| 197 | var templ_7745c5c3_Var14 string | 197 | var templ_7745c5c3_Var14 string | ||
| 198 | - | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | 198 | + | templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.Assets.Log) |
| 199 | if templ_7745c5c3_Err != nil { | 199 | if templ_7745c5c3_Err != nil { | ||
| 200 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 30, Col: 19} | 200 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 31, Col: 29} |
| 201 | } | 201 | } | ||
| 202 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | 202 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) | ||
| 203 | if templ_7745c5c3_Err != nil { | 203 | if templ_7745c5c3_Err != nil { | ||
| 204 | return templ_7745c5c3_Err | 204 | return templ_7745c5c3_Err | ||
| 205 | } | 205 | } | ||
| 206 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" data-sel=\"\" data-nav=\"\"") | 206 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" defer></script></head><body data-ref=\"") |
| 207 | + | if templ_7745c5c3_Err != nil { | |||
| 208 | + | return templ_7745c5c3_Err | |||
| 209 | + | } | |||
| 210 | + | var templ_7745c5c3_Var15 string | |||
| 211 | + | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.RefPath) | |||
| 212 | + | if templ_7745c5c3_Err != nil { | |||
| 213 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 23} | |||
| 214 | + | } | |||
| 215 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) | |||
| 216 | + | if templ_7745c5c3_Err != nil { | |||
| 217 | + | return templ_7745c5c3_Err | |||
| 218 | + | } | |||
| 219 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-sha=\"") | |||
| 220 | + | if templ_7745c5c3_Err != nil { | |||
| 221 | + | return templ_7745c5c3_Err | |||
| 222 | + | } | |||
| 223 | + | var templ_7745c5c3_Var16 string | |||
| 224 | + | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.SHA) | |||
| 225 | + | if templ_7745c5c3_Err != nil { | |||
| 226 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 35, Col: 19} | |||
| 227 | + | } | |||
| 228 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | |||
| 229 | + | if templ_7745c5c3_Err != nil { | |||
| 230 | + | return templ_7745c5c3_Err | |||
| 231 | + | } | |||
| 232 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" data-sel=\"\" data-nav=\"\"") | |||
| 207 | if templ_7745c5c3_Err != nil { | 233 | if templ_7745c5c3_Err != nil { | ||
| 208 | return templ_7745c5c3_Err | 234 | return templ_7745c5c3_Err | ||
| 209 | } | 235 | } | ||
| 210 | if l.PathsURL != "" { | 236 | if l.PathsURL != "" { | ||
| 211 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " data-paths=\"") | 237 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " data-paths=\"") |
| 212 | if templ_7745c5c3_Err != nil { | 238 | if templ_7745c5c3_Err != nil { | ||
| 213 | return templ_7745c5c3_Err | 239 | return templ_7745c5c3_Err | ||
| 214 | } | 240 | } | ||
| 215 | - | var templ_7745c5c3_Var15 string | 241 | + | var templ_7745c5c3_Var17 string |
| 216 | - | templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) | 242 | + | templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.PathsURL) |
| 217 | if templ_7745c5c3_Err != nil { | 243 | if templ_7745c5c3_Err != nil { | ||
| 218 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 34, Col: 27} | 244 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 39, Col: 27} |
| 219 | } | 245 | } | ||
| 220 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) | 246 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17) |
| 221 | if templ_7745c5c3_Err != nil { | 247 | if templ_7745c5c3_Err != nil { | ||
| 222 | return templ_7745c5c3_Err | 248 | return templ_7745c5c3_Err | ||
| 223 | } | 249 | } | ||
| 224 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" data-blob=\"") | 250 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" data-blob=\"") |
| 225 | if templ_7745c5c3_Err != nil { | 251 | if templ_7745c5c3_Err != nil { | ||
| 226 | return templ_7745c5c3_Err | 252 | return templ_7745c5c3_Err | ||
| 227 | } | 253 | } | ||
| 228 | - | var templ_7745c5c3_Var16 string | 254 | + | var templ_7745c5c3_Var18 string |
| 229 | - | templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) | 255 | + | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(l.BlobBase) |
| 230 | if templ_7745c5c3_Err != nil { | 256 | if templ_7745c5c3_Err != nil { | ||
| 231 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 35, Col: 26} | 257 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 40, Col: 26} |
| 232 | } | 258 | } | ||
| 233 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) | 259 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18) |
| 234 | if templ_7745c5c3_Err != nil { | 260 | if templ_7745c5c3_Err != nil { | ||
| 235 | return templ_7745c5c3_Err | 261 | return templ_7745c5c3_Err | ||
| 236 | } | 262 | } | ||
| 237 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"") | 263 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"") |
| 238 | if templ_7745c5c3_Err != nil { | 264 | if templ_7745c5c3_Err != nil { | ||
| 239 | return templ_7745c5c3_Err | 265 | return templ_7745c5c3_Err | ||
| 240 | } | 266 | } | ||
| 241 | } | 267 | } | ||
| 242 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "><a class=\"skip\" href=\"#main\">skip to content</a>") | 268 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "><a class=\"skip\" href=\"#main\">skip to content</a>") |
| 243 | if templ_7745c5c3_Err != nil { | 269 | if templ_7745c5c3_Err != nil { | ||
| 244 | return templ_7745c5c3_Err | 270 | return templ_7745c5c3_Err | ||
| 245 | } | 271 | } | ||
| @@ -247,48 +273,48 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 247 | if templ_7745c5c3_Err != nil { | 273 | if templ_7745c5c3_Err != nil { | ||
| 248 | return templ_7745c5c3_Err | 274 | return templ_7745c5c3_Err | ||
| 249 | } | 275 | } | ||
| 250 | - | var templ_7745c5c3_Var17 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} | 276 | + | var templ_7745c5c3_Var19 = []any{"work", templ.KV("has-rail", l.HasRail && rail != nil)} |
| 251 | - | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) | 277 | + | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) |
| 252 | if templ_7745c5c3_Err != nil { | 278 | if templ_7745c5c3_Err != nil { | ||
| 253 | return templ_7745c5c3_Err | 279 | return templ_7745c5c3_Err | ||
| 254 | } | 280 | } | ||
| 255 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"") | 281 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"") |
| 256 | if templ_7745c5c3_Err != nil { | 282 | if templ_7745c5c3_Err != nil { | ||
| 257 | return templ_7745c5c3_Err | 283 | return templ_7745c5c3_Err | ||
| 258 | } | 284 | } | ||
| 259 | - | var templ_7745c5c3_Var18 string | 285 | + | var templ_7745c5c3_Var20 string |
| 260 | - | templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String()) | 286 | + | templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var19).String()) |
| 261 | if templ_7745c5c3_Err != nil { | 287 | if templ_7745c5c3_Err != nil { | ||
| 262 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | 288 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 1, Col: 0} | ||
| 263 | } | 289 | } | ||
| 264 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18) | 290 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20) |
| 265 | if templ_7745c5c3_Err != nil { | 291 | if templ_7745c5c3_Err != nil { | ||
| 266 | return templ_7745c5c3_Err | 292 | return templ_7745c5c3_Err | ||
| 267 | } | 293 | } | ||
| 268 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">") | 294 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">") |
| 269 | if templ_7745c5c3_Err != nil { | 295 | if templ_7745c5c3_Err != nil { | ||
| 270 | return templ_7745c5c3_Err | 296 | return templ_7745c5c3_Err | ||
| 271 | } | 297 | } | ||
| 272 | if l.Lede != "" { | 298 | if l.Lede != "" { | ||
| 273 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") | 299 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<section class=\"work-lede\" aria-label=\"Description\"><p class=\"lede\">") |
| 274 | if templ_7745c5c3_Err != nil { | 300 | if templ_7745c5c3_Err != nil { | ||
| 275 | return templ_7745c5c3_Err | 301 | return templ_7745c5c3_Err | ||
| 276 | } | 302 | } | ||
| 277 | - | var templ_7745c5c3_Var19 string | 303 | + | var templ_7745c5c3_Var21 string |
| 278 | - | templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) | 304 | + | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(l.Lede) |
| 279 | if templ_7745c5c3_Err != nil { | 305 | if templ_7745c5c3_Err != nil { | ||
| 280 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 42, Col: 81} | 306 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 47, Col: 81} |
| 281 | } | 307 | } | ||
| 282 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) | 308 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) |
| 283 | if templ_7745c5c3_Err != nil { | 309 | if templ_7745c5c3_Err != nil { | ||
| 284 | return templ_7745c5c3_Err | 310 | return templ_7745c5c3_Err | ||
| 285 | } | 311 | } | ||
| 286 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p></section>") | 312 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p></section>") |
| 287 | if templ_7745c5c3_Err != nil { | 313 | if templ_7745c5c3_Err != nil { | ||
| 288 | return templ_7745c5c3_Err | 314 | return templ_7745c5c3_Err | ||
| 289 | } | 315 | } | ||
| 290 | } | 316 | } | ||
| 291 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<main id=\"main\" tabindex=\"-1\">") | 317 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<main id=\"main\" tabindex=\"-1\">") |
| 292 | if templ_7745c5c3_Err != nil { | 318 | if templ_7745c5c3_Err != nil { | ||
| 293 | return templ_7745c5c3_Err | 319 | return templ_7745c5c3_Err | ||
| 294 | } | 320 | } | ||
| @@ -296,12 +322,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 296 | if templ_7745c5c3_Err != nil { | 322 | if templ_7745c5c3_Err != nil { | ||
| 297 | return templ_7745c5c3_Err | 323 | return templ_7745c5c3_Err | ||
| 298 | } | 324 | } | ||
| 299 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</main>") | 325 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</main>") |
| 300 | if templ_7745c5c3_Err != nil { | 326 | if templ_7745c5c3_Err != nil { | ||
| 301 | return templ_7745c5c3_Err | 327 | return templ_7745c5c3_Err | ||
| 302 | } | 328 | } | ||
| 303 | if l.HasRail && rail != nil { | 329 | if l.HasRail && rail != nil { | ||
| 304 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<aside class=\"rail\" aria-label=\"Context\">") | 330 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<aside class=\"rail\" aria-label=\"Context\">") |
| 305 | if templ_7745c5c3_Err != nil { | 331 | if templ_7745c5c3_Err != nil { | ||
| 306 | return templ_7745c5c3_Err | 332 | return templ_7745c5c3_Err | ||
| 307 | } | 333 | } | ||
| @@ -309,12 +335,12 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 309 | if templ_7745c5c3_Err != nil { | 335 | if templ_7745c5c3_Err != nil { | ||
| 310 | return templ_7745c5c3_Err | 336 | return templ_7745c5c3_Err | ||
| 311 | } | 337 | } | ||
| 312 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</aside>") | 338 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</aside>") |
| 313 | if templ_7745c5c3_Err != nil { | 339 | if templ_7745c5c3_Err != nil { | ||
| 314 | return templ_7745c5c3_Err | 340 | return templ_7745c5c3_Err | ||
| 315 | } | 341 | } | ||
| 316 | } | 342 | } | ||
| 317 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>") | 343 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</div>") |
| 318 | if templ_7745c5c3_Err != nil { | 344 | if templ_7745c5c3_Err != nil { | ||
| 319 | return templ_7745c5c3_Err | 345 | return templ_7745c5c3_Err | ||
| 320 | } | 346 | } | ||
| @@ -342,7 +368,7 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 342 | if templ_7745c5c3_Err != nil { | 368 | if templ_7745c5c3_Err != nil { | ||
| 343 | return templ_7745c5c3_Err | 369 | return templ_7745c5c3_Err | ||
| 344 | } | 370 | } | ||
| 345 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</body></html>") | 371 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</body></html>") |
| 346 | if templ_7745c5c3_Err != nil { | 372 | if templ_7745c5c3_Err != nil { | ||
| 347 | return templ_7745c5c3_Err | 373 | return templ_7745c5c3_Err | ||
| 348 | } | 374 | } | ||
| @@ -350,8 +376,8 @@ func Shell(l Layout, rail templ.Component) templ.Component { | |||||
| 350 | }) | 376 | }) | ||
| 351 | } | 377 | } | ||
| 352 | 378 | ||||
| 353 | - | // Header is the page header: the instance, the path, the ref switcher, the | 379 | + | // Header is the page header: the logo (home), the path, the ref switcher, |
| 354 | - | // "go to" palette and the owner; inside a repository, the tabs below. | 380 | + | // the "go to" palette and the owner; inside a repository, the tabs below. |
| 355 | func Header(l Layout) templ.Component { | 381 | func Header(l Layout) templ.Component { | ||
| 356 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | 382 | return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||
| 357 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | 383 | templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||
| @@ -368,288 +394,288 @@ func Header(l Layout) templ.Component { | |||||
| 368 | }() | 394 | }() | ||
| 369 | } | 395 | } | ||
| 370 | ctx = templ.InitializeContext(ctx) | 396 | ctx = templ.InitializeContext(ctx) | ||
| 371 | - | templ_7745c5c3_Var20 := templ.GetChildren(ctx) | 397 | + | templ_7745c5c3_Var22 := templ.GetChildren(ctx) |
| 372 | - | if templ_7745c5c3_Var20 == nil { | 398 | + | if templ_7745c5c3_Var22 == nil { |
| 373 | - | templ_7745c5c3_Var20 = templ.NopComponent | 399 | + | templ_7745c5c3_Var22 = templ.NopComponent |
| 374 | } | 400 | } | ||
| 375 | ctx = templ.ClearChildren(ctx) | 401 | ctx = templ.ClearChildren(ctx) | ||
| 376 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span> <span class=\"brand-name\">") | 402 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<header class=\"hdr\"><div class=\"hdr-top\"><a class=\"brand\" href=\"/\" data-g=\"h\" aria-label=\"home\" title=\"home (g h)\"><span class=\"brand-mark\" aria-hidden=\"true\"></span></a> ") |
| 377 | - | if templ_7745c5c3_Err != nil { | |||
| 378 | - | return templ_7745c5c3_Err | |||
| 379 | - | } | |||
| 380 | - | var templ_7745c5c3_Var21 string | |||
| 381 | - | templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(l.Instance) | |||
| 382 | - | if templ_7745c5c3_Err != nil { | |||
| 383 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 70, Col: 41} | |||
| 384 | - | } | |||
| 385 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) | |||
| 386 | - | if templ_7745c5c3_Err != nil { | |||
| 387 | - | return templ_7745c5c3_Err | |||
| 388 | - | } | |||
| 389 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</span></a> ") | |||
| 390 | if templ_7745c5c3_Err != nil { | 403 | if templ_7745c5c3_Err != nil { | ||
| 391 | return templ_7745c5c3_Err | 404 | return templ_7745c5c3_Err | ||
| 392 | } | 405 | } | ||
| 393 | if len(l.Crumbs) > 0 { | 406 | if len(l.Crumbs) > 0 { | ||
| 394 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") | 407 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<nav class=\"crumbs\" aria-label=\"Path\"><ol>") |
| 395 | if templ_7745c5c3_Err != nil { | 408 | if templ_7745c5c3_Err != nil { | ||
| 396 | return templ_7745c5c3_Err | 409 | return templ_7745c5c3_Err | ||
| 397 | } | 410 | } | ||
| 398 | for _, c := range l.Crumbs { | 411 | for _, c := range l.Crumbs { | ||
| 399 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<li>") | 412 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<li>") |
| 400 | if templ_7745c5c3_Err != nil { | 413 | if templ_7745c5c3_Err != nil { | ||
| 401 | return templ_7745c5c3_Err | 414 | return templ_7745c5c3_Err | ||
| 402 | } | 415 | } | ||
| 403 | if c.Href != "" { | 416 | if c.Href != "" { | ||
| 404 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a href=\"") | 417 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<a href=\"") |
| 405 | if templ_7745c5c3_Err != nil { | 418 | if templ_7745c5c3_Err != nil { | ||
| 406 | return templ_7745c5c3_Err | 419 | return templ_7745c5c3_Err | ||
| 407 | } | 420 | } | ||
| 408 | - | var templ_7745c5c3_Var22 templ.SafeURL | 421 | + | var templ_7745c5c3_Var23 templ.SafeURL |
| 409 | - | templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) | 422 | + | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href)) |
| 410 | if templ_7745c5c3_Err != nil { | 423 | if templ_7745c5c3_Err != nil { | ||
| 411 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 79, Col: 38} | 424 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 83, Col: 38} |
| 412 | } | 425 | } | ||
| 413 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | 426 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) |
| 414 | if templ_7745c5c3_Err != nil { | 427 | if templ_7745c5c3_Err != nil { | ||
| 415 | return templ_7745c5c3_Err | 428 | return templ_7745c5c3_Err | ||
| 416 | } | 429 | } | ||
| 417 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\"") | 430 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"") |
| 418 | if templ_7745c5c3_Err != nil { | 431 | if templ_7745c5c3_Err != nil { | ||
| 419 | return templ_7745c5c3_Err | 432 | return templ_7745c5c3_Err | ||
| 420 | } | 433 | } | ||
| 421 | if c.Current { | 434 | if c.Current { | ||
| 422 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " aria-current=\"page\"") | 435 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " aria-current=\"page\"") |
| 423 | if templ_7745c5c3_Err != nil { | 436 | if templ_7745c5c3_Err != nil { | ||
| 424 | return templ_7745c5c3_Err | 437 | return templ_7745c5c3_Err | ||
| 425 | } | 438 | } | ||
| 426 | } | 439 | } | ||
| 427 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, ">") | 440 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, ">") |
| 428 | if templ_7745c5c3_Err != nil { | 441 | if templ_7745c5c3_Err != nil { | ||
| 429 | return templ_7745c5c3_Err | 442 | return templ_7745c5c3_Err | ||
| 430 | } | 443 | } | ||
| 431 | - | var templ_7745c5c3_Var23 string | 444 | + | var templ_7745c5c3_Var24 string |
| 432 | - | templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | 445 | + | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) |
| 433 | if templ_7745c5c3_Err != nil { | 446 | if templ_7745c5c3_Err != nil { | ||
| 434 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 83, Col: 19} | 447 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 87, Col: 19} |
| 435 | } | 448 | } | ||
| 436 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | 449 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) |
| 437 | if templ_7745c5c3_Err != nil { | 450 | if templ_7745c5c3_Err != nil { | ||
| 438 | return templ_7745c5c3_Err | 451 | return templ_7745c5c3_Err | ||
| 439 | } | 452 | } | ||
| 440 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</a>") | 453 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</a>") |
| 441 | if templ_7745c5c3_Err != nil { | 454 | if templ_7745c5c3_Err != nil { | ||
| 442 | return templ_7745c5c3_Err | 455 | return templ_7745c5c3_Err | ||
| 443 | } | 456 | } | ||
| 444 | } else { | 457 | } else { | ||
| 445 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<span") | 458 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<span") |
| 446 | if templ_7745c5c3_Err != nil { | 459 | if templ_7745c5c3_Err != nil { | ||
| 447 | return templ_7745c5c3_Err | 460 | return templ_7745c5c3_Err | ||
| 448 | } | 461 | } | ||
| 449 | if c.Current { | 462 | if c.Current { | ||
| 450 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " aria-current=\"page\"") | 463 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " aria-current=\"page\"") |
| 451 | if templ_7745c5c3_Err != nil { | 464 | if templ_7745c5c3_Err != nil { | ||
| 452 | return templ_7745c5c3_Err | 465 | return templ_7745c5c3_Err | ||
| 453 | } | 466 | } | ||
| 454 | } | 467 | } | ||
| 455 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, ">") | 468 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ">") |
| 456 | if templ_7745c5c3_Err != nil { | 469 | if templ_7745c5c3_Err != nil { | ||
| 457 | return templ_7745c5c3_Err | 470 | return templ_7745c5c3_Err | ||
| 458 | } | 471 | } | ||
| 459 | - | var templ_7745c5c3_Var24 string | 472 | + | var templ_7745c5c3_Var25 string |
| 460 | - | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) | 473 | + | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label) |
| 461 | if templ_7745c5c3_Err != nil { | 474 | if templ_7745c5c3_Err != nil { | ||
| 462 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 89, Col: 19} | 475 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 93, Col: 19} |
| 463 | } | 476 | } | ||
| 464 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | 477 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) |
| 465 | if templ_7745c5c3_Err != nil { | 478 | if templ_7745c5c3_Err != nil { | ||
| 466 | return templ_7745c5c3_Err | 479 | return templ_7745c5c3_Err | ||
| 467 | } | 480 | } | ||
| 468 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</span>") | 481 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</span>") |
| 469 | if templ_7745c5c3_Err != nil { | 482 | if templ_7745c5c3_Err != nil { | ||
| 470 | return templ_7745c5c3_Err | 483 | return templ_7745c5c3_Err | ||
| 471 | } | 484 | } | ||
| 472 | } | 485 | } | ||
| 473 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</li>") | 486 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</li>") |
| 474 | if templ_7745c5c3_Err != nil { | 487 | if templ_7745c5c3_Err != nil { | ||
| 475 | return templ_7745c5c3_Err | 488 | return templ_7745c5c3_Err | ||
| 476 | } | 489 | } | ||
| 477 | } | 490 | } | ||
| 478 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</ol></nav>") | 491 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</ol></nav>") |
| 479 | if templ_7745c5c3_Err != nil { | 492 | if templ_7745c5c3_Err != nil { | ||
| 480 | return templ_7745c5c3_Err | 493 | return templ_7745c5c3_Err | ||
| 481 | } | 494 | } | ||
| 482 | } | 495 | } | ||
| 483 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"hdr-acts\">") | 496 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<div class=\"hdr-acts\">") |
| 484 | if templ_7745c5c3_Err != nil { | 497 | if templ_7745c5c3_Err != nil { | ||
| 485 | return templ_7745c5c3_Err | 498 | return templ_7745c5c3_Err | ||
| 486 | } | 499 | } | ||
| 487 | if l.Ref != "" { | 500 | if l.Ref != "" { | ||
| 488 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<a class=\"btn ref\" href=\"") | 501 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<a class=\"btn ref\" href=\"") |
| 489 | if templ_7745c5c3_Err != nil { | 502 | if templ_7745c5c3_Err != nil { | ||
| 490 | return templ_7745c5c3_Err | 503 | return templ_7745c5c3_Err | ||
| 491 | } | 504 | } | ||
| 492 | - | var templ_7745c5c3_Var25 templ.SafeURL | 505 | + | var templ_7745c5c3_Var26 templ.SafeURL |
| 493 | - | templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) | 506 | + | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(repoHref(l.Repo, "refs"))) |
| 494 | if templ_7745c5c3_Err != nil { | 507 | if templ_7745c5c3_Err != nil { | ||
| 495 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 100, Col: 52} | 508 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 104, Col: 52} |
| 496 | } | 509 | } | ||
| 497 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | 510 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) |
| 498 | if templ_7745c5c3_Err != nil { | 511 | if templ_7745c5c3_Err != nil { | ||
| 499 | return templ_7745c5c3_Err | 512 | return templ_7745c5c3_Err | ||
| 500 | } | 513 | } | ||
| 501 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") | 514 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\" data-fire=\"refs\" aria-keyshortcuts=\"b\" title=\"switch branch or tag (b)\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span> <span class=\"ref-name\">") |
| 502 | if templ_7745c5c3_Err != nil { | 515 | if templ_7745c5c3_Err != nil { | ||
| 503 | return templ_7745c5c3_Err | 516 | return templ_7745c5c3_Err | ||
| 504 | } | 517 | } | ||
| 505 | - | var templ_7745c5c3_Var26 string | 518 | + | var templ_7745c5c3_Var27 string |
| 506 | - | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) | 519 | + | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(l.Ref) |
| 507 | if templ_7745c5c3_Err != nil { | 520 | if templ_7745c5c3_Err != nil { | ||
| 508 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 106, Col: 36} | 521 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 110, Col: 36} |
| 509 | } | 522 | } | ||
| 510 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | 523 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) |
| 511 | if templ_7745c5c3_Err != nil { | 524 | if templ_7745c5c3_Err != nil { | ||
| 512 | return templ_7745c5c3_Err | 525 | return templ_7745c5c3_Err | ||
| 513 | } | 526 | } | ||
| 514 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span> <span class=\"sha\">") | 527 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span> <span class=\"sha\">") |
| 515 | if templ_7745c5c3_Err != nil { | 528 | if templ_7745c5c3_Err != nil { | ||
| 516 | return templ_7745c5c3_Err | 529 | return templ_7745c5c3_Err | ||
| 517 | } | 530 | } | ||
| 518 | - | var templ_7745c5c3_Var27 string | 531 | + | var templ_7745c5c3_Var28 string |
| 519 | - | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) | 532 | + | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(l.Short) |
| 520 | if templ_7745c5c3_Err != nil { | 533 | if templ_7745c5c3_Err != nil { | ||
| 521 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 107, Col: 33} | 534 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 111, Col: 33} |
| 522 | } | 535 | } | ||
| 523 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | 536 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) |
| 524 | if templ_7745c5c3_Err != nil { | 537 | if templ_7745c5c3_Err != nil { | ||
| 525 | return templ_7745c5c3_Err | 538 | return templ_7745c5c3_Err | ||
| 526 | } | 539 | } | ||
| 527 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span></a> ") | 540 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</span></a> ") |
| 528 | if templ_7745c5c3_Err != nil { | 541 | if templ_7745c5c3_Err != nil { | ||
| 529 | return templ_7745c5c3_Err | 542 | return templ_7745c5c3_Err | ||
| 530 | } | 543 | } | ||
| 531 | } | 544 | } | ||
| 532 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> <button type=\"button\" class=\"btn icon\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\" aria-label=\"Keyboard shortcuts\" title=\"keyboard shortcuts (?)\">?</button> ") | 545 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<button type=\"button\" class=\"btn search\" data-js aria-haspopup=\"dialog\" aria-controls=\"panel-cmd\" aria-keyshortcuts=\"Control+K :\" title=\"go to a section, ref, file or repo (ctrl k or :)\" data-fire=\"cmd\"><span class=\"ico i-search\" aria-hidden=\"true\"></span> <span class=\"search-label\">Go to…</span> <kbd aria-hidden=\"true\">ctrl k</kbd></button> ") |
| 533 | if templ_7745c5c3_Err != nil { | 546 | if templ_7745c5c3_Err != nil { | ||
| 534 | return templ_7745c5c3_Err | 547 | return templ_7745c5c3_Err | ||
| 535 | } | 548 | } | ||
| 536 | if SignedIn(ctx) { | 549 | if SignedIn(ctx) { | ||
| 537 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\">owner</span> ") | 550 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<form class=\"owner\" method=\"post\" action=\"/-/logout\"><span class=\"owner-badge\" title=\"signed in as the owner\"><span class=\"ico i-user\" aria-hidden=\"true\"></span>") |
| 551 | + | if templ_7745c5c3_Err != nil { | |||
| 552 | + | return templ_7745c5c3_Err | |||
| 553 | + | } | |||
| 554 | + | var templ_7745c5c3_Var29 string | |||
| 555 | + | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(l.Owner) | |||
| 556 | + | if templ_7745c5c3_Err != nil { | |||
| 557 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 130, Col: 123} | |||
| 558 | + | } | |||
| 559 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | |||
| 560 | + | if templ_7745c5c3_Err != nil { | |||
| 561 | + | return templ_7745c5c3_Err | |||
| 562 | + | } | |||
| 563 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span> ") | |||
| 538 | if templ_7745c5c3_Err != nil { | 564 | if templ_7745c5c3_Err != nil { | ||
| 539 | return templ_7745c5c3_Err | 565 | return templ_7745c5c3_Err | ||
| 540 | } | 566 | } | ||
| 541 | if l.Passkeys { | 567 | if l.Passkeys { | ||
| 542 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a class=\"btn\" href=\"/-/passkeys\" title=\"add or remove the passkeys that sign you in\">Passkeys</a> ") | 568 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a class=\"btn icon\" href=\"/-/passkeys\" aria-label=\"Passkeys\" title=\"passkeys: add or remove the passkeys that sign you in\"><span class=\"ico i-key\" aria-hidden=\"true\"></span></a> ") |
| 543 | if templ_7745c5c3_Err != nil { | 569 | if templ_7745c5c3_Err != nil { | ||
| 544 | return templ_7745c5c3_Err | 570 | return templ_7745c5c3_Err | ||
| 545 | } | 571 | } | ||
| 546 | } | 572 | } | ||
| 547 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<button type=\"submit\" class=\"btn\" title=\"sign out of this browser\">Sign out</button></form>") | 573 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<button type=\"submit\" class=\"btn icon\" aria-label=\"Sign out\" title=\"sign out of this browser\"><span class=\"ico i-log-out\" aria-hidden=\"true\"></span></button></form>") |
| 548 | if templ_7745c5c3_Err != nil { | 574 | if templ_7745c5c3_Err != nil { | ||
| 549 | return templ_7745c5c3_Err | 575 | return templ_7745c5c3_Err | ||
| 550 | } | 576 | } | ||
| 551 | } else if l.Passkeys { | 577 | } else if l.Passkeys { | ||
| 552 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a class=\"btn\" href=\"/-/login\" title=\"sign in with a passkey\">Sign in</a>") | 578 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<a class=\"btn icon\" href=\"/-/login\" aria-label=\"Sign in\" title=\"sign in with a passkey\"><span class=\"ico i-log-in\" aria-hidden=\"true\"></span></a>") |
| 553 | if templ_7745c5c3_Err != nil { | 579 | if templ_7745c5c3_Err != nil { | ||
| 554 | return templ_7745c5c3_Err | 580 | return templ_7745c5c3_Err | ||
| 555 | } | 581 | } | ||
| 556 | } | 582 | } | ||
| 557 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</div></div>") | 583 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</div></div>") |
| 558 | if templ_7745c5c3_Err != nil { | 584 | if templ_7745c5c3_Err != nil { | ||
| 559 | return templ_7745c5c3_Err | 585 | return templ_7745c5c3_Err | ||
| 560 | } | 586 | } | ||
| 561 | if len(l.Sections) > 0 { | 587 | if len(l.Sections) > 0 { | ||
| 562 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") | 588 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<nav class=\"tabs\" aria-label=\"Sections\"><ul>") |
| 563 | if templ_7745c5c3_Err != nil { | 589 | if templ_7745c5c3_Err != nil { | ||
| 564 | return templ_7745c5c3_Err | 590 | return templ_7745c5c3_Err | ||
| 565 | } | 591 | } | ||
| 566 | for _, s := range l.Sections { | 592 | for _, s := range l.Sections { | ||
| 567 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<li><a href=\"") | 593 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<li><a href=\"") |
| 568 | if templ_7745c5c3_Err != nil { | 594 | if templ_7745c5c3_Err != nil { | ||
| 569 | return templ_7745c5c3_Err | 595 | return templ_7745c5c3_Err | ||
| 570 | } | 596 | } | ||
| 571 | - | var templ_7745c5c3_Var28 templ.SafeURL | 597 | + | var templ_7745c5c3_Var30 templ.SafeURL |
| 572 | - | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) | 598 | + | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href)) |
| 573 | if templ_7745c5c3_Err != nil { | 599 | if templ_7745c5c3_Err != nil { | ||
| 574 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 144, Col: 36} | 600 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 147, Col: 36} |
| 575 | } | 601 | } | ||
| 576 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | 602 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) |
| 577 | if templ_7745c5c3_Err != nil { | 603 | if templ_7745c5c3_Err != nil { | ||
| 578 | return templ_7745c5c3_Err | 604 | return templ_7745c5c3_Err | ||
| 579 | } | 605 | } | ||
| 580 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\" data-key=\"") | 606 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" data-key=\"") |
| 581 | if templ_7745c5c3_Err != nil { | 607 | if templ_7745c5c3_Err != nil { | ||
| 582 | return templ_7745c5c3_Err | 608 | return templ_7745c5c3_Err | ||
| 583 | } | 609 | } | ||
| 584 | - | var templ_7745c5c3_Var29 string | 610 | + | var templ_7745c5c3_Var31 string |
| 585 | - | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | 611 | + | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) |
| 586 | if templ_7745c5c3_Err != nil { | 612 | if templ_7745c5c3_Err != nil { | ||
| 587 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 145, Col: 24} | 613 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 148, Col: 24} |
| 588 | } | 614 | } | ||
| 589 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29) | 615 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31) |
| 590 | if templ_7745c5c3_Err != nil { | 616 | if templ_7745c5c3_Err != nil { | ||
| 591 | return templ_7745c5c3_Err | 617 | return templ_7745c5c3_Err | ||
| 592 | } | 618 | } | ||
| 593 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\" aria-keyshortcuts=\"") | 619 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\" aria-keyshortcuts=\"") |
| 594 | if templ_7745c5c3_Err != nil { | 620 | if templ_7745c5c3_Err != nil { | ||
| 595 | return templ_7745c5c3_Err | 621 | return templ_7745c5c3_Err | ||
| 596 | } | 622 | } | ||
| 597 | - | var templ_7745c5c3_Var30 string | 623 | + | var templ_7745c5c3_Var32 string |
| 598 | - | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) | 624 | + | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Key) |
| 599 | if templ_7745c5c3_Err != nil { | 625 | if templ_7745c5c3_Err != nil { | ||
| 600 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 146, Col: 33} | 626 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 149, Col: 33} |
| 601 | } | 627 | } | ||
| 602 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30) | 628 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32) |
| 603 | if templ_7745c5c3_Err != nil { | 629 | if templ_7745c5c3_Err != nil { | ||
| 604 | return templ_7745c5c3_Err | 630 | return templ_7745c5c3_Err | ||
| 605 | } | 631 | } | ||
| 606 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\"") | 632 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\"") |
| 607 | if templ_7745c5c3_Err != nil { | 633 | if templ_7745c5c3_Err != nil { | ||
| 608 | return templ_7745c5c3_Err | 634 | return templ_7745c5c3_Err | ||
| 609 | } | 635 | } | ||
| 610 | if s.Current { | 636 | if s.Current { | ||
| 611 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " aria-current=\"page\"") | 637 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, " aria-current=\"page\"") |
| 612 | if templ_7745c5c3_Err != nil { | 638 | if templ_7745c5c3_Err != nil { | ||
| 613 | return templ_7745c5c3_Err | 639 | return templ_7745c5c3_Err | ||
| 614 | } | 640 | } | ||
| 615 | } | 641 | } | ||
| 616 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, ">") | 642 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, ">") |
| 617 | if templ_7745c5c3_Err != nil { | 643 | if templ_7745c5c3_Err != nil { | ||
| 618 | return templ_7745c5c3_Err | 644 | return templ_7745c5c3_Err | ||
| 619 | } | 645 | } | ||
| 620 | - | var templ_7745c5c3_Var31 string | 646 | + | var templ_7745c5c3_Var33 string |
| 621 | - | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) | 647 | + | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) |
| 622 | if templ_7745c5c3_Err != nil { | 648 | if templ_7745c5c3_Err != nil { | ||
| 623 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 150, Col: 17} | 649 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 153, Col: 17} |
| 624 | } | 650 | } | ||
| 625 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | 651 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) |
| 626 | if templ_7745c5c3_Err != nil { | 652 | if templ_7745c5c3_Err != nil { | ||
| 627 | return templ_7745c5c3_Err | 653 | return templ_7745c5c3_Err | ||
| 628 | } | 654 | } | ||
| 629 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<kbd aria-hidden=\"true\">") | 655 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<kbd aria-hidden=\"true\">") |
| 630 | if templ_7745c5c3_Err != nil { | 656 | if templ_7745c5c3_Err != nil { | ||
| 631 | return templ_7745c5c3_Err | 657 | return templ_7745c5c3_Err | ||
| 632 | } | 658 | } | ||
| 633 | - | var templ_7745c5c3_Var32 string | 659 | + | var templ_7745c5c3_Var34 string |
| 634 | - | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) | 660 | + | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(s.Key) |
| 635 | if templ_7745c5c3_Err != nil { | 661 | if templ_7745c5c3_Err != nil { | ||
| 636 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 150, Col: 50} | 662 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 153, Col: 50} |
| 637 | } | 663 | } | ||
| 638 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | 664 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) |
| 639 | if templ_7745c5c3_Err != nil { | 665 | if templ_7745c5c3_Err != nil { | ||
| 640 | return templ_7745c5c3_Err | 666 | return templ_7745c5c3_Err | ||
| 641 | } | 667 | } | ||
| 642 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</kbd></a></li>") | 668 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</kbd></a></li>") |
| 643 | if templ_7745c5c3_Err != nil { | 669 | if templ_7745c5c3_Err != nil { | ||
| 644 | return templ_7745c5c3_Err | 670 | return templ_7745c5c3_Err | ||
| 645 | } | 671 | } | ||
| 646 | } | 672 | } | ||
| 647 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</ul></nav>") | 673 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</ul></nav>") |
| 648 | if templ_7745c5c3_Err != nil { | 674 | if templ_7745c5c3_Err != nil { | ||
| 649 | return templ_7745c5c3_Err | 675 | return templ_7745c5c3_Err | ||
| 650 | } | 676 | } | ||
| 651 | } | 677 | } | ||
| 652 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</header>") | 678 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</header>") |
| 653 | if templ_7745c5c3_Err != nil { | 679 | if templ_7745c5c3_Err != nil { | ||
| 654 | return templ_7745c5c3_Err | 680 | return templ_7745c5c3_Err | ||
| 655 | } | 681 | } | ||
| @@ -675,38 +701,38 @@ func Footer(l Layout) templ.Component { | |||||
| 675 | }() | 701 | }() | ||
| 676 | } | 702 | } | ||
| 677 | ctx = templ.InitializeContext(ctx) | 703 | ctx = templ.InitializeContext(ctx) | ||
| 678 | - | templ_7745c5c3_Var33 := templ.GetChildren(ctx) | 704 | + | templ_7745c5c3_Var35 := templ.GetChildren(ctx) |
| 679 | - | if templ_7745c5c3_Var33 == nil { | 705 | + | if templ_7745c5c3_Var35 == nil { |
| 680 | - | templ_7745c5c3_Var33 = templ.NopComponent | 706 | + | templ_7745c5c3_Var35 = templ.NopComponent |
| 681 | } | 707 | } | ||
| 682 | ctx = templ.ClearChildren(ctx) | 708 | ctx = templ.ClearChildren(ctx) | ||
| 683 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<footer class=\"foot\"><span>ermite ") | 709 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<footer class=\"foot\"><span>ermite ") |
| 684 | if templ_7745c5c3_Err != nil { | 710 | if templ_7745c5c3_Err != nil { | ||
| 685 | return templ_7745c5c3_Err | 711 | return templ_7745c5c3_Err | ||
| 686 | } | 712 | } | ||
| 687 | - | var templ_7745c5c3_Var34 string | 713 | + | var templ_7745c5c3_Var36 string |
| 688 | - | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) | 714 | + | templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version) |
| 689 | if templ_7745c5c3_Err != nil { | 715 | if templ_7745c5c3_Err != nil { | ||
| 690 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 163, Col: 26} | 716 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 166, Col: 26} |
| 691 | } | 717 | } | ||
| 692 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) | 718 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) |
| 693 | if templ_7745c5c3_Err != nil { | 719 | if templ_7745c5c3_Err != nil { | ||
| 694 | return templ_7745c5c3_Err | 720 | return templ_7745c5c3_Err | ||
| 695 | } | 721 | } | ||
| 696 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span> <a href=\"") | 722 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</span> <a href=\"") |
| 697 | if templ_7745c5c3_Err != nil { | 723 | if templ_7745c5c3_Err != nil { | ||
| 698 | return templ_7745c5c3_Err | 724 | return templ_7745c5c3_Err | ||
| 699 | } | 725 | } | ||
| 700 | - | var templ_7745c5c3_Var35 templ.SafeURL | 726 | + | var templ_7745c5c3_Var37 templ.SafeURL |
| 701 | - | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) | 727 | + | templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL)) |
| 702 | if templ_7745c5c3_Err != nil { | 728 | if templ_7745c5c3_Err != nil { | ||
| 703 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 164, Col: 38} | 729 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 167, Col: 38} |
| 704 | } | 730 | } | ||
| 705 | - | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | 731 | + | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) |
| 706 | if templ_7745c5c3_Err != nil { | 732 | if templ_7745c5c3_Err != nil { | ||
| 707 | return templ_7745c5c3_Err | 733 | return templ_7745c5c3_Err | ||
| 708 | } | 734 | } | ||
| 709 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <span class=\"foot-keys\"><kbd>?</kbd> keyboard shortcuts</span></footer>") | 735 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button></footer>") |
| 710 | if templ_7745c5c3_Err != nil { | 736 | if templ_7745c5c3_Err != nil { | ||
| 711 | return templ_7745c5c3_Err | 737 | return templ_7745c5c3_Err | ||
| 712 | } | 738 | } | ||
| @@ -734,12 +760,12 @@ func Hint(l Layout) templ.Component { | |||||
| 734 | }() | 760 | }() | ||
| 735 | } | 761 | } | ||
| 736 | ctx = templ.InitializeContext(ctx) | 762 | ctx = templ.InitializeContext(ctx) | ||
| 737 | - | templ_7745c5c3_Var36 := templ.GetChildren(ctx) | 763 | + | templ_7745c5c3_Var38 := templ.GetChildren(ctx) |
| 738 | - | if templ_7745c5c3_Var36 == nil { | 764 | + | if templ_7745c5c3_Var38 == nil { |
| 739 | - | templ_7745c5c3_Var36 = templ.NopComponent | 765 | + | templ_7745c5c3_Var38 = templ.NopComponent |
| 740 | } | 766 | } | ||
| 741 | ctx = templ.ClearChildren(ctx) | 767 | ctx = templ.ClearChildren(ctx) | ||
| 742 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") | 768 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<div class=\"hint\" role=\"status\" aria-live=\"polite\"><span class=\"hint-nav\" :text=\"document.body.dataset.nav || ''\"></span> <span class=\"hint-sel\" :text=\"document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''\"></span> <span class=\"hint-flash\" :text=\"document.body.dataset.flash || ''\"></span>") |
| 743 | if templ_7745c5c3_Err != nil { | 769 | if templ_7745c5c3_Err != nil { | ||
| 744 | return templ_7745c5c3_Err | 770 | return templ_7745c5c3_Err | ||
| 745 | } | 771 | } | ||
| @@ -747,7 +773,7 @@ func Hint(l Layout) templ.Component { | |||||
| 747 | if templ_7745c5c3_Err != nil { | 773 | if templ_7745c5c3_Err != nil { | ||
| 748 | return templ_7745c5c3_Err | 774 | return templ_7745c5c3_Err | ||
| 749 | } | 775 | } | ||
| 750 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</div>") | 776 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</div>") |
| 751 | if templ_7745c5c3_Err != nil { | 777 | if templ_7745c5c3_Err != nil { | ||
| 752 | return templ_7745c5c3_Err | 778 | return templ_7745c5c3_Err | ||
| 753 | } | 779 | } | ||
A internal/adapters/web/static/app/apple-touch-icon.png +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/web/static/app/icon-192.png +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/web/static/app/icon-512.png +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/web/static/app/icon-maskable-512.png +0 -0 binary file: no text diff
binary file: no text diff
M internal/adapters/web/static/css/10-base.css +3 -0
| @@ -17,6 +17,9 @@ | |||
| 17 | 17 | /* a cross-document fade between pages, where the browser has it */ | |
| 18 | 18 | @view-transition { navigation: auto; } | |
| 19 | 19 | ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 120ms; } | |
| 20 | + | /* the snapshots fade over this layer: transparent, it shows a white canvas | |
| 21 | + | for a frame when the new page is a little late */ | |
| 22 | + | ::view-transition { background: var(--bg-canvas); } | |
| 20 | 23 | @media (prefers-reduced-motion: reduce) { | |
| 21 | 24 | @view-transition { navigation: none; } | |
| 22 | 25 | } | |
| @@ -17,6 +17,9 @@ | |||||
| 17 | /* a cross-document fade between pages, where the browser has it */ | 17 | /* a cross-document fade between pages, where the browser has it */ | ||
| 18 | @view-transition { navigation: auto; } | 18 | @view-transition { navigation: auto; } | ||
| 19 | ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 120ms; } | 19 | ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 120ms; } | ||
| 20 | + | /* the snapshots fade over this layer: transparent, it shows a white canvas | |||
| 21 | + | for a frame when the new page is a little late */ | |||
| 22 | + | ::view-transition { background: var(--bg-canvas); } | |||
| 20 | @media (prefers-reduced-motion: reduce) { | 23 | @media (prefers-reduced-motion: reduce) { | ||
| 21 | @view-transition { navigation: none; } | 24 | @view-transition { navigation: none; } | ||
| 22 | } | 25 | } | ||
M internal/adapters/web/static/css/20-shell.css +15 -8
| @@ -21,6 +21,10 @@ | |||
| 21 | 21 | .i-search { --i: url(icons/search.svg); } | |
| 22 | 22 | .i-file { --i: url(icons/file.svg); } | |
| 23 | 23 | .i-tag { --i: url(icons/tag.svg); } | |
| 24 | + | .i-user { --i: url(icons/user.svg); } | |
| 25 | + | .i-key { --i: url(icons/key.svg); } | |
| 26 | + | .i-log-in { --i: url(icons/log-in.svg); } | |
| 27 | + | .i-log-out { --i: url(icons/log-out.svg); } | |
| 24 | 28 | ||
| 25 | 29 | .pill { | |
| 26 | 30 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | |
| @@ -48,9 +52,7 @@ | |||
| 48 | 52 | max-width: var(--page); margin: 0 auto; height: 56px; padding: 0 var(--pad); | |
| 49 | 53 | } | |
| 50 | 54 | .brand { | |
| 51 | - | display: flex; align-items: center; gap: var(--s2); flex: none; | |
| 52 | - | font: 700 var(--fs) / 1 var(--mono); color: var(--ink-hi); text-decoration: none; | |
| 53 | - | & .brand-name { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 55 | + | display: flex; align-items: center; flex: none; text-decoration: none; | |
| 54 | 56 | &:hover { text-decoration: none; } | |
| 55 | 57 | } | |
| 56 | 58 | /* the hermit by his fire: a grey picture in its own rounded square */ | |
| @@ -78,7 +80,10 @@ | |||
| 78 | 80 | } | |
| 79 | 81 | .owner { | |
| 80 | 82 | display: flex; align-items: center; gap: var(--s2); | |
| 81 | - | & .owner-badge { font: 600 var(--fs-xs) / 1 var(--mono); color: var(--ink-dim); padding: 0 var(--s1); } | |
| 83 | + | & .owner-badge { | |
| 84 | + | display: inline-flex; align-items: center; gap: 6px; max-width: 20ch; padding: 0 var(--s1); | |
| 85 | + | font: 600 var(--fs-s) / 1 var(--mono); color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; | |
| 86 | + | } | |
| 82 | 87 | } | |
| 83 | 88 | ||
| 84 | 89 | .tabs { | |
| @@ -103,6 +108,9 @@ | |||
| 103 | 108 | flex: 1; width: 100%; max-width: var(--page); margin: 0 auto; | |
| 104 | 109 | padding: var(--s5) var(--pad) var(--s7); | |
| 105 | 110 | display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s5); align-items: start; | |
| 111 | + | /* .work grows to fill the page (flex: 1): without this, the rows share the | |
| 112 | + | spare height and a short content pushes the rail down */ | |
| 113 | + | align-content: start; | |
| 106 | 114 | } | |
| 107 | 115 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | |
| 108 | 116 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | |
| @@ -156,7 +164,8 @@ | |||
| 156 | 164 | font-size: var(--fs-s); color: var(--ink-mute); | |
| 157 | 165 | &::before { content: ""; flex-basis: 100%; height: 1px; margin-bottom: var(--s2); background: var(--border); } | |
| 158 | 166 | & a { color: var(--ink-dim); } | |
| 159 | - | & .foot-keys { margin-left: auto; } | |
| 167 | + | & .foot-keys { margin-left: auto; color: var(--ink-dim); } | |
| 168 | + | & .foot-keys:hover { color: var(--ink-hi); } | |
| 160 | 169 | } | |
| 161 | 170 | ||
| 162 | 171 | /* hint: bottom right, only while it has something to say */ | |
| @@ -187,14 +196,12 @@ | |||
| 187 | 196 | @media (max-width: 720px) { | |
| 188 | 197 | :root { --pad: var(--s4); } | |
| 189 | 198 | .hdr-top { gap: var(--s3); height: 52px; } | |
| 190 | - | .brand-name { display: none; } | |
| 191 | 199 | .crumbs li:not(:last-child) { display: none; } | |
| 192 | - | .ref .sha, .owner-badge, .hdr-acts .icon { display: none; } | |
| 200 | + | .ref .sha, .owner .owner-badge { display: none; } | |
| 193 | 201 | .ref { max-width: 16ch; } | |
| 194 | 202 | .work { padding-top: var(--s4); } | |
| 195 | 203 | .rail { grid-template-columns: minmax(0, 1fr); } | |
| 196 | 204 | .tabs kbd { display: none; } | |
| 197 | - | .foot .foot-keys { display: none; } | |
| 198 | 205 | .list .c-who, .list .c-desc, .list .c-sub, .list .c-ref, .list .c-size, .list .c-lang { display: none; } | |
| 199 | 206 | } | |
| 200 | 207 | } | |
| @@ -21,6 +21,10 @@ | |||||
| 21 | .i-search { --i: url(icons/search.svg); } | 21 | .i-search { --i: url(icons/search.svg); } | ||
| 22 | .i-file { --i: url(icons/file.svg); } | 22 | .i-file { --i: url(icons/file.svg); } | ||
| 23 | .i-tag { --i: url(icons/tag.svg); } | 23 | .i-tag { --i: url(icons/tag.svg); } | ||
| 24 | + | .i-user { --i: url(icons/user.svg); } | |||
| 25 | + | .i-key { --i: url(icons/key.svg); } | |||
| 26 | + | .i-log-in { --i: url(icons/log-in.svg); } | |||
| 27 | + | .i-log-out { --i: url(icons/log-out.svg); } | |||
| 24 | 28 | ||||
| 25 | .pill { | 29 | .pill { | ||
| 26 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | 30 | display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1); | ||
| @@ -48,9 +52,7 @@ | |||||
| 48 | max-width: var(--page); margin: 0 auto; height: 56px; padding: 0 var(--pad); | 52 | max-width: var(--page); margin: 0 auto; height: 56px; padding: 0 var(--pad); | ||
| 49 | } | 53 | } | ||
| 50 | .brand { | 54 | .brand { | ||
| 51 | - | display: flex; align-items: center; gap: var(--s2); flex: none; | 55 | + | display: flex; align-items: center; flex: none; text-decoration: none; |
| 52 | - | font: 700 var(--fs) / 1 var(--mono); color: var(--ink-hi); text-decoration: none; | |||
| 53 | - | & .brand-name { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |||
| 54 | &:hover { text-decoration: none; } | 56 | &:hover { text-decoration: none; } | ||
| 55 | } | 57 | } | ||
| 56 | /* the hermit by his fire: a grey picture in its own rounded square */ | 58 | /* the hermit by his fire: a grey picture in its own rounded square */ | ||
| @@ -78,7 +80,10 @@ | |||||
| 78 | } | 80 | } | ||
| 79 | .owner { | 81 | .owner { | ||
| 80 | display: flex; align-items: center; gap: var(--s2); | 82 | display: flex; align-items: center; gap: var(--s2); | ||
| 81 | - | & .owner-badge { font: 600 var(--fs-xs) / 1 var(--mono); color: var(--ink-dim); padding: 0 var(--s1); } | 83 | + | & .owner-badge { |
| 84 | + | display: inline-flex; align-items: center; gap: 6px; max-width: 20ch; padding: 0 var(--s1); | |||
| 85 | + | font: 600 var(--fs-s) / 1 var(--mono); color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; | |||
| 86 | + | } | |||
| 82 | } | 87 | } | ||
| 83 | 88 | ||||
| 84 | .tabs { | 89 | .tabs { | ||
| @@ -103,6 +108,9 @@ | |||||
| 103 | flex: 1; width: 100%; max-width: var(--page); margin: 0 auto; | 108 | flex: 1; width: 100%; max-width: var(--page); margin: 0 auto; | ||
| 104 | padding: var(--s5) var(--pad) var(--s7); | 109 | padding: var(--s5) var(--pad) var(--s7); | ||
| 105 | display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s5); align-items: start; | 110 | display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s5); align-items: start; | ||
| 111 | + | /* .work grows to fill the page (flex: 1): without this, the rows share the | |||
| 112 | + | spare height and a short content pushes the rail down */ | |||
| 113 | + | align-content: start; | |||
| 106 | } | 114 | } | ||
| 107 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | 115 | .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); } | ||
| 108 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | 116 | .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; } | ||
| @@ -156,7 +164,8 @@ | |||||
| 156 | font-size: var(--fs-s); color: var(--ink-mute); | 164 | font-size: var(--fs-s); color: var(--ink-mute); | ||
| 157 | &::before { content: ""; flex-basis: 100%; height: 1px; margin-bottom: var(--s2); background: var(--border); } | 165 | &::before { content: ""; flex-basis: 100%; height: 1px; margin-bottom: var(--s2); background: var(--border); } | ||
| 158 | & a { color: var(--ink-dim); } | 166 | & a { color: var(--ink-dim); } | ||
| 159 | - | & .foot-keys { margin-left: auto; } | 167 | + | & .foot-keys { margin-left: auto; color: var(--ink-dim); } |
| 168 | + | & .foot-keys:hover { color: var(--ink-hi); } | |||
| 160 | } | 169 | } | ||
| 161 | 170 | ||||
| 162 | /* hint: bottom right, only while it has something to say */ | 171 | /* hint: bottom right, only while it has something to say */ | ||
| @@ -187,14 +196,12 @@ | |||||
| 187 | @media (max-width: 720px) { | 196 | @media (max-width: 720px) { | ||
| 188 | :root { --pad: var(--s4); } | 197 | :root { --pad: var(--s4); } | ||
| 189 | .hdr-top { gap: var(--s3); height: 52px; } | 198 | .hdr-top { gap: var(--s3); height: 52px; } | ||
| 190 | - | .brand-name { display: none; } | |||
| 191 | .crumbs li:not(:last-child) { display: none; } | 199 | .crumbs li:not(:last-child) { display: none; } | ||
| 192 | - | .ref .sha, .owner-badge, .hdr-acts .icon { display: none; } | 200 | + | .ref .sha, .owner .owner-badge { display: none; } |
| 193 | .ref { max-width: 16ch; } | 201 | .ref { max-width: 16ch; } | ||
| 194 | .work { padding-top: var(--s4); } | 202 | .work { padding-top: var(--s4); } | ||
| 195 | .rail { grid-template-columns: minmax(0, 1fr); } | 203 | .rail { grid-template-columns: minmax(0, 1fr); } | ||
| 196 | .tabs kbd { display: none; } | 204 | .tabs kbd { display: none; } | ||
| 197 | - | .foot .foot-keys { display: none; } | |||
| 198 | .list .c-who, .list .c-desc, .list .c-sub, .list .c-ref, .list .c-size, .list .c-lang { display: none; } | 205 | .list .c-who, .list .c-desc, .list .c-sub, .list .c-ref, .list .c-size, .list .c-lang { display: none; } | ||
| 199 | } | 206 | } | ||
| 200 | } | 207 | } | ||
M internal/adapters/web/static/icons/branch.svg +1 -1
| @@ -1,1 +1,1 @@ | |||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="3.5" r="1.5"/><circle cx="5" cy="12.5" r="1.5"/><circle cx="11" cy="4.5" r="1.5"/><path d="M5 5v6M11 6c0 3.5-6 2-6 5"/></svg>⏎̸ | |
| 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="M15 6a9 9 0 0 0-9 9V3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/></svg> | |
| @@ -1,1 +1,1 @@ | |||||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="3.5" r="1.5"/><circle cx="5" cy="12.5" r="1.5"/><circle cx="11" cy="4.5" r="1.5"/><path d="M5 5v6M11 6c0 3.5-6 2-6 5"/></svg>⏎̸ | 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="M15 6a9 9 0 0 0-9 9V3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/></svg> |
M internal/adapters/web/static/icons/file.svg +1 -1
| @@ -1,1 +1,1 @@ | |||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"><path d="M3.5 1.5h6l3 3v10h-9zM9.5 1.5v3h3M6 8h4M6 11h4"/></svg>⏎̸ | |
| 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg> | |
| @@ -1,1 +1,1 @@ | |||||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"><path d="M3.5 1.5h6l3 3v10h-9zM9.5 1.5v3h3M6 8h4M6 11h4"/></svg>⏎̸ | 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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg> |
A internal/adapters/web/static/icons/key.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="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="#000"/></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="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="#000"/></svg> | |||
A internal/adapters/web/static/icons/log-in.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="m10 17 5-5-5-5"/><path d="M15 12H3"/><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/></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="m10 17 5-5-5-5"/><path d="M15 12H3"/><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/></svg> | |||
A internal/adapters/web/static/icons/log-out.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="m16 17 5-5-5-5"/><path d="M21 12H9"/><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/></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="m16 17 5-5-5-5"/><path d="M21 12H9"/><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/></svg> | |||
M internal/adapters/web/static/icons/search.svg +1 -1
| @@ -1,1 +1,1 @@ | |||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round"><circle cx="7" cy="7" r="4.5"/><path d="m10.5 10.5 3.5 3.5"/></svg>⏎̸ | |
| 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="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg> | |
| @@ -1,1 +1,1 @@ | |||||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round"><circle cx="7" cy="7" r="4.5"/><path d="m10.5 10.5 3.5 3.5"/></svg>⏎̸ | 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="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg> |
M internal/adapters/web/static/icons/tag.svg +1 -1
| @@ -1,1 +1,1 @@ | |||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5v5l6.5 6.5 5-5L7 2.5z"/><circle cx="5" cy="5.5" r="1"/></svg> | |
| 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="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r=".5" fill="#000"/></svg> | |
| @@ -1,1 +1,1 @@ | |||||
| 1 | - | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5v5l6.5 6.5 5-5L7 2.5z"/><circle cx="5" cy="5.5" r="1"/></svg> | 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="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r=".5" fill="#000"/></svg> |
A internal/adapters/web/static/icons/user.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"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/></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"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/></svg> | |||
M internal/adapters/web/static/js/ermite.js +26 -0
| @@ -255,6 +255,15 @@ | |||
| 255 | 255 | a.setAttribute('aria-controls', 'panel-refs'); | |
| 256 | 256 | } | |
| 257 | 257 | ||
| 258 | + | // A login link signs in without a click: its page submits its form once a | |
| 259 | + | // browser shows it, not while it is prerendered. | |
| 260 | + | const auto = $('form[data-autosubmit]'); | |
| 261 | + | if (auto) { | |
| 262 | + | const go = () => auto.requestSubmit(); | |
| 263 | + | if (document.prerendering) document.addEventListener('prerenderingchange', go, { once: true }); | |
| 264 | + | else go(); | |
| 265 | + | } | |
| 266 | + | ||
| 258 | 267 | // In the app shell the page does not scroll, <main> does: give it focus so arrows, | |
| 259 | 268 | // PageUp/PageDown and space scroll the content without a click first. | |
| 260 | 269 | if (!location.hash) document.getElementById('main')?.focus({ preventScroll: true }); | |
| @@ -310,7 +319,24 @@ | |||
| 310 | 319 | window.ermiteLive = (() => { | |
| 311 | 320 | let reloading = false; | |
| 312 | 321 | let stopped = false; | |
| 322 | + | // A stream that drops and comes back within LOST_AFTER (a proxy or the | |
| 323 | + | // server closing it, a network hiccup) is not worth a message. | |
| 324 | + | const LOST_AFTER = 5000; | |
| 325 | + | let lostTimer = 0; | |
| 326 | + | const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; }; | |
| 313 | 327 | return { | |
| 328 | + | // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows | |
| 329 | + | // only if the stream is still down LOST_AFTER later. | |
| 330 | + | lost() { | |
| 331 | + | if (lostTimer) return; | |
| 332 | + | lostTimer = setTimeout(() => { lostTimer = 0; setState('offline'); }, LOST_AFTER); | |
| 333 | + | }, | |
| 334 | + | // back is htmx:sse:after:connection. | |
| 335 | + | back() { | |
| 336 | + | clearTimeout(lostTimer); | |
| 337 | + | lostTimer = 0; | |
| 338 | + | setState('live'); | |
| 339 | + | }, | |
| 314 | 340 | swap(html) { | |
| 315 | 341 | const t = document.createElement('template'); | |
| 316 | 342 | t.innerHTML = html; | |
| @@ -255,6 +255,15 @@ | |||||
| 255 | a.setAttribute('aria-controls', 'panel-refs'); | 255 | a.setAttribute('aria-controls', 'panel-refs'); | ||
| 256 | } | 256 | } | ||
| 257 | 257 | ||||
| 258 | + | // A login link signs in without a click: its page submits its form once a | |||
| 259 | + | // browser shows it, not while it is prerendered. | |||
| 260 | + | const auto = $('form[data-autosubmit]'); | |||
| 261 | + | if (auto) { | |||
| 262 | + | const go = () => auto.requestSubmit(); | |||
| 263 | + | if (document.prerendering) document.addEventListener('prerenderingchange', go, { once: true }); | |||
| 264 | + | else go(); | |||
| 265 | + | } | |||
| 266 | + | ||||
| 258 | // In the app shell the page does not scroll, <main> does: give it focus so arrows, | 267 | // In the app shell the page does not scroll, <main> does: give it focus so arrows, | ||
| 259 | // PageUp/PageDown and space scroll the content without a click first. | 268 | // PageUp/PageDown and space scroll the content without a click first. | ||
| 260 | if (!location.hash) document.getElementById('main')?.focus({ preventScroll: true }); | 269 | if (!location.hash) document.getElementById('main')?.focus({ preventScroll: true }); | ||
| @@ -310,7 +319,24 @@ | |||||
| 310 | window.ermiteLive = (() => { | 319 | window.ermiteLive = (() => { | ||
| 311 | let reloading = false; | 320 | let reloading = false; | ||
| 312 | let stopped = false; | 321 | let stopped = false; | ||
| 322 | + | // A stream that drops and comes back within LOST_AFTER (a proxy or the | |||
| 323 | + | // server closing it, a network hiccup) is not worth a message. | |||
| 324 | + | const LOST_AFTER = 5000; | |||
| 325 | + | let lostTimer = 0; | |||
| 326 | + | const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; }; | |||
| 313 | return { | 327 | return { | ||
| 328 | + | // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows | |||
| 329 | + | // only if the stream is still down LOST_AFTER later. | |||
| 330 | + | lost() { | |||
| 331 | + | if (lostTimer) return; | |||
| 332 | + | lostTimer = setTimeout(() => { lostTimer = 0; setState('offline'); }, LOST_AFTER); | |||
| 333 | + | }, | |||
| 334 | + | // back is htmx:sse:after:connection. | |||
| 335 | + | back() { | |||
| 336 | + | clearTimeout(lostTimer); | |||
| 337 | + | lostTimer = 0; | |||
| 338 | + | setState('live'); | |||
| 339 | + | }, | |||
| 314 | swap(html) { | 340 | swap(html) { | ||
| 315 | const t = document.createElement('template'); | 341 | const t = document.createElement('template'); | ||
| 316 | t.innerHTML = html; | 342 | t.innerHTML = html; | ||
M internal/adapters/web/view.go +3 -2
| @@ -34,8 +34,9 @@ type CmdItem struct{ Kind, Label, Href string } | |||
| 34 | 34 | ||
| 35 | 35 | // Layout is the input of the page shell. | |
| 36 | 36 | type Layout struct { | |
| 37 | - | Title string | |
| 38 | - | Instance string | |
| 37 | + | Title string | |
| 38 | + | // Owner is the owner's name (ERMITE_OWNER), shown when signed in. | |
| 39 | + | Owner string | |
| 39 | 40 | Version string | |
| 40 | 41 | SourceURL string | |
| 41 | 42 | Assets Assets | |
| @@ -34,8 +34,9 @@ type CmdItem struct{ Kind, Label, Href string } | |||||
| 34 | 34 | ||||
| 35 | // Layout is the input of the page shell. | 35 | // Layout is the input of the page shell. | ||
| 36 | type Layout struct { | 36 | type Layout struct { | ||
| 37 | - | Title string | 37 | + | Title string |
| 38 | - | Instance string | 38 | + | // Owner is the owner's name (ERMITE_OWNER), shown when signed in. |
| 39 | + | Owner string | |||
| 39 | Version string | 40 | Version string | ||
| 40 | SourceURL string | 41 | SourceURL string | ||
| 41 | Assets Assets | 42 | Assets Assets | ||
M internal/adapters/web/web.go +1 -1
| @@ -390,7 +390,7 @@ func (s *Server) notFound(context.Context, *http.Request) (templ.Component, int, | |||
| 390 | 390 | // base is the Layout every page starts from. | |
| 391 | 391 | func (s *Server) base() Layout { | |
| 392 | 392 | return Layout{ | |
| 393 | - | Instance: s.instance, | |
| 393 | + | Owner: s.cfg.Owner, | |
| 394 | 394 | Version: s.cfg.Version, | |
| 395 | 395 | SourceURL: s.cfg.SourceURL, | |
| 396 | 396 | Assets: s.assets.urls, | |
| @@ -390,7 +390,7 @@ func (s *Server) notFound(context.Context, *http.Request) (templ.Component, int, | |||||
| 390 | // base is the Layout every page starts from. | 390 | // base is the Layout every page starts from. | ||
| 391 | func (s *Server) base() Layout { | 391 | func (s *Server) base() Layout { | ||
| 392 | return Layout{ | 392 | return Layout{ | ||
| 393 | - | Instance: s.instance, | 393 | + | Owner: s.cfg.Owner, |
| 394 | Version: s.cfg.Version, | 394 | Version: s.cfg.Version, | ||
| 395 | SourceURL: s.cfg.SourceURL, | 395 | SourceURL: s.cfg.SourceURL, | ||
| 396 | Assets: s.assets.urls, | 396 | Assets: s.assets.urls, | ||
M internal/adapters/webauthn/webauthn.go +13 -11
| @@ -19,20 +19,21 @@ import ( | |||
| 19 | 19 | "bnema.dev/ermite/internal/ports" | |
| 20 | 20 | ) | |
| 21 | 21 | ||
| 22 | - | // ownerName names the one user in the options sent to the browser. | |
| 23 | - | const ownerName = "owner" | |
| 24 | - | ||
| 25 | 22 | // Verifier runs the WebAuthn ceremonies for the owner of one public URL. | |
| 26 | 23 | type Verifier struct { | |
| 27 | 24 | web *gowebauthn.WebAuthn | |
| 25 | + | // name names the one user in the options sent to the browser: the | |
| 26 | + | // owner's name (ERMITE_OWNER), which password managers show. | |
| 27 | + | name string | |
| 28 | 28 | } | |
| 29 | 29 | ||
| 30 | 30 | var _ ports.PasskeyVerifier = (*Verifier)(nil) | |
| 31 | 31 | ||
| 32 | 32 | // New builds a verifier for publicURL: the relying party ID is its host name, | |
| 33 | 33 | // the only accepted origin is its origin. It fails when the library refuses the | |
| 34 | - | // configuration, for example for a host that is an IP address. | |
| 35 | - | func New(publicURL string) (*Verifier, error) { | |
| 34 | + | // configuration, for example for a host that is an IP address. ownerName is | |
| 35 | + | // the name the browser shows for the passkey. | |
| 36 | + | func New(publicURL, ownerName string) (*Verifier, error) { | |
| 36 | 37 | u, err := url.Parse(publicURL) | |
| 37 | 38 | if err != nil { | |
| 38 | 39 | return nil, fmt.Errorf("passkeys: parse public URL: %w", err) | |
| @@ -57,18 +58,19 @@ func New(publicURL string) (*Verifier, error) { | |||
| 57 | 58 | if err != nil { | |
| 58 | 59 | return nil, fmt.Errorf("passkeys: %w", err) | |
| 59 | 60 | } | |
| 60 | - | return &Verifier{web: web}, nil | |
| 61 | + | return &Verifier{web: web, name: ownerName}, nil | |
| 61 | 62 | } | |
| 62 | 63 | ||
| 63 | 64 | // owner is the single WebAuthn user. | |
| 64 | 65 | type owner struct { | |
| 66 | + | name string | |
| 65 | 67 | handle []byte | |
| 66 | 68 | credentials []gowebauthn.Credential | |
| 67 | 69 | } | |
| 68 | 70 | ||
| 69 | 71 | func (o owner) WebAuthnID() []byte { return o.handle } | |
| 70 | - | func (o owner) WebAuthnName() string { return ownerName } | |
| 71 | - | func (o owner) WebAuthnDisplayName() string { return ownerName } | |
| 72 | + | func (o owner) WebAuthnName() string { return o.name } | |
| 73 | + | func (o owner) WebAuthnDisplayName() string { return o.name } | |
| 72 | 74 | func (o owner) WebAuthnCredentials() []gowebauthn.Credential { return o.credentials } | |
| 73 | 75 | ||
| 74 | 76 | // refuse builds the error of a failed verification: an ErrUnauthenticated whose | |
| @@ -84,7 +86,7 @@ func (v *Verifier) BeginRegistration(userHandle []byte, existing []domain.Passke | |||
| 84 | 86 | for _, p := range existing { | |
| 85 | 87 | exclude = append(exclude, descriptor(p)) | |
| 86 | 88 | } | |
| 87 | - | creation, session, err := v.web.BeginRegistration(owner{handle: userHandle}, | |
| 89 | + | creation, session, err := v.web.BeginRegistration(owner{name: v.name, handle: userHandle}, | |
| 88 | 90 | gowebauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementRequired), | |
| 89 | 91 | gowebauthn.WithExclusions(exclude), | |
| 90 | 92 | gowebauthn.WithConveyancePreference(protocol.PreferNoAttestation), | |
| @@ -105,7 +107,7 @@ func (v *Verifier) FinishRegistration(userHandle []byte, ceremony, response []by | |||
| 105 | 107 | if err != nil { | |
| 106 | 108 | return domain.Passkey{}, refuse("registration", err) | |
| 107 | 109 | } | |
| 108 | - | cred, err := v.web.CreateCredential(owner{handle: userHandle}, session, parsed) | |
| 110 | + | cred, err := v.web.CreateCredential(owner{name: v.name, handle: userHandle}, session, parsed) | |
| 109 | 111 | if err != nil { | |
| 110 | 112 | return domain.Passkey{}, refuse("registration", err) | |
| 111 | 113 | } | |
| @@ -158,7 +160,7 @@ func (v *Verifier) FinishLogin(userHandle []byte, ceremony, response []byte, loo | |||
| 158 | 160 | return nil, err | |
| 159 | 161 | } | |
| 160 | 162 | stored = p | |
| 161 | - | return owner{handle: userHandle, credentials: []gowebauthn.Credential{credential(p)}}, nil | |
| 163 | + | return owner{name: v.name, handle: userHandle, credentials: []gowebauthn.Credential{credential(p)}}, nil | |
| 162 | 164 | } | |
| 163 | 165 | _, cred, err := v.web.ValidatePasskeyLogin(handler, session, parsed) | |
| 164 | 166 | if lookupErr != nil { | |
| @@ -19,20 +19,21 @@ import ( | |||||
| 19 | "bnema.dev/ermite/internal/ports" | 19 | "bnema.dev/ermite/internal/ports" | ||
| 20 | ) | 20 | ) | ||
| 21 | 21 | ||||
| 22 | - | // ownerName names the one user in the options sent to the browser. | |||
| 23 | - | const ownerName = "owner" | |||
| 24 | - | ||||
| 25 | // Verifier runs the WebAuthn ceremonies for the owner of one public URL. | 22 | // Verifier runs the WebAuthn ceremonies for the owner of one public URL. | ||
| 26 | type Verifier struct { | 23 | type Verifier struct { | ||
| 27 | web *gowebauthn.WebAuthn | 24 | web *gowebauthn.WebAuthn | ||
| 25 | + | // name names the one user in the options sent to the browser: the | |||
| 26 | + | // owner's name (ERMITE_OWNER), which password managers show. | |||
| 27 | + | name string | |||
| 28 | } | 28 | } | ||
| 29 | 29 | ||||
| 30 | var _ ports.PasskeyVerifier = (*Verifier)(nil) | 30 | var _ ports.PasskeyVerifier = (*Verifier)(nil) | ||
| 31 | 31 | ||||
| 32 | // New builds a verifier for publicURL: the relying party ID is its host name, | 32 | // New builds a verifier for publicURL: the relying party ID is its host name, | ||
| 33 | // the only accepted origin is its origin. It fails when the library refuses the | 33 | // the only accepted origin is its origin. It fails when the library refuses the | ||
| 34 | - | // configuration, for example for a host that is an IP address. | 34 | + | // configuration, for example for a host that is an IP address. ownerName is |
| 35 | - | func New(publicURL string) (*Verifier, error) { | 35 | + | // the name the browser shows for the passkey. |
| 36 | + | func New(publicURL, ownerName string) (*Verifier, error) { | |||
| 36 | u, err := url.Parse(publicURL) | 37 | u, err := url.Parse(publicURL) | ||
| 37 | if err != nil { | 38 | if err != nil { | ||
| 38 | return nil, fmt.Errorf("passkeys: parse public URL: %w", err) | 39 | return nil, fmt.Errorf("passkeys: parse public URL: %w", err) | ||
| @@ -57,18 +58,19 @@ func New(publicURL string) (*Verifier, error) { | |||||
| 57 | if err != nil { | 58 | if err != nil { | ||
| 58 | return nil, fmt.Errorf("passkeys: %w", err) | 59 | return nil, fmt.Errorf("passkeys: %w", err) | ||
| 59 | } | 60 | } | ||
| 60 | - | return &Verifier{web: web}, nil | 61 | + | return &Verifier{web: web, name: ownerName}, nil |
| 61 | } | 62 | } | ||
| 62 | 63 | ||||
| 63 | // owner is the single WebAuthn user. | 64 | // owner is the single WebAuthn user. | ||
| 64 | type owner struct { | 65 | type owner struct { | ||
| 66 | + | name string | |||
| 65 | handle []byte | 67 | handle []byte | ||
| 66 | credentials []gowebauthn.Credential | 68 | credentials []gowebauthn.Credential | ||
| 67 | } | 69 | } | ||
| 68 | 70 | ||||
| 69 | func (o owner) WebAuthnID() []byte { return o.handle } | 71 | func (o owner) WebAuthnID() []byte { return o.handle } | ||
| 70 | - | func (o owner) WebAuthnName() string { return ownerName } | 72 | + | func (o owner) WebAuthnName() string { return o.name } |
| 71 | - | func (o owner) WebAuthnDisplayName() string { return ownerName } | 73 | + | func (o owner) WebAuthnDisplayName() string { return o.name } |
| 72 | func (o owner) WebAuthnCredentials() []gowebauthn.Credential { return o.credentials } | 74 | func (o owner) WebAuthnCredentials() []gowebauthn.Credential { return o.credentials } | ||
| 73 | 75 | ||||
| 74 | // refuse builds the error of a failed verification: an ErrUnauthenticated whose | 76 | // refuse builds the error of a failed verification: an ErrUnauthenticated whose | ||
| @@ -84,7 +86,7 @@ func (v *Verifier) BeginRegistration(userHandle []byte, existing []domain.Passke | |||||
| 84 | for _, p := range existing { | 86 | for _, p := range existing { | ||
| 85 | exclude = append(exclude, descriptor(p)) | 87 | exclude = append(exclude, descriptor(p)) | ||
| 86 | } | 88 | } | ||
| 87 | - | creation, session, err := v.web.BeginRegistration(owner{handle: userHandle}, | 89 | + | creation, session, err := v.web.BeginRegistration(owner{name: v.name, handle: userHandle}, |
| 88 | gowebauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementRequired), | 90 | gowebauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementRequired), | ||
| 89 | gowebauthn.WithExclusions(exclude), | 91 | gowebauthn.WithExclusions(exclude), | ||
| 90 | gowebauthn.WithConveyancePreference(protocol.PreferNoAttestation), | 92 | gowebauthn.WithConveyancePreference(protocol.PreferNoAttestation), | ||
| @@ -105,7 +107,7 @@ func (v *Verifier) FinishRegistration(userHandle []byte, ceremony, response []by | |||||
| 105 | if err != nil { | 107 | if err != nil { | ||
| 106 | return domain.Passkey{}, refuse("registration", err) | 108 | return domain.Passkey{}, refuse("registration", err) | ||
| 107 | } | 109 | } | ||
| 108 | - | cred, err := v.web.CreateCredential(owner{handle: userHandle}, session, parsed) | 110 | + | cred, err := v.web.CreateCredential(owner{name: v.name, handle: userHandle}, session, parsed) |
| 109 | if err != nil { | 111 | if err != nil { | ||
| 110 | return domain.Passkey{}, refuse("registration", err) | 112 | return domain.Passkey{}, refuse("registration", err) | ||
| 111 | } | 113 | } | ||
| @@ -158,7 +160,7 @@ func (v *Verifier) FinishLogin(userHandle []byte, ceremony, response []byte, loo | |||||
| 158 | return nil, err | 160 | return nil, err | ||
| 159 | } | 161 | } | ||
| 160 | stored = p | 162 | stored = p | ||
| 161 | - | return owner{handle: userHandle, credentials: []gowebauthn.Credential{credential(p)}}, nil | 163 | + | return owner{name: v.name, handle: userHandle, credentials: []gowebauthn.Credential{credential(p)}}, nil |
| 162 | } | 164 | } | ||
| 163 | _, cred, err := v.web.ValidatePasskeyLogin(handler, session, parsed) | 165 | _, cred, err := v.web.ValidatePasskeyLogin(handler, session, parsed) | ||
| 164 | if lookupErr != nil { | 166 | if lookupErr != nil { | ||
M internal/adapters/webauthn/webauthn_test.go +10 -10
| @@ -90,7 +90,7 @@ func TestRegisterThenLogin(t *testing.T) { | |||
| 90 | 90 | for name, s := range sites { | |
| 91 | 91 | t.Run(name, func(t *testing.T) { | |
| 92 | 92 | t.Parallel() | |
| 93 | - | v, err := webauthn.New(s.url) | |
| 93 | + | v, err := webauthn.New(s.url, "owner") | |
| 94 | 94 | require.NoError(t, err) | |
| 95 | 95 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true}) | |
| 96 | 96 | d.cred.Counter = 5 | |
| @@ -119,7 +119,7 @@ func TestRegisterThenLogin(t *testing.T) { | |||
| 119 | 119 | func TestRegistrationReportsBackupFlags(t *testing.T) { | |
| 120 | 120 | t.Parallel() | |
| 121 | 121 | s := sites["https"] | |
| 122 | - | v, err := webauthn.New(s.url) | |
| 122 | + | v, err := webauthn.New(s.url, "owner") | |
| 123 | 123 | require.NoError(t, err) | |
| 124 | 124 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true, | |
| 125 | 125 | Transports: []virtualwebauthn.Transport{virtualwebauthn.TransportInternal, virtualwebauthn.TransportHybrid}}) | |
| @@ -139,7 +139,7 @@ func TestRegistrationReportsBackupFlags(t *testing.T) { | |||
| 139 | 139 | func TestOptions(t *testing.T) { | |
| 140 | 140 | t.Parallel() | |
| 141 | 141 | s := sites["https"] | |
| 142 | - | v, err := webauthn.New(s.url) | |
| 142 | + | v, err := webauthn.New(s.url, "owner") | |
| 143 | 143 | require.NoError(t, err) | |
| 144 | 144 | ||
| 145 | 145 | existing := domain.Passkey{ID: []byte("already-registered"), PublicKey: []byte("k"), Transports: []string{"usb"}} | |
| @@ -197,7 +197,7 @@ func TestOptions(t *testing.T) { | |||
| 197 | 197 | func TestLoginRefused(t *testing.T) { | |
| 198 | 198 | t.Parallel() | |
| 199 | 199 | s := sites["localhost"] | |
| 200 | - | v, err := webauthn.New(s.url) | |
| 200 | + | v, err := webauthn.New(s.url, "owner") | |
| 201 | 201 | require.NoError(t, err) | |
| 202 | 202 | ||
| 203 | 203 | tests := []struct { | |
| @@ -315,7 +315,7 @@ func setExpires(t *testing.T, ceremony []byte, at time.Time) []byte { | |||
| 315 | 315 | func TestLoginStoreFailureIsNotAnUnauthenticated(t *testing.T) { | |
| 316 | 316 | t.Parallel() | |
| 317 | 317 | s := sites["localhost"] | |
| 318 | - | v, err := webauthn.New(s.url) | |
| 318 | + | v, err := webauthn.New(s.url, "owner") | |
| 319 | 319 | require.NoError(t, err) | |
| 320 | 320 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | |
| 321 | 321 | ceremony, options := d.assertion(t, v, s) | |
| @@ -334,7 +334,7 @@ func TestLoginStoreFailureIsNotAnUnauthenticated(t *testing.T) { | |||
| 334 | 334 | func TestCounterThatDoesNotAdvanceKeepsTheStoredOne(t *testing.T) { | |
| 335 | 335 | t.Parallel() | |
| 336 | 336 | s := sites["localhost"] | |
| 337 | - | v, err := webauthn.New(s.url) | |
| 337 | + | v, err := webauthn.New(s.url, "owner") | |
| 338 | 338 | require.NoError(t, err) | |
| 339 | 339 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | |
| 340 | 340 | ||
| @@ -364,7 +364,7 @@ func TestCounterThatDoesNotAdvanceKeepsTheStoredOne(t *testing.T) { | |||
| 364 | 364 | func TestZeroCounterIsNotAClone(t *testing.T) { | |
| 365 | 365 | t.Parallel() | |
| 366 | 366 | s := sites["localhost"] | |
| 367 | - | v, err := webauthn.New(s.url) | |
| 367 | + | v, err := webauthn.New(s.url, "owner") | |
| 368 | 368 | require.NoError(t, err) | |
| 369 | 369 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true}) | |
| 370 | 370 | for range 2 { | |
| @@ -378,7 +378,7 @@ func TestZeroCounterIsNotAClone(t *testing.T) { | |||
| 378 | 378 | func TestRegistrationRefused(t *testing.T) { | |
| 379 | 379 | t.Parallel() | |
| 380 | 380 | s := sites["https"] | |
| 381 | - | v, err := webauthn.New(s.url) | |
| 381 | + | v, err := webauthn.New(s.url, "owner") | |
| 382 | 382 | require.NoError(t, err) | |
| 383 | 383 | ||
| 384 | 384 | tests := []struct { | |
| @@ -436,11 +436,11 @@ func TestRegistrationRefused(t *testing.T) { | |||
| 436 | 436 | func TestNew(t *testing.T) { | |
| 437 | 437 | t.Parallel() | |
| 438 | 438 | for _, ok := range []string{"https://git.example.com", "http://localhost:7341", "https://Git.Example.com:8443/"} { | |
| 439 | - | _, err := webauthn.New(ok) | |
| 439 | + | _, err := webauthn.New(ok, "owner") | |
| 440 | 440 | assert.NoError(t, err, ok) | |
| 441 | 441 | } | |
| 442 | 442 | for _, bad := range []string{"http://127.0.0.1:7341", "http://[::1]:7341", "git.example.com", "ftp://git.example.com", "", "https://"} { | |
| 443 | - | _, err := webauthn.New(bad) | |
| 443 | + | _, err := webauthn.New(bad, "owner") | |
| 444 | 444 | assert.Error(t, err, bad) | |
| 445 | 445 | } | |
| 446 | 446 | } | |
| @@ -90,7 +90,7 @@ func TestRegisterThenLogin(t *testing.T) { | |||||
| 90 | for name, s := range sites { | 90 | for name, s := range sites { | ||
| 91 | t.Run(name, func(t *testing.T) { | 91 | t.Run(name, func(t *testing.T) { | ||
| 92 | t.Parallel() | 92 | t.Parallel() | ||
| 93 | - | v, err := webauthn.New(s.url) | 93 | + | v, err := webauthn.New(s.url, "owner") |
| 94 | require.NoError(t, err) | 94 | require.NoError(t, err) | ||
| 95 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true}) | 95 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true}) | ||
| 96 | d.cred.Counter = 5 | 96 | d.cred.Counter = 5 | ||
| @@ -119,7 +119,7 @@ func TestRegisterThenLogin(t *testing.T) { | |||||
| 119 | func TestRegistrationReportsBackupFlags(t *testing.T) { | 119 | func TestRegistrationReportsBackupFlags(t *testing.T) { | ||
| 120 | t.Parallel() | 120 | t.Parallel() | ||
| 121 | s := sites["https"] | 121 | s := sites["https"] | ||
| 122 | - | v, err := webauthn.New(s.url) | 122 | + | v, err := webauthn.New(s.url, "owner") |
| 123 | require.NoError(t, err) | 123 | require.NoError(t, err) | ||
| 124 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true, | 124 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true, | ||
| 125 | Transports: []virtualwebauthn.Transport{virtualwebauthn.TransportInternal, virtualwebauthn.TransportHybrid}}) | 125 | Transports: []virtualwebauthn.Transport{virtualwebauthn.TransportInternal, virtualwebauthn.TransportHybrid}}) | ||
| @@ -139,7 +139,7 @@ func TestRegistrationReportsBackupFlags(t *testing.T) { | |||||
| 139 | func TestOptions(t *testing.T) { | 139 | func TestOptions(t *testing.T) { | ||
| 140 | t.Parallel() | 140 | t.Parallel() | ||
| 141 | s := sites["https"] | 141 | s := sites["https"] | ||
| 142 | - | v, err := webauthn.New(s.url) | 142 | + | v, err := webauthn.New(s.url, "owner") |
| 143 | require.NoError(t, err) | 143 | require.NoError(t, err) | ||
| 144 | 144 | ||||
| 145 | existing := domain.Passkey{ID: []byte("already-registered"), PublicKey: []byte("k"), Transports: []string{"usb"}} | 145 | existing := domain.Passkey{ID: []byte("already-registered"), PublicKey: []byte("k"), Transports: []string{"usb"}} | ||
| @@ -197,7 +197,7 @@ func TestOptions(t *testing.T) { | |||||
| 197 | func TestLoginRefused(t *testing.T) { | 197 | func TestLoginRefused(t *testing.T) { | ||
| 198 | t.Parallel() | 198 | t.Parallel() | ||
| 199 | s := sites["localhost"] | 199 | s := sites["localhost"] | ||
| 200 | - | v, err := webauthn.New(s.url) | 200 | + | v, err := webauthn.New(s.url, "owner") |
| 201 | require.NoError(t, err) | 201 | require.NoError(t, err) | ||
| 202 | 202 | ||||
| 203 | tests := []struct { | 203 | tests := []struct { | ||
| @@ -315,7 +315,7 @@ func setExpires(t *testing.T, ceremony []byte, at time.Time) []byte { | |||||
| 315 | func TestLoginStoreFailureIsNotAnUnauthenticated(t *testing.T) { | 315 | func TestLoginStoreFailureIsNotAnUnauthenticated(t *testing.T) { | ||
| 316 | t.Parallel() | 316 | t.Parallel() | ||
| 317 | s := sites["localhost"] | 317 | s := sites["localhost"] | ||
| 318 | - | v, err := webauthn.New(s.url) | 318 | + | v, err := webauthn.New(s.url, "owner") |
| 319 | require.NoError(t, err) | 319 | require.NoError(t, err) | ||
| 320 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | 320 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | ||
| 321 | ceremony, options := d.assertion(t, v, s) | 321 | ceremony, options := d.assertion(t, v, s) | ||
| @@ -334,7 +334,7 @@ func TestLoginStoreFailureIsNotAnUnauthenticated(t *testing.T) { | |||||
| 334 | func TestCounterThatDoesNotAdvanceKeepsTheStoredOne(t *testing.T) { | 334 | func TestCounterThatDoesNotAdvanceKeepsTheStoredOne(t *testing.T) { | ||
| 335 | t.Parallel() | 335 | t.Parallel() | ||
| 336 | s := sites["localhost"] | 336 | s := sites["localhost"] | ||
| 337 | - | v, err := webauthn.New(s.url) | 337 | + | v, err := webauthn.New(s.url, "owner") |
| 338 | require.NoError(t, err) | 338 | require.NoError(t, err) | ||
| 339 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | 339 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | ||
| 340 | 340 | ||||
| @@ -364,7 +364,7 @@ func TestCounterThatDoesNotAdvanceKeepsTheStoredOne(t *testing.T) { | |||||
| 364 | func TestZeroCounterIsNotAClone(t *testing.T) { | 364 | func TestZeroCounterIsNotAClone(t *testing.T) { | ||
| 365 | t.Parallel() | 365 | t.Parallel() | ||
| 366 | s := sites["localhost"] | 366 | s := sites["localhost"] | ||
| 367 | - | v, err := webauthn.New(s.url) | 367 | + | v, err := webauthn.New(s.url, "owner") |
| 368 | require.NoError(t, err) | 368 | require.NoError(t, err) | ||
| 369 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true}) | 369 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{BackupEligible: true, BackupState: true}) | ||
| 370 | for range 2 { | 370 | for range 2 { | ||
| @@ -378,7 +378,7 @@ func TestZeroCounterIsNotAClone(t *testing.T) { | |||||
| 378 | func TestRegistrationRefused(t *testing.T) { | 378 | func TestRegistrationRefused(t *testing.T) { | ||
| 379 | t.Parallel() | 379 | t.Parallel() | ||
| 380 | s := sites["https"] | 380 | s := sites["https"] | ||
| 381 | - | v, err := webauthn.New(s.url) | 381 | + | v, err := webauthn.New(s.url, "owner") |
| 382 | require.NoError(t, err) | 382 | require.NoError(t, err) | ||
| 383 | 383 | ||||
| 384 | tests := []struct { | 384 | tests := []struct { | ||
| @@ -436,11 +436,11 @@ func TestRegistrationRefused(t *testing.T) { | |||||
| 436 | func TestNew(t *testing.T) { | 436 | func TestNew(t *testing.T) { | ||
| 437 | t.Parallel() | 437 | t.Parallel() | ||
| 438 | for _, ok := range []string{"https://git.example.com", "http://localhost:7341", "https://Git.Example.com:8443/"} { | 438 | for _, ok := range []string{"https://git.example.com", "http://localhost:7341", "https://Git.Example.com:8443/"} { | ||
| 439 | - | _, err := webauthn.New(ok) | 439 | + | _, err := webauthn.New(ok, "owner") |
| 440 | assert.NoError(t, err, ok) | 440 | assert.NoError(t, err, ok) | ||
| 441 | } | 441 | } | ||
| 442 | for _, bad := range []string{"http://127.0.0.1:7341", "http://[::1]:7341", "git.example.com", "ftp://git.example.com", "", "https://"} { | 442 | for _, bad := range []string{"http://127.0.0.1:7341", "http://[::1]:7341", "git.example.com", "ftp://git.example.com", "", "https://"} { | ||
| 443 | - | _, err := webauthn.New(bad) | 443 | + | _, err := webauthn.New(bad, "owner") |
| 444 | assert.Error(t, err, bad) | 444 | assert.Error(t, err, bad) | ||
| 445 | } | 445 | } | ||
| 446 | } | 446 | } | ||
M internal/app/cli_web.go +4 -4
| @@ -35,11 +35,11 @@ func newWebLoginCmd(env *cliEnv) *cobra.Command { | |||
| 35 | 35 | cmd := &cobra.Command{ | |
| 36 | 36 | Use: "login", | |
| 37 | 37 | Short: "Print a one-time link that signs your browser in", | |
| 38 | - | Long: "Ask the server for a login link and print it on standard output. Open it in your browser and confirm:\n" + | |
| 38 | + | Long: "Ask the server for a login link and print it on standard output. Open it in your browser:\n" + | |
| 39 | 39 | "the browser is then signed in as the owner for 12 hours (private repositories, merge requests).\n\n" + | |
| 40 | 40 | "The link works once, for about a minute, and only until the server restarts. It is a secret: it is\n" + | |
| 41 | - | "printed and nowhere else (not stored, not logged); do not paste it into a chat or a ticket. Opening\n" + | |
| 42 | - | "it only shows a confirmation page; confirming it signs the browser in.\n\n" + | |
| 41 | + | "printed and nowhere else (not stored, not logged); do not paste it into a chat or a ticket. The page\n" + | |
| 42 | + | "signs the browser in by itself; link previews, which run no script, cannot use it.\n\n" + | |
| 43 | 43 | "JSON fields (--json): url, expires_at (RFC 3339 UTC).", | |
| 44 | 44 | Args: cobra.NoArgs, | |
| 45 | 45 | } | |
| @@ -56,7 +56,7 @@ func newWebLoginCmd(env *cliEnv) *cobra.Command { | |||
| 56 | 56 | if *asJSON { | |
| 57 | 57 | return writeJSON(cmd.OutOrStdout(), jsonLoginLink{URL: res.Msg.GetUrl(), ExpiresAt: jsonTime(res.Msg.GetExpiresAt())}) | |
| 58 | 58 | } | |
| 59 | - | noticef(cmd, "open this link in your browser and confirm; it works once, until %s UTC", shortTime(res.Msg.GetExpiresAt(), "shortly")) | |
| 59 | + | noticef(cmd, "open this link in your browser; it works once, until %s UTC", shortTime(res.Msg.GetExpiresAt(), "shortly")) | |
| 60 | 60 | _, err = fmt.Fprintln(cmd.OutOrStdout(), res.Msg.GetUrl()) | |
| 61 | 61 | return err | |
| 62 | 62 | } | |
| @@ -35,11 +35,11 @@ func newWebLoginCmd(env *cliEnv) *cobra.Command { | |||||
| 35 | cmd := &cobra.Command{ | 35 | cmd := &cobra.Command{ | ||
| 36 | Use: "login", | 36 | Use: "login", | ||
| 37 | Short: "Print a one-time link that signs your browser in", | 37 | Short: "Print a one-time link that signs your browser in", | ||
| 38 | - | Long: "Ask the server for a login link and print it on standard output. Open it in your browser and confirm:\n" + | 38 | + | Long: "Ask the server for a login link and print it on standard output. Open it in your browser:\n" + |
| 39 | "the browser is then signed in as the owner for 12 hours (private repositories, merge requests).\n\n" + | 39 | "the browser is then signed in as the owner for 12 hours (private repositories, merge requests).\n\n" + | ||
| 40 | "The link works once, for about a minute, and only until the server restarts. It is a secret: it is\n" + | 40 | "The link works once, for about a minute, and only until the server restarts. It is a secret: it is\n" + | ||
| 41 | - | "printed and nowhere else (not stored, not logged); do not paste it into a chat or a ticket. Opening\n" + | 41 | + | "printed and nowhere else (not stored, not logged); do not paste it into a chat or a ticket. The page\n" + |
| 42 | - | "it only shows a confirmation page; confirming it signs the browser in.\n\n" + | 42 | + | "signs the browser in by itself; link previews, which run no script, cannot use it.\n\n" + |
| 43 | "JSON fields (--json): url, expires_at (RFC 3339 UTC).", | 43 | "JSON fields (--json): url, expires_at (RFC 3339 UTC).", | ||
| 44 | Args: cobra.NoArgs, | 44 | Args: cobra.NoArgs, | ||
| 45 | } | 45 | } | ||
| @@ -56,7 +56,7 @@ func newWebLoginCmd(env *cliEnv) *cobra.Command { | |||||
| 56 | if *asJSON { | 56 | if *asJSON { | ||
| 57 | return writeJSON(cmd.OutOrStdout(), jsonLoginLink{URL: res.Msg.GetUrl(), ExpiresAt: jsonTime(res.Msg.GetExpiresAt())}) | 57 | return writeJSON(cmd.OutOrStdout(), jsonLoginLink{URL: res.Msg.GetUrl(), ExpiresAt: jsonTime(res.Msg.GetExpiresAt())}) | ||
| 58 | } | 58 | } | ||
| 59 | - | noticef(cmd, "open this link in your browser and confirm; it works once, until %s UTC", shortTime(res.Msg.GetExpiresAt(), "shortly")) | 59 | + | noticef(cmd, "open this link in your browser; it works once, until %s UTC", shortTime(res.Msg.GetExpiresAt(), "shortly")) |
| 60 | _, err = fmt.Fprintln(cmd.OutOrStdout(), res.Msg.GetUrl()) | 60 | _, err = fmt.Fprintln(cmd.OutOrStdout(), res.Msg.GetUrl()) | ||
| 61 | return err | 61 | return err | ||
| 62 | } | 62 | } | ||
M internal/app/forge.go +1 -1
| @@ -317,7 +317,7 @@ func newForge(ctx context.Context, cfg config.Config, opts options) (_ *forge, e | |||
| 317 | 317 | // Owner passkeys (ADR 0026): built only when the public URL allows them. | |
| 318 | 318 | // When it does, a verifier that cannot be built is fatal. | |
| 319 | 319 | if ok, reason := passkeysAvailable(cfg.PublicURL); ok { | |
| 320 | - | verifier, err := webauthn.New(cfg.PublicURL) | |
| 320 | + | verifier, err := webauthn.New(cfg.PublicURL, cfg.Owner) | |
| 321 | 321 | if err != nil { | |
| 322 | 322 | return nil, fmt.Errorf("passkeys: %w", err) | |
| 323 | 323 | } | |
| @@ -317,7 +317,7 @@ func newForge(ctx context.Context, cfg config.Config, opts options) (_ *forge, e | |||||
| 317 | // Owner passkeys (ADR 0026): built only when the public URL allows them. | 317 | // Owner passkeys (ADR 0026): built only when the public URL allows them. | ||
| 318 | // When it does, a verifier that cannot be built is fatal. | 318 | // When it does, a verifier that cannot be built is fatal. | ||
| 319 | if ok, reason := passkeysAvailable(cfg.PublicURL); ok { | 319 | if ok, reason := passkeysAvailable(cfg.PublicURL); ok { | ||
| 320 | - | verifier, err := webauthn.New(cfg.PublicURL) | 320 | + | verifier, err := webauthn.New(cfg.PublicURL, cfg.Owner) |
| 321 | if err != nil { | 321 | if err != nil { | ||
| 322 | return nil, fmt.Errorf("passkeys: %w", err) | 322 | return nil, fmt.Errorf("passkeys: %w", err) | ||
| 323 | } | 323 | } | ||
M internal/app/testdata/web/blob-binary.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>data.bin · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -129,8 +132,8 @@ | |||
| 129 | 132 | <footer class="foot"> | |
| 130 | 133 | <span>ermite devel</span> | |
| 131 | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 132 | - | <span class="foot-keys"> | |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 134 | 137 | </footer> | |
| 135 | 138 | <div class="hint" role="status" aria-live="polite"> | |
| 136 | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>data.bin · files · example.test</title> | 7 | <title>data.bin · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -129,8 +132,8 @@ | |||||
| 129 | <footer class="foot"> | 132 | <footer class="foot"> | ||
| 130 | <span>ermite devel</span> | 133 | <span>ermite devel</span> | ||
| 131 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 132 | - | <span class="foot-keys"> | 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 134 | </footer> | 137 | </footer> | ||
| 135 | <div class="hint" role="status" aria-live="polite"> | 138 | <div class="hint" role="status" aria-live="polite"> | ||
| 136 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-code.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>main.go · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -199,8 +202,8 @@ | |||
| 199 | 202 | <footer class="foot"> | |
| 200 | 203 | <span>ermite devel</span> | |
| 201 | 204 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 202 | - | <span class="foot-keys"> | |
| 203 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 205 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 206 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 204 | 207 | </footer> | |
| 205 | 208 | <div class="hint" role="status" aria-live="polite"> | |
| 206 | 209 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>main.go · files · example.test</title> | 7 | <title>main.go · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -199,8 +202,8 @@ | |||||
| 199 | <footer class="foot"> | 202 | <footer class="foot"> | ||
| 200 | <span>ermite devel</span> | 203 | <span>ermite devel</span> | ||
| 201 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 204 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 202 | - | <span class="foot-keys"> | 205 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 203 | - | <kbd>?</kbd> keyboard shortcuts</span> | 206 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 204 | </footer> | 207 | </footer> | ||
| 205 | <div class="hint" role="status" aria-live="polite"> | 208 | <div class="hint" role="status" aria-live="polite"> | ||
| 206 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 209 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-empty.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>empty.txt · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -129,8 +132,8 @@ | |||
| 129 | 132 | <footer class="foot"> | |
| 130 | 133 | <span>ermite devel</span> | |
| 131 | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 132 | - | <span class="foot-keys"> | |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 134 | 137 | </footer> | |
| 135 | 138 | <div class="hint" role="status" aria-live="polite"> | |
| 136 | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>empty.txt · files · example.test</title> | 7 | <title>empty.txt · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -129,8 +132,8 @@ | |||||
| 129 | <footer class="foot"> | 132 | <footer class="foot"> | ||
| 130 | <span>ermite devel</span> | 133 | <span>ermite devel</span> | ||
| 131 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 132 | - | <span class="foot-keys"> | 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 134 | </footer> | 137 | </footer> | ||
| 135 | <div class="hint" role="status" aria-live="polite"> | 138 | <div class="hint" role="status" aria-live="polite"> | ||
| 136 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-image.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>pic.png · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -131,8 +134,8 @@ | |||
| 131 | 134 | <footer class="foot"> | |
| 132 | 135 | <span>ermite devel</span> | |
| 133 | 136 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 134 | - | <span class="foot-keys"> | |
| 135 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 137 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 138 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 136 | 139 | </footer> | |
| 137 | 140 | <div class="hint" role="status" aria-live="polite"> | |
| 138 | 141 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>pic.png · files · example.test</title> | 7 | <title>pic.png · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -131,8 +134,8 @@ | |||||
| 131 | <footer class="foot"> | 134 | <footer class="foot"> | ||
| 132 | <span>ermite devel</span> | 135 | <span>ermite devel</span> | ||
| 133 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 136 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 134 | - | <span class="foot-keys"> | 137 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 135 | - | <kbd>?</kbd> keyboard shortcuts</span> | 138 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 136 | </footer> | 139 | </footer> | ||
| 137 | <div class="hint" role="status" aria-live="polite"> | 140 | <div class="hint" role="status" aria-live="polite"> | ||
| 138 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 141 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-markdown.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>docs/guide.md · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -65,7 +69,6 @@ | |||
| 65 | 69 | <span class="search-label">Go to…</span> | |
| 66 | 70 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 67 | 71 | </button> | |
| 68 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 69 | 72 | </div> | |
| 70 | 73 | </div> | |
| 71 | 74 | <nav class="tabs" aria-label="Sections"> | |
| @@ -196,8 +199,8 @@ | |||
| 196 | 199 | <footer class="foot"> | |
| 197 | 200 | <span>ermite devel</span> | |
| 198 | 201 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 199 | - | <span class="foot-keys"> | |
| 200 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 202 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 203 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 201 | 204 | </footer> | |
| 202 | 205 | <div class="hint" role="status" aria-live="polite"> | |
| 203 | 206 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>docs/guide.md · files · example.test</title> | 7 | <title>docs/guide.md · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -65,7 +69,6 @@ | |||||
| 65 | <span class="search-label">Go to…</span> | 69 | <span class="search-label">Go to…</span> | ||
| 66 | <kbd aria-hidden="true">ctrl k</kbd> | 70 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 67 | </button> | 71 | </button> | ||
| 68 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 69 | </div> | 72 | </div> | ||
| 70 | </div> | 73 | </div> | ||
| 71 | <nav class="tabs" aria-label="Sections"> | 74 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -196,8 +199,8 @@ | |||||
| 196 | <footer class="foot"> | 199 | <footer class="foot"> | ||
| 197 | <span>ermite devel</span> | 200 | <span>ermite devel</span> | ||
| 198 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 201 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 199 | - | <span class="foot-keys"> | 202 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 200 | - | <kbd>?</kbd> keyboard shortcuts</span> | 203 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 201 | </footer> | 204 | </footer> | ||
| 202 | <div class="hint" role="status" aria-live="polite"> | 205 | <div class="hint" role="status" aria-live="polite"> | ||
| 203 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 206 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-svg.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>logo.svg · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -154,8 +157,8 @@ | |||
| 154 | 157 | <footer class="foot"> | |
| 155 | 158 | <span>ermite devel</span> | |
| 156 | 159 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 157 | - | <span class="foot-keys"> | |
| 158 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 160 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 161 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 159 | 162 | </footer> | |
| 160 | 163 | <div class="hint" role="status" aria-live="polite"> | |
| 161 | 164 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>logo.svg · files · example.test</title> | 7 | <title>logo.svg · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -154,8 +157,8 @@ | |||||
| 154 | <footer class="foot"> | 157 | <footer class="foot"> | ||
| 155 | <span>ermite devel</span> | 158 | <span>ermite devel</span> | ||
| 156 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 159 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 157 | - | <span class="foot-keys"> | 160 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 158 | - | <kbd>?</kbd> keyboard shortcuts</span> | 161 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 159 | </footer> | 162 | </footer> | ||
| 160 | <div class="hint" role="status" aria-live="polite"> | 163 | <div class="hint" role="status" aria-live="polite"> | ||
| 161 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 164 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-text.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>notes.txt · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -149,8 +152,8 @@ | |||
| 149 | 152 | <footer class="foot"> | |
| 150 | 153 | <span>ermite devel</span> | |
| 151 | 154 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 152 | - | <span class="foot-keys"> | |
| 153 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 155 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 156 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 154 | 157 | </footer> | |
| 155 | 158 | <div class="hint" role="status" aria-live="polite"> | |
| 156 | 159 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>notes.txt · files · example.test</title> | 7 | <title>notes.txt · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -149,8 +152,8 @@ | |||||
| 149 | <footer class="foot"> | 152 | <footer class="foot"> | ||
| 150 | <span>ermite devel</span> | 153 | <span>ermite devel</span> | ||
| 151 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 154 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 152 | - | <span class="foot-keys"> | 155 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 153 | - | <kbd>?</kbd> keyboard shortcuts</span> | 156 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 154 | </footer> | 157 | </footer> | ||
| 155 | <div class="hint" role="status" aria-live="polite"> | 158 | <div class="hint" role="status" aria-live="polite"> | ||
| 156 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 159 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-too-large.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>big.txt · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -129,8 +132,8 @@ | |||
| 129 | 132 | <footer class="foot"> | |
| 130 | 133 | <span>ermite devel</span> | |
| 131 | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 132 | - | <span class="foot-keys"> | |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 134 | 137 | </footer> | |
| 135 | 138 | <div class="hint" role="status" aria-live="polite"> | |
| 136 | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>big.txt · files · example.test</title> | 7 | <title>big.txt · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -129,8 +132,8 @@ | |||||
| 129 | <footer class="foot"> | 132 | <footer class="foot"> | ||
| 130 | <span>ermite devel</span> | 133 | <span>ermite devel</span> | ||
| 131 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 134 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 132 | - | <span class="foot-keys"> | 135 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 133 | - | <kbd>?</kbd> keyboard shortcuts</span> | 136 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 134 | </footer> | 137 | </footer> | ||
| 135 | <div class="hint" role="status" aria-live="polite"> | 138 | <div class="hint" role="status" aria-live="polite"> | ||
| 136 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 139 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/blob-trojan.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>evil.go · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -196,8 +199,8 @@ | |||
| 196 | 199 | <footer class="foot"> | |
| 197 | 200 | <span>ermite devel</span> | |
| 198 | 201 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 199 | - | <span class="foot-keys"> | |
| 200 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 202 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 203 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 201 | 204 | </footer> | |
| 202 | 205 | <div class="hint" role="status" aria-live="polite"> | |
| 203 | 206 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>evil.go · files · example.test</title> | 7 | <title>evil.go · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -196,8 +199,8 @@ | |||||
| 196 | <footer class="foot"> | 199 | <footer class="foot"> | ||
| 197 | <span>ermite devel</span> | 200 | <span>ermite devel</span> | ||
| 198 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 201 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 199 | - | <span class="foot-keys"> | 202 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 200 | - | <kbd>?</kbd> keyboard shortcuts</span> | 203 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 201 | </footer> | 204 | </footer> | ||
| 202 | <div class="hint" role="status" aria-live="polite"> | 205 | <div class="hint" role="status" aria-live="polite"> | ||
| 203 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 206 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-big.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 add a very large file · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -166,8 +169,8 @@ | |||
| 166 | 169 | <footer class="foot"> | |
| 167 | 170 | <span>ermite devel</span> | |
| 168 | 171 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 169 | - | <span class="foot-keys"> | |
| 170 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 172 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 173 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 171 | 174 | </footer> | |
| 172 | 175 | <div class="hint" role="status" aria-live="polite"> | |
| 173 | 176 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 add a very large file · diffs · example.test</title> | 7 | <title>SHA7 add a very large file · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -166,8 +169,8 @@ | |||||
| 166 | <footer class="foot"> | 169 | <footer class="foot"> | ||
| 167 | <span>ermite devel</span> | 170 | <span>ermite devel</span> | ||
| 168 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 171 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 169 | - | <span class="foot-keys"> | 172 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 170 | - | <kbd>?</kbd> keyboard shortcuts</span> | 173 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 171 | </footer> | 174 | </footer> | ||
| 172 | <div class="hint" role="status" aria-live="polite"> | 175 | <div class="hint" role="status" aria-live="polite"> | ||
| 173 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 176 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-binary.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 change a binary file · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -161,8 +164,8 @@ | |||
| 161 | 164 | <footer class="foot"> | |
| 162 | 165 | <span>ermite devel</span> | |
| 163 | 166 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 164 | - | <span class="foot-keys"> | |
| 165 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 167 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 168 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 166 | 169 | </footer> | |
| 167 | 170 | <div class="hint" role="status" aria-live="polite"> | |
| 168 | 171 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 change a binary file · diffs · example.test</title> | 7 | <title>SHA7 change a binary file · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -161,8 +164,8 @@ | |||||
| 161 | <footer class="foot"> | 164 | <footer class="foot"> | ||
| 162 | <span>ermite devel</span> | 165 | <span>ermite devel</span> | ||
| 163 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 166 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 164 | - | <span class="foot-keys"> | 167 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 165 | - | <kbd>?</kbd> keyboard shortcuts</span> | 168 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 166 | </footer> | 169 | </footer> | ||
| 167 | <div class="hint" role="status" aria-live="polite"> | 170 | <div class="hint" role="status" aria-live="polite"> | ||
| 168 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 171 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-merge.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 merge the side branch · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -200,8 +203,8 @@ | |||
| 200 | 203 | <footer class="foot"> | |
| 201 | 204 | <span>ermite devel</span> | |
| 202 | 205 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 203 | - | <span class="foot-keys"> | |
| 204 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 206 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 207 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 205 | 208 | </footer> | |
| 206 | 209 | <div class="hint" role="status" aria-live="polite"> | |
| 207 | 210 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 merge the side branch · diffs · example.test</title> | 7 | <title>SHA7 merge the side branch · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -200,8 +203,8 @@ | |||||
| 200 | <footer class="foot"> | 203 | <footer class="foot"> | ||
| 201 | <span>ermite devel</span> | 204 | <span>ermite devel</span> | ||
| 202 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 205 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 203 | - | <span class="foot-keys"> | 206 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 204 | - | <kbd>?</kbd> keyboard shortcuts</span> | 207 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 205 | </footer> | 208 | </footer> | ||
| 206 | <div class="hint" role="status" aria-live="polite"> | 209 | <div class="hint" role="status" aria-live="polite"> | ||
| 207 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 210 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-modify-split.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -321,8 +324,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 321 | 324 | <footer class="foot"> | |
| 322 | 325 | <span>ermite devel</span> | |
| 323 | 326 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 324 | - | <span class="foot-keys"> | |
| 325 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 327 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 328 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 326 | 329 | </footer> | |
| 327 | 330 | <div class="hint" role="status" aria-live="polite"> | |
| 328 | 331 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -321,8 +324,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 321 | <footer class="foot"> | 324 | <footer class="foot"> | ||
| 322 | <span>ermite devel</span> | 325 | <span>ermite devel</span> | ||
| 323 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 326 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 324 | - | <span class="foot-keys"> | 327 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 325 | - | <kbd>?</kbd> keyboard shortcuts</span> | 328 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 326 | </footer> | 329 | </footer> | ||
| 327 | <div class="hint" role="status" aria-live="polite"> | 330 | <div class="hint" role="status" aria-live="polite"> | ||
| 328 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 331 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-modify.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -321,8 +324,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 321 | 324 | <footer class="foot"> | |
| 322 | 325 | <span>ermite devel</span> | |
| 323 | 326 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 324 | - | <span class="foot-keys"> | |
| 325 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 327 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 328 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 326 | 329 | </footer> | |
| 327 | 330 | <div class="hint" role="status" aria-live="polite"> | |
| 328 | 331 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | 7 | <title>SHA7 modify a file and make a script executable · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -321,8 +324,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 321 | <footer class="foot"> | 324 | <footer class="foot"> | ||
| 322 | <span>ermite devel</span> | 325 | <span>ermite devel</span> | ||
| 323 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 326 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 324 | - | <span class="foot-keys"> | 327 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 325 | - | <kbd>?</kbd> keyboard shortcuts</span> | 328 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 326 | </footer> | 329 | </footer> | ||
| 327 | <div class="hint" role="status" aria-live="polite"> | 330 | <div class="hint" role="status" aria-live="polite"> | ||
| 328 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 331 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-rename.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 rename a file and delete another · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -226,8 +229,8 @@ | |||
| 226 | 229 | <footer class="foot"> | |
| 227 | 230 | <span>ermite devel</span> | |
| 228 | 231 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 229 | - | <span class="foot-keys"> | |
| 230 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 232 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 233 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 231 | 234 | </footer> | |
| 232 | 235 | <div class="hint" role="status" aria-live="polite"> | |
| 233 | 236 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 rename a file and delete another · diffs · example.test</title> | 7 | <title>SHA7 rename a file and delete another · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -226,8 +229,8 @@ | |||||
| 226 | <footer class="foot"> | 229 | <footer class="foot"> | ||
| 227 | <span>ermite devel</span> | 230 | <span>ermite devel</span> | ||
| 228 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 231 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 229 | - | <span class="foot-keys"> | 232 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 230 | - | <kbd>?</kbd> keyboard shortcuts</span> | 233 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 231 | </footer> | 234 | </footer> | ||
| 232 | <div class="hint" role="status" aria-live="polite"> | 235 | <div class="hint" role="status" aria-live="polite"> | ||
| 233 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 236 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-root.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 root: the first files · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -542,8 +545,8 @@ | |||
| 542 | 545 | <footer class="foot"> | |
| 543 | 546 | <span>ermite devel</span> | |
| 544 | 547 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 545 | - | <span class="foot-keys"> | |
| 546 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 548 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 549 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 547 | 550 | </footer> | |
| 548 | 551 | <div class="hint" role="status" aria-live="polite"> | |
| 549 | 552 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 root: the first files · diffs · example.test</title> | 7 | <title>SHA7 root: the first files · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -542,8 +545,8 @@ | |||||
| 542 | <footer class="foot"> | 545 | <footer class="foot"> | ||
| 543 | <span>ermite devel</span> | 546 | <span>ermite devel</span> | ||
| 544 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 547 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 545 | - | <span class="foot-keys"> | 548 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 546 | - | <kbd>?</kbd> keyboard shortcuts</span> | 549 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 547 | </footer> | 550 | </footer> | ||
| 548 | <div class="hint" role="status" aria-live="polite"> | 551 | <div class="hint" role="status" aria-live="polite"> | ||
| 549 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 552 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commit-trojan.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>SHA7 add evil.go · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -335,8 +338,8 @@ | |||
| 335 | 338 | <footer class="foot"> | |
| 336 | 339 | <span>ermite devel</span> | |
| 337 | 340 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 338 | - | <span class="foot-keys"> | |
| 339 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 341 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 342 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 340 | 343 | </footer> | |
| 341 | 344 | <div class="hint" role="status" aria-live="polite"> | |
| 342 | 345 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>SHA7 add evil.go · diffs · example.test</title> | 7 | <title>SHA7 add evil.go · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -335,8 +338,8 @@ | |||||
| 335 | <footer class="foot"> | 338 | <footer class="foot"> | ||
| 336 | <span>ermite devel</span> | 339 | <span>ermite devel</span> | ||
| 337 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 340 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 338 | - | <span class="foot-keys"> | 341 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 339 | - | <kbd>?</kbd> keyboard shortcuts</span> | 342 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 340 | </footer> | 343 | </footer> | ||
| 341 | <div class="hint" role="status" aria-live="polite"> | 344 | <div class="hint" role="status" aria-live="polite"> | ||
| 342 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 345 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commits-page2.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>commits · long · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -160,8 +163,8 @@ | |||
| 160 | 163 | <footer class="foot"> | |
| 161 | 164 | <span>ermite devel</span> | |
| 162 | 165 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 163 | - | <span class="foot-keys"> | |
| 164 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 166 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 167 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 165 | 168 | </footer> | |
| 166 | 169 | <div class="hint" role="status" aria-live="polite"> | |
| 167 | 170 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>commits · long · example.test</title> | 7 | <title>commits · long · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -160,8 +163,8 @@ | |||||
| 160 | <footer class="foot"> | 163 | <footer class="foot"> | ||
| 161 | <span>ermite devel</span> | 164 | <span>ermite devel</span> | ||
| 162 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 165 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 163 | - | <span class="foot-keys"> | 166 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 164 | - | <kbd>?</kbd> keyboard shortcuts</span> | 167 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 165 | </footer> | 168 | </footer> | ||
| 166 | <div class="hint" role="status" aria-live="polite"> | 169 | <div class="hint" role="status" aria-live="polite"> | ||
| 167 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 170 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commits-path.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>history of a.txt · long · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -175,8 +178,8 @@ | |||
| 175 | 178 | <footer class="foot"> | |
| 176 | 179 | <span>ermite devel</span> | |
| 177 | 180 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 178 | - | <span class="foot-keys"> | |
| 179 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 181 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 182 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 180 | 183 | </footer> | |
| 181 | 184 | <div class="hint" role="status" aria-live="polite"> | |
| 182 | 185 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>history of a.txt · long · example.test</title> | 7 | <title>history of a.txt · long · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -175,8 +178,8 @@ | |||||
| 175 | <footer class="foot"> | 178 | <footer class="foot"> | ||
| 176 | <span>ermite devel</span> | 179 | <span>ermite devel</span> | ||
| 177 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 180 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 178 | - | <span class="foot-keys"> | 181 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 179 | - | <kbd>?</kbd> keyboard shortcuts</span> | 182 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 180 | </footer> | 183 | </footer> | ||
| 181 | <div class="hint" role="status" aria-live="polite"> | 184 | <div class="hint" role="status" aria-live="polite"> | ||
| 182 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 185 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/commits-root.html +10 -7
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>commits · long · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -1053,8 +1056,8 @@ | |||
| 1053 | 1056 | <footer class="foot"> | |
| 1054 | 1057 | <span>ermite devel</span> | |
| 1055 | 1058 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 1056 | - | <span class="foot-keys"> | |
| 1057 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 1059 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 1060 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 1058 | 1061 | </footer> | |
| 1059 | 1062 | <div class="hint" role="status" aria-live="polite"> | |
| 1060 | 1063 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -1063,8 +1066,8 @@ | |||
| 1063 | 1066 | </span> | |
| 1064 | 1067 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | |
| 1065 | 1068 | </span> | |
| 1066 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 1067 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 1069 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 1070 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 1068 | 1071 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 1069 | 1072 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 1070 | 1073 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>commits · long · example.test</title> | 7 | <title>commits · long · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -1053,8 +1056,8 @@ | |||||
| 1053 | <footer class="foot"> | 1056 | <footer class="foot"> | ||
| 1054 | <span>ermite devel</span> | 1057 | <span>ermite devel</span> | ||
| 1055 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 1058 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 1056 | - | <span class="foot-keys"> | 1059 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 1057 | - | <kbd>?</kbd> keyboard shortcuts</span> | 1060 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 1058 | </footer> | 1061 | </footer> | ||
| 1059 | <div class="hint" role="status" aria-live="polite"> | 1062 | <div class="hint" role="status" aria-live="polite"> | ||
| 1060 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 1063 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
| @@ -1063,8 +1066,8 @@ | |||||
| 1063 | </span> | 1066 | </span> | ||
| 1064 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | 1067 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | ||
| 1065 | </span> | 1068 | </span> | ||
| 1066 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | 1069 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Along&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 1067 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 1070 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 1068 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 1071 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 1069 | htmx:sse:before:connection -> ermiteLive.gate(event); | 1072 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 1070 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 1073 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/app/testdata/web/commits-small.html +10 -7
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>commits · diffs · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -314,8 +317,8 @@ | |||
| 314 | 317 | <footer class="foot"> | |
| 315 | 318 | <span>ermite devel</span> | |
| 316 | 319 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 317 | - | <span class="foot-keys"> | |
| 318 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 320 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 321 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 319 | 322 | </footer> | |
| 320 | 323 | <div class="hint" role="status" aria-live="polite"> | |
| 321 | 324 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -324,8 +327,8 @@ | |||
| 324 | 327 | </span> | |
| 325 | 328 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | |
| 326 | 329 | </span> | |
| 327 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 328 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 330 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 331 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 329 | 332 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 330 | 333 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 331 | 334 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>commits · diffs · example.test</title> | 7 | <title>commits · diffs · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -314,8 +317,8 @@ | |||||
| 314 | <footer class="foot"> | 317 | <footer class="foot"> | ||
| 315 | <span>ermite devel</span> | 318 | <span>ermite devel</span> | ||
| 316 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 319 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 317 | - | <span class="foot-keys"> | 320 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 318 | - | <kbd>?</kbd> keyboard shortcuts</span> | 321 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 319 | </footer> | 322 | </footer> | ||
| 320 | <div class="hint" role="status" aria-live="polite"> | 323 | <div class="hint" role="status" aria-live="polite"> | ||
| 321 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 324 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
| @@ -324,8 +327,8 @@ | |||||
| 324 | </span> | 327 | </span> | ||
| 325 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | 328 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | ||
| 326 | </span> | 329 | </span> | ||
| 327 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | 330 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Adiffs&view=commits&ref=main&head=SHA40" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 328 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 331 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 329 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 332 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 330 | htmx:sse:before:connection -> ermiteLive.gate(event); | 333 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 331 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 334 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/app/testdata/web/empty.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>quiet · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -47,7 +51,6 @@ | |||
| 47 | 51 | <span class="search-label">Go to…</span> | |
| 48 | 52 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 49 | 53 | </button> | |
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 51 | 54 | </div> | |
| 52 | 55 | </div> | |
| 53 | 56 | </header> | |
| @@ -78,8 +81,8 @@ | |||
| 78 | 81 | <footer class="foot"> | |
| 79 | 82 | <span>ermite devel</span> | |
| 80 | 83 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 81 | - | <span class="foot-keys"> | |
| 82 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 84 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 85 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 83 | 86 | </footer> | |
| 84 | 87 | <div class="hint" role="status" aria-live="polite"> | |
| 85 | 88 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>quiet · example.test</title> | 7 | <title>quiet · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -47,7 +51,6 @@ | |||||
| 47 | <span class="search-label">Go to…</span> | 51 | <span class="search-label">Go to…</span> | ||
| 48 | <kbd aria-hidden="true">ctrl k</kbd> | 52 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 49 | </button> | 53 | </button> | ||
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 51 | </div> | 54 | </div> | ||
| 52 | </div> | 55 | </div> | ||
| 53 | </header> | 56 | </header> | ||
| @@ -78,8 +81,8 @@ | |||||
| 78 | <footer class="foot"> | 81 | <footer class="foot"> | ||
| 79 | <span>ermite devel</span> | 82 | <span>ermite devel</span> | ||
| 80 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 83 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 81 | - | <span class="foot-keys"> | 84 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 82 | - | <kbd>?</kbd> keyboard shortcuts</span> | 85 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 83 | </footer> | 86 | </footer> | ||
| 84 | <div class="hint" role="status" aria-live="polite"> | 87 | <div class="hint" role="status" aria-live="polite"> | ||
| 85 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 88 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/home.html +10 -7
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <div class="hdr-acts"> | |
| 37 | 41 | <button type="button" class="btn search" data-js aria-haspopup="dialog" aria-controls="panel-cmd" aria-keyshortcuts="Control+K :" title="go to a section, ref, file or repo (ctrl k or :)" data-fire="cmd"> | |
| @@ -40,7 +44,6 @@ | |||
| 40 | 44 | <span class="search-label">Go to…</span> | |
| 41 | 45 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 42 | 46 | </button> | |
| 43 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 44 | 47 | </div> | |
| 45 | 48 | </div> | |
| 46 | 49 | </header> | |
| @@ -103,8 +106,8 @@ | |||
| 103 | 106 | <footer class="foot"> | |
| 104 | 107 | <span>ermite devel</span> | |
| 105 | 108 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 106 | - | <span class="foot-keys"> | |
| 107 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 109 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 110 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 108 | 111 | </footer> | |
| 109 | 112 | <div class="hint" role="status" aria-live="polite"> | |
| 110 | 113 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -113,8 +116,8 @@ | |||
| 113 | 116 | </span> | |
| 114 | 117 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | |
| 115 | 118 | </span> | |
| 116 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 117 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 119 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 120 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 118 | 121 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 119 | 122 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 120 | 123 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>example.test</title> | 7 | <title>example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <div class="hdr-acts"> | 40 | <div class="hdr-acts"> | ||
| 37 | <button type="button" class="btn search" data-js aria-haspopup="dialog" aria-controls="panel-cmd" aria-keyshortcuts="Control+K :" title="go to a section, ref, file or repo (ctrl k or :)" data-fire="cmd"> | 41 | <button type="button" class="btn search" data-js aria-haspopup="dialog" aria-controls="panel-cmd" aria-keyshortcuts="Control+K :" title="go to a section, ref, file or repo (ctrl k or :)" data-fire="cmd"> | ||
| @@ -40,7 +44,6 @@ | |||||
| 40 | <span class="search-label">Go to…</span> | 44 | <span class="search-label">Go to…</span> | ||
| 41 | <kbd aria-hidden="true">ctrl k</kbd> | 45 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 42 | </button> | 46 | </button> | ||
| 43 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 44 | </div> | 47 | </div> | ||
| 45 | </div> | 48 | </div> | ||
| 46 | </header> | 49 | </header> | ||
| @@ -103,8 +106,8 @@ | |||||
| 103 | <footer class="foot"> | 106 | <footer class="foot"> | ||
| 104 | <span>ermite devel</span> | 107 | <span>ermite devel</span> | ||
| 105 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 108 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 106 | - | <span class="foot-keys"> | 109 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 107 | - | <kbd>?</kbd> keyboard shortcuts</span> | 110 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 108 | </footer> | 111 | </footer> | ||
| 109 | <div class="hint" role="status" aria-live="polite"> | 112 | <div class="hint" role="status" aria-live="polite"> | ||
| 110 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 113 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
| @@ -113,8 +116,8 @@ | |||||
| 113 | </span> | 116 | </span> | ||
| 114 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | 117 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | ||
| 115 | </span> | 118 | </span> | ||
| 116 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | 119 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=home" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 117 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 120 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 118 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 121 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 119 | htmx:sse:before:connection -> ermiteLive.gate(event); | 122 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 120 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 123 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/app/testdata/web/not-found-page.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>404 · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -47,7 +51,6 @@ | |||
| 47 | 51 | <span class="search-label">Go to…</span> | |
| 48 | 52 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 49 | 53 | </button> | |
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 51 | 54 | </div> | |
| 52 | 55 | </div> | |
| 53 | 56 | </header> | |
| @@ -67,8 +70,8 @@ | |||
| 67 | 70 | <footer class="foot"> | |
| 68 | 71 | <span>ermite devel</span> | |
| 69 | 72 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 70 | - | <span class="foot-keys"> | |
| 71 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 73 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 74 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 72 | 75 | </footer> | |
| 73 | 76 | <div class="hint" role="status" aria-live="polite"> | |
| 74 | 77 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>404 · example.test</title> | 7 | <title>404 · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -47,7 +51,6 @@ | |||||
| 47 | <span class="search-label">Go to…</span> | 51 | <span class="search-label">Go to…</span> | ||
| 48 | <kbd aria-hidden="true">ctrl k</kbd> | 52 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 49 | </button> | 53 | </button> | ||
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 51 | </div> | 54 | </div> | ||
| 52 | </div> | 55 | </div> | ||
| 53 | </header> | 56 | </header> | ||
| @@ -67,8 +70,8 @@ | |||||
| 67 | <footer class="foot"> | 70 | <footer class="foot"> | ||
| 68 | <span>ermite devel</span> | 71 | <span>ermite devel</span> | ||
| 69 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 72 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 70 | - | <span class="foot-keys"> | 73 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 71 | - | <kbd>?</kbd> keyboard shortcuts</span> | 74 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 72 | </footer> | 75 | </footer> | ||
| 73 | <div class="hint" role="status" aria-live="polite"> | 76 | <div class="hint" role="status" aria-live="polite"> | ||
| 74 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 77 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/not-found.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>404 · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -47,7 +51,6 @@ | |||
| 47 | 51 | <span class="search-label">Go to…</span> | |
| 48 | 52 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 49 | 53 | </button> | |
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 51 | 54 | </div> | |
| 52 | 55 | </div> | |
| 53 | 56 | </header> | |
| @@ -67,8 +70,8 @@ | |||
| 67 | 70 | <footer class="foot"> | |
| 68 | 71 | <span>ermite devel</span> | |
| 69 | 72 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 70 | - | <span class="foot-keys"> | |
| 71 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 73 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 74 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 72 | 75 | </footer> | |
| 73 | 76 | <div class="hint" role="status" aria-live="polite"> | |
| 74 | 77 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>404 · example.test</title> | 7 | <title>404 · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -47,7 +51,6 @@ | |||||
| 47 | <span class="search-label">Go to…</span> | 51 | <span class="search-label">Go to…</span> | ||
| 48 | <kbd aria-hidden="true">ctrl k</kbd> | 52 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 49 | </button> | 53 | </button> | ||
| 50 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 51 | </div> | 54 | </div> | ||
| 52 | </div> | 55 | </div> | ||
| 53 | </header> | 56 | </header> | ||
| @@ -67,8 +70,8 @@ | |||||
| 67 | <footer class="foot"> | 70 | <footer class="foot"> | ||
| 68 | <span>ermite devel</span> | 71 | <span>ermite devel</span> | ||
| 69 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 72 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 70 | - | <span class="foot-keys"> | 73 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 71 | - | <kbd>?</kbd> keyboard shortcuts</span> | 74 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 72 | </footer> | 75 | </footer> | ||
| 73 | <div class="hint" role="status" aria-live="polite"> | 76 | <div class="hint" role="status" aria-live="polite"> | ||
| 74 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 77 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/refs.html +10 -7
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>refs · demo · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -56,7 +60,6 @@ | |||
| 56 | 60 | <span class="search-label">Go to…</span> | |
| 57 | 61 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 58 | 62 | </button> | |
| 59 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 60 | 63 | </div> | |
| 61 | 64 | </div> | |
| 62 | 65 | <nav class="tabs" aria-label="Sections"> | |
| @@ -190,8 +193,8 @@ | |||
| 190 | 193 | <footer class="foot"> | |
| 191 | 194 | <span>ermite devel</span> | |
| 192 | 195 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 193 | - | <span class="foot-keys"> | |
| 194 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 196 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 197 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 195 | 198 | </footer> | |
| 196 | 199 | <div class="hint" role="status" aria-live="polite"> | |
| 197 | 200 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -200,8 +203,8 @@ | |||
| 200 | 203 | </span> | |
| 201 | 204 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | |
| 202 | 205 | </span> | |
| 203 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 204 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 206 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 207 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 205 | 208 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 206 | 209 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 207 | 210 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>refs · demo · example.test</title> | 7 | <title>refs · demo · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -56,7 +60,6 @@ | |||||
| 56 | <span class="search-label">Go to…</span> | 60 | <span class="search-label">Go to…</span> | ||
| 57 | <kbd aria-hidden="true">ctrl k</kbd> | 61 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 58 | </button> | 62 | </button> | ||
| 59 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 60 | </div> | 63 | </div> | ||
| 61 | </div> | 64 | </div> | ||
| 62 | <nav class="tabs" aria-label="Sections"> | 65 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -190,8 +193,8 @@ | |||||
| 190 | <footer class="foot"> | 193 | <footer class="foot"> | ||
| 191 | <span>ermite devel</span> | 194 | <span>ermite devel</span> | ||
| 192 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 195 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 193 | - | <span class="foot-keys"> | 196 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 194 | - | <kbd>?</kbd> keyboard shortcuts</span> | 197 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 195 | </footer> | 198 | </footer> | ||
| 196 | <div class="hint" role="status" aria-live="polite"> | 199 | <div class="hint" role="status" aria-live="polite"> | ||
| 197 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 200 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
| @@ -200,8 +203,8 @@ | |||||
| 200 | </span> | 203 | </span> | ||
| 201 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | 204 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | ||
| 202 | </span> | 205 | </span> | ||
| 203 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | 206 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=refs" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 204 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 207 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 205 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 208 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 206 | htmx:sse:before:connection -> ermiteLive.gate(event); | 209 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 207 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 210 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/app/testdata/web/summary.html +10 -7
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>demo · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -53,7 +57,6 @@ | |||
| 53 | 57 | <span class="search-label">Go to…</span> | |
| 54 | 58 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 55 | 59 | </button> | |
| 56 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 57 | 60 | </div> | |
| 58 | 61 | </div> | |
| 59 | 62 | <nav class="tabs" aria-label="Sections"> | |
| @@ -219,8 +222,8 @@ | |||
| 219 | 222 | <footer class="foot"> | |
| 220 | 223 | <span>ermite devel</span> | |
| 221 | 224 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 222 | - | <span class="foot-keys"> | |
| 223 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 225 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 226 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 224 | 227 | </footer> | |
| 225 | 228 | <div class="hint" role="status" aria-live="polite"> | |
| 226 | 229 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -229,8 +232,8 @@ | |||
| 229 | 232 | </span> | |
| 230 | 233 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | |
| 231 | 234 | </span> | |
| 232 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | |
| 233 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | |
| 235 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); | |
| 236 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); | |
| 234 | 237 | htmx:sse:error -> ermiteLive.error(event.detail.status); | |
| 235 | 238 | htmx:sse:before:connection -> ermiteLive.gate(event); | |
| 236 | 239 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>demo · example.test</title> | 7 | <title>demo · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -53,7 +57,6 @@ | |||||
| 53 | <span class="search-label">Go to…</span> | 57 | <span class="search-label">Go to…</span> | ||
| 54 | <kbd aria-hidden="true">ctrl k</kbd> | 58 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 55 | </button> | 59 | </button> | ||
| 56 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 57 | </div> | 60 | </div> | ||
| 58 | </div> | 61 | </div> | ||
| 59 | <nav class="tabs" aria-label="Sections"> | 62 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -219,8 +222,8 @@ | |||||
| 219 | <footer class="foot"> | 222 | <footer class="foot"> | ||
| 220 | <span>ermite devel</span> | 223 | <span>ermite devel</span> | ||
| 221 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 224 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 222 | - | <span class="foot-keys"> | 225 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 223 | - | <kbd>?</kbd> keyboard shortcuts</span> | 226 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 224 | </footer> | 227 | </footer> | ||
| 225 | <div class="hint" role="status" aria-live="polite"> | 228 | <div class="hint" role="status" aria-live="polite"> | ||
| 226 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 229 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
| @@ -229,8 +232,8 @@ | |||||
| 229 | </span> | 232 | </span> | ||
| 230 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | 233 | <span class="hint-flash" :text="document.body.dataset.flash || ''"> | ||
| 231 | </span> | 234 | </span> | ||
| 232 | - | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> data.state = 'live'; | 235 | + | <span class="hint-live" data-state="connecting" hx-sse:connect="/-/events?topic=repo%3Ademo&view=summary" hx-swap="none" :text="data.state === 'offline' ? 'live updates paused' : ''" hx-on="htmx:sse:after:connection -> ermiteLive.back(); |
| 233 | - | htmx:sse:close, htmx:sse:error -> data.state = 'offline'; | 236 | + | htmx:sse:close, htmx:sse:error -> ermiteLive.lost(); |
| 234 | htmx:sse:error -> ermiteLive.error(event.detail.status); | 237 | htmx:sse:error -> ermiteLive.error(event.detail.status); | ||
| 235 | htmx:sse:before:connection -> ermiteLive.gate(event); | 238 | htmx:sse:before:connection -> ermiteLive.gate(event); | ||
| 236 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | 239 | home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data); | ||
M internal/app/testdata/web/tree-nested.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>src/lib · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -65,7 +69,6 @@ | |||
| 65 | 69 | <span class="search-label">Go to…</span> | |
| 66 | 70 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 67 | 71 | </button> | |
| 68 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 69 | 72 | </div> | |
| 70 | 73 | </div> | |
| 71 | 74 | <nav class="tabs" aria-label="Sections"> | |
| @@ -149,8 +152,8 @@ | |||
| 149 | 152 | <footer class="foot"> | |
| 150 | 153 | <span>ermite devel</span> | |
| 151 | 154 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 152 | - | <span class="foot-keys"> | |
| 153 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 155 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 156 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 154 | 157 | </footer> | |
| 155 | 158 | <div class="hint" role="status" aria-live="polite"> | |
| 156 | 159 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>src/lib · files · example.test</title> | 7 | <title>src/lib · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -65,7 +69,6 @@ | |||||
| 65 | <span class="search-label">Go to…</span> | 69 | <span class="search-label">Go to…</span> | ||
| 66 | <kbd aria-hidden="true">ctrl k</kbd> | 70 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 67 | </button> | 71 | </button> | ||
| 68 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 69 | </div> | 72 | </div> | ||
| 70 | </div> | 73 | </div> | ||
| 71 | <nav class="tabs" aria-label="Sections"> | 74 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -149,8 +152,8 @@ | |||||
| 149 | <footer class="foot"> | 152 | <footer class="foot"> | ||
| 150 | <span>ermite devel</span> | 153 | <span>ermite devel</span> | ||
| 151 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 154 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 152 | - | <span class="foot-keys"> | 155 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 153 | - | <kbd>?</kbd> keyboard shortcuts</span> | 156 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 154 | </footer> | 157 | </footer> | ||
| 155 | <div class="hint" role="status" aria-live="polite"> | 158 | <div class="hint" role="status" aria-live="polite"> | ||
| 156 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 159 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/tree-root.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>tree · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -59,7 +63,6 @@ | |||
| 59 | 63 | <span class="search-label">Go to…</span> | |
| 60 | 64 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 61 | 65 | </button> | |
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 63 | 66 | </div> | |
| 64 | 67 | </div> | |
| 65 | 68 | <nav class="tabs" aria-label="Sections"> | |
| @@ -376,8 +379,8 @@ | |||
| 376 | 379 | <footer class="foot"> | |
| 377 | 380 | <span>ermite devel</span> | |
| 378 | 381 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 379 | - | <span class="foot-keys"> | |
| 380 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 382 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 383 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 381 | 384 | </footer> | |
| 382 | 385 | <div class="hint" role="status" aria-live="polite"> | |
| 383 | 386 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>tree · files · example.test</title> | 7 | <title>tree · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -59,7 +63,6 @@ | |||||
| 59 | <span class="search-label">Go to…</span> | 63 | <span class="search-label">Go to…</span> | ||
| 60 | <kbd aria-hidden="true">ctrl k</kbd> | 64 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 61 | </button> | 65 | </button> | ||
| 62 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 63 | </div> | 66 | </div> | ||
| 64 | </div> | 67 | </div> | ||
| 65 | <nav class="tabs" aria-label="Sections"> | 68 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -376,8 +379,8 @@ | |||||
| 376 | <footer class="foot"> | 379 | <footer class="foot"> | ||
| 377 | <span>ermite devel</span> | 380 | <span>ermite devel</span> | ||
| 378 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 381 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 379 | - | <span class="foot-keys"> | 382 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 380 | - | <kbd>?</kbd> keyboard shortcuts</span> | 383 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 381 | </footer> | 384 | </footer> | ||
| 382 | <div class="hint" role="status" aria-live="polite"> | 385 | <div class="hint" role="status" aria-live="polite"> | ||
| 383 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 386 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/tree-slash-ref.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>x · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -146,8 +149,8 @@ | |||
| 146 | 149 | <footer class="foot"> | |
| 147 | 150 | <span>ermite devel</span> | |
| 148 | 151 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 149 | - | <span class="foot-keys"> | |
| 150 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 152 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 153 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 151 | 154 | </footer> | |
| 152 | 155 | <div class="hint" role="status" aria-live="polite"> | |
| 153 | 156 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>x · files · example.test</title> | 7 | <title>x · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -146,8 +149,8 @@ | |||||
| 146 | <footer class="foot"> | 149 | <footer class="foot"> | ||
| 147 | <span>ermite devel</span> | 150 | <span>ermite devel</span> | ||
| 148 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 151 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 149 | - | <span class="foot-keys"> | 152 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 150 | - | <kbd>?</kbd> keyboard shortcuts</span> | 153 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 151 | </footer> | 154 | </footer> | ||
| 152 | <div class="hint" role="status" aria-live="polite"> | 155 | <div class="hint" role="status" aria-live="polite"> | ||
| 153 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 156 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||
M internal/app/testdata/web/tree-subdir.html +8 -5
| @@ -5,7 +5,12 @@ | |||
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | 6 | <meta name="color-scheme" content="light dark"> | |
| 7 | 7 | <title>docs · files · example.test</title> | |
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |
| 8 | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | |
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |
| 9 | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | |
| 10 | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | |
| 11 | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | |
| @@ -28,10 +33,9 @@ | |||
| 28 | 33 | <a class="skip" href="#main">skip to content</a> | |
| 29 | 34 | <header class="hdr"> | |
| 30 | 35 | <div class="hdr-top"> | |
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | |
| 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> | |
| 32 | 37 | <span class="brand-mark" aria-hidden="true"> | |
| 33 | 38 | </span> | |
| 34 | - | <span class="brand-name">example.test</span> | |
| 35 | 39 | </a> | |
| 36 | 40 | <nav class="crumbs" aria-label="Path"> | |
| 37 | 41 | <ol> | |
| @@ -62,7 +66,6 @@ | |||
| 62 | 66 | <span class="search-label">Go to…</span> | |
| 63 | 67 | <kbd aria-hidden="true">ctrl k</kbd> | |
| 64 | 68 | </button> | |
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |
| 66 | 69 | </div> | |
| 67 | 70 | </div> | |
| 68 | 71 | <nav class="tabs" aria-label="Sections"> | |
| @@ -171,8 +174,8 @@ | |||
| 171 | 174 | <footer class="foot"> | |
| 172 | 175 | <span>ermite devel</span> | |
| 173 | 176 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | |
| 174 | - | <span class="foot-keys"> | |
| 175 | - | <kbd>?</kbd> keyboard shortcuts</span> | |
| 177 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> | |
| 178 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> | |
| 176 | 179 | </footer> | |
| 177 | 180 | <div class="hint" role="status" aria-live="polite"> | |
| 178 | 181 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | |
| @@ -5,7 +5,12 @@ | |||||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> | ||
| 7 | <title>docs · files · example.test</title> | 7 | <title>docs · files · example.test</title> | ||
| 8 | + | <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"> | |||
| 9 | + | <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"> | |||
| 10 | + | <meta name="apple-mobile-web-app-title" content="ermite"> | |||
| 8 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | 11 | <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg"> | ||
| 12 | + | <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png"> | |||
| 13 | + | <link rel="manifest" href="/-/static/app/manifest.HASH.webmanifest"> | |||
| 9 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | 14 | <link rel="preload" href="/-/static/fonts/atkinson-next.HASH.woff2" as="font" type="font/woff2" crossorigin> | ||
| 10 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | 15 | <link rel="stylesheet" href="/-/static/css/ermite.HASH.css"> | ||
| 11 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | 16 | <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'> | ||
| @@ -28,10 +33,9 @@ | |||||
| 28 | <a class="skip" href="#main">skip to content</a> | 33 | <a class="skip" href="#main">skip to content</a> | ||
| 29 | <header class="hdr"> | 34 | <header class="hdr"> | ||
| 30 | <div class="hdr-top"> | 35 | <div class="hdr-top"> | ||
| 31 | - | <a class="brand" href="/" data-g="h" title="home (g h)"> | 36 | + | <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)"> |
| 32 | <span class="brand-mark" aria-hidden="true"> | 37 | <span class="brand-mark" aria-hidden="true"> | ||
| 33 | </span> | 38 | </span> | ||
| 34 | - | <span class="brand-name">example.test</span> | |||
| 35 | </a> | 39 | </a> | ||
| 36 | <nav class="crumbs" aria-label="Path"> | 40 | <nav class="crumbs" aria-label="Path"> | ||
| 37 | <ol> | 41 | <ol> | ||
| @@ -62,7 +66,6 @@ | |||||
| 62 | <span class="search-label">Go to…</span> | 66 | <span class="search-label">Go to…</span> | ||
| 63 | <kbd aria-hidden="true">ctrl k</kbd> | 67 | <kbd aria-hidden="true">ctrl k</kbd> | ||
| 64 | </button> | 68 | </button> | ||
| 65 | - | <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button> | |||
| 66 | </div> | 69 | </div> | ||
| 67 | </div> | 70 | </div> | ||
| 68 | <nav class="tabs" aria-label="Sections"> | 71 | <nav class="tabs" aria-label="Sections"> | ||
| @@ -171,8 +174,8 @@ | |||||
| 171 | <footer class="foot"> | 174 | <footer class="foot"> | ||
| 172 | <span>ermite devel</span> | 175 | <span>ermite devel</span> | ||
| 173 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | 176 | <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a> | ||
| 174 | - | <span class="foot-keys"> | 177 | + | <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"> |
| 175 | - | <kbd>?</kbd> keyboard shortcuts</span> | 178 | + | <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button> |
| 176 | </footer> | 179 | </footer> | ||
| 177 | <div class="hint" role="status" aria-live="polite"> | 180 | <div class="hint" role="status" aria-live="polite"> | ||
| 178 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | 181 | <span class="hint-nav" :text="document.body.dataset.nav || ''"> | ||