Tumblr Dashboard - clickable links to images and display time-stamps

Linkifies all images in the tumblr dashboard stream. The script also displays the time-stamp of each post in the upper right corner.

目前為 2018-10-27 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Tumblr Dashboard - clickable links to images and display time-stamps
// @namespace    tumblr_dashboard_linkify
// @version      1.2.0
// @license      GNU AGPLv3
// @description  Linkifies all images in the tumblr dashboard stream. 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/*
// @run-at document-end
// ==/UserScript==

function createImageLinks(myDoc, myContext) {

//console.info("createImageLinks: ", myContext);
  
  if (myDoc===null) myDoc= myContext;
  if (myDoc===null) return;
  if (myContext===null) myContext= myDoc;
  
  var matches;
  var tmpstr;
  var parentnode;
//  var newnode;

  matches=myDoc.evaluate("//div[contains(@class,'post_content')]//img[contains(@class,'post_media_photo') and (contains(@src,'.jpg') or contains(@src,'.png') or contains(@src,'.gif'))] | " +
                         "//div[contains(@class,'post_content')]//figure[contains(@class,'tmblr-full')]/img[contains(@src,'.jpg') or contains(@src,'.png') or contains(@src,'.gif')] | " +
                         "//div[contains(@class,'post_content')]//a[contains(@class,'photoset_photo')]/img[contains(@src,'.jpg') or contains(@src,'.png') or contains(@src,'.gif')]", 
                         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.getAttribute("src"), false);
//        console.info("highresurl: ", tmpstr);
        parentnode=el.parentNode;
//        console.info("parentnode: ", parentnode);
        if (parentnode.nodeName.toLowerCase() == "a") {
          if (parentnode.hasAttribute("class") &&
              (parentnode.getAttribute("class").indexOf("post_media_photo") >= 0 ||
               parentnode.getAttribute("class").indexOf("photoset_photo") >= 0) ) {
//            console.info("set parentnode href: ", tmpstr);
            parentnode.setAttribute("href", tmpstr); 
          }
          // if it is a link type but with other style classes -> do nothing
        } else {
          insertLinkElement(myDoc, el, tmpstr);
//          newnode = myDoc.createElement("a");
//          newnode.setAttribute("href", tmpstr);
//          newnode.setAttribute("target", "_blank");
//          console.info("newnode href: ", tmpstr);
//          parentnode.replaceChild(newnode, el);
//          newnode.appendChild(el);
//          console.info("new child of this parent: ", parentnode);
        } 
      } catch (e) { console.warn("error: ", e); }
    }
	}
}


function createImageLinksForBackground(myDoc, myContext) {

  if (myDoc===null) myDoc= myContext;
  if (myDoc===null) return;
  if (myContext===null) myContext= myDoc;
  
  var matches;
  var tmpstr;
  var parentnode;
  var orglink;

  matches=myDoc.evaluate("//div[contains(@class,'post_content')]//div[contains(@class,'post_media')]//a[contains(@class,'link-button') and contains(@class,'has-thumbnail')]/div[contains(@style,'background-image: url(') or contains(@style,'background-image:url(')]", 
                         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 = el.style.backgroundImage;
        if (!tmpstr.startsWith("url(")) continue;
				// strip leading url(' / url(" and trailing ') / ")
        tmpstr = tmpstr.substring(5, tmpstr.lastIndexOf(tmpstr.charAt(4)));
        tmpstr=getHighResImageURL(tmpstr, false);
//        console.info("highresurl: ", tmpstr);
        parentnode=el.parentNode; // this is a link(a) element
        orglink = parentnode.getAttribute("href");
        parentnode.removeAttribute("href"); //removing the href causes the a element to act like a div element
        insertLinkElement(myDoc, el, tmpstr); // linkify the div with the background image only (instead of the whole parent element)
        //Now create links to the original URL - but from smaller areas that do NOT overlay the whole image
        el = parentnode.querySelector("div.publisher-container");
        if (el !== null) {
          insertLinkElement(myDoc, el, orglink);
        }  
        el = parentnode.querySelector("div.title");
        if (el !== null) {
          insertLinkElement(myDoc, el, orglink);
        }  
        el = parentnode.querySelector("div.excerpt");
        if (el !== null) {
          insertLinkElement(myDoc, el, orglink);
        }  
      } 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 tmpstr;
  var elements;
  var headernode;
  var linknode;
  var pos;
  var newnode;
  var newnode2;

  matches=myDoc.evaluate("//div[contains(@class,'post_wrapper')]", 
                         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);
        elements = el.getElementsByClassName("post_header");
        if (elements.length <= 0) continue;
        headernode = elements[0];
        elements = el.getElementsByClassName("post_permalink");
        if (elements.length <= 0) continue;
        linknode = elements[0];
        if (!linknode.hasAttribute("title")) continue;
        tmpstr = linknode.getAttribute("title");
        pos = tmpstr.indexOf(" - ");
        if (pos >= 0) {
          tmpstr = tmpstr.substr(pos+3);
        }
        newnode = myDoc.createElement("div");
        newnode.setAttribute("class", "post_source");
        newnode2 = myDoc.createElement("div");
//        newnode2.setAttribute("class", "post_source_app");
        newnode2.setAttribute("style", "float: right; text-overflow: ellipsis; overflow: hidden;");
        newnode2.textContent = tmpstr;
        newnode.appendChild(newnode2);
        elements = headernode.getElementsByClassName("post_source");
        if (elements.length <= 0) {
          headernode.appendChild(newnode);
        } else {
          headernode.replaceChild(newnode, elements[0]);
        }
      } catch (e) { console.warn("error: ", e) }
    }
	}
}


