-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathmastodonInject.js
148 lines (133 loc) · 4.06 KB
/
mastodonInject.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"use strict";
const TIMEOUT_DURATION = 20000;
/**
* Replacement onClick handler for Follow button.
*
* @param {Event} event
* @returns {void}
*/
function onClickFollow(event) {
event.stopPropagation();
event.preventDefault();
const username = window.location.pathname.split("/").slice(-1)[0];
// activate AutoRemoteFollow
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.
*
* @param {string} selector
* @param {boolean} [multiple=false]
* @param {number} timeoutDuration
* @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) {
return new Promise((resolve, reject) => {
const getElement = () => (
multiple
? document.querySelectorAll(selector)
: document.querySelector(selector)
);
const isElementFound = (el) => (!multiple && el) || (multiple && el.length > 0);
const timeout = window.setTimeout(() => {
reject(new Error("waitForElement timed out"));
}, timeoutDuration);
const element = getElement();
if (isElementFound(element)){
window.clearTimeout(timeout);
return resolve(element);
}
const observer = new MutationObserver(() => {
const element = getElement();
if (isElementFound(element)){
window.clearTimeout(timeout);
resolve(element);
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
return null;
});
}
/**
* Inject replacement onClick handler for Follow button.
*
* @returns {void}
*/
async function injectFollowButton() {
try {
const followButton = await waitForElement("#mastodon .account__header__tabs__buttons button:first-of-type", false, TIMEOUT_DURATION);
followButton.addEventListener("click", onClickFollow);
} catch (error) {
// 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";
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);
}
} catch (error) {
// Interaction buttons failed to appear
}
});
}
/**
* Initialise injection for all remote Mastodon buttons.
*
* @returns {void}
*/
async function init() {
injectFollowButton();
const observer = new MutationObserver(() => {
Promise.allSettled([
injectInteractionButtons(),
]);
});
const feedElement = await waitForElement(
"#mastodon .item-list[role='feed']",
false,
TIMEOUT_DURATION
);
observer.observe(feedElement, {
childList: true, subtree: true,
});
}
init();