// ==UserScript==
// @name Emoji & Symbol Picker
// @namespace https://greasyfork.org/en/users/85671-jcunews
// @version 1.1.7
// @author jcunews
// @license GNU AGPLv3
// @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.
// @match *://*/*
// @include *:*
// @grant none
// ==/UserScript==
((eleHdr, eleFontSize, eleAll, prevEleDown, eleDown, dragBaseX, dragBaseY, dragStartX, dragStartY, eleFocus, a, b) => {
//=== CONFIGURATION BEGIN ===
//hotkey setting to show/hide picker panel
var hotkeyCtrl = false;
var hotkeyShift = false;
var hotkeyAlt = true;
var hotkeyKey = "`";
//=== CONFIGURATION END ===
var blocks = [
"Emoticons (1F600-1F64F)",
"Supplemental Symbols and Pictographs (1F900-1F9FF)\u{1f985}",
"Miscellaneous Symbols and Pictographs (1F300-1F5FF)",
"Transport and Map Symbols (1F680-1F6FF)",
"Miscellaneous Symbols (2600-26FF)",
"Dingbats (2700-27BF)",
"Arrows (2190-21FF)",
"Miscellaneous Symbols and Arrows (2B00-2BFF)",
"Supplemental Arrows-C (1F800-1F8FF)",
"Miscellaneous Technical (2300-23FF)",
"Geometric Shapes (25A0-25FF)\u25a4",
"Geometric Shapes Extended (1F780-1F7FF)\u{1f78a}",
"Block Elements (2580-259F)\u258a",
"Box Drawing (2500-257F)\u2554"
];
var fontSizes = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72, 96, 120, 144, 176, 208, 240];
function isEditable(e) {
while (e) {
if (e.contentEditable === "true") return true;
e = e.parentNode;
}
return false;
}
function panelMove(e) {
panel.style.left = dragBaseX + (e.screenX - dragStartX) + "px";
panel.style.top = dragBaseY + (e.screenY - dragStartY) + "px";
};
function panelResize(e) {
panel.style.width = dragBaseX + (e.screenX - dragStartX) + "px";
panel.style.height = dragBaseY + (e.screenY - dragStartY) + "px";
};
function selectBlock() {
panel.querySelector(".ekResize~.ekButton.selected").classList.remove("selected");
this.classList.add("selected");
panel.querySelector(".ekBlock.selected").classList.remove("selected");
panel.querySelector(`.ekBlock[index="${this.getAttribute("index")}"]`).classList.add("selected");
panel.querySelector(".ekBody").scrollTo(0, 0);
}
if (document.contentType === "application/xml") return;
var to = {createHTML: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to, html = s => tp.createHTML(s);
var panel = document.createElement("DIV");
panel.id = "ekPanel";
panel.innerHTML = html(`<style>
#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}
#ekPanel *{font:inherit}
.ekContent{display:flex;flex-direction:column;position:relative;width:100%;height:100%}
.ekHeader{flex-shrink:1;margin:0;padding:0 0 .15em .5ex;background:#000;color:#fff;cursor:move}
.ekButton{display:inline-block;vertical-align:top;width:1.5em;height:1.5em;text-align:center;cursor:pointer}
#ekPanel .ekClose{float:right;border-radius:4px;background:#f00;font-size:84%}
.ekBody{display:block;flex-shrink:1;height:100%;overflow-y:scroll}
.ekBlock{display:none}
.ekBlock.selected{display:block}
.ekBlock .ekButton{margin:0 .3ex .3ex 0}
.ekBlock .ekButton:hover{background:#ddf}
.ekFooter{display:block;flex-shrink:1;border-top:1px solid #000}
.ekResize{position:absolute;right:0;bottom:0;border-top:1em solid #fff;border-right:1em solid #bbb;width:auto;height:auto;cursor:se-resize}
.ekResize~.ekButton{margin-right:.3ex;border-radius:4px}
.ekResize~.ekButton.selected{background:#bdb}
.ekResize~.ekButton:hover{background:#ddf}
.ekResize~.ekButton:last-child{margin-right:1.5em}
#ekPanel .ekFontSize{margin-left:2ex;font-size:84%}
</style>
<div class="ekContent">
<h4 class="ekHeader"><div class="ekClose ekButton">✖</div>Emoji & Symbol Picker</h4>
<div class="ekBody"></div>
<div class="ekFooter">
<div class="ekResize ekButton"></div>
<select class="ekFontSize"></select>
</div>
</div>`);
panel.addEventListener("mousedown", e => {
if (document.activeElement && (document.activeElement !== eleFontSize)) eleFocus = document.activeElement;
prevEleDown = eleDown;
eleDown = e.target;
if (((prevEleDown === eleFontSize) && (eleDown === eleFontSize)) || !eleDown || !["SELECT", "OPTION"].includes(eleDown.tagName)) {
eleDown = null;
e.preventDefault();
eleFocus && eleFocus.focus();
}
});
(eleHdr = panel.querySelector(".ekHeader")).onmousedown = e => {
if ((e.buttons !== 1) || (e.target !== eleHdr)) return;
e.preventDefault();
dragBaseX = panel.offsetLeft;
dragBaseY = panel.offsetTop;
dragStartX = e.screenX;
dragStartY = e.screenY;
addEventListener("mousemove", panelMove);
panel.onmouseup = function(e) {
removeEventListener("mousemove", panelMove);
panel.onmouseup = null;
};
};
panel.querySelector(".ekClose").onclick = () => {
panel.remove();
eleFocus && eleFocus.focus();
eleFocus = null;
};
(a = panel.querySelector(".ekBody")).addEventListener("click", (e,c,s,d,f) => {
if (((e = e.target).className !== "ekButton") || !eleFocus) return;
if ((eleFocus.tagName === "TEXTAREA") || ((eleFocus.tagName === "INPUT") && (eleFocus.type === "text"))) {
a = eleFocus.selectionStart;
b = eleFocus.selectionEnd;
if (a > b) {
c = a; a = b; b = c;
}
eleFocus.value = eleFocus.value.slice(0, a) + e.textContent + eleFocus.value.slice(b);
eleFocus.selectionStart = eleFocus.selectionEnd = a + e.textContent.length;
} else if (isEditable(eleFocus) && (s = getSelection())) {
eleFocus && eleFocus.focus();
if (("InstallTrigger" in window) && (s.rangeCount > 1)) {
//Firefox can't mix text and element nodes in a selection. So if there's multiple seletion ranges, merge them into one range.
a = s.getRangeAt(s.rangeCount - 1);
b = a.endContainer;
c = a.endOffset;
s.collapseToStart();
a = s.getRangeAt(0);
a.setEnd(b, c);
}
s.deleteFromDocument();
a = s.getRangeAt(0);
b = a.startContainer;
c = a.startOffset;
if (b.nodeType === Node.ELEMENT_NODE) {
b.insertBefore(document.createTextNode(e.textContent), b.childNodes[c]);
} else b.data = b.data.slice(0, c) + e.textContent + b.data.slice(c);
a.setStart(b, c + e.textContent.length);
a.setEnd(b, c + e.textContent.length);
}
});
b = panel.querySelector(".ekFooter");
blocks.forEach((n,i,e,d,s,j,h) => {
b.insertBefore(e = document.createElement("DIV"), b.lastElementChild);
j = n.lastIndexOf(")");
if (j < (n.length - 1)) {
s = n.codePointAt(j + 1);
n = n.slice(0, j + 1);
}
e.title = n;
d = n.match(/\((.*)-(.*)\)/);
d.shift();
d = d.map(v => parseInt(v, 16));
e.textContent = String.fromCodePoint(s || d[0]);
e.className = "ekButton" + (i ? "" : " selected");
e.setAttribute("index", i);
e.onclick = selectBlock;
e = document.createElement("DIV");
e.className = "ekBlock" + (i ? "" : " selected");
e.setAttribute("index", i);
s = "";
for (j = d[0]; j <= d[1]; j++) {
h = j.toString(16).toUpperCase();
s += `<div class="ekButton" title="Character code ${j} (0x${("0000000" + h).slice(j > 0xffffff ? 0 : j > 0xffff ? -6 : -4)})">&#x${h};</div>`;
}
e.innerHTML = html(s);
a.appendChild(e);
});
(eleAll = Array.from(panel.querySelectorAll("*"))).unshift(panel);
eleFontSize = panel.querySelector(".ekFontSize");
fontSizes.forEach(v => {
eleFontSize.appendChild(b = document.createElement("OPTION"));
b.textContent = v + "pt";
(v === 12) && (b.selected = true);
});
eleFontSize.onchange = () => {
panel.querySelector(".ekBody").style.fontSize = eleFontSize.value;
eleFocus && eleFocus.focus();
};
panel.querySelector(".ekResize").onmousedown = e => {
if (e.buttons !== 1) return;
e.preventDefault();
dragBaseX = panel.offsetWidth;
dragBaseY = panel.offsetHeight;
dragStartX = e.screenX;
dragStartY = e.screenY;
addEventListener("mousemove", panelResize);
panel.onmouseup = function(e) {
removeEventListener("mousemove", panelResize);
panel.onmouseup = null;
};
};
addEventListener("keydown", e => {
if ((e.ctrlKey !== hotkeyCtrl) || (e.shiftKey !== hotkeyShift) || (e.altKey !== hotkeyAlt) || (e.key.toUpperCase() !== hotkeyKey.toUpperCase())) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (panel.parentNode) {
panel.remove();
eleFocus && eleFocus.focus();
} else {
eleFocus = document.activeElement;
eleDown = null;
document.body.appendChild(panel);
}
}, true);
})();