GitHub Toggle Code Wrap

A userscript that adds a code wrap toggle button

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

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