function insertLinkElement(myDoc, wrapElement, linkTarget) {
	var newnode;
  var parentnode;
  
  newnode = myDoc.createElement("a");
  newnode.setAttribute("href", linkTarget);
  newnode.setAttribute("target", "_blank");
  parentnode = wrapElement.parentNode;
  parentnode.replaceChild(newnode, wrapElement);
  newnode.appendChild(wrapElement);
}


// 2018-08-09 - RAW (original upload) images do not seem to be working anymore
function getHighResImageURL(imageurl, isgetraw) {
  var result = imageurl;
  var tmplen = imageurl.length;
  var pos;
  if ((imageurl.toLowerCase().lastIndexOf(".jpg") == tmplen-4) ||
      (imageurl.toLowerCase().lastIndexOf(".png") == tmplen-4)) {
    if (isgetraw) {
      result = "https://s3.amazonaws.com/data" + 
               imageurl.slice(imageurl.indexOf(".tumblr.com/"), imageurl.lastIndexOf("_")) + 
               "_raw" + 
               imageurl.substring(imageurl.lastIndexOf("."));
    } else {
	    result = imageurl.replace("_250.","_1280.").replace("_400.","_1280.").replace("_500.","_1280.").replace("_540.","_1280.").replace("_640.","_1280.");
    }
  } 
  else if ((imageurl.toLowerCase().lastIndexOf(".gif") == tmplen-4)) {
    if (isgetraw) {
      result = "https://s3.amazonaws.com/data" + 
               imageurl.slice(imageurl.indexOf(".tumblr.com/"), imageurl.lastIndexOf("_")) + 
               "_raw" + 
               imageurl.substring(imageurl.lastIndexOf("."));
    } else {
	    result = imageurl.replace("_250.","_540.").replace("_400.","_540.").replace("_500.","_540.");
    }
  }
  return result;
}


// 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);
			createImageLinksForBackground(mutation.target.ownerDocument, addedNode);
      displayDateTime(mutation.target.ownerDocument, addedNode);
    });
  });    
});
 
// configuration of the observer
// NOTE: subtree is false as the wanted nodes are direct children of <ol id="posts"> -> notable performance improvement
var config = { attributes: false, childList: true, characterData: false, subtree: false };
 
// pass in the target node (<ol> element contains all dashboard posts), as well as the observer options
var postsmatch = document.evaluate("//ol[@id='posts']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
//console.info("postsmatch: ", postsmatch);
var postsnode = postsmatch.singleNodeValue;
//console.info("postsnode: ", postsnode);

//process already loaded nodes (the initial posts before scrolling down for the first time)
createImageLinks(document, postsnode);
createImageLinksForBackground(document, postsnode);
displayDateTime(document, postsnode);

//start the observer for new nodes
observer.observe(postsnode, config);