GitHub Toggle Code Wrap

A userscript that adds a code wrap toggle button

目前为 2019-07-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Toggle Code Wrap
  3. // @version 1.1.10
  4. // @description A userscript that adds a code wrap toggle button
  5. // @license MIT
  6. // @author StylishThemes
  7. // @namespace https://github.com/StylishThemes
  8. // @include https://github.com/*
  9. // @include https://gist.github.com/*
  10. // @run-at document-idle
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_addStyle
  15. // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=634242
  16. // @icon https://avatars3.githubusercontent.com/u/6145677?v=3&s=200
  17. // ==/UserScript==
  18. /* jshint esnext:true, unused:true */
  19. (() => {
  20. "use strict";
  21. /*
  22. This code is also part of the GitHub-Dark Script
  23. (https://github.com/StylishThemes/GitHub-Dark-Script)
  24. Extracted out into a separate userscript in case users only want
  25. to add this functionality
  26. */
  27. // set by GM popup menu
  28. let globalWrap = GM_getValue("github-global-code-wrap", true),
  29. busy = false;
  30.  
  31. const wrapIcon = `
  32. <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 768 768">
  33. <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"/>
  34. </svg>`,
  35.  
  36. // inline code wrap css
  37. wrapCss = {
  38. "wrapped": "white-space: pre-wrap !important; word-break: break-all !important; overflow-wrap: break-word !important; display: block !important;",
  39. "unwrap": "white-space: pre !important; word-break: normal !important; display: block !important;"
  40. };
  41.  
  42. function findWrap(event) {
  43. const target = event.target;
  44. if (target.classList.contains("ghd-wrap-toggle")) {
  45. toggleClasses(target);
  46. }
  47. }
  48.  
  49. function findSibling(node, selector) {
  50. node = node.parentNode.firstElementChild;
  51. while ((node = node.nextElementSibling)) {
  52. if (node.matches(selector)) {
  53. return node;
  54. }
  55. }
  56. return null;
  57. }
  58.  
  59. function toggleClasses(button) {
  60. let css,
  61. target = findSibling(button, "code, pre, .highlight, .diff-table");
  62. if (!target) {
  63. console.error("Code wrap icon associated code not found", button);
  64. return;
  65. }
  66. // code with line numbers
  67. if (target.nodeName === "TABLE") {
  68. if (target.className.indexOf("wrap-table") < 0) {
  69. css = !globalWrap;
  70. } else {
  71. css = target.classList.contains("ghd-unwrap-table");
  72. }
  73. target.classList.toggle("ghd-wrap-table", css);
  74. target.classList.toggle("ghd-unwrap-table", !css);
  75. button.classList.toggle("wrapped", css);
  76. button.classList.toggle("unwrap", !css);
  77. } else {
  78. css = target.getAttribute("style") || "";
  79. if (css === "") {
  80. css = wrapCss[globalWrap ? "unwrap" : "wrapped"];
  81. } else {
  82. css = wrapCss[css === wrapCss.wrapped ? "unwrap" : "wrapped"];
  83. }
  84. target.setAttribute("style", css);
  85. button.classList.toggle("wrapped", css === wrapCss.wrapped);
  86. button.classList.toggle("unwrap", css === wrapCss.wrapped);
  87. }
  88. }
  89.  
  90. function addCodeWrapButton(button, target) {
  91. target.insertBefore(button.cloneNode(true), target.childNodes[0]);
  92. target.classList.add("ghd-code-wrapper");
  93. }
  94.  
  95. function moveMenu(codeWrap) {
  96. const menu = $("details", codeWrap);
  97. if (menu) {
  98. menu.classList.add("ghd-menu");
  99. codeWrap.parentNode.appendChild(menu);
  100. }
  101. }
  102.  
  103. // Add code wrap toggle
  104. function buildCodeWrap() {
  105. if (busy) {
  106. return;
  107. }
  108. busy = true;
  109.  
  110. // add wrap code buttons
  111. let wrapper = $$(".blob-wrapper"),
  112. indx = wrapper ? wrapper.length : 0;
  113. const button = document.createElement("button");
  114. button.className = "ghd-wrap-toggle tooltipped tooltipped-sw btn btn-sm" +
  115. (globalWrap ? "" : " unwrap");
  116. button.setAttribute("aria-label", "Toggle code wrap");
  117. button.innerHTML = wrapIcon;
  118.  
  119. // Code in table with line numbers
  120. while (indx--) {
  121. if (!$(".ghd-wrap-toggle", wrapper[indx])) {
  122. addCodeWrapButton(button, wrapper[indx]);
  123. moveMenu(wrapper[indx]); // Fixes #66
  124. }
  125. }
  126.  
  127. // Code in markdown comments & wiki pages
  128. wrapper = $$(`
  129. .markdown-body pre:not(.ghd-code-wrapper),
  130. .markdown-format pre:not(.ghd-code-wrapper)`
  131. );
  132. indx = wrapper ? wrapper.length : 0;
  133. while (indx--) {
  134. const pre = wrapper[indx];
  135. const code = $("code", pre);
  136. const wrap = pre.parentNode;
  137. if (code) {
  138. addCodeWrapButton(button, pre);
  139. } else if (wrap.classList.contains("highlight")) {
  140. addCodeWrapButton(button, wrap);
  141. }
  142. }
  143. busy = false;
  144. }
  145.  
  146. function init() {
  147. document.addEventListener("click", findWrap);
  148. $("body").classList.toggle("nowrap", !globalWrap);
  149. buildCodeWrap();
  150. }
  151.  
  152. function $(str, el) {
  153. return (el || document).querySelector(str);
  154. }
  155.  
  156. function $$(str, el) {
  157. return [...(el || document).querySelectorAll(str)];
  158. }
  159.  
  160. // don't initialize if GitHub Dark Script is active
  161. if (!$("#ghd-menu")) {
  162. GM_addStyle(`
  163. /* icons next to a pre */
  164. .ghd-wrap-toggle {
  165. padding: 3px 5px;
  166. position: absolute;
  167. right: 3px;
  168. top: 3px;
  169. -moz-user-select: none;
  170. -webkit-user-select: none;
  171. cursor: pointer;
  172. z-index: 20;
  173. }
  174. .ghd-code-wrapper:not(:hover) .ghd-wrap-toggle {
  175. border-color: transparent !important;
  176. background: transparent !important;
  177. }
  178. .ghd-menu {
  179. margin-top: 45px;
  180. }
  181. /* file & diff code tables */
  182. body .ghd-wrap-table td.blob-code-inner:not(.blob-code-hunk) {
  183. white-space: pre-wrap !important;
  184. word-break: break-all !important;
  185. }
  186. body .ghd-unwrap-table td.blob-code-inner:not(.blob-code-hunk) {
  187. white-space: pre !important;
  188. word-break: normal !important;
  189. }
  190. /* icons for non-syntax highlighted code blocks;
  191. * see https://github.com/gjtorikian/html-proofer/blob/master/README.md
  192. */
  193. .markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) {
  194. right: 3.4em;
  195. }
  196. .ghd-wrap-toggle svg {
  197. height: 14px;
  198. width: 14px;
  199. fill: rgba(110, 110, 110, .4);
  200. pointer-events: none;
  201. vertical-align: text-bottom;
  202. }
  203. .ghd-code-wrapper:hover .ghd-wrap-toggle.unwrap svg,
  204. .ghd-code-wrapper:hover .ghd-wrap-toggle svg {
  205. fill: #8b0000; /* wrap disabled (red) */
  206. }
  207. body:not(.nowrap) .ghd-code-wrapper:hover .ghd-wrap-toggle:not(.unwrap) svg,
  208. .ghd-code-wrapper:hover .ghd-wrap-toggle.wrapped svg {
  209. fill: #006400; /* wrap enabled (green) */
  210. }
  211. .blob-wrapper, .markdown-body pre, .markdown-body .highlight,
  212. .ghd-code-wrapper {
  213. position: relative;
  214. }
  215. /* global code wrap */
  216. body:not(.nowrap) .blob-code-inner:not(.blob-code-hunk),
  217. body:not(.nowrap) .markdown-body pre > code,
  218. body:not(.nowrap) .markdown-body .highlight > pre {
  219. white-space: pre-wrap !important;
  220. word-break: break-all !important;
  221. overflow-wrap: break-word !important;
  222. display: block !important;
  223. }
  224. td.blob-code-inner {
  225. display: table-cell !important;
  226. }
  227. `);
  228.  
  229. document.addEventListener("ghmo:container", buildCodeWrap);
  230. document.addEventListener("ghmo:preview", buildCodeWrap);
  231.  
  232. // Add GM options
  233. GM_registerMenuCommand("Set Global Code Wrap Option", () => {
  234. const body = $("body"),
  235. val = prompt("Global Code Wrap (true/false):", "" + globalWrap);
  236. globalWrap = /^t/.test(val);
  237. GM_setValue("github-global-code-wrap", globalWrap);
  238. body.classList.toggle("nowrap", !globalWrap);
  239. });
  240.  
  241. init();
  242. }
  243.  
  244. })();