// ==UserScript==
// @name 8chan Nested Inline Reply
// @version 2.0.5
// @description Make Nested Inline Reply like 4chanX
// @match https://8chan.moe/*/res/*
// @match https://8chan.se/*/res/*
// @grant GM_addStyle
// @grant GM.addStyle
// @license MIT
// @namespace https://greasyfork.org/users/1459581
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
.collapsible-container {
margin-left: 20px;
padding-left: 5px;
margin-top: 8px;
}
.post-content.collapsed {
display: none;
}
.altBacklinks {
display: none !important;
}
.postCell.post-content {
border: none !important;
}
.innerPost {
width: auto;
max-width: none !important;#474b53
}
.moved-post {
position: relative;
opacity: 0.9;
}
.linkQuote.toggled, .panelBacklinks a.toggled {
color: #9a5;
}
.post-placeholder {
display: none;
padding: 5px;
background: rgba(50, 50, 50, 0.3);
border: 1px dashed #474b53;
font-style: italic;
color: #8c8c8c;
text-align: center;
margin: 5px 0;
}
.placeholder-visible {
display: block;
}
.post-restored {
border: 1px solid black;
}
`);
const movedPosts = new Map();
const linkContainers = new Map();
const originalPosts = new Map();
document.body.addEventListener('click', function(event) {
const target = event.target;
if (target.classList.contains('linkQuote') && event.ctrlKey) {
const postId = target.href.match(/#q?(\d+)/)?.[1];
if (postId && typeof qr !== 'undefined' && qr.showQr) {
qr.showQr(postId);
event.preventDefault();
return;
}
}
if (target.classList.contains('restore-post-link')) {
event.preventDefault();
const postId = target.dataset.postId;
if (postId && movedPosts.has(postId)) {
restorePost(postId);
}
return;
}
if ((target.parentNode && target.parentNode.classList.contains('panelBacklinks')) && !event.ctrlKey) {
const link = target.closest('a');
if (!link) return;
const rawHash = link.hash.includes('?') ? link.hash.split('?')[0] : link.hash;
const targetId = rawHash.substring(1).replace(/^q/, '');
if (movedPosts.has(targetId)) {
event.preventDefault();
restorePost(targetId);
return;
}
}
if ((target.parentNode && target.parentNode.classList.contains('panelBacklinks'))) {
event.preventDefault();
const link = target.closest('a');
if (!link) return;
const rawHash = link.hash.includes('?') ? link.hash.split('?')[0] : link.hash;
const targetId = rawHash.substring(1).replace(/^q/, '');
if (linkContainers.has(link)) {
const container = linkContainers.get(link);
const content = container.querySelector('.post-content');
if (content) {
const wasCollapsed = content.classList.contains('collapsed');
content.classList.toggle('collapsed');
link.classList.toggle('toggled');
if (!wasCollapsed && movedPosts.has(targetId)) {
const postData = movedPosts.get(targetId);
postData.links.delete(link);
if (postData.links.size === 0) {
restorePost(targetId);
}
}
}
return;
}
if (!originalPosts.has(targetId)) {
let targetPost = document.getElementById(targetId);
if (!targetPost) return;
originalPosts.set(targetId, targetPost);
}
let postToUse;
if (movedPosts.has(targetId)) {
postToUse = movedPosts.get(targetId).element;
} else {
postToUse = document.getElementById(targetId) || originalPosts.get(targetId);
if (!postToUse) return;
}
const level = link.closest('.collapsible-container')?.dataset.level || 0;
const container = document.createElement('div');
container.className = 'collapsible-container';
container.dataset.level = parseInt(level) + 1;
movePostToContainer(targetId, postToUse, container, link);
const postContainer = link.closest('.innerPost');
if (postContainer) {
postContainer.appendChild(container);
} else {
link.parentNode.insertBefore(container, link.nextSibling);
}
linkContainers.set(link, container);
link.classList.add('toggled');
}
});
function movePostToContainer(postId, postToUse, container, link) {
if (movedPosts.has(postId)) {
const postData = movedPosts.get(postId);
const lightClone = postData.element.cloneNode(true);
lightClone.classList.add('post-content', 'moved-post', 'post-restored'); // ← ADDED
lightClone.setAttribute('data-original-id', postId);
container.appendChild(lightClone);
postData.links.add(link);
return;
}
if (!document.getElementById(postId) && originalPosts.has(postId)) {
const originalPost = originalPosts.get(postId);
const clone = originalPost.cloneNode(true);
clone.classList.add('post-content', 'moved-post', 'post-restored'); // ← ADDED
clone.setAttribute('data-original-id', postId);
container.appendChild(clone);
const placeholder = document.createElement('div');
placeholder.className = 'post-placeholder';
movedPosts.set(postId, {
element: clone,
placeholder: placeholder,
links: new Set([link])
});
return;
}
const placeholder = document.createElement('div');
placeholder.className = 'post-placeholder placeholder-visible';
placeholder.innerHTML = `Post moved <a href="#" class="restore-post-link" data-post-id="${postId}">Restore</a>`;
postToUse.parentNode.insertBefore(placeholder, postToUse);
postToUse.setAttribute('data-original-id', postId);
const innerPost = postToUse.querySelector('.innerPost');
if (innerPost) {
innerPost.classList.add('post-restored');
}
container.appendChild(postToUse);
movedPosts.set(postId, {
element: postToUse,
placeholder: placeholder,
links: new Set([link])
});
}
function restorePost(postId) {
if (!movedPosts.has(postId)) return;
const {element, placeholder, links} = movedPosts.get(postId);
links.forEach(link => {
if (linkContainers.has(link)) {
const container = linkContainers.get(link);
container.remove();
linkContainers.delete(link);
link.classList.remove('toggled', 'post-restored');
}
});
document.querySelectorAll(`.moved-post[data-original-id="${postId}"]`).forEach(instance => {
if (instance !== element) {
instance.remove();
}
});
if (placeholder.parentNode) {
placeholder.parentNode.insertBefore(element, placeholder);
placeholder.remove();
}
element.classList.remove('post-content', 'moved-post', 'post-restored');
element.removeAttribute('data-original-id');
const innerPost = element.querySelector('.innerPost');
if (innerPost) {
innerPost.classList.remove('post-restored');
}
movedPosts.delete(postId);
if (!originalPosts.has(postId)) {
originalPosts.set(postId, element);
}
}
function cleanupBacklinks() {
document.querySelectorAll('span.panelBacklinks a').forEach(link => {
const href = link.getAttribute('href');
if (href?.includes('#')) {
link.href = `#${href.split('#')[1].split('?')[0]}`;
}
});
}
const observer = new MutationObserver((mutations) => {
let shouldProcess = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1 &&
(node.classList?.contains('post') ||
node.querySelector?.('.post, .linkQuote, .panelBacklinks'))) {
shouldProcess = true;
break;
}
}
if (shouldProcess) break;
}
}
if (shouldProcess) {
cleanupBacklinks();
}
});
const threadContainer = document.querySelector('.thread');
if (threadContainer) {
observer.observe(threadContainer, { childList: true, subtree: true });
} else {
observer.observe(document.body, { childList: true, subtree: false });
}
cleanupBacklinks();
})();