GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

As of 2016-07-24. See the latest version.

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