Highlight Keywords with Scrollable Popup and Memory Storage (Immediate Highlight + Hotkey)

Highlight keywords on any site with scrollable popup, memory storage, Bootstrap design, and hotkey support

// ==UserScript==
// @name         Highlight Keywords with Scrollable Popup and Memory Storage (Immediate Highlight + Hotkey)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Highlight keywords on any site with scrollable popup, memory storage, Bootstrap design, and hotkey support
// @author       You
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @license      CC BY-NC-ND 4.0
// ==/UserScript==

(function () {
    "use strict";

    let keywords = JSON.parse(localStorage.getItem("highlightKeywords")) || [];

    function saveKeywords() {
        localStorage.setItem("highlightKeywords", JSON.stringify(keywords));
    }

    function highlightNewKeyword(keywordObj) {
        const { keyword, wholeWord, caseSensitive } = keywordObj;
        const regex = new RegExp(
        `${wholeWord ? "\\b" : ""}(${keyword})${wholeWord ? "\\b" : ""}`,
        caseSensitive ? "" : "gi"
        );

        function highlightText(node) {

            if (document.getElementById("keyword-popup")?.contains(node)) {
                return;
            }

            if (node.nodeType === Node.TEXT_NODE && regex.test(node.nodeValue)) {
                const span = document.createElement("span");
                span.innerHTML = node.nodeValue.replace(regex, "<mark>$1</mark>");
                node.replaceWith(span);
            } else if (
                node.nodeType === Node.ELEMENT_NODE &&
                node.nodeName !== "SCRIPT" &&
                node.nodeName !== "STYLE"
            ) {
                for (let i = 0; i < node.childNodes.length; i++) {
                highlightText(node.childNodes[i]);
                }
            }
        }
        highlightText(document.body);
    }

    function clearHighlights() {
        document.querySelectorAll("mark").forEach((mark) => {
        const parent = mark.parentNode;
        parent.replaceChild(document.createTextNode(mark.textContent), mark);
        parent.normalize();
        });
    }

    function highlightKeywords() {
        clearHighlights();
        if (keywords) {
        keywords.forEach((keywordObj) => highlightNewKeyword(keywordObj));
        }
    }

    function removeHighlight(keywordObj) {
        const regex = new RegExp(
        `^${keywordObj.keyword}$`,
        keywordObj.caseSensitive ? "" : "i"
        );
        const highlightedElements = document.querySelectorAll("mark");

        highlightedElements.forEach((el) => {
        if (regex.test(el.textContent)) {
            const textNode = document.createTextNode(el.textContent);
            el.replaceWith(textNode);
        }
        });
    }

    window.addEventListener("load", highlightKeywords);

    function addCustomStyles() {
        const style = document.createElement("style");
        style.innerHTML = `
                #keyword-popup {
                    color: black;
                    display: none;
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    z-index: 10000;
                    background: white;
                    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
                    border-radius: 8px;
                    width: 50rem;
                    max-height: 80%;
                    overflow-y: auto;
                    padding: 20px;
                    font-family: Arial, sans-serif;
                    overflow-x: hidden;
                }
                #keywordContainer {
                    padding-right: 0.5rem;
                    scrollbar-width: thin;
                    scrollbar-color: #007bff #f1f1f1;
                    --width: 100%;
                    --height: 20rem;
                    min-width: var(--width);
                    max-width: var(--width);
                    min-height: var(--height);
                    max-height: var(--height);
                    overflow-y: auto;
                }
                .modal-header-123 {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }
                .modal-title-123 {
                    margin: 0;
                    color: black;
                    font-size: 1.5em;
                }
                .btn-close-123 {
                    cursor: pointer;
                    background: none;
                    border: none;
                    font-size: 1.2em;
                }
                .list-group-123-item {
                    position: relative;
                    display: block;
                    padding: 0.5rem 1rem;
                    color: #212529;
                    text-decoration: none;
                    background-color: #fff;
                    border: 1px solid #dee2e6;
                    overflow: hidden;
                }
                .list-group-123-item:first-child {
                    border-top-left-radius: inherit;
                    border-top-right-radius: inherit;
                }
                .list-group-123-item:last-child {
                    border-bottom-right-radius: inherit;
                    border-bottom-left-radius: inherit;
                }
                .list-group-123-item:disabled {
                    color: rgba(33, 37, 41, 0.75);
                    pointer-events: none;
                    background-color: #fff;
                }
                .btn-danger-123 {
                    background-color: #ff4d4d;
                    border: none;
                    color: white;
                    padding: 5px 10px;
                    border-radius: 5px;
                    cursor: pointer;
                }
                .btn-danger-123:hover {
                    background-color: #e60000;
                }
                .form-control-123 {
                    width: 97%;
                    padding: 10px;
                    margin-top: 10px;
                    border: 1px solid #ddd;
                    border-radius: 5px;
                    background-color: white;
                }
                .modal-footer-123 {
                    display: flex;
                    justify-content: flex-end;
                    margin-top: 20px;
                }
                .btn-123 {
                    margin-left: 10px;
                    padding: 10px 15px;
                    border-radius: 5px;
                    cursor: pointer;
                }
                .btn-secondary-123 {
                    background-color: #6c757d;
                    color: white;
                    border: none;
                }
                .btn-secondary-123:hover {
                    background-color: #5a6268;
                }
                .btn-primary-123 {
                    background-color: #007bff;
                    color: white;
                    border: none;
                }
                .btn-primary-123:hover {
                    background-color: #0056b3;
                }
                .toggle-indicator_123 {
                    display: inline-block;
                    min-width: 2rem;
                    max-width: 2rem;
                    min-height: 2rem;
                    max-height: 2rem;
                    border-radius: 50%;
                    margin-right: 5px;
                }
                .toggle-indicator_123.false {
                    background-color: red;
                }
                .toggle-indicator_123.true {
                    background-color: green;
                }

                .d-flex_123 {
                    display: flex !important;
                    }
                .justify-content-between_123 {
                    justify-content: space-between !important;
                }

                .align-items-center_123 {
                    align-items: center !important;
                }
                .popupContent123 {
                    flex-grow: 1;
                }
                .toast_123 {
                    position: fixed;
                    bottom: 20px;
                    left: 50%;
                    transform: translateX(-50%);
                    padding: 15px;
                    background-color: #333;
                    color: white;
                    border-radius: 5px;
                    opacity: 0.5;
                    transition: opacity 0.5s ease;
                    z-index: 10001;
                    }

                .toast_123.hidden {
                    opacity: 0;
                    transition: opacity 0.5s ease, visibility 0s 4s;
                    visibility: hidden;
                }

        `;
        document.head.appendChild(style);
    }

    function setupTurnOffToggle() {
        const isTurnedOff = localStorage.getItem("highlightScriptOff") === "true";

        const turnOffToggle = document.getElementById("turnOffToggle");
        if (turnOffToggle) {
            turnOffToggle.checked = isTurnedOff;

            turnOffToggle.addEventListener("change", (e) => {
                localStorage.setItem("highlightScriptOff", e.target.checked);
                location.reload();
            });
        }
    }

    function createPopup() {
        const popup = document.createElement("div");
        popup.id = "keyword-popup";
        popup.classList.add("modal", "fade");
        popup.style.display = "none";

        popup.innerHTML = `
                <div class="modal-dialog-123">
                    <div class="modal-content-123">
                        <div class="modal-header-123">
                            <h5 class="modal-title-123">Manage Keywords</h5>
                            <button type="button" class="btn-close-123" id="closePopup">X</button>
                        </div>
                        <div class="modal-body-123">
                            <div id="keywordContainer" class="list-group-123 mb-2-123"></div>
                            <input type="text" id="newKeyword" class="form-control-123 mt-2-123" placeholder="Enter new keyword">
                            <label class="asasdasda">
                                <input type="checkbox" id="wholeWordToggle">
                                Whole Word
                            </label>
                            <label class="asasdasda">
                                <input type="checkbox" id="caseSensitiveToggle">
                                Case Sensitive
                            </label>
                            <label>
                                <input type="checkbox" id="turnOffToggle"> Turn Off at this page
                            </label>
                        </div>
                        <div class="modal-footer-123">
                            <button type="button" class="btn-123 btn-secondary-123" id="closeButton">Close</button>
                            <button type="button" class="btn-123 btn-primary-123" id="addKeywordButton">Add Keyword</button>
                        </div>
                    </div>
                </div>
            `;

        document.body.appendChild(popup);
        renderKeywordList();

        document.getElementById("closeButton").onclick = hidePopup;
        document.getElementById("closePopup").onclick = hidePopup;

        document.getElementById("addKeywordButton").onclick = () => {
            const newKeyword = document.getElementById("newKeyword").value.trim();
            const wholeWord = document.getElementById("wholeWordToggle").checked;
            const caseSensitive = document.getElementById(
                "caseSensitiveToggle"
            ).checked;

            if (newKeyword) {
                const keywordObj = { keyword: newKeyword, wholeWord, caseSensitive };
                keywords.push(keywordObj);
                saveKeywords();
                highlightNewKeyword(keywordObj);
                renderKeywordList();
                document.getElementById("newKeyword").value = "";
                document.getElementById("wholeWordToggle").checked = false;
                document.getElementById("caseSensitiveToggle").checked = false;
            }
        };

        setupTurnOffToggle();
    }

    function showToast() {
        const toast = document.createElement("div");
        toast.classList.add("toast_123");
        toast.textContent = "Press [CTRL + A] to open Search Popup";
        document.body.appendChild(toast);

        setTimeout(() => {
        toast.classList.add("hidden");
        }, 4000);
    }

    function renderKeywordList() {
        const keywordContainer = document.getElementById("keywordContainer");
        keywordContainer.innerHTML = "";

        keywords.forEach((keywordObj, index) => {
            const keywordText =
                keywordObj.keyword.length > 20
                    ? keywordObj.keyword.slice(0, 20) + "..."
                    : keywordObj.keyword;

            const matchCount = countKeywordMatches(keywordObj);

            const keywordItem = document.createElement("div");
            keywordItem.classList.add(
                "list-group-123-item",
                "d-flex_123",
                "justify-content-between_123",
                "align-items-center_123"
            );

            keywordItem.innerHTML = `
                <span class="toggle-indicator_123 ${keywordObj.wholeWord}" title="${keywordObj.wholeWord ? "Whole Word: ON" : "Whole Word: OFF"}"></span>
                <span class="toggle-indicator_123 ${keywordObj.caseSensitive}" title="${keywordObj.caseSensitive ? "Case Sensitive: ON" : "Case Sensitive: OFF"}"></span>
                <span class="popupContent123">${keywordText}</span>
                <button class="btn-123 btn-danger-123 btn-sm-123" data-index="${index}">Remove</button>
            `;

            keywordItem.querySelector(".toggle-indicator_123:nth-child(1)").onclick = function () {
                keywordObj.wholeWord = !keywordObj.wholeWord;
                saveKeywords();
                renderKeywordList();
                highlightKeywords();
            };

            keywordItem.querySelector(".toggle-indicator_123:nth-child(2)").onclick = function () {
                keywordObj.caseSensitive = !keywordObj.caseSensitive;
                saveKeywords();
                renderKeywordList();
                highlightKeywords();
            };

            keywordItem.querySelector("button").onclick = function () {
                removeHighlight(keywordObj);
                keywords.splice(index, 1);
                saveKeywords();
                renderKeywordList();
                highlightKeywords();
            };

            keywordContainer.appendChild(keywordItem);
        });
        highlightKeywords();
    }

    function countKeywordMatches(keywordObj) {
        const { keyword, wholeWord, caseSensitive } = keywordObj;
        const regex = new RegExp(
            `${wholeWord ? "\\b" : ""}(${keyword})${wholeWord ? "\\b" : ""}`,
            caseSensitive ? "g" : "gi"
        );
        const text = document.body.innerText;
        const matches = text.match(regex);
        return matches ? matches.length : 0;
    }

    function updateToggleIndicator(element, state) {
        element.className = `toggle-indicator_123 ${state}`;
        element.title = `${element.className.includes("true") ? "ON" : "OFF"}`;
    }

    function setInitialToggleStates() {
        keywords.forEach((keywordObj, index) => {
        const wholeWordToggle = document.querySelector(
            `.toggle-indicator_123:nth-child(1)`
        );
        const caseSensitiveToggle = document.querySelector(
            `.toggle-indicator_123:nth-child(2)`
        );
        updateToggleIndicator(wholeWordToggle, keywordObj.wholeWord);
        updateToggleIndicator(caseSensitiveToggle, keywordObj.caseSensitive);
        });
    }

    function showPopup() {
        const popup = document.getElementById("keyword-popup");
        if (popup) {
        popup.style.display = "block";
        } else {
        console.error("Popup element not found");
        }
    }

    function hidePopup() {
        const popup = document.getElementById("keyword-popup");
        if (popup) {
        popup.style.display = "none";
        } else {
        console.error("Popup element not found");
        }
    }

    function registerHotkey() {
        document.addEventListener("keydown", function (event) {
            const isTurnedOff = localStorage.getItem("highlightScriptOff") === "true";

            if (event.ctrlKey && event.key === "f") {
                if (isTurnedOff) {
                    showToast();
                    return;
                }
                event.preventDefault();
                showPopup();
            } else if (event.altKey && event.key === "a") {
                if (isTurnedOff) {
                    showPopup();
                }
            } else if (event.key === "Escape") {
                hidePopup();
            }
        });
    }

    addCustomStyles();
    createPopup();
    registerHotkey();
})();