// ==UserScript==
// @name chan embedded quote fixer
// @namespace http://tampermonkey.net/
// @version 1.6
// @description Puts embedded quotes in end of post rather than beginning, can also click on checkboxes in posts to remove them, also works on 4channel boards, can tesselate whole thread/inlined posts, can change size of expanded media, can color posts to help keep track of embedded conversations, can hide extra options on each post via toggle (posts/thread), can manually drag posts around page via toggle (posts/thread), can remove (You)s, can change text flow on expanded images
// @author DUVish
// @match http://boards.4chan.org/*/thread/*
// @match https://boards.4chan.org/*/thread/*
// @match https://boards.4channel.org/*/thread/*
// @match http://boards.4channel.org/*/thread/*
// @grant none
// ==/UserScript==
document.querySelector("head").innerHTML += `
<style>
div.post {overflow: visible;}
div.image-expanded:not(.regularTextFlow) {display: unset !important;}
div.image-expanded.regularTextFlow {display: table !important;}
</style>
`;
//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();
addTessellationThread();
addThreadWidthToggle();
addMediaZoom();
addMediaFlow();
addColorPostsThread();
addColorPosts();
setPostColor();
addCollapseExtraNodesToggle();
addPostDraggableTogglePost();
addPostsDraggableToggleThread();
addRemoveYousInPost();
addEditabilityToPost();
addCollapseExtrasInPosts();
addExpandExtrasInPosts();
expandPostsPostOn();
}, 2500);
//});
let greenTextStyler = `<style>
blockquote > div > span.quote {
color: #789922;
}
</style>`;
document.querySelector("head").innerHTML += greenTextStyler;
//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
var postColor = '';
var colorDiff = 38;
if (window.location.href.includes("boards.4chan.org")) colorDiff = 28;
function addTessellationThread() {
let newSpan = document.createElement("span");
newSpan.innerText = "[Tessellate Thread]";
newSpan.classList.add("tessellateThread");
newSpan.classList.add("collapsible");
newSpan.style.paddingLeft = "6px";
newSpan.style.paddingRight = "1px";
newSpan.style.fontSize = "11px";
newSpan.style.color = "rgb(46, 54, 144)";
newSpan.style.opacity = "1";
document.querySelector(".opContainer").querySelector(".postInfo.desktop").insertBefore(newSpan, document.querySelector(".opContainer").querySelector(".postMenuBtn"));
newSpan.addEventListener("click", tessellateThread);
}
function tessellateThreadAlgorithmic(e) {
let node = e.target;
let threadNode = document.querySelector(".thread");
let posts = Array.from(document.querySelectorAll(".post.reply"));
if (e.target.style.opacity === "1") {
//threadNode.style.maxWidth = "100%";
//threadNode.style.display = "flex";
//threadNode.style.flexWrap = "wrap";
node.style.opacity = "0.55";
Array.from(document.querySelectorAll(".sideArrows")).forEach(el => {el.style.opacity = "0"; el.style.zIndex = "-1";});
posts.forEach(el => {el.style.position = "relative"; el.style.bottom = "0px"; el.style.top = "0px"; el.style.left = "0px"; el.style.right = "0px";});
let topBound = Math.floor(document.querySelector(".postContainer.opContainer").getBoundingClientRect().bottom + window.scrollY);
let leftBound = 24;
//console.log("before top-level iteration", topBound, leftBound);
posts.forEach((post, postIdx) => {
if (postIdx === 0) return;
//post.style.left = document.documentElement.clientWidth - post.getBoundingClientRect().right;
let beginningTop = post.getBoundingClientRect().top + window.scrollY;
let currentBottomDiff = 0;
let currentRightDiff = 0;
let currentLeftDiff = 0;
let currentTopDiff = 0;
let currentLeftBound = document.documentElement.clientWidth - post.getBoundingClientRect().right;
//console.log("before first while loop", beginningTop, currentLeftBound);
//console.log("collection before while loop", posts, posts.slice(0, postIdx));
while(!intersectionCheck(post.getBoundingClientRect().top + window.scrollY - currentBottomDiff, currentLeftBound, posts.slice(0, postIdx), topBound, leftBound, post)) {
currentBottomDiff += 2;
//post.style.bottom = `${Number(post.style.bottom.match(/(\d+)px/)[1]) + 2}px`;
}
post.style.bottom = `${Number(post.style.bottom.match(/(\d+)px/)[1]) + currentBottomDiff}px`;
let currentTopBound = beginningTop + window.scrollY - currentBottomDiff;
while(!intersectionCheck(currentTopBound, currentLeftBound - currentRightDiff, posts.slice(0, postIdx), topBound, leftBound, post)) {
//post.style.right = `${Number(post.style.right.match(/(\d+)px/)[1]) + 2}px`;
currentRightDiff += 2;
}
post.style.left = `${Number(post.style.left.match(/(\d+)px/)[1]) - currentRightDiff + currentLeftBound}px`;
});
} else {
//undo
node.style.opacity = "1";
threadNode.style.maxWidth = "";
Array.from(document.querySelectorAll(".sideArrows")).forEach(el => {el.style.opacity = "1"; el.style.zIndex = "0";});
posts.forEach(el => {el.style.position = "unset"; el.style.bottom = "0px"; el.style.top = "0px"; el.style.left = "0px"; el.style.right = "0px";});
}
}
function intersectionCheck(currentTop, currentLeft, collection, topBound, leftBound, post) {
//console.log("entering intersectionCheck, collection", collection);
for (let el of collection) {
//console.log("comparing", post, el, currentTop, currentLeft, topBound, leftBound);
if (currentTop < el.getBoundingClientRect().bottom + window.scrollY && currentLeft < el.getBoundingClientRect().right ||
currentTop < topBound || currentLeft < leftBound) return true;
}
return false;
}
function tessellateThread(e) {
let node = e.target;
let threadNode = document.querySelector(".thread");
if (threadNode.style.display !== "flex") {
threadNode.style.display = "flex";
threadNode.style.flexWrap = "wrap";
threadNode.style.maxWidth = "100%";
node.style.opacity = "0.55";
} else {
threadNode.style.display = "";
threadNode.style.flexWrap = "";
threadNode.style.maxWidth = "";
node.style.opacity = "1.0";
}
}
function addThreadWidthToggle() {
let newSpan = document.createElement("span");
newSpan.innerText = "[Toggle Thread Width]";
newSpan.classList.add("threadWidthToggle");
newSpan.classList.add("collapsible");
newSpan.style.paddingLeft = "6px";
newSpan.style.paddingRight = "1px";
newSpan.style.fontSize = "11px";
newSpan.style.color = "rgb(46, 54, 144)";
newSpan.title = "Toggle thread width to and from 100% viewport width (without tessellating)";
document.querySelector(".opContainer").querySelector(".postInfo.desktop").insertBefore(newSpan, document.querySelector(".opContainer").querySelector(".postMenuBtn"));
newSpan.addEventListener("click", toggleThreadWidth);
}
function toggleThreadWidth(e) {
let node = e.target;
if (Number(node.style.opacity) > 0.95 || !node.style.opacity) {
document.querySelector(".thread").style.maxWidth = "100%";
node.style.opacity = "0.55";
} else {
document.querySelector(".thread").style.maxWidth = "";
node.style.opacity = "1.0";
}
}
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.classList.add("collapsible");
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.classList.add("collapsible");
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.classList.add("collapsible");
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 addMediaZoom() {
Array.from(document.querySelectorAll(".fileText")).forEach(el => {
if (Array.from(el.children).filter(el => el.classList.contains("mediaSizeChange")).length === 0) {
let span = document.createElement("span");
span.classList.add("mediaSizeChange");
span.classList.add("collapsible");
let spanBigger = document.createElement("span");
let spanSmaller = document.createElement("span");
let spanBiggerMore = document.createElement("span");
let spanSmallerMore = document.createElement("span");
let spanReset = document.createElement("span");
spanBigger.classList.add("mediaSizeIncrease");
spanSmaller.classList.add("mediaSizeDecrease");
spanBiggerMore.classList.add("mediaSizeIncreaseMore");
spanSmallerMore.classList.add("mediaSizeDecreaseMore");
spanReset.classList.add("mediaSizeReset");
spanBigger.innerText = "(++)";
spanSmaller.innerText = "(--)";
spanBiggerMore.innerHTML = `(<span style="font-size: 16px; position: relative; top: 1.5px;">++</span>)`;
spanSmallerMore.innerHTML = `(<span style="font-size: 18px; position: relative; top: 2px;">--</span>)`;
spanReset.innerText = "(Reset)";
spanBigger.style.color = "#1019d2e6";
spanBiggerMore.style.color = "#1019d2e6";
spanSmaller.style.color = "#1019d2e6";
spanSmallerMore.style.color = "#1019d2e6";
spanReset.style.color = "#1019d2e6";
spanReset.style.fontSize = "10px";
span.innerHTML = `[ Size - ${spanSmaller.outerHTML} ${spanSmallerMore.outerHTML} | ${spanBigger.outerHTML} ${spanBiggerMore.outerHTML} | ${spanReset.outerHTML} ]`;
el.appendChild(span);
span.style.fontSize = "12px";
span.style.position = "relative";
span.style.bottom = "1px";
span.style.paddingRight = "4px";
span.style.paddingLeft = "4px";
spanReset.style.position = "relative";
spanReset.style.bottom = "1px";
}
let spanBigger = el.getElementsByClassName("mediaSizeIncrease")[0];
spanBigger.removeEventListener("click", mediaSizeIncrease);
let spanSmaller = el.getElementsByClassName("mediaSizeDecrease")[0];
spanSmaller.removeEventListener("click", mediaSizeDecrease);
let spanBiggerMore = el.getElementsByClassName("mediaSizeIncreaseMore")[0];
spanBiggerMore.removeEventListener("click", mediaSizeIncreaseMore);
let spanSmallerMore = el.getElementsByClassName("mediaSizeDecreaseMore")[0];
spanSmallerMore.removeEventListener("click", mediaSizeDecreaseMore);
let spanReset = el.getElementsByClassName("mediaSizeReset")[0];
spanReset.removeEventListener("click", mediaSizeReset);
spanBigger.addEventListener("click", mediaSizeIncrease);
spanSmaller.addEventListener("click", mediaSizeDecrease);
spanBiggerMore.addEventListener("click", mediaSizeIncreaseMore);
spanSmallerMore.addEventListener("click", mediaSizeDecreaseMore);
spanReset.addEventListener("click", mediaSizeReset);
});
}
function mediaSizeIncrease(e) {
let min = 0;
let max = Number(e.target.parentNode.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[0]);
let fileDiv = e.target.parentNode.parentNode.parentNode;
if (Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO").length !== 0) {
let vidEl = Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO")[0];
if (vidEl.style.maxWidth) {
vidEl.style.width = vidEl.style.maxWidth;
vidEl.style.maxWidth = "";
vidEl.style.maxHeight = "";
}
vidEl.style.height = "";
let currentWidth = Number(vidEl.style.width.match(/\d+/));
currentWidth += 20;
vidEl.style.width = `${currentWidth}px`;
} else {
if (fileDiv.classList.contains("image-expanded")) {
let img = Array.from(fileDiv.querySelectorAll("img")).filter(el => el.classList.contains("expanded-thumb"))[0];
if (img) {
if (img.style.maxWidth) {
img.style.width = img.style.maxWidth;
img.style.maxWidth = "";
img.style.maxHeight = "";
}
img.style.height = "";
let currentWidth = Number(img.style.width.match(/\d+/));
currentWidth += 20;
img.style.width = `${currentWidth}px`;
}
}
}
}
function mediaSizeDecrease(e) {
let min = 0;
let max = Number(e.target.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[0]);
let fileDiv = e.target.parentNode.parentNode.parentNode;
if (Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO").length !== 0) {
let vidEl = Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO")[0];
if (vidEl.style.maxWidth) {
vidEl.style.width = vidEl.style.maxWidth;
vidEl.style.maxWidth = "";
vidEl.style.maxHeight = "";
}
vidEl.style.height = "";
let currentWidth = Number(vidEl.style.width.match(/\d+/));
currentWidth -= 20;
if (currentWidth < 0) {
vidEl.style.width = `0px`;
currentWidth = 0;
} else {
vidEl.style.width = `${currentWidth}px`;
}
} else {
if (fileDiv.classList.contains("image-expanded")) {
let img = Array.from(fileDiv.querySelectorAll("img")).filter(el => el.classList.contains("expanded-thumb"))[0];
if (img) {
if (img.style.maxWidth) {
img.style.width = img.style.maxWidth;
img.style.maxWidth = "";
img.style.maxHeight = "";
}
img.style.height = "";
let currentWidth = Number(img.style.width.match(/\d+/));
currentWidth -= 20;
if (currentWidth < 0) {
img.style.width = `${max}px}`;
} else {
img.style.width = `${currentWidth}px`;
}
}
}
}
}
function mediaSizeIncreaseMore(e) {
let min = 0;
let max = Number(e.target.parentNode.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[0]);
let fileDiv = e.target.parentNode.parentNode.parentNode.parentNode;
if (Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO").length !== 0) {
let vidEl = Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO")[0];
if (vidEl.style.maxWidth) {
vidEl.style.width = vidEl.style.maxWidth;
vidEl.style.maxWidth = "";
vidEl.style.maxHeight = "";
}
vidEl.style.height = "";
let currentWidth = Number(vidEl.style.width.match(/\d+/));
currentWidth += 100;
vidEl.style.width = `${currentWidth}px`;
} else {
if (fileDiv.classList.contains("image-expanded")) {
let img = Array.from(fileDiv.querySelectorAll("img")).filter(el => el.classList.contains("expanded-thumb"))[0];
if (img) {
if (img.style.maxWidth) {
img.style.width = img.style.maxWidth;
img.style.maxWidth = "";
img.style.maxHeight = "";
}
img.style.height = "";
let currentWidth = Number(img.style.width.match(/\d+/));
currentWidth += 100;
img.style.width = `${currentWidth}px`;
}
}
}
}
function mediaSizeDecreaseMore(e) {
let min = 0;
let max = Number(e.target.parentNode.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[0]);
let fileDiv = e.target.parentNode.parentNode.parentNode.parentNode;
if (Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO").length !== 0) {
let vidEl = Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO")[0];
if (vidEl.style.maxWidth) {
vidEl.style.width = vidEl.style.maxWidth;
vidEl.style.maxWidth = "";
vidEl.style.maxHeight = "";
}
vidEl.style.height = "";
let currentWidth = Number(vidEl.style.width.match(/\d+/));
currentWidth -= 100;
if (currentWidth < 0) {
vidEl.style.width = `0px`;
currentWidth = 0;
} else {
vidEl.style.width = `${currentWidth}px`;
}
} else {
if (fileDiv.classList.contains("image-expanded")) {
let img = Array.from(fileDiv.querySelectorAll("img")).filter(el => el.classList.contains("expanded-thumb"))[0];
if (img) {
if (img.style.maxWidth) {
img.style.width = img.style.maxWidth;
img.style.maxWidth = "";
img.style.maxHeight = "";
}
img.style.height = "";
let currentWidth = Number(img.style.width.match(/\d+/));
currentWidth -= 100;
if (currentWidth < 0) {
img.style.width = `${max}px}`;
} else {
img.style.width = `${currentWidth}px`;
}
}
}
}
}
function mediaSizeReset(e) {
let width = Number(e.target.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[0]);
let height = Number(e.target.parentNode.parentNode.innerText.match(/\d+x\d+/)[0].split("x")[1]);
let fileDiv = e.target.parentNode.parentNode.parentNode;
if (Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO").length !== 0) {
let vidEl = Array.from(fileDiv.children).filter(el => el.tagName === "VIDEO")[0];
vidEl.style.width = `${width}px`;
vidEl.style.height = `${height}px`;
} else {
if (fileDiv.classList.contains("image-expanded")) {
let img = Array.from(fileDiv.querySelectorAll("img")).filter(el => el.classList.contains("expanded-thumb"))[0];
if (img) {
img.style.width = `${width}px`;
img.style.height = `${height}px`;
}
}
}
}
function addMediaFlow() {
Array.from(document.querySelectorAll(".fileText")).forEach(el => {
if (Array.from(el.children).filter(el => el.classList.contains("mediaFlowChange")).length === 0) {
let span = document.createElement("span");
span.classList.add("mediaFlowChange");
span.classList.add("collapsible");
span.innerText = "[Text flow]";
el.appendChild(span);
span.style.fontSize = "10px";
span.style.color = "#1019d2e6";
span.style.position = "relative";
span.style.bottom = "1px";
span.style.paddingRight = "4px";
span.style.opacity = "1";
}
let spanFlow = el.getElementsByClassName("mediaFlowChange")[0];
spanFlow.removeEventListener("click", mediaFlowChange);
spanFlow.addEventListener("click", mediaFlowChange);
});
}
function mediaFlowChange(e) {
let flowSpan = e.target;
let flowSpanOpacity = flowSpan.style.opacity;
let fileTextSpan = flowSpan.parentNode.parentNode;
console.log("in MediaFlowChange", flowSpan, fileTextSpan, flowSpanOpacity, typeof flowSpanOpacity, fileTextSpan.classList.contains("image-expanded"), fileTextSpan.style.display);
if (fileTextSpan.classList.contains("image-expanded")) {
if (flowSpanOpacity === "1") {
e.target.style.opacity = "0.55";
//fileTextSpan.style.display = "table";
fileTextSpan.classList.add("regularTextFlow");
} else {
e.target.style.opacity = "1";
//fileTextSpan.style.display = "unset";
fileTextSpan.classList.remove("regularTextFlow");
}
}
}
function addYTSizeChangeCapability() {
//change yt size
}
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 setPostColor() {
if (!postColor) {
if (document.querySelectorAll(".post.reply").length > 0) {
let post = document.querySelector(".post.reply");
let rgbStr = window.getComputedStyle(post).backgroundColor;
let matches = rgbStr.match(/rgb\((\d+),\s?(\d+),\s?(\d+)\)/i);
postColor = {r: Number(matches[1]), g: Number(matches[2]), b: Number(matches[3])};
}
}
}
function addColorPostsThread() {
let btn = document.querySelector(".op").querySelector(".postMenuBtn");
let span = document.createElement("span");
span.classList.add("colorChange");
span.classList.add("collapsible");
span.title = "Color all posts in thread";
let colorOn = document.createElement("span");
let colorOff = document.createElement("span");
colorOn.classList.add("colorOnThread");
colorOff.classList.add("colorOffThread");
colorOn.innerText = "Color Posts";
colorOff.innerText = "Off";
span.innerHTML = `[${colorOn.outerHTML} | ${colorOff.outerHTML}]`;
btn.parentNode.insertBefore(span, btn);
span.style.paddingLeft = "6px";
span.style.paddingRight = "1px";
span.style.fontSize = "11px";
span.style.color = "rgb(46, 53, 144)";
document.querySelector(".colorOnThread").addEventListener("click", colorPostsThreadOn);
document.querySelector(".colorOffThread").addEventListener("click", colorPostsThreadOff);
}
function colorPostsThreadOn(e) {
Array.from(document.querySelectorAll(".post.reply")).forEach(el => {
el.style.backgroundColor = `rgb(${Math.random() > 0.8 ? postColor.r - (Math.random() * (colorDiff / 5)) : postColor.r + (Math.random() * (colorDiff * 1.45))},
${Math.random() > 0.8 ? postColor.g - (Math.random() * (colorDiff / 5)) : postColor.g + (Math.random() * (colorDiff * 1.45))},
${Math.random() > 0.8 ? postColor.b - (Math.random() * (colorDiff / 5)) : postColor.b + (Math.random() * (colorDiff * 1.45))})`;
});
}
function colorPostsThreadOff(e) {
Array.from(document.querySelectorAll(".post.reply")).forEach(el => {
el.style.backgroundColor = `rgb(${postColor.r}, ${postColor.g}, ${postColor.b})`;
});
}
function addCollapseExtraNodesToggle() {
let btn = document.querySelector(".op").querySelector(".postMenuBtn");
let span = document.createElement("span");
span.classList.add("collapseExtraNodes");
span.classList.add("collapsible");
span.title = "Collapse all extra nodes in thread";
span.innerText = "[Collapse extras]";
btn.parentNode.insertBefore(span, btn);
span.style.paddingLeft = "6px";
span.style.paddingRight = "1px";
span.style.fontSize = "11px";
span.style.color = "rgb(46, 53, 144)";
span.style.opacity = "1";
span.addEventListener("click", collapseNodesToggle);
}
let threadNodesCollapsed = false;
function collapseNodesToggle(e) {
if (!threadNodesCollapsed) {
Array.from(document.querySelectorAll(".collapsible")).forEach(el => el.style.display = "none");
threadNodesCollapsed = true;
//e.target.style.opacity = "0.55";
} else {
Array.from(document.querySelectorAll(".collapsible")).forEach(el => el.style.display = "unset");
threadNodesCollapsed = false;
//e.target.style.opacity = "1";
}
}
function addCollapseExtrasInPosts() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach((el, i) => {
if (i === 0) return;
if (Array.from(el.children).filter(child => child.classList.contains("collapseExtraNodes")).length === 0) {
el.parentNode.dataset.originalColor = window.getComputedStyle(el).backgroundColor;
let span = document.createElement("span");
span.classList.add("collapseExtraNodes");
span.classList.add("collapsible");
span.title = "Collapse all extra nodes of inlined posts within this one, including this - reverse with arrow button";
span.innerHTML = `[Collapse extras]`;
el.insertBefore(span, el.querySelector(".postMenuBtn"));
span.style.paddingLeft = "6px";
span.style.paddingRight = "1px";
span.style.fontSize = "11px";
span.style.color = "rgb(46, 53, 144)";
}
let collapseSpan = el.querySelector(".collapseExtraNodes");
collapseSpan.removeEventListener("click", collapsePostsPostOn);
collapseSpan.addEventListener("click", collapsePostsPostOn);
});
}
function collapsePostsPostOn(e) {
Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply")).forEach(el => {
Array.from(el.querySelectorAll(".collapsible")).forEach(collapsibleEl => collapsibleEl.style.display = "none");
});
}
function addExpandExtrasInPosts() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach((el, i) => {
if (i === 0) {
let buttonMenuSpan = el.querySelector(".postMenuBtn");
buttonMenuSpan.removeEventListener("click", expandPostsThreadOn);
buttonMenuSpan.addEventListener("click", expandPostsThreadOn);
return;
};
let buttonMenuSpan = el.querySelector(".postMenuBtn");
buttonMenuSpan.removeEventListener("click", expandPostsPostOn);
buttonMenuSpan.addEventListener("click", expandPostsPostOn);
});
}
function expandPostsPostOn(e) {
Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply")).forEach(el => {
Array.from(el.querySelectorAll(".collapsible")).forEach(collapsibleEl => collapsibleEl.style.display = "unset");
});
}
function expandPostsThreadOn(e) {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach(el => {
Array.from(el.querySelectorAll(".collapsible")).forEach(collapsibleEl => collapsibleEl.style.display = "unset");
});
}
function addColorPosts() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach((el, i) => {
if (i === 0) return;
if (Array.from(el.children).filter(child => child.classList.contains("colorChange")).length === 0) {
el.parentNode.dataset.originalColor = window.getComputedStyle(el).backgroundColor;
let span = document.createElement("span");
span.classList.add("colorChange");
span.classList.add("collapsible");
span.title = "Color all inlined posts within this one";
let colorOn = document.createElement("span");
let colorOff = document.createElement("span");
colorOn.classList.add("colorOnPost");
colorOff.classList.add("colorOffPost");
colorOn.innerText = "Color Posts";
colorOff.innerText = "Off";
span.innerHTML = `[${colorOn.outerHTML} | ${colorOff.outerHTML}]`;
el.insertBefore(span, el.querySelector(".postMenuBtn"));
span.style.paddingLeft = "6px";
span.style.paddingRight = "1px";
span.style.fontSize = "11px";
span.style.color = "rgb(46, 53, 144)";
}
let colorOn = el.querySelector(".colorOnPost");
let colorOff = el.querySelector(".colorOffPost");
colorOn.removeEventListener("click", colorPostsPostOn);
colorOn.addEventListener("click", colorPostsPostOn);
colorOff.removeEventListener("click", colorPostsPostOff);
colorOff.addEventListener("click", colorPostsPostOff);
});
}
function colorPostsPostOn(e) {
//console.log("colorPostsPoston", e.target);
Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply")).forEach(el => {
let nums = getTwoToThreeHigherNums();
let randomDiff = getAtLeastOneQuarterFromRandom();
let mappedNums = nums.map((num, idx) => num > 0.5 ? getSpecificColorFromMappedNumIdx(idx) + (randomDiff * colorDiff) : getSpecificColorFromMappedNumIdx(idx) - (randomDiff * colorDiff));
el.style.backgroundColor = `rgb(${mappedNums[0]}, ${mappedNums[1]}, ${mappedNums[2]})`;
});
}
function colorPostsPostOff(e) {
Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply")).forEach(el => {
el.style.backgroundColor = `rgb(${postColor.r}, ${postColor.g}, ${postColor.b})`;
});
}
function getTwoToThreeHigherNums() {
while(true) {
let arr = [Math.random(), Math.random(), Math.random()];
let numbersAboveHalf = arr.filter(el => el > 0.5).length;
if (numbersAboveHalf > 1 && numbersAboveHalf < 4) {
if (numbersAboveHalf === 3) {
if (arr.filter(el => el > 0.85).length > 2) return arr;
} else return arr;
}
}
}
function getAtLeastOneQuarterFromRandom() {
while(true) {
let num = Math.random();
if (num > 0.25) return num;
}
}
function getSpecificColorFromMappedNumIdx(idx) {
// if (idx === 0) return 215;
// if (idx === 1) return 215;
// else return 215;
if (idx === 0) return postColor.r;
if (idx === 1) return postColor.g;
else return postColor.b;
}
function addPostsDraggableToggleThread() {
let btn = document.querySelector(".op").querySelector(".postMenuBtn");
let metaSpan = document.createElement("span");
metaSpan.classList.add("postsDraggableContainer");
metaSpan.classList.add("collapsible");
let spanDrag = document.createElement("span");
let spanDragReset = document.createElement("span");
spanDragReset.classList.add("postsDraggableReset");
spanDrag.classList.add("postsDraggableToggle");
spanDrag.title = "Toggle for whether posts are draggable/movable";
spanDrag.innerText = "Posts Draggable";
spanDragReset.title = "Reset positions of all posts";
spanDragReset.innerText = "Reset";
metaSpan.innerHTML = `[${spanDrag.outerHTML} | ${spanDragReset.outerHTML}]`;
btn.parentNode.insertBefore(metaSpan, btn);
metaSpan.style.paddingLeft = "6px";
metaSpan.style.paddingRight = "1px";
metaSpan.style.fontSize = "11px";
metaSpan.style.color = "rgb(46, 53, 144)";
spanDrag = document.querySelector(".postsDraggableToggle");
spanDrag.style.opacity = "1";
spanDragReset = document.querySelector(".postsDraggableReset");
spanDrag.addEventListener("click", postsDraggableToggle);
spanDragReset.addEventListener("click", postsDraggableReset);
}
function postsDraggableReset() {
let posts = Array.from(document.querySelectorAll(".post.reply"));
posts.forEach(post => {
//post.style.position = "unset";
post.style.top = "";
post.style.bottom = "";
post.style.left = "";
post.style.right = "";
});
}
let currentStartDragHorizontal = null;
let currentStartDragVertical = null;
let currentStartDragHorizontalDiff = null;
let currentStartDragVerticalDiff = null;
let currentStartDragNode = null;
let currentDragZIndex = 1;
function postsDraggableToggle(e) {
if (e.target.style.opacity === "1") {
e.target.style.opacity = "0.55";
document.addEventListener("dragover", documentDragOver, true);
document.addEventListener("drop", documentDrop, true);
let posts = Array.from(document.querySelectorAll(".post.reply"));
posts.forEach(post => {
if (post.draggable !== true) {
post.draggable = true;
post.addEventListener("dragstart", postDragStart, true);
}
});
} else {
e.target.style.opacity = "1";
document.removeEventListener("dragover", documentDragOver);
document.removeEventListener("drop", documentDrop);
let posts = Array.from(document.querySelectorAll(".post.reply"));
posts.forEach(post => {
if (post.draggable !== false) {
post.draggable = false;
post.removeEventListener("dragstart", postDragStart);
}
});
}
}
function addPostDraggableTogglePost() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach((el, i) => {
if (i === 0) return;
if (Array.from(el.children).filter(child => child.classList.contains("postsDraggableContainer")).length === 0) {
el.parentNode.dataset.originalColor = window.getComputedStyle(el).backgroundColor;
let metaSpan = document.createElement("span");
metaSpan.classList.add("postsDraggableContainer");
metaSpan.classList.add("collapsible");
let spanDrag = document.createElement("span");
let spanDragReset = document.createElement("span");
spanDragReset.classList.add("postsDraggableResetPost");
spanDrag.classList.add("postsDraggableTogglePost");
spanDrag.title = "Toggle for whether inlined posts are draggable/movable";
spanDrag.innerText = "Posts Draggable";
spanDragReset.title = "Reset positions of all inlined posts";
spanDragReset.innerText = "Reset";
metaSpan.innerHTML = `[${spanDrag.outerHTML} | ${spanDragReset.outerHTML}]`;
el.insertBefore(metaSpan, el.querySelector(".postMenuBtn"));
metaSpan.style.paddingLeft = "6px";
metaSpan.style.paddingRight = "1px";
metaSpan.style.fontSize = "11px";
metaSpan.style.color = "rgb(46, 53, 144)";
}
let spanDrag = el.querySelector(".postsDraggableTogglePost");
spanDrag.style.opacity = "1";
let spanDragReset = el.querySelector(".postsDraggableResetPost");
spanDrag.removeEventListener("click", postsDraggableTogglePost);
spanDragReset.removeEventListener("click", postsDraggableResetPost);
spanDrag.addEventListener("click", postsDraggableTogglePost);
spanDragReset.addEventListener("click", postsDraggableResetPost);
});
}
function postsDraggableResetPost(e) {
let posts = Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply"));
posts.forEach(post => {
//post.style.position = "unset";
post.style.top = "";
post.style.bottom = "";
post.style.left = "";
post.style.right = "";
});
}
function postsDraggableTogglePost(e) {
if (e.target.style.opacity === "1") {
e.target.style.opacity = "0.55";
document.addEventListener("dragover", documentDragOver, true);
document.addEventListener("drop", documentDrop, true);
let posts = Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply"));
posts.forEach(post => {
if (post.draggable !== true) {
post.draggable = true;
post.addEventListener("dragstart", postDragStart, true);
}
});
} else {
e.target.style.opacity = "1";
document.removeEventListener("dragover", documentDragOver);
document.removeEventListener("drop", documentDrop);
let posts = Array.from(e.target.parentNode.parentNode.parentNode.querySelectorAll(".post.reply"));
posts.forEach(post => {
if (post.draggable !== false) {
post.draggable = false;
post.removeEventListener("dragstart", postDragStart);
}
});
}
}
function postDragStart(e) {
e.dataTransfer.setData("text", e.target.outerHTML);
//currentStartDragHorizontalDiff = Math.floor(e.clientX - e.target.getBoundingClientRect().left);
//currentStartDragVerticalDiff = Math.floor(e.clientY - e.target.getBoundingClientRect().top);
currentStartDragHorizontal = e.clientX;
currentStartDragVertical = e.clientY + window.scrollY;
currentStartDragNode = e.target;
//e.target.style.position = "absolute";
if (e.target.style.top === "") e.target.style.top = "0px";
if (e.target.style.left === "") e.target.style.left = "0px";
e.target.style.zIndex = JSON.stringify(currentDragZIndex);
currentDragZIndex++;
}
function documentDragOver(e) {
e.preventDefault();
}
function documentDrop(e) {
e.preventDefault();
currentStartDragNode.style.left = `${Number(currentStartDragNode.style.left.match(/([-\.\d]+)px/)[1]) + e.clientX - currentStartDragHorizontal}px`;
currentStartDragNode.style.top = `${Number(currentStartDragNode.style.top.match(/([-\.\d]+)px/)[1]) + window.scrollY + e.clientY - currentStartDragVertical}px`;
//currentStartDragNode.style.left = `${e.clientX - currentStartDragHorizontalDiff}px`;
//currentStartDragNode.style.top = `${window.scrollY + e.clientY - currentStartDragVerticalDiff}px`;
//currentStartDragNode.style.position = "absolute";
if (currentStartDragNode.style.position !== "relative") currentStartDragNode.style.position = "relative";
currentStartDragHorizontalDiff = null;
currentStartDragVerticalDiff = null;
currentStartDragNode = null;
}
function addRemoveYousInPost() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach(el => {
if (Array.from(el.children).filter(el => el.classList.contains("removeYouPost")).length === 0) {
let btn = el.querySelector(".postMenuBtn");
let newSpan = document.createElement("span");
newSpan.innerText = "[Remove (You)s]";
newSpan.style.paddingLeft = "6px";
newSpan.style.paddingRight = "1px";
newSpan.style.fontSize = "11px";
newSpan.style.color = "rgb(46, 53, 144)";
newSpan.classList.add("removeYouPost");
newSpan.classList.add("collapsible");
newSpan.title = "Remove all (You)s from current post (works recursively downward)";
btn.parentNode.insertBefore(newSpan, btn);
}
});
Array.from(document.querySelectorAll(".removeYouPost")).forEach(el => {
el.removeEventListener("click", removeYouPost);
el.addEventListener("click", removeYouPost);
});
}
function removeYouPost(e) {
let post = e.target.parentNode.parentNode;
Array.from(post.querySelectorAll("a.quotelink")).forEach(el => {
el.innerText = el.innerText.replace(" (You)", "");
});
}
function addEditabilityToPost() {
Array.from(document.querySelectorAll(".postInfo.desktop")).forEach(el => {
if (Array.from(el.children).filter(el => el.classList.contains("addEditability")).length === 0) {
let btn = el.querySelector(".postMenuBtn");
let newSpan = document.createElement("span");
newSpan.innerText = "[Editable]";
newSpan.style.paddingLeft = "6px";
newSpan.style.paddingRight = "1px";
newSpan.style.fontSize = "11px";
newSpan.style.color = "rgb(46, 53, 144)";
newSpan.classList.add("addEditability");
newSpan.classList.add("collapsible");
newSpan.title = "Make post type-editable";
btn.parentNode.insertBefore(newSpan, btn);
}
});
Array.from(document.querySelectorAll(".addEditability")).forEach(el => {
el.removeEventListener("click", toggleEditabilityForPost);
el.addEventListener("click", toggleEditabilityForPost);
});
}
function toggleEditabilityForPost(e) {
let flag = "false";
if ((e.target.style.opacity === "1") || !e.target.style.opacity) {
e.target.style.opacity = "0.55";
flag = "true";
} else {
e.target.style.opacity = "1";
flag = "false";
}
let post = e.target.parentNode.parentNode;
let allEmbeddedPosts = Array.from(post.querySelectorAll(".postMessage"));
allEmbeddedPosts.forEach(post => post.contentEditable = flag);
}
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));
let post = postsArr[0].parentNode.parentNode.parentNode;
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 rgba(172, 127, 127, 0.6)";
newPost.children[1].style.borderRadius = "2px";
newPost.children[0].style.display = "none";
//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);
if (JSON.parse(localStorage.getItem("4chan-settings")).inlineQuotes) 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);
if (JSON.parse(localStorage.getItem("4chan-settings")).inlineQuotes) setTimeout(function() {node.parentNode.parentNode.parentNode.parentNode.children[2].children[0].remove();});
}
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) {
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();
addCollapseAndExpand();
let tesQuoteSpans = Array.from(document.getElementsByClassName("tessellateQuotes"));
tesQuoteSpans.forEach(el => el.removeEventListener("click", tessellateQuotes));
tesQuoteSpans.forEach(el => el.addEventListener("click", tessellateQuotes));
addTessellationQuotes();
addQuotedPostsContainer();
addMediaZoom();
addMediaFlow();
addColorPosts();
setPostColor();
addPostDraggableTogglePost();
addRemoveYousInPost();
addEditabilityToPost();
addCollapseExtrasInPosts();
addExpandExtrasInPosts();
expandPostsPostOn();
}
let observer = new MutationObserver(resetQLEV);
observer.observe(document.getElementsByClassName("thread")[0], {childList: true});
let quotePreviewObserver = new MutationObserver(quotePreviewHandler);
quotePreviewObserver.observe(document.querySelector("body"), {childList: true});
function quotePreviewHandler(newNodes) {
if (newNodes[0] && newNodes[0].addedNodes.length > 0) newNodes[0].addedNodes.forEach(el => {if (el.id === "quote-preview") el.style.zIndex = currentDragZIndex++;});
}