- // ==UserScript==
- // @name GitHub Toggle Code Wrap
- // @version 1.0.0
- // @description A userscript that adds a code wrap toggle button
- // @license https://creativecommons.org/licenses/by-sa/4.0/
- // @namespace https://github.com/StylishThemes
- // @include https://github.com/*
- // @run-at document-idle
- // @grant GM_registerMenuCommand
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_addStyle
- // @author StylishThemes
- // ==/UserScript==
- /* global GM_registerMenuCommand, GM_getValue, GM_setValue, GM_addStyle */
- /*jshint unused:true */
- (function() {
- "use strict";
- /*
- This code is also part of the GitHub-Dark Script (https://github.com/StylishThemes/GitHub-Dark-Script)
- Extracted out into a separate userscript in case users only want to add this functionality
- */
- var busy = false,
-
- // set by GM popup menu
- globalWrap = GM_getValue("github-global-code-wrap", true),
-
- wrapIcon = "<svg xmlns='http://www.w3.org/2000/svg' width='768' height='768' viewBox='0 0 768 768'><path d='M544.5 352.5q52.5 0 90 37.5t37.5 90-37.5 90-90 37.5H480V672l-96-96 96-96v64.5h72q25.5 0 45-19.5t19.5-45-19.5-45-45-19.5H127.5v-63h417zm96-192v63h-513v-63h513zm-513 447v-63h192v63h-192z'/></svg>",
-
- // inline code wrap css
- wrapCss = {
- "wrapped" : "white-space: pre-wrap !important; word-break: break-all !important; display: block !important;",
- "unwrap" : "white-space: pre !important; word-break: normal !important; display: block !important;"
- },
-
- findWrap = function(event) {
- var target = event.target;
- if (target.classList.contains("ghd-wrap-toggle")) {
- toggleClasses(target);
- }
- },
-
- // equivalent to .next("code, pre, .highlight, .diff-table");
- findNext = function(el) {
- var nextSib = el.nextElementSibling;
- if (/code|pre/i.test(nextSib.nodeName) ||
- nextSib && (nextSib.classList.contains("highlight") ||
- nextSib.classList.contains("diff-table"))) {
- return nextSib;
- } else {
- return el;
- }
- },
-
- toggleClasses = function(icon) {
- var css,
- code = findNext(icon);
- if (code.querySelector("code")) {
- code = code.querySelector("code");
- }
- if (!code) {
- console.error("Code wrap icon associated code not found", icon);
- return;
- }
- busy = true;
- // code with line numbers
- if (code.nodeName === "TABLE") {
- if (code.className.indexOf("wrap-table") < 0) {
- css = !globalWrap;
- } else {
- css = code.classList.contains("ghd-unwrap-table");
- }
- if (css) {
- code.classList.add("ghd-wrap-table");
- code.classList.remove("ghd-unwrap-table");
- icon.classList.add("wrapped");
- icon.classList.remove("unwrap");
- } else {
- code.classList.remove("ghd-wrap-table");
- code.classList.add("ghd-unwrap-table");
- icon.classList.remove("wrapped");
- icon.classList.add("unwrap");
- }
- } else {
- css = code.getAttribute("style") || "";
- if (css === "") {
- css = wrapCss[globalWrap ? "unwrap" : "wrapped"];
- } else {
- css = wrapCss[css === wrapCss.wrapped ? "unwrap" : "wrapped"];
- }
- code.setAttribute("style", css);
- if (css === wrapCss.wrapped) {
- icon.classList.add("wrapped");
- icon.classList.remove("unwrap");
- } else {
- icon.classList.add("unwrap");
- icon.classList.remove("wrapped");
- }
- }
- busy = false;
- },
-
- getPrevSib = function(el, name) {
- var prev = el.previousSibling;
- while (prev) {
- if (prev.nodeType !== 1 || !prev.classList.contains(name)) {
- prev = prev.previousSibling;
- } else {
- return prev;
- }
- }
- return null;
- },
-
- // Add code wrap toggle
- buildCodeWrap = function() {
- // mutation events happen quick, so we still add an update flag
- busy = true;
- // add wrap code icons
- var tmp,
- wrapper = document.querySelectorAll(".blob-wrapper"),
- indx = wrapper ? wrapper.length : 0,
-
- // <div class='ghd-wrap-toggle tooltipped tooltipped-w' aria-label='Toggle code wrap'>
- icon = document.createElement("div");
- icon.className = "ghd-wrap-toggle tooltipped tooltipped-w";
- icon.setAttribute("aria-label", "Toggle code wrap");
- icon.innerHTML = wrapIcon;
- // $(".blob-wrapper").prepend(wrapIcon);
- while (indx--) {
- if (!wrapper[indx].querySelector(".ghd-wrap-toggle")) {
- wrapper[indx].insertBefore(icon.cloneNode(true), wrapper[indx].childNodes[0]);
- }
- }
-
- // $(".markdown-body pre").before(wrapIcon);
- wrapper = document.querySelectorAll(".markdown-body pre");
- indx = wrapper ? wrapper.length : 0;
- while (indx--) {
- tmp = getPrevSib(wrapper[indx], "ghd-wrap-toggle");
- if (!tmp) {
- wrapper[indx].parentNode.insertBefore(icon.cloneNode(true), wrapper[indx]);
- }
- }
-
- busy = false;
- },
-
- init = function() {
- document.addEventListener("click", findWrap);
- if (!globalWrap) {
- document.querySelector("body").classList.add("nowrap");
- }
- buildCodeWrap();
- },
-
- // DOM targets - to detect GitHub dynamic ajax page loading
- targets = document.querySelectorAll([
- "#js-repo-pjax-container",
- // targeted by ZenHub
- "#js-repo-pjax-container > .container",
- "#js-pjax-container",
- ".js-preview-body"
- ].join(","));
-
- // don't initialize if GitHub Dark Script is active
- if (!document.querySelector("#ghd-menu")) {
- GM_addStyle([
- // icons next to a pre
- ".ghd-wrap-toggle { position:absolute; right:1.4em; margin-top:.2em; -moz-user-select:none; -webkit-user-select:none; cursor:pointer; z-index:20; }",
- // file & diff code tables
- ".ghd-wrap-table td.blob-code-inner { white-space: pre-wrap !important; word-break: break-all !important; }",
- ".ghd-unwrap-table td.blob-code-inner { white-space: pre !important; word-break: normal !important; }",
-
- // icons inside a wrapper immediatly around a pre
- ".highlight > .ghd-wrap-toggle { right:.5em; top:.5em; margin-top:0; }",
- // icons for non-syntax highlighted code blocks; see https://github.com/gjtorikian/html-proofer/blob/master/README.md
- ".markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) { right: 3.4em; }",
- ".ghd-wrap-toggle svg { height:1.25em; width:1.25em; fill:rgba(110,110,110,.4); pointer-events:none; }",
- ".ghd-wrap-toggle.unwrap:hover svg, .ghd-wrap-toggle:hover svg { fill:#8b0000; }", // wrap disabled (red)
- "body:not(.nowrap) .ghd-wrap-toggle:not(.unwrap):hover svg, .ghd-wrap-toggle.wrapped:hover svg { fill:#006400; }", // wrap enabled (green)
- ".blob-wrapper, .markdown-body pre, .markdown-body .highlight { position:relative; }",
- // global code wrap
- ".blob-code-inner,",
- ".markdown-body pre > code,",
- ".markdown-body .highlight > pre {",
- "white-space: pre-wrap !important;",
- "word-break: break-all !important;",
- "display: block !important;",
- "}",
- "td.blob-code-inner {display: table-cell !important;}"
- ].join(""));
-
- // update TOC when content changes
- Array.prototype.forEach.call(targets, function(target) {
- new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- // preform checks before adding code wrap to minimize function calls
- if (!busy && mutation.target === target) {
- buildCodeWrap();
- }
- });
- }).observe(target, {
- childList: true,
- subtree: true
- });
- });
-
- // Add GM options
- GM_registerMenuCommand("Set Global Code Wrap Option", function() {
- var body = document.querySelector("body"),
- val = prompt("Global Code Wrap (true/false):", globalWrap);
- globalWrap = /^t/.test(val);
- GM_setValue("github-global-code-wrap", globalWrap);
- if (!globalWrap) {
- body.classList.add("nowrap");
- } else {
- body.classList.remove("nowrap");
- }
- });
-
- init();
- }
-
- })();