您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Puts embedded quotes in end of post rather than beginning, can also click on checkboxes in posts to remove them, allows for expand all/collapse all via arrows in post header, can tessellate inlined quotes in post
当前为
// ==UserScript== // @name 4chan embedded quote fixer // @namespace http://tampermonkey.net/ // @version 0.3 // @description Puts embedded quotes in end of post rather than beginning, can also click on checkboxes in posts to remove them, allows for expand all/collapse all via arrows in post header, can tessellate inlined quotes in post // @author You // @match http://boards.4chan.org/*/thread/* // @match https://boards.4chan.org/*/thread/* // @grant none // ==/UserScript== //window.onload(function() { setTimeout(function() { let quotesList = Array.from(document.getElementsByClassName("quotelink")).filter(node => window.getComputedStyle(node).getPropertyValue("font-size") === "10.6667px"); quotesList.forEach(node => { node.addEventListener("click", qEV); }); Array.from(document.getElementsByTagName("span")).filter(el => { try { if (el.children.length > 0 && el.children[0].classList.contains("quotelink")) return true; } catch(error) { //console.log("custom error in span EL", el); return false; } }).forEach(el => el.addEventListener("click", qSpanClick)); addRemoveCapability(); addCollapseAndExpand(); addTessellationQuotes(); addQuotedPostsContainer(); }, 3000); //}); //tessellation feature - move element all the way to the right, try and move up - if interfering with getBoundingClientRect in two axes, then move left, then try and move up again - repeat until both up and left are exhausted //repeat for all posts - tesselation would not just add display: flex and flex-wrap:wrap to thread container, but also margin/-margin to each individual post - can either add some caching capability by storing data on //dome nodes themselves, or just store all of the HTML before, revert on option, and re-compute each time, possibly also add to barchive, also add for within posts themselves //image size slider - a slider appears when hovering or clicking over new span in image data row in post - has a slider, and position on slider determines the size of the full image via % function addTessellationQuotes() { Array.from(document.getElementsByClassName("postInfo")).forEach(el => { if (el.getElementsByClassName("tessellateQuotes").length === 0) { let btn = el.querySelector(".postMenuBtn"); let tesSpan = document.createElement("span"); tesSpan.innerText = "[Tessellate]"; tesSpan.classList.add("tessellateQuotes"); tesSpan.style.paddingLeft = "6px"; tesSpan.style.paddingRight = "1px"; tesSpan.style.fontSize = "11px"; tesSpan.style.color = "rgb(46, 54, 144)"; el.insertBefore(tesSpan, btn); tesSpan.removeEventListener("click", tessellateQuotes); tesSpan.addEventListener("click", tessellateQuotes); } }); } function tessellateQuotes(e) { let tesSpan = e.target; let quotesNode = e.target.parentNode.parentNode.querySelector(".quotedPostsContainer"); if (quotesNode.style.display !== "flex") { quotesNode.style.display = "flex"; quotesNode.style.flexWrap = "wrap"; tesSpan.style.opacity = "0.55"; } else { quotesNode.style.display = ""; quotesNode.style.flexWrap = ""; tesSpan.style.color = "rgb(46, 54, 144)"; tesSpan.style.opacity = "1.0"; } } function addQuotedPostsContainer() { Array.from(document.getElementsByClassName("postMessage")).forEach(el => { if (el.getElementsByClassName("quotedPostsContainer").length === 0) { let container = document.createElement("span"); container.classList.add("quotedPostsContainer"); el.appendChild(container); } }); } function addCollapseAndExpand() { Array.from(document.querySelectorAll(".post.reply")).forEach(postNode => { if (Array.from(postNode.children[1].children).filter(node => node.classList.contains("expandSpan")).length === 0) { let expandSpan = document.createElement("span"); expandSpan.classList.add("expandSpan"); expandSpan.innerText = "[↓]"; expandSpan.title = "expand all 'quoted by' posts"; expandSpan.style.color = "#1019d2e6"; expandSpan.style.fontSize = "11px"; expandSpan.style.paddingLeft = "5px"; expandSpan.style.paddingRight = "5px"; expandSpan.style.whiteSpace = "nowrap"; let collapseSpan = document.createElement("span"); collapseSpan.classList.add("collapseSpan"); collapseSpan.style.color = "#1019d2e6"; collapseSpan.innerText = "[↑]"; collapseSpan.title = "collapse all 'quoted by' posts"; collapseSpan.style.fontSize = "11px"; collapseSpan.style.paddingRight = "0px"; collapseSpan.style.whiteSpace = "nowrap"; postNode.children[1].insertBefore(expandSpan, postNode.querySelector(".postMenuBtn")); postNode.children[1].insertBefore(collapseSpan, postNode.querySelector(".postMenuBtn")); } let expand = postNode.querySelector(".expandSpan"); let collapse = postNode.querySelector(".collapseSpan"); expand.removeEventListener("click", expandSpanEL); expand.addEventListener("click", expandSpanEL); collapse.removeEventListener("click", collapseSpanEL); collapse.addEventListener("click", collapseSpanEL); }); } function expandSpanEL(e) { Array.from(Array.from(e.target.parentNode.children).filter(node => node.classList.contains("backlink"))[0].children).forEach(node => {if (!node.children[0].classList.contains("linkfade")) node.children[0].click();}); } function collapseSpanEL(e) { Array.from(Array.from(e.target.parentNode.children).filter(node => node.classList.contains("backlink"))[0].children).forEach(node => {if (node.children[0].classList.contains("linkfade")) node.children[0].click();}); } function qSpanClick(e) { e.preventDefault(); } function addRemoveCapability() { //turns all delete checkboxes to simply post removal buttons Array.from(document.getElementsByClassName("postInfo desktop")).forEach(el => el.children[0].addEventListener("click", postRemove)); } function postRemove(e) { e.preventDefault(); e.target.parentNode.parentNode.parentNode.remove(); } function qEV(e, node=e.target) { //console.log("quote clicked."); e.preventDefault(); if (!node.classList.contains("linkfade")) { //if not grayed out, first time clicking, adding node node.classList.add("linkfade"); //finding post and constructing new post let postsArr = Array.from(document.getElementsByClassName("postNum desktop")).filter(el => el.children[1].innerText === node.innerText.slice(2)); //console.log("postsAArr", postsArr); let post = postsArr[0].parentNode.parentNode.parentNode; //console.log("particular post from postArr", post.outerHTML); let newPost = post.cloneNode(true); //newPost.outerHTML = post.outerHTML; newPost.classList.add("inlined"); newPost.children[1].classList.remove("highlight"); newPost.style.display = ""; newPost.style.marginTop = "10px"; newPost.children[1].style.border = "1px solid #a1b7c899"; newPost.children[0].style.display = "none"; //console.log("newPost before adding", newPost.outerHTML, "oldPost", post.outerHTML, "boolean", newPost.outerHTML === post.outerHTML); //adding to parent post if (node.parentNode.parentNode.parentNode.parentNode.children[2].classList.contains("file")) { Array.from(node.parentNode.parentNode.parentNode.parentNode.children[3].children).filter(el => el.classList.contains("quotedPostsContainer"))[0].appendChild(newPost); setTimeout(function() {node.parentNode.parentNode.parentNode.parentNode.children[3].children[0].remove();}); } else { Array.from(node.parentNode.parentNode.parentNode.parentNode.children[2].children).filter(el => el.classList.contains("quotedPostsContainer"))[0].appendChild(newPost); setTimeout(function() {node.parentNode.parentNode.parentNode.parentNode.children[2].children[0].remove();}); } //console.log("node just added", newPost); setTimeout(function(){newPost.style.display = "";}, 10); //resetting event listeners resetQLEV(); } else { //grayed out, second time clicking, removing node node.classList.remove("linkfade"); let idx = node.parentNode.parentNode.parentNode.parentNode.children[2].classList.contains("file") ? 3 : 2; Array.from(node.parentNode.parentNode.parentNode.parentNode.children[idx].children).forEach(innerNode => { if (innerNode.classList.contains("quotedPostsContainer")) { Array.from(innerNode.children).forEach(el => { if (el.classList.contains("inlined") && node.innerText.slice(2) === el.children[1].children[1].children[3].children[1].innerText) el.remove(); }); } }); } } function resetQLEV() { let newQuotesList = Array.from(document.getElementsByClassName("quotelink")).filter(node => window.getComputedStyle(node).getPropertyValue("font-size") === "10.6667px"); newQuotesList.forEach(node => node.removeEventListener("click", qEV)); newQuotesList.forEach(node => node.addEventListener("click", qEV)); let newSpanList = Array.from(document.getElementsByTagName("span")).filter(el => { try { if (el.children.length > 0 && el.children[0].classList.contains("quotelink")) return true; } catch(error) { //console.log("custom error in span EL", el); return false; } }); newSpanList.forEach(el => el.removeEventListener("click", qSpanClick)); newSpanList.forEach(el => el.addEventListener("click", qSpanClick)); Array.from(document.getElementsByClassName("postInfo desktop")).forEach(el => el.children[0].removeEventListener("click", postRemove)); addRemoveCapability(); setTimeout(function() {addCollapseAndExpand();}, 100); let tesQuoteSpans = Array.from(document.getElementsByClassName("tessellateQuotes")); tesQuoteSpans.forEach(el => el.removeEventListener("click", tessellateQuotes)); tesQuoteSpans.forEach(el => el.addEventListener("click", tessellateQuotes)); addTessellationQuotes(); addQuotedPostsContainer(); } let observer = new MutationObserver(resetQLEV); observer.observe(document.getElementsByClassName("thread")[0], {childList: true});