GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

目前為 2017-03-12 提交的版本,檢視 最新版本

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