Skip to content

Instantly share code, notes, and snippets.

@tnodet
Last active February 23, 2026 09:46
Show Gist options
  • Select an option

  • Save tnodet/0f4d85ed251b6b977ddd8197f64488d2 to your computer and use it in GitHub Desktop.

Select an option

Save tnodet/0f4d85ed251b6b977ddd8197f64488d2 to your computer and use it in GitHub Desktop.
A simple Tampermonkey script to hide the annoying emoji / emoticon popup that appears in Jira when you type a colon (":")
// ==UserScript==
// @name Jira - Hide Emoji / Emoticon Typeahead Popup
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Hides the emoji / emoticon popup when typing a colon (":") in Jira
// @author Tanguy Nodet
// @match https://*.atlassian.net/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=jira.atlassian.com
// @grant none
// ==/UserScript==
(function () {
"use strict";
// -----------------------------------------------------------------------------------
// 1️⃣ Hide emoji popups
// -----------------------------------------------------------------------------------
const popupObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) continue;
// Only target editor popups
if (
node.tagName === "DIV" &&
node.getAttribute("data-editor-popup") === "true" &&
node.getAttribute("data-testid") === "popup-wrapper"
) {
// Check if any item in the popup has a data-emoji-id attribute
const hasEmojiItems = node.querySelector("[data-emoji-id]") !== null;
if (hasEmojiItems) {
// We hide the popup instead of removing it to avoid an error later on when
// the page tries to remove it itself.
node.style.display = "none";
}
}
}
}
});
popupObserver.observe(document.body, { childList: true, subtree: true });
// -----------------------------------------------------------------------------------
// 2️⃣ Neutralize <mark> node style (blue text)
// -----------------------------------------------------------------------------------
const style = document.createElement("style");
style.textContent = `
mark[data-type-ahead-query="true"][data-trigger=":"] {
color: inherit !important;
background: none !important;
}
`;
document.head.appendChild(style);
// -----------------------------------------------------------------------------------
// Helper: move the cursor to the end of a text node
// -----------------------------------------------------------------------------------
function moveCursorToEndOf(textNode) {
const selection = window.getSelection();
const range = document.createRange();
range.setStart(textNode, textNode.length);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
const ZWS = "\u200B"; // zero-width space character
// -----------------------------------------------------------------------------------
// 3️⃣ Bypass the typeahead logic by replacing the <mark> node
// -----------------------------------------------------------------------------------
// This is the most tricky part:
// - We can't just replace the <mark> node with a ":" text node, because Jira's
// typeahead logic will detect it and re-create the <mark> node, creating an
// infinite loop of mutations.
// - Instead, we replace the <mark> node with a ":" + ZWS text node. This way, the
// typeahead logic is somehow bypassed and doesn't re-create the <mark> node.
// This could work with other characters, but ZWS is a good candidate because it's
// invisible, so the user doesn't see it.
// - We can't cleanup the ZWS character immediately because the typeahead logic would
// re-create the <mark> node if it detects the ":" character alone, triggering the
// infinite loop. Instead, we wait for the next tick to cleanup the ZWS character.
// - The complete fix goes like this:
// 1. Detect <mark> node creation and replace it with ":" + ZWS text node
// 2. Move cursor after the ZWS character
// 3. In the next tick, cleanup the ZWS character from the text node
// 4. Move cursor at the end of the current text node
const markObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) continue;
if (
node.tagName === "MARK" &&
node.getAttribute("data-type-ahead-query") === "true" &&
node.getAttribute("data-trigger") === ":"
) {
const parent = node.parentNode;
if (!parent) return;
// Replace <mark> node with a TextNode containing colon + ZWS
const textNode = document.createTextNode(":" + ZWS);
node.replaceWith(textNode);
// Move cursor after ZWS
moveCursorToEndOf(textNode);
setTimeout(() => {
// Cleanup ZWS inside parent
const walker = document.createTreeWalker(
parent,
NodeFilter.SHOW_TEXT,
null,
false,
);
// Find the current text node that contains the ZWS and remove it
let current;
while ((current = walker.nextNode())) {
if (current.nodeValue.includes(ZWS)) {
current.nodeValue = current.nodeValue.replace(ZWS, "");
break; // only ever one ZWS
}
}
// Move cursor at end of current node
moveCursorToEndOf(current);
}, 0);
}
}
}
});
markObserver.observe(document.body, { childList: true, subtree: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment