🏠 Home 

2ch Project Redesign - Enhanced

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

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