Created
June 28, 2025 03:44
-
-
Save tatelax/287d10935bc34693228426a65552945d to your computer and use it in GitHub Desktop.
fxtwitter share button for X
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name fxtwitter Share Button for X (Styled + Snackbar + Hover) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2.0 | |
| // @description Adds a compact 'fxtwitter' button next to Share on X tweets with hover effect and clipboard notification. Clean layout, Safari-safe. | |
| // @author You | |
| // @match https://twitter.com/* | |
| // @match https://x.com/* | |
| // @grant none | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| const LABEL = "FX"; | |
| const TOOLTIP = "Copy fxtwitter link"; | |
| const TOAST_ID = "fxtwitter-toast"; | |
| function showToast(message) { | |
| let toast = document.getElementById(TOAST_ID); | |
| if (!toast) { | |
| toast = document.createElement("div"); | |
| toast.id = TOAST_ID; | |
| toast.style.position = "fixed"; | |
| toast.style.bottom = "24px"; | |
| toast.style.left = "50%"; | |
| toast.style.transform = "translateX(-50%)"; | |
| toast.style.background = "#333"; | |
| toast.style.color = "#fff"; | |
| toast.style.padding = "8px 16px"; | |
| toast.style.borderRadius = "999px"; | |
| toast.style.fontSize = "14px"; | |
| toast.style.zIndex = "9999"; | |
| toast.style.opacity = "0"; | |
| toast.style.transition = "opacity 0.3s ease"; | |
| toast.style.fontFamily = "inherit"; | |
| document.body.appendChild(toast); | |
| } | |
| toast.textContent = message; | |
| toast.style.opacity = "1"; | |
| setTimeout(() => { | |
| toast.style.opacity = "0"; | |
| }, 1800); | |
| } | |
| async function copyToClipboard(text) { | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| showToast("Copied fxtwitter link!"); | |
| } catch (err) { | |
| console.error("[fxtwitter] Clipboard error:", err); | |
| showToast("Failed to copy"); | |
| } | |
| } | |
| function createFXButton(tweet) { | |
| if (tweet.querySelector(".fxtwitter-button")) return; | |
| const shareBtn = [ | |
| ...tweet.querySelectorAll('button[aria-label*="Share post"]'), | |
| ].pop(); | |
| if (!shareBtn) return; | |
| const buttonGroup = shareBtn.closest('div[role="group"]'); | |
| if (!buttonGroup || !(buttonGroup instanceof Node)) return; | |
| const fxButton = document.createElement("button"); | |
| fxButton.className = shareBtn.className + " fxtwitter-button"; | |
| fxButton.setAttribute("role", "button"); | |
| fxButton.setAttribute("tabindex", "0"); | |
| fxButton.setAttribute("aria-label", TOOLTIP); | |
| fxButton.title = TOOLTIP; | |
| fxButton.type = "button"; | |
| // Base button style | |
| fxButton.style.all = "unset"; | |
| fxButton.style.cursor = "pointer"; | |
| fxButton.style.display = "flex"; | |
| fxButton.style.alignItems = "center"; | |
| fxButton.style.justifyContent = "center"; | |
| fxButton.style.height = "24px"; | |
| fxButton.style.borderRadius = "9999px"; | |
| fxButton.style.transition = "background-color 0.2s ease"; | |
| // Light/dark theme hover background | |
| const hoverBgLight = "rgba(0, 0, 0, 0.05)"; | |
| const hoverBgDark = "rgba(255, 255, 255, 0.1)"; | |
| const isDarkMode = () => { | |
| const bg = getComputedStyle(document.body).backgroundColor; | |
| return !bg || bg !== "rgb(255, 255, 255)"; | |
| }; | |
| fxButton.addEventListener("mouseenter", () => { | |
| fxButton.style.backgroundColor = isDarkMode() | |
| ? hoverBgDark | |
| : hoverBgLight; | |
| }); | |
| fxButton.addEventListener("mouseleave", () => { | |
| fxButton.style.backgroundColor = "transparent"; | |
| }); | |
| // Inner wrapper and label | |
| const wrapper = document.createElement("div"); | |
| wrapper.style.display = "flex"; | |
| wrapper.style.alignItems = "center"; | |
| wrapper.style.justifyContent = "center"; | |
| wrapper.style.height = "100%"; | |
| wrapper.style.padding = "0 4px"; | |
| const labelDiv = document.createElement("div"); | |
| labelDiv.setAttribute("dir", "ltr"); | |
| labelDiv.style.fontSize = "13px"; | |
| labelDiv.style.fontWeight = "500"; | |
| labelDiv.style.lineHeight = "1"; | |
| labelDiv.textContent = LABEL; | |
| wrapper.appendChild(labelDiv); | |
| fxButton.appendChild(wrapper); | |
| fxButton.addEventListener("click", (e) => { | |
| e.stopPropagation(); | |
| const link = tweet.querySelector('a[href*="/status/"]'); | |
| if (!link) return; | |
| const fullUrl = new URL(link.href, window.location.origin); | |
| const fxUrl = fullUrl.href.replace( | |
| /^(https:\/\/)(x|twitter)\.com/, | |
| "$1fxtwitter.com" | |
| ); | |
| copyToClipboard(fxUrl); | |
| }); | |
| buttonGroup.appendChild(fxButton); | |
| } | |
| function scanAndInject() { | |
| document | |
| .querySelectorAll('article[data-testid="tweet"]') | |
| .forEach(createFXButton); | |
| } | |
| window.addEventListener("load", () => { | |
| setTimeout(() => { | |
| scanAndInject(); | |
| const observer = new MutationObserver(scanAndInject); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| }, 1000); | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment