mirror of
https://github.com/ItsVipra/ProToots
synced 2025-02-16 12:38:23 +00:00
Merge pull request #32 from ItsVipra/support-for-javascript-modules
Add `esbuild` for JavaScript Bundling
This commit is contained in:
commit
8be9fde3d5
13 changed files with 1794 additions and 213 deletions
0
.firefox-profile/.gitkeep
Normal file
0
.firefox-profile/.gitkeep
Normal file
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -150,3 +150,4 @@ web-ext-artifacts
|
|||
TODO
|
||||
edgecases
|
||||
notes
|
||||
.firefox-profile
|
||||
|
|
|
@ -70,6 +70,7 @@ Alternatively you can download an unsigned version from the [releases page](http
|
|||
## Developer setup
|
||||
|
||||
- Clone the repository
|
||||
- [Install web-ext](https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/#installation-section) and [set it up](https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/#using-web-ext-section)
|
||||
- Install the required dependencies using `npm install`
|
||||
- Start the development workflow with `npm start`
|
||||
- Mess around with with [protoots.js](/src/content_scripts/protoots.js)
|
||||
- Trans rights!
|
||||
|
|
1506
package-lock.json
generated
1506
package-lock.json
generated
File diff suppressed because it is too large
Load diff
17
package.json
17
package.json
|
@ -1,9 +1,18 @@
|
|||
{
|
||||
"name": "protoots",
|
||||
"scripts": {
|
||||
"format": "prettier --write --ignore-path .gitignore ."
|
||||
"build:scripts": "node scripts/build.mjs",
|
||||
"build:webext": "web-ext build --overwrite-dest",
|
||||
"start": "run-p -l -r watch:**",
|
||||
"watch:scripts": "node scripts/watch.mjs",
|
||||
"watch:webext": "web-ext run --keep-profile-changes --profile-create-if-missing --firefox-profile=.firefox-profile/",
|
||||
"format": "prettier --write --ignore-path .gitignore .",
|
||||
"package": "run-s -l build:**"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sprout2000/esbuild-copy-plugin": "1.1.8",
|
||||
"esbuild": "0.17.19",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.8",
|
||||
"web-ext": "^7.6.2"
|
||||
},
|
||||
|
@ -12,5 +21,11 @@
|
|||
"printWidth": 100,
|
||||
"useTabs": true,
|
||||
"trailingComma": "all"
|
||||
},
|
||||
"webExt": {
|
||||
"sourceDir": "dist/"
|
||||
},
|
||||
"dependencies": {
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
}
|
||||
}
|
||||
|
|
7
scripts/build.mjs
Normal file
7
scripts/build.mjs
Normal file
|
@ -0,0 +1,7 @@
|
|||
import * as esbuild from "esbuild";
|
||||
import { defaultBuildOptions } from "./shared.mjs";
|
||||
|
||||
await esbuild.build({
|
||||
...defaultBuildOptions,
|
||||
minify: true,
|
||||
});
|
47
scripts/shared.mjs
Normal file
47
scripts/shared.mjs
Normal file
|
@ -0,0 +1,47 @@
|
|||
import copyPluginPkg from "@sprout2000/esbuild-copy-plugin";
|
||||
import path from "path";
|
||||
const { copyPlugin } = copyPluginPkg; // js and your fucking mess of imports, sigh.
|
||||
|
||||
/**
|
||||
* This array contains all files that we want to handle with esbuild.
|
||||
* For now, this is limited to our scripts, but it can be extended to more files in the future if needed.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const files = [
|
||||
path.join("src", "content_scripts", "protoots.js"),
|
||||
path.join("src", "options", "options.js"),
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
*/
|
||||
export const defaultBuildOptions = {
|
||||
entryPoints: files,
|
||||
|
||||
// Use bundling. Especially useful because web extensions do not support it by default for some reason.
|
||||
bundle: true,
|
||||
|
||||
// Settings for the correct esbuild output.
|
||||
outbase: "src",
|
||||
outdir: "dist",
|
||||
|
||||
// Because we modify the files, sourcemaps are essential for us.
|
||||
sourcemap: "inline",
|
||||
|
||||
// self-explanatory
|
||||
platform: "browser",
|
||||
logLevel: "info",
|
||||
|
||||
// Copy all files from src/ except our build files (they would be overwritten) to dist/.
|
||||
plugins: [
|
||||
copyPlugin({
|
||||
src: "src",
|
||||
dest: "dist",
|
||||
recursive: true,
|
||||
|
||||
// Return true if the file should be copied and false otherwise.
|
||||
filter: (src) => !src.endsWith(".js"),
|
||||
}),
|
||||
],
|
||||
};
|
4
scripts/watch.mjs
Normal file
4
scripts/watch.mjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
import * as esbuild from "esbuild";
|
||||
import { defaultBuildOptions } from "./shared.mjs";
|
||||
let ctx = await esbuild.context(defaultBuildOptions);
|
||||
await ctx.watch();
|
|
@ -6,39 +6,16 @@
|
|||
// obligatory crime. because be gay, do crime.
|
||||
// 8======D
|
||||
|
||||
import { fetchPronouns } from "../libs/fetchPronouns";
|
||||
import { getLogging, isLogging } from "../libs/logging";
|
||||
import { error, warn, log, info, debug } from "../libs/logging";
|
||||
|
||||
// const max_age = 8.64e7
|
||||
const max_age = 24 * 60 * 60 * 1000; //time after which cached pronouns should be checked again: 24h
|
||||
const host_name = location.host;
|
||||
|
||||
//before anything else, check whether we're on a Mastodon page
|
||||
checkSite();
|
||||
let logging;
|
||||
|
||||
/** @param {any[]} arguments */
|
||||
function error(...arguments) {
|
||||
if (logging) console.error(...arguments);
|
||||
}
|
||||
|
||||
/** @param {any[]} arguments */
|
||||
function warn(...arguments) {
|
||||
if (logging) console.warn(...arguments);
|
||||
}
|
||||
|
||||
/** @param {any[]} arguments */
|
||||
function log(...arguments) {
|
||||
if (logging) console.log(...arguments);
|
||||
}
|
||||
|
||||
/** @param {any[]} arguments */
|
||||
function info(...arguments) {
|
||||
if (logging) console.info(...arguments);
|
||||
}
|
||||
|
||||
/** @param {any[]} arguments */
|
||||
function debug(...arguments) {
|
||||
if (logging) console.debug(...arguments);
|
||||
}
|
||||
|
||||
// log("hey vippy, du bist cute <3")
|
||||
|
||||
/**
|
||||
|
@ -46,14 +23,7 @@ function debug(...arguments) {
|
|||
* If so creates an 'readystatechange' EventListener, with callback to main()
|
||||
*/
|
||||
async function checkSite() {
|
||||
try {
|
||||
let { logging: optionValue } = await browser.storage.sync.get("logging");
|
||||
logging = optionValue;
|
||||
} catch {
|
||||
// Enable the logging automatically if we cannot determine the user preference.
|
||||
logging = true;
|
||||
}
|
||||
|
||||
getLogging();
|
||||
let requestDest = location.protocol + "//" + host_name + "/api/v1/instance";
|
||||
let response = await fetch(requestDest);
|
||||
|
||||
|
@ -192,154 +162,6 @@ function addtoTootObserver(ActionElement) {
|
|||
tootObserver.observe(ActionElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches pronouns associated with account name.
|
||||
* If cache misses status is fetched from the instance.
|
||||
*
|
||||
* @param {string | undefined} statusID ID of the status being requested, in case cache misses.
|
||||
* @param {string} account_name The account name, used for caching. Should have the "@" prefix.
|
||||
*/
|
||||
async function fetchPronouns(statusID, account_name) {
|
||||
// log(`searching for ${account_name}`);
|
||||
let cacheResult = { pronounsCache: {} };
|
||||
try {
|
||||
cacheResult = await browser.storage.local.get();
|
||||
if (!cacheResult.pronounsCache) {
|
||||
//if result doesn't have "pronounsCache" create it
|
||||
let pronounsCache = {};
|
||||
await browser.storage.local.set({ pronounsCache });
|
||||
cacheResult = { pronounsCache: {} };
|
||||
}
|
||||
} catch {
|
||||
cacheResult = { pronounsCache: {} };
|
||||
// ignore errors, we have an empty object as fallback.
|
||||
}
|
||||
if (account_name[0] == "@") account_name = account_name.substring(1);
|
||||
|
||||
// if the username doesn't contain an @ (i.e. the post we're looking at is from this instance)
|
||||
// append the host name to it, to avoid cache overlap between instances
|
||||
if (!account_name.includes("@")) {
|
||||
account_name = account_name + "@" + host_name;
|
||||
}
|
||||
|
||||
// Extract the current cache by using object destructuring.
|
||||
if (account_name in cacheResult.pronounsCache) {
|
||||
let { value, timestamp } = cacheResult.pronounsCache[account_name];
|
||||
|
||||
// If we have a cached value and it's not outdated, use it.
|
||||
if (value && Date.now() - timestamp < max_age) {
|
||||
info(`${account_name} in cache with value: ${value}`);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
info(`${account_name} cache entry is stale, refreshing`);
|
||||
|
||||
if (!statusID) {
|
||||
console.warn(
|
||||
`Could not fetch pronouns for user ${account_name}, because no status ID was passed. This is an issue we're working on.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`${account_name} not in cache, fetching status`);
|
||||
let status = await fetchStatus(statusID);
|
||||
|
||||
let PronounField = getPronounField(status, account_name);
|
||||
if (PronounField == "null") {
|
||||
//TODO: if no field check bio
|
||||
info(`no pronouns found for ${account_name}, cached null`);
|
||||
}
|
||||
return PronounField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches status by statusID from host_name with user's access token.
|
||||
*
|
||||
* @param {string} statusID ID of status being requested.
|
||||
* @returns {Promise<object>} Contents of the status in json form.
|
||||
*/
|
||||
async function fetchStatus(statusID) {
|
||||
const accessToken = await getActiveAccessToken();
|
||||
//fetch status from home server with access token
|
||||
const response = await fetch(`${location.protocol}//${host_name}/api/v1/statuses/${statusID}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
let status = await response.json();
|
||||
|
||||
//if status contains a reblog get that for further processing - we want the embedded post's author
|
||||
if (status.reblog) status = status.reblog;
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for fields labelled "pronouns" in the statuses' author.
|
||||
* If found returns the value of said field.
|
||||
*
|
||||
* @param {string} status
|
||||
* @param {string} account_name
|
||||
* @returns {string} Author pronouns if found. Otherwise returns "null"
|
||||
*/
|
||||
function getPronounField(status, account_name) {
|
||||
debug(status);
|
||||
// get account from status and pull out fields
|
||||
let account = status["account"];
|
||||
let fields = account["fields"];
|
||||
|
||||
for (let field of fields) {
|
||||
//match fields against "pronouns"
|
||||
//TODO: multiple languages
|
||||
if (field["name"].toLowerCase().includes("pronouns")) {
|
||||
debug(`${account["acct"]}: ${field["value"]}`);
|
||||
|
||||
let pronounSet = generatePronounSet(account_name, field["value"]);
|
||||
cachePronouns(account_name, pronounSet);
|
||||
return field["value"];
|
||||
}
|
||||
}
|
||||
|
||||
//if not returned by this point no field with pronouns was found
|
||||
let pronounSet = generatePronounSet(account_name, "null");
|
||||
|
||||
cachePronouns(account_name, pronounSet);
|
||||
return "null";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates object with pronoun related data to be saved to storage.
|
||||
*
|
||||
* @param {string} account Full account name as generated in fetchPronouns()
|
||||
* @param {string} value Contents of the found field's value
|
||||
* @returns {object} Object containing account name, timestamp and pronouns
|
||||
*/
|
||||
function generatePronounSet(account, value) {
|
||||
return { acct: account, timestamp: Date.now(), value: value }; //TODO: this should be just account right
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an entry to the "pronounsCache" object in local storage.
|
||||
*
|
||||
* @param {string} account The account ID
|
||||
* @param {{ acct: any; timestamp: number; value: any; }} set The data to cache.
|
||||
*/
|
||||
async function cachePronouns(account, set) {
|
||||
let cache = { pronounsCache: {} };
|
||||
try {
|
||||
cache = await browser.storage.local.get();
|
||||
} catch {
|
||||
// ignore errors, we have an empty object as fallback.
|
||||
}
|
||||
|
||||
cache.pronounsCache[account] = set;
|
||||
try {
|
||||
await browser.storage.local.set(cache);
|
||||
debug(`${account} cached`);
|
||||
} catch (e) {
|
||||
error(`${account} could not been cached: `, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the pro-plate to the element. The caller needs to ensure that the passed element
|
||||
* is defined and that it's either a:
|
||||
|
@ -387,6 +209,13 @@ async function addProplate(element) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (accountName[0] == "@") accountName = accountName.substring(1);
|
||||
// if the username doesn't contain an @ (i.e. the post we're looking at is from this instance)
|
||||
// append the host name to it, to avoid cache overlap between instances
|
||||
if (!accountName.includes("@")) {
|
||||
accountName = accountName + "@" + host_name;
|
||||
}
|
||||
|
||||
//get the name element and apply CSS
|
||||
let nametagEl = /** @type {HTMLElement|null} */ (element.querySelector(".display-name__html"));
|
||||
if (!nametagEl) {
|
||||
|
@ -408,16 +237,12 @@ async function addProplate(element) {
|
|||
//create plate
|
||||
const proplate = document.createElement("span");
|
||||
let pronouns = await fetchPronouns(statusId, accountName);
|
||||
if (pronouns == "null" && !logging) {
|
||||
if (pronouns == "null" && !isLogging()) {
|
||||
return;
|
||||
}
|
||||
proplate.innerHTML = sanitizePronouns(pronouns);
|
||||
proplate.classList.add("protoots-proplate");
|
||||
if (
|
||||
(host_name == "queer.group" && (accountName == "@vivien" || accountName == "@jasmin")) ||
|
||||
accountName == "@jasmin@queer.group" ||
|
||||
accountName == "@vivien@queer.group"
|
||||
) {
|
||||
if (accountName == "jasmin@queer.group" || accountName == "vivien@queer.group") {
|
||||
//i think you can figure out what this does on your own
|
||||
proplate.classList.add("proplate-pog");
|
||||
}
|
||||
|
@ -445,24 +270,6 @@ function hasClasses(element, ...cl) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current access token for the user.
|
||||
* @returns {Promise<string>} The accessToken for the current user if we are logged in.
|
||||
*/
|
||||
async function getActiveAccessToken() {
|
||||
// Fortunately, Mastodon provides the initial state in a <script> element at the beginning of the page.
|
||||
// Besides a lot of other information, it contains the access token for the current user.
|
||||
const initialStateEl = document.getElementById("initial-state");
|
||||
if (!initialStateEl) {
|
||||
error("user not logged in yet");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse the JSON inside the script tag and extract the meta element from it.
|
||||
const { meta } = JSON.parse(initialStateEl.innerText);
|
||||
return meta.access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the pronoun field by removing various long information parts.
|
||||
* As of today, this just removes custom emojis from the field.
|
||||
|
|
24
src/libs/caching.js
Normal file
24
src/libs/caching.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { debug, error } from "./logging";
|
||||
import { storage } from "webextension-polyfill";
|
||||
/**
|
||||
* Appends an entry to the "pronounsCache" object in local storage.
|
||||
*
|
||||
* @param {string} account The account ID
|
||||
* @param {{ acct: any; timestamp: number; value: any; }} set The data to cache.
|
||||
*/
|
||||
export async function cachePronouns(account, value) {
|
||||
let cache = { pronounsCache: {} };
|
||||
try {
|
||||
cache = await storage.local.get();
|
||||
} catch {
|
||||
// ignore errors, we have an empty object as fallback.
|
||||
}
|
||||
|
||||
cache.pronounsCache[account] = { acct: account, timestamp: Date.now(), value: value };
|
||||
try {
|
||||
await storage.local.set(cache);
|
||||
debug(`${account} cached`);
|
||||
} catch (e) {
|
||||
error(`${account} could not been cached: `, e);
|
||||
}
|
||||
}
|
127
src/libs/fetchPronouns.js
Normal file
127
src/libs/fetchPronouns.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { info } from "./logging";
|
||||
import { cachePronouns } from "./caching";
|
||||
|
||||
/**
|
||||
* Fetches pronouns associated with account name.
|
||||
* If cache misses status is fetched from the instance.
|
||||
*
|
||||
* @param {string | undefined} statusID ID of the status being requested, in case cache misses.
|
||||
* @param {string} account_name The account name, used for caching. Should have the "@" prefix.
|
||||
*/
|
||||
export async function fetchPronouns(statusID, account_name) {
|
||||
// log(`searching for ${account_name}`);
|
||||
let cacheResult = { pronounsCache: {} };
|
||||
try {
|
||||
cacheResult = await storage.local.get();
|
||||
if (!cacheResult.pronounsCache) {
|
||||
//if result doesn't have "pronounsCache" create it
|
||||
let pronounsCache = {};
|
||||
await storage.local.set({ pronounsCache });
|
||||
cacheResult = { pronounsCache: {} };
|
||||
}
|
||||
} catch {
|
||||
cacheResult = { pronounsCache: {} };
|
||||
// ignore errors, we have an empty object as fallback.
|
||||
}
|
||||
// Extract the current cache by using object destructuring.
|
||||
if (account_name in cacheResult.pronounsCache) {
|
||||
let { value, timestamp } = cacheResult.pronounsCache[account_name];
|
||||
|
||||
// If we have a cached value and it's not outdated, use it.
|
||||
if (value && Date.now() - timestamp < max_age) {
|
||||
info(`${account_name} in cache with value: ${value}`);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
info(`${account_name} cache entry is stale, refreshing`);
|
||||
|
||||
if (!statusID) {
|
||||
console.warn(
|
||||
`Could not fetch pronouns for user ${account_name}, because no status ID was passed. This is an issue we're working on.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`${account_name} not in cache, fetching status`);
|
||||
let status = await fetchStatus(statusID);
|
||||
|
||||
let PronounField = getPronounField(status, account_name);
|
||||
if (PronounField == "null") {
|
||||
//TODO: if no field check bio
|
||||
info(`no pronouns found for ${account_name}, cached null`);
|
||||
}
|
||||
return PronounField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches status by statusID from host_name with user's access token.
|
||||
*
|
||||
* @param {string} statusID ID of status being requested.
|
||||
* @returns {Promise<object>} Contents of the status in json form.
|
||||
*/
|
||||
async function fetchStatus(statusID) {
|
||||
const accessToken = await getActiveAccessToken();
|
||||
//fetch status from home server with access token
|
||||
const response = await fetch(
|
||||
`${location.protocol}//${location.host}/api/v1/statuses/${statusID}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
},
|
||||
);
|
||||
|
||||
let status = await response.json();
|
||||
|
||||
//if status contains a reblog get that for further processing - we want the embedded post's author
|
||||
if (status.reblog) status = status.reblog;
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for fields labelled "pronouns" in the statuses' author.
|
||||
* If found returns the value of said field.
|
||||
*
|
||||
* @param {string} status
|
||||
* @param {string} account_name
|
||||
* @returns {string} Author pronouns if found. Otherwise returns "null"
|
||||
*/
|
||||
function getPronounField(status, account_name) {
|
||||
debug(status);
|
||||
// get account from status and pull out fields
|
||||
let account = status["account"];
|
||||
let fields = account["fields"];
|
||||
|
||||
for (let field of fields) {
|
||||
//match fields against "pronouns"
|
||||
//TODO: multiple languages
|
||||
if (field["name"].toLowerCase().includes("pronouns")) {
|
||||
debug(`${account["acct"]}: ${field["value"]}`);
|
||||
|
||||
cachePronouns(account_name, field["value"]);
|
||||
return field["value"];
|
||||
}
|
||||
}
|
||||
|
||||
//if not returned by this point no field with pronouns was found
|
||||
|
||||
cachePronouns(account_name, "null");
|
||||
return "null";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current access token for the user.
|
||||
* @returns {Promise<string>} The accessToken for the current user if we are logged in.
|
||||
*/
|
||||
async function getActiveAccessToken() {
|
||||
// Fortunately, Mastodon provides the initial state in a <script> element at the beginning of the page.
|
||||
// Besides a lot of other information, it contains the access token for the current user.
|
||||
const initialStateEl = document.getElementById("initial-state");
|
||||
if (!initialStateEl) {
|
||||
error("user not logged in yet");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse the JSON inside the script tag and extract the meta element from it.
|
||||
const { meta } = JSON.parse(initialStateEl.innerText);
|
||||
return meta.access_token;
|
||||
}
|
40
src/libs/logging.js
Normal file
40
src/libs/logging.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
export async function getLogging() {
|
||||
try {
|
||||
let { logging: optionValue } = await storage.sync.get("logging");
|
||||
logging = optionValue;
|
||||
} catch {
|
||||
// Enable the logging automatically if we cannot determine the user preference.
|
||||
logging = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
let logging;
|
||||
|
||||
/** @param {any[]} args */
|
||||
export function error(...args) {
|
||||
if (logging) console.error(...args);
|
||||
}
|
||||
|
||||
/** @param {any[]} args */
|
||||
export function warn(...args) {
|
||||
if (logging) console.warn(...args);
|
||||
}
|
||||
|
||||
/** @param {any[]} args */
|
||||
export function log(...args) {
|
||||
if (logging) console.log(...args);
|
||||
}
|
||||
|
||||
/** @param {any[]} args */
|
||||
export function info(...args) {
|
||||
if (logging) console.info(...args);
|
||||
}
|
||||
|
||||
/** @param {any[]} args */
|
||||
export function debug(...args) {
|
||||
if (logging) console.debug(...args);
|
||||
}
|
|
@ -1,25 +1,27 @@
|
|||
import { storage } from "webextension-polyfill";
|
||||
|
||||
function saveOptions(e) {
|
||||
e.preventDefault();
|
||||
browser.storage.sync.set({
|
||||
storage.sync.set({
|
||||
logging: document.querySelector("#logging").checked,
|
||||
});
|
||||
}
|
||||
|
||||
function restoreOptions() {
|
||||
function setCurrentChoice(result) {
|
||||
document.querySelector("#logging").checked = result.logging || off;
|
||||
document.querySelector("#logging").checked = result.logging || false;
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
|
||||
let getting = browser.storage.sync.get("logging");
|
||||
let getting = storage.sync.get("logging");
|
||||
getting.then(setCurrentChoice, onError);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||
document.querySelector("form").addEventListener("submit", saveOptions);
|
||||
document.querySelector("#resetbutton").addEventListener("click", async () => {
|
||||
await browser.storage.local.clear();
|
||||
await storage.local.clear();
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue