2ch Project Redesign - Enhanced

Комплексный редизайн для 2ch.hk

当前为 2025-03-03 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         2ch Project Redesign - Enhanced
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  Комплексный редизайн для 2ch.hk
// @author       You
// @match        https://2ch.hk/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration (with persistence) ---
    const defaultConfig = {
        enableCollapsibleThreads: true,
        enableSmoothScrolling: true,
        enableImageZoom: true,
        enablePostHighlighting: true,
        shadowStrength: 'medium', // 'subtle', 'medium', 'strong'
        enableQuickReplyAbove: false,
        enableAutoUpdate: true,     // Auto-update the thread when enabled
    };

    // Load config from storage
    let config = GM_getValue("dvach_redesign_config", defaultConfig);

    // --- Shadow Styles ---
    const shadowStyles = {
        subtle: '0 2px 5px rgba(0,0,0,0.1)',
        medium: '0 4px 8px rgba(0,0,0,0.2)',
        strong: '0 6px 12px rgba(0,0,0,0.3)',
    };
    const selectedShadow = shadowStyles[config.shadowStrength] || shadowStyles.medium;

    // --- CSS ---
    GM_addStyle(`
        body {
            font-family: sans-serif;
            line-height: 1.6;
            margin: 20px;
            background-color: var(--theme_default_bg, #f0f0f0);
            color: var(--theme_default_text, #333);
        }

        header, main, aside, footer {
            margin-bottom: 20px;
            padding: 15px;
            background-color: var(--theme_default_postbg, #fff);
            border-radius: 8px;
            box-shadow: ${selectedShadow};
        }

        a {
            color: var(--theme_default_link, #007bff);
            text-decoration: none;
        }

        a:hover {
            text-decoration: underline;
        }

        .cntnt {
            display: flex;
            flex-wrap: wrap;
        }

        .cntnt__aside {
            width: 250px;
            margin-right: 20px;
        }

        .cntnt__main {
            flex: 1;
        }

        .post {
            margin-bottom: 15px;
            padding: 10px;
            border: 1px solid var(--theme_default_border, #ddd);
            border-radius: 5px;
            background-color: var(--theme_default_postbg, #f9f9f9);
            transition: background-color 0.2s ease;
            box-shadow: ${selectedShadow};
        }

        /* Post Highlighting */
        ${config.enablePostHighlighting ? `
        .post:hover {
            background-color: var(--theme_default_postbghighlight, #e9ecef);
        }
        ` : ''}

        /* Improved comment structure styling */
        .post_type_reply {
            margin-left: 20px;
            border-left: 3px solid var(--theme_default_link, #007bff);
            padding-left: 10px;
        }
        .post_type_reply:before {
            content: none;
        }


        .post__details {
            font-size: 0.9em;
            color: var(--theme_default_alttext, #777);
        }

        .post__message {
            margin-top: 10px;
        }

        .postform {
            width: 100%;
        }

        .input, .button {
            padding: 8px;
            margin-bottom: 8px;
            border: 1px solid var(--theme_default_btnborder, #ccc);
            border-radius: 4px;
        }

        .button {
            background-color: var(--theme_default_link, #007bff);
            color: white;
            cursor: pointer;
        }

        .button:hover {
            background-color: #0056b3;
        }

        .qr {
            background-color: var(--theme_default_postbg, #fff);
            border: 1px solid var(--theme_default_border, #ccc);
            border-radius: 4px;
            padding: 10px;
            margin-bottom: 10px;
			box-shadow: ${selectedShadow};
        }

        .qr__header {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--theme_default_text, #333);
        }
		.post__images img {
			max-width: 300px;
			height: auto;
			border-radius: 5px;
			margin-right: 10px;
			margin-bottom: 5px;
		}

        /* More general styling for a cleaner look */
        hr {
            border: none;
            border-top: 1px solid var(--theme_default_border, #ccc);
            margin: 20px 0;
        }

		.header__title {
			text-align: center;
			font-size: 2em;
			margin-bottom: 20px;
		}

		/* Basic mobile-responsiveness */
		@media (max-width: 768px) {
			.cntnt {
				flex-direction: column;
			}

			.cntnt__aside {
				width: 100%;
				margin-right: 0;
			}

			.post_type_reply{
				margin-left: 0;
			}
		}

		/* Specific to address sidebar coloring */
		.fm__header {
			background-color: var(--theme_default_postbg);
			color: var(--theme_default_text);
		}

		.fm__sub li a {
			color: var(--theme_default_link); /* Links in sidebar */
		}

        /* Image Zoom */
        ${config.enableImageZoom ? `
        .post__image-link {
            display: inline-block;
            position: relative;
            overflow: hidden;
        }
        .post__image-link:hover img {
            transform: scale(1.2);
            transition: transform 0.2s ease;
            cursor: zoom-in;
        }
        `: ''}

        /* Collapsible Threads */
        ${config.enableCollapsibleThreads ? `
        .thread__collapse-button {
            cursor: pointer;
            color: var(--theme_default_link, #007bff);
            margin-left: 5px;
            font-size: 0.8em;
        }
        .thread__replies.collapsed {
            display: none;
        }
        ` : ''}

        /* Settings Menu Styles */
        #dvach-settings-menu {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: var(--theme_default_postbg, #fff);
            border: 1px solid var(--theme_default_border, #ccc);
            border-radius: 5px;
            padding: 20px;
            z-index: 1000;
            box-shadow: ${selectedShadow};
            min-width: 300px;
        }

        #dvach-settings-menu h2 {
            margin-top: 0;
            margin-bottom: 15px;
            color: var(--theme_default_text, #333);
        }

        #dvach-settings-menu label {
            display: block;
            margin-bottom: 8px;
            color: var(--theme_default_text, #333);
        }
        #dvach-settings-menu select{
            margin-bottom: 8px;
        }

        #dvach-settings-menu button {
            margin-top: 10px;
        }

        #dvach-settings-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 999;
            display: none;
        }
        .newpost__wrapper{
            position: relative;
        }
        #qr-container-top{
            position: absolute;
            top: 35px;
            width: 100%;
        }
        #qr-container-bottom {
            position: relative;
        }

        /* Auto-Update Styling*/

        .autorefresh.modified {
            font-weight: bold;
        }

    `);

    // --- Functions ---
    function GM_addStyle(css) {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
        return style; // Return the style element
    }
    // Collapsible Threads
    function addThreadCollapse() {
        if (!config.enableCollapsibleThreads) return;

        document.querySelectorAll('.thread').forEach(thread => {
            const opPost = thread.querySelector('.post_type_oppost');
            if (!opPost) return;

            const replies = thread.querySelector('.thread__replies') || thread;

            const collapseButton = document.createElement('span');
            collapseButton.classList.add('thread__collapse-button');
            collapseButton.textContent = '[Свернуть]';
            collapseButton.addEventListener('click', () => {
                replies.classList.toggle('collapsed');
                collapseButton.textContent = replies.classList.contains('collapsed') ? '[Развернуть]' : '[Свернуть]';
            });

             // Insert before the refmap, or at the end of details if no refmap
            const refmap = opPost.querySelector('.post__refmap');
			const details = opPost.querySelector(".post__details")
            if (refmap) {
                details.insertBefore(collapseButton, refmap);
            } else {
                details.appendChild(collapseButton);
            }
        });
    }


    // Smooth Scrolling
    function smoothScrollTo(target) {
        if (!config.enableSmoothScrolling) {
            window.location.hash = target;
            return;
        }

        const element = document.querySelector(target);
        if (element) {
            element.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });
        }
    }

    function setupSmoothScrollLinks() {
        document.querySelectorAll('a[href^="#"]').forEach(anchor => {
            anchor.addEventListener('click', function (e) {
                e.preventDefault();
                smoothScrollTo(this.getAttribute('href'));
            });
        });
    }


    // --- Settings Menu ---
    function createSettingsMenu() {
        const overlay = document.createElement('div');
        overlay.id = "dvach-settings-overlay";
        document.body.appendChild(overlay);

        const menu = document.createElement('div');
        menu.id = "dvach-settings-menu";
        menu.innerHTML = `
            <h2>Настройки Dвач Redesign</h2>
            <label><input type="checkbox" data-setting="enableCollapsibleThreads"> Сворачиваемые треды</label>
            <label><input type="checkbox" data-setting="enableSmoothScrolling"> Плавная прокрутка</label>
            <label><input type="checkbox" data-setting="enableImageZoom"> Увеличение изображений при наведении</label>
            <label><input type="checkbox" data-setting="enablePostHighlighting"> Подсветка постов при наведении</label>
            <label><input type="checkbox" data-setting="enableQuickReplyAbove"> Быстрый ответ сверху треда</label>
            <label><input type="checkbox" data-setting="enableAutoUpdate"> Автообновление треда</label>
            <label>
                Сила теней:
                <select data-setting="shadowStrength">
                    <option value="subtle">Слабая</option>
                    <option value="medium">Средняя</option>
                    <option value="strong">Сильная</option>
                </select>
            </label>
            <button id="dvach-settings-save">Сохранить</button>
            <button id="dvach-settings-cancel">Отмена</button>
        `;

        document.body.appendChild(menu);

        // Event listeners for checkboxes
        menu.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
            const settingName = checkbox.dataset.setting;
            checkbox.checked = config[settingName]; // Set initial state

            checkbox.addEventListener('change', () => {
                config[settingName] = checkbox.checked;
                // No need to save here; save on "Сохранить"
            });
        });

        // Event listener for select
        menu.querySelector('select').addEventListener('change', (event) => {
            config.shadowStrength = event.target.value;
             // No need to save here; save on "Сохранить"
        });

        // Set initial value for the select dropdown
        menu.querySelector(`select[data-setting="shadowStrength"]`).value = config.shadowStrength;


        // Save button
        document.getElementById('dvach-settings-save').addEventListener('click', () => {
            GM_setValue("dvach_redesign_config", config); // Save ALL settings
            menu.style.display = 'none';
            overlay.style.display = 'none';
            location.reload(); // Simplest way to apply changes
        });

        // Cancel button
        document.getElementById('dvach-settings-cancel').addEventListener('click', () => {
            menu.style.display = 'none';
            overlay.style.display = 'none';
        });

        // Add to settings link
        const settingsLink = document.getElementById('settings'); // Existing settings link on Dvach
        if(settingsLink) {
			settingsLink.addEventListener('click', (e) => {
				e.preventDefault();
				menu.style.display = 'block';
                overlay.style.display = 'block';
			});
        }
    }

    // --- Quick reply box logic ---
    function createQuickReplyBoxes() {
        if(config.enableQuickReplyAbove){
            // Quick reply box above the thread
            const threadContainerTop = document.querySelector("#TopNormalReply");
            if(threadContainerTop) {
                const qrContainerTop = document.createElement("div");
                qrContainerTop.id = "qr-container-top";
                const qrFormTop = document.getElementById("qr-postform").cloneNode(true);

                // Clear any previous event listeners before adding our own.
                const newSubmitTop = qrFormTop.querySelector("#qr-submit");
                const clonedSubmitTop = newSubmitTop.cloneNode(true);
                newSubmitTop.parentNode.replaceChild(clonedSubmitTop, newSubmitTop);

                clonedSubmitTop.addEventListener("click", (e) => {
                    e.preventDefault();
                    document.getElementById("qr-postform").querySelector("#qr-shampoo").value = qrFormTop.querySelector("#qr-shampoo").value
                    document.getElementById("qr-postform").requestSubmit();
                })
                qrContainerTop.appendChild(qrFormTop);
                threadContainerTop.appendChild(qrContainerTop);
            }
        }
        // Quick reply box at the bottom of thread.
        const threadContainerBot = document.getElementById("BottomNormalReply");

        if(threadContainerBot) {
            const qrContainerBot = document.createElement("div");
            qrContainerBot.id = "qr-container-bottom";
            const qrFormBot = document.getElementById("qr-postform").cloneNode(true);

            // Clear any previous event listeners before adding our own.
            const newSubmitBot = qrFormBot.querySelector("#qr-submit");
            const clonedSubmitBot = newSubmitBot.cloneNode(true);
            newSubmitBot.parentNode.replaceChild(clonedSubmitBot, newSubmitBot);

            clonedSubmitBot.addEventListener("click", (e) => {
                e.preventDefault();
                document.getElementById("qr-postform").querySelector("#qr-shampoo").value = qrFormBot.querySelector("#qr-shampoo").value
                document.getElementById("qr-postform").requestSubmit();
            });
            qrContainerBot.appendChild(qrFormBot);
            threadContainerBot.appendChild(qrContainerBot);
        }
    }

    // --- Auto-Update ---
    function autoUpdateThread() {
        if (config.enableAutoUpdate) {
            const autoRefreshCheckbox = document.querySelector('.js-refresh-checkbox');
            if (autoRefreshCheckbox && !autoRefreshCheckbox.checked) {
                autoRefreshCheckbox.click(); // Simulate a click to enable it.
				autoRefreshCheckbox.parentElement.classList.add('modified');
            }
        }
    }

    // --- Initialization ---

    // Simplify Aside
    const aside = document.querySelector('.cntnt__aside aside');
    if (aside) {
        const fmSubLists = aside.querySelectorAll('.fm__sub');
        const fmList = aside.querySelector('#fmenu');
        fmSubLists.forEach(subList => {
            Array.from(subList.children).forEach(listItem => {
                fmList.appendChild(listItem);
            });
            subList.remove();
        });
    }

    addThreadCollapse();
    setupSmoothScrollLinks();
    createSettingsMenu();
    createQuickReplyBoxes()
    autoUpdateThread(); // Enable auto-update based on config

    // Fix for quick reply close.
    window.addEventListener('load', () => {
        const qrClose = document.getElementById('qr-close');
        if (qrClose) {
            const qrBox = document.getElementById('qr');
			const displayQr = qrBox.style.display;

            //Clear any previous event listners before adding our own
			if (!qrClose.getAttribute('listener')) {
            	qrClose.addEventListener('click', () => {
            	    qrBox.style.display = 'none';
            	});
				qrClose.setAttribute('listener', 'true');
			}
        }
    });

    // --- Tampermonkey Menu Commands ---
    GM_registerMenuCommand("Настройки Dвач Redesign", () => {
        document.getElementById('dvach-settings-menu').style.display = 'block';
        document.getElementById('dvach-settings-overlay').style.display = 'block';
    });
})();