Merge pull request #6 from LINUXexpert-org/default-mail-handler

Offer ihasmail as the browser's mailto: handler
This commit is contained in:
LINUXexpert.org
2026-08-23 13:29:21 -07:00
committed by GitHub
10 changed files with 294 additions and 19 deletions
+35 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { formatAddress, initials, isValidEmail, parseAddressList } from "../address";
import { formatAddress, initials, isValidEmail, parseAddressList, parseMailto } from "../address";
describe("address parsing", () => {
it("parses mixed lists", () => {
@@ -21,3 +21,37 @@ describe("address parsing", () => {
expect(initials({ name: null, email: "[email protected]" })).toBe("LK");
});
});
describe("mailto URLs", () => {
it("takes recipients from the path, the to header, or both", () => {
expect(parseMailto("mailto:[email protected]")).toMatchObject({ to: [{ name: null, email: "[email protected]" }] });
expect(parseMailto("mailto:[email protected]").to).toEqual([{ name: null, email: "[email protected]" }]);
expect(parseMailto("mailto:[email protected][email protected]").to).toHaveLength(2);
expect(parseMailto("mailto:[email protected],[email protected]").to).toHaveLength(2);
});
it("reads cc, bcc, subject and body", () => {
const m = parseMailto("mailto:[email protected][email protected]&[email protected]&subject=Hello%20there&body=Line%20one");
expect(m.cc).toEqual([{ name: null, email: "[email protected]" }]);
expect(m.bcc).toEqual([{ name: null, email: "[email protected]" }]);
expect(m.subject).toBe("Hello there");
expect(m.body).toBe("Line one");
});
it("is case-insensitive about headers and decodes plus as space", () => {
const m = parseMailto("MAILTO:[email protected]?SUBJECT=Re:+lunch&Body=see+you");
expect(m.subject).toBe("Re: lunch");
expect(m.body).toBe("see you");
});
it("keeps display names and survives malformed escapes", () => {
expect(parseMailto('mailto:%22Smith%2C%20John%22%20%[email protected]%3E').to).toEqual([{ name: "Smith, John", email: "[email protected]" }]);
expect(parseMailto("mailto:[email protected]?subject=100%").subject).toBe("100%");
});
it("ignores headers it does not understand", () => {
const m = parseMailto("mailto:[email protected]?x-random=1&subject=Hi");
expect(m.subject).toBe("Hi");
expect(m.to).toHaveLength(1);
});
});
+35
View File
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";
import { draftFromMailto } from "@/store/compose";
/**
* mailto: URLs arrive from anywhere — a web page, a document, another app —
* so the body must reach the composer as text, never as markup.
*/
describe("draftFromMailto", () => {
it("fills recipients, subject and body", () => {
const d = draftFromMailto("mailto:[email protected][email protected]&[email protected]&subject=Q3%20plan&body=Hi%20Ann");
expect(d.to).toEqual([{ name: null, email: "[email protected]" }]);
expect(d.cc).toEqual([{ name: null, email: "[email protected]" }]);
expect(d.bcc).toEqual([{ name: null, email: "[email protected]" }]);
expect(d.showCc).toBe(true);
expect(d.showBcc).toBe(true);
expect(d.subject).toBe("Q3 plan");
expect(d.text).toBe("Hi Ann");
});
it("escapes markup in the body and keeps line breaks", () => {
const d = draftFromMailto("mailto:[email protected]?body=%3Cimg%20src%3Dx%20onerror%3Dboom%3E%20%26%20plain%0Asecond%20line");
expect(d.html).not.toContain("<img");
expect(d.html).toContain("&lt;img");
expect(d.html).toContain("&amp;");
expect(d.html).toContain("<br>");
expect(d.text).toContain("<img");
});
it("leaves the body alone when the URL has none", () => {
const d = draftFromMailto("mailto:[email protected]");
expect(d.html).toBeUndefined();
expect(d.text).toBeUndefined();
expect(d.showCc).toBe(false);
});
});