GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

Verze ze dne 21. 01. 2020. Zobrazit nejnovější verzi.

  1. // ==UserScript==
  2. // @name GitHub Dark Script
  3. // @version 2.5.7
  4. // @description GitHub Dark in userscript form, with a settings panel
  5. // @license MIT
  6. // @author StylishThemes
  7. // @namespace https://github.com/StylishThemes
  8. // @include /^https?://((blog|gist|guides|help|raw|status|developer)\.)?github\.com/((?!generated_pages\/preview).)*$/
  9. // @include https://*.githubusercontent.com/*
  10. // @include https://*graphql-explorer.githubapp.com/*
  11. // @run-at document-start
  12. // @inject-into content
  13. // @grant GM.addStyle
  14. // @grant GM_addStyle
  15. // @grant GM.getValue
  16. // @grant GM_getValue
  17. // @grant GM.setValue
  18. // @grant GM_setValue
  19. // @grant GM.info
  20. // @grant GM_info
  21. // @grant GM.xmlHttpRequest
  22. // @grant GM_xmlhttpRequest
  23. // @grant GM.registerMenuCommand
  24. // @grant GM_registerMenuCommand
  25. // @connect githubusercontent.com
  26. // @connect raw.githubusercontent.com
  27. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  28. // @require https://greatest.deepsurf.us/scripts/15563-jscolor/code/jscolor.js?version=106439
  29. // @require https://greatest.deepsurf.us/scripts/28721-mutations/code/mutations.js?version=634242
  30. // @icon https://avatars3.githubusercontent.com/u/6145677?v=3&s=200
  31. // @homepageURL https://github.com/StylishThemes/GitHub-Dark-Script
  32. // ==/UserScript==
  33. /* global jscolor */
  34. (async () => {
  35. "use strict";
  36.  
  37. const version = GM.info.script.version,
  38.  
  39. // delay until package.json allowed to load
  40. delay = 8.64e7, // 24 hours in milliseconds
  41.  
  42. // Keyboard shortcut to open ghd panel (only a two key combo coded)
  43. keyboardOpen = "g+0",
  44. keyboardToggle = "g+-",
  45. // keyboard shortcut delay from first to second letter
  46. keyboardDelay = 1000,
  47.  
  48. // base urls to fetch style and package.json
  49. root = "https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/",
  50.  
  51. defaults = {
  52. attach: "scroll",
  53. color: "#4183C4",
  54. enable: true,
  55. font: "Menlo",
  56. image: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGBAMAAACDAP+3AAAAGFBMVEUfHx8eHh4dHR0bGxshISEiIiIlJSUjIyM9IpsJAAAFjUlEQVR4AT3UuZLcOBaF4QuI2XJxboIhF/eQFe1WovoBAAqccpkaZpc5+4yrXa8/RGpx/lrIXPjFCYjTp9z8REqF4VYNWB3Av3zQJ6b6xBwlKB/9kRkCjXVwGH3ziK5UcjFHVkmgY6osiBsGDFfseqq2ZbTz7E00qBDpzOxnD7ToABeros1vM6MX0rBQaG1ith1A/HJkvkHxsPGJ82dP8vVCyWmbyPTaAfGzg40bgIdrv2f3pBVPycUcufx+BSUUWDuCZi6zBqdM50ElKYPODqtLDjc31rBb9CZ59lbN/JScuMxHLUBcGiy6QRH9zpwgZGhRj8qSydPVgNNVgbWqYX3HbM9K2rqTnKVmsmwKWzc1ffEd20+Zq3Ji65kl6TSjALNvzmJt4Pi2f1etytGJmy5erLAgbNY4bjykC3YCLIS3nSZMKgwRsBarWgjdeVzIEDzpTkoOUArTF4WFXYHwxY585sT0nmTYMxmXfs8fzwswfnam8TMU49bvqSRnyRPnqlno4tVQQiH2A9Za8tNTfXQ0lxbSxUaZna0uLlj9Q0XzD96CpsOZUftolINKBWJpAOoAJC0T6QqZnOtfvcfJFcDrD4Cuy5Hng316XrqzJ204HynyHwWed6i+XGF40Uw2T7Lc71HyssngEOrgONfBY7wvW0UZdVAma5xmSNjRp3xkvKJkW6aSg7PK4K0+mbKqYB0WYBgWwxCXiS74zBCVlEFpYQDEwjcA1qccb5yO6ZL8ozt/h3wHSCdWzLuqxU2ZZ9ev9MvRMbMvV9BQgN0qrFjlkzPQanI9nuaGCokVK2LV1Y2egyY1aFQGxjM9I7RBBAgyGEJtpKHP0lUySSeWCpyKHMT2pmM/vyP55u2Rw5lcSeabAfgiG5TPDX3uP3QvcoSipJXQByUCjS4C8VXqxEEZOJxzmJoyogFNJBRsCJs2XmoWWrWFqTsnbwtSn43gNFTTob9/SEpaPJNhUBKDGoZGCMINxvBv8vuKbb//lg/sK0wfPgBica/QsSk5F3KK4Ui6Yw+uv4+DWEOFbhdPOnbY5PLFpzrZMhakeqomY0Vz0TO+elQGTWdCk1IYFAOaoZg0IJQhT+YreXF+yia+O1cgtGufjXxQw28f85RPXfd15zv13ABoD15kB7FKJ/7pbHKP6+9TgNgkVj68NeV8Tp24f7OOndCgJzR3RNJBPNFReCmstMVqvjjzBoeK4GOFoBN32CPxu+4TwwBDa4DJTe/OU9c9ku7EGyfOVxh+fw9g/AATxPqKTEXJKEdCIBkB4iBUlO6MjUrWi6M5Kz31YAqFsYaCeB0KJC5d1+foo3LQWSfRaDrwdAQrMEC27yDZXJf7TlOJ2Bczr1di3OWvZB6XrvvqPuWJPDk9dAHgm7LvuZJTEdKqO3J3XgostArEnvkqgUznx3PX7cSzz1FXZyvakTA4XVVMbCPFPK1cFj66S0WoqQI1XG2uoU7CMPquO2VaUDJFQMdVgXKD2bpz6ufzzxXbxszHQ9fGO/F7A998yBQG6cShE+P+Pk7t1FwfF1QHN1Eui1VapRxCdj8tCtI1bog1Fo011Sx9u3o6c9bufI6wAT26Av9xJ+WWpTKbbBPp3K/1LbC4Vuhv396RCbJw4untjxVPndj+dIB9dVD8z2dylZ+6vMeJwbYChHJkvHV2J3fdHsJPASeHhrXq6QheXu1nBhUr5u6ryT0I13BFKD01ViZ/n3oaziRG7c6Ayg7g1LPeztNdT36ueMqcN4XGv3finjfv+7I/kMJ4d046MUanOA1QtMH1kLlfFasm99NiutSw63yNDeH4zeL1Uu8XKHNfcThPSSNwchGMbgUETScwkCcK77pH2jsgrAssvVyB8FLJ7GrmwyD8eVqsHoY/FwIv9T7lPu9+Yf8/9+w4nS1ma78AAAAASUVORK5CYII=')",
  57. tab: 0, // 0 is disabled
  58. theme: "Twilight", // GitHub
  59. themeCm: "Twilight", // CodeMirror
  60. themeJp: "Twilight", // Jupyter
  61. type: "tiled",
  62. wrap: false,
  63.  
  64. // toggle buttons
  65. enableCodeWrap: true,
  66. enableMonospace: true,
  67. // diff toggle + accordion mode
  68. modeDiffToggle: "1",
  69.  
  70. // internal variables
  71. date: 0,
  72. version: 0,
  73. rawCss: "",
  74. cssgithub: "",
  75. csscodemirror: "",
  76. cssjupyter: "",
  77. processedCss: ""
  78. },
  79.  
  80. // extract style & theme name
  81. regex = /\/\*! [^*]+ \*\//,
  82. themesXref = {
  83. github: {
  84. placeholder: "syntax-theme",
  85. folder: "themes/github/"
  86. },
  87. codemirror: {
  88. placeholder: "syntax-codemirror",
  89. folder: "themes/codemirror/"
  90. },
  91. jupyter: {
  92. placeholder: "syntax-jupyter",
  93. folder: "themes/jupyter/"
  94. }
  95. },
  96. // available theme names
  97. themes = {
  98. github: {
  99. "Ambiance": "ambiance",
  100. "Chaos": "chaos",
  101. "Clouds Midnight": "clouds-midnight",
  102. "Cobalt": "cobalt",
  103. "GitHub Dark": "github-dark",
  104. "Idle Fingers": "idle-fingers",
  105. "Kr Theme": "kr-theme",
  106. "Merbivore": "merbivore",
  107. "Merbivore Soft": "merbivore-soft",
  108. "Mono Industrial": "mono-industrial",
  109. "Mono Industrial Clear": "mono-industrial-clear",
  110. "Monokai": "monokai",
  111. "Monokai Spacegray Eighties": "monokai-spacegray-eighties",
  112. "Obsidian": "obsidian",
  113. "One Dark": "one-dark",
  114. "Pastel on Dark": "pastel-on-dark",
  115. "Railscasts": "railscasts",
  116. "Solarized Dark": "solarized-dark",
  117. "Terminal": "terminal",
  118. "Tomorrow Night": "tomorrow-night",
  119. "Tomorrow Night Blue": "tomorrow-night-blue",
  120. "Tomorrow Night Bright": "tomorrow-night-bright",
  121. "Tomorrow Night Eigthies": "tomorrow-night-eighties",
  122. "Twilight": "twilight",
  123. "Vibrant Ink": "vibrant-ink"
  124. },
  125. // CodeMirror themes
  126. codemirror: {
  127. "Ambiance": "ambiance",
  128. "Base16 Ocean Dark": "base16-ocean-dark",
  129. "Cobalt": "cobalt",
  130. "Dracula": "dracula",
  131. "Material": "material",
  132. "Monokai": "monokai",
  133. "Monokai Spacegray Eighties": "monokai-spacegray-eighties",
  134. "One Dark": "one-dark",
  135. "Pastel on Dark": "pastel-on-dark",
  136. "Railscasts": "railscasts",
  137. "Solarized Dark": "solarized-dark",
  138. "Tomorrow Night Bright": "tomorrow-night-bright",
  139. "Tomorrow Night Eigthies": "tomorrow-night-eighties",
  140. "Twilight": "twilight",
  141. "Vibrant Ink": "vibrant-ink"
  142. },
  143. // Jupyter (pygments) themes
  144. jupyter: {
  145. "Base16 Ocean Dark": "base16-ocean",
  146. "Dracula": "dracula",
  147. "GitHub Dark": "github-dark",
  148. "Idle Fingers": "idle-fingers",
  149. "Monokai": "monokai",
  150. "Monokai Spacegray Eighties": "monokai-spacegray-eighties",
  151. "Obsidian": "obsidian",
  152. "Pastel on Dark": "pastel-on-dark",
  153. "Railscasts": "railscasts",
  154. "Solarized Dark": "solarized-dark",
  155. "Tomorrow Night": "tomorrow-night",
  156. "Tomorrow Night Blue": "tomorrow-night-blue",
  157. "Tomorrow Night Bright": "tomorrow-night-bright",
  158. "Tomorrow Night Eigthies": "tomorrow-night-eighties",
  159. "Twilight": "twilight"
  160. }
  161. },
  162.  
  163. type = {
  164. tiled: `
  165. background-repeat: repeat !important;
  166. background-size: auto !important;
  167. background-position: left top !important;
  168. `,
  169. fit: `
  170. background-repeat: no-repeat !important;
  171. background-size: cover !important;
  172. background-position: center top !important;
  173. `
  174. },
  175.  
  176. wrapCss = {
  177. wrapped: `
  178. white-space: pre-wrap !important;
  179. word-break: break-all !important;
  180. overflow-wrap: break-word !important;
  181. display: block !important;
  182. `,
  183. unwrap: `
  184. white-space: pre !important;
  185. word-break: normal !important;
  186. display: block !important;
  187. `
  188. },
  189.  
  190. // https://github.com/StylishThemes/GitHub-code-wrap/blob/master/github-code-wrap.css
  191. wrapCodeCss = `
  192. /* GitHub: Enable wrapping of long code lines */
  193. .blob-code-inner:not(.blob-code-hunk),
  194. .markdown-body pre > code,
  195. .markdown-body .highlight > pre { ${wrapCss.wrapped} }
  196. td.blob-code-inner {
  197. display: table-cell !important;
  198. }
  199. `,
  200.  
  201. wrapIcon = `
  202. <svg xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768">
  203. <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"/>
  204. </svg>
  205. `,
  206.  
  207. monospaceIcon = `
  208. <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 32 32">
  209. <path d="M5.91 7.31v8.41c0 .66.05 1.09.14 1.31.09.21.23.37.41.48.18.11.52.16 1.02.16v.41H2.41v-.41c.5 0 .86-.05 1.03-.14.16-.11.3-.27.41-.5.11-.23.16-.66.16-1.3V11.7c0-1.14-.04-1.87-.11-2.2-.04-.26-.13-.42-.24-.53-.11-.1-.27-.14-.46-.14-.21 0-.48.05-.77.18l-.18-.41 3.14-1.28h.52v-.01zm-.95-5.46c.32 0 .59.11.82.34.23.23.34.5.34.82 0 .32-.11.59-.34.82-.23.22-.51.34-.82.34-.32 0-.59-.11-.82-.34s-.36-.5-.36-.82c0-.32.11-.59.34-.82.24-.23.51-.34.84-.34zm19.636 19.006h-3.39v-1.64h5.39v9.8h3.43v1.66h-9.18v-1.66h3.77v-8.16h-.02zm.7-6.44c.21 0 .43.04.63.13.18.09.36.2.5.34s.25.3.34.5c.07.18.13.39.13.61 0 .22-.04.41-.13.61s-.19.36-.34.5-.3.25-.5.32c-.2.09-.39.13-.62.13-.21 0-.43-.04-.61-.12-.19-.07-.35-.19-.5-.34-.14-.14-.25-.3-.34-.5-.07-.2-.13-.39-.13-.61s.04-.43.13-.61c.07-.18.2-.36.34-.5s.31-.25.5-.34c.17-.09.39-.12.6-.12zM2 30L27.82 2H30L4.14 30H2z"/>
  210. </svg>
  211. `,
  212.  
  213. fileIcon = `
  214. <svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="10" height="6.5" viewBox="0 0 10 6.5">
  215. <path d="M0 1.5L1.5 0l3.5 3.7L8.5.0 10 1.5 5 6.5 0 1.5z"/>
  216. </svg>
  217. `,
  218.  
  219. $style = make({
  220. el: "style",
  221. cl4ss: "ghd-style"
  222. });
  223.  
  224. let timer, picker; // jscolor picker
  225. let isInitialized = "pending";
  226. // prevent mutationObserver from going nuts
  227. let isUpdating = false;
  228. // set when css code to test is pasted into the settings panel
  229. let testing = false;
  230. const debug = true;
  231. let data = {};
  232.  
  233. function updatePanel() {
  234. if (!isInitialized || !$("#ghd-settings-inner")) { return }
  235. // prevent multiple change events from processing
  236. isUpdating = true;
  237.  
  238. let temp;
  239. const body = $("body");
  240. const panel = $("#ghd-settings-inner");
  241.  
  242. $(".ghd-attach", panel).value = data.attach || defaults.attach;
  243. $(".ghd-font", panel).value = data.font || defaults.font;
  244. $(".ghd-image", panel).value = data.image || defaults.image;
  245. $(".ghd-tab", panel).value = data.tab || defaults.tab;
  246. $(".ghd-theme", panel).value = data.theme || defaults.theme;
  247. $(".ghd-themecm", panel).value = data.themeCm || defaults.themeCm;
  248. $(".ghd-themejp", panel).value = data.themeJp || defaults.themeJp;
  249. $(".ghd-type", panel).value = data.type || defaults.type;
  250.  
  251. $(".ghd-enable", panel).checked = isBool("enable");
  252. $(".ghd-wrap", panel).checked = isBool("wrap");
  253.  
  254. $(".ghd-codewrap-checkbox", panel).checked = isBool("enableCodeWrap");
  255. $(".ghd-monospace-checkbox", panel).checked = isBool("enableMonospace");
  256.  
  257. const el = $(".ghd-diff-select", panel);
  258. temp = `${data.modeDiffToggle || defaults.modeDiffToggle}`;
  259. el.value = temp;
  260. toggleClass(el, "enabled", temp !== "0");
  261.  
  262. // update version tooltip
  263. $(".ghd-versions", panel).setAttribute("aria-label", getVersionTooltip());
  264.  
  265. temp = data.color || defaults.color;
  266. $(".ghd-color").value = temp;
  267. // update swatch color & color picker value
  268. $("#ghd-swatch").style.backgroundColor = temp;
  269.  
  270. if (picker) {
  271. picker.fromString(temp);
  272. }
  273. $style.disabled = !data.enable;
  274.  
  275. toggleClass(body, "ghd-disabled", !data.enable);
  276. toggleClass(body, "nowrap", !data.wrap);
  277.  
  278. if (data.enableCodeWrap !== data.lastCW ||
  279. data.enableMonospace !== data.lastMS ||
  280. data.modeDiffToggle !== data.lastDT) {
  281. data.lastCW = data.enableCodeWrap;
  282. data.lastMS = data.enableMonospace;
  283. data.lastDT = data.modeDiffToggle;
  284. updateToggles();
  285. }
  286.  
  287. isUpdating = false;
  288. }
  289.  
  290. async function getStoredValues(init) {
  291. data = await GM.getValue("data", defaults);
  292. try {
  293. data = JSON.parse(data);
  294. if (!Object.keys(data).length || ({}).toString.call(data) !== "[object Object]") {
  295. throw new Error();
  296. }
  297. } catch (err) { // compat
  298. data = await GM.getValue("data", defaults);
  299. }
  300. if (debug) {
  301. if (init) {
  302. console.info("GitHub-Dark Script initializing!");
  303. }
  304. console.info("Retrieved stored values", data);
  305. }
  306. }
  307.  
  308. async function setStoredValues(reset) {
  309. data.processedCss = $style.textContent;
  310. await GM.setValue("data", JSON.stringify(reset ? defaults : data));
  311. updatePanel();
  312. if (debug) {
  313. console.info(`${reset ? "Resetting" : "Saving"} current values`, data);
  314. }
  315. }
  316.  
  317. // modified from http://stackoverflow.com/a/5624139/145346
  318. function hexToRgb(hex) {
  319. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  320. return result ? [
  321. parseInt(result[1], 16),
  322. parseInt(result[2], 16),
  323. parseInt(result[3], 16)
  324. ].join(", ") : "";
  325. }
  326.  
  327. // convert version "1.2.3" into "001002003" for easier comparison
  328. function convertVersion(val) {
  329. let index;
  330. const parts = val ? val.split(".") : "";
  331. let str = "";
  332. const len = parts.length;
  333.  
  334. for (index = 0; index < len; index++) {
  335. str += (`000${parts[index]}`).slice(-3);
  336. }
  337. if (debug) {
  338. console.info(`Converted version "${val}" to "${str}" for easy comparison`);
  339. }
  340. return val ? str : val;
  341. }
  342.  
  343. function checkVersion() {
  344. if (debug) {
  345. console.info("Fetching package.json");
  346. }
  347. GM.xmlHttpRequest({
  348. method: "GET",
  349. url: `${root}package.json`,
  350. onload: response => {
  351. const pkg = JSON.parse(response.responseText);
  352.  
  353. // save last loaded date, so package.json is only loaded once a day
  354. data.date = new Date().getTime();
  355.  
  356. const ver = convertVersion(pkg.version);
  357. // if new available, load it & parse
  358. if (ver > data.version) {
  359. if (data.version !== 0 && debug) {
  360. console.info(`Updating from ${data.version} to ${ver}`);
  361. }
  362. data.version = ver;
  363. fetchAndApplyStyle();
  364. } else {
  365. addSavedStyle();
  366. }
  367. // save new date/version
  368. GM.setValue("data", JSON.stringify(data));
  369. }
  370. });
  371. }
  372.  
  373. function fetchAndApplyStyle() {
  374. if (debug) {
  375. console.info(`Fetching ${root}github-dark.css`);
  376. }
  377. GM.xmlHttpRequest({
  378. method: "GET",
  379. url: `${root}github-dark.css`,
  380. onload: response => {
  381. data.rawCss = response.responseText;
  382. processStyle();
  383. }
  384. });
  385. }
  386.  
  387. // load syntax highlighting theme
  388. function fetchAndApplyTheme(name, group) {
  389. if (!data.enable) {
  390. if (debug) {
  391. console.info("Disabled: stop theme processing");
  392. }
  393. return;
  394. }
  395. if (data[`last${group}`] === name && data[`css${group}`] !== "") {
  396. return applyTheme(name, group);
  397. }
  398. const themeUrl = `${root}${themesXref[group].folder}${themes[group][name]}.min.css`;
  399. if (debug) {
  400. console.info(`Fetching ${group} ${name} theme`, themeUrl);
  401. }
  402. GM.xmlHttpRequest({
  403. method: "GET",
  404. url: themeUrl,
  405. onload: response => {
  406. let theme = response.responseText;
  407. if (response.status === 200 && theme) {
  408. data[`css${group}`] = theme;
  409. data[`last${group}`] = name;
  410. applyTheme(name, group);
  411. } else {
  412. theme = data[`css${group}`];
  413. console.error(`Failed to load ${group} theme file: "${name}"`);
  414. console.info(`Falling back to previous ${group} theme of ${theme.substring(0, theme.indexOf("*/") + 2)}`);
  415. }
  416. }
  417. });
  418. }
  419.  
  420. async function applyTheme(_name, group) {
  421. let theme, css;
  422. if (debug) {
  423. theme = (data[`css${group}`] || "").match(regex);
  424. console.info(`Adding syntax ${group} theme "${theme}" to css`);
  425. }
  426. css = data.processedCss || "";
  427. css = css.replace(
  428. `/*[[${themesXref[group].placeholder}]]*/`,
  429. data[`css${group}`] || ""
  430. );
  431. applyStyle(css);
  432. await setStoredValues();
  433. isUpdating = false;
  434. }
  435.  
  436. function setTabSize() {
  437. return data.tab > 0 ?
  438. `pre, .highlight, .diff-table, .tab-size {
  439. tab-size: ${data.tab} !important;
  440. -moz-tab-size: ${data.tab} !important;
  441. }` :
  442. "";
  443. }
  444.  
  445. function processStyle() {
  446. const url = /^url/.test(data.image || "") ? data.image :
  447. (data.image === "none" ? "none" : `url('${data.image}')`);
  448. if (!data.enable) {
  449. if (debug) {
  450. console.info("Disabled: stop processing");
  451. }
  452. return;
  453. }
  454. if (debug) {
  455. console.info("Processing set styles");
  456. }
  457.  
  458. const processed = (data.rawCss || "")
  459. // remove moz-document wrapper
  460. .replace(/@-moz-document regexp\((.*)\) \{(\n|\r)+/, "")
  461. // replace background image; if no "url" at start, then use "none"
  462. .replace(/\/\*\[\[bg-choice\]\]\*\/ url\(.*\)/, url)
  463. // Add tiled or fit window size css
  464. .replace("/*[[bg-options]]*/", type[data.type || "tiled"])
  465. // set scroll or fixed background image
  466. .replace("/*[[bg-attachment]]*/ fixed", data.attach || "scroll")
  467. // replace base-color
  468. .replace(/\/\*\[\[base-color\]\]\*\/ #\w{3,6}/g, data.color || "#4183C4")
  469. // replace base-color-rgb
  470. .replace(/\/\*\[\[base-color-rgb\]\]\*\//g, hexToRgb(data.color || "#4183c4"))
  471. // add font choice
  472. .replace("/*[[font-choice]]*/", data.font || "Menlo")
  473. // add tab size
  474. .replace(/\/\*\[\[tab-size\]\]\*\//g, setTabSize())
  475. // code wrap css
  476. .replace("/*[[code-wrap]]*/", data.wrap ? wrapCodeCss : "")
  477. // remove default syntax
  478. .replace(/\s+\/\* grunt build - remove start[\s\S]+grunt build - remove end \*\/$/m, "");
  479.  
  480. data.processedCss = processed;
  481. fetchAndApplyTheme(data.theme, "github");
  482. fetchAndApplyTheme(data.themeCm, "codemirror");
  483. fetchAndApplyTheme(data.themeJp, "jupyter");
  484. }
  485.  
  486. function applyStyle(css) {
  487. if (debug) {
  488. console.info("Applying style", `"${(css || "").match(regex)}"`);
  489. }
  490. $style.textContent = css || "";
  491. }
  492.  
  493. function addSavedStyle() {
  494. if (debug) {
  495. console.info("Adding previously saved style");
  496. }
  497.  
  498. // apply already processed css to prevent FOUC
  499. $style.textContent = data.processedCss;
  500. }
  501.  
  502. function updateStyle() {
  503. isUpdating = true;
  504.  
  505. if (debug) {
  506. console.info("Updating user settings");
  507. }
  508.  
  509. const body = $("body"),
  510. panel = $("#ghd-settings-inner");
  511.  
  512. data.attach = $(".ghd-attach", panel).value;
  513. // get hex value directly
  514. data.color = picker.toHEXString();
  515. data.enable = $(".ghd-enable", panel).checked;
  516. data.font = $(".ghd-font", panel).value;
  517. data.image = $(".ghd-image", panel).value;
  518. data.tab = $(".ghd-tab", panel).value;
  519. data.theme = $(".ghd-theme", panel).value;
  520. data.themeCm = $(".ghd-themecm", panel).value;
  521. data.themeJp = $(".ghd-themejp", panel).value;
  522. data.type = $(".ghd-type", panel).value;
  523. data.wrap = $(".ghd-wrap", panel).checked;
  524.  
  525. data.enableCodeWrap = $(".ghd-codewrap-checkbox", panel).checked;
  526. data.enableMonospace = $(".ghd-monospace-checkbox", panel).checked;
  527.  
  528. data.modeDiffToggle = $(".ghd-diff-select", panel).value;
  529.  
  530. $style.disabled = !data.enable;
  531.  
  532. toggleClass(body, "ghd-disabled", !data.enable);
  533. toggleClass(body, "nowrap", !data.wrap);
  534.  
  535. if (testing) {
  536. processStyle();
  537. testing = false;
  538. } else {
  539. fetchAndApplyStyle();
  540. }
  541. isUpdating = false;
  542. }
  543.  
  544. // user can force GitHub-dark update
  545. async function forceUpdate(css) {
  546. if (css) {
  547. // add raw css directly for style testing
  548. data.rawCss = css;
  549. processStyle();
  550. } else {
  551. // clear saved date
  552. data.version = 0;
  553. data.cssgithub = "";
  554. data.csscodemirror = "";
  555. data.cssjupyter = "";
  556. await GM.setValue("data", JSON.stringify(data));
  557. closePanel("forced");
  558. }
  559. }
  560.  
  561. function getVersionTooltip() {
  562. let indx;
  563. const ver = [];
  564. // convert stored css version from "001014049" into "1.14.49" for tooltip
  565. const parts = String(data.version).match(/\d{3}/g);
  566. const len = parts && parts.length || 0;
  567.  
  568. for (indx = 0; indx < len; indx++) {
  569. ver.push(parseInt(parts[indx]));
  570. }
  571. return `Script v${version}\nCSS ${(ver.length ? `v${ver.join(".")}` : "unknown")}`;
  572. }
  573.  
  574. function buildOptions(group) {
  575. let options = "";
  576. Object.keys(themes[group]).forEach(theme => {
  577. options += `<option value="${theme}">${theme}</option>`;
  578. });
  579. return options;
  580. }
  581.  
  582. async function buildSettings() {
  583. if (debug) {
  584. console.info("Adding settings panel & GitHub Dark link to profile dropdown");
  585. }
  586. // Script-specific CSS
  587. GM.addStyle(`
  588. #ghd-menu:hover { cursor:pointer }
  589. #ghd-settings { position:fixed; z-index:65535; top:0; bottom:0; left:0; right:0; opacity:0; visibility:hidden; }
  590. #ghd-settings.in { opacity:1; visibility:visible; background:rgba(0,0,0,.5); }
  591. #ghd-settings-inner { position:fixed; left:50%; top:50%; transform:translate(-50%,-50%); width:25rem; box-shadow:0 .5rem 1rem #111; color:#c0c0c0 }
  592. #ghd-settings label { margin-left:.5rem; position:relative; top:-1px }
  593. #ghd-settings-close { height:1rem; width:1rem; fill:#666; float:right; cursor:pointer }
  594. #ghd-settings-close:hover { fill:#ccc }
  595. #ghd-settings .ghd-right { float:right; padding:5px; }
  596. #ghd-settings p { line-height:22px; }
  597. #ghd-settings .form-select, #ghd-settings input[type="text"] { height:30px; min-height:30px; }
  598. #ghd-swatch { width:25px; height:22px; display:inline-block; margin:3px 10px; border-radius:4px; }
  599. #ghd-settings input, #ghd-settings select { border:#555 1px solid; }
  600. #ghd-settings .ghd-attach, #ghd-settings .ghd-diff-select { padding-right:25px; }
  601. #ghd-settings input[type="checkbox"] { margin-top:3px; width: 16px !important; height: 16px !important; border-radius: 3px !important; }
  602. #ghd-settings .boxed-group-inner { padding:0; }
  603. #ghd-settings .ghd-footer { padding:10px; border-top:#555 solid 1px; }
  604. #ghd-settings .ghd-settings-wrapper { max-height:60vh; overflow-y:auto; padding:4px 10px; }
  605. #ghd-settings .ghd-tab { width:6em; }
  606. #ghd-settings .octicon { vertical-align:text-bottom !important; }
  607. #ghd-settings .ghd-paste-area { position:absolute; bottom:50px; top:37px; left:2px; right:2px; width:396px !important; height:-moz-calc(100% - 85px);
  608. resize:none !important; border-style:solid; z-index:0; }
  609.  
  610. /* code wrap toggle: https://gist.github.com/silverwind/6c1701f56e62204cc42b
  611. icons next to a pre */
  612. .ghd-wrap-toggle { padding: 3px 5px; position:absolute; right:3px; top:3px; -moz-user-select:none; -webkit-user-select:none; cursor:pointer; z-index:20; }
  613. .ghd-code-wrapper:not(:hover) .ghd-wrap-toggle { border-color:transparent !important; background:transparent !important; }
  614. .ghd-menu { margin-top:45px; }
  615. /* file & diff code tables */
  616. .ghd-wrap-table .blob-code-inner:not(.blob-code-hunk) { white-space:pre-wrap !important; word-break:break-all !important; }
  617. .ghd-unwrap-table .blob-code-inner:not(.blob-code-hunk) { white-space:pre !important; word-break:normal !important; }
  618. .ghd-wrap-toggle > *, .ghd-monospace > *, .ghd-file-toggle > * { pointer-events:none; vertical-align:middle !important; }
  619. /* icons for non-syntax highlighted code blocks; see https://github.com/gjtorikian/html-proofer/blob/master/README.md */
  620. .markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) { right:3.4em; }
  621. .ghd-wrap-toggle svg { height:14px; width:14px; fill:rgba(110,110,110,.4); vertical-align:text-bottom; }
  622. /* wrap disabled (red) */
  623. .ghd-code-wrapper:hover .ghd-wrap-toggle.unwrap svg, .ghd-code-wrapper:hover .ghd-wrap-toggle svg { fill:#8b0000; }
  624. /* wrap enabled (green) */
  625. body:not(.nowrap) .ghd-code-wrapper:hover .ghd-wrap-toggle:not(.unwrap) svg, .ghd-code-wrapper:hover .ghd-wrap-toggle.wrapped svg { fill:#006400; }
  626. .markdown-body pre, .markdown-body .highlight, .ghd-code-wrapper { position:relative; }
  627. /* monospace font toggle */
  628. .ghd-monospace-font { font-family:"${data.font}", Menlo, Inconsolata, "Droid Mono", monospace !important; font-size:1em !important; }
  629. /* file collapsed icon */
  630. .Details--on .ghd-file-toggle svg { -webkit-transform:rotate(90deg); transform:rotate(90deg); }
  631. `);
  632.  
  633. const opts = buildOptions("github");
  634. const optscm = buildOptions("codemirror");
  635. const optsjp = buildOptions("jupyter");
  636. const ver = getVersionTooltip();
  637.  
  638. // circle-question-mark icon
  639. const icon = `
  640. <svg class="octicon" xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 16 14">
  641. <path d="M6 10h2v2H6V10z m4-3.5c0 2.14-2 2.5-2 2.5H6c0-0.55 0.45-1 1-1h0.5c0.28 0 0.5-0.22 0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28 0-0.5 0.22-0.5 0.5v0.5H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z" />
  642. </svg>
  643. `;
  644.  
  645. // Settings panel markup
  646. make({
  647. el: "div",
  648. appendTo: "body",
  649. attr: {id: "ghd-settings"},
  650. html: `
  651. <div id="ghd-settings-inner" class="boxed-group">
  652. <h3>GitHub-Dark Settings
  653. <a href="https://github.com/StylishThemes/GitHub-Dark-Script/wiki" class="tooltipped tooltipped-e" aria-label="See documentation">${icon}</a>
  654. <svg id="ghd-settings-close" xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="160 160 608 608"><path d="M686.2 286.8L507.7 465.3l178.5 178.5-45 45-178.5-178.5-178.5 178.5-45-45 178.5-178.5-178.5-178.5 45-45 178.5 178.5 178.5-178.5z"/></svg>
  655. </h3>
  656. <div class="boxed-group-inner">
  657. <form>
  658. <div class="ghd-settings-wrapper">
  659. <p class="ghd-checkbox">
  660. <label>Enable GitHub-Dark<input class="ghd-enable ghd-right" type="checkbox"></label>
  661. </p>
  662. <p>
  663. <label>Color:</label>
  664. <input class="ghd-color ghd-right form-control" type="text" value="#4183C4">
  665. <span id="ghd-swatch" class="ghd-right"></span>
  666. </p>
  667. <h4>Background</h4>
  668. <p>
  669. <label>Image:</label>
  670. <input class="ghd-image ghd-right form-control" type="text">
  671. <a href="https://github.com/StylishThemes/GitHub-Dark/wiki/Image" class="tooltipped tooltipped-e" aria-label="Click to learn about GitHub's Content Security&#10;Policy and how to add a custom image">${icon}</a>
  672. </p>
  673. <p>
  674. <label>Image type:</label>
  675. <select class="ghd-type ghd-right form-select">
  676. <option value="tiled">Tiled</option>
  677. <option value="fit">Fit window</option>
  678. </select>
  679. </p>
  680. <p>
  681. <label>Image attachment:</label>
  682. <select class="ghd-attach ghd-right form-select">
  683. <option value="scroll">Scroll</option>
  684. <option value="fixed">Fixed</option>
  685. </select>
  686. </p>
  687. <h4>Code</h4>
  688. <p><label>GitHub Theme:</label> <select class="ghd-theme ghd-right form-select">${opts}</select></p>
  689. <p><label>CodeMirror Theme:</label> <select class="ghd-themecm ghd-right form-select">${optscm}</select></p>
  690. <p><label>Jupyter Theme:</label> <select class="ghd-themejp ghd-right form-select">${optsjp}</select></p>
  691. <p>
  692. <label>Font Name:</label> <input class="ghd-font ghd-right form-control" type="text">
  693. <a href="http://www.cssfontstack.com/" class="tooltipped tooltipped-e" aria-label="Add a system installed (monospaced) font name;&#10;this script will not load external fonts!">${icon}</a>
  694. </p>
  695. <p>
  696. <label>Tab Size:</label>
  697. <select class="ghd-tab ghd-right form-select">
  698. <option value="0">disable</option>
  699. <option value="1">1</option>
  700. <option value="2">2</option>
  701. <option value="3">3</option>
  702. <option value="4">4</option>
  703. <option value="5">5</option>
  704. <option value="6">6</option>
  705. <option value="7">7</option>
  706. <option value="8">8</option>
  707. </select>
  708. </p>
  709. <p class="ghd-checkbox">
  710. <label>Wrap<input class="ghd-wrap ghd-right" type="checkbox"></label>
  711. </p>
  712. <h4>Toggles</h4>
  713. <p class="ghd-checkbox">
  714. <label>Code Wrap<input class="ghd-codewrap-checkbox ghd-right" type="checkbox"></label>
  715. </p>
  716. <p class="ghd-checkbox">
  717. <label>Comment Monospace Font<input class="ghd-monospace-checkbox ghd-right" type="checkbox"></label>
  718. </p>
  719. <p class="ghd-range">
  720. <label>Diff File Collapse</label>
  721. <select class="ghd-diff-select ghd-right form-select">
  722. <option value="0">Disabled</option>
  723. <option value="1">Enabled</option>
  724. <option value="2">Accordion</option>
  725. </select>
  726. </p>
  727. </div>
  728. <div class="ghd-footer">
  729. <div class="BtnGroup">
  730. <a href="#" class="ghd-update btn btn-sm tooltipped tooltipped-n tooltipped-multiline" aria-label="Update style if the newest release is not loading; the page will reload!">Force Update Style</a>
  731. <a href="#" class="ghd-textarea-toggle btn btn-sm tooltipped tooltipped-n" aria-label="Paste CSS update">
  732. <svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 16 16" fill="#eee">
  733. <path d="M15 11 1 11 8 3z"/>
  734. </svg>
  735. </a>
  736. <div class="ghd-paste-area-content" aria-hidden="true" style="display:none">
  737. <textarea class="ghd-paste-area" placeholder="Paste GitHub-Dark Style here!"></textarea>
  738. </div>
  739. </div>&nbsp;
  740. <a href="#" class="ghd-reset btn btn-sm btn-danger tooltipped tooltipped-n" aria-label="Reset to defaults;&#10;there is no undo!">Reset All Settings</a>
  741. <span class="ghd-versions ghd-right tooltipped tooltipped-n" aria-label="${ver}">
  742. <svg class="ghd-info" xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24">
  743. <path fill="#444" d="M12,9c0.82,0,1.5-0.68,1.5-1.5S12.82,6,12,6s-1.5,0.68-1.5,1.5S11.18,9,12,9z M12,1.5 C6.211,1.5,1.5,6.211,1.5,12S6.211,22.5,12,22.5S22.5,17.789,22.5,12S17.789,1.5,12,1.5z M12,19.5c-4.148,0-7.5-3.352-7.5-7.5 S7.852,4.5,12,4.5s7.5,3.352,7.5,7.5S16.148,19.5,12,19.5z M13.5,12c0-0.75-0.75-1.5-1.5-1.5s-0.75,0-1.5,0S9,11.25,9,12h1.5 c0,0,0,3.75,0,4.5S11.25,18,12,18s0.75,0,1.5,0s1.5-0.75,1.5-1.5h-1.5C13.5,16.5,13.5,12.75,13.5,12z"/>
  744. </svg>
  745. </span>
  746. </div>
  747. </form>
  748. </div>
  749. </div>
  750. `
  751. });
  752. updateToggles();
  753. }
  754.  
  755. function addCodeWrapButton(button, target) {
  756. target.insertBefore(button.cloneNode(true), target.childNodes[0]);
  757. target.classList.add("ghd-code-wrapper");
  758. }
  759.  
  760. function moveMenu(codeWrap) {
  761. const menu = $("details", codeWrap);
  762. if (menu) {
  763. menu.classList.add("ghd-menu");
  764. codeWrap.parentNode.appendChild(menu);
  765. }
  766. }
  767.  
  768. // Add code wrap toggle
  769. function buildCodeWrap() {
  770. // mutation events happen quick, so we still add an update flag
  771. isUpdating = true;
  772. const icon = make({
  773. el: "button",
  774. cl4ss: `ghd-wrap-toggle tooltipped tooltipped-sw btn btn-sm${
  775. data.wrap ? "" : " unwrap"}`,
  776. attr: {"aria-label": "Toggle code wrap"},
  777. html: wrapIcon
  778. });
  779. $$(".blob-wrapper").forEach(el => {
  780. if (el && !$(".ghd-wrap-toggle", el)) {
  781. addCodeWrapButton(icon, el);
  782. moveMenu(el); // Fixes #66
  783. }
  784. });
  785. $$(`
  786. .markdown-body pre:not(.ghd-code-wrapper),
  787. .markdown-format pre:not(.ghd-code-wrapper)`
  788. ).forEach(pre => {
  789. const code = $("code", pre);
  790. const wrap = pre.parentNode;
  791. if (code) {
  792. addCodeWrapButton(icon, pre);
  793. } else if (wrap.classList.contains("highlight")) {
  794. addCodeWrapButton(icon, wrap);
  795. }
  796. });
  797. isUpdating = false;
  798. }
  799.  
  800. // Add monospace font toggle
  801. function addMonospaceToggle() {
  802. isUpdating = true;
  803. const button = make({
  804. el: "button",
  805. cl4ss: "ghd-monospace toolbar-item tooltipped tooltipped-n",
  806. attr: {
  807. "type": "button",
  808. "aria-label": "Toggle monospaced font",
  809. "tabindex": "-1"
  810. },
  811. html: monospaceIcon
  812. });
  813. $$(".toolbar-commenting").forEach(el => {
  814. if (el && !$(".ghd-monospace", el)) {
  815. // prepend
  816. el.insertBefore(button.cloneNode(true), el.childNodes[0]);
  817. }
  818. });
  819. isUpdating = false;
  820. }
  821.  
  822. // Add file diffs toggle
  823. function addFileToggle() {
  824. isUpdating = true;
  825.  
  826. const button = make({
  827. el: "button",
  828. cl4ss: "ghd-file-toggle btn btn-sm tooltipped tooltipped-n",
  829. attr: {
  830. "type": "button",
  831. "aria-label": "Click to Expand or Collapse file",
  832. "tabindex": "-1"
  833. },
  834. html: fileIcon
  835. });
  836.  
  837. $$("#files .file-actions").forEach(el => {
  838. if (el && !$(".ghd-file-toggle", el)) {
  839. // hide GitHub toggle view button
  840. el.querySelector(".js-details-target").style.display = "none";
  841. el.appendChild(button.cloneNode(true));
  842. }
  843. });
  844.  
  845. const firstButton = $(".ghd-file-toggle");
  846. // accordion mode = start with all but first entry collapsed
  847. if (firstButton && data.modeDiffToggle === "2") {
  848. toggleFile({
  849. target: firstButton
  850. }, true);
  851. }
  852. isUpdating = false;
  853. }
  854.  
  855. // Add toggle buttons after page updates
  856. function updateToggles() {
  857. if (isUpdating) {
  858. return;
  859. }
  860. clearTimeout(timer);
  861. timer = setTimeout(() => {
  862. if (data.enableCodeWrap) {
  863. buildCodeWrap();
  864. } else {
  865. removeAll(".ghd-wrap-toggle");
  866. toggleClass($$(".Details--on"), "Details--on", false);
  867. }
  868. if (data.enableMonospace) {
  869. addMonospaceToggle();
  870. } else {
  871. removeAll(".ghd-monospace");
  872. toggleClass($$(".ghd-monospace-font"), "ghd-monospace-font", false);
  873. }
  874. if (data.modeDiffToggle !== "0") {
  875. addFileToggle();
  876. } else {
  877. removeAll(".ghd-file-toggle");
  878. toggleClass($$(".Details--on"), "Details--on", false);
  879. }
  880. }, 200);
  881. }
  882.  
  883. function makeRow(vals, str) {
  884. return make({
  885. el: "tr",
  886. cl4ss: "ghd-shortcut",
  887. html: `<td class="keys"><kbd>${vals[0]}</kbd> <kbd>${vals[1]}</kbd></td><td>${str}</td>`
  888. });
  889. }
  890.  
  891. // add keyboard shortcut to help menu (press "?")
  892. function buildShortcut() {
  893. let row;
  894. const el = $(".keyboard-mappings tbody");
  895.  
  896. if (el && !$(".ghd-shortcut")) {
  897. row = makeRow(keyboardOpen.split("+"), "GitHub-Dark: open settings");
  898. el.appendChild(row);
  899. row = makeRow(keyboardToggle.split("+"), "GitHub-Dark: toggle style");
  900. el.appendChild(row);
  901. }
  902. }
  903.  
  904. function findSibling(node, selector) {
  905. node = node.parentNode.firstElementChild;
  906. while ((node = node.nextElementSibling)) {
  907. if (node.matches(selector)) {
  908. return node;
  909. }
  910. }
  911. return null;
  912. }
  913.  
  914. function toggleCodeWrap(el) {
  915. let css;
  916. const overallWrap = data.wrap;
  917. const target = findSibling(el, ".highlight, .diff-table, code, pre");
  918.  
  919. if (!target) {
  920. if (debug) {
  921. console.info("Code wrap icon associated code not found", el);
  922. }
  923. return;
  924. }
  925. // code with line numbers
  926. if (target.nodeName === "TABLE") {
  927. if (target.className.indexOf("wrap-table") < 0) {
  928. css = !overallWrap;
  929. } else {
  930. css = target.classList.contains("ghd-unwrap-table");
  931. }
  932. toggleClass(target, "ghd-wrap-table", css);
  933. toggleClass(target, "ghd-unwrap-table", !css);
  934. toggleClass(el, "wrapped", css);
  935. toggleClass(el, "unwrap", !css);
  936. } else {
  937. css = target.getAttribute("style") || "";
  938. if (css === "") {
  939. css = wrapCss[overallWrap ? "unwrap" : "wrapped"];
  940. } else {
  941. css = wrapCss[css === wrapCss.wrapped ? "unwrap" : "wrapped"];
  942. }
  943. target.setAttribute("style", css);
  944. toggleClass(el, "wrapped", css === wrapCss.wrapped);
  945. toggleClass(el, "unwrap", css === wrapCss.unwrap);
  946. }
  947. }
  948.  
  949. function toggleMonospace(el) {
  950. let tmp = el.closest(".previewable-comment-form");
  951.  
  952. // single comment
  953. const textarea = $(".comment-form-textarea", tmp);
  954.  
  955. if (textarea) {
  956. toggleClass(textarea, "ghd-monospace-font");
  957. textarea.focus();
  958. tmp = textarea.classList.contains("ghd-monospace-font");
  959. toggleClass(el, "ghd-icon-active", tmp);
  960. }
  961. }
  962.  
  963. function toggleSibs(target, state) {
  964. // oddly, when a "Details--on" class is applied, the content is hidden
  965. const isCollapsed = state || target.classList.contains("Details--on"),
  966. toggles = $$(".file");
  967. let el,
  968. indx = toggles.length;
  969. while (indx--) {
  970. el = toggles[indx];
  971. if (el !== target) {
  972. el.classList.toggle("Details--on", isCollapsed);
  973. }
  974. }
  975. }
  976.  
  977. function toggleFile(event, init) {
  978. isUpdating = true;
  979. const el = event.target.closest(".file");
  980. if (el && data.modeDiffToggle === "2") {
  981. if (!init) {
  982. el.classList.toggle("Details--on");
  983. }
  984. toggleSibs(el, true);
  985. } else if (el) {
  986. el.classList.toggle("Details--on");
  987. // shift+click toggle all files!
  988. if (event.shiftKey) {
  989. toggleSibs(el);
  990. }
  991. }
  992. isUpdating = false;
  993. document.activeElement.blur();
  994. // move current open panel to the top
  995. if (el && !el.classList.contains("Details--on")) {
  996. location.hash = el.id;
  997. }
  998. }
  999.  
  1000. function bindEvents() {
  1001. let el, lastKey;
  1002.  
  1003. const panel = $("#ghd-settings-inner");
  1004. const swatch = $("#ghd-swatch", panel);
  1005.  
  1006. // finish initialization
  1007. $("#ghd-settings-inner .ghd-enable").checked = data.enable;
  1008. toggleClass($("body"), "ghd-disabled", !data.enable);
  1009.  
  1010. // Create our menu entry
  1011. const menu = make({
  1012. el: "a",
  1013. cl4ss: "dropdown-item",
  1014. html: "GitHub Dark Settings",
  1015. attr: {id: "ghd-menu"}
  1016. });
  1017.  
  1018. // .header changed to .Header 22 Aug 2017
  1019. el = $$(`
  1020. .header .dropdown-item[href="/settings/profile"],
  1021. .header .dropdown-item[data-ga-click*="go to profile"],
  1022. .Header .dropdown-item[href="/settings/profile"],
  1023. .Header .dropdown-item[data-ga-click*="go to profile"]`
  1024. );
  1025. // get last found item - gists only have the "go to profile" item;
  1026. // GitHub has both
  1027. el = el[el.length - 1];
  1028.  
  1029. if (el) {
  1030. // insert after
  1031. el.parentNode.insertBefore(menu, el.nextSibling);
  1032. on($("#ghd-menu"), "click", () => {
  1033. openPanel();
  1034. });
  1035. }
  1036.  
  1037. on(document, "keypress keydown", event => {
  1038. clearTimeout(timer);
  1039. // use "g+o" to open up ghd options panel
  1040. const openKeys = keyboardOpen.split("+"),
  1041. toggleKeys = keyboardToggle.split("+"),
  1042. key = event.key.toLowerCase(),
  1043. panelVisible = $("#ghd-settings").classList.contains("in");
  1044.  
  1045. // press escape to close the panel
  1046. if (key === "escape" && panelVisible) {
  1047. closePanel();
  1048. return;
  1049. }
  1050. // use event.which from keypress for shortcuts
  1051. // prevent opening panel while typing "go" in comments
  1052. if (event.type === "keydown" || /(input|textarea)/i.test(document.activeElement.nodeName)) {
  1053. return;
  1054. }
  1055. if (lastKey === openKeys[0] && key === openKeys[1]) {
  1056. if (panelVisible) {
  1057. closePanel();
  1058. } else {
  1059. openPanel();
  1060. }
  1061. }
  1062. if (lastKey === toggleKeys[0] && key === toggleKeys[1]) {
  1063. toggleStyle();
  1064. }
  1065. lastKey = key;
  1066. timer = setTimeout(() => {
  1067. lastKey = null;
  1068. }, keyboardDelay);
  1069.  
  1070. // add shortcut to help menu
  1071. if (key === "?") {
  1072. // table doesn't exist until user presses "?"
  1073. setTimeout(() => {
  1074. buildShortcut();
  1075. }, 300);
  1076. }
  1077. });
  1078.  
  1079. // add ghd-settings panel bindings
  1080. on($$("#ghd-settings, #ghd-settings-close"), "click keyup", event => {
  1081. // press escape to close settings
  1082. if (event.type === "keyup" && event.which !== 27) {
  1083. return;
  1084. }
  1085. closePanel();
  1086. });
  1087.  
  1088. on(panel, "click", event => {
  1089. event.stopPropagation();
  1090. });
  1091.  
  1092. on($(".ghd-reset", panel), "click", async event => {
  1093. event.preventDefault();
  1094. isUpdating = true;
  1095. // pass true to reset values
  1096. await setStoredValues(true);
  1097. // add reset values back to data
  1098. await getStoredValues();
  1099. // add reset values to panel
  1100. updatePanel();
  1101. // update style
  1102. updateStyle();
  1103. });
  1104.  
  1105. on($$("input[type='text']", panel), "focus", function() {
  1106. // select all text when focused
  1107. this.select();
  1108. });
  1109.  
  1110. on($$("select, input", panel), "change", () => {
  1111. if (!isUpdating) {
  1112. updateStyle();
  1113. }
  1114. });
  1115.  
  1116. on($(".ghd-update", panel), "click", async event => {
  1117. event.preventDefault();
  1118. await forceUpdate();
  1119. });
  1120.  
  1121. on($(".ghd-textarea-toggle", panel), "click", function(event) {
  1122. event.preventDefault();
  1123. let hidden, el;
  1124. this.classList.remove("selected");
  1125. el = next(this, ".ghd-paste-area-content");
  1126. if (el) {
  1127. hidden = el.style.display === "none";
  1128. el.style.display = hidden ? "" : "none";
  1129. if (el.style.display !== "none") {
  1130. el.classList.add("selected");
  1131. el = $("textarea", el);
  1132. el.focus();
  1133. el.select();
  1134. }
  1135. }
  1136. return false;
  1137. });
  1138.  
  1139. on($(".ghd-paste-area-content", panel), "paste", async event => {
  1140. const toggle = $(".ghd-textarea-toggle", panel),
  1141. textarea = event.target;
  1142. setTimeout(async () => {
  1143. textarea.parentNode.style.display = "none";
  1144. toggle.classList.remove("selected");
  1145. testing = true;
  1146. await forceUpdate(textarea.value);
  1147. }, 200);
  1148. });
  1149.  
  1150. // Toggles
  1151. on($("body"), "click", event => {
  1152. const target = event.target;
  1153. if (data.enableCodeWrap && target.classList.contains("ghd-wrap-toggle")) {
  1154. // **** CODE WRAP TOGGLE ****
  1155. event.stopPropagation();
  1156. toggleCodeWrap(target);
  1157. } else if (data.enableMonospace && target.classList.contains("ghd-monospace")) {
  1158. // **** MONOSPACE FONT TOGGLE ****
  1159. event.stopPropagation();
  1160. toggleMonospace(target);
  1161. return false;
  1162. } else if (data.modeDiffToggle !== "0" && target.classList.contains("ghd-file-toggle")) {
  1163. // **** CODE DIFF COLLAPSE TOGGLE ****
  1164. event.stopPropagation();
  1165. toggleFile(event);
  1166. }
  1167. });
  1168.  
  1169. // style color picker
  1170. picker = new jscolor($(".ghd-color", panel));
  1171. picker.zIndex = 65536;
  1172. picker.hash = true;
  1173. picker.backgroundColor = "#333";
  1174. picker.padding = 0;
  1175. picker.borderWidth = 0;
  1176. picker.borderColor = "#444";
  1177. picker.onFineChange = () => {
  1178. swatch.style.backgroundColor = `#${picker}`;
  1179. };
  1180. }
  1181.  
  1182. function openPanel() {
  1183. // Don't show options panel on page that's missing main styles (e.g. help)
  1184. if ($(".modal-backdrop")) {
  1185. $(".modal-backdrop").click();
  1186. }
  1187. updatePanel();
  1188. $("#ghd-settings").classList.add("in");
  1189. }
  1190.  
  1191. function closePanel(flag) {
  1192. $("#ghd-settings").classList.remove("in");
  1193. picker.hide();
  1194.  
  1195. if (flag === "forced") {
  1196. // forced update; partial re-initialization
  1197. init();
  1198. } else {
  1199. // apply changes when the panel is closed
  1200. updateStyle();
  1201. }
  1202. }
  1203.  
  1204. function toggleStyle() {
  1205. const isEnabled = !data.enable;
  1206. data.enable = isEnabled;
  1207. $("#ghd-settings-inner .ghd-enable").checked = isEnabled;
  1208. // add processedCss back into style (emptied when disabled)
  1209. if (isEnabled) {
  1210. // data.processedCss is empty when ghd is disabled on init
  1211. if (!data.processedCss) {
  1212. processStyle();
  1213. } else {
  1214. addSavedStyle();
  1215. }
  1216. }
  1217. $style.disabled = !isEnabled;
  1218. }
  1219.  
  1220. async function init() {
  1221. if (!document.head) {
  1222. return;
  1223. }
  1224.  
  1225. document.head.parentNode.insertBefore($style, document.head.nextSibling);
  1226. await getStoredValues(true);
  1227.  
  1228. $style.disabled = !data.enable;
  1229. data.lastgithub = data.themeGH;
  1230. data.lastcodemirror = data.themeCM;
  1231. data.lastjupyter = data.themeJP;
  1232. data.lastCW = data.enableCodeWrap;
  1233. data.lastMS = data.enableMonospace;
  1234. data.lastDT = data.modeDiffToggle;
  1235.  
  1236. if (!data.processedCss) {
  1237. fetchAndApplyStyle();
  1238. } else {
  1239. // only load package.json once a day, or after a forced update
  1240. if ((new Date().getTime() > data.date + delay) || data.version === 0) {
  1241. // get package.json from GitHub-Dark & compare versions
  1242. // load new script if a newer one is available
  1243. checkVersion();
  1244. } else {
  1245. addSavedStyle();
  1246. }
  1247. }
  1248. isInitialized = false;
  1249. }
  1250.  
  1251. // add style at document-start
  1252. await init();
  1253.  
  1254. async function buildOnLoad() {
  1255. if (isInitialized === "pending") {
  1256. // init after DOM loaded on .atom pages
  1257. await init();
  1258. }
  1259. // add panel even if you're not logged in - open panel using keyboard
  1260. // shortcut just not on githubusercontent pages (no settings panel needed)
  1261. if (window.location.host.indexOf("githubusercontent.com") < 0) {
  1262. buildSettings();
  1263. // add event binding on document ready
  1264. bindEvents();
  1265.  
  1266. on(document, "ghmo:container", updateToggles);
  1267. on(document, "ghmo:comments", updateToggles);
  1268. on(document, "ghmo:diff", updateToggles);
  1269. on(document, "ghmo:preview", updateToggles);
  1270. }
  1271.  
  1272. isInitialized = true;
  1273. }
  1274.  
  1275. if (document.readyState === "loading") {
  1276. on(document, "DOMContentLoaded", buildOnLoad);
  1277. } else {
  1278. buildOnLoad();
  1279. }
  1280.  
  1281. /* utility functions */
  1282. function isBool(name) {
  1283. const val = data[name];
  1284. return typeof val === "boolean" ? val : defaults[name];
  1285. }
  1286.  
  1287. function $(str, el) {
  1288. return (el || document).querySelector(str);
  1289. }
  1290.  
  1291. function $$(str, el) {
  1292. return [...(el || document).querySelectorAll(str)];
  1293. }
  1294.  
  1295. function next(el, selector) {
  1296. while ((el = el.nextElementSibling)) {
  1297. if (el && el.matches(selector)) {
  1298. return el;
  1299. }
  1300. }
  1301. return null;
  1302. }
  1303.  
  1304. function make(obj) {
  1305. let key;
  1306. const el = document.createElement(obj.el);
  1307.  
  1308. if (obj.cl4ss) { el.className = obj.cl4ss }
  1309. if (obj.html) { el.innerHTML = obj.html }
  1310. if (obj.attr) {
  1311. for (key of Object.keys(obj.attr)) {
  1312. el.setAttribute(key, obj.attr[key]);
  1313. }
  1314. }
  1315. if (obj.appendTo) {
  1316. $(obj.appendTo).appendChild(el);
  1317. }
  1318. return el;
  1319. }
  1320.  
  1321. function removeAll(selector) {
  1322. isUpdating = true;
  1323. $$(selector).forEach(el => {
  1324. el.parentNode.removeChild(el);
  1325. });
  1326. isUpdating = false;
  1327. }
  1328.  
  1329. function on(els, name, callback) {
  1330. els = Array.isArray(els) ? els : [els];
  1331. const events = name.split(/\s+/);
  1332. els.forEach(el => {
  1333. events.forEach(ev => {
  1334. el.addEventListener(ev, callback);
  1335. });
  1336. });
  1337. }
  1338.  
  1339. function toggleClass(els, cl4ss, flag) {
  1340. els = Array.isArray(els) ? els : [els];
  1341. els.forEach(el => {
  1342. if (el) {
  1343. if (typeof flag === "undefined") {
  1344. flag = !el.classList.contains(cl4ss);
  1345. }
  1346. el.classList.toggle(cl4ss, flag);
  1347. }
  1348. });
  1349. }
  1350.  
  1351. // Add GM options
  1352. // await GM.registerMenuCommand("GitHub Dark Script debug logging", async () => {
  1353. // let val = prompt(
  1354. // "Toggle GitHub Dark Script debug log (true/false):",
  1355. // "" + debug
  1356. // );
  1357. // if (val) {
  1358. // debug = /^t/.test(val);
  1359. // await GM.setValue("debug", debug);
  1360. // }
  1361. // });
  1362. })();