OK Site Beautifier

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

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();
    }

})();