mirror of
https://github.com/ItsVipra/ProToots
synced 2024-11-10 14:14:19 +00:00
Merge branch 'main' of https://github.com/ItsVipra/ProToots
This commit is contained in:
commit
e75051b509
10 changed files with 402 additions and 341 deletions
26
.github/workflows/codequality.yml
vendored
Normal file
26
.github/workflows/codequality.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: Code quality
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the
|
||||
# added or changed files to the repository.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Format files using prettier
|
||||
run: npm run format
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
name: Commit possible changes
|
||||
with:
|
||||
commit_message: "Format files using prettier"
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 100,
|
||||
"useTabs": true,
|
||||
"trailingComma": "all"
|
||||
}
|
18
README.md
18
README.md
|
@ -1,23 +1,27 @@
|
|||
# ProToots (v0.99)
|
||||
|
||||
A Firefox extension which displays an author's pronouns next to their name on Mastodon.
|
||||
![A Mastodon screenshot showing off pronouns next to a person's name](documentation/firefox_ehHwJufMau.png)
|
||||
|
||||
## Download/Installation
|
||||
|
||||
### ⚠️ This is a **pre-release, temporary extension**! It will be **removed when you restart your browser**! ⚠️
|
||||
ℹ️ We're working on a proper firefox store release already.
|
||||
|
||||
ℹ️ We're working on a proper firefox store release already.
|
||||
|
||||
To install go to [the releases page](https://github.com/ItsVipra/ProToots/releases) and follow the instructions there.
|
||||
|
||||
## FAQ
|
||||
|
||||
Why does it need permission for all websites?
|
||||
|
||||
> The addon needs to determine whether or not the site you are currently browsing is a Mastodon server. For that to work, it requires access to all sites. Otherwise, each existing Mastodon server would have to be explicitly added.
|
||||
|
||||
## setup
|
||||
|
||||
## setup
|
||||
- install web-ext with `npm install --global web-ext`
|
||||
- optionally:
|
||||
- run `web-ext run --firefox-profile='$ProfileNameOfYourChoosing' --profile-create-if-mising`
|
||||
- open that profile in firefox, log into fedi
|
||||
- after that when you run `web-ext run -p='$ProfileNameOfYourChoosing'` you should be logged into your fedi account
|
||||
- install web-ext with `npm install --global web-ext`
|
||||
- optionally:
|
||||
- run `web-ext run --firefox-profile='$ProfileNameOfYourChoosing' --profile-create-if-mising`
|
||||
- open that profile in firefox, log into fedi
|
||||
- after that when you run `web-ext run -p='$ProfileNameOfYourChoosing'` you should be logged into your fedi account
|
||||
- run the extension with `web-ext run -u -u="yourinstancehere"`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "ProToots",
|
||||
"version": "0.99",
|
||||
"manifest_version": 2,
|
||||
"name": "ProToots",
|
||||
"version": "0.99",
|
||||
|
||||
"icons":{
|
||||
"48": "src/icons/icon small_size/icon small_size.png",
|
||||
|
@ -21,27 +21,22 @@
|
|||
"default_popup": "src/options/options.html"
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
],
|
||||
"js" : [
|
||||
"src/content_scripts/protoots.js"
|
||||
],
|
||||
"css": ["src/styles/proplate.css"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://*/*"],
|
||||
"js": ["src/content_scripts/protoots.js"],
|
||||
"css": ["src/styles/proplate.css"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
|
||||
"options_ui": {
|
||||
"page": "src/options/options.html"
|
||||
},
|
||||
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "protoots@trans.rights"
|
||||
}
|
||||
}
|
||||
"options_ui": {
|
||||
"page": "src/options/options.html"
|
||||
},
|
||||
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "protoots@trans.rights"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"scripts": {
|
||||
"format": "prettier --write --ignore-path .gitignore ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.8.8",
|
||||
"web-ext": "^7.6.2"
|
||||
},
|
||||
"prettier": {
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 100,
|
||||
"useTabs": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,31 +4,31 @@
|
|||
//document.body.display-name__html.append();
|
||||
|
||||
// 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.hostname
|
||||
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()
|
||||
checkSite();
|
||||
let logging;
|
||||
|
||||
function error() {
|
||||
if (logging) console.error(arguments);
|
||||
if (logging) console.error(arguments);
|
||||
}
|
||||
|
||||
function warn() {
|
||||
if (logging) console.warn(arguments);
|
||||
if (logging) console.warn(arguments);
|
||||
}
|
||||
|
||||
function log() {
|
||||
if (logging) console.log(arguments);
|
||||
if (logging) console.log(arguments);
|
||||
}
|
||||
|
||||
function info() {
|
||||
if (logging) console.info(arguments);
|
||||
if (logging) console.info(arguments);
|
||||
}
|
||||
|
||||
function debug() {
|
||||
if (logging) console.debug(arguments);
|
||||
if (logging) console.debug(arguments);
|
||||
}
|
||||
|
||||
// log("hey vippy, du bist cute <3")
|
||||
|
@ -38,19 +38,24 @@ function debug() {
|
|||
* If so creates an 'readystatechange' EventListener, with callback to main()
|
||||
*/
|
||||
async function checkSite() {
|
||||
await browser.storage.sync.get("logging").then((res) => {logging = res['logging']}, () => {logging = true});
|
||||
let requestDest = location.protocol + '//' + host_name + '/api/v1/instance'
|
||||
let response = await fetch(requestDest)
|
||||
await browser.storage.sync.get("logging").then(
|
||||
(res) => {
|
||||
logging = res["logging"];
|
||||
},
|
||||
() => {
|
||||
logging = true;
|
||||
},
|
||||
);
|
||||
let requestDest = location.protocol + "//" + host_name + "/api/v1/instance";
|
||||
let response = await fetch(requestDest);
|
||||
|
||||
if (response) {
|
||||
// debug('checksite response got', {'response' : response.json()})
|
||||
if (response) {
|
||||
// debug('checksite response got', {'response' : response.json()})
|
||||
|
||||
document.addEventListener('readystatechange', main)
|
||||
|
||||
}
|
||||
else {
|
||||
warn('Not a Mastodon instance')
|
||||
}
|
||||
document.addEventListener("readystatechange", main);
|
||||
} else {
|
||||
warn("Not a Mastodon instance");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,38 +65,35 @@ async function checkSite() {
|
|||
*
|
||||
*/
|
||||
function main() {
|
||||
// debug('selection for id mastodon', {'result': document.querySelector("#mastodon")})
|
||||
if (document.querySelector("#mastodon")) {
|
||||
log('Mastodon instance, activating Protoots')
|
||||
// debug('selection for id mastodon', {'result': document.querySelector("#mastodon")})
|
||||
if (document.querySelector("#mastodon")) {
|
||||
log("Mastodon instance, activating Protoots");
|
||||
|
||||
let lastUrl = location.href
|
||||
new MutationObserver((mutations) => {
|
||||
const url = location.href
|
||||
if (url !== lastUrl) {
|
||||
lastUrl = url
|
||||
onUrlChange()
|
||||
}
|
||||
let lastUrl = location.href;
|
||||
new MutationObserver((mutations) => {
|
||||
const url = location.href;
|
||||
if (url !== lastUrl) {
|
||||
lastUrl = url;
|
||||
onUrlChange();
|
||||
}
|
||||
|
||||
for (const m of mutations) {
|
||||
m.addedNodes.forEach(n => {
|
||||
if (!(n instanceof HTMLElement))
|
||||
return
|
||||
for (const m of mutations) {
|
||||
m.addedNodes.forEach((n) => {
|
||||
if (!(n instanceof HTMLElement)) return;
|
||||
|
||||
if (n.className == "column") {
|
||||
debug("found a column: ", n)
|
||||
createObserver(n)
|
||||
//TODO: yet another bad hack, pls fix
|
||||
//TODO: doesn't work when going from detailed-status to detailed-status
|
||||
document.querySelectorAll(".detailed-status").forEach(el => addProplate(el))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}).observe(document, { subtree: true, childList: true })}
|
||||
else {
|
||||
warn('Not a Mastodon instance')
|
||||
}
|
||||
if (n.className == "column") {
|
||||
debug("found a column: ", n);
|
||||
createObserver(n);
|
||||
//TODO: yet another bad hack, pls fix
|
||||
//TODO: doesn't work when going from detailed-status to detailed-status
|
||||
document.querySelectorAll(".detailed-status").forEach((el) => addProplate(el));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).observe(document, { subtree: true, childList: true });
|
||||
} else {
|
||||
warn("Not a Mastodon instance");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,55 +104,56 @@ function main() {
|
|||
* @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}`);
|
||||
// log(`searching for ${account_name}`);
|
||||
|
||||
let cacheResult = await browser.storage.local.get().then(getSuccess, onError);
|
||||
let cacheResult = await browser.storage.local.get().then(getSuccess, onError);
|
||||
|
||||
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
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// debug(cacheResult);
|
||||
// debug(cacheResult);
|
||||
|
||||
if (Object.keys(cacheResult).length == 0) {
|
||||
let pronounsCache = {}
|
||||
await browser.storage.local.set({pronounsCache}).then(setSuccess, onError);
|
||||
warn('created pronounsCache in storage');
|
||||
}
|
||||
if (Object.keys(cacheResult).length == 0) {
|
||||
let pronounsCache = {};
|
||||
await browser.storage.local.set({ pronounsCache }).then(setSuccess, onError);
|
||||
warn("created pronounsCache in storage");
|
||||
}
|
||||
|
||||
let cacheKeys = Object.keys(cacheResult["pronounsCache"])
|
||||
let cacheKeys = Object.keys(cacheResult["pronounsCache"]);
|
||||
|
||||
if (cacheKeys.includes(account_name)) {
|
||||
let entryValue = cacheResult["pronounsCache"][account_name].value
|
||||
let entryTimestamp = cacheResult["pronounsCache"][account_name].timestamp
|
||||
if ((Date.now() - entryTimestamp) < max_age) {
|
||||
info(`${account_name} in cache:`, {'cache entry': cacheResult["pronounsCache"][account_name]} )
|
||||
return entryValue
|
||||
}
|
||||
else {
|
||||
info(`${account_name} entry is stale, refreshing`);
|
||||
}
|
||||
}
|
||||
if (cacheKeys.includes(account_name)) {
|
||||
let entryValue = cacheResult["pronounsCache"][account_name].value;
|
||||
let entryTimestamp = cacheResult["pronounsCache"][account_name].timestamp;
|
||||
if (Date.now() - entryTimestamp < max_age) {
|
||||
info(`${account_name} in cache:`, {
|
||||
"cache entry": cacheResult["pronounsCache"][account_name],
|
||||
});
|
||||
return entryValue;
|
||||
} else {
|
||||
info(`${account_name} 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;
|
||||
}
|
||||
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);
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,16 +163,16 @@ async function fetchPronouns(statusID, account_name) {
|
|||
* @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}` },
|
||||
});
|
||||
|
||||
const accessToken = await getActiveAccessToken();
|
||||
//fetch status from home server with access token
|
||||
const response = await fetch(`https://${host_name}/api/v1/statuses/${statusID}`, { headers: { 'Authorization': `Bearer ${accessToken}` } });
|
||||
|
||||
let status = await response.json();
|
||||
return status;
|
||||
let status = await response.json();
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches for fields labelled "pronouns" in the statuses' author.
|
||||
* If found returns the value of said field.
|
||||
|
@ -179,31 +182,28 @@ async function fetchStatus(statusID) {
|
|||
* @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"];
|
||||
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']}`);
|
||||
for (let field of fields) {
|
||||
//match fields against "pronouns"
|
||||
//TODO: multiple languages
|
||||
if (field["name"].toLowerCase().includes("pronouns")) {
|
||||
debug(`${account["acct"]}: ${field["value"]}`);
|
||||
|
||||
if (!(field['value'].includes("a href"))) { //filter links
|
||||
let pronounSet = generatePronounSet(account_name, field['value'])
|
||||
cachePronouns(account_name, pronounSet)
|
||||
return(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");
|
||||
|
||||
//if not returned by this point no field with pronouns was found
|
||||
let pronounSet = generatePronounSet(account_name, 'null')
|
||||
|
||||
cachePronouns(account_name, pronounSet)
|
||||
return "null"
|
||||
cachePronouns(account_name, pronounSet);
|
||||
return "null";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,7 +214,7 @@ function getPronounField(status, account_name) {
|
|||
* @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
|
||||
return { acct: account, timestamp: Date.now(), value: value }; //TODO: this should be just account right
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,24 +224,22 @@ function generatePronounSet(account, value) {
|
|||
* @param {{ acct: any; timestamp: number; value: any; }} set The data to cache.
|
||||
*/
|
||||
async function cachePronouns(account, set) {
|
||||
let result = await browser.storage.local.get("pronounsCache").then(getSuccess, onError);
|
||||
let pronounsCache = result['pronounsCache']
|
||||
pronounsCache[account] = set
|
||||
await browser.storage.local.set({pronounsCache}).then(setSuccess, onError);
|
||||
debug(`caching ${account}`)
|
||||
// return
|
||||
let result = await browser.storage.local.get("pronounsCache").then(getSuccess, onError);
|
||||
let pronounsCache = result["pronounsCache"];
|
||||
pronounsCache[account] = set;
|
||||
await browser.storage.local.set({ pronounsCache }).then(setSuccess, onError);
|
||||
debug(`caching ${account}`);
|
||||
// return
|
||||
}
|
||||
|
||||
function getSuccess(result) {
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
function setSuccess() {
|
||||
|
||||
}
|
||||
function setSuccess() {}
|
||||
|
||||
function onError(error) {
|
||||
error('Failed save to storage!');
|
||||
error("Failed save to storage!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,21 +252,24 @@ function onError(error) {
|
|||
*
|
||||
* @param {Element | HTMLElement} element The status where the element should be added.
|
||||
*/
|
||||
async function addProplate (element) {
|
||||
if (!(element instanceof HTMLElement)) return;
|
||||
async function addProplate(element) {
|
||||
if (!(element instanceof HTMLElement)) return;
|
||||
|
||||
//check whether element has already had a proplate added
|
||||
if (element.hasAttribute("protoots-checked")) return
|
||||
//check whether element has already had a proplate added
|
||||
if (element.hasAttribute("protoots-checked")) return;
|
||||
|
||||
//if not add the attribute
|
||||
element.setAttribute("protoots-checked", "true")
|
||||
let statusId = element.dataset.id
|
||||
//if not add the attribute
|
||||
element.setAttribute("protoots-checked", "true");
|
||||
let statusId = element.dataset.id;
|
||||
if (!statusId) {
|
||||
// We don't have a status ID, pronouns might not be in cache
|
||||
warn("The element passed to addProplate does not have a data-id attribute, although it should have one.", element);
|
||||
warn(
|
||||
"The element passed to addProplate does not have a data-id attribute, although it should have one.",
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
||||
let accountNameEl = element.querySelector(".display-name__account");
|
||||
let accountNameEl = element.querySelector(".display-name__account");
|
||||
if (!accountNameEl) {
|
||||
warn(
|
||||
"The element passed to addProplate does not have a .display-name__account, although it should have one.",
|
||||
|
@ -276,14 +277,14 @@ async function addProplate (element) {
|
|||
);
|
||||
return;
|
||||
}
|
||||
let accountName = accountNameEl.textContent
|
||||
if (!accountName){
|
||||
warn("Could not extract the account name from the element.")
|
||||
let accountName = accountNameEl.textContent;
|
||||
if (!accountName) {
|
||||
warn("Could not extract the account name from the element.");
|
||||
return;
|
||||
}
|
||||
|
||||
//get the name element and apply CSS
|
||||
let nametagEl =/** @type {HTMLElement|null} */ (element.querySelector(".display-name__html"));
|
||||
//get the name element and apply CSS
|
||||
let nametagEl = /** @type {HTMLElement|null} */ (element.querySelector(".display-name__html"));
|
||||
if (!nametagEl) {
|
||||
warn(
|
||||
"The element passed to addProplate does not have a .display-name__html, although it should have one.",
|
||||
|
@ -295,66 +296,77 @@ async function addProplate (element) {
|
|||
nametagEl.style.display = "flex";
|
||||
nametagEl.style.alignItems = "baseline";
|
||||
|
||||
//create plate
|
||||
const proplate = document.createElement("span");
|
||||
let pronouns = await fetchPronouns(statusId, accountName)
|
||||
if (pronouns == "null" && !logging) {return}
|
||||
proplate.textContent = pronouns;
|
||||
//create plate
|
||||
const proplate = document.createElement("span");
|
||||
let pronouns = await fetchPronouns(statusId, accountName);
|
||||
if (pronouns == "null" && !logging) {
|
||||
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") {
|
||||
//i think you can figure out what this does on your own
|
||||
proplate.classList.add("pog")
|
||||
}
|
||||
if (
|
||||
(host_name == "queer.group" && (accountName == "@vivien" || accountName == "@jasmin")) ||
|
||||
accountName == "@jasmin@queer.group" ||
|
||||
accountName == "@vivien@queer.group"
|
||||
) {
|
||||
//i think you can figure out what this does on your own
|
||||
proplate.classList.add("pog");
|
||||
}
|
||||
|
||||
//add plate to nametag
|
||||
nametagEl.appendChild(proplate);
|
||||
//add plate to nametag
|
||||
nametagEl.appendChild(proplate);
|
||||
}
|
||||
|
||||
function createObserver(element) {
|
||||
// select column as observation target
|
||||
// const targetNode = document.querySelector(".column");
|
||||
const targetNode = element
|
||||
// select column as observation target
|
||||
// const targetNode = document.querySelector(".column");
|
||||
const targetNode = element;
|
||||
|
||||
// observe childlist and subtree events
|
||||
// docs: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
|
||||
const config = { childList: true, subtree: true, attributes: true };
|
||||
// observe childlist and subtree events
|
||||
// docs: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
|
||||
const config = { childList: true, subtree: true, attributes: true };
|
||||
|
||||
// define callback inline
|
||||
const callback = (/** @type {MutationRecord[]} */ mutationList, /** @type {MutationObserver} */ observer) => {
|
||||
for (const mutation of mutationList) {
|
||||
// define callback inline
|
||||
const callback = (
|
||||
/** @type {MutationRecord[]} */ mutationList,
|
||||
/** @type {MutationObserver} */ observer,
|
||||
) => {
|
||||
for (const mutation of mutationList) {
|
||||
mutation.addedNodes.forEach((n) => {
|
||||
if (!(n instanceof HTMLElement)) return;
|
||||
|
||||
mutation.addedNodes.forEach((n) => {
|
||||
if (!(n instanceof HTMLElement)) return;
|
||||
//case for all the other normal statuses
|
||||
if (
|
||||
containsClass(n.classList, "status") &&
|
||||
!containsClass(n.classList, "status__prepend")
|
||||
) {
|
||||
//|| containsClass(n.classList, "detailed-status"))) {
|
||||
addProplate(n);
|
||||
} else {
|
||||
//for nodes that have a broken classlist
|
||||
let statusElement = n.querySelector(".status");
|
||||
if (statusElement) {
|
||||
addProplate(statusElement);
|
||||
}
|
||||
//potential solution for dirty hack?
|
||||
// debug(".status not found looking for .detailed-status", {"element:": n})
|
||||
// statusElement = n.querySelector(".detailed-status")
|
||||
// if (statusElement != null) {
|
||||
// addProplate(statusElement);
|
||||
// }
|
||||
}
|
||||
});
|
||||
}
|
||||
//TODO: bad hack, please remove
|
||||
document.querySelectorAll(".status").forEach((el) => addProplate(el));
|
||||
document.querySelectorAll(".detailed-status").forEach((el) => addProplate(el));
|
||||
};
|
||||
|
||||
//case for all the other normal statuses
|
||||
if (containsClass(n.classList, "status") && !containsClass(n.classList, "status__prepend")) { //|| containsClass(n.classList, "detailed-status"))) {
|
||||
addProplate(n);
|
||||
}
|
||||
else {
|
||||
//for nodes that have a broken classlist
|
||||
let statusElement = n.querySelector(".status")
|
||||
if (statusElement) {
|
||||
addProplate(statusElement);
|
||||
}
|
||||
//potential solution for dirty hack?
|
||||
// debug(".status not found looking for .detailed-status", {"element:": n})
|
||||
// statusElement = n.querySelector(".detailed-status")
|
||||
// if (statusElement != null) {
|
||||
// addProplate(statusElement);
|
||||
// }
|
||||
}
|
||||
})
|
||||
}
|
||||
//TODO: bad hack, please remove
|
||||
document.querySelectorAll(".status").forEach(el => addProplate(el));
|
||||
document.querySelectorAll(".detailed-status").forEach(el => addProplate(el));
|
||||
};
|
||||
// Create an observer instance linked to the callback function
|
||||
const observer = new MutationObserver(callback);
|
||||
|
||||
// Create an observer instance linked to the callback function
|
||||
const observer = new MutationObserver(callback);
|
||||
|
||||
// Start observing the target node for configured mutations
|
||||
observer.observe(targetNode, config);
|
||||
// Start observing the target node for configured mutations
|
||||
observer.observe(targetNode, config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,9 +374,11 @@ function createObserver(element) {
|
|||
* Creates a new MutationObserver for each column on the page.
|
||||
*/
|
||||
function onUrlChange() {
|
||||
//select all columns for advanced interface
|
||||
document.querySelectorAll(".column").forEach(el => {createObserver(el)});
|
||||
// createObserver();
|
||||
//select all columns for advanced interface
|
||||
document.querySelectorAll(".column").forEach((el) => {
|
||||
createObserver(el);
|
||||
});
|
||||
// createObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -373,15 +387,15 @@ function onUrlChange() {
|
|||
* @returns Whether the classList contains the class.
|
||||
*/
|
||||
function containsClass(classList, cl) {
|
||||
if (!classList|| !cl) return false;
|
||||
if (!classList || !cl) return false;
|
||||
|
||||
for (const c of classList) {
|
||||
if (c === cl) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for (const c of classList) {
|
||||
if (c === cl) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -391,13 +405,28 @@ return false
|
|||
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")
|
||||
const initialStateEl = document.getElementById("initial-state");
|
||||
if (!initialStateEl) {
|
||||
error("user not logged in yet")
|
||||
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)
|
||||
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.
|
||||
*
|
||||
* @param {string} str The input string.
|
||||
* @returns The sanitized string.
|
||||
*/
|
||||
function sanitizePronouns(str) {
|
||||
// Remove all custom emojis with the :shortcode: format.
|
||||
str = str.replace(/:[\w_]+:/gi, "");
|
||||
|
||||
// Finally, remove leading and trailing whitespace.
|
||||
return str.trim();
|
||||
}
|
||||
|
|
|
@ -1,74 +1,75 @@
|
|||
body {
|
||||
margin: 0;
|
||||
--background: hsl(249, 11%, 90%);
|
||||
--hover: hsl(249, 11%, 75%);
|
||||
--border: hsl(0, 0%, 50%);
|
||||
--link: hsl(0, 0%, 25%);
|
||||
font-family: system-ui, sans-serif;
|
||||
margin: 0;
|
||||
--background: hsl(249, 11%, 90%);
|
||||
--hover: hsl(249, 11%, 75%);
|
||||
--border: hsl(0, 0%, 50%);
|
||||
--link: hsl(0, 0%, 25%);
|
||||
}
|
||||
|
||||
.protoots-settings{
|
||||
margin-bottom: 0;
|
||||
.protoots-settings {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
background-color: var(--background);
|
||||
margin: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: var(--background);
|
||||
margin: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.switch{
|
||||
display: block;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0.25em;
|
||||
padding-left: 0.75em;
|
||||
margin-top: 0.25em;
|
||||
.switch {
|
||||
display: block;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0.25em;
|
||||
padding-left: 0.75em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
input{
|
||||
margin: 0.75em;
|
||||
input {
|
||||
margin: 0.75em;
|
||||
}
|
||||
|
||||
.description {
|
||||
/* up right down left */
|
||||
padding: 0 0 0 0.75em;
|
||||
margin: 0 0 0.5em 0;
|
||||
/* up right down left */
|
||||
padding: 0 0 0 0.75em;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--background);
|
||||
padding: 1em;
|
||||
border-top: 1px dashed var(--border);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
background-color: var(--background);
|
||||
padding: 1em;
|
||||
border-top: 1px dashed var(--border);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.75em 1em;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
padding: 0.75em 1em;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--hover);
|
||||
background-color: var(--hover);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link);
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #1c1b22;
|
||||
color: hsl(0, 0%, 90%);
|
||||
--background: hsl(249, 11%, 25%);
|
||||
--hover: hsl(249, 11%, 75%);
|
||||
--border: hsl(0, 0%, 90%);
|
||||
--link: hsl(0, 0%, 75%);
|
||||
}
|
||||
}
|
||||
body {
|
||||
background-color: #1c1b22;
|
||||
color: hsl(0, 0%, 90%);
|
||||
--background: hsl(249, 11%, 25%);
|
||||
--hover: hsl(249, 11%, 75%);
|
||||
--border: hsl(0, 0%, 90%);
|
||||
--link: hsl(0, 0%, 75%);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<form class="protoots-settings">
|
||||
<div class="row">
|
||||
<div class="text">
|
||||
<label for="logging" class="switch">Enable logging</label>
|
||||
<p id="logging-description" class="description">Log debug information to browser console</p>
|
||||
</div>
|
||||
<input type="checkbox" id="logging" name="logging" aria-describedby="logging-description">
|
||||
<head>
|
||||
<link rel="stylesheet" href="options.css" />
|
||||
</head>
|
||||
<body>
|
||||
<form class="protoots-settings">
|
||||
<div class="row">
|
||||
<div class="text">
|
||||
<label for="logging" class="switch">Enable logging</label>
|
||||
<p id="logging-description" class="description">
|
||||
Log debug information to browser console
|
||||
</p>
|
||||
</div>
|
||||
<input type="checkbox" id="logging" name="logging" aria-describedby="logging-description" />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button type="submit">Save</button>
|
||||
<a target="_blank" href="https://github.com/ItsVipra/ProToots"
|
||||
>More info / help on Github</a
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button type="submit">Save</button>
|
||||
<a target="_blank" href="https://github.com/ItsVipra/ProToots">More info / help on Github</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
function saveOptions(e) {
|
||||
e.preventDefault();
|
||||
browser.storage.sync.set({
|
||||
logging: document.querySelector("#logging").checked
|
||||
});
|
||||
}
|
||||
e.preventDefault();
|
||||
browser.storage.sync.set({
|
||||
logging: document.querySelector("#logging").checked,
|
||||
});
|
||||
}
|
||||
|
||||
function restoreOptions() {
|
||||
function setCurrentChoice(result) {
|
||||
document.querySelector("#logging").checked = result.logging || off;
|
||||
}
|
||||
function restoreOptions() {
|
||||
function setCurrentChoice(result) {
|
||||
document.querySelector("#logging").checked = result.logging || off;
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
function onError(error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
|
||||
let getting = browser.storage.sync.get("logging");
|
||||
getting.then(setCurrentChoice, onError);
|
||||
}
|
||||
let getting = browser.storage.sync.get("logging");
|
||||
getting.then(setCurrentChoice, onError);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||
document.querySelector("form").addEventListener("submit", saveOptions);
|
||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||
document.querySelector("form").addEventListener("submit", saveOptions);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
}
|
||||
|
||||
/* dark theme on detailed status */
|
||||
.theme-default .detailed-status .protoots-proplate {
|
||||
.theme-default .detailed-status .protoots-proplate {
|
||||
/* scrollable background */
|
||||
background-color: #282c37;
|
||||
|
||||
|
@ -49,16 +49,18 @@
|
|||
|
||||
/* rainbow pronouns for jasmin and i cause why not */
|
||||
.pog:hover {
|
||||
background-image: linear-gradient(45deg,
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 154, 0, 1) 10%,
|
||||
rgba(208, 222, 33, 1) 20%,
|
||||
rgba(79, 220, 74, 1) 30%,
|
||||
rgba(63, 218, 216, 1) 40%,
|
||||
rgba(47, 201, 226, 1) 50%,
|
||||
rgba(28, 127, 238, 1) 60%,
|
||||
rgba(95, 21, 242, 1) 70%,
|
||||
rgba(186, 12, 248, 1) 80%,
|
||||
rgba(251, 7, 217, 1) 90%,
|
||||
rgba(255, 0, 0, 1) 100%);
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 154, 0, 1) 10%,
|
||||
rgba(208, 222, 33, 1) 20%,
|
||||
rgba(79, 220, 74, 1) 30%,
|
||||
rgba(63, 218, 216, 1) 40%,
|
||||
rgba(47, 201, 226, 1) 50%,
|
||||
rgba(28, 127, 238, 1) 60%,
|
||||
rgba(95, 21, 242, 1) 70%,
|
||||
rgba(186, 12, 248, 1) 80%,
|
||||
rgba(251, 7, 217, 1) 90%,
|
||||
rgba(255, 0, 0, 1) 100%
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue