Skip to content

Instantly share code, notes, and snippets.

@eniehack
Last active October 23, 2024 04:38
Show Gist options
  • Save eniehack/317db91fd3d09aa66215ad4b3ec55f41 to your computer and use it in GitHub Desktop.
Save eniehack/317db91fd3d09aa66215ad4b3ec55f41 to your computer and use it in GitHub Desktop.
Mastodon v4.3.0に伴うdeck UI の破壊的変更に https://gist.github.com/eniehack/59bc3e55170c8869b9fbffbdeaf29ad1 が対応できなくなったのでここに対応版を置きます。
// SPDX-License-Identifier: MIT
// ==UserScript==
// @name Mastodonに引用ボタンを追加する
// @name:en Add button to copy toot's url
// @name:ja Mastodonに引用ボタンを追加する
// @namespace http://www.eniehack.net/~eniehack/works/firefox-userscripts
// @version 0.3.0
// @description:en Add button to copy toot's url for quote toot on Mastodon's deck UI
// @description:ja MastodonのDeck UIにtootを引用するためのURLコピーボタンをboostボタンの隣に追加します。
// @author eniehack
// @license MIT
// @match https://fedibird.com/deck/*
// @match https://best-friends.chat/deck/*
// @match https://mstdn.jp/deck/*
// @match https://pawoo.net/deck/*
// @grant GM.setValue
// @grant GM.getValue
// @description MastodonのDeck UIにtootを引用するためのURLコピーボタンをboostボタンの隣に追加します。
// @downloadURL https://update.greasyfork.org/scripts/496161/Mastodon%E3%81%AB%E5%BC%95%E7%94%A8%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B.user.js
// @updateURL https://update.greasyfork.org/scripts/496161/Mastodon%E3%81%AB%E5%BC%95%E7%94%A8%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B.meta.js
// ==/UserScript==
/*jshint esversion: 8 */
(async () => {
const quoteSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-quote" viewBox="0 0 16 16">
<path d="M12 12a1 1 0 0 0 1-1V8.558a1 1 0 0 0-1-1h-1.388q0-.527.062-1.054.093-.558.31-.992t.559-.683q.34-.279.868-.279V3q-.868 0-1.52.372a3.3 3.3 0 0 0-1.085.992 4.9 4.9 0 0 0-.62 1.458A7.7 7.7 0 0 0 9 7.558V11a1 1 0 0 0 1 1zm-6 0a1 1 0 0 0 1-1V8.558a1 1 0 0 0-1-1H4.612q0-.527.062-1.054.094-.558.31-.992.217-.434.559-.683.34-.279.868-.279V3q-.868 0-1.52.372a3.3 3.3 0 0 0-1.085.992 4.9 4.9 0 0 0-.62 1.458A7.7 7.7 0 0 0 3 7.558V11a1 1 0 0 0 1 1z"/>
</svg>`
const insertBefore = (newNode, existingNode) => {
existingNode.parentNode.insertBefore(newNode, existingNode.previousSibling);
};
const createQuoteButtonElement = () => {
const qtBtn = document.createElement("button");
qtBtn.setAttribute(
"class",
"status__action-bar__button icon-button quote-icon",
);
qtBtn.setAttribute("type", "button");
qtBtn.setAttribute(
"style",
"font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 18px;",
);
qtBtn.setAttribute("aria-label", "quote");
qtBtn.setAttribute("aria-hidden", "false");
qtBtn.setAttribute("title", "quote");
qtBtn.setAttribute("tabindex", "0");
qtBtn.innerHTML = quoteSvg;
/*const quoteIcon = document.createElement("img");
quoteIcon.setAttribute("src", "https://icons.getbootstrap.com/assets/icons/quote.svg");
quoteIcon.setAttribute("aria-hidden", "true");
qtBtn.appendChild(quoteIcon); */
return qtBtn;
};
const fetchPostId = (target) => {
const status = target.querySelector("div.status")
return status.attributes.getNamedItem("data-id").value;
};
const generateText = async (url) => {
const tmpl = await GM.getValue("template", `{{url}}`);
return tmpl.replace("{{url}}", url);
};
const copyText = (text) => {
navigator.clipboard.writeText(text);
};
const insertQuoteButton = (targetArticle) => {
const target = targetArticle.querySelector(
".status__action-bar__button-wrapper"
);
const qtbtn = createQuoteButtonElement();
// console.log(targetArticle)
const postId = fetchPostId(targetArticle);
const textareaElem = document.querySelector(
"textarea.autosuggest-textarea__textarea",
);
qtbtn.onclick = () => {
fetch(`https://${location.host}/api/v1/statuses/${postId}`)
.then((res) => res.json())
.then((json) => json.url)
.then((url) => generateText(url))
.then((txt) => copyText(txt));
};
insertBefore(qtbtn, target);
};
const callback = (entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
insertQuoteButton(entry.target);
} else {
const quotebtn = entry.target.querySelector(".quote-icon");
if (quotebtn === null) continue;
quotebtn.remove();
}
}
};
const mutationObservers = [];
setTimeout(
() => {
//console.debug(target);
const mutationObserverTargets = document.querySelectorAll(
"div.column > div.scrollable > div.item-list",
);
const io = new IntersectionObserver(callback);
const mutationObserverConfig = {
attributes: false,
childList: true,
subtree: false,
};
mutationObserverTargets.forEach((target) => {
const mo = new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
if (0 < mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node.tagName !== "ARTICLE") continue;
io.observe(node);
}
}
if (0 < mutation.removedNodes.length) {
mutation.removedNodes.forEach((node) => {
io.unobserve(node);
});
}
//console.log(mutation);
}
});
mo.observe(target, mutationObserverConfig);
mutationObservers.push(mo);
});
//console.debug(articles.length);
const articles = document.querySelectorAll("article");
articles.forEach((article) => {
io.observe(article);
});
},
await GM.getValue("insert_before_second", 3000),
);
document.addEventListener("unload", () => {
mutationObservers.forEach((mo) => mo.disconnect());
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment