aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-09-23 01:46:34 +0530
committerBobby <[email protected]>2025-09-23 01:46:34 +0530
commit663c22f60de5822978f5a2e187e31c9b4cd58ba7 (patch)
tree63b5f80895ca35f16f477ee3706cbc9a3001539d
parent3d605293b5e33e734a7d6c6d749ab2f72b020628 (diff)
downloadthunderbird-ai-compose-663c22f60de5822978f5a2e187e31c9b4cd58ba7.tar.xz
thunderbird-ai-compose-663c22f60de5822978f5a2e187e31c9b4cd58ba7.zip
move types to global; add isHTML bool and a sample text insert
-rw-r--r--package-lock.json245
-rw-r--r--src/background.ts20
-rw-r--r--src/popup/popup.ts88
-rw-r--r--src/types.ts21
4 files changed, 115 insertions, 259 deletions
diff --git a/package-lock.json b/package-lock.json
index f22cc3f..5ce8b52 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,21 @@
{
"name": "thunderbird-ai-compose",
- "version": "1.0.0",
+ "version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "thunderbird-ai-compose",
- "version": "1.0.0",
+ "version": "0.1.0",
"license": "MIT",
"dependencies": {
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@types/firefox-webext-browser": "^120.0.4",
+ "@types/webextension-polyfill": "^0.12.3",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
- "cpy-cli": "^6.0.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
@@ -1086,19 +1086,6 @@
"win32"
]
},
- "node_modules/@sindresorhus/merge-streams": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
- "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1120,6 +1107,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/webextension-polyfill": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.12.3.tgz",
+ "integrity": "sha512-F58aDVSeN/MjUGazXo/cPsmR76EvqQhQ1v4x23hFjUX0cfAJYE+JBWwiOGW36/VJGGxoH74sVlRIF3z7SJCKyg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
@@ -1615,64 +1609,6 @@
"node": "^14.18.0 || >=16.10.0"
}
},
- "node_modules/copy-file": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/copy-file/-/copy-file-11.1.0.tgz",
- "integrity": "sha512-X8XDzyvYaA6msMyAM575CUoygY5b44QzLcGRKsK3MFmXcOvQa518dNPLsKYwkYsn72g3EiW+LE0ytd/FlqWmyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.11",
- "p-event": "^6.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cpy": {
- "version": "12.0.1",
- "resolved": "https://registry.npmjs.org/cpy/-/cpy-12.0.1.tgz",
- "integrity": "sha512-hCnNla4AB27lUncMuO7KFjge0u0C5R74iKMBOajKOMB9ONGXcIek314ZTpxg16BuNYRTjPz7UW3tPXgJVLxUww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "copy-file": "^11.1.0",
- "globby": "^14.1.0",
- "junk": "^4.0.1",
- "micromatch": "^4.0.8",
- "p-filter": "^4.1.0",
- "p-map": "^7.0.3"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cpy-cli": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-6.0.0.tgz",
- "integrity": "sha512-q7GUqTDnRymCbScJ4Ph1IUM86wWdKG8JbgrvKLgvvehH4wrbRcVN+jRwOTlxJdwm7ykdXMKSp6IESksFeHa0eA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cpy": "^12.0.0",
- "meow": "^13.2.0"
- },
- "bin": {
- "cpy": "cli.js"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2233,44 +2169,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/globby": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz",
- "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@sindresorhus/merge-streams": "^2.1.0",
- "fast-glob": "^3.3.3",
- "ignore": "^7.0.3",
- "path-type": "^6.0.0",
- "slash": "^5.1.0",
- "unicorn-magic": "^0.3.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globby/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2435,19 +2333,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/junk": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz",
- "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -2549,19 +2434,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/meow": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
- "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2676,38 +2548,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/p-event": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz",
- "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-timeout": "^6.1.2"
- },
- "engines": {
- "node": ">=16.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-filter": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz",
- "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-map": "^7.0.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -2740,32 +2580,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-map": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz",
- "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-timeout": {
- "version": "6.1.4",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz",
- "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -2823,19 +2637,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/path-type": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz",
- "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -3148,19 +2949,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/slash": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
- "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/source-map": {
"version": "0.8.0-beta.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
@@ -3572,19 +3360,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/unicorn-magic": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
- "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
diff --git a/src/background.ts b/src/background.ts
index 997dd68..48d51a7 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -1,21 +1,4 @@
-type Identity = {
- id?: string;
- email?: string;
- name?: string;
-};
-
-type ComposeContext = {
- account: Identity;
- compose: {
- subject?: string;
- to?: string[];
- cc?: string[];
- bcc?: string[];
- bodyPlain?: string;
- bodyHTML?: string;
- identityId?: string;
- };
-};
+import { ComposeContext } from "./types";
async function getComposeContext(): Promise<ComposeContext> {
const data: ComposeContext = { account: {}, compose: {} };
@@ -34,6 +17,7 @@ async function getComposeContext(): Promise<ComposeContext> {
data.compose.identityId = details?.identityId ?? undefined;
data.compose.bodyPlain = details?.plainTextBody ?? "";
data.compose.bodyHTML = details?.body ?? "";
+ data.compose.isHtml = Boolean(details?.body && details?.body.trim());
const accounts: any[] = (browser as any).accounts?.list
? await (browser as any).accounts.list()
diff --git a/src/popup/popup.ts b/src/popup/popup.ts
index 78866e9..dc28bdc 100644
--- a/src/popup/popup.ts
+++ b/src/popup/popup.ts
@@ -1,18 +1,94 @@
-type Payload = {
+import { ComposeContext } from "../types";
+
+interface Payload {
prompt: string;
- context: any;
-};
+ context: ComposeContext;
+}
const promptEl: HTMLTextAreaElement = document.getElementById("prompt") as HTMLTextAreaElement;
const sendBtn: HTMLButtonElement = document.getElementById("send") as HTMLButtonElement;
const outputEl: HTMLElement = document.getElementById("output") as HTMLElement;
-sendBtn.addEventListener("click", async (): Promise<void> => {
- const ctx: any = await browser.runtime.sendMessage({ type: "getComposeContext" });
+async function getActiveComposeTabId(): Promise<number | undefined> {
+ const tabs: browser.tabs.Tab[] = (await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ })) as browser.tabs.Tab[];
+ return tabs[0]?.id;
+}
+
+function insertAtReply(body: string, aiText: string, isHtml: boolean): string {
+ if (isHtml) {
+ const parser: DOMParser = new DOMParser();
+ const doc: Document = parser.parseFromString(body, "text/html");
+ const paragraphs: NodeListOf<HTMLParagraphElement> = doc.querySelectorAll("body > p");
+
+ let target: HTMLParagraphElement | null = null;
+ for (const p of Array.from(paragraphs)) {
+ if (p.textContent && p.textContent.trim().length > 0) {
+ target = p;
+ break;
+ }
+ }
+
+ if (target) {
+ target.insertAdjacentHTML("afterend", aiText);
+ return doc.body.innerHTML;
+ }
+
+ const citeDiv: HTMLElement | null = doc.querySelector("div.moz-cite-prefix");
+ if (citeDiv) {
+ citeDiv.insertAdjacentHTML("beforebegin", aiText);
+ return doc.body.innerHTML;
+ }
+
+ doc.body.insertAdjacentHTML("afterbegin", aiText);
+ return doc.body.innerHTML;
+ } else {
+ const replyEnd: number = body.indexOf("\nOn ");
+ if (replyEnd !== -1) {
+ return body.slice(0, replyEnd).trimEnd() + "\n" + aiText + "\n" + body.slice(replyEnd);
+ }
+ return aiText + "\n\n" + body;
+ }
+}
+
+async function handleInsert(): Promise<void> {
+ const ctx: ComposeContext = (await browser.runtime.sendMessage({
+ type: "getComposeContext",
+ })) as ComposeContext;
+
const payload: Payload = {
prompt: promptEl.value,
context: ctx,
};
+
+ const tabId: number | undefined = await getActiveComposeTabId();
+ if (tabId === undefined) {
+ outputEl.textContent = "No active compose tab.";
+ return;
+ }
+
+ const isHtml: boolean =
+ ctx.compose.isHtml ?? Boolean(ctx.compose.bodyHTML && ctx.compose.bodyHTML.trim());
+ const aiResult: string = isHtml
+ ? "<p>This response will be inserted by AI</p>"
+ : "This response will be inserted by AI";
+
+ const replyPrefix: string | undefined =
+ ctx.compose.bodyPlain?.split("\n\n")[0].trim() || undefined;
+ const oldBody: string = isHtml ? (ctx.compose.bodyHTML ?? "") : (ctx.compose.bodyPlain ?? "");
+
+ const newBody: string = insertAtReply(oldBody, aiResult, isHtml);
+
+ await (browser as any).compose.setComposeDetails(tabId, { body: newBody });
+
outputEl.textContent = JSON.stringify(payload, null, 2);
- alert(JSON.stringify(payload, null, 2));
+}
+
+sendBtn.addEventListener("click", (): void => {
+ handleInsert().catch((err: unknown): void => {
+ console.error("Popup insert failed:", err);
+ outputEl.textContent = "Error inserting text. See console.";
+ });
});
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..2f6c00a
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,21 @@
+export interface Identity {
+ id?: string;
+ email?: string;
+ name?: string;
+}
+
+export interface ComposeDetails {
+ subject?: string;
+ to?: string[];
+ cc?: string[];
+ bcc?: string[];
+ bodyPlain?: string;
+ bodyHTML?: string;
+ identityId?: string;
+ isHtml?: boolean;
+}
+
+export interface ComposeContext {
+ account: Identity;
+ compose: ComposeDetails;
+}