GitHub Toggle Code Wrap

A userscript that adds a code wrap toggle button

目前为 2016-04-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Toggle Code Wrap
  3. // @version 1.0.0
  4. // @description A userscript that adds a code wrap toggle button
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace https://github.com/StylishThemes
  7. // @include https://github.com/*
  8. // @run-at document-idle
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_addStyle
  13. // @author StylishThemes
  14. // ==/UserScript==
  15. /* global GM_registerMenuCommand, GM_getValue, GM_setValue, GM_addStyle */
  16. /*jshint unused:true */
  17. (function() {
  18. "use strict";
  19. /*
  20. This code is also part of the GitHub-Dark Script (https://github.com/StylishThemes/GitHub-Dark-Script)
  21. Extracted out into a separate userscript in case users only want to add this functionality
  22. */
  23. var busy = false,
  24.  
  25. // set by GM popup menu
  26. globalWrap = GM_getValue("github-global-code-wrap", true),
  27.  
  28. 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>",
  29.  
  30. // inline code wrap css
  31. wrapCss = {
  32. "wrapped" : "white-space: pre-wrap !important; word-break: break-all !important; display: block !important;",
  33. "unwrap" : "white-space: pre !important; word-break: normal !important; display: block !important;"
  34. },
  35.  
  36. findWrap = function(event) {
  37. var target = event.target;
  38. if (target.classList.contains("ghd-wrap-toggle")) {
  39. toggleClasses(target);
  40. }
  41. },
  42.  
  43. // equivalent to .next("code, pre, .highlight, .diff-table");
  44. findNext = function(el) {
  45. var nextSib = el.nextElementSibling;
  46. if (/code|pre/i.test(nextSib.nodeName) ||
  47. nextSib && (nextSib.classList.contains("highlight") ||
  48. nextSib.classList.contains("diff-table"))) {
  49. return nextSib;
  50. } else {
  51. return el;
  52. }
  53. },
  54.  
  55. toggleClasses = function(icon) {
  56. var css,
  57. code = findNext(icon);
  58. if (code.querySelector("code")) {
  59. code = code.querySelector("code");
  60. }
  61. if (!code) {
  62. console.error("Code wrap icon associated code not found", icon);
  63. return;
  64. }
  65. busy = true;
  66. // code with line numbers
  67. if (code.nodeName === "TABLE") {
  68. if (code.className.indexOf("wrap-table") < 0) {
  69. css = !globalWrap;
  70. } else {
  71. css = code.classList.contains("ghd-unwrap-table");
  72. }
  73. if (css) {
  74. code.classList.add("ghd-wrap-table");
  75. code.classList.remove("ghd-unwrap-table");
  76. icon.classList.add("wrapped");
  77. icon.classList.remove("unwrap");
  78. } else {
  79. code.classList.remove("ghd-wrap-table");
  80. code.classList.add("ghd-unwrap-table");
  81. icon.classList.remove("wrapped");
  82. icon.classList.add("unwrap");
  83. }
  84. } else {
  85. css = code.getAttribute("style") || "";
  86. if (css === "") {
  87. css = wrapCss[globalWrap ? "unwrap" : "wrapped"];
  88. } else {
  89. css = wrapCss[css === wrapCss.wrapped ? "unwrap" : "wrapped"];
  90. }
  91. code.setAttribute("style", css);
  92. if (css === wrapCss.wrapped) {
  93. icon.classList.add("wrapped");
  94. icon.classList.remove("unwrap");
  95. } else {
  96. icon.classList.add("unwrap");
  97. icon.classList.remove("wrapped");
  98. }
  99. }
  100. busy = false;
  101. },
  102.  
  103. getPrevSib = function(el, name) {
  104. var prev = el.previousSibling;
  105. while (prev) {
  106. if (prev.nodeType !== 1 || !prev.classList.contains(name)) {
  107. prev = prev.previousSibling;
  108. } else {
  109. return prev;
  110. }
  111. }
  112. return null;
  113. },
  114.  
  115. // Add code wrap toggle
  116. buildCodeWrap = function() {
  117. // mutation events happen quick, so we still add an update flag
  118. busy = true;
  119. // add wrap code icons
  120. var tmp,
  121. wrapper = document.querySelectorAll(".blob-wrapper"),
  122. indx = wrapper ? wrapper.length : 0,
  123.  
  124. // <div class='ghd-wrap-toggle tooltipped tooltipped-w' aria-label='Toggle code wrap'>
  125. icon = document.createElement("div");
  126. icon.className = "ghd-wrap-toggle tooltipped tooltipped-w";
  127. icon.setAttribute("aria-label", "Toggle code wrap");
  128. icon.innerHTML = wrapIcon;
  129. // $(".blob-wrapper").prepend(wrapIcon);
  130. while (indx--) {
  131. if (!wrapper[indx].querySelector(".ghd-wrap-toggle")) {
  132. wrapper[indx].insertBefore(icon.cloneNode(true), wrapper[indx].childNodes[0]);
  133. }
  134. }
  135.  
  136. // $(".markdown-body pre").before(wrapIcon);
  137. wrapper = document.querySelectorAll(".markdown-body pre");
  138. indx = wrapper ? wrapper.length : 0;
  139. while (indx--) {
  140. tmp = getPrevSib(wrapper[indx], "ghd-wrap-toggle");
  141. if (!tmp) {
  142. wrapper[indx].parentNode.insertBefore(icon.cloneNode(true), wrapper[indx]);
  143. }
  144. }
  145.  
  146. busy = false;
  147. },
  148.  
  149. init = function() {
  150. document.addEventListener("click", findWrap);
  151. if (!globalWrap) {
  152. document.querySelector("body").classList.add("nowrap");
  153. }
  154. buildCodeWrap();
  155. },
  156.  
  157. // DOM targets - to detect GitHub dynamic ajax page loading
  158. targets = document.querySelectorAll([
  159. "#js-repo-pjax-container",
  160. // targeted by ZenHub
  161. "#js-repo-pjax-container > .container",
  162. "#js-pjax-container",
  163. ".js-preview-body"
  164. ].join(","));
  165.  
  166. // don't initialize if GitHub Dark Script is active
  167. if (!document.querySelector("#ghd-menu")) {
  168. GM_addStyle([
  169. // icons next to a pre
  170. ".ghd-wrap-toggle { position:absolute; right:1.4em; margin-top:.2em; -moz-user-select:none; -webkit-user-select:none; cursor:pointer; z-index:20; }",
  171. // file & diff code tables
  172. ".ghd-wrap-table td.blob-code-inner { white-space: pre-wrap !important; word-break: break-all !important; }",
  173. ".ghd-unwrap-table td.blob-code-inner { white-space: pre !important; word-break: normal !important; }",
  174.  
  175. // icons inside a wrapper immediatly around a pre
  176. ".highlight > .ghd-wrap-toggle { right:.5em; top:.5em; margin-top:0; }",
  177. // icons for non-syntax highlighted code blocks; see https://github.com/gjtorikian/html-proofer/blob/master/README.md
  178. ".markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) { right: 3.4em; }",
  179. ".ghd-wrap-toggle svg { height:1.25em; width:1.25em; fill:rgba(110,110,110,.4); pointer-events:none; }",
  180. ".ghd-wrap-toggle.unwrap:hover svg, .ghd-wrap-toggle:hover svg { fill:#8b0000; }", // wrap disabled (red)
  181. "body:not(.nowrap) .ghd-wrap-toggle:not(.unwrap):hover svg, .ghd-wrap-toggle.wrapped:hover svg { fill:#006400; }", // wrap enabled (green)
  182. ".blob-wrapper, .markdown-body pre, .markdown-body .highlight { position:relative; }",
  183. // global code wrap
  184. ".blob-code-inner,",
  185. ".markdown-body pre > code,",
  186. ".markdown-body .highlight > pre {",
  187. "white-space: pre-wrap !important;",
  188. "word-break: break-all !important;",
  189. "display: block !important;",
  190. "}",
  191. "td.blob-code-inner {display: table-cell !important;}"
  192. ].join(""));
  193.  
  194. // update TOC when content changes
  195. Array.prototype.forEach.call(targets, function(target) {
  196. new MutationObserver(function(mutations) {
  197. mutations.forEach(function(mutation) {
  198. // preform checks before adding code wrap to minimize function calls
  199. if (!busy && mutation.target === target) {
  200. buildCodeWrap();
  201. }
  202. });
  203. }).observe(target, {
  204. childList: true,
  205. subtree: true
  206. });
  207. });
  208.  
  209. // Add GM options
  210. GM_registerMenuCommand("Set Global Code Wrap Option", function() {
  211. var body = document.querySelector("body"),
  212. val = prompt("Global Code Wrap (true/false):", globalWrap);
  213. globalWrap = /^t/.test(val);
  214. GM_setValue("github-global-code-wrap", globalWrap);
  215. if (!globalWrap) {
  216. body.classList.add("nowrap");
  217. } else {
  218. body.classList.remove("nowrap");
  219. }
  220. });
  221.  
  222. init();
  223. }
  224.  
  225. })();