GitHub Toggle Code Wrap

A userscript that adds a code wrap toggle button

当前为 2017-04-21 提交的版本,查看 最新版本

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