GitHub Label Color Picker

A userscript that adds a color picker to the label color input

  1. // ==UserScript==
  2. // @name GitHub Label Color Picker
  3. // @version 1.0.9
  4. // @description A userscript that adds a color picker to the label color input
  5. // @license MIT
  6. // @author Rob Garrison
  7. // @contributor darkred
  8. // @namespace https://github.com/Mottie
  9. // @match https://github.com/*
  10. // @run-at document-idle
  11. // @grant GM_addStyle
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_registerMenuCommand
  15. // @require https://greasyfork.org/scripts/23181-colorpicker/code/colorPicker.js?version=147862
  16. // @require https://greasyfork.org/scripts/398877-utils-js/code/utilsjs.js?version=1079637
  17. // @icon https://github.githubassets.com/pinned-octocat.svg
  18. // @supportURL https://github.com/Mottie/GitHub-userscripts/issues
  19. // ==/UserScript==
  20.  
  21. /* global jsColorPicker $ on */
  22. (() => {
  23. "use strict";
  24.  
  25. GM_addStyle(`
  26. div.cp-app { margin:100px 0 0 -7px; z-index:10; }
  27. .js-new-label-color-icon { pointer-events:none; }
  28. .js-new-label-color-icon.color-scale-black { color:#000 !important; }
  29. `);
  30.  
  31. function addPicker() {
  32. if ($(".js-new-label-color")) {
  33. jsColorPicker(".js-new-label-color-input", {
  34. customBG: "#222",
  35. noAlpha: true,
  36. renderCallback: function(colors) {
  37. const input = this && this.input;
  38. if (input) {
  39. updateSwatch(input, colors);
  40. }
  41. }
  42. });
  43. }
  44. }
  45.  
  46. function updateSwatch(input, colors) {
  47. input.value = colors.HEX;
  48. const colorStyle = calcStyle(colors.rgb, colors.hsl);
  49.  
  50. // Update color swatch next to input
  51. const inputSwatch = $(".js-new-label-color", input.closest("dd"));
  52. inputSwatch.style = colorStyle;
  53.  
  54. // Update label preview
  55. const labelSwatch = $(
  56. ".js-label-preview .IssueLabel--big",
  57. input.closest(".Box-row")
  58. );
  59. labelSwatch.style = colorStyle;
  60. }
  61.  
  62. function calcStyle(rgb, hsl) {
  63. // GitHub adds CSS variables to the wrapper
  64. // rgb is used as the foreground (text) color
  65. // hsl is used to calculate a color variant for the background
  66. const multiplier = { h: 360, s: 100, l: 100 };
  67. const fg = Object.entries(rgb).map(
  68. ([c, v]) => `--label-${c}:${(v * 255).toFixed(0)}`
  69. );
  70. const bg = Object.entries(hsl).map(
  71. ([c, v]) => `--label-${c}:${(v * multiplier[c]).toFixed(0)}`
  72. );
  73. // --label-r:255; --label-g:255; --label-b:255; --label-h:15; --label-s:0; --label-l:100;
  74. return `${fg.join("; ")}; ${bg.join("; ")}`;
  75. }
  76.  
  77. /* replace colorPicker storage */
  78. window.ColorPicker.docCookies = (key, val) => {
  79. if (typeof val === "undefined") {
  80. return GM_getValue(key);
  81. }
  82. GM_setValue(key, val);
  83. };
  84.  
  85. /* colorPickerMemosNoAlpha *MUST* follow this format
  86. "'rgba(83,25,231,1)','rgba(86,66,66,1)','rgba(22,20,223,1)'"
  87. */
  88. function convertColorsToRgba(values) {
  89. // see http://stackoverflow.com/a/26196012/145346
  90. return values
  91. .replace(/['"]/g, "")
  92. .split(/\s*,(?![^()]*(?:\([^()]*\))?\))\s*/g)
  93. .map(val => {
  94. const rgb = hexToRgb(val);
  95. if (rgb) {
  96. return `'rgba(${rgb.r},${rgb.g},${rgb.b},1)'`;
  97. } else if (rgb === null && val.indexOf("rgba(") > -1) {
  98. // allow adding rgba() definitions
  99. return`'${val}'`;
  100. }
  101. })
  102. .filter(Boolean)
  103. .join(",");
  104. }
  105.  
  106. // Modified code from http://stackoverflow.com/a/5624139/145346
  107. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  108. const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  109. function hexToRgb(hex) {
  110. const modHex = hex.replace(shorthandRegex, (_, r, g, b) => {
  111. return r + r + g + g + b + b;
  112. });
  113. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(modHex);
  114. return result ? {
  115. r: parseInt(result[1], 16),
  116. g: parseInt(result[2], 16),
  117. b: parseInt(result[3], 16)
  118. } : null;
  119. }
  120.  
  121. // Add GM options
  122. GM_registerMenuCommand(
  123. "Set label ColorPicker swatches (8 HEX or RGBA Max)",
  124. () => {
  125. const colors = GM_getValue("colorPickerMemosNoAlpha", "#000000,#ffffff"),
  126. val = prompt("Set label default colors (8 max):", colors);
  127. if (val !== null && typeof val === "string") {
  128. GM_setValue("colorPickerMemosNoAlpha", convertColorsToRgba(val));
  129. }
  130. }
  131. );
  132.  
  133. on(document.body, "click", event => {
  134. // initialize if "Edit" or "New label" button clicked
  135. // because "Save changes" updates the entire item
  136. if (
  137. event.target?.matches(".js-edit-label, .js-details-target")
  138. ) {
  139. addPicker();
  140. }
  141. });
  142. addPicker();
  143. })();