diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..6f5738c Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png new file mode 100644 index 0000000..087d336 Binary files /dev/null and b/public/favicon-96x96.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..8771dcf Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.png b/public/favicon.png deleted file mode 100644 index 4b82d09..0000000 Binary files a/public/favicon.png and /dev/null differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..79cd69f --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ +RealFaviconGeneratorhttps://realfavicongenerator.net \ No newline at end of file diff --git a/public/index.html b/public/index.html index ada80d0..bdbd0ca 100644 --- a/public/index.html +++ b/public/index.html @@ -6,8 +6,13 @@ kovi — Your reading, beautifully understood - - + + + + + + + diff --git a/public/kovi-logo.png b/public/kovi-logo.png index f6a99ce..15fe938 100644 Binary files a/public/kovi-logo.png and b/public/kovi-logo.png differ diff --git a/public/kovi-mark.png b/public/kovi-mark.png index 6f19ed8..4847b6c 100644 Binary files a/public/kovi-mark.png and b/public/kovi-mark.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..8f211ef --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,23 @@ +{ + "name": "kovi", + "short_name": "kovi", + "description": "Your reading, beautifully understood.", + "start_url": "/", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "theme_color": "#f6f3ec", + "background_color": "#f6f3ec", + "display": "standalone" +} diff --git a/public/web-app-manifest-192x192.png b/public/web-app-manifest-192x192.png new file mode 100644 index 0000000..0d15643 Binary files /dev/null and b/public/web-app-manifest-192x192.png differ diff --git a/public/web-app-manifest-512x512.png b/public/web-app-manifest-512x512.png new file mode 100644 index 0000000..fc4552a Binary files /dev/null and b/public/web-app-manifest-512x512.png differ diff --git a/server.mjs b/server.mjs index bd8931a..f0199bf 100644 --- a/server.mjs +++ b/server.mjs @@ -307,7 +307,7 @@ async function handleApi(req,res,url) { return false; } -const MIME={'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.webp':'image/webp','.gif':'image/gif','.ico':'image/x-icon','.zip':'application/zip'}; +const MIME={'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.webp':'image/webp','.gif':'image/gif','.ico':'image/x-icon','.webmanifest':'application/manifest+json','.zip':'application/zip'}; async function serveFile(res,filePath,{cache=false}={}) { try { const st=await fsp.stat(filePath); if(!st.isFile()) return false; diff --git a/tests/frontend.test.mjs b/tests/frontend.test.mjs index 045011c..2f33cb1 100644 --- a/tests/frontend.test.mjs +++ b/tests/frontend.test.mjs @@ -5,6 +5,7 @@ import fs from 'node:fs'; const source=fs.readFileSync(new URL('../public/app.js', import.meta.url),'utf8'); const html=fs.readFileSync(new URL('../public/index.html', import.meta.url),'utf8'); const css=fs.readFileSync(new URL('../public/styles.css', import.meta.url),'utf8'); +const server=fs.readFileSync(new URL('../server.mjs', import.meta.url),'utf8'); test('manual SQLite upload attaches the browser CSRF token',()=>{ assert.match(source,/setRequestHeader\('X-Kovi-CSRF',csrf\)/); @@ -159,6 +160,23 @@ test('sidebar and empty state use the kovi logo assets instead of split legacy b assert.match(html,/id="themeColor"/);assert.match(source,/themeColor/); }); -test('the favicon uses the kovi mascot instead of the old svg mark',()=>{ - assert.match(html,/rel="icon" href="\/favicon\.png"/);assert.doesNotMatch(html,/logo\.svg/); +test('the favicon set uses the kovi mascot assets instead of the old svg mark',()=>{ + assert.match(html,/rel="icon" type="image\/png" sizes="96x96" href="\/favicon-96x96\.png"/); + assert.match(html,/rel="icon" type="image\/svg\+xml" href="\/favicon\.svg"/); + assert.match(html,/rel="shortcut icon" href="\/favicon\.ico"/); + assert.match(html,/rel="apple-touch-icon" sizes="180x180" href="\/apple-touch-icon\.png"/); + assert.match(html,/rel="manifest" href="\/site\.webmanifest"/); + assert.doesNotMatch(html,/favicon\.png/);assert.doesNotMatch(html,/logo\.svg/); +}); + +test('the web app manifest brands kovi and points at the installed icons',()=>{ + const manifest=JSON.parse(fs.readFileSync(new URL('../public/site.webmanifest', import.meta.url),'utf8')); + assert.equal(manifest.name,'kovi');assert.equal(manifest.short_name,'kovi'); + assert.deepEqual(manifest.icons.map(icon=>icon.src),['/web-app-manifest-192x192.png','/web-app-manifest-512x512.png']); + for(const icon of manifest.icons){assert.equal(icon.type,'image/png');assert.match(icon.sizes,/^\d+x\d+$/);} + assert.match(manifest.theme_color,/^#[0-9a-f]{6}$/i); +}); + +test('the static server serves the web app manifest with its manifest MIME type',()=>{ + assert.match(server,/\.webmanifest':'application\/manifest\+json'/); });