diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index efba3bf..b06a5be 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -155,8 +155,8 @@ 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({ - file: "/content_script/mastodonInject.js" + browser.tabs.executeScript(tabId, { + file: "/content_script/mastodonInject.js", }); } } diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index ee1c5d7..64a3572 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -1,7 +1,5 @@ "use strict"; -const TIMEOUT_DURATION = 20000; - /** * Replacement onClick handler for Follow button. * @@ -26,10 +24,14 @@ function onClickInteract(event) { event.stopPropagation(); event.preventDefault(); const articleElement = event.target.closest("article[data-id]"); + const getId = () => { + const rawId = articleElement.getAttribute("data-id"); + return rawId.slice(0, 2) === "f-" ? rawId.slice(2) : rawId; + }; const tootId = ( articleElement === null ? window.location.pathname.split("/").slice(-1)[0] - : articleElement.getAttribute("data-id") + : getId() ); // activate AutoRemoteFollow window.open(`/interact/${tootId}`, "_blank"); @@ -40,12 +42,12 @@ function onClickInteract(event) { * * @param {string} selector * @param {boolean} [multiple=false] - * @param {number} timeoutDuration + * @param {number} [timeoutDuration=200000] * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} * from which this was adapted * @returns {Promise} */ -function waitForElement(selector, multiple = false, timeoutDuration) { +function waitForElement(selector, multiple = false, timeoutDuration = 200000) { return new Promise((resolve, reject) => { const getElement = () => ( multiple @@ -59,14 +61,14 @@ function waitForElement(selector, multiple = false, timeoutDuration) { }, timeoutDuration); const element = getElement(); - if (isElementFound(element)){ + if (isElementFound(element)) { window.clearTimeout(timeout); return resolve(element); } const observer = new MutationObserver(() => { const element = getElement(); - if (isElementFound(element)){ + if (isElementFound(element)) { window.clearTimeout(timeout); resolve(element); observer.disconnect(); @@ -79,7 +81,7 @@ function waitForElement(selector, multiple = false, timeoutDuration) { }); return null; - }); + }); } /** @@ -89,7 +91,7 @@ function waitForElement(selector, multiple = false, timeoutDuration) { */ async function injectFollowButton() { try { - const followButton = await waitForElement("#mastodon .account__header__tabs__buttons button:first-of-type", false, TIMEOUT_DURATION); + const followButton = await waitForElement("#mastodon .account__header__tabs__buttons button:first-of-type", false); followButton.addEventListener("click", onClickFollow); } catch (error) { // Follow button failed to appear @@ -103,22 +105,30 @@ async function injectFollowButton() { */ async function injectInteractionButtons() { const INJECTED_REPLY_CLASS = "mastodon-simplified-federation-injected-interaction"; - const replyButtons = await waitForElement( - "#mastodon .item-list[role='feed'] article[data-id] .status__action-bar button," + - "#mastodon .detailed-status__wrapper .detailed-status__action-bar button", - true, - TIMEOUT_DURATION, - ); - replyButtons.forEach((button) => { - try { - if (!button.classList.contains(INJECTED_REPLY_CLASS)){ - button.addEventListener("click", onClickInteract); - button.classList.add(INJECTED_REPLY_CLASS); + const TIMELINE_SELECTOR = "#mastodon .item-list[role='feed'] article[data-id] .status__action-bar button"; // timeline / user profile + const STATUS_NO_REPLIES_SELECTOR = "#mastodon .detailed-status__wrapper .detailed-status__action-bar button"; // status with no replies + const STATUS_WITH_REPLIES_SELECTOR = "#mastodon .status__wrapper .status__action-bar button"; // status with replies + try { + const replyButtons = await waitForElement([ + TIMELINE_SELECTOR, + STATUS_NO_REPLIES_SELECTOR, + STATUS_WITH_REPLIES_SELECTOR, + ].join(","), true,); + replyButtons.forEach((button) => { + try { + if (!button.classList.contains(INJECTED_REPLY_CLASS)) { + button.addEventListener("click", onClickInteract); + button.classList.add(INJECTED_REPLY_CLASS); + } + } catch (error) { + // Failed to inject interaction buttons } - } catch (error) { - // Interaction buttons failed to appear - } - }); + }); + } catch (error) { + // Interaction buttons failed to appear + } + + } /** @@ -126,23 +136,40 @@ async function injectInteractionButtons() { * * @returns {void} */ +function initInjections() { + injectFollowButton().catch(console.error); + injectInteractionButtons().catch(console.error); +} + +/** + * Initialise script and re-run if there are changes. + * + * @returns {void} + */ async function init() { - injectFollowButton(); + const MASTODON_INJECTED_CLASS = "mastodon-simplified-federation-injected"; + + if (document.body.classList.contains(MASTODON_INJECTED_CLASS)) { + // init has already run + return; + } + + document.body.classList.add(MASTODON_INJECTED_CLASS); + initInjections(); const observer = new MutationObserver(() => { - Promise.allSettled([ - injectInteractionButtons(), - ]); + initInjections(); }); - const feedElement = await waitForElement( - "#mastodon .item-list[role='feed']", + // monitor only the main column in the Mastodon UI + const mainColumn = await waitForElement( + "#mastodon .ui", false, - TIMEOUT_DURATION ); - observer.observe(feedElement, { - childList: true, subtree: true, + observer.observe(mainColumn, { + childList: true, + subtree: true, }); } -init(); \ No newline at end of file +init().catch(console.error); \ No newline at end of file