feature: inject event listeners mimicking <v4 interaction button functionality on v4 Mastodon instances

This commit is contained in:
Huey 2022-11-21 12:28:16 +08:00
parent 497b8a2c78
commit 3c6d43b598
No known key found for this signature in database
GPG key ID: 54C82E718C137231
2 changed files with 79 additions and 11 deletions

View file

@ -147,12 +147,18 @@ function getInteractionType(url) {
/** /**
* Handles changes to the URL of a tab. * Handles changes to the URL of a tab.
* *
* @param {string} tabId
* @param {Object} changeInfo
* @returns {void} * @returns {void}
*/ */
function onTabUpdate() { async function onTabUpdate(tabId, changeInfo) {
const ownMastodon = await AddonSettings.get("ownMastodon");
const currentURL = new URL(changeInfo.url);
if(ownMastodon.server !== currentURL.hostname){
browser.tabs.executeScript({ browser.tabs.executeScript({
file: "/content_script/mastodonInject.js" file: "/content_script/mastodonInject.js"
}); });
}
} }
/** /**

View file

@ -1,5 +1,7 @@
"use strict"; "use strict";
const TIMEOUT_DURATION = 20000;
/** /**
* Replacement onClick handler for Follow button. * Replacement onClick handler for Follow button.
* *
@ -14,32 +16,57 @@ function onClickFollow(event) {
window.open(`/users/${username}/remote_follow`, "_blank"); window.open(`/users/${username}/remote_follow`, "_blank");
} }
/**
* Replacement onClick handler for interaction buttons.
*
* @param {Event} event
* @returns {void}
*/
function onClickInteract(event) {
event.stopPropagation();
event.preventDefault();
const articleElement = event.target.closest("article[data-id]");
const tootId = (
articleElement === null
? window.location.pathname.split("/").slice(-1)[0]
: articleElement.getAttribute("data-id")
);
// activate AutoRemoteFollow
window.open(`/interact/${tootId}`, "_blank");
}
/** /**
* Wait for element to appear. * Wait for element to appear.
* *
* @param {string} selector * @param {string} selector
* @param {boolean} multiple
* @param {number} timeoutDuration * @param {number} timeoutDuration
* @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134}
* from which this was adapted * from which this was adapted
* @returns {Promise} * @returns {Promise}
*/ */
function waitForElement(selector, timeoutDuration) { function waitForElement(selector, multiple = false, timeoutDuration) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const getElement = () => document.querySelector(selector); const getElement = () => (
multiple
? document.querySelectorAll(selector)
: document.querySelector(selector)
);
const isElementFound = (el) => (!multiple && el) || (multiple && el.length > 0);
const timeout = window.setTimeout(() => { const timeout = window.setTimeout(() => {
reject(new Error("waitForElement timed out")); reject(new Error("waitForElement timed out"));
}, timeoutDuration); }, timeoutDuration);
const element = getElement(); const element = getElement();
if(element){ if(isElementFound(element)){
window.clearTimeout(timeout); window.clearTimeout(timeout);
return resolve(element); return resolve(element);
} }
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
const element = getElement(); const element = getElement();
if(element){ if(isElementFound(element)){
window.clearTimeout(timeout); window.clearTimeout(timeout);
resolve(element); resolve(element);
observer.disconnect(); observer.disconnect();
@ -62,20 +89,55 @@ function waitForElement(selector, timeoutDuration) {
*/ */
async function injectFollowButton() { async function injectFollowButton() {
try { try {
const followButton = await waitForElement(".account__header__tabs__buttons button:first-of-type", 20000); const followButton = await waitForElement(".account__header__tabs__buttons button:first-of-type", false, TIMEOUT_DURATION);
followButton.addEventListener("click", onClickFollow); followButton.addEventListener("click", onClickFollow);
} catch (error) { } catch (error) {
// Follow button failed to appear // Follow button failed to appear
} }
} }
/**
* Inject replacement onClick handler for Interaction buttons.
*
* @returns {void}
*/
async function injectInteractionButtons() {
const INJECTED_REPLY_CLASS = "mastodon-simplified-federation-injected-interaction";
try {
const replyButtons = await waitForElement(
".item-list[role='feed'] article[data-id] .status__action-bar button," +
".detailed-status__wrapper .detailed-status__action-bar button",
true,
TIMEOUT_DURATION,
);
replyButtons.forEach((button) => {
if(!button.classList.contains(INJECTED_REPLY_CLASS)){
button.addEventListener("click", onClickInteract);
button.classList.add(INJECTED_REPLY_CLASS);
}
});
} catch (error) {
// Interaction buttons failed to appear
console.log(error);
}
}
/** /**
* Initialise injection for Mastodon Follow button. * Initialise injection for Mastodon Follow button.
* *
* @returns {void} * @returns {void}
*/ */
async function init() { function init() {
await injectFollowButton(); const observer = new MutationObserver(() => {
Promise.allSettled([
injectFollowButton(),
injectInteractionButtons(),
]);
});
observer.observe(document.body, {
childList: true, subtree: true,
});
} }
init(); init();