diff --git a/site/assets/account.html b/site/assets/account.html index e722408..d738294 100644 --- a/site/assets/account.html +++ b/site/assets/account.html @@ -8,6 +8,7 @@ Your account — readm3 + diff --git a/site/assets/admin.html b/site/assets/admin.html index a2364f8..7e87810 100644 --- a/site/assets/admin.html +++ b/site/assets/admin.html @@ -3,7 +3,7 @@ Your Markdown workspace — readm3 - + diff --git a/site/build.ts b/site/build.ts index bf9d765..a5fefb5 100644 --- a/site/build.ts +++ b/site/build.ts @@ -182,6 +182,8 @@ function shell(options: { title: string; description: string; path: string; main + + @@ -725,6 +727,14 @@ write("viewer.webmanifest", JSON.stringify({ launch_type: "single-client", }], })); +// The site-wide manifest: every page but the editor links it, so readm3.com installs from its homepage. +write("manifest.webmanifest", JSON.stringify({ + id: "/", name: "readm3", short_name: "readm3", + description: "Markdown editing and sharing for people and teams.", + start_url: "/", scope: "/", display: "standalone", + background_color: "#05070a", theme_color: "#05070a", lang: "en", + icons: [192, 512].flatMap((size) => ["any", "maskable"].map((purpose) => ({ src: `/viewer-assets/icon-${size}.png`, sizes: `${size}x${size}`, type: "image/png", purpose }))), +})); const precache = ["/viewer", script, ...chunks, viewerCss, sharedCss, adminCss, seed, "/viewer.webmanifest", "/favicon.svg", "/viewer-assets/icon-192.png", "/viewer-assets/icon-512.png"]; const cacheVersion = createHash("sha256").update(viewerHtml + precache.join("|")).digest("hex").slice(0, 16); write("viewer-sw.js", readFileSync(join(here, "assets/viewer-sw.js"), "utf8") diff --git a/site/test/viewer.spec.ts b/site/test/viewer.spec.ts index 20ce26c..24856cf 100644 --- a/site/test/viewer.spec.ts +++ b/site/test/viewer.spec.ts @@ -610,6 +610,20 @@ test("still renders a Markdown paste as a document", async ({ page, request }) = await expect(page.locator("#raw-link")).toBeVisible(); }); +test("the homepage links an installable site manifest", async ({ request }) => { + expect(await (await request.get("/")).text()).toContain(''); + const response = await request.get("/manifest.webmanifest"); + expect(response.headers()["content-type"]).toContain("manifest+json"); + const manifest = await response.json(); + expect(manifest).toMatchObject({ name: "readm3", start_url: "/", scope: "/", display: "standalone" }); + for (const size of ["192x192", "512x512"]) expect(manifest.icons.some((icon: { sizes: string }) => icon.sizes === size)).toBe(true); + for (const icon of manifest.icons) { + const image = await request.get(icon.src); + expect(image.ok()).toBe(true); + expect(image.headers()["content-type"]).toBe("image/png"); + } +}); + test("installs a complete offline shell and restores edited documents after an offline reload", async ({ page, context, request }) => { const manifestResponse = await request.get("/viewer.webmanifest"); expect(manifestResponse.headers()["content-type"]).toContain("manifest+json");