GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

目前為 2016-02-04 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name GitHub Dark Script
  3. // @version 1.0.7-beta
  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 /render\.githubusercontent\.com/
  8. // @include /raw\.githubusercontent\.com/
  9. // @grant GM_addStyle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_xmlhttpRequest
  13. // @run-at document-start
  14. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js
  15. // @require https://greasyfork.org/scripts/15563-jscolor/code/jscolor.js?version=97027
  16. // ==/UserScript==
  17. /* global jQuery, GM_addStyle, GM_getValue, GM_setValue, GM_xmlhttpRequest, jscolor */
  18. /* eslint-disable indent, quotes */
  19. (function($) {
  20. 'use strict';
  21.  
  22. var ghd = {
  23.  
  24. version : '1.0.7-beta',
  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.  
  50. // url gets replaced by css when loaded
  51. themes : {
  52. 'Ambiance' : 'themes/ambiance.min.css',
  53. 'Chaos' : 'themes/chaos.min.css',
  54. 'Clouds Midnight' : 'themes/clouds-midnight.min.css',
  55. 'Cobalt' : 'themes/cobalt.min.css',
  56. 'Idle Fingers' : 'themes/idle-fingers.min.css',
  57. 'Kr Theme' : 'themes/kr-theme.min.css',
  58. 'Merbivore' : 'themes/merbivore.min.css',
  59. 'Merbivore Soft' : 'themes/merbivore-soft.min.css',
  60. 'Mono Industrial' : 'themes/mono-industrial.min.css',
  61. 'Mono Industrial Clear' : 'themes/mono-industrial-clear.min.css',
  62. 'Monokai' : 'themes/monokai.min.css',
  63. 'Pastel on Dark' : 'themes/pastel-on-dark.min.css',
  64. 'Solarized Dark' : 'themes/solarized-dark.min.css',
  65. 'Terminal' : 'themes/terminal.min.css',
  66. 'Tomorrow Night' : 'themes/tomorrow-night.min.css',
  67. 'Tomorrow Night Blue' : 'themes/tomorrow-night-blue.min.css',
  68. 'Tomorrow Night Bright' : 'themes/tomorrow-night-bright.min.css',
  69. 'Tomorrow Night Eigthies' : 'themes/tomorrow-night-eighties.min.css',
  70. 'Twilight' : 'themes/twilight.min.css',
  71. 'Vibrant Ink' : 'themes/vibrant-ink.min.css'
  72. },
  73.  
  74. type : {
  75. 'tiled' : 'background-repeat: repeat !important; background-size: auto !important; background-position: left top !important;',
  76. 'fit' : 'background-repeat: no-repeat !important; background-size: cover !important; background-position: center top !important;'
  77. },
  78.  
  79. wrapCss : {
  80. 'wrapped' : 'white-space: pre-wrap !important; word-break: break-all !important; display: block !important;',
  81. 'unwrap' : 'white-space: pre !important; word-break: normal !important; display: block !important;'
  82. },
  83.  
  84. wrapCodeCss : [
  85. '/* GitHub: Enable wrapping of long code lines */',
  86. ' .blob-code-inner,',
  87. ' .markdown-body pre > code,',
  88. ' .markdown-body .highlight > pre {',
  89. ' white-space: pre-wrap !important;',
  90. ' word-break: break-all !important;',
  91. ' display: block !important;',
  92. ' }',
  93. ' td.blob-code-inner {',
  94. ' display: table-cell !important;',
  95. ' }'
  96. ].join('\n'),
  97.  
  98. wrapIcon : '<div class="ghd-wrap-toggle tooltipped tooltipped-w" aria-label="Toggle code wrap"><svg xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768"><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"/></svg></div>',
  99.  
  100. // extract style & theme name
  101. regex: /\/\*! [^\*]+ \*\//,
  102.  
  103. updatePanel : function() {
  104. // prevent multiple change events from processing
  105. this.isUpdating = true;
  106.  
  107. var color,
  108. data = this.data,
  109. defaults = this.defaults,
  110. $panel = $('#ghd-settings-inner');
  111.  
  112. $panel.find('.ghd-attach').val(data.attach || defaults.attach);
  113. $panel.find('.ghd-font').val(data.font || defaults.font);
  114. $panel.find('.ghd-image').val(data.image || defaults.image);
  115. $panel.find('.ghd-tab').val(data.tab || defaults.tab);
  116. $panel.find('.ghd-theme').val(data.theme || defaults.theme);
  117. $panel.find('.ghd-type').val(data.type || defaults.type);
  118.  
  119. $panel.find('.ghd-enable').prop('checked', typeof data.enable === 'boolean' ? data.enable : defaults.enable);
  120. $panel.find('.ghd-wrap').prop('checked', typeof data.wrap === 'boolean' ? data.wrap : defaults.wrap);
  121.  
  122. color = data.color || defaults.color;
  123. $panel.find('.ghd-color').val(color);
  124. // update swatch color & color picker value
  125. $panel.find('#ghd-swatch').css('background-color', color);
  126.  
  127. if (this.picker) {
  128. this.picker.fromString(color);
  129. }
  130. this.$style.prop('disabled', !data.enable);
  131. $('body')
  132. .toggleClass('ghd-disabled', !data.enable)
  133. .toggleClass('nowrap', !data.wrap);
  134.  
  135. this.isUpdating = false;
  136. },
  137.  
  138. /*
  139. this.data = {
  140. attach : 'scroll',
  141. color : '#4183C4',
  142. enable : true,
  143. font : 'Menlo',
  144. image : 'url()',
  145. tab : 4,
  146. theme : 'Tomorrow Night',
  147. type : 'tiled',
  148. wrap : true, // code: wrap long lines
  149.  
  150. date : 1450159200000, // last loaded package.json
  151. version : '001014032', // v1.14.32 = last stored GitHub-Dark version
  152.  
  153. rawCss : '@-moz-document regexp("^...', // github-dark.css (unprocessed css)
  154. themeCss : '/*! Tomorrow Night * /.ace_editor,.highlight{...', // theme/{name}.min.css
  155. processedCss : '' // css saved directly from this.$style
  156. }
  157. */
  158. getStoredValues : function() {
  159. var defaults = this.defaults;
  160.  
  161. this.data = {
  162. attach : GM_getValue('attach', defaults.attach),
  163. color : GM_getValue('color', defaults.color),
  164. enable : GM_getValue('enable', defaults.enable),
  165. font : GM_getValue('font', defaults.font),
  166. image : GM_getValue('image', defaults.image),
  167. tab : GM_getValue('tab', defaults.tab),
  168. theme : GM_getValue('theme', defaults.theme),
  169. type : GM_getValue('type', defaults.type),
  170. wrap : GM_getValue('wrap', defaults.wrap),
  171.  
  172. date : GM_getValue('date', 0),
  173. version : GM_getValue('version', 0),
  174.  
  175. rawCss : GM_getValue('rawCss', ''),
  176. themeCss : GM_getValue('themeCss', ''),
  177. processedCss : GM_getValue('processedCss', '')
  178. };
  179.  
  180. debug('Retrieved stored values', this.data);
  181. },
  182.  
  183. setStoredValues : function(reset) {
  184. var data = this.data,
  185. defaults = this.defaults;
  186.  
  187. GM_setValue('attach', reset ? defaults.attach : data.attach);
  188. GM_setValue('color', reset ? defaults.color : data.color);
  189. GM_setValue('enable', reset ? defaults.enable : data.enable);
  190. GM_setValue('font', reset ? defaults.font : data.font);
  191. GM_setValue('image', reset ? defaults.image : data.image);
  192. GM_setValue('tab', reset ? defaults.tab : data.tab);
  193. GM_setValue('theme', reset ? defaults.theme : data.theme);
  194. GM_setValue('type', reset ? defaults.type : data.type);
  195. GM_setValue('wrap', reset ? defaults.wrap : data.wrap);
  196.  
  197. GM_setValue('date', reset ? 0 : data.date);
  198. GM_setValue('version', reset ? 0 : data.version);
  199.  
  200. GM_setValue('rawCss', data.rawCss);
  201. GM_setValue('themeCss', data.themeCss);
  202. GM_setValue('processedCss', ghd.$style.text());
  203.  
  204. debug((reset ? 'Resetting' : 'Saving') + ' current values', data);
  205. },
  206.  
  207. // convert version "1.2.3" into "001002003" for easier comparison
  208. convertVersion : function(val) {
  209. var index,
  210. parts = val ? val.split('.') : '',
  211. str = '',
  212. len = parts.length;
  213. for (index = 0; index < len; index++) {
  214. str += ('000' + parts[index]).slice(-3);
  215. }
  216. debug('Converted version "' + val + '" to "' + str + '" for easy comparison');
  217. return val ? str : val;
  218. },
  219.  
  220. checkVersion : function() {
  221. debug('Fetching package.json');
  222. GM_xmlhttpRequest({
  223. method : 'GET',
  224. url : ghd.root + 'package.json',
  225. onload : function(response) {
  226. // store package JSON (not accessed anywhere else, but just in case)
  227. ghd.data.package = JSON.parse(response.responseText);
  228.  
  229. // save last loaded date, so package.json is only loaded once a day
  230. ghd.data.date = new Date().getTime();
  231. GM_setValue('date', ghd.data.date);
  232.  
  233. var version = ghd.convertVersion(ghd.data.package.version);
  234. // if new available, load it & parse
  235. if (version > ghd.data.version) {
  236. if (ghd.data.version !== 0) {
  237. debug('Updating from', ghd.data.version, 'to', version);
  238. }
  239. ghd.data.version = version;
  240. GM_setValue('version', ghd.data.version);
  241. ghd.fetchAndApplyStyle();
  242. } else {
  243. ghd.addSavedStyle();
  244. }
  245. }
  246. });
  247. },
  248.  
  249. fetchAndApplyStyle : function() {
  250. debug('Fetching github-dark.css');
  251. GM_xmlhttpRequest({
  252. method : 'GET',
  253. url : ghd.root + 'github-dark.css',
  254. onload : function(response) {
  255. ghd.data.rawCss = response.responseText;
  256. ghd.applyStyle(ghd.processStyle());
  257. ghd.getTheme();
  258. }
  259. });
  260. },
  261.  
  262. addSavedStyle : function() {
  263. debug('Adding previously saved style');
  264. // apply already processed css to prevent FOUC
  265. this.$style.text(this.data.processedCss);
  266. },
  267.  
  268. // load syntax highlighting theme, if necessary
  269. getTheme : function() {
  270. if (!this.data.enable) {
  271. debug('Disabled: stop theme processing');
  272. return;
  273. }
  274. var name = this.data.theme || 'Twilight';
  275. // test if this.themes contains the url (.min.css), or the actual css
  276. if (/\.min\.css$/.test(this.themes[name])) {
  277. var themeUrl = ghd.root + ghd.themes[name];
  278. debug('Loading "' + name + '" theme', themeUrl);
  279. GM_xmlhttpRequest({
  280. method : 'GET',
  281. url : themeUrl,
  282. onload : function(response) {
  283. var theme = response.responseText;
  284. if (theme) {
  285. ghd.themes[name] = theme;
  286. ghd.data.themeCss = theme;
  287. ghd.processTheme();
  288. } else {
  289. debug('Failed to load theme file', '"' + theme + '"');
  290. }
  291. }
  292. });
  293. } else {
  294. ghd.data.themeCss = ghd.themes[name];
  295. ghd.processTheme();
  296. }
  297. },
  298.  
  299. processStyle : function() {
  300. var data = this.data,
  301. url = /^url/.test(data.image || '') ? data.image :
  302. (data.image === 'none' ? 'none' : 'url("' + data.image + '")');
  303. if (!data.enable) {
  304. debug('Disabled: stop processing');
  305. return;
  306. }
  307. debug('Processing set styles');
  308.  
  309. var ret = (data.rawCss || '')
  310. // remove moz-document wrapper
  311. .replace(/@-moz-document regexp\((.*)\) \{(\n|\r)+/, '')
  312. // replace background image; if no 'url' at start, then use 'none'
  313. .replace(/\/\*\[\[bg-choice\]\]\*\/ url\(.*\)/, url)
  314. // Add tiled or fit window size css
  315. .replace('/*[[bg-options]]*/', this.type[data.type || 'tiled'])
  316. // set scroll or fixed background image
  317. .replace('/*[[bg-attachment]]*/ fixed', data.attach || 'scroll')
  318. // replace base-color
  319. .replace(/\/\*\[\[base-color\]\]\*\/ #\w{3,6}/g, data.color || '#4183C4')
  320. // add font choice
  321. .replace('/*[[font-choice]]*/', data.font || 'Menlo')
  322. // add tab size
  323. .replace(/\/\*\[\[tab-size\]\]\*\/ \d+/g, data.tab || 4)
  324. // code wrap css
  325. .replace('/*[[code-wrap]]*/', data.wrap ? ghd.wrapCodeCss : '')
  326. // remove default syntax
  327. .replace(/\s+\/\* grunt build - remove to end of file(.*(\n|\r))+\}$/m, '');
  328.  
  329. // see https://github.com/StylishThemes/GitHub-Dark/issues/275
  330. if (/firefox/i.test(navigator.userAgent)) {
  331. ret = ret
  332. .replace(/select, input, textarea/, 'select, input:not([type="checkbox"]), textarea')
  333. .replace(/input\[type=\"checkbox\"\][\s\S]+?}/gm, '');
  334. }
  335. return ret;
  336. },
  337.  
  338. // this.data.themeCss should be populated with user selected theme
  339. // called asynchronously from processStyle()
  340. processTheme : function() {
  341. debug('Adding syntax theme to css');
  342. var css = this.$style.text() || '';
  343. // look for /*[[syntax-theme]]*/ label, if it doesn't exist, reprocess raw css
  344. if (!/syntax-theme/.test(css)) {
  345. debug('Need to process raw style before applying theme');
  346. this.applyStyle(this.processStyle());
  347. css = this.$style.text() || '';
  348. }
  349. // add syntax highlighting theme
  350. css = css.replace('/*[[syntax-theme]]*/', this.data.themeCss || '');
  351.  
  352. debug('Applying "' + this.data.theme + '" theme', '"' +
  353. (this.data.themeCss || '').match(this.regex) + '"');
  354.  
  355. this.$style.text(css);
  356. this.setStoredValues();
  357. this.isUpdating = false;
  358. },
  359.  
  360. applyStyle : function(css) {
  361. debug('Applying style', '"' + (css || '').match(this.regex) + '"');
  362. // add to style
  363. this.$style.text(css || '');
  364. this.setStoredValues();
  365. },
  366.  
  367. updateStyle : function() {
  368. this.isUpdating = true;
  369. var $panel = $('#ghd-settings-inner'),
  370. data = this.data;
  371.  
  372. data.attach = $panel.find('.ghd-attach').val();
  373. // get hex value directly
  374. data.color = this.picker.toHEXString();
  375. data.enable = $panel.find('.ghd-enable').is(':checked');
  376. data.font = $panel.find('.ghd-font').val();
  377. data.image = $panel.find('.ghd-image').val();
  378. data.tab = $panel.find('.ghd-tab').val();
  379. data.theme = $panel.find('.ghd-theme').val();
  380. data.type = $panel.find('.ghd-type').val();
  381. data.wrap = $panel.find('.ghd-wrap').is(':checked');
  382.  
  383. debug('Updating user settings', data);
  384.  
  385. this.$style.prop('disabled', !data.enable);
  386. $('body')
  387. .toggleClass('ghd-disabled', !data.enable)
  388. .toggleClass('nowrap', !data.wrap);
  389.  
  390. this.applyStyle(this.processStyle());
  391. this.getTheme();
  392. this.isUpdating = false;
  393. },
  394.  
  395. // user can force GitHub-dark update
  396. forceUpdate : function() {
  397. // clear saved date
  398. GM_setValue('version', 0);
  399. document.location.reload();
  400. },
  401.  
  402. buildSettings : function() {
  403. debug('Adding settings panel & GitHub Dark link to profile dropdown');
  404. // Script-specific CSS
  405. GM_addStyle([
  406. '#ghd-menu:hover { cursor:pointer }',
  407. '#ghd-settings { position:fixed; z-index: 65535; top:0; bottom:0; left:0; right:0; opacity:0; visibility:hidden; }',
  408. '#ghd-settings.in { opacity:1; visibility:visible; background:rgba(0,0,0,.5); }',
  409. '#ghd-settings-inner { position:fixed; left:50%; top:50%; transform:translate(-50%,-50%); width:25rem; box-shadow: 0 .5rem 1rem #111; color:#c0c0c0 }',
  410. '#ghd-settings label { margin-left:.5rem; position:relative; top:-1px }',
  411. '#ghd-settings-close { height: 1rem; width: 1rem; fill: #666; float:right; cursor:pointer }',
  412. '#ghd-settings-close:hover { fill: #ccc }',
  413. '#ghd-settings .ghd-right { float: right; }',
  414. '#ghd-settings p { line-height: 25px; }',
  415. '#ghd-swatch { width:25px; height:25px; display:inline-block; margin:3px 10px; border-radius:4px; }',
  416. '#ghd-settings .checkbox input { margin-top: .35em }',
  417. '#ghd-settings input[type="checkbox"] { width: 16px !important; height: 16px !important; border-radius: 3px !important; }',
  418. '#ghd-settings .boxed-group-inner { padding: 0; }',
  419. '#ghd-settings .ghd-footer { padding: 10px; border-top: #555 solid 1px; }',
  420. '#ghd-settings .ghd-settings-wrapper { max-height: 60vh; overflow-y:auto; padding: 1px 10px; }',
  421. '#ghd-settings .ghd-tab { width: 5em; }',
  422. '#ghd-settings .ghd-info { vertical-align: middle; }',
  423.  
  424. // code wrap toggle: https://gist.github.com/silverwind/6c1701f56e62204cc42b
  425. // icons next to a pre
  426. '.ghd-wrap-toggle { position:absolute; right:1.4em; margin-top:.2em; -moz-user-select:none; -webkit-user-select:none; cursor:pointer; z-index:20; }',
  427. // file & diff code tables
  428. '.ghd-wrap-table td.blob-code-inner { white-space: pre-wrap !important; word-break: break-all !important; }',
  429. '.ghd-unwrap-table td.blob-code-inner { white-space: pre !important; word-break: normal !important; }',
  430. // icons inside a wrapper immediatly around a pre
  431. '.highlight > .ghd-wrap-toggle { right:.5em; top:.5em; margin-top:0; }',
  432. // icons for non-syntax highlighted code blocks; see https://github.com/gjtorikian/html-proofer/blob/master/README.md
  433. '.markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) { right: 3.4em; }',
  434. '.ghd-wrap-toggle svg { height:1.25em; width:1.25em; fill:rgba(110,110,110,.4); }',
  435. '.ghd-wrap-toggle.unwrap:hover svg, .ghd-wrap-toggle:hover svg { fill:#8b0000; }', // wrap disabled (red)
  436. 'body:not(.nowrap) .ghd-wrap-toggle:not(.unwrap):hover svg, .ghd-wrap-toggle.wrapped:hover svg { fill:#006400; }', // wrap enabled (green)
  437. '.blob-wrapper, .markdown-body pre, .markdown-body .highlight { position:relative; }',
  438. // hide wrap icon when style disabled
  439. 'body.ghd-disabled .ghd-wrap-toggle { display: none; }'
  440. ].join(''));
  441.  
  442. var version = [],
  443. themes = '<select class="ghd-theme ghd-right">';
  444. $.each(this.themes, function(opt) {
  445. themes += '<option value="' + opt + '">' + opt + '</option>';
  446. });
  447. $.each(this.data.version.match(/\d{3}/g), function(i, v) {
  448. version.push(parseInt(v, 10));
  449. });
  450.  
  451. // Settings panel markup
  452. $('body').append([
  453. '<div id="ghd-settings">',
  454. '<div id="ghd-settings-inner" class="boxed-group">',
  455. '<h3>GitHub-Dark Settings',
  456. '<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>',
  457. '</h3>',
  458. '<div class="boxed-group-inner">',
  459. '<form>',
  460. '<div class="ghd-settings-wrapper">',
  461. '<p class="checkbox">',
  462. '<label>Enable GitHub-Dark<input class="ghd-enable ghd-right" type="checkbox"></label>',
  463. '</p>',
  464. '<p>',
  465. '<label>Color:</label> <input class="ghd-color ghd-right" type="text" value="#4183C4">',
  466. '<span id="ghd-swatch" class="ghd-right"></span>',
  467. '</p>',
  468. '<h4>Background</h4>',
  469. '<p>',
  470. '<label>Image:</label> <input class="ghd-image ghd-right" type="text">',
  471. '<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"><sup>?</sup></a>',
  472. '</p>',
  473. '<p>',
  474. '<label>Image type:</label>',
  475. '<select class="ghd-type ghd-right">',
  476. '<option value="tiled">Tiled</option>',
  477. '<option value="fit">Fit window</option>',
  478. '</select>',
  479. '</p>',
  480. '<p>',
  481. '<label>Image attachment:</label>',
  482. '<select class="ghd-attach ghd-right">',
  483. '<option value="scroll">Scroll</option>',
  484. '<option value="fixed">Fixed</option>',
  485. '</select>',
  486. '</p>',
  487. '<h4>Code</h4>',
  488. '<p><label>Theme:</label> ' + themes + '</select></p>',
  489. '<p>',
  490. '<label>Font Name:</label> <input class="ghd-font ghd-right" type="text">',
  491. '<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!"><sup>?</sup></a>',
  492. '</p>',
  493. '<p>',
  494. '<label>Tab Size:</label> <input class="ghd-tab ghd-right" type="text">',
  495. '</p>',
  496. '<p class="checkbox">',
  497. '<label>Wrap<input class="ghd-wrap ghd-right" type="checkbox"></label>',
  498. '</p>',
  499. '</div>',
  500. '<div class="ghd-footer">',
  501. '<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>&nbsp;',
  502. '<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>',
  503. '<span class="ghd-right tooltipped tooltipped-n" aria-label="Script v' + this.version + '&#10;CSS v' + version.join('.') + '">',
  504. '<svg class="ghd-info" xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24">',
  505. '<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"/>',
  506. '</svg>',
  507. '</span>',
  508. '</div>',
  509. '</form>',
  510. '</div>',
  511. '</div>',
  512. '</div>',
  513. ].join(''));
  514.  
  515. ghd.buildCodeWrap();
  516. },
  517.  
  518. buildCodeWrap : function() {
  519. // mutation events happen quick, so we still add an update flag
  520. this.isUpdating = true;
  521. // add wrap code icons
  522. $('.blob-wrapper').prepend(this.wrapIcon);
  523. $('.markdown-body pre').before(this.wrapIcon);
  524. this.isUpdating = false;
  525. },
  526.  
  527. // add keyboard shortcut to help menu (press "?")
  528. buildShortcut : function() {
  529. var openPanel = this.keyboardOpen.split('+'),
  530. toggleStyle = this.keyboardToggle.split('+');
  531. if (!$('.ghd-shortcut').length) {
  532. $('.keyboard-mappings:eq(0) tbody:eq(0)').append([
  533. '<tr class="ghd-shortcut">',
  534. '<td class="keys">',
  535. '<kbd>' + openPanel[0] + '</kbd> <kbd>' + openPanel[1] + '</kbd>',
  536. '</td>',
  537. '<td>GitHub-Dark: open settings</td>',
  538. '</tr>',
  539. '<tr class="ghd-shortcut">',
  540. '<td class="keys">',
  541. '<kbd>' + toggleStyle[0] + '</kbd> <kbd>' + toggleStyle[1] + '</kbd>',
  542. '</td>',
  543. '<td>GitHub-Dark: toggle style</td>',
  544. '</tr>'
  545. ].join(''));
  546. }
  547. },
  548.  
  549. bindEvents : function() {
  550. var menu, lastKey,
  551. $panel = $('#ghd-settings-inner'),
  552. $swatch = $panel.find('#ghd-swatch');
  553.  
  554. // finish initialization
  555. $('#ghd-settings-inner .ghd-enable')[0].checked = this.data.enable;
  556. $('body').toggleClass('ghd-disabled', !this.data.enable);
  557.  
  558. // Create our menu entry
  559. menu = $('<a id="ghd-menu" class="dropdown-item">GitHub Dark Settings</a>');
  560. $('.header .dropdown-item[href="/settings/profile"], .header .dropdown-item[data-ga-click*="go to profile"]')
  561. // gists only have the "go to profile" item; GitHub has both
  562. .filter(':last')
  563. .after(menu);
  564.  
  565. $('#ghd-menu').on('click', function() {
  566. ghd.openPanel();
  567. });
  568.  
  569. // not sure what GitHub uses, so rolling our own
  570. $(document).on('keypress keydown', function(e) {
  571. clearTimeout(ghd.timer);
  572. // use "g+o" to open up ghd options panel
  573. var openPanel = ghd.keyboardOpen.split('+'),
  574. toggleStyle = ghd.keyboardToggle.split('+'),
  575. key = String.fromCharCode(e.which).toLowerCase(),
  576. panelVisible = $('#ghd-settings').hasClass('in');
  577.  
  578. // press escape to close the panel
  579. if (e.which === 27 && panelVisible) {
  580. ghd.closePanel();
  581. return;
  582. }
  583. // use e.which from keypress for shortcuts
  584. // prevent opening panel while typing "go" in comments
  585. if (e.type === 'keydown' || /(input|textarea)/i.test(document.activeElement.nodeName)) {
  586. return;
  587. }
  588. if (lastKey === openPanel[0] && key === openPanel[1]) {
  589. if (panelVisible) {
  590. ghd.closePanel();
  591. } else {
  592. ghd.openPanel();
  593. }
  594. }
  595. if (lastKey === toggleStyle[0] && key === toggleStyle[1]) {
  596. ghd.toggleStyle();
  597. }
  598. lastKey = key;
  599. ghd.timer = setTimeout(function() {
  600. lastKey = null;
  601. }, ghd.keyboardDelay);
  602.  
  603. // add shortcut to help menu
  604. if (key === '?') {
  605. // table doesn't exist until user presses "?"
  606. setTimeout(function() {
  607. ghd.buildShortcut();
  608. }, 300);
  609. }
  610. });
  611.  
  612. // add bindings
  613. $('#ghd-settings, #ghd-settings-close').on('click keyup', function(e) {
  614. // press escape to close settings
  615. if (e.type === 'keyup' && e.which !== 27) {
  616. return;
  617. }
  618. ghd.closePanel();
  619. });
  620.  
  621. $panel.on('click', function(e) {
  622. e.stopPropagation();
  623. });
  624.  
  625. $panel.find('.ghd-reset').on('click', function() {
  626. ghd.isUpdating = true;
  627. // pass true to reset values
  628. ghd.setStoredValues(true);
  629. // add reset values back to this.data
  630. ghd.getStoredValues();
  631. // add reset values to panel
  632. ghd.updatePanel();
  633. // update style
  634. ghd.updateStyle();
  635. return false;
  636. });
  637.  
  638. $panel.find('input[type="text"]').on('focus', function() {
  639. // select all text when focused
  640. this.select();
  641. });
  642.  
  643. $panel.find('select, input').on('change', function() {
  644. if (!ghd.isUpdating) {
  645. ghd.updateStyle();
  646. }
  647. });
  648.  
  649. $panel.find('.ghd-update').on('click', function() {
  650. ghd.forceUpdate();
  651. return false;
  652. });
  653.  
  654. $('body').on('click', '.ghd-wrap-toggle', function() {
  655. var css,
  656. overallWrap = ghd.data.wrap,
  657. $this = $(this),
  658. $code = $this.next('code, pre, .highlight');
  659. if ($code.find('code').length) {
  660. $code = $code.find('code');
  661. }
  662. // code with line numbers
  663. if ($code[0].nodeName === 'TABLE') {
  664. if ($code[0].className.indexOf('ghd-') < 0) {
  665. css = !overallWrap;
  666. } else {
  667. css = $code.hasClass('ghd-unwrap-table');
  668. }
  669. $code
  670. .toggleClass('ghd-wrap-table', css)
  671. .toggleClass('ghd-unwrap-table', !css);
  672. $this
  673. .toggleClass('wrapped', css)
  674. .toggleClass('unwrap', !css);
  675. } else {
  676. css = $code.attr('style') || '';
  677. if (css === '') {
  678. css = ghd.wrapCss[overallWrap ? 'unwrap' : 'wrapped'];
  679. } else {
  680. css = ghd.wrapCss[css === ghd.wrapCss.wrapped ? 'unwrap' : 'wrapped'];
  681. }
  682. $code.attr('style', css);
  683. $this
  684. .toggleClass('wrapped', css === ghd.wrapCss.wrapped)
  685. .toggleClass('unwrap', css === ghd.wrapCss.unwrap);
  686. }
  687. });
  688.  
  689. this.picker = new jscolor($panel.find('.ghd-color')[0]);
  690. this.picker.zIndex = 65536;
  691. this.picker.hash = true;
  692. this.picker.backgroundColor = '#333';
  693. this.picker.padding = 0;
  694. this.picker.borderWidth = 0;
  695. this.picker.borderColor = '#444';
  696. this.picker.onFineChange = function() {
  697. $swatch[0].style.backgroundColor = '#' + ghd.picker;
  698. };
  699. },
  700.  
  701. openPanel : function() {
  702. $('.modal-backdrop').click();
  703. ghd.updatePanel();
  704. $('#ghd-settings').addClass('in');
  705. },
  706.  
  707. closePanel : function() {
  708. $('#ghd-settings').removeClass('in');
  709. ghd.picker.hide();
  710.  
  711. // apply changes when the panel is closed
  712. ghd.updateStyle();
  713. },
  714.  
  715. toggleStyle : function() {
  716. var isEnabled = !this.data.enable;
  717. this.data.enable = isEnabled;
  718. $('#ghd-settings-inner .ghd-enable').prop('checked', isEnabled);
  719. // add processedCss back into style (emptied when disabled)
  720. if (isEnabled) {
  721. this.addSavedStyle();
  722. }
  723. this.$style.prop('disabled', !isEnabled);
  724. },
  725.  
  726. init : function() {
  727. debug('GitHub-Dark Script initializing!');
  728.  
  729. // add style tag to head
  730. ghd.$style = $('<style class="ghd-style">').appendTo('head');
  731.  
  732. this.getStoredValues();
  733. // save stored theme stored themes
  734. if (this.data.themeCss) {
  735. this.themes[this.data.theme] = this.data.themeCss;
  736. }
  737.  
  738. this.$style.prop('disabled', !this.data.enable);
  739.  
  740. // only load package.json once a day, or after a forced update
  741. if ((new Date().getTime() > this.data.date + this.delay) || this.data.version === 0) {
  742. // get package.json from GitHub-Dark & compare versions
  743. // load new script if a newer one is available
  744. this.checkVersion();
  745. } else {
  746. this.addSavedStyle();
  747. }
  748. }
  749. };
  750.  
  751. // add style at document-start
  752. ghd.init();
  753.  
  754. $(function() {
  755. // add panel even if you're not logged in - open panel using keyboard shortcut
  756. ghd.buildSettings();
  757. // add event binding on document ready
  758. ghd.bindEvents();
  759.  
  760. var targets = document.querySelectorAll('#js-repo-pjax-container, #js-pjax-container, .js-contribution-activity');
  761.  
  762. Array.prototype.forEach.call(targets, function(target) {
  763. new MutationObserver(function(mutations) {
  764. mutations.forEach(function(mutation) {
  765. // preform checks before adding code wrap to minimize function calls
  766. if (!(ghd.isUpdating || document.querySelectorAll('.ghd-wrap-toggle').length) && mutation.target === target) {
  767. ghd.buildCodeWrap();
  768. }
  769. });
  770. }).observe(target, {
  771. childList: true,
  772. subtree: true
  773. });
  774. });
  775. });
  776.  
  777. // include a "?debug" anywhere in the browser URL to enable debugging
  778. function debug() {
  779. if (/\?debug/.test(window.location.href)) {
  780. console.log.apply(console, arguments);
  781. }
  782. }
  783. })(jQuery.noConflict(true));