Monorepo for Tangled tangled.org

appview: support safari icons (favicon, bookmark)

Safari < 26.0 doesn't support svg favicon and even after 26.0, it
defaults to #000 color, resulting to black favicon on white background.
It is better to have actual ico image hosted on `/favicon.ico`

To prevent chrome and other browsers choosing ico image for favicon over
svg, we are restricting the `favicon.ico` size to `48x48`.

180x180 `apple-touch-icon.png` is also needed to support Safari bookmark
icons. Commonly known as "Favorites". Because Safari bookmark icons is
pretty large, we are using logo-dolly (dolly with eyes) here.

reference:
<https://dev.to/masakudamatsu/favicon-nightmare-how-to-maintain-sanity-3al7>

Fix: <https://tangled.org/tangled.org/core/issues/306>

Signed-off-by: Seongmin Lee <git@boltless.me>

boltless.me 4902c505 3596ea2d

verified
Changed files
+100 -21
appview
nix
+1 -1
appview/middleware/middleware.go
··· 176 176 } 177 177 178 178 func (mw Middleware) ResolveIdent() middlewareFunc { 179 - excluded := []string{"favicon.ico"} 179 + excluded := []string{"favicon.ico", "favicon.svg"} 180 180 181 181 return func(next http.Handler) http.Handler { 182 182 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
appview/pages/assets/apple-touch-icon.png

This is a binary file and will not be displayed.

appview/pages/assets/favicon.ico

This is a binary file and will not be displayed.

+83
appview/pages/assets/favicon.svg
··· 1 + <svg 2 + version="1.1" 3 + id="svg1" 4 + width="25" 5 + height="25" 6 + color="#ffffff" 7 + viewBox="0 0 25 25" 8 + sodipodi:docname="tangled_dolly_face_only_black_on_trans.svg" 9 + inkscape:export-filename="tangled_dolly_silhouette_black_on_trans.svg" 10 + inkscape:export-xdpi="96" 11 + inkscape:export-ydpi="96" 12 + inkscape:version="1.4 (e7c3feb100, 2024-10-09)" 13 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 14 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 15 + xmlns="http://www.w3.org/2000/svg" 16 + xmlns:svg="http://www.w3.org/2000/svg" 17 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 18 + xmlns:cc="http://creativecommons.org/ns#"> 19 + <sodipodi:namedview 20 + id="namedview1" 21 + pagecolor="#ffffff" 22 + bordercolor="#000000" 23 + borderopacity="0.25" 24 + inkscape:showpageshadow="2" 25 + inkscape:pageopacity="0.0" 26 + inkscape:pagecheckerboard="true" 27 + inkscape:deskcolor="#d5d5d5" 28 + inkscape:zoom="64" 29 + inkscape:cx="4.96875" 30 + inkscape:cy="13.429688" 31 + inkscape:window-width="3840" 32 + inkscape:window-height="2160" 33 + inkscape:window-x="0" 34 + inkscape:window-y="0" 35 + inkscape:window-maximized="0" 36 + inkscape:current-layer="g1" 37 + borderlayer="true"> 38 + <inkscape:page 39 + x="0" 40 + y="0" 41 + width="25" 42 + height="25" 43 + id="page2" 44 + margin="0" 45 + bleed="0" /> 46 + </sodipodi:namedview> 47 + <g 48 + inkscape:groupmode="layer" 49 + inkscape:label="Image" 50 + id="g1" 51 + transform="translate(-0.42924038,-0.87777209)"> 52 + <path 53 + class="dolly" 54 + fill="currentColor" 55 + style="stroke-width:0.111183" 56 + d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1.7138087,-0.916491 0.079382,0.927141 0.1688108,1.923227 0.4821259,2.828358 0.3596254,1.171275 1.6262605,1.915695 2.8251855,1.745211 0.08481,-0.0066 0.218672,-0.01769 0.218672,-0.0176 z" 57 + id="path7" 58 + sodipodi:nodetypes="sccccccccccccccccccsscccccccccscccccccsc" /> 59 + </g> 60 + <metadata 61 + id="metadata1"> 62 + <rdf:RDF> 63 + <cc:Work 64 + rdf:about=""> 65 + <cc:license 66 + rdf:resource="http://creativecommons.org/licenses/by/4.0/" /> 67 + </cc:Work> 68 + <cc:License 69 + rdf:about="http://creativecommons.org/licenses/by/4.0/"> 70 + <cc:permits 71 + rdf:resource="http://creativecommons.org/ns#Reproduction" /> 72 + <cc:permits 73 + rdf:resource="http://creativecommons.org/ns#Distribution" /> 74 + <cc:requires 75 + rdf:resource="http://creativecommons.org/ns#Notice" /> 76 + <cc:requires 77 + rdf:resource="http://creativecommons.org/ns#Attribution" /> 78 + <cc:permits 79 + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> 80 + </cc:License> 81 + </rdf:RDF> 82 + </metadata> 83 + </svg>
+8 -5
appview/pages/pages.go
··· 210 210 return tpl.ExecuteTemplate(w, "layouts/base", params) 211 211 } 212 212 213 - func (p *Pages) Favicon(w io.Writer) error { 214 - return p.executePlain("fragments/dolly/silhouette", w, nil) 215 - } 216 - 217 213 type LoginParams struct { 218 214 ReturnUrl string 219 215 ErrorCode string ··· 1414 1410 return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub)))) 1415 1411 } 1416 1412 1413 + func (p *Pages) StaticRedirect(target string) http.HandlerFunc { 1414 + return func(w http.ResponseWriter, r *http.Request) { 1415 + http.Redirect(w, r, target, http.StatusMovedPermanently) 1416 + } 1417 + } 1418 + 1417 1419 func Cache(h http.Handler) http.Handler { 1418 1420 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1419 1421 path := strings.Split(r.URL.Path, "?")[0] 1420 1422 1421 1423 if strings.HasSuffix(path, ".css") { 1422 - // on day for css files 1424 + // one day for css files 1423 1425 w.Header().Set("Cache-Control", "public, max-age=86400") 1424 1426 } else { 1427 + // one year for others 1425 1428 w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") 1426 1429 } 1427 1430 h.ServeHTTP(w, r)
+5
appview/pages/templates/layouts/base.html
··· 7 7 <meta name="description" content="Social coding, but for real this time!"/> 8 8 <meta name="htmx-config" content='{"includeIndicatorStyles": false}'> 9 9 10 + <!-- favicon/web manifest --> 11 + <link rel="icon" href="/favicon.ico" sizes="48x48"/> 12 + <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml"/> 13 + <link rel="apple-touch-icon" href="/static/apple-touch-icon.png"/> 14 + 10 15 <script defer src="/static/htmx.min.js"></script> 11 16 <script defer src="/static/htmx-ext-ws.min.js"></script> 12 17 <script defer src="/static/actor-typeahead.js" type="module"></script>
+2 -2
appview/state/router.go
··· 32 32 s.pages, 33 33 ) 34 34 35 - router.Get("/favicon.svg", s.Favicon) 36 - router.Get("/favicon.ico", s.Favicon) 35 + router.Get("/favicon.ico", s.pages.StaticRedirect("/static/favicon.ico")) 36 + router.Get("/favicon.svg", s.pages.StaticRedirect("/static/favicon.svg")) 37 37 router.Get("/pwa-manifest.json", s.PWAManifest) 38 38 router.Get("/robots.txt", s.RobotsTxt) 39 39
-13
appview/state/state.go
··· 202 202 return s.db.Close() 203 203 } 204 204 205 - func (s *State) Favicon(w http.ResponseWriter, r *http.Request) { 206 - w.Header().Set("Content-Type", "image/svg+xml") 207 - w.Header().Set("Cache-Control", "public, max-age=31536000") // one year 208 - w.Header().Set("ETag", `"favicon-svg-v1"`) 209 - 210 - if match := r.Header.Get("If-None-Match"); match == `"favicon-svg-v1"` { 211 - w.WriteHeader(http.StatusNotModified) 212 - return 213 - } 214 - 215 - s.pages.Favicon(w) 216 - } 217 - 218 205 func (s *State) RobotsTxt(w http.ResponseWriter, r *http.Request) { 219 206 w.Header().Set("Content-Type", "text/plain") 220 207 w.Header().Set("Cache-Control", "public, max-age=86400") // one day
+1
nix/pkgs/appview-static-files.nix
··· 26 26 cp -f ${inter-fonts-src}/InterVariable*.ttf fonts/ 27 27 cp -f ${ibm-plex-mono-src}/fonts/complete/woff2/IBMPlexMono*.woff2 fonts/ 28 28 cp -f ${actor-typeahead-src}/actor-typeahead.js . 29 + cp -f ${src}/appview/pages/assets/* . 29 30 # tailwindcss -c $src/tailwind.config.js -i $src/input.css -o tw.css won't work 30 31 # for whatever reason (produces broken css), so we are doing this instead 31 32 cd ${src} && ${tailwindcss}/bin/tailwindcss -i input.css -o $out/tw.css