diff --git a/src/background/background.ts b/src/background/background.ts index 78bc913..4eb67b0 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,9 +1,11 @@ import { browser, Runtime, Tabs } from "webextension-polyfill-ts"; import { GTranslateScraper } from "./gtranslate_scraper"; import { Flashcards } from "./database"; +import { Communicator, command, commandKinds } from "../communication"; const scraper = new GTranslateScraper(); const db = new Flashcards(); +let com = new Communicator(); console.log(db); const getTab = async (s: Runtime.MessageSender): Promise => { @@ -15,53 +17,41 @@ const getTab = async (s: Runtime.MessageSender): Promise => { return tabs[0]; }; -const isEnabled = async (tabID: number): Promise => - (await browser.sessions.getTabValue(tabID, "lingoEnabled")) || false; -const setEnabled = async (tabID: number, value: boolean) => +const isEnabledSession = async (tabID: number): Promise => + (await browser.sessions.getTabValue(tabID, "lingoEnabled")) ?? false; + +const setEnabledSession = async (tabID: number, value: boolean) => await browser.sessions.setTabValue(tabID, "lingoEnabled", value); const injectScript = async (tabID: number, enabled?: boolean) => { await browser.tabs.executeScript(tabID, { file: "/content_script.bundle.js", }); - if (!enabled) enabled = await isEnabled(tabID); + if (!enabled) enabled = await isEnabledSession(tabID); browser.tabs.sendMessage(tabID, { - commandKind: commands.setEnabled, + commandKind: commandKinds.setEnabled, value: enabled, }); }; -const setEnabledCommand = async (v: boolean, s: Runtime.MessageSender) => { +com.setEnabledCallback = async (v: boolean, s: Runtime.MessageSender) => { const tab = await getTab(s); injectScript(tab.id); - await setEnabled(tab.id, v); -}; -const getEnabledCommand = async ( - s: Runtime.MessageSender -): Promise => { - const tab = await getTab(s); - return isEnabled(tab.id); + await setEnabledSession(tab.id, v); }; -browser.runtime.onMessage.addListener( - (c: command, s: Runtime.MessageSender): Promise => { - switch (c.commandKind) { - case commands.setEnabled: - return setEnabledCommand(c.value, s); - case commands.getEnabled: - return getEnabledCommand(s); - case commands.translate: - return scraper.translate(c.toTranslate); - case commands.addFlashcard: - return db.addTranslation(c.translation); - } - } -); +com.getEnabledCallback = async (s: Runtime.MessageSender): Promise => { + const tab = await getTab(s); + return isEnabledSession(tab.id); +}; + +com.translateCallback = (toTranslate) => scraper.translate(toTranslate); +com.addFlashcardCallback = (card) => db.addTranslation(card); browser.tabs.onUpdated.addListener(async (tabID, changeInfo) => { if (changeInfo.status == "complete") { - if (await isEnabled(tabID)) { + if (await isEnabledSession(tabID)) { injectScript(tabID); } } diff --git a/src/background/database.ts b/src/background/database.ts index 909e0ce..3b76b20 100644 --- a/src/background/database.ts +++ b/src/background/database.ts @@ -26,12 +26,17 @@ export class Flashcards { }; } - addTranslation(t: Translation): Promise { - let card: flashcard = { - ...t, - dateAdded: new Date(), - exported: false, - }; + addTranslation(t: Translation | flashcard): Promise { + let card: flashcard; + if ("dateAdded" in t && "exported" in t) card = t; + else { + card = { + ...t, + dateAdded: new Date(), + exported: false, + }; + } + let req = this.db .transaction(["flashcards"], "readwrite") .objectStore("flashcards") diff --git a/src/communication.ts b/src/communication.ts new file mode 100644 index 0000000..97e67e7 --- /dev/null +++ b/src/communication.ts @@ -0,0 +1,76 @@ +//There has to be a better way to do this while remaining type safe... +import { browser, Runtime } from "webextension-polyfill-ts"; +import { flashcard } from "./background/database"; + +export class Communicator { + setEnabledCallback: (value: boolean, sender: Runtime.MessageSender) => void; + getEnabledCallback: (sender: Runtime.MessageSender) => Promise; + translateCallback: ( + toTranslate: string, + sender: Runtime.MessageSender + ) => Promise; + + addFlashcardCallback: ( + value: Translation | flashcard, + sender: Runtime.MessageSender + ) => Promise; + + constructor() { + browser.runtime.onMessage.addListener( + (c: command, s: Runtime.MessageSender) => { + switch (c.commandKind) { + case commandKinds.setEnabled: + return this.setEnabledCallback(c.value, s); + case commandKinds.getEnabled: + return this.getEnabledCallback(s); + case commandKinds.translate: + return this.translateCallback(c.toTranslate, s); + case commandKinds.addFlashcard: + return this.addFlashcardCallback(c.card, s); + } + } + ); + } + + static setEnabled = (v: boolean) => + sendMessage({ commandKind: commandKinds.setEnabled, value: v }); + static getEnabled = (): Promise => + sendMessage({ commandKind: commandKinds.getEnabled }); + static translate = (toTranslate: string): Promise => + sendMessage({ + commandKind: commandKinds.translate, + toTranslate: toTranslate, + }); + static addFlashcard = (card: flashcard | Translation) => { + sendMessage({ commandKind: commandKinds.addFlashcard, card: card }); + }; +} + +const sendMessage = (m: command) => browser.runtime.sendMessage(m); + +export const enum commandKinds { + setEnabled = "setEnabled", + getEnabled = "getEnabled", + translate = "translate", + addFlashcard = "addFlashcard", +} +interface setEnabled { + commandKind: commandKinds.setEnabled; + value: boolean; +} + +interface getEnabled { + commandKind: commandKinds.getEnabled; +} + +interface translate { + commandKind: commandKinds.translate; + toTranslate: string; +} + +interface addFlashcard { + commandKind: commandKinds.addFlashcard; + card: Translation | flashcard; +} + +export type command = setEnabled | getEnabled | translate | addFlashcard; diff --git a/src/content_script/content_script.ts b/src/frontend/content_script/content_script.ts similarity index 85% rename from src/content_script/content_script.ts rename to src/frontend/content_script/content_script.ts index c505dc9..2ce7bfa 100644 --- a/src/content_script/content_script.ts +++ b/src/frontend/content_script/content_script.ts @@ -1,5 +1,5 @@ -import { browser } from "webextension-polyfill-ts"; import "./custom_elements"; +import Communicator from "../../communication"; import tippy from "tippy.js"; import "tippy.js/dist/tippy.css"; import "./tippy.scss"; @@ -14,13 +14,14 @@ declare global { if (window.hasRun) return; window.hasRun = true; - browser.runtime.onMessage.addListener((c: command) => { - switch (c.commandKind) { - case commands.setEnabled: - setEnabled(c.value); - break; - } - }); + let con = new Communicator(); + con.setEnabledCallback = (v: boolean) => { + document.removeEventListener("selectionchange", onSelectionChange); //Always remove it avoid duplicate event listeners + if (v) { + onSelectionChange(); // Call it since selection may have changed since it was enabled + document.addEventListener("selectionchange", onSelectionChange); + } else tippyInstance.hide(); + }; const tippyInstance = setupTippy(); @@ -32,14 +33,6 @@ declare global { timeoutID = window.setTimeout(tippyInstance.show, 500); }; - function setEnabled(v: boolean) { - document.removeEventListener("selectionchange", onSelectionChange); //Always remove it avoid duplicate event listeners - if (v) { - onSelectionChange(); // Call it since selection may have changed since it was enabled - document.addEventListener("selectionchange", onSelectionChange); - } else tippyInstance.hide(); - } - function setupTippy() { const translateElement = document.createElement("lingo-translate"); return tippy(document.body, { diff --git a/src/content_script/custom_elements.ts b/src/frontend/content_script/custom_elements.ts similarity index 86% rename from src/content_script/custom_elements.ts rename to src/frontend/content_script/custom_elements.ts index 8949ada..44dbfcc 100644 --- a/src/content_script/custom_elements.ts +++ b/src/frontend/content_script/custom_elements.ts @@ -1,4 +1,4 @@ -import { browser } from "webextension-polyfill-ts"; +import { Communicator } from "../../communication"; import contents from "./spinner.scss?raw"; class LingoTranslate extends HTMLElement { @@ -11,10 +11,7 @@ class LingoTranslate extends HTMLElement { this.shadowRoot.innerHTML = ""; const toTranslate = this.getAttribute("translate"); if (toTranslate) { - const translation = browser.runtime.sendMessage({ - commandKind: commands.translate, - toTranslate: toTranslate, - }); + const translation = Communicator.translate(toTranslate); translation.then((t: Translation) => { if (t.src == this.getAttribute("translate")) { this.shadowRoot.innerHTML = ""; @@ -66,12 +63,9 @@ class LingoTranslation extends HTMLElement { this.srcElm = document.createElement("span"); this.addToCollection = document.createElement("button"); this.addToCollection.addEventListener("click", () => - browser.runtime.sendMessage({ - commandKind: commands.addFlashcard, - translation: { - src: this.getAttribute("src"), - translation: this.getAttribute("translation"), - }, + Communicator.addFlashcard({ + src: this.getAttribute("src"), + result: this.getAttribute("translation"), }) ); diff --git a/src/content_script/spinner.scss b/src/frontend/content_script/spinner.scss similarity index 100% rename from src/content_script/spinner.scss rename to src/frontend/content_script/spinner.scss diff --git a/src/content_script/tippy.scss b/src/frontend/content_script/tippy.scss similarity index 100% rename from src/content_script/tippy.scss rename to src/frontend/content_script/tippy.scss diff --git a/src/popup/popup.html b/src/frontend/popup/popup.html similarity index 100% rename from src/popup/popup.html rename to src/frontend/popup/popup.html diff --git a/src/popup/popup.scss b/src/frontend/popup/popup.scss similarity index 100% rename from src/popup/popup.scss rename to src/frontend/popup/popup.scss diff --git a/src/frontend/popup/popup.ts b/src/frontend/popup/popup.ts new file mode 100644 index 0000000..51cdaec --- /dev/null +++ b/src/frontend/popup/popup.ts @@ -0,0 +1,49 @@ +import { Communicator } from "../../communication"; +import "./popup.scss"; + +const con = new Communicator(); + +class ExtensionToggle extends HTMLButtonElement { + isON: boolean = false; + textContent: string = "OFF"; + + public set on(v: boolean) { + if (this.isON == v) { + return; + } + this.isON = v; + this.render(); + } + public get on(): boolean { + return this.isON; + } + + public render() { + this.textContent = this.on ? "ON" : "OFF"; + } + + constructor() { + super(); + this.addEventListener("click", () => { + this.on = !this.on; + + const toggleEvent = new CustomEvent("extensionToggled", { + detail: this.on, + }); + this.dispatchEvent(toggleEvent); + }); + } +} +customElements.define("extension-toggle", ExtensionToggle, { + extends: "button", +}); + +const toggle = ( + document.querySelector("button[is=extension-toggle]") +); +toggle.addEventListener("extensionToggled", (e: CustomEvent) => + Communicator.setEnabled(e.detail) +); + +con.setEnabledCallback = (v) => (toggle.on = v); +Communicator.getEnabled().then((v) => (toggle.on = v)); diff --git a/src/popup/popup.ts b/src/popup/popup.ts deleted file mode 100644 index 019bdfb..0000000 --- a/src/popup/popup.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { browser } from "webextension-polyfill-ts"; -import "./popup.scss"; -class ExtensionToggle extends HTMLButtonElement { - isON: boolean = false; - textContent: string = "OFF"; - - public set on(v: boolean) { - if (this.isON == v) { - return; - } - this.isON = v; - this.render(); - const toggleEvent = new CustomEvent("extensionToggled", { - detail: this.on, - }); - this.dispatchEvent(toggleEvent); - } - public get on(): boolean { - return this.isON; - } - - public render() { - this.textContent = this.on ? "ON" : "OFF"; - } - - constructor() { - super(); - this.addEventListener("click", () => (this.on = !this.on)); - } -} -customElements.define("extension-toggle", ExtensionToggle, { - extends: "button", -}); - -const toggle = ( - document.querySelector("button[is=extension-toggle]") -); -toggle.addEventListener("extensionToggled", async (e: CustomEvent) => { - browser.runtime.sendMessage({ - commandKind: commands.setEnabled, - value: e.detail, - }); -}); - -browser.runtime.onMessage.addListener((c: command) => { - switch (c.commandKind) { - case commands.setEnabled: - toggle.on = c.value; - break; - - default: - console.log("Unimplemented command"); - break; - } -}); - -browser.runtime - .sendMessage({ commandKind: commands.getEnabled }) - .then((r) => (toggle.on = r)); diff --git a/src/types.d.ts b/src/types.d.ts index 6f9e0dd..46b749a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -3,33 +3,6 @@ declare module "*?raw" { export = contents; } -declare const enum commands { - setEnabled = "setEnabled", - getEnabled = "getEnabled", - translate = "translate", - addFlashcard = "addFlashcard", -} -interface setEnabled { - commandKind: commands.setEnabled; - value: boolean; -} - -interface getEnabled { - commandKind: commands.getEnabled; -} - -interface translate { - commandKind: commands.translate; - toTranslate: string; -} - -interface addFlashcard { - commandKind: commands.addFlashcard; - translation: Translation; -} - -type command = setEnabled | getEnabled | translate | addFlashcard; - type TranslationRequest = string; interface Translation { src: string; diff --git a/webpack.config.js b/webpack.config.js index 1e844ea..80854f4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,10 +9,11 @@ let mode = process.env.NODE_ENV || "development"; let options = { entry: { - popup: path.join(__dirname, "src", "popup", "popup.ts"), + popup: path.join(__dirname, "src", "frontend", "popup", "popup.ts"), content_script: path.join( __dirname, "src", + "frontend", "content_script", "content_script.ts" ), @@ -73,7 +74,7 @@ let options = { patterns: [{ from: path.resolve(__dirname, "src", "manifest.json") }], }), new HtmlWebpackPlugin({ - template: path.join(__dirname, "src", "popup", "popup.html"), + template: path.join(__dirname, "src", "frontend", "popup", "popup.html"), filename: "popup.html", chunks: ["popup"], }),