Update dependencies
This commit is contained in:
parent
c0bad165a2
commit
872ca6d576
19 changed files with 2356 additions and 5020 deletions
|
@ -6,7 +6,6 @@ module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"prettier/@typescript-eslint",
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"semi": true,
|
"semi": true
|
||||||
"svelteSortOrder": "scripts-markup-styles"
|
|
||||||
}
|
}
|
||||||
|
|
57
package.json
57
package.json
|
@ -4,42 +4,44 @@
|
||||||
"description": "Lingo is a small browser extension to help you learn languages",
|
"description": "Lingo is a small browser extension to help you learn languages",
|
||||||
"main": "webpack.config.js",
|
"main": "webpack.config.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@asmagin/google-translate-api": "^8.0.2",
|
||||||
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
||||||
"svelte-feather-icons": "^3.2.2",
|
"svelte-feather-icons": "^4.0.0",
|
||||||
"svelte-select": "^3.11.0",
|
"svelte-select": "^4.4.7",
|
||||||
"tippy.js": "^6.2.5"
|
"tippy.js": "^6.2.5",
|
||||||
|
"webextension-polyfill": "^0.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/svelte": "^1.0.8",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.9.1",
|
"@types/webextension-polyfill": "^0.8.3",
|
||||||
"@typescript-eslint/parser": "^3.9.1",
|
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||||
"copy-webpack-plugin": "^6.0.3",
|
"@typescript-eslint/parser": "^5.20.0",
|
||||||
"css-loader": "^4.0.0",
|
"copy-webpack-plugin": "^10.2.4",
|
||||||
"eslint": "^7.7.0",
|
"css-loader": "^6.7.1",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint": "^8.13.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
"html-loader": "^1.1.0",
|
"html-loader": "^3.1.0",
|
||||||
"html-webpack-plugin": "4.3.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"husky": ">=4",
|
"husky": ">=4",
|
||||||
"lint-staged": ">=10",
|
"lint-staged": ">=10",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"prettier-plugin-svelte": "^1.1.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"raw-loader": "^4.0.1",
|
"raw-loader": "^4.0.1",
|
||||||
"sass": "^1.26.10",
|
"sass": "^1.26.10",
|
||||||
"sass-loader": "^9.0.2",
|
"sass-loader": "^12.6.0",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^3.3.1",
|
||||||
"svelte": "^3.24.1",
|
"svelte": "^3.24.1",
|
||||||
"svelte-check": "^1.0.11",
|
"svelte-check": "^2.7.0",
|
||||||
"svelte-loader": "^2.13.6",
|
"svelte-loader": "^3.1.2",
|
||||||
"svelte-preprocess": "^4.0.10",
|
"svelte-preprocess": "^4.0.10",
|
||||||
"ts-loader": "^8.0.1",
|
"ts-loader": "^9.2.8",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.6.3",
|
||||||
"web-ext": "^4.3.0",
|
"web-ext": "^6.8.0",
|
||||||
"webextension-polyfill-ts": "^0.19.0",
|
"webpack": "^5.72.0",
|
||||||
"webpack": "^4.43.0",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-cli": "^3.3.12",
|
"worklet-loader": "^2.0.0",
|
||||||
"worklet-loader": "^1.0.0",
|
"zip-webpack-plugin": "^4.0.1"
|
||||||
"zip-webpack-plugin": "^3.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
|
@ -48,11 +50,6 @@
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"husky": {
|
|
||||||
"hooks": {
|
|
||||||
"pre-commit": "lint-staged"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*/**": [
|
"*/**": [
|
||||||
"eslint --fix ",
|
"eslint --fix ",
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import { browser, Runtime } from "webextension-polyfill-ts";
|
import browser, { type Runtime } from "webextension-polyfill";
|
||||||
import { GTranslateScraper } from "./gtranslate_scraper";
|
import { GTranslate } from "./translate";
|
||||||
import { Flashcards } from "./database";
|
import { Flashcards } from "./database";
|
||||||
import { BackgroundMessenger } from "../background_frontend_commands";
|
import { BackgroundMessenger } from "../background_frontend_commands";
|
||||||
import content_script from "../frontend/content_script/content_script.ts?file";
|
import content_script from "../frontend/content_script/content_script.ts?file";
|
||||||
|
|
||||||
const con = new BackgroundMessenger();
|
const con = new BackgroundMessenger();
|
||||||
|
|
||||||
const scraper = new GTranslateScraper();
|
const scraper = new GTranslate();
|
||||||
con.addMessageListener("translate", async (toTranslate, sender) => {
|
con.addMessageListener("translate", async (toTranslate, sender) => {
|
||||||
return scraper.translate(
|
return scraper.translate(toTranslate, await getCurrentLanguages());
|
||||||
toTranslate,
|
|
||||||
await getCurrentLanguages(),
|
|
||||||
sender!.tab?.id
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
con.addMessageListener("getLanguages", () => scraper.languages);
|
con.addMessageListener("getLanguages", () => scraper.get_languages());
|
||||||
|
|
||||||
const db = new Flashcards();
|
const db = new Flashcards();
|
||||||
window.flashcardDB = db;
|
window.flashcardDB = db;
|
||||||
|
@ -23,7 +19,7 @@ con.addMessageListener("addFlashcard", (c) => db.addFlashcard(c));
|
||||||
con.addMessageListener("removeFlashcard", (c) => db.removeFlashcard(c));
|
con.addMessageListener("removeFlashcard", (c) => db.removeFlashcard(c));
|
||||||
|
|
||||||
const getCurrentLanguages = async () => {
|
const getCurrentLanguages = async () => {
|
||||||
const langs = await scraper.languages;
|
const langs = await scraper.get_languages();
|
||||||
const srcLangCode =
|
const srcLangCode =
|
||||||
(await browser.storage.local.get("srcLang"))["srcLang"] ?? "de";
|
(await browser.storage.local.get("srcLang"))["srcLang"] ?? "de";
|
||||||
const dstLangCode =
|
const dstLangCode =
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { browser, Runtime } from "webextension-polyfill-ts";
|
|
||||||
import { Communicator, commandList, commandFunction } from "../communicator";
|
|
||||||
import { newPromise } from "../utils";
|
|
||||||
|
|
||||||
interface BackgroundCommands extends commandList {
|
|
||||||
translationFinished: TranslationMessage;
|
|
||||||
languageList: LanguageListMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ContentScriptCommands extends commandList {
|
|
||||||
translationRequest: TranslationRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TranslationMessage extends commandFunction {
|
|
||||||
args: Translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LanguageListMessage extends commandFunction {
|
|
||||||
args: Array<Language>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TranslationRequest extends commandFunction {
|
|
||||||
args: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BackgroundMessenger extends Communicator<
|
|
||||||
BackgroundCommands,
|
|
||||||
ContentScriptCommands
|
|
||||||
> {
|
|
||||||
conn: Promise<Runtime.Port>;
|
|
||||||
private resolvConn: (p: Runtime.Port) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
[this.conn, this.resolvConn] = newPromise<Runtime.Port>();
|
|
||||||
|
|
||||||
browser.runtime.onConnect.addListener((p) => {
|
|
||||||
if (p.name != "gtranslate_scraper_conn") return;
|
|
||||||
this.resolvConn(p);
|
|
||||||
p.onDisconnect.addListener(
|
|
||||||
() => ([this.conn, this.resolvConn] = newPromise<Runtime.Port>())
|
|
||||||
);
|
|
||||||
p.onMessage.addListener((m) => this.onMessage(m, p.sender!));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async runCommand<K extends keyof ContentScriptCommands>(
|
|
||||||
command: K,
|
|
||||||
args: ContentScriptCommands[K]["args"]
|
|
||||||
) {
|
|
||||||
const msg = this.getCommandMessage(command, args);
|
|
||||||
(await this.conn).postMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContentScriptMessenger extends Communicator<
|
|
||||||
ContentScriptCommands,
|
|
||||||
BackgroundCommands
|
|
||||||
> {
|
|
||||||
conn: Runtime.Port;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.conn = browser.runtime.connect({
|
|
||||||
name: "gtranslate_scraper_conn",
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} as any); //The declaration file is wrong
|
|
||||||
this.conn.onMessage.addListener((m) => this.onMessage(m));
|
|
||||||
}
|
|
||||||
async runCommand<K extends keyof BackgroundCommands>(
|
|
||||||
command: K,
|
|
||||||
args: BackgroundCommands[K]["args"]
|
|
||||||
) {
|
|
||||||
const msg = this.getCommandMessage(command, args);
|
|
||||||
return this.conn.postMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { ContentScriptMessenger } from "./gtranslate_commands";
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
if (window.location.host != "translate.google.com" || window.hasRun) return;
|
|
||||||
window.hasRun = true;
|
|
||||||
|
|
||||||
const conn = new ContentScriptMessenger();
|
|
||||||
|
|
||||||
const src = <HTMLTextAreaElement>document.querySelector("textarea#source");
|
|
||||||
const result = <HTMLDivElement>(
|
|
||||||
document.querySelector("div.results-container")
|
|
||||||
);
|
|
||||||
|
|
||||||
const [srcLang, dstLang] = Array.from(
|
|
||||||
document.querySelectorAll(".jfk-button-checked")
|
|
||||||
).map((d) => ({
|
|
||||||
code: d.getAttribute("value")!,
|
|
||||||
name: d.textContent!,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const isTranslating = () => result.classList.contains("translating");
|
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
const wasTranslating = mutations.some((m) =>
|
|
||||||
m.oldValue!.split(" ").some((c) => c == "translating")
|
|
||||||
);
|
|
||||||
if (wasTranslating && !isTranslating()) {
|
|
||||||
sendTranslation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe(result, {
|
|
||||||
attributes: true,
|
|
||||||
attributeOldValue: true,
|
|
||||||
attributeFilter: ["class"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const sendTranslation = () => {
|
|
||||||
conn.runCommand("translationFinished", {
|
|
||||||
src: src.value,
|
|
||||||
result: result.innerText.trim(),
|
|
||||||
languages: {
|
|
||||||
srcLang: srcLang,
|
|
||||||
dstLang: dstLang,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
conn.addMessageListener("translate", (toTranslate) => {
|
|
||||||
if (src.value == toTranslate && !isTranslating()) sendTranslation();
|
|
||||||
else src.value = toTranslate;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getLanguages = () => {
|
|
||||||
const langs = document.querySelectorAll(
|
|
||||||
"div.language_list_section:nth-child(3) > .language_list_item_wrapper"
|
|
||||||
);
|
|
||||||
return Array.from(langs).map((l) => {
|
|
||||||
const code = l.classList[1].split("-").pop();
|
|
||||||
const name = l.querySelector(".language_list_item_language_name")!
|
|
||||||
.textContent;
|
|
||||||
return { code: code!, name: name! };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
conn.runCommand("languageList", getLanguages());
|
|
||||||
})();
|
|
|
@ -1,177 +0,0 @@
|
||||||
import { browser, WebRequest } from "webextension-polyfill-ts";
|
|
||||||
import content_script from "./gtranslate_content_script.ts?file";
|
|
||||||
import { BackgroundMessenger } from "./gtranslate_commands";
|
|
||||||
import { newPromise } from "../utils";
|
|
||||||
|
|
||||||
interface TranslationRequest {
|
|
||||||
tabID?: number;
|
|
||||||
resolveFn: (value: Translation) => void;
|
|
||||||
rejectFn: (reason: string) => void;
|
|
||||||
toTranslate: string;
|
|
||||||
lang: LanguagePair;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GTranslateScraper {
|
|
||||||
private queue: Array<TranslationRequest> = [];
|
|
||||||
private current?: TranslationRequest;
|
|
||||||
private iframe = <HTMLIFrameElement>document.createElement("iframe");
|
|
||||||
private current_lang: LanguagePair | undefined;
|
|
||||||
|
|
||||||
languages: Promise<Array<Language>>;
|
|
||||||
msg: BackgroundMessenger;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
let languagesResolve: (value: Array<Language>) => void;
|
|
||||||
[this.languages, languagesResolve] = newPromise<Array<Language>>();
|
|
||||||
|
|
||||||
this.msg = new BackgroundMessenger();
|
|
||||||
this.msg.addMessageListener("languageList", languagesResolve);
|
|
||||||
this.msg.addMessageListener("translationFinished", (trans) =>
|
|
||||||
this.onTranslationRecieved(trans)
|
|
||||||
);
|
|
||||||
|
|
||||||
//This allows us to create the google translate iframe by removing the x-frame-options header
|
|
||||||
browser.webRequest.onHeadersReceived.addListener(
|
|
||||||
allowIFrameAccess,
|
|
||||||
{
|
|
||||||
urls: ["https://translate.google.com/?*"],
|
|
||||||
types: ["sub_frame"],
|
|
||||||
},
|
|
||||||
["blocking", "responseHeaders"]
|
|
||||||
);
|
|
||||||
|
|
||||||
this.ChangeLanguage("de", "en"); //Sample languages so we can get the language list
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ChangeLanguage(
|
|
||||||
srcLangCode: string,
|
|
||||||
dstLangCode: string
|
|
||||||
): Promise<void> {
|
|
||||||
if (!document.body.contains(this.iframe))
|
|
||||||
document.body.appendChild(this.iframe);
|
|
||||||
|
|
||||||
const [connectedPromise, onConnected] = newPromise<void>();
|
|
||||||
|
|
||||||
//Registers a temp content script, because we cannot inject scripts into iframes created on the background html page, because they have no tabId
|
|
||||||
browser.contentScripts
|
|
||||||
.register({
|
|
||||||
matches: ["https://translate.google.com/?*"],
|
|
||||||
allFrames: true,
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: content_script,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
runAt: "document_end",
|
|
||||||
})
|
|
||||||
.then((r) => {
|
|
||||||
this.iframe.addEventListener(
|
|
||||||
"load",
|
|
||||||
() => {
|
|
||||||
r.unregister;
|
|
||||||
this.msg.conn.then(() => onConnected());
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
this.iframe.src = `https://translate.google.com/?op=translate&sl=${srcLangCode}&tl=${dstLangCode}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return connectedPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onTranslationRecieved(trans: Translation) {
|
|
||||||
if (this.current?.toTranslate !== trans.src)
|
|
||||||
//I don't know how to code well so I gotta put in sanity checks like this to make debugging easier
|
|
||||||
console.error(
|
|
||||||
"Current request changed before OnTranslationRecieved was called. This should never happen"
|
|
||||||
);
|
|
||||||
if (this.current) this.current.resolveFn(trans);
|
|
||||||
this.current = undefined;
|
|
||||||
this.processFromQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processFromQueue() {
|
|
||||||
if (this.current) return;
|
|
||||||
this.current = this.queue.pop();
|
|
||||||
if (!this.current) return;
|
|
||||||
|
|
||||||
this.processCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processCurrent() {
|
|
||||||
if (!this.current)
|
|
||||||
throw new Error(
|
|
||||||
"ProcessCurrent called while current translation request is undefined"
|
|
||||||
);
|
|
||||||
const cur = this.current;
|
|
||||||
|
|
||||||
let promise = Promise.resolve();
|
|
||||||
if (this.current_lang != this.current.lang) {
|
|
||||||
this.current_lang = cur.lang;
|
|
||||||
|
|
||||||
promise = this.ChangeLanguage(
|
|
||||||
this.current_lang.srcLang.code,
|
|
||||||
this.current_lang.dstLang.code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await promise;
|
|
||||||
if (cur == this.current) this.msg.runCommand("translate", cur.toTranslate);
|
|
||||||
else
|
|
||||||
throw new Error(
|
|
||||||
"The current request changed while processCurrent was running. This should never happen"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async translate(
|
|
||||||
toTranslate: string,
|
|
||||||
langs: LanguagePair,
|
|
||||||
tabID?: number
|
|
||||||
): Promise<Translation> {
|
|
||||||
toTranslate = toTranslate.trim();
|
|
||||||
if (toTranslate == "")
|
|
||||||
return Promise.resolve({
|
|
||||||
src: "",
|
|
||||||
result: "",
|
|
||||||
languages: langs,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [promise, resolve, reject] = newPromise<Translation>();
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
toTranslate: toTranslate,
|
|
||||||
resolveFn: resolve,
|
|
||||||
rejectFn: reject,
|
|
||||||
tabID: tabID,
|
|
||||||
lang: langs,
|
|
||||||
};
|
|
||||||
//Remove the requests from the same tab
|
|
||||||
if (tabID)
|
|
||||||
this.queue = this.queue.filter((r) => {
|
|
||||||
if (r.tabID !== tabID) {
|
|
||||||
r.rejectFn("Got another request from the same tab");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.push(req);
|
|
||||||
|
|
||||||
if (this.queue.length == 1 && !this.current) {
|
|
||||||
this.current = this.queue.pop();
|
|
||||||
await this.msg.conn;
|
|
||||||
this.processCurrent();
|
|
||||||
}
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowIFrameAccess = (info: WebRequest.OnHeadersReceivedDetailsType) => {
|
|
||||||
//Make sure that only the background page can access this iframe
|
|
||||||
if (info.documentUrl != document.URL) return;
|
|
||||||
|
|
||||||
let headers = info.responseHeaders;
|
|
||||||
headers = headers?.filter((header) => {
|
|
||||||
const h = header.name.toString().toLowerCase();
|
|
||||||
h != "x-frame-options" && h != "frame-options";
|
|
||||||
});
|
|
||||||
return { responseHeaders: headers };
|
|
||||||
};
|
|
42
src/background/translate.ts
Normal file
42
src/background/translate.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { newPromise } from "../utils";
|
||||||
|
import googleTranslateApi from "@asmagin/google-translate-api";
|
||||||
|
import translate from "@asmagin/google-translate-api";
|
||||||
|
|
||||||
|
const languages = googleTranslateApi.languages;
|
||||||
|
|
||||||
|
export abstract class Translator {
|
||||||
|
abstract translate(
|
||||||
|
toTranslate: string,
|
||||||
|
langs: LanguagePair
|
||||||
|
): Promise<Translation>;
|
||||||
|
|
||||||
|
abstract get_languages(): Promise<Language[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GTranslate extends Translator {
|
||||||
|
async translate(
|
||||||
|
toTranslate: string,
|
||||||
|
langs: LanguagePair
|
||||||
|
): Promise<Translation> {
|
||||||
|
const resp = await translate(toTranslate, {
|
||||||
|
from: langs.srcLang.code,
|
||||||
|
to: langs.dstLang.code,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
src: resp.from.text.value,
|
||||||
|
languages: langs,
|
||||||
|
result: resp.text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_languages() {
|
||||||
|
const res: Language[] = Object.keys(languages).map((k) => {
|
||||||
|
const v = (languages as any)[k as any] as string;
|
||||||
|
return {
|
||||||
|
code: k,
|
||||||
|
name: v,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return Promise.resolve(res);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
import { Communicator, commandFunction, commandList } from "./communicator";
|
import {
|
||||||
import { browser } from "webextension-polyfill-ts";
|
Communicator,
|
||||||
|
type commandFunction,
|
||||||
|
type commandList,
|
||||||
|
} from "./communicator";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
interface BackgroundCommands extends commandList {
|
interface BackgroundCommands extends commandList {
|
||||||
setEnabled: setEnabled;
|
setEnabled: setEnabled;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import type { Runtime } from "webextension-polyfill-ts";
|
import type { Runtime } from "webextension-polyfill";
|
||||||
|
|
||||||
export interface commandFunction {
|
export interface commandFunction {
|
||||||
args: any;
|
args: any;
|
||||||
|
@ -29,7 +29,7 @@ export abstract class Communicator<
|
||||||
listener<RecvCommands, keyof RecvCommands>
|
listener<RecvCommands, keyof RecvCommands>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
abstract async runCommand<K extends keyof SendCommands>(
|
abstract runCommand<K extends keyof SendCommands>(
|
||||||
command: K,
|
command: K,
|
||||||
args: SendCommands[K]["args"],
|
args: SendCommands[K]["args"],
|
||||||
...rest: unknown[]
|
...rest: unknown[]
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
card
|
card
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
import type { Flashcards } from "../../background/database";
|
import type { Flashcards } from "../../background/database";
|
||||||
export let cardID: IDBValidKey;
|
export let cardID: IDBValidKey;
|
||||||
|
|
||||||
const flashcardDB = getContext<Flashcards>("flashcardDB");
|
const flashcardDB = getContext<Promise<Flashcards>>("flashcardDB");
|
||||||
$: cardPromise = flashcardDB.getCard(cardID);
|
$: cardPromise = flashcardDB.then((db) => db.getCard(cardID));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 0.1em;
|
margin: 0.1em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: calc(100%-1vw);
|
width: calc(100% - 1vw);
|
||||||
background-color: var(--background-color-3);
|
background-color: var(--background-color-3);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from "webextension-polyfill-ts";
|
import browser from "webextension-polyfill";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
import VirtualList from "@sveltejs/svelte-virtual-list";
|
import VirtualList from "@sveltejs/svelte-virtual-list/VirtualList.svelte";
|
||||||
import Flashcard from "./Flashcard.svelte";
|
import Flashcard from "./Flashcard.svelte";
|
||||||
/*
|
/*
|
||||||
const onCardChange = (card: Flashcard, change: "added" | "removed") => {
|
const onCardChange = (card: Flashcard, change: "added" | "removed") => {
|
||||||
|
@ -19,14 +19,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let cards: IDBValidKey[] = [];
|
let cards: IDBValidKey[] = [];
|
||||||
|
const flashcardDBPromise = browser.runtime
|
||||||
|
.getBackgroundPage()
|
||||||
|
.then((bp) => bp.flashcardDB);
|
||||||
|
setContext("flashcardDB", flashcardDBPromise);
|
||||||
(async () => {
|
(async () => {
|
||||||
const backgroundPage = await browser.runtime.getBackgroundPage();
|
const flashcardDB = await flashcardDBPromise;
|
||||||
const flashcardDB = backgroundPage.flashcardDB;
|
|
||||||
cards = await flashcardDB.getAllKeys();
|
cards = await flashcardDB.getAllKeys();
|
||||||
//flashcardDB.addChangeCallback(onCardChange);
|
//flashcardDB.addChangeCallback(onCardChange);
|
||||||
//window.addEventListener("beforeunload", () => flashcardDB.removeChangeCallback(onCardChange));
|
//window.addEventListener("beforeunload", () => flashcardDB.removeChangeCallback(onCardChange));
|
||||||
|
|
||||||
setContext("flashcardDB", flashcardDB);
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Select from "svelte-select";
|
import Select from "svelte-select/src/Select.svelte";
|
||||||
export let langs: Array<Language>;
|
export let langs: Array<Language>;
|
||||||
export let selectedValue: Language;
|
export let selectedValue: Language;
|
||||||
|
$: console.log(selectedValue);
|
||||||
|
|
||||||
const getLabel = (o: Language) => o.name;
|
const getLabel = (o: Language) => o.name;
|
||||||
const itemFilter = (label: string, filterText: string) =>
|
|
||||||
label.toLowerCase().startsWith(filterText.toLowerCase());
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="select-styling">
|
<div class="select-styling">
|
||||||
|
@ -14,9 +13,9 @@
|
||||||
getOptionLabel={getLabel}
|
getOptionLabel={getLabel}
|
||||||
getSelectionLabel={getLabel}
|
getSelectionLabel={getLabel}
|
||||||
optionIdentifier="code"
|
optionIdentifier="code"
|
||||||
{itemFilter}
|
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
bind:selectedValue />
|
bind:value={selectedValue}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="container">
|
<div id="container">
|
||||||
|
|
||||||
<label class:enabled>
|
<label class:enabled>
|
||||||
<input type="checkbox" bind:checked={enabled} />
|
<input type="checkbox" bind:checked={enabled} />
|
||||||
{enabled ? 'Disable Extension' : 'Enable Extension'}
|
{enabled ? "Disable Extension" : "Enable Extension"}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if selected}
|
{#if selected}
|
||||||
|
|
1
src/global.d.ts
vendored
Normal file
1
src/global.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="svelte" />
|
|
@ -6,6 +6,5 @@
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"strict": true
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "src/node_modules"],
|
"include": ["/**/*"]
|
||||||
"exclude": ["node_modules/*"]
|
|
||||||
}
|
}
|
|
@ -61,6 +61,13 @@ let options = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4
|
||||||
|
test: /node_modules\/svelte\/.*\.mjs$/,
|
||||||
|
resolve: {
|
||||||
|
fullySpecified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in a new issue