// ==UserScript== // @name /b/ackwash revolutions // @namespace http://www.4chan.org // @description Add tooltips to 4chan quotes (>>). // @author tkirby, aeosynth, VIPPER // @include *.4chan.org/* // @include *suptg.thisisnotatrueending.com/archive/* // @exclude *dis.4chan.org/* // @version 0.21 // @grant GM_xmlhttpRequest // @downloadURL https://update.greasyfork.icu/scripts/5850/backwash%20revolutions.user.js // @updateURL https://update.greasyfork.icu/scripts/5850/backwash%20revolutions.meta.js // ==/UserScript== const TIP_X_OFFSET = 40; // in pixels const BACKLINKS = true; // 4chan imageboards only const ISOP = ">>POST (OP)"; // style for links to OP const ISOUT = ">>>POST"; // style for links to other threads const ISBACK = ">>POST"; // style for backlinks const SEPARATOR = " "; // backlink separator (a single space) function bw_wash(post) { qts = d.evaluate(".//a[contains(@class, 'quotelink')][starts-with(text(), '>>')]", post, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); backlinkid = ""; for(var i = 0, qt = null; qt = qts.snapshotItem(i++); ) { id = qt.toString().split("#")[1]; if(/>>\/(rs|\w+\/([a-zA-Z]+|$))/i.test(qt.textContent) || /dis.4chan.org/i.test(qt.href)) { // >>/rs || >>/board/text || >>/board/ || /textboard/ continue; } else if(id == "p" + op) { qt.textContent = ISOP.replace("POST", id.replace("p", "")); } else if(!d.getElementById(id)) { getPost(qt.toString()); out = qt.textContent.match(/\/.+\//) || ""; qt.textContent = ISOUT.replace("POST", id.replace("p", out)); } if(BACKLINKS) backlink(qt, id); qt.addEventListener("mouseover", function(e) { bw_show(e, this) }, false); qt.addEventListener("mousemove", function(e) { bw_track(e) }, true); qt.addEventListener("mouseout", function() { bw_hide() }, false); qt.addEventListener("mousedown", function() { bw_hide() }, false); } } function bw_hide() { bw_tipcell.innerHTML = ""; bw_tooltip.style.setProperty("display", "none", "important"); } function bw_show(e, me) { id = me.hash.split("#")[1]; posthtml = ""; try { posthtml = d.getElementById(id).innerHTML; } catch(err) { posthtml = checkCache(id, me); } // Code cleaning: input, id, [Reply] posthtml = posthtml.replace(//ig, ""); posthtml = posthtml.replace(/.id="[^"]+"/ig, ""); bw_tipcell.innerHTML = posthtml; reply = d.evaluate(".//a[text()='Reply']", bw_tipcell, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(reply && reply.parentNode) reply.parentNode.parentNode.removeChild(reply.parentNode); // Marks the backwashed link backpost = d.evaluate(".//a[contains(@href, '" + (e.target.parentNode.parentNode.id) + "')]", bw_tipcell, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(backpost) backpost.style.setProperty("text-decoration-style", "double", "important"); // Fix OP if(/^ vp_height) tip_y_offset = window.scrollY; else if(e.pageY + tip_height >= vp_bottom) tip_y_offset = vp_bottom - bw_tooltip.clientHeight; else tip_y_offset = e.pageY - tip_height; bw_tooltip.style.top = tip_y_offset + "px"; if(e.pageX + TIP_X_OFFSET > vp_width * 0.6) { bw_tooltip.style.right = vp_width + TIP_X_OFFSET - e.pageX + "px"; bw_tooltip.style.left = ""; } else { bw_tooltip.style.left = e.pageX + TIP_X_OFFSET + "px"; bw_tooltip.style.right = ""; } } function backlink(post, id) { // CLUSTERFUCK if(!d.getElementById(id)) return; var nid = d.evaluate(".//div[contains(@class, 'postInfo')][last()]", d.getElementById(id), null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; var pid = d.evaluate("ancestor::div[@id][1]/@id", post, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent; if(new RegExp("#" + pid + "\"").test(nid.innerHTML) || !/[0-9]/.test(pid)) return; link = d.createElement("A"); link.setAttribute("href", "#" + pid); // (/\/res\//.test(window.location) ? "" : "res/") + post.toString().match(/[0-9]+#(?=p[0-9]+)/) + pid link.setAttribute("onclick", "replyhl('" + pid.replace("p", "") + "')"); link.setAttribute("style", "font-size: smaller"); link.textContent = ISBACK.replace("POST", pid.replace("p", "")); link.addEventListener("mouseover", function(e) { bw_show(e, this) }, false); link.addEventListener("mousemove", function(e) { bw_track(e) }, true); link.addEventListener("mouseout", function() { bw_hide() }, false); link.addEventListener("mousedown", function() { bw_hide() }, false); nid.appendChild(d.createTextNode(SEPARATOR)); nid.appendChild(link); } function checkCache(id, url) { if(!id) id = "p" + url.pathname.match(/[0-9]+_?[0-9]+/); for(var i in cache) { if(url.href.split("#")[0] == cache[i].url) { var responseText = cache[i].post; if(responseText == "Loading post") return ("

Loading post #" + id + "...

"); var expr = new RegExp("
"); responseText = responseText.match(expr); responseText = responseText ? responseText[0] : "

Error: Post not found

"; return responseText; } } return "

Error: Post not found

"; // Should never happen } function getPost(id) { cached = false; link = id.split("#")[0]; for(var i in cache) if(link == cache[i].url) { // force cached entry to update on new cross-links cached = true; cache[i].post = "Loading post"; } if(!cached) cache.push({ url : link, post : "Loading post" }); GM_xmlhttpRequest({ method : "GET", url : link, headers: { "Accept" : "application/xml" }, onload : function(response) { for(var i in cache) if(id.split("#")[0] == cache[i].url) cache[i].post = response.responseText; } }); } const d = document; var op = window.location.pathname.match(/thread\/([0-9]+)/) || window.location.search.match(/[0-9]+(?:$|#)/) || ""; if(op[1]) op = op[1]; var cache = []; var bw_tipcell = d.createElement("DIV"); bw_tipcell.setAttribute("id", "bw_tipcell"); bw_tipcell.setAttribute("class", "post reply preview"); bw_tipcell.setAttribute("style", "padding: 5px; margin: 0px; border-width: 1px !important; opacity: inherit !important"); var bw_tooltip = d.createElement("DIV"); bw_tooltip.setAttribute("id", "bw_tooltip"); bw_tooltip.setAttribute("class", "postContainer"); bw_tooltip.setAttribute("style", "display: none; position: absolute; margin: 0px 1px; padding: 0px"); bw_tooltip.appendChild(bw_tipcell); d.body.appendChild(bw_tooltip); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { for(var i = 0, newpost = null; newpost = mutation.addedNodes[i++]; ) if(newpost.nodeName.toLowerCase() == "div") bw_wash(newpost); }); }); observer.observe(d.body, { childList : true, subtree : true }); window.onload = function() { var posts = d.evaluate(".//blockquote[contains(@class, 'postMessage')]", d.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var i = 0, post = null; post = posts.snapshotItem(i++); ) bw_wash(post); }