2ch tree post fork

делает треды древовидными, добавляет сворачивание веток и подсветку новых

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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