diff options
| author | Bobby <[email protected]> | 2025-09-23 01:46:34 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-09-23 01:46:34 +0530 |
| commit | 663c22f60de5822978f5a2e187e31c9b4cd58ba7 (patch) | |
| tree | 63b5f80895ca35f16f477ee3706cbc9a3001539d | |
| parent | 3d605293b5e33e734a7d6c6d749ab2f72b020628 (diff) | |
| download | thunderbird-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.json | 245 | ||||
| -rw-r--r-- | src/background.ts | 20 | ||||
| -rw-r--r-- | src/popup/popup.ts | 88 | ||||
| -rw-r--r-- | src/types.ts | 21 |
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; +} |
