Emoji & Symbol Picker

Adds ability to input emoji and symbol characters via picker popup which is accessible using ALT+` (ALT and backquote) keyboard shortcut (configurable in the script). Character will be generated at the (blinking) cursor. If there's text selected, the character will replace the selection. Note: this script will not work on inputboxes whose keyboard inputs is script driven. e.g. WYSIWYG text boxes.

  1. // ==UserScript==
  2. // @name Emoji & Symbol Picker
  3. // @namespace https://greasyfork.org/en/users/85671-jcunews
  4. // @version 1.1.7
  5. // @author jcunews
  6. // @license GNU AGPLv3
  7. // @description Adds ability to input emoji and symbol characters via picker popup which is accessible using ALT+` (ALT and backquote) keyboard shortcut (configurable in the script). Character will be generated at the (blinking) cursor. If there's text selected, the character will replace the selection. Note: this script will not work on inputboxes whose keyboard inputs is script driven. e.g. WYSIWYG text boxes.
  8. // @match *://*/*
  9. // @include *:*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. ((eleHdr, eleFontSize, eleAll, prevEleDown, eleDown, dragBaseX, dragBaseY, dragStartX, dragStartY, eleFocus, a, b) => {
  14.  
  15. //=== CONFIGURATION BEGIN ===
  16.  
  17. //hotkey setting to show/hide picker panel
  18. var hotkeyCtrl = false;
  19. var hotkeyShift = false;
  20. var hotkeyAlt = true;
  21. var hotkeyKey = "`";
  22.  
  23. //=== CONFIGURATION END ===
  24.  
  25. var blocks = [
  26. "Emoticons (1F600-1F64F)",
  27. "Supplemental Symbols and Pictographs (1F900-1F9FF)\u{1f985}",
  28. "Miscellaneous Symbols and Pictographs (1F300-1F5FF)",
  29. "Transport and Map Symbols (1F680-1F6FF)",
  30. "Miscellaneous Symbols (2600-26FF)",
  31. "Dingbats (2700-27BF)",
  32. "Arrows (2190-21FF)",
  33. "Miscellaneous Symbols and Arrows (2B00-2BFF)",
  34. "Supplemental Arrows-C (1F800-1F8FF)",
  35. "Miscellaneous Technical (2300-23FF)",
  36. "Geometric Shapes (25A0-25FF)\u25a4",
  37. "Geometric Shapes Extended (1F780-1F7FF)\u{1f78a}",
  38. "Block Elements (2580-259F)\u258a",
  39. "Box Drawing (2500-257F)\u2554"
  40. ];
  41. var fontSizes = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72, 96, 120, 144, 176, 208, 240];
  42.  
  43. function isEditable(e) {
  44. while (e) {
  45. if (e.contentEditable === "true") return true;
  46. e = e.parentNode;
  47. }
  48. return false;
  49. }
  50.  
  51. function panelMove(e) {
  52. panel.style.left = dragBaseX + (e.screenX - dragStartX) + "px";
  53. panel.style.top = dragBaseY + (e.screenY - dragStartY) + "px";
  54. };
  55.  
  56. function panelResize(e) {
  57. panel.style.width = dragBaseX + (e.screenX - dragStartX) + "px";
  58. panel.style.height = dragBaseY + (e.screenY - dragStartY) + "px";
  59. };
  60.  
  61. function selectBlock() {
  62. panel.querySelector(".ekResize~.ekButton.selected").classList.remove("selected");
  63. this.classList.add("selected");
  64. panel.querySelector(".ekBlock.selected").classList.remove("selected");
  65. panel.querySelector(`.ekBlock[index="${this.getAttribute("index")}"]`).classList.add("selected");
  66. panel.querySelector(".ekBody").scrollTo(0, 0);
  67. }
  68.  
  69. if (document.contentType === "application/xml") return;
  70. var to = {createHTML: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to, html = s => tp.createHTML(s);
  71. var panel = document.createElement("DIV");
  72. panel.id = "ekPanel";
  73. panel.innerHTML = html(`<style>
  74. #ekPanel{display:block;position:fixed;z-index:999999999;left:33vw;top:33vh;box-shadow:0 0 10px 0;border:2px solid #000;border-radius:6px;box-sizing:border-box;min-width:58ex;width:58ex;max-width:99vw;min-height:6.8em;max-height:99vh;height:12em;background:#fff;font:normal normal normal 12pt/normal sans-serif}
  75. #ekPanel *{font:inherit}
  76. .ekContent{display:flex;flex-direction:column;position:relative;width:100%;height:100%}
  77. .ekHeader{flex-shrink:1;margin:0;padding:0 0 .15em .5ex;background:#000;color:#fff;cursor:move}
  78. .ekButton{display:inline-block;vertical-align:top;width:1.5em;height:1.5em;text-align:center;cursor:pointer}
  79. #ekPanel .ekClose{float:right;border-radius:4px;background:#f00;font-size:84%}
  80. .ekBody{display:block;flex-shrink:1;height:100%;overflow-y:scroll}
  81. .ekBlock{display:none}
  82. .ekBlock.selected{display:block}
  83. .ekBlock .ekButton{margin:0 .3ex .3ex 0}
  84. .ekBlock .ekButton:hover{background:#ddf}
  85. .ekFooter{display:block;flex-shrink:1;border-top:1px solid #000}
  86. .ekResize{position:absolute;right:0;bottom:0;border-top:1em solid #fff;border-right:1em solid #bbb;width:auto;height:auto;cursor:se-resize}
  87. .ekResize~.ekButton{margin-right:.3ex;border-radius:4px}
  88. .ekResize~.ekButton.selected{background:#bdb}
  89. .ekResize~.ekButton:hover{background:#ddf}
  90. .ekResize~.ekButton:last-child{margin-right:1.5em}
  91. #ekPanel .ekFontSize{margin-left:2ex;font-size:84%}
  92. </style>
  93. <div class="ekContent">
  94. <h4 class="ekHeader"><div class="ekClose ekButton">&#x2716;</div>Emoji &amp; Symbol Picker</h4>
  95. <div class="ekBody"></div>
  96. <div class="ekFooter">
  97. <div class="ekResize ekButton"></div>
  98. <select class="ekFontSize"></select>
  99. </div>
  100. </div>`);
  101. panel.addEventListener("mousedown", e => {
  102. if (document.activeElement && (document.activeElement !== eleFontSize)) eleFocus = document.activeElement;
  103. prevEleDown = eleDown;
  104. eleDown = e.target;
  105. if (((prevEleDown === eleFontSize) && (eleDown === eleFontSize)) || !eleDown || !["SELECT", "OPTION"].includes(eleDown.tagName)) {
  106. eleDown = null;
  107. e.preventDefault();
  108. eleFocus && eleFocus.focus();
  109. }
  110. });
  111. (eleHdr = panel.querySelector(".ekHeader")).onmousedown = e => {
  112. if ((e.buttons !== 1) || (e.target !== eleHdr)) return;
  113. e.preventDefault();
  114. dragBaseX = panel.offsetLeft;
  115. dragBaseY = panel.offsetTop;
  116. dragStartX = e.screenX;
  117. dragStartY = e.screenY;
  118. addEventListener("mousemove", panelMove);
  119. panel.onmouseup = function(e) {
  120. removeEventListener("mousemove", panelMove);
  121. panel.onmouseup = null;
  122. };
  123. };
  124. panel.querySelector(".ekClose").onclick = () => {
  125. panel.remove();
  126. eleFocus && eleFocus.focus();
  127. eleFocus = null;
  128. };
  129. (a = panel.querySelector(".ekBody")).addEventListener("click", (e,c,s,d,f) => {
  130. if (((e = e.target).className !== "ekButton") || !eleFocus) return;
  131. if ((eleFocus.tagName === "TEXTAREA") || ((eleFocus.tagName === "INPUT") && (eleFocus.type === "text"))) {
  132. a = eleFocus.selectionStart;
  133. b = eleFocus.selectionEnd;
  134. if (a > b) {
  135. c = a; a = b; b = c;
  136. }
  137. eleFocus.value = eleFocus.value.slice(0, a) + e.textContent + eleFocus.value.slice(b);
  138. eleFocus.selectionStart = eleFocus.selectionEnd = a + e.textContent.length;
  139. } else if (isEditable(eleFocus) && (s = getSelection())) {
  140. eleFocus && eleFocus.focus();
  141. if (("InstallTrigger" in window) && (s.rangeCount > 1)) {
  142. //Firefox can't mix text and element nodes in a selection. So if there's multiple seletion ranges, merge them into one range.
  143. a = s.getRangeAt(s.rangeCount - 1);
  144. b = a.endContainer;
  145. c = a.endOffset;
  146. s.collapseToStart();
  147. a = s.getRangeAt(0);
  148. a.setEnd(b, c);
  149. }
  150. s.deleteFromDocument();
  151. a = s.getRangeAt(0);
  152. b = a.startContainer;
  153. c = a.startOffset;
  154. if (b.nodeType === Node.ELEMENT_NODE) {
  155. b.insertBefore(document.createTextNode(e.textContent), b.childNodes[c]);
  156. } else b.data = b.data.slice(0, c) + e.textContent + b.data.slice(c);
  157. a.setStart(b, c + e.textContent.length);
  158. a.setEnd(b, c + e.textContent.length);
  159. }
  160. });
  161. b = panel.querySelector(".ekFooter");
  162. blocks.forEach((n,i,e,d,s,j,h) => {
  163. b.insertBefore(e = document.createElement("DIV"), b.lastElementChild);
  164. j = n.lastIndexOf(")");
  165. if (j < (n.length - 1)) {
  166. s = n.codePointAt(j + 1);
  167. n = n.slice(0, j + 1);
  168. }
  169. e.title = n;
  170. d = n.match(/\((.*)-(.*)\)/);
  171. d.shift();
  172. d = d.map(v => parseInt(v, 16));
  173. e.textContent = String.fromCodePoint(s || d[0]);
  174. e.className = "ekButton" + (i ? "" : " selected");
  175. e.setAttribute("index", i);
  176. e.onclick = selectBlock;
  177. e = document.createElement("DIV");
  178. e.className = "ekBlock" + (i ? "" : " selected");
  179. e.setAttribute("index", i);
  180. s = "";
  181. for (j = d[0]; j <= d[1]; j++) {
  182. h = j.toString(16).toUpperCase();
  183. s += `<div class="ekButton" title="Character code ${j} (0x${("0000000" + h).slice(j > 0xffffff ? 0 : j > 0xffff ? -6 : -4)})">&#x${h};</div>`;
  184. }
  185. e.innerHTML = html(s);
  186. a.appendChild(e);
  187. });
  188. (eleAll = Array.from(panel.querySelectorAll("*"))).unshift(panel);
  189. eleFontSize = panel.querySelector(".ekFontSize");
  190. fontSizes.forEach(v => {
  191. eleFontSize.appendChild(b = document.createElement("OPTION"));
  192. b.textContent = v + "pt";
  193. (v === 12) && (b.selected = true);
  194. });
  195. eleFontSize.onchange = () => {
  196. panel.querySelector(".ekBody").style.fontSize = eleFontSize.value;
  197. eleFocus && eleFocus.focus();
  198. };
  199. panel.querySelector(".ekResize").onmousedown = e => {
  200. if (e.buttons !== 1) return;
  201. e.preventDefault();
  202. dragBaseX = panel.offsetWidth;
  203. dragBaseY = panel.offsetHeight;
  204. dragStartX = e.screenX;
  205. dragStartY = e.screenY;
  206. addEventListener("mousemove", panelResize);
  207. panel.onmouseup = function(e) {
  208. removeEventListener("mousemove", panelResize);
  209. panel.onmouseup = null;
  210. };
  211. };
  212.  
  213. addEventListener("keydown", e => {
  214. if ((e.ctrlKey !== hotkeyCtrl) || (e.shiftKey !== hotkeyShift) || (e.altKey !== hotkeyAlt) || (e.key.toUpperCase() !== hotkeyKey.toUpperCase())) return;
  215. e.preventDefault();
  216. e.stopPropagation();
  217. e.stopImmediatePropagation();
  218. if (panel.parentNode) {
  219. panel.remove();
  220. eleFocus && eleFocus.focus();
  221. } else {
  222. eleFocus = document.activeElement;
  223. eleDown = null;
  224. document.body.appendChild(panel);
  225. }
  226. }, true);
  227.  
  228. })();