Greasy Fork

来自缓存

Greasy Fork is available in English.

8chan Style Script

Script to style 8chan

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        8chan Style Script
// @namespace   8chanSS
// @match       *://8chan.moe/*
// @match       *://8chan.se/*
// @grant       none
// @version     1.1
// @author      Anon
// @run-at      document-end
// @description Script to style 8chan
// @license     MIT
// ==/UserScript==

// Header Catalog Links
// Function to append /catalog.html to links
function appendCatalogToLinks() {
    const navboardsSpan = document.getElementById('navBoardsSpan');
    if (navboardsSpan) {
        const links = navboardsSpan.getElementsByTagName('a');
        for (let link of links) {
            if (link.href && !link.href.endsWith('/catalog.html')) {
                link.href += '/catalog.html';
            }
        }
    }
}

// Initial call to append links on page load
appendCatalogToLinks();

// Set up a MutationObserver to watch for changes in the #navboardsSpan div
const observer = new MutationObserver(appendCatalogToLinks);
const config = { childList: true, subtree: true };

const navboardsSpan = document.getElementById('navBoardsSpan');
if (navboardsSpan) {
    observer.observe(navboardsSpan, config);
}


// Scroll to last read post
// Function to save the scroll position
const MAX_PAGES = 10; // Maximum number of pages to store scroll positions
const currentPage = window.location.href;

// Specify pages to exclude from scroll position saving
const excludedPages = [
    '*/catalog.html', // Add any other pages you want to exclude
];

// Function to save the scroll position for the current page
function saveScrollPosition() {
    // Check if the current page is in the excluded pages list
    if (excludedPages.includes(currentPage)) {
        return; // Skip saving scroll position for excluded pages
    }

    const scrollPosition = window.scrollY; // Get the current vertical scroll position
    localStorage.setItem(`scrollPosition_${currentPage}`, scrollPosition); // Store it in localStorage with a unique key

    // Manage the number of stored scroll positions
    manageScrollStorage();
}

// Function to restore the scroll position for the current page
function restoreScrollPosition() {
    const savedPosition = localStorage.getItem(`scrollPosition_${currentPage}`); // Retrieve the saved position for the current page
    if (savedPosition) {
        window.scrollTo(0, parseInt(savedPosition, 10)); // Scroll to the saved position
    }
}

// Function to manage the number of stored scroll positions
function manageScrollStorage() {
    const keys = Object.keys(localStorage).filter(key => key.startsWith('scrollPosition_'));

    // If the number of stored positions exceeds the limit, remove the oldest
    if (keys.length > MAX_PAGES) {
        // Sort keys by their creation time (assuming the order of keys reflects the order of storage)
        keys.sort((a, b) => {
            return localStorage.getItem(a) - localStorage.getItem(b);
        });
        // Remove the oldest entries until we are within the limit
        while (keys.length > MAX_PAGES) {
            localStorage.removeItem(keys.shift());
        }
    }
}

// Event listener to save scroll position before the page unloads
window.addEventListener('beforeunload', saveScrollPosition);

// Restore scroll position when the page loads
window.addEventListener('load', restoreScrollPosition);

// Toggle Announcement & Posting Form
// Create the button
const button = document.createElement('button');
button.style.margin = '10px';
const postingFormDiv = document.getElementById('postingForm');
const announcementDiv = document.getElementById('dynamicAnnouncement');
const panelMessageDiv = document.getElementById('panelMessage');

// Check if divs exist
if (postingFormDiv && announcementDiv && panelMessageDiv) {
    // Insert the button before the announcement div
    postingFormDiv.parentNode.insertBefore(button, postingFormDiv);
    // Retrieve the visibility states from localStorage
    const isPostingFormVisible = localStorage.getItem('postingFormVisible') === 'true';
    const isAnnouncementVisible = localStorage.getItem('announcementVisible') === 'true';
    const isPanelMessageVisible = localStorage.getItem('panelMessageVisible') === 'true';
    // Set the initial state of the divs and button based on stored values
    if (isPostingFormVisible) {
        postingFormDiv.style.display = 'block'; // Show the posting div
    } else {
        postingFormDiv.style.display = 'none'; // Hide the posting div
    }
    if (isAnnouncementVisible) {
        announcementDiv.style.display = 'block'; // Show the announcement div
    } else {
        announcementDiv.style.display = 'none'; // Hide the announcement div
    }
    if (isPanelMessageVisible) {
        panelMessageDiv.style.display = 'block'; // Show the panel message div
    } else {
        panelMessageDiv.style.display = 'none'; // Hide the panel message div
    }
    // Update button text based on the visibility of the announcement div
    button.textContent = (isPostingFormVisible && isAnnouncementVisible && isPanelMessageVisible) ? '-' : '+';
    // Add click event listener to the button
    button.addEventListener('click', () => {
        // Toggle visibility of both divs
        const isCurrentlyVisible = postingFormDiv.style.display !== 'none' && announcementDiv.style.display !== 'none' && panelMessageDiv.style.display !== 'none';

        if (isCurrentlyVisible) {
            postingFormDiv.style.display = 'none'; // Hide the posting div
            announcementDiv.style.display = 'none'; // Hide the announcement div
            panelMessageDiv.style.display = 'none'; // Hide the panel message div
            button.textContent = '+'; // Change button text
            localStorage.setItem('postingFormVisible', 'false'); // Save state
            localStorage.setItem('announcementVisible', 'false'); // Save state
            localStorage.setItem('panelMessageVisible', 'false'); // Save state
        } else {
            postingFormDiv.style.display = 'block'; // Hide the posting div
            announcementDiv.style.display = 'block'; // Show the announcement div
            panelMessageDiv.style.display = 'block'; // Show the panel message div
            button.textContent = '-'; // Change button text
            localStorage.setItem('postingFormVisible', 'true'); // Save state
            localStorage.setItem('announcementVisible', 'true'); // Save state
            localStorage.setItem('panelMessageVisible', 'true'); // Save state
        }
    });
}

