GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

Versione datata 03/06/2016. Vedi la nuova versione l'ultima versione.

  1. // ==UserScript==
  2. // @name GitHub Dark Script
  3. // @version 1.0.13
  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_info
  13. // @grant GM_xmlhttpRequest
  14. // @connect githubusercontent.com
  15. // @connect raw.githubusercontent.com
  16. // @run-at document-start
  17. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js
  18. // @require https://greatest.deepsurf.us/scripts/15563-jscolor/code/jscolor.js?version=106439
  19. // ==/UserScript==
  20. /* global jQuery, jscolor */
  21. /* eslint-disable indent, quotes */
  22. (function($) {
  23. 'use strict';
  24.  
  25. var ghd = {
  26.  
  27. version : GM_info.script.version,
  28.  
  29. // delay until package.json allowed to load
  30. delay : 8.64e7, // 24 hours in milliseconds
  31.  
  32. // Keyboard shortcut to open ghd panel (only a two key combo coded)
  33. keyboardOpen : 'g+0',
  34. keyboardToggle : 'g+-',
  35. // keyboard shortcut delay from first to second letter
  36. keyboardDelay : 1000,
  37.  
  38. // base urls to fetch style and package.json
  39. root : 'https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/',
  40.  
  41. defaults : {
  42. attach : 'scroll',
  43. color : '#4183C4',
  44. enable : true,
  45. font : 'Menlo',
  46. image : 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGBAMAAACDAP+3AAAAGFBMVEUfHx8eHh4dHR0bGxshISEiIiIlJSUjIyM9IpsJAAAFjUlEQVR4AT3UuZLcOBaF4QuI2XJxboIhF/eQFe1WovoBAAqccpkaZpc5+4yrXa8/RGpx/lrIXPjFCYjTp9z8REqF4VYNWB3Av3zQJ6b6xBwlKB/9kRkCjXVwGH3ziK5UcjFHVkmgY6osiBsGDFfseqq2ZbTz7E00qBDpzOxnD7ToABeros1vM6MX0rBQaG1ith1A/HJkvkHxsPGJ82dP8vVCyWmbyPTaAfGzg40bgIdrv2f3pBVPycUcufx+BSUUWDuCZi6zBqdM50ElKYPODqtLDjc31rBb9CZ59lbN/JScuMxHLUBcGiy6QRH9zpwgZGhRj8qSydPVgNNVgbWqYX3HbM9K2rqTnKVmsmwKWzc1ffEd20+Zq3Ji65kl6TSjALNvzmJt4Pi2f1etytGJmy5erLAgbNY4bjykC3YCLIS3nSZMKgwRsBarWgjdeVzIEDzpTkoOUArTF4WFXYHwxY585sT0nmTYMxmXfs8fzwswfnam8TMU49bvqSRnyRPnqlno4tVQQiH2A9Za8tNTfXQ0lxbSxUaZna0uLlj9Q0XzD96CpsOZUftolINKBWJpAOoAJC0T6QqZnOtfvcfJFcDrD4Cuy5Hng316XrqzJ204HynyHwWed6i+XGF40Uw2T7Lc71HyssngEOrgONfBY7wvW0UZdVAma5xmSNjRp3xkvKJkW6aSg7PK4K0+mbKqYB0WYBgWwxCXiS74zBCVlEFpYQDEwjcA1qccb5yO6ZL8ozt/h3wHSCdWzLuqxU2ZZ9ev9MvRMbMvV9BQgN0qrFjlkzPQanI9nuaGCokVK2LV1Y2egyY1aFQGxjM9I7RBBAgyGEJtpKHP0lUySSeWCpyKHMT2pmM/vyP55u2Rw5lcSeabAfgiG5TPDX3uP3QvcoSipJXQByUCjS4C8VXqxEEZOJxzmJoyogFNJBRsCJs2XmoWWrWFqTsnbwtSn43gNFTTob9/SEpaPJNhUBKDGoZGCMINxvBv8vuKbb//lg/sK0wfPgBica/QsSk5F3KK4Ui6Yw+uv4+DWEOFbhdPOnbY5PLFpzrZMhakeqomY0Vz0TO+elQGTWdCk1IYFAOaoZg0IJQhT+YreXF+yia+O1cgtGufjXxQw28f85RPXfd15zv13ABoD15kB7FKJ/7pbHKP6+9TgNgkVj68NeV8Tp24f7OOndCgJzR3RNJBPNFReCmstMVqvjjzBoeK4GOFoBN32CPxu+4TwwBDa4DJTe/OU9c9ku7EGyfOVxh+fw9g/AATxPqKTEXJKEdCIBkB4iBUlO6MjUrWi6M5Kz31YAqFsYaCeB0KJC5d1+foo3LQWSfRaDrwdAQrMEC27yDZXJf7TlOJ2Bczr1di3OWvZB6XrvvqPuWJPDk9dAHgm7LvuZJTEdKqO3J3XgostArEnvkqgUznx3PX7cSzz1FXZyvakTA4XVVMbCPFPK1cFj66S0WoqQI1XG2uoU7CMPquO2VaUDJFQMdVgXKD2bpz6ufzzxXbxszHQ9fGO/F7A998yBQG6cShE+P+Pk7t1FwfF1QHN1Eui1VapRxCdj8tCtI1bog1Fo011Sx9u3o6c9bufI6wAT26Av9xJ+WWpTKbbBPp3K/1LbC4Vuhv396RCbJw4untjxVPndj+dIB9dVD8z2dylZ+6vMeJwbYChHJkvHV2J3fdHsJPASeHhrXq6QheXu1nBhUr5u6ryT0I13BFKD01ViZ/n3oaziRG7c6Ayg7g1LPeztNdT36ueMqcN4XGv3finjfv+7I/kMJ4d046MUanOA1QtMH1kLlfFasm99NiutSw63yNDeH4zeL1Uu8XKHNfcThPSSNwchGMbgUETScwkCcK77pH2jsgrAssvVyB8FLJ7GrmwyD8eVqsHoY/FwIv9T7lPu9+Yf8/9+w4nS1ma78AAAAASUVORK5CYII=")',
  47. tab : 4,
  48. theme : 'Twilight',
  49. type : 'tiled',
  50. wrap : false
  51. },
  52.  
  53. // url gets replaced by css when loaded
  54. themes : {
  55. 'Ambiance' : 'themes/ambiance.min.css',
  56. 'Chaos' : 'themes/chaos.min.css',
  57. 'Clouds Midnight' : 'themes/clouds-midnight.min.css',
  58. 'Cobalt' : 'themes/cobalt.min.css',
  59. 'Idle Fingers' : 'themes/idle-fingers.min.css',
  60. 'Kr Theme' : 'themes/kr-theme.min.css',
  61. 'Merbivore' : 'themes/merbivore.min.css',
  62. 'Merbivore Soft' : 'themes/merbivore-soft.min.css',
  63. 'Mono Industrial' : 'themes/mono-industrial.min.css',
  64. 'Mono Industrial Clear' : 'themes/mono-industrial-clear.min.css',
  65. 'Monokai' : 'themes/monokai.min.css',
  66. 'Obsidian' : 'themes/obsidian.min.css',
  67. 'Pastel on Dark' : 'themes/pastel-on-dark.min.css',
  68. 'Solarized Dark' : 'themes/solarized-dark.min.css',
  69. 'Terminal' : 'themes/terminal.min.css',
  70. 'Tomorrow Night' : 'themes/tomorrow-night.min.css',
  71. 'Tomorrow Night Blue' : 'themes/tomorrow-night-blue.min.css',
  72. 'Tomorrow Night Bright' : 'themes/tomorrow-night-bright.min.css',
  73. 'Tomorrow Night Eigthies' : 'themes/tomorrow-night-eighties.min.css',
  74. 'Twilight' : 'themes/twilight.min.css',
  75. 'Vibrant Ink' : 'themes/vibrant-ink.min.css'
  76. },
  77.  
  78. type : {
  79. 'tiled' : 'background-repeat: repeat !important; background-size: auto !important; background-position: left top !important;',
  80. 'fit' : 'background-repeat: no-repeat !important; background-size: cover !important; background-position: center top !important;'
  81. },
  82.  
  83. wrapCss : {
  84. 'wrapped' : 'white-space: pre-wrap !important; word-break: break-all !important; display: block !important;',
  85. 'unwrap' : 'white-space: pre !important; word-break: normal !important; display: block !important;'
  86. },
  87.  
  88. wrapCodeCss : [
  89. '/* GitHub: Enable wrapping of long code lines */',
  90. ' .blob-code-inner,',
  91. ' .markdown-body pre > code,',
  92. ' .markdown-body .highlight > pre {',
  93. ' white-space: pre-wrap !important;',
  94. ' word-break: break-all !important;',
  95. ' overflow-wrap: break-word !important;',
  96. ' display: block !important;',
  97. ' }',
  98. ' td.blob-code-inner {',
  99. ' display: table-cell !important;',
  100. ' }'
  101. ].join('\n'),
  102.  
  103. 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>',
  104.  
  105. // extract style & theme name
  106. regex: /\/\*! [^\*]+ \*\//,
  107.  
  108. updatePanel : function() {
  109. // prevent multiple change events from processing
  110. this.isUpdating = true;
  111.  
  112. var color,
  113. data = this.data,
  114. defaults = this.defaults,
  115. $panel = $('#ghd-settings-inner');
  116.  
  117. $panel.find('.ghd-attach').val(data.attach || defaults.attach);
  118. $panel.find('.ghd-font').val(data.font || defaults.font);
  119. $panel.find('.ghd-image').val(data.image || defaults.image);
  120. $panel.find('.ghd-tab').val(data.tab || defaults.tab);
  121. $panel.find('.ghd-theme').val(data.theme || defaults.theme);
  122. $panel.find('.ghd-type').val(data.type || defaults.type);
  123.  
  124. $panel.find('.ghd-enable').prop('checked', typeof data.enable === 'boolean' ? data.enable : defaults.enable);
  125. $panel.find('.ghd-wrap').prop('checked', typeof data.wrap === 'boolean' ? data.wrap : defaults.wrap);
  126.  
  127. color = data.color || defaults.color;
  128. $panel.find('.ghd-color').val(color);
  129. // update swatch color & color picker value
  130. $panel.find('#ghd-swatch').css('background-color', color);
  131.  
  132. if (this.picker) {
  133. this.picker.fromString(color);
  134. }
  135. this.$style.prop('disabled', !data.enable);
  136. $('body')
  137. .toggleClass('ghd-disabled', !data.enable)
  138. .toggleClass('nowrap', !data.wrap);
  139.  
  140. this.isUpdating = false;
  141. },
  142.  
  143. /*
  144. this.data = {
  145. attach : 'scroll',
  146. color : '#4183C4',
  147. enable : true,
  148. font : 'Menlo',
  149. image : 'url()',
  150. tab : 4,
  151. theme : 'Tomorrow Night',
  152. type : 'tiled',
  153. wrap : true, // code: wrap long lines
  154.  
  155. date : 1450159200000, // last loaded package.json
  156. version : '001014032', // v1.14.32 = last stored GitHub-Dark version
  157.  
  158. rawCss : '@-moz-document regexp("^...', // github-dark.css (unprocessed css)
  159. themeCss : '/*! Tomorrow Night * /.ace_editor,.highlight{...', // theme/{name}.min.css
  160. processedCss : '' // css saved directly from this.$style
  161. }
  162. */
  163. getStoredValues : function() {
  164. var defaults = this.defaults;
  165.  
  166. this.data = {
  167. attach : GM_getValue('attach', defaults.attach),
  168. color : GM_getValue('color', defaults.color),
  169. enable : GM_getValue('enable', defaults.enable),
  170. font : GM_getValue('font', defaults.font),
  171. image : GM_getValue('image', defaults.image),
  172. tab : GM_getValue('tab', defaults.tab),
  173. theme : GM_getValue('theme', defaults.theme),
  174. type : GM_getValue('type', defaults.type),
  175. wrap : GM_getValue('wrap', defaults.wrap),
  176.  
  177. date : GM_getValue('date', 0),
  178. version : GM_getValue('version', 0),
  179.  
  180. rawCss : GM_getValue('rawCss', ''),
  181. themeCss : GM_getValue('themeCss', ''),
  182. processedCss : GM_getValue('processedCss', '')
  183. };
  184.  
  185. debug('Retrieved stored values', this.data);
  186. },
  187.  
  188. setStoredValues : function(reset) {
  189. var data = this.data,
  190. defaults = this.defaults;
  191.  
  192. GM_setValue('attach', reset ? defaults.attach : data.attach);
  193. GM_setValue('color', reset ? defaults.color : data.color);
  194. GM_setValue('enable', reset ? defaults.enable : data.enable);
  195. GM_setValue('font', reset ? defaults.font : data.font);
  196. GM_setValue('image', reset ? defaults.image : data.image);
  197. GM_setValue('tab', reset ? defaults.tab : data.tab);
  198. GM_setValue('theme', reset ? defaults.theme : data.theme);
  199. GM_setValue('type', reset ? defaults.type : data.type);
  200. GM_setValue('wrap', reset ? defaults.wrap : data.wrap);
  201.  
  202. GM_setValue('date', reset ? 0 : data.date);
  203. GM_setValue('version', reset ? 0 : data.version);
  204.  
  205. GM_setValue('rawCss', reset ? '' : data.rawCss);
  206. GM_setValue('themeCss', reset ? '' : data.themeCss);
  207. GM_setValue('processedCss', ghd.$style.text());
  208.  
  209. debug((reset ? 'Resetting' : 'Saving') + ' current values', data);
  210. },
  211.  
  212. // convert version "1.2.3" into "001002003" for easier comparison
  213. convertVersion : function(val) {
  214. var index,
  215. parts = val ? val.split('.') : '',
  216. str = '',
  217. len = parts.length;
  218. for (index = 0; index < len; index++) {
  219. str += ('000' + parts[index]).slice(-3);
  220. }
  221. debug('Converted version "' + val + '" to "' + str + '" for easy comparison');
  222. return val ? str : val;
  223. },
  224.  
  225. checkVersion : function() {
  226. debug('Fetching package.json');
  227. GM_xmlhttpRequest({
  228. method : 'GET',
  229. url : ghd.root + 'package.json',
  230. onload : function(response) {
  231. // store package JSON (not accessed anywhere else, but just in case)
  232. ghd.data.package = JSON.parse(response.responseText);
  233.  
  234. // save last loaded date, so package.json is only loaded once a day
  235. ghd.data.date = new Date().getTime();
  236. GM_setValue('date', ghd.data.date);
  237.  
  238. var version = ghd.convertVersion(ghd.data.package.version);
  239. // if new available, load it & parse
  240. if (version > ghd.data.version) {
  241. if (ghd.data.version !== 0) {
  242. debug('Updating from', ghd.data.version, 'to', version);
  243. }
  244. ghd.data.version = version;
  245. GM_setValue('version', ghd.data.version);
  246. ghd.fetchAndApplyStyle(ghd.fetchAndApplyTheme.bind(ghd));
  247. } else {
  248. ghd.addSavedStyle();
  249. }
  250. }
  251. });
  252. },
  253.  
  254. fetchAndApplyStyle : function(cb) {
  255. debug('Fetching github-dark.css');
  256. GM_xmlhttpRequest({
  257. method : 'GET',
  258. url : ghd.root + 'github-dark.css',
  259. onload : function(response) {
  260. ghd.data.rawCss = response.responseText;
  261. ghd.applyStyle(ghd.processStyle());
  262. if (cb) cb();
  263. }
  264. });
  265. },
  266.  
  267. // load syntax highlighting theme
  268. fetchAndApplyTheme : function(cb) {
  269. if (!this.data.enable) {
  270. debug('Disabled: stop theme processing');
  271. return;
  272. }
  273. var name = this.data.theme || 'Twilight';
  274. var themeUrl = ghd.root + ghd.themes[name];
  275. debug('Fetching ' + name + ' theme', themeUrl);
  276. GM_xmlhttpRequest({
  277. method : 'GET',
  278. url : themeUrl,
  279. onload : function(response) {
  280. var theme = response.responseText;
  281. if (response.status === 200 && theme) {
  282. ghd.data.themeCss = theme;
  283.  
  284. debug('Adding syntax theme to css');
  285. var css = ghd.$style.text() || '';
  286. css = css.replace('/*[[syntax-theme]]*/', ghd.data.themeCss || '');
  287.  
  288. ghd.applyStyle(css);
  289. ghd.isUpdating = false;
  290. if (cb) cb();
  291. } else {
  292. throw Error('Failed to load theme file', '"' + theme + '"');
  293. }
  294. }
  295. });
  296. },
  297.  
  298.  
  299. addSavedStyle : function() {
  300. debug('Adding previously saved style');
  301. // apply already processed css to prevent FOUC
  302. this.$style.text(this.data.processedCss);
  303. },
  304.  
  305. processStyle : function() {
  306. var data = this.data,
  307. url = /^url/.test(data.image || '') ? data.image :
  308. (data.image === 'none' ? 'none' : 'url("' + data.image + '")');
  309. if (!data.enable) {
  310. debug('Disabled: stop processing');
  311. return;
  312. }
  313. debug('Processing set styles');
  314.  
  315. var ret = (data.rawCss || '')
  316. // remove moz-document wrapper
  317. .replace(/@-moz-document regexp\((.*)\) \{(\n|\r)+/, '')
  318. // replace background image; if no 'url' at start, then use 'none'
  319. .replace(/\/\*\[\[bg-choice\]\]\*\/ url\(.*\)/, url)
  320. // Add tiled or fit window size css
  321. .replace('/*[[bg-options]]*/', this.type[data.type || 'tiled'])
  322. // set scroll or fixed background image
  323. .replace('/*[[bg-attachment]]*/ fixed', data.attach || 'scroll')
  324. // replace base-color
  325. .replace(/\/\*\[\[base-color\]\]\*\/ #\w{3,6}/g, data.color || '#4183C4')
  326. // add font choice
  327. .replace('/*[[font-choice]]*/', data.font || 'Menlo')
  328. // add tab size
  329. .replace(/\/\*\[\[tab-size\]\]\*\/ \d+/g, data.tab || 4)
  330. // code wrap css
  331. .replace('/*[[code-wrap]]*/', data.wrap ? ghd.wrapCodeCss : '')
  332. // remove default syntax
  333. .replace(/\s+\/\* grunt build - remove to end of file(.*(\n|\r))+\}$/m, '');
  334.  
  335. // see https://github.com/StylishThemes/GitHub-Dark/issues/275
  336. if (/firefox/i.test(navigator.userAgent)) {
  337. ret = ret
  338. .replace(/select, input, textarea/, 'select, input:not([type="checkbox"]), textarea')
  339. .replace(/input\[type=\"checkbox\"\][\s\S]+?}/gm, '');
  340. }
  341. return ret;
  342. },
  343.  
  344. applyStyle : function(css) {
  345. debug('Applying style', '"' + (css || '').match(this.regex) + '"');
  346. this.$style.text(css || '');
  347. this.setStoredValues();
  348. },
  349.  
  350. updateStyle : function() {
  351. this.isUpdating = true;
  352. var $panel = $('#ghd-settings-inner'),
  353. data = this.data;
  354.  
  355. data.attach = $panel.find('.ghd-attach').val();
  356. // get hex value directly
  357. data.color = this.picker.toHEXString();
  358. data.enable = $panel.find('.ghd-enable').is(':checked');
  359. data.font = $panel.find('.ghd-font').val();
  360. data.image = $panel.find('.ghd-image').val();
  361. data.tab = $panel.find('.ghd-tab').val();
  362. data.theme = $panel.find('.ghd-theme').val();
  363. data.type = $panel.find('.ghd-type').val();
  364. data.wrap = $panel.find('.ghd-wrap').is(':checked');
  365.  
  366. debug('Updating user settings', data);
  367.  
  368. this.$style.prop('disabled', !data.enable);
  369. $('body')
  370. .toggleClass('ghd-disabled', !data.enable)
  371. .toggleClass('nowrap', !data.wrap);
  372.  
  373. this.fetchAndApplyStyle(this.fetchAndApplyTheme.bind(this));
  374. this.isUpdating = false;
  375. },
  376.  
  377. // user can force GitHub-dark update
  378. forceUpdate : function(css) {
  379. if (css) {
  380. // add raw css directly for style testing
  381. this.data.rawCss = css;
  382. this.applyStyle(ghd.processStyle());
  383. this.fetchAndApplyTheme();
  384. } else {
  385. // clear saved date
  386. GM_setValue('version', 0);
  387. document.location.reload();
  388. }
  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; padding:5px; }',
  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. '#ghd-settings .ghd-info, .ghd-file-toggle svg { vertical-align: middle !important; }',
  412. '#ghd-settings .paste-area { position:absolute; bottom:50px; top:37px; left:2px; right:2px; width:396px; z-index:0; }',
  413.  
  414. // code wrap toggle: https://gist.github.com/silverwind/6c1701f56e62204cc42b
  415. // icons next to a pre
  416. '.ghd-wrap-toggle { position:absolute; right:1.4em; margin-top:.2em; -moz-user-select:none; -webkit-user-select:none; cursor:pointer; z-index:20; }',
  417. // file & diff code tables
  418. '.ghd-wrap-table td.blob-code-inner { white-space: pre-wrap !important; word-break: break-all !important; }',
  419. '.ghd-unwrap-table td.blob-code-inner { white-space: pre !important; word-break: normal !important; }',
  420. // icons inside a wrapper immediatly around a pre
  421. '.highlight > .ghd-wrap-toggle { right:.5em; top:.5em; margin-top:0; }',
  422. // icons for non-syntax highlighted code blocks; see https://github.com/gjtorikian/html-proofer/blob/master/README.md
  423. '.markdown-body:not(.comment-body) .ghd-wrap-toggle:not(:first-child) { right: 3.4em; }',
  424. '.ghd-wrap-toggle svg { height:1.25em; width:1.25em; fill:rgba(110,110,110,.4); }',
  425. '.ghd-wrap-toggle.unwrap:hover svg, .ghd-wrap-toggle:hover svg { fill:#8b0000; }', // wrap disabled (red)
  426. 'body:not(.nowrap) .ghd-wrap-toggle:not(.unwrap):hover svg, .ghd-wrap-toggle.wrapped:hover svg { fill:#006400; }', // wrap enabled (green)
  427. '.blob-wrapper, .markdown-body pre, .markdown-body .highlight { position:relative; }',
  428. // hide wrap icon when style disabled
  429. 'body.ghd-disabled .ghd-wrap-toggle, .ghd-collapsed-file, .file.open .data.ghd-collapsed-file { display: none; }',
  430. // monospace font toggle
  431. '.ghd-monospace-font { font-family: Menlo, Inconsolata, "Droid Mono", monospace !important; font-size: 1em !important; }',
  432. // file collapsed icon
  433. '.ghd-file-collapsed svg { -webkit-transform:rotate(90deg); transform:rotate(90deg); }'
  434. ].join(''));
  435.  
  436. var version = [],
  437. themes = '<select class="ghd-theme ghd-right">',
  438. // convert stored css version from "001014049" into "1.14.49" for tooltip
  439. parts = String(this.data.version).match(/\d{3}/g);
  440. $.each(this.themes, function(opt) {
  441. themes += '<option value="' + opt + '">' + opt + '</option>';
  442. });
  443. if (parts && parts.length) {
  444. $.each(parts, function(_i, v) {
  445. version.push(parseInt(v));
  446. });
  447. }
  448.  
  449. // Settings panel markup
  450. $('body').append([
  451. '<div id="ghd-settings">',
  452. '<div id="ghd-settings-inner" class="boxed-group">',
  453. '<h3>GitHub-Dark Settings',
  454. '<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>',
  455. '</h3>',
  456. '<div class="boxed-group-inner">',
  457. '<form>',
  458. '<div class="ghd-settings-wrapper">',
  459. '<p class="checkbox">',
  460. '<label>Enable GitHub-Dark<input class="ghd-enable ghd-right" type="checkbox"></label>',
  461. '</p>',
  462. '<p>',
  463. '<label>Color:</label> <input class="ghd-color ghd-right" type="text" value="#4183C4">',
  464. '<span id="ghd-swatch" class="ghd-right"></span>',
  465. '</p>',
  466. '<h4>Background</h4>',
  467. '<p>',
  468. '<label>Image:</label> <input class="ghd-image ghd-right" type="text">',
  469. '<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>',
  470. '</p>',
  471. '<p>',
  472. '<label>Image type:</label>',
  473. '<select class="ghd-type ghd-right">',
  474. '<option value="tiled">Tiled</option>',
  475. '<option value="fit">Fit window</option>',
  476. '</select>',
  477. '</p>',
  478. '<p>',
  479. '<label>Image attachment:</label>',
  480. '<select class="ghd-attach ghd-right">',
  481. '<option value="scroll">Scroll</option>',
  482. '<option value="fixed">Fixed</option>',
  483. '</select>',
  484. '</p>',
  485. '<h4>Code</h4>',
  486. '<p><label>Theme:</label> ' + themes + '</select></p>',
  487. '<p>',
  488. '<label>Font Name:</label> <input class="ghd-font ghd-right" type="text">',
  489. '<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>',
  490. '</p>',
  491. '<p>',
  492. '<label>Tab Size:</label> <input class="ghd-tab ghd-right" type="text">',
  493. '</p>',
  494. '<p class="checkbox">',
  495. '<label>Wrap<input class="ghd-wrap ghd-right" type="checkbox"></label>',
  496. '</p>',
  497. '</div>',
  498. '<div class="ghd-footer">',
  499. '<div class="btn-group">',
  500. '<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>',
  501. '<a href="#" class="ghd-textarea-toggle btn btn-sm tooltipped tooltipped-n" aria-label="Paste CSS update">',
  502. '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewbox="0 0 16 16" fill="#eee">',
  503. '<path d="M15 11 1 11 8 3z"/>',
  504. '</svg>',
  505. '</a>',
  506. '<div class="paste-area-content" aria-hidden="true" style="display:none">',
  507. '<textarea class="paste-area" placeholder="Paste GitHub-Dark Style here!"></textarea>',
  508. '</div>',
  509. '</div>&nbsp;',
  510. '<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>',
  511. '<span class="ghd-right tooltipped tooltipped-n" aria-label="Script v' + this.version + '&#10;CSS ' + (version.length ? 'v' + version.join('.') : 'unknown') + '">',
  512. '<svg class="ghd-info" xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24">',
  513. '<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"/>',
  514. '</svg>',
  515. '</span>',
  516. '</div>',
  517. '</form>',
  518. '</div>',
  519. '</div>',
  520. '</div>',
  521. ].join(''));
  522.  
  523. ghd.updateToggles();
  524. },
  525.  
  526. // Add code wrap toggle
  527. buildCodeWrap : function() {
  528. // mutation events happen quick, so we still add an update flag
  529. this.isUpdating = true;
  530. // add wrap code icons
  531. $('.blob-wrapper').prepend(this.wrapIcon);
  532. $('.markdown-body pre').before(this.wrapIcon);
  533. this.isUpdating = false;
  534. },
  535.  
  536. // Add monospace font toggle
  537. addMonospaceToggle : function() {
  538. this.isUpdating = true;
  539. var indx, $el,
  540. $toolbars = $('.toolbar-commenting'),
  541. len = $toolbars.length;
  542. for (indx = 0; indx < len; indx++) {
  543. $el = $toolbars.eq(indx);
  544. if (!$el.find('.ghd-monospace').length) {
  545. $el.prepend([
  546. '<button type="button" class="ghd-monospace toolbar-item tooltipped tooltipped-n" aria-label="Toggle monospaced font" tabindex="-1">',
  547. '<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewbox="0 0 32 32">',
  548. '<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"/>',
  549. '</svg>',
  550. '</button>'
  551. ].join(''));
  552. }
  553. }
  554. this.isUpdating = false;
  555. },
  556.  
  557. // Add file diffs toggle
  558. addFileToggle : function() {
  559. this.updating = true;
  560. var indx, $el,
  561. $files = $('#files .file-actions'),
  562. len = $files.length;
  563. for (indx = 0; indx < len; indx++) {
  564. $el = $files.eq(indx);
  565. if (!$el.find('.ghd-file-toggle').length) {
  566. $el.append([
  567. '<button type="button" class="ghd-file-toggle btn btn-sm tooltipped tooltipped-n" aria-label="Click to Expand or Collapse file" tabindex="-1">',
  568. '<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="10" height="6.5" viewBox="0 0 10 6.5">',
  569. '<path d="M0 1.497L1.504 0l3.49 3.76L8.505.016 10 1.52 4.988 6.51 0 1.496z"/>',
  570. '</svg>',
  571. '</button>'
  572. ].join(''));
  573. }
  574. }
  575. this.updating = false;
  576. },
  577.  
  578. // Add toggle buttons after page updates
  579. updateToggles : function() {
  580. ghd.buildCodeWrap();
  581. ghd.addMonospaceToggle();
  582. ghd.addFileToggle();
  583. },
  584.  
  585. // add keyboard shortcut to help menu (press "?")
  586. buildShortcut : function() {
  587. var openPanel = this.keyboardOpen.split('+'),
  588. toggleStyle = this.keyboardToggle.split('+');
  589. if (!$('.ghd-shortcut').length) {
  590. $('.keyboard-mappings:eq(0) tbody:eq(0)').append([
  591. '<tr class="ghd-shortcut">',
  592. '<td class="keys">',
  593. '<kbd>' + openPanel[0] + '</kbd> <kbd>' + openPanel[1] + '</kbd>',
  594. '</td>',
  595. '<td>GitHub-Dark: open settings</td>',
  596. '</tr>',
  597. '<tr class="ghd-shortcut">',
  598. '<td class="keys">',
  599. '<kbd>' + toggleStyle[0] + '</kbd> <kbd>' + toggleStyle[1] + '</kbd>',
  600. '</td>',
  601. '<td>GitHub-Dark: toggle style</td>',
  602. '</tr>'
  603. ].join(''));
  604. }
  605. },
  606.  
  607. bindEvents : function() {
  608. var menu, lastKey,
  609. $panel = $('#ghd-settings-inner'),
  610. $swatch = $panel.find('#ghd-swatch');
  611.  
  612. // finish initialization
  613. $('#ghd-settings-inner .ghd-enable')[0].checked = this.data.enable;
  614. $('body').toggleClass('ghd-disabled', !this.data.enable);
  615.  
  616. // Create our menu entry
  617. menu = $('<a id="ghd-menu" class="dropdown-item">GitHub Dark Settings</a>');
  618. $('.header .dropdown-item[href="/settings/profile"], .header .dropdown-item[data-ga-click*="go to profile"]')
  619. // gists only have the "go to profile" item; GitHub has both
  620. .filter(':last')
  621. .after(menu);
  622.  
  623. $('#ghd-menu').on('click', function() {
  624. ghd.openPanel();
  625. });
  626.  
  627. // not sure what GitHub uses, so rolling our own
  628. $(document).on('keypress keydown', function(e) {
  629. clearTimeout(ghd.timer);
  630. // use "g+o" to open up ghd options panel
  631. var openPanel = ghd.keyboardOpen.split('+'),
  632. toggleStyle = ghd.keyboardToggle.split('+'),
  633. key = String.fromCharCode(e.which).toLowerCase(),
  634. panelVisible = $('#ghd-settings').hasClass('in');
  635.  
  636. // press escape to close the panel
  637. if (e.which === 27 && panelVisible) {
  638. ghd.closePanel();
  639. return;
  640. }
  641. // use e.which from keypress for shortcuts
  642. // prevent opening panel while typing "go" in comments
  643. if (e.type === 'keydown' || /(input|textarea)/i.test(document.activeElement.nodeName)) {
  644. return;
  645. }
  646. if (lastKey === openPanel[0] && key === openPanel[1]) {
  647. if (panelVisible) {
  648. ghd.closePanel();
  649. } else {
  650. ghd.openPanel();
  651. }
  652. }
  653. if (lastKey === toggleStyle[0] && key === toggleStyle[1]) {
  654. ghd.toggleStyle();
  655. }
  656. lastKey = key;
  657. ghd.timer = setTimeout(function() {
  658. lastKey = null;
  659. }, ghd.keyboardDelay);
  660.  
  661. // add shortcut to help menu
  662. if (key === '?') {
  663. // table doesn't exist until user presses "?"
  664. setTimeout(function() {
  665. ghd.buildShortcut();
  666. }, 300);
  667. }
  668. });
  669.  
  670. // add ghd-settings panel bindings
  671. $('#ghd-settings, #ghd-settings-close').on('click keyup', function(e) {
  672. // press escape to close settings
  673. if (e.type === 'keyup' && e.which !== 27) {
  674. return;
  675. }
  676. ghd.closePanel();
  677. });
  678.  
  679. $panel.on('click', function(e) {
  680. e.stopPropagation();
  681. });
  682.  
  683. $panel.find('.ghd-reset').on('click', function() {
  684. ghd.isUpdating = true;
  685. // pass true to reset values
  686. ghd.setStoredValues(true);
  687. // add reset values back to this.data
  688. ghd.getStoredValues();
  689. // add reset values to panel
  690. ghd.updatePanel();
  691. // update style
  692. ghd.updateStyle();
  693. return false;
  694. });
  695.  
  696. $panel.find('input[type="text"]').on('focus', function() {
  697. // select all text when focused
  698. this.select();
  699. });
  700.  
  701. $panel.find('select, input').on('change', function() {
  702. if (!ghd.isUpdating) {
  703. ghd.updateStyle();
  704. }
  705. });
  706.  
  707. $panel.find('.ghd-update').on('click', function() {
  708. ghd.forceUpdate();
  709. return false;
  710. });
  711.  
  712. $panel.find('.ghd-textarea-toggle').on('click', function() {
  713. var $this = $(this),
  714. $dropdown = $this
  715. .removeClass('selected')
  716. .next('.paste-area-content')
  717. .toggle();
  718. if ($dropdown.is(':visible')) {
  719. $this.addClass('selected');
  720. $dropdown
  721. .find('textarea')
  722. .focus()
  723. .select();
  724. }
  725. return false;
  726. });
  727.  
  728. $panel.find('.paste-area-content').on('paste', function(e) {
  729. var $toggle = $panel.find('.ghd-textarea-toggle');
  730. var $textarea = $(e.target);
  731. setTimeout(function() {
  732. $textarea.parent().hide();
  733. $toggle.removeClass('selected');
  734. ghd.forceUpdate($textarea.val());
  735. }, 200);
  736. });
  737.  
  738. // **** CODE WRAP TOGGLE ****
  739. $('body').on('click', '.ghd-wrap-toggle', function() {
  740. var css,
  741. overallWrap = ghd.data.wrap,
  742. $this = $(this),
  743. $code = $this.next('code, pre, .highlight, .diff-table');
  744. if ($code.find('code').length) {
  745. $code = $code.find('code');
  746. }
  747. if (!$code.length) {
  748. debug('Code wrap icon associated code not found', $this);
  749. return;
  750. }
  751. // code with line numbers
  752. if ($code[0].nodeName === 'TABLE') {
  753. if ($code[0].className.indexOf('ghd-') < 0) {
  754. css = !overallWrap;
  755. } else {
  756. css = $code.hasClass('ghd-unwrap-table');
  757. }
  758. $code
  759. .toggleClass('ghd-wrap-table', css)
  760. .toggleClass('ghd-unwrap-table', !css);
  761. $this
  762. .toggleClass('wrapped', css)
  763. .toggleClass('unwrap', !css);
  764. } else {
  765. css = $code.attr('style') || '';
  766. if (css === '') {
  767. css = ghd.wrapCss[overallWrap ? 'unwrap' : 'wrapped'];
  768. } else {
  769. css = ghd.wrapCss[css === ghd.wrapCss.wrapped ? 'unwrap' : 'wrapped'];
  770. }
  771. $code.attr('style', css);
  772. $this
  773. .toggleClass('wrapped', css === ghd.wrapCss.wrapped)
  774. .toggleClass('unwrap', css === ghd.wrapCss.unwrap);
  775. }
  776. });
  777.  
  778. // **** MONOSPACE FONT TOGGLE ****
  779. $('body').on('click', '.ghd-monospace', function(e) {
  780. e.stopPropagation();
  781. var $this = $(this),
  782. $textarea = $this
  783. // each comment
  784. .closest('.previewable-comment-form')
  785. .find('.comment-form-textarea')
  786. .toggleClass('ghd-monospace-font')
  787. .focus();
  788. $this.toggleClass('ghd-icon-active', $textarea.hasClass('ghd-monospace-font'));
  789. return false;
  790. });
  791.  
  792. // **** CODE DIFF COLLAPSE TOGGLE ****
  793. $('body').on('click', '.ghd-file-toggle', function(e) {
  794. e.stopPropagation();
  795. ghd.updating = true;
  796. $(this)
  797. .toggleClass('ghd-file-collapsed')
  798. .closest('.file-header')
  799. // toggle view of file or image; "image" class added to "Diff suppressed..."
  800. .nextAll('.blob-wrapper, .render-wrapper, .image, .rich-diff')
  801. .toggleClass('ghd-collapsed-file');
  802. // shift+click toggle all files!
  803. if (e.shiftKey) {
  804. var indx,
  805. isCollapsed = $(this).hasClass('ghd-file-collapsed'),
  806. $toggles = $('.ghd-file-toggle').not(this),
  807. len = $toggles.length;
  808. for (indx = 0; indx < len; indx++) {
  809. $toggles.eq(indx)
  810. .toggleClass('ghd-file-collapsed', isCollapsed)
  811. .closest('.file-header')
  812. .next('.blob-wrapper, .render-wrapper, .image, .rich-diff')
  813. .toggleClass('ghd-collapsed-file', isCollapsed);
  814. }
  815. }
  816. ghd.updating = false;
  817. });
  818.  
  819. // style color picker
  820. this.picker = new jscolor($panel.find('.ghd-color')[0]);
  821. this.picker.zIndex = 65536;
  822. this.picker.hash = true;
  823. this.picker.backgroundColor = '#333';
  824. this.picker.padding = 0;
  825. this.picker.borderWidth = 0;
  826. this.picker.borderColor = '#444';
  827. this.picker.onFineChange = function() {
  828. $swatch[0].style.backgroundColor = '#' + ghd.picker;
  829. };
  830. },
  831.  
  832. openPanel : function() {
  833. $('.modal-backdrop').click();
  834. ghd.updatePanel();
  835. $('#ghd-settings').addClass('in');
  836. },
  837.  
  838. closePanel : function() {
  839. $('#ghd-settings').removeClass('in');
  840. ghd.picker.hide();
  841.  
  842. // apply changes when the panel is closed
  843. ghd.updateStyle();
  844. },
  845.  
  846. toggleStyle : function() {
  847. var isEnabled = !this.data.enable;
  848. this.data.enable = isEnabled;
  849. $('#ghd-settings-inner .ghd-enable').prop('checked', isEnabled);
  850. // add processedCss back into style (emptied when disabled)
  851. if (isEnabled) {
  852. this.addSavedStyle();
  853. }
  854. this.$style.prop('disabled', !isEnabled);
  855. },
  856.  
  857. init : function() {
  858. debug('GitHub-Dark Script initializing!');
  859.  
  860. // add style tag to head
  861. ghd.$style = $('<style class="ghd-style">').appendTo('head');
  862.  
  863. this.getStoredValues();
  864. this.$style.prop('disabled', !this.data.enable);
  865.  
  866. // only load package.json once a day, or after a forced update
  867. if ((new Date().getTime() > this.data.date + this.delay) || this.data.version === 0) {
  868. // get package.json from GitHub-Dark & compare versions
  869. // load new script if a newer one is available
  870. this.checkVersion();
  871. } else {
  872. this.addSavedStyle();
  873. }
  874. }
  875. };
  876.  
  877. // add style at document-start
  878. ghd.init();
  879.  
  880. $(function() {
  881. // add panel even if you're not logged in - open panel using keyboard shortcut
  882. ghd.buildSettings();
  883. // add event binding on document ready
  884. ghd.bindEvents();
  885.  
  886. var targets = document.querySelectorAll('#js-repo-pjax-container, #js-pjax-container, .js-contribution-activity');
  887.  
  888. Array.prototype.forEach.call(targets, function(target) {
  889. new MutationObserver(function(mutations) {
  890. mutations.forEach(function(mutation) {
  891. // preform checks before adding code wrap to minimize function calls
  892. if (!(ghd.isUpdating || document.querySelectorAll('.ghd-wrap-toggle').length) && mutation.target === target) {
  893. ghd.updateToggles();
  894. }
  895. });
  896. }).observe(target, {
  897. childList: true,
  898. subtree: true
  899. });
  900. });
  901. });
  902.  
  903. // include a "?debug" anywhere in the browser URL to enable debugging
  904. function debug() {
  905. if (/\?debug/.test(window.location.href)) {
  906. console.log.apply(console, arguments);
  907. }
  908. }
  909. })(jQuery.noConflict(true));