您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
делает треды древовидными, добавляет сворачивание веток и подсветку новых
// ==UserScript== // @name 2ch tree post fork // @namespace http://tampermonkey.net/ // @version 1.3 // @description делает треды древовидными, добавляет сворачивание веток и подсветку новых // @author You // @match http://2ch.hk/*/res/* // @match https://2ch.hk/*/res/* // @match http://2ch.life/*/res/* // @match https://2ch.life/*/res/* // @grant none // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== (function () { "use strict"; console.time("tree script"); // Вспомогательные функции // Добавляет CSS стили function addStyle(css) { const style = document.createElement('style'); style.type = 'text/css'; style.textContent = css; document.head.appendChild(style); } // Получает номер поста из элемента function getPostNumber(postElement) { if (!postElement) return null; const id = postElement.id; // "post-123456" return parseInt(id.replace("post-", "")); } // Перемещает пост и применяет стили function postMove(linkPost, isNewPost = false) { const nodePostCurr = linkPost.parentNode.parentNode; // Текущий пост (обертка .post) const postNumber = linkPost.innerText.match(/\d+/); if (!postNumber) return; // Если не удалось извлечь номер, выходим const targetPostNumber = postNumber[0]; // Проверяем, ссылка на OP, другой тред или несуществующий пост if (/OP|→/.test(linkPost.innerText)) { return; } const nodePostReply = document.querySelector(`#post-${targetPostNumber}`); if (!nodePostReply) { //console.warn(`Target post #${targetPostNumber} not found.`); // отладка, если пост не найден return; } // Добавляем класс, помечающий что в посте есть ответы (для сворачивания) if (!nodePostReply.classList.contains('has-replies')) { nodePostReply.classList.add('has-replies'); // Добавляем кнопку сворачивания const collapseButton = document.createElement('span'); collapseButton.classList.add('collapse-button'); collapseButton.textContent = '[-]'; collapseButton.title = "Свернуть/Развернуть ветку"; // Добавляем обработчик сворачивания/разворачивания collapseButton.addEventListener('click', (event) => { event.stopPropagation(); // Предотвращаем всплытие, чтобы клик по кнопке не выделял пост const replies = nodePostReply.querySelectorAll(':scope > .post'); // :scope - только непосредственные дочерние .post replies.forEach(reply => { reply.classList.toggle('collapsed'); }); collapseButton.textContent = collapseButton.textContent === '[-]' ? '[+]' : '[-]'; // Меняем текст кнопки }); // Вставляем кнопку сворачивания перед .post__details const postDetails = nodePostReply.querySelector('.post__details'); if (postDetails) { postDetails.parentNode.insertBefore(collapseButton, postDetails); } } nodePostReply.append(nodePostCurr); // Перемещаем // Подсветка новых постов if (isNewPost) { nodePostCurr.classList.add('new-post'); // Добавляем класс для новых // Убираем подсветку при клике (однократно) nodePostCurr.addEventListener("click", () => { nodePostCurr.classList.remove('new-post'); nodePostCurr.style["border-left"] = "2px dashed"; // Добавляем dashed border при клике }, { once: true }); } } // --- Основная логика --- // 1. Обработка существующих постов const initialLinks = document.querySelectorAll(`.post__message > :nth-child(1)[data-num]`); initialLinks.forEach(postMove); // 2. Наблюдение за новыми постами const threadContainer = document.querySelector(".thread"); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(addedNode => { // Проверяем, что добавленный узел - это пост (у него есть класс .post) if (addedNode.classList && addedNode.classList.contains('post')) { const newLink = addedNode.querySelector(`.post__message > :nth-child(1)[data-num]`); if (newLink) { postMove(newLink, true); } } }); } }); }); // 3. Запускаем наблюдение observer.observe(threadContainer, { childList: true }); // 4. Стили addStyle(` .post .post_type_reply { border-left: 2px solid white; /* Исходный цвет границы */ margin-left: 5px; /* небольшой отступ */ padding-left: 5px; } .new-post { border-left-color: yellow !important; /* Подсветка новых постов */ } .post.collapsed { display: none; } .collapse-button{ cursor: pointer; margin-right: 5px; color: #888; /* Серый цвет */ } .has-replies{ position: relative; /* Для позиционирования кнопки */ } `); console.timeEnd("tree script"); })();