docs: a README with a face, in English and Portuguese; decode every HTML entity in previews
The README opens like a product page: the mark, a line on what it is, the links, the language switch and a picture of the real interface running on an invented mailbox, in dark and light and in both languages. scripts/screenshots holds that invented mailbox and the script that photographs it, so the pictures can be redone. Previews now decode any HTML entity through the browser rather than a short list, so a Portuguese or marketing preview no longer prints ç or ·. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8226c76d7a
commit
1e06491019
16 files changed
+679
-5
No files matched your search
@@ -16,7 +16,21 @@ const NAMED: Record<string, string> = {
|
||||
nbsp: " ",
|
||||
};
|
||||
|
||||
/**
|
||||
* Turns `á`, `—`, `'` and the rest into the characters they
|
||||
* stand for. The browser already knows all of them (HTML defines over two
|
||||
* thousand), so it is asked rather than given a short list: a list stops at
|
||||
* `&` and leaves `ç` and `·` printed in a Portuguese or a
|
||||
* marketing preview. A textarea holds text only, so nothing in it runs.
|
||||
*/
|
||||
function decodeEntities(text: string): string {
|
||||
if (!text.includes("&")) return text;
|
||||
if (typeof document !== "undefined") {
|
||||
const holder = document.createElement("textarea");
|
||||
holder.innerHTML = text;
|
||||
return holder.value;
|
||||
}
|
||||
// No DOM (rendered on the server): the few that matter most.
|
||||
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, body: string) => {
|
||||
if (body[0] === "#") {
|
||||
const code = body[1].toLowerCase() === "x" ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
|
||||
|
||||
@@ -26,4 +26,16 @@ describe("plainSnippet", () => {
|
||||
it("keeps an entity it does not know", () => {
|
||||
expect(plainSnippet("a &madeup; b")).toBe("a &madeup; b");
|
||||
});
|
||||
|
||||
// The preview of a Portuguese or a marketing message is full of these, and a
|
||||
// short list of entities printed them as text.
|
||||
it("decodes any named entity, not only the common few", () => {
|
||||
expect(plainSnippet("Atualização — número 3 → ok · fim")).toBe("Atualiza\u00e7\u00e3o \u2014 n\u00famero 3 \u2192 ok \u00b7 fim");
|
||||
expect(plainSnippet("Café & crème …")).toBe("Caf\u00e9 & cr\u00e8me \u2026");
|
||||
});
|
||||
|
||||
it("does not run markup that an entity decodes into", () => {
|
||||
expect(plainSnippet("<img src=x onerror=alert(1)> safe")).toBe("<img src=x onerror=alert(1)> safe");
|
||||
expect(document.querySelector("img")).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user