您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Applies a modern, material design with dark mode and syntax highlighting to okmij.org.
// ==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(); } })();