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