vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Remove the avatars and read people by name again
28b19c7433fa6f6fe1ce826c443870385d856eb8 · Keenan Nemetz · 8d ago · parent 0fbeca85
Remove the avatars and read people by name again The marks were harder to read than the names they replaced, so they are gone: the account menu is back to the user icon, and the issue and change tables, the profile, the directory and the contributor list all say the account name. This takes out everything that existed to draw a mark: the DiceBear bundle and its licence file, the entry source that built it, the Landscape style, avatar.go and its tests, the avatar template and its three helper functions, the avatar palette in everforest.css and the illustration rules in vole.css, and the app.js block that rendered and re-rendered them on a theme change. The tables keep the layout from the previous change — an author column and an assignee column — because that part reads better with names too. The assignee cells are the @name chips that already linked to the filtered list. `vole workflow run verify` passes, and the licence inventory is back to one entry fewer.
server/web/avatar.go −83
| @@ -1,83 +0,0 @@ | |||
| 1 | package web | ||
| 2 | |||
| 3 | // A name's avatar. | ||
| 4 | // | ||
| 5 | // The mark itself is drawn in the browser by DiceBear from a seed, so this file | ||
| 6 | // decides only the ground and the fallback. Two things follow from that split, | ||
| 7 | // and both are deliberate: | ||
| 8 | // | ||
| 9 | // - The server renders the account's initial on one of seven Everforest grounds, | ||
| 10 | // exactly as every avatar looked before the marks existed. A browser without | ||
| 11 | // scripts, or one that has not run the bundle yet, shows a letter rather than | ||
| 12 | // an empty tile. The avatar is decoration; nothing about the page depends on | ||
| 13 | // it arriving. | ||
| 14 | // - The seed is the name, never an index or a session. The same account draws | ||
| 15 | // the same mark on every page and between visits, and the instance stores no | ||
| 16 | // image, accepts no upload, and calls no avatar service. The bundle is | ||
| 17 | // embedded in the binary like every other asset, so choosing an avatar makes | ||
| 18 | // no request anywhere. | ||
| 19 | // | ||
| 20 | // The tone is derived here rather than in the sheet so a letter keeps its ground | ||
| 21 | // even when the stylesheet has not loaded; vole.css names the same seven colours. | ||
| 22 | // The mark is decoration, not an identifier: a name is what identifies an account. | ||
| 23 | |||
| 24 | import ( | ||
| 25 | "crypto/sha256" | ||
| 26 | "strings" | ||
| 27 | ) | ||
| 28 | |||
| 29 | // avatarView is one avatar ready to render. Href is empty for a name that is not | ||
| 30 | // an account; the template then draws a span instead of a link. | ||
| 31 | type avatarView struct { | ||
| 32 | // Name is the seed, rendered as an attribute so the browser can draw the mark. | ||
| 33 | Name string | ||
| 34 | Tone int | ||
| 35 | // Initial is the fallback letter, shown until the mark is drawn. | ||
| 36 | Initial string | ||
| 37 | Href string | ||
| 38 | // Label is set when the avatar is the only content of its element, as in a | ||
| 39 | // table cell. It is decoration beside a printed name, but it is the name | ||
| 40 | // there, so it has to say so to a screen reader. | ||
| 41 | Label string | ||
| 42 | } | ||
| 43 | |||
| 44 | // avatar is the avatar for a name, on its own. | ||
| 45 | func (w *Web) avatar(name string) avatarView { return portrait(name) } | ||
| 46 | |||
| 47 | // avatarUser is the avatar for an account, linked to the profile it belongs to | ||
| 48 | // when the name resolves to one. | ||
| 49 | func (w *Web) avatarUser(ref userRef) avatarView { | ||
| 50 | v := portrait(ref.Name) | ||
| 51 | v.Href = ref.Href | ||
| 52 | return v | ||
| 53 | } | ||
| 54 | |||
| 55 | // avatarOnly is avatarUser for a place where the picture is the whole content, | ||
| 56 | // such as an author cell in a table. It carries the name for a screen reader | ||
| 57 | // instead of hiding itself the way a decorative avatar does. | ||
| 58 | func (w *Web) avatarOnly(ref userRef) avatarView { | ||
| 59 | v := w.avatarUser(ref) | ||
| 60 | v.Label = ref.Name | ||
| 61 | return v | ||
| 62 | } | ||
| 63 | |||
| 64 | // portrait derives the ground and the fallback letter from the name. SHA-256 | ||
| 65 | // rather than a cheap hash so two similar names do not land on the same tone by | ||
| 66 | // construction; the mark's own variation comes from the bundle's seeded PRNG. | ||
| 67 | func portrait(name string) avatarView { | ||
| 68 | sum := sha256.Sum256([]byte(name)) | ||
| 69 | return avatarView{ | ||
| 70 | Name: name, | ||
| 71 | Tone: int(sum[8]%7) + 1, | ||
| 72 | Initial: initial(name), | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | // initial is the fallback letter. Account names are authoritative, so the first | ||
| 77 | // rune is enough; an empty name gets "?" rather than an empty tile. | ||
| 78 | func initial(name string) string { | ||
| 79 | for _, r := range strings.TrimSpace(name) { | ||
| 80 | return strings.ToUpper(string(r)) | ||
| 81 | } | ||
| 82 | return "?" | ||
| 83 | } | ||