您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
NEW Tumblr UI - READ DESCRIPTION. Linkifies all images in the tumblr dashboard and side-view streams. The script also displays the time-stamp of each post in the upper right corner.
当前为
// ==UserScript== // @name Tumblr Dashboard - clickable links to images and display time-stamps // @namespace tumblr_dashboard_linkify // @version 3.1 // @license GNU AGPLv3 // @description NEW Tumblr UI - READ DESCRIPTION. Linkifies all images in the tumblr dashboard and side-view streams. The script also displays the time-stamp of each post in the upper right corner. // @author marp // @homepageURL https://greasyfork.org/en/users/204542-marp // @include https://www.tumblr.com/dashboard // @include https://www.tumblr.com/dashboard/* // @include https://www.tumblr.com/likes // @include https://www.tumblr.com/likes/* // @include https://*.media.tumblr.com/*.png // @include https://*.media.tumblr.com/*.gif // @include https://*.media.tumblr.com/*.jpg // @include https://*.media.tumblr.com/*.webp // @run-at document-end // ==/UserScript== function nsResolver(prefix) { if (prefix === 'svg') { return 'http://www.w3.org/2000/svg'; } else { return null; } } function doNothing_tumblr_dashboard_linkify(event) { e.preventDefault(); return false; } function insertLinkElement(myDoc, wrapElement, linkTarget) { var newnode; var parentnode; newnode = myDoc.createElement("a"); newnode.setAttribute("href", linkTarget); newnode.setAttribute("target", "_blank"); newnode.addEventListener("click", doNothing_tumblr_dashboard_linkify, true); parentnode = wrapElement.parentNode; parentnode.replaceChild(newnode, wrapElement); newnode.appendChild(wrapElement); } function getHighResImageURL(imageElement) { var srcarray; var tmpstr; srcarray = imageElement.getAttribute("srcset").split(","); // QUICK AND DIRTY - assume largest image is the last in array... seems to be true for Tumblr... but might change... tmpstr = srcarray[srcarray.length-1].trim(); return tmpstr.substring(0, tmpstr.indexOf(" ")); } function createImageLinks(myDoc, myContext) { if (myDoc===null) myDoc= myContext; if (myDoc===null) return; if (myContext===null) myContext= myDoc; var matches; var tmpstr; // the img might be added as part of a whole pos (first expr) - or just the img or the div/img, in which case we need to check if the image is part of the correct hierarchy (second expr) matches=myDoc.evaluate(".//article[@aria-label]//button[@aria-label]//figure/div/img[@role='img' and @srcset and @sizes]" + " | " + "self::img[@role='img' and @srcset and @sizes and parent::div/parent::figure/ancestor::button[@aria-label]/ancestor::article[@aria-label]]" + " | " + "self::div/img[@role='img' and @srcset and @sizes and parent::div/parent::figure/ancestor::button[@aria-label]/ancestor::article[@aria-label]]", myContext, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0, el; (i<matches.snapshotLength); i++) { el=matches.snapshotItem(i); if (el) { try { // console.info("matched-element: ", el); tmpstr=getHighResImageURL(el); insertLinkElement(myDoc, el.parentNode, tmpstr); } catch (e) { console.warn("error: ", e); } } } } function displayDateTime(myDoc, myContext) { if (myDoc===null) myDoc= myContext; if (myDoc===null) return; if (myContext===null) myContext= myDoc; var matches; var singlematch; var singlenode; var moreoptionsnode; var permalinknode; var tmpstr; var newnode; var pos; matches=myDoc.evaluate(".//article[@aria-label]//header[@role='banner']", myContext, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0, el; (i<matches.snapshotLength); i++) { el=matches.snapshotItem(i); if (el) { try { //console.info("matched-element: ", el); // find the grandchild div that contains the "More Options" Ellipsis button // make sure that this div is the last child (if it is not then this script might have run already) singlematch = myDoc.evaluate("((./div/div)[last()])[.//button//svg:svg]", el, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null); singlenode = singlematch.singleNodeValue; if (singlenode) { moreoptionsnode = singlenode; // find the permalink element - this has the timestamp as the title attribute (mouseover popup) singlematch = myDoc.evaluate("./a[@role='button' and @target='_blank' and @title]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); singlenode = singlematch.singleNodeValue; if (singlenode) { permalinknode = singlenode; // get the class attrib of the "more options" div - we'll create a second div with same class tmpstr = moreoptionsnode.getAttribute("class"); // create new node with the timestamp text newnode = myDoc.createElement("div"); newnode.setAttribute("class", tmpstr); newnode.setAttribute("style", "font-size: 85%"); newnode.setAttribute("displaytimestampscript", "1"); // flag that this node was added my this script tmpstr = permalinknode.getAttribute("title"); pos = tmpstr.lastIndexOf(" - "); if (pos >= 0) { tmpstr = tmpstr.substring(pos+3); } newnode.textContent = tmpstr; moreoptionsnode.parentNode.appendChild(newnode); } } } catch (e) { console.warn("error: ", e); } } } } function removeImageHtmlCrap(myDoc, myContext) { if (myDoc===null) myDoc= myContext; if (myDoc===null) return; if (myContext===null) myContext= myDoc; var imgurl_full; var imgurl_match; var partialurl; var singlematch; var singlenode; var sib; var vsize; imgurl_full = window.location.href; // this part of the URL isd the same for all available sizes of the image partialurl = imgurl_full.match(/https?:\/\/[^/]+\.tumblr\.com\/[^/]+\//i); if (partialurl) { imgurl_match = partialurl[0]; singlematch = myDoc.evaluate("./descendant-or-self::img[contains(@srcset,'" + imgurl_match + "') or contains(@src,'" + imgurl_match + "')]", myContext, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); singlenode = singlematch.singleNodeValue; if (singlenode) { // modify the image to use the largest available size varient (which is equal to the page URL!) // change several styles so that the image fits into the available viewport space singlenode.parentNode.setAttribute("style", "padding: 0px;"); sib = singlenode.previousElementSibling; //this is thr blog title (if available) if (sib) { vsize = sib.clientHeight; } else { vsize = 0; } if (singlenode.hasAttribute("srcset")) { singlenode.removeAttribute("srcset"); singlenode.removeAttribute("sizes"); } singlenode.setAttribute("src", imgurl_full); singlenode.setAttribute("style", "max-width: 99vw; max-height: calc(99vh - " + vsize +"px);"); singlenode.removeAttribute("class"); } //removce all DIVs that are unrelated to the image as well as to the blog title (which appears right above the image) matches = myDoc.evaluate("./descendant-or-self::div[not( ./descendant-or-self::img["+ "(contains(@srcset,'" + imgurl_match + "') or contains(@src,'" + imgurl_match + "'))] )"+ "and "+ "not( ./ancestor-or-self::div/child::img["+ "(contains(@srcset,'" + imgurl_match + "') or contains(@src,'" + imgurl_match + "'))] ) ]", myContext, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0, el; (i<matches.snapshotLength); i++) { el=matches.snapshotItem(i); if (el) { try { el.remove(); } catch (e) { console.warn("error: ", e); } } } } } if ( window.location.href.includes('.media.tumblr.com/') ) { // special part of script - acting only on direct image URLs to remove the HTML-crap injected by Tumblr // create an observer instance and iterate through each individual new node var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(addedNode) { removeImageHtmlCrap(mutation.target.ownerDocument, addedNode.parentNode); }); }); }); // configuration of the observer var config = { attributes: false, childList: true, characterData: false, subtree: true }; // new twitter UI has few stable IDs - need to start very high with "root" node var singlematch = document.evaluate("//body[@id='tumblr']/div[@id='root']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); //console.info("singlematch: ", singlematch); var rootnode = singlematch.singleNodeValue; //start the observer for new nodes observer.observe(rootnode, config); //process already loaded nodes (the initial posts before scrolling down for the first time) removeImageHtmlCrap(document, rootnode); } else { // this is "normal" part of script - acting on anything except direct image URLs // create an observer instance and iterate through each individual new node var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(addedNode) { createImageLinks(mutation.target.ownerDocument, addedNode); displayDateTime(mutation.target.ownerDocument, addedNode); }); mutation.removedNodes.forEach(function(removedNode) { // TUMBLR - OH NO YOU DON'T !! if (removedNode.hasAttribute("displaytimestampscript")) { mutation.target.insertBefore(removedNode, mutation.nextSibling); } }); }); }); // configuration of the observer var config = { attributes: false, childList: true, characterData: false, subtree: true }; // new twitter UI has few stable IDs - need to start very high with "root" node var singlematch = document.evaluate("//body[@id='tumblr']/div[@id='root']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); //console.info("singlematch: ", singlematch); var rootnode = singlematch.singleNodeValue; //start the observer for new nodes observer.observe(rootnode, config); //process already loaded nodes (the initial posts before scrolling down for the first time) createImageLinks(document, rootnode); displayDateTime(document, rootnode); }