GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

目前为 2016-01-03 提交的版本。查看 最新版本

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