// ==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});