您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A userscript that shows whitespace (space, tabs and carriage returns) in code blocks
当前为
// ==UserScript== // @name GitHub Code Show Whitespace // @version 0.1.0 // @description A userscript that shows whitespace (space, tabs and carriage returns) in code blocks // @license https://opensource.org/licenses/MIT // @namespace https://github.com/Mottie // @include https://github.com/* // @run-at document-idle // @grant GM_addStyle // @author Rob Garrison // ==/UserScript== (() => { "use strict"; // include em-space & en-space? const whitespace = { "%20" : "<span class='pl-space ghcw-whitespace'> </span>", "%A0" : "<span class='pl-nbsp ghcw-whitespace'> </span>", "%09" : "<span class='pl-tab ghcw-whitespace'>\x09</span>", // non-matching key; applied manually "CRLF" : "<span class='pl-crlf ghcw-whitespace'></span>\n" }, // ignore +/- in diff code blocks regexWS = /^(?:[+-]*)(\x20| |\x09)+/g, regexCR = /\r*\n$/, regexTabSize = /\btab-size-\d\b/g, toggleButton = document.createElement("div"); toggleButton.className = "ghcw-toggle btn btn-sm tooltipped tooltipped-n"; toggleButton.setAttribute("aria-label", "Toggle Whitespace"); toggleButton.innerHTML = "<span class='pl-tab'></span>"; GM_addStyle(` .highlight .blob-code-inner { tab-size: 2; } /* GitHub-Dark overrides the above setting */ .highlight.ghcw-active.tab-size-2 .pl-tab { width: 1.1em; } .highlight.ghcw-active.tab-size-4 .pl-tab { width: 2.2em; } .highlight.ghcw-active.tab-size-6 .pl-tab { width: 3.3em; } .highlight.ghcw-active.tab-size-8 .pl-tab { width: 4.4em; } .ghcw-active .ghcw-whitespace { position: relative; display: inline-block; } .ghcw-active .ghcw-whitespace:before { position: absolute; overflow: hidden; opacity: .4; user-select: none; } .ghcw-toggle .pl-tab { pointer-events: none; } .ghcw-active .pl-space:before { content: "\\b7"; } .ghcw-active .pl-nbsp:before { content: "\\b7"; } .ghcw-active .pl-tab:before, .ghcw-toggle .pl-tab:before { content: "\\bb"; } .ghcw-active .pl-crlf:before { content: "\\231d"; top: -.75em; } `); let debounce, busy = false, observers = []; function addToggle() { $$(".file-actions").forEach(el => { if (!$(".ghcw-toggle", el)) { el.insertBefore(toggleButton.cloneNode(true), el.childNodes[0]); } }); } function addWhitespace(block) { let lines, indx, len; if (block && !block.classList.contains("ghcw-processed")) { block.classList.add("ghcw-processed"); updateTabSize(block); indx = 0; // class name of each code row lines = $$(".blob-code-inner:not(.blob-code-hunk)", block); len = lines.length; // loop with delay to allow user interaction const loop = () => { let line, // max number of DOM insertions per loop max = 0; while (max < 50 && indx < len) { if (indx >= len) { return; } line = lines[indx]; line.innerHTML = line.innerHTML .replace(regexWS, s => { let idx = 0, ln = s.length, result = ""; for (idx = 0; idx < ln; idx++) { result += whitespace[encodeURI(s[idx])] || s[idx] || ""; } return result; }) // remove end CRLF if it exists .replace(regexCR, "") + whitespace.CRLF; max++; indx++; } if (indx < len) { setTimeout(() => { loop(); }, 200); } }; loop(); } } function updateTabSize(block) { // remove previous tab-size setting block.className = block.className.replace(regexTabSize, " "); // calculate tab-size; GitHub-Dark allows user modification const len = window.getComputedStyle($(".blob-code-inner", block)).tabSize; block.classList.add(`tab-size-${len}`); } function removeObservers() { // remove saved observers observers.forEach(observer => { if (observer) { observer.disconnect(); } }); observers = []; } function addObservers() { // Observe progressively loaded content $$(` .js-diff-progressive-container, .js-diff-load-container` ).forEach(target => { const obsrvr = new MutationObserver(mutations => { mutations.forEach(mutation => { // preform checks before adding code wrap to minimize function calls const tar = mutation.target; if (!busy && tar && ( tar.classList.contains("js-diff-progressive-container") || tar.classList.contains("js-diff-load-container") || tar.classList.contains("blob-wrapper") ) ) { clearTimeout(debounce); debounce = setTimeout(() => { addToggle(); }, 500); } }); }); obsrvr.observe(target, { childList: true, subtree: true }); observers.push(obsrvr); }); } function init() { // toggle added to diff & file view addToggle(); removeObservers(); if (document.querySelector("#files.diff-view")) { addObservers(); } } function $(selector, el) { return (el || document).querySelector(selector); } function $$(selector, el) { return Array.from((el || document).querySelectorAll(selector)); } function closest(selector, el) { while (el && el.nodeType === 1) { if (el.matches(selector)) { return el; } el = el.parentNode; } return null; } // bind whitespace toggle button document.addEventListener("click", event => { const target = event.target; if ( target.nodeName === "DIV" && target.classList.contains("ghcw-toggle") ) { let block = $(".highlight", closest(".file", target)); if (block) { target.classList.toggle("selected"); block.classList.toggle("ghcw-active"); updateTabSize(block); addWhitespace(block); } } }); document.addEventListener("pjax:end", init); setTimeout(() => { init(); }, 500); })();