// Keyboard Shortcuts
// QR (CTRL+Q)
function toggleDiv(event) {
    // Check if Ctrl + Q is pressed
    if (event.ctrlKey && (event.key === 'q' || event.key === 'Q')) {
        const hiddenDiv = document.getElementById('quick-reply');
        // Toggle QR
        if (hiddenDiv.style.display === 'none' || hiddenDiv.style.display === '') {
            hiddenDiv.style.display = 'block'; // Show the div
        }
        else {
            hiddenDiv.style.display = 'none'; // Hide the div
        }
    }
}
// Add an event listener for keydown events
document.addEventListener('keydown', toggleDiv);


// Custom CSS injection
function addCustomCSS(css) {
    if (!css) return;
    const style = document.createElement('style');
    style.type = 'text/css';
    style.appendChild(document.createTextNode(css));
    document.head.appendChild(style);
}
// Get the current URL path
const currentPath = window.location.pathname.toLowerCase();
const currentHost = window.location.hostname.toLowerCase();

// Apply CSS based on URL pattern
// Thread page CSS
if (/\/res\/[^/]+\.html$/.test(currentPath)) {
    const css = `
/* Quick Reply */
#quick-reply {
display: block;
padding: 0 !important;
top: auto !important;
bottom: 0;
left: auto !important;
position: fixed;
right: 0 !important;
}
#qrbody {
resize: vertical;
max-height: 50vh;
}
.floatingMenu {
padding: 0 !important;
}
#qrFilesBody {
max-width: 300px;
}
/* Banner */
#bannerImage {
width: 305px;
right: 0;
position: fixed;
top: 26px;
}
.innerUtility.top {
margin-top: 2em;
background-color: transparent !important;
color: var(--link-color) !important;
}
.innerUtility.top a {
color: var(--link-color) !important;
}
/* Hover Posts */
img[style*="position: fixed"] {
position: fixed !important;
max-width: 80vw;
max-height: 80vh !important;
z-index: 200;
}
.quoteTooltip {
z-index: 110;
}
/* (You) Replies */
.innerPost:has(.youName) {
border-left: solid #68b723 5px;
}
.innerPost:has(.quoteLink.you) {
border-left: solid #dd003e 5px;
}
/* Filename */
.originalNameLink {
display: inline;
overflow-wrap: anywhere;
white-space: normal;
}
`;
    addCustomCSS(css);
}

if (/^8chan\.(se|moe)$/.test(currentHost)) {
    // General CSS for all pages
    const css = `
/* Margins */
#mainPanel {
margin-left: 10px;
margin-right: 305px;
margin-top: 0;
margin-bottom: 0;
}
.innerPost {
margin-left: 40px;
display: block;
}
/* Cleanup */
#footer,
#actionsForm,
#navTopBoardsSpan,
.coloredIcon.linkOverboard,
.coloredIcon.linkSfwOver,
.coloredIcon.multiboardButton,
#navLinkSpan>span:nth-child(9),
#navLinkSpan>span:nth-child(11),
#navLinkSpan>span:nth-child(13) {
display: none;
}
/* Header */
#dynamicHeaderThread,
.navHeader {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}
/* Thread Watcher */
#watchedMenu .floatingContainer {
min-width: 330px;
}
.quoteTooltip .innerPost {
overflow: hidden;
}
`;
    addCustomCSS(css);
}

// Catalog page CSS
if (/\/catalog\.html$/.test(currentPath)) {
    const css = `
#dynamicAnnouncement {
display: none;
}
#postingForm {
margin: 2em auto;
}
`;
    addCustomCSS(css);
}