делает треды древовидными
// ==UserScript==
// @name 2ch tree post
// @namespace http://tampermonkey.net/
// @version 2.0
// @description делает треды древовидными
// @author You
// @match http://2ch.hk/*/res/*
// @match https://2ch.hk/*/res/*
// @match http://2ch.life/*/res/*
// @match https://2ch.life/*/res/*
// @match http://2ch.org/*/res/*
// @match https://2ch.org/*/res/*
// @grant none
// @grant GM_addStyle
// ==/UserScript==
(function () {
"use strict";
console.time("tree script");
// Кэшируем часто используемые селекторы
const SELECTORS = {
postLink: ".post__message > :nth-child(1)[data-num]",
thread: ".thread",
};
// Быстрый кэш целей вида #post-123
const postById = new Map();
for (const el of document.querySelectorAll("[id^='post-']")) {
postById.set(el.id, el);
}
function getTargetPostByNum(num) {
if (!num) return null;
const id = `post-${num}`;
const cached = postById.get(id);
if (cached && cached.isConnected) return cached;
const el = document.getElementById(id);
if (el) postById.set(id, el);
return el;
}
// Функция для перемещения поста
function postMove(linkPost, isNewPost = false) {
const postContainer = linkPost.closest(".post");
if (!postContainer) return;
const postNumber = linkPost.dataset.num;
if (!postNumber) return;
const targetPost = getTargetPostByNum(postNumber);
if (!targetPost) return;
// Защита от лишней работы (если уже лежит там же)
if (postContainer.parentElement === targetPost) return;
targetPost.append(postContainer);
if (isNewPost) {
const handleClick = () => {
postContainer.style.borderLeft = "2px dashed";
};
postContainer.addEventListener("click", handleClick, { once: true });
}
}
// Обработка существующих постов
const posts = document.querySelectorAll(SELECTORS.postLink);
posts.forEach(postMove);
// Наблюдение за новыми постами
const thread = document.querySelector(SELECTORS.thread);
if (thread) {
let isRelocating = false;
const observer = new MutationObserver((mutations) => {
if (isRelocating) return;
for (const mutation of mutations) {
if (!mutation.addedNodes || mutation.addedNodes.length === 0) continue;
for (const node of mutation.addedNodes) {
if (!(node instanceof Element)) continue;
// Если добавили сам пост — ограничиваем поиск только им
const scope = node.matches(".post") ? node : node.querySelector?.(".post");
if (!scope) continue;
// Обновим кэш для новых #post-... (если такие появились)
const id = scope.id;
if (id && id.startsWith("post-")) postById.set(id, scope);
const newPostLink = scope.querySelector(SELECTORS.postLink);
if (!newPostLink) continue;
isRelocating = true;
try {
postMove(newPostLink, true);
} finally {
isRelocating = false;
}
}
}
});
observer.observe(thread, { childList: true });
}
console.timeEnd("tree script");
// Оптимизированная функция добавления стилей
function GM_addStyle(css) {
const styleId = "GM_addStyleBy8626";
let style = document.getElementById(styleId);
if (!style) {
style = document.createElement("style");
style.id = styleId;
style.type = "text/css";
document.head.appendChild(style);
}
style.sheet.insertRule(
css,
(style.sheet.rules || style.sheet.cssRules || []).length
);
}
GM_addStyle(".post .post_type_reply { border-left-color: white; }");
})();