OK Site Beautifier

Applies a modern, material design with dark mode and syntax highlighting to okmij.org.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OK Site Beautifier
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Applies a modern, material design with dark mode and syntax highlighting to okmij.org.
// @author       Your Name
// @match        *://okmij.org/ftp/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-ocaml.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-haskell.min.js
// @resource     PRISM_CSS_LIGHT https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css
// @resource     PRISM_CSS_DARK https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css
// @grant        GM_getResourceText
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. CONFIGURATION ---
    const config = {
        theme: GM_getValue('theme', 'light'), // 'light' or 'dark'
        syntaxHighlighting: GM_getValue('syntaxHighlighting', true) // true or false
    };

     /**
     * Removes common leading whitespace from a code block.
     * Specifically targets the 4-space indent found on the site.
     * @param {string} code The raw code string.
     * @returns {string} The dedented code string.
     */
    function dedent(code) {
        const lines = code.split('\n');

        // Don't process empty or single-line blocks
        if (lines.length <= 1) return code;

        // Check if all content-bearing lines start with 4 spaces.
        const canDedent = lines
            .filter(line => line.trim() !== '') // Ignore empty lines
            .every(line => line.startsWith('    '));

        if (canDedent) {
            return lines.map(line => line.substring(4)).join('\n');
        }

        // If the pattern doesn't match, return the original code.
        return code;
    }

    // --- 2. STYLES ---

    // Load PrismJS syntax highlighting themes
    const prismCssLight = GM_getResourceText('PRISM_CSS_LIGHT');
    const prismCssDark = GM_getResourceText('PRISM_CSS_DARK');

    GM_addStyle(`
        /* --- Base & Variables --- */
        :root {
            --bg-color: #f9f9f9;
            --text-color: #212121;
            --primary-color: #007acc;
            --link-color: #005a99;
            --card-bg: #ffffff;
            --border-color: #e0e0e0;
            --code-bg: #f0f0f0;
            --header-color: #333;
            --hr-color: #ccc;
            --shadow-color: rgba(0, 0, 0, 0.08);
        }

        body.dark-mode {
            --bg-color: #1e1e1e;
            --text-color: #e0e0e0;
            --primary-color: #2196F3;
            --link-color: #64b5f6;
            --card-bg: #2a2a2a;
            --border-color: #424242;
            --code-bg: #333;
            --header-color: #f5f5f5;
            --hr-color: #444;
            --shadow-color: rgba(0, 0, 0, 0.3);
        }

        /* --- General Layout & Typography --- */
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            line-height: 1.7;
            padding: 2rem 1rem;
            max-width: 900px;
            margin: 0 auto;
            transition: background-color 0.3s, color 0.3s;
        }

        h1, h2, h3 {
            color: var(--header-color);
            font-weight: 600;
            margin-top: 2.5em;
            margin-bottom: 1em;
            border-bottom: 1px solid var(--border-color);
            padding-bottom: 0.3em;
        }

        h1 { font-size: 2.5rem; text-align: center; }
        h2 { font-size: 2rem; }
        h3 { font-size: 1.5rem; }

        a {
            color: var(--link-color);
            text-decoration: none;
            transition: color 0.2s;
        }
        a:hover {
            color: var(--primary-color);
            text-decoration: underline;
        }

        hr {
            border: none;
            border-top: 1px solid var(--hr-color);
            margin: 2rem auto;
        }

        /* --- Content Sections (using dl as cards) --- */
        dl {
            background-color: var(--card-bg);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 1.5rem 2rem;
            margin: 2rem 0;
            box-shadow: 0 4px 12px var(--shadow-color);
            transition: background-color 0.3s, border-color 0.3s;
        }

        dt {
            font-weight: bold;
            color: var(--primary-color);
            margin-top: 1em;
        }

        dd {
            margin-left: 0;
            padding-bottom: 1em;
            border-bottom: 1px dashed var(--border-color);
        }
        dd:last-child {
            border-bottom: none;
            padding-bottom: 0;
        }
        dl > dd:first-of-type {
             margin-top: 0.5rem;
        }


        /* --- Lists --- */
        ul {
            padding-left: 20px;
        }
        li {
            margin-bottom: 0.5rem;
        }
        li.separator {
            list-style-type: none;
            height: 1rem;
        }

        /* --- Code Blocks --- */
        pre {
            background-color: var(--code-bg) !important;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 1rem;
            overflow-x: auto;
            font-family: "Fira Code", "Consolas", "Menlo", monospace;
            font-size: 0.9rem;
            line-height: 1.5;
            transition: background-color 0.3s, border-color 0.3s;
        }

        code {
             font-family: "Fira Code", "Consolas", "Menlo", monospace;
        }

        /* Hide syntax highlighting by default, enable with a class */
        .prism-highlight-disabled pre[class*="language-"] {
             color: var(--text-color) !important; /* Override prism styles */
        }
        .prism-highlight-disabled .token {
             all: unset !important; /* Forcefully remove token styling */
        }

        /* --- Navigation Bar --- */
        #navbar {
            text-align: center;
            padding: 1rem 0;
            border-bottom: 1px solid var(--border-color);
        }

        /* --- Footer --- */
        #footer {
            margin-top: 4rem;
            text-align: center;
            font-size: 0.9rem;
            color: #888;
        }
        body.dark-mode #footer {
            color: #777;
        }

        /* --- UI Controls --- */
        #userscript-controls {
            position: fixed;
            bottom: 20px;
            right: 20px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            z-index: 9999;
        }

        #userscript-controls button {
            background-color: var(--card-bg);
            color: var(--text-color);
            border: 1px solid var(--border-color);
            border-radius: 50%;
            width: 48px;
            height: 48px;
            font-size: 24px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 8px var(--shadow-color);
            transition: all 0.2s ease-in-out;
        }
        #userscript-controls button:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow-color);
            color: var(--primary-color);
        }
    `);

    // --- 3. UI CONTROLS (BUTTONS) ---
    const controlsContainer = document.createElement('div');
    controlsContainer.id = 'userscript-controls';
    controlsContainer.innerHTML = `
        <button id="theme-switcher" title="Toggle Light/Dark Mode"></button>
        <button id="highlight-switcher" title="Toggle Syntax Highlighting"></button>
    `;
    document.body.appendChild(controlsContainer);

    const themeSwitcher = document.getElementById('theme-switcher');
    const highlightSwitcher = document.getElementById('highlight-switcher');

    // --- 4. LOGIC & EVENT HANDLERS ---

    // Function to apply syntax highlighting
    function applySyntaxHighlighting() {
        if (config.syntaxHighlighting) {
            document.body.classList.remove('prism-highlight-disabled');
            // Inject correct Prism CSS based on theme
            if (document.getElementById('prism-styles')) document.getElementById('prism-styles').remove();
            const prismStyle = document.createElement('style');
            prismStyle.id = 'prism-styles';
            prismStyle.textContent = (config.theme === 'dark') ? prismCssDark : prismCssLight;
            document.head.appendChild(prismStyle);

            // Add language classes to <pre> tags for Prism
            document.querySelectorAll('pre').forEach(pre => {
                // Heuristic to guess language if not specified
                const codeElement = pre.querySelector('code') || pre;
                codeElement.textContent = dedent(codeElement.textContent);

                const codeContent = pre.textContent;
                let lang = '';
                
                if (/(::|->|=>|data|where|do)\b/.test(codeContent)) {
                    lang = 'haskell';
                } else {
                    lang = "ocaml";

                }

                if (lang) {
                    pre.classList.add(`language-${lang}`);
                    // Prism expects a <code> tag inside <pre>
                    if (!pre.querySelector('code')) {
                        pre.innerHTML = `<code class="language-${lang}">${pre.innerHTML}</code>`;
                    } else {
                         pre.querySelector('code').classList.add(`language-${lang}`);
                    }
                }
            });
            Prism.highlightAll();
        } else {
            document.body.classList.add('prism-highlight-disabled');
        }
        updateHighlightButton();
    }

    // Function to update theme
    function updateTheme() {
        document.body.classList.toggle('dark-mode', config.theme === 'dark');
        themeSwitcher.innerHTML = config.theme === 'dark' ? '☀️' : '🌙';
        // Re-apply highlighting to get the correct theme
        applySyntaxHighlighting();
    }

    // Function to update highlight button
    function updateHighlightButton() {
        highlightSwitcher.innerHTML = '✨';
        highlightSwitcher.style.opacity = config.syntaxHighlighting ? '1' : '0.5';
    }


    // Event Listeners
    themeSwitcher.addEventListener('click', () => {
        config.theme = (config.theme === 'light') ? 'dark' : 'light';
        GM_setValue('theme', config.theme);
        updateTheme();
    });

    highlightSwitcher.addEventListener('click', () => {
        config.syntaxHighlighting = !config.syntaxHighlighting;
        GM_setValue('syntaxHighlighting', config.syntaxHighlighting);
        applySyntaxHighlighting();
    });

    // --- 5. INITIALIZATION ---
    function init() {
        console.log("Oleg Kiselyov's Site Beautifier Initialized.");
        updateTheme(); // This will also call applySyntaxHighlighting
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // The DOM is already ready.
        init();
    }

})();