Greasy Fork is available in English.

GitHub Dark Script

GitHub Dark in userscript form, with a settings panel

Fra 03.10.2017. Se den seneste versjonen.

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