您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a collapsible toggle to posts in your notifications for media
// ==UserScript== // @name Mastodon - Collapse Media in Notifications By Default // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description Adds a collapsible toggle to posts in your notifications for media // @author LupoMikti // @license MIT // @match https://mastodon.social/* // @match https://mstdn.jp/* // @match https://mastodon.art/* // @match https://pawoo.net/* // @match https://baraag.net/* // @icon https://www.google.com/s2/favicons?sz=64&domain=mastodon.social // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; let css = `.media-toggle { margin-top: 10px; margin-inline-start: 48px; width: calc(100% - 48px); & span { cursor: pointer; } } .notification-ungrouped:not(.notification-ungrouped--unread) .media-toggle span { color: #606984; filter: brightness(1.5); } article:not([data-toggle-open]) .notification-ungrouped .status > .media-gallery, article:not([data-toggle-open]) .notification-ungrouped .status > .media-gallery__item, article:not([data-toggle-open]) .notification-ungrouped .status div:has(.video-player), article[data-toggle-open="false"] .notification-ungrouped .status > .media-gallery, article[data-toggle-open="false"] .notification-ungrouped .status > .media-gallery__item, article[data-toggle-open="false"] .notification-ungrouped .status div:has(.video-player) { display: none; } .notification-ungrouped .status-card.expanded .status-card__image { visibility: collapse; } ` // This waits until we have the notifications response JSON then makes the script wait 1 second more for all DOM changes to take place var origOpen = XMLHttpRequest.prototype.open XMLHttpRequest.prototype.open = function(method, url) { this.addEventListener('load', function() { // console.log('XHR finished loading', method, this.status, url); if (url.includes('api/v2/notifications?') && window.location.pathname.includes(`/notifications`)) { return setTimeout(init, 1000) } }) this.addEventListener('error', function() { console.log('XHR errored out', method, url) }) origOpen.apply(this, arguments) } window.addEventListener('load', () => { let oldHref = document.location.href const bodyObserver = new MutationObserver((mutationList) => { if (oldHref !== document.location.href) { oldHref = document.location.href refreshScript() } }) bodyObserver.observe(document.body, {childList: true, subtree: true}) }) // We cannot inject a style element without the nonce, so get it and use it let styleNonce = document.querySelector('meta[name=style-nonce]').content document.head.insertAdjacentHTML('beforeend',`<style id="notif-media-toggle-css" nonce="${styleNonce}">${css}</style>`) let notificationColumn window.addEventListener('popstate', refreshScript) function init() { notificationColumn = document.querySelector('.column[aria-label="Notifications"] .item-list') let initialPostsWithMedia = document.querySelectorAll(`.item-list article[style=""] .notification-ungrouped .status > .media-gallery, .item-list article[style=""] .notification-ungrouped .status > .media-gallery__item, .item-list article[style=""] .notification-ungrouped .status div:has(.video-player), .item-list article:not(article[style]) .notification-ungrouped .status > .media-gallery, .item-list article:not(article[style]) .notification-ungrouped .status > .media-gallery__item, .item-list article:not(article[style]) .notification-ungrouped .status div:has(.video-player)`) initialPostsWithMedia.forEach((mediaSection) => {insertToggle(mediaSection)}) startObserving() } function refreshScript() { if (!document.location.pathname.includes(`/notifications`)) return return setTimeout(init, 500) } function getAncestorWithDataId(node) { while (node && Array.from(node.attributes).every(attr => attr.name !== 'data-id')) { node = node.parentNode } return node } // Inserts the toggle span into a post function insertToggle(mediaSection, parentElementId = null, wasKeptOpen = 'false') { if (!mediaSection) return if (mediaSection.parentNode.querySelector('.media-toggle')) return const showingMedia = (wasKeptOpen === 'true') if (!parentElementId) { parentElementId = getAncestorWithDataId(mediaSection)?.getAttribute('data-id') } if (!parentElementId) { console.error("Failed to retrieve the element with the corresponding data-id") return } const targetSelector = mediaSection.className ? mediaSection.className.split(' ').map(name => `.${name}`).join('') : 'div:has(>.video-player)' mediaSection.insertAdjacentHTML('beforebegin', `<div class="media-toggle" data-toggle-target="[data-id='${parentElementId}'] ${targetSelector}"><span>Click to ${showingMedia ? 'hide' : 'show'} media</span></div>`) mediaSection.parentNode.querySelector('.media-toggle').addEventListener('click', doToggle) } // Click handler for the toggle span function doToggle(event) { if(event.target.nodeName !== 'SPAN') return let toggle = event.currentTarget let toggleTargetSelector = toggle?.getAttribute('data-toggle-target') let parentElementSelector = toggleTargetSelector?.split(' ')[0] let toggleTarget = document.querySelector(toggleTargetSelector) let parentElement = document.querySelector(parentElementSelector) if (!toggleTarget.checkVisibility()) { parentElement.setAttribute('data-toggle-open', 'true') toggleTarget.style = toggleTarget.style.cssText + " display: grid;" toggle.childNodes[0].innerText = "Click to hide media" } else { parentElement.setAttribute('data-toggle-open', 'false') toggleTarget.style = toggleTarget.style.cssText.replace(" display: grid;", "") toggle.childNodes[0].innerText = "Click to show media" } } // Mutation observer needed because page dynamicaly hides + removes/adds post content as you scroll function startObserving() { const observeColumn = (mutationList) => { for (const mutation of mutationList) { if (mutation.type === 'attributes') { const article = mutation.target if (article.nodeName !== 'ARTICLE') continue // if the target is not an article element skip if (!mutation.oldValue) continue // if the target's old attribute value is empty, skip if (article.style.cssText) continue // if the target article's style attribute is NOT being changed to empty, skip insertToggle(article.querySelector('.notification-ungrouped .status > .media-gallery, .notification-ungrouped .status > .media-gallery__item, .notification-ungrouped .status div:has(.video-player)'), article.getAttribute('data-id'), article.getAttribute('data-toggle-open') || 'false') } if (mutation.type === 'childList') { const parent = mutation.target let movedNodes = [] if (mutation.addedNodes.length > 0) { movedNodes = Array.from(mutation.addedNodes) if (!(parent.matches('div.status') && parent.hasAttribute('data-id') && movedNodes.some(node => node.matches('.media-gallery, .media-gallery__item, div:has(.video-player)')))) continue // if the target is not the parent of the media container we want, skip if (parent.hasAttribute('data-toggle-open') && parent.getAttribute('data-toggle-open') === 'true') { let mediaNode = movedNodes.find(node => node.matches('.media-gallery, .media-gallery__item, div:has(.video-player)')) mediaNode.style = mediaNode.style.cssText + " display: grid;" } insertToggle(parent.querySelector('.media-gallery, .media-gallery__item, div:has(.video-player)'), parent.getAttribute('data-id'), parent.getAttribute('data-toggle-open') || 'false') } if (mutation.removedNodes.length > 0) { movedNodes = Array.from(mutation.removedNodes) if (!(parent.matches('div.status') && parent.hasAttribute('data-id') && movedNodes.some(node => node.matches('.media-gallery, .media-gallery__item, div:has(.video-player)')))) continue // if the target is not the parent of the media container we want, skip parent.querySelector('.media-toggle').replaceWith() } } } } const observer = new MutationObserver(observeColumn) observer.observe(notificationColumn, { subtree: true, attributeFilter: ["style"], attributeOldValue: true }) const observer2 = new MutationObserver(observeColumn) observer2.observe(notificationColumn, { subtree: true, childList: true }) } })();