Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: should now inject interaction buttons properly on post detail page #87

Merged
merged 7 commits into from
Dec 13, 2022
4 changes: 2 additions & 2 deletions src/background/modules/AutoRemoteFollow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
}
}
Expand Down
95 changes: 61 additions & 34 deletions src/content_script/mastodonInject.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use strict";

const TIMEOUT_DURATION = 20000;

/**
* Replacement onClick handler for Follow button.
*
Expand All @@ -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");
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -79,7 +81,7 @@ function waitForElement(selector, multiple = false, timeoutDuration) {
});

return null;
});
});
}

/**
Expand All @@ -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
Expand All @@ -103,46 +105,71 @@ 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
}


}

/**
* Initialise injection for all remote Mastodon buttons.
*
* @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']",
hueyy marked this conversation as resolved.
Show resolved Hide resolved
// 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();
init().catch(console.error);