Custom search and highlighting aka (Ctrl+F) 2025-10-31.1.33

search and highlight tool

Version au 01/11/2025. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name           Custom search and highlighting aka (Ctrl+F)  2025-10-31.1.33
// @namespace      http://tampermonkey.net/
// @version        2025-10-31.1.33
// @description    search and highlight  tool
// @author         kr6r5kugkkgk
// @match          *://*/*
// @license        MIT
// @run-at         document-idle
// @icon           https://i.pinimg.com/originals/79/09/89/7909897ceea2691e5a4942766c678ff3.png
// ==/UserScript==


(function() {
    'use strict';
    // Переменные для функционала
    let matches = [];
    let currentIndex = -1;
    // Переменные для настроек подсветки (по умолчанию)
    let settings = {
        highlightDefaultBg: 'rgb(52, 22, 86)',
        highlightCurrentBg: 'rgb(179, 117, 238)',
        highlightTextColor: 'rgb(232, 248, 101)',
        highlightBorderColor: 'rgb(139, 248, 194)',
        highlightBorderRadius: 12,
        highlightBorderThickness: 2,
        highlightDefaultOpacity: 0.8,  // Новое: прозрачность для обычного фона (0-1)
        highlightCurrentOpacity: 1.0,  // Новое: прозрачность для текущего фона (0-1)
        highlightPadding: 5 , 
        lang: 'ru'
 // Новое: padding в px для mark
    };
    const translations = {
    ru: {
        searchPlaceholder: 'Поиск по странице...',
        settingsTitle: 'Настройки подсветки',
        defaultBg: 'Цвет фона (обычный):',
        defaultOpacity: 'Прозрачность обычного фона (0-1):',
        currentBg: 'Цвет фона (текущий):',
        currentOpacity: 'Прозрачность текущего фона (0-1):',
        textColor: 'Цвет текста:',
        borderColor: 'Цвет бордера:',
        borderRadius: 'Скругление (px):',
        borderThickness: 'Толщина бордера (px):',
        padding: 'Padding (px):',
        save: 'Сохранить',
        cancel: 'Отмена',
        lang: 'Язык интерфейса:'
    },
    en: {
        searchPlaceholder: 'Search page...',
        settingsTitle: 'Highlight settings',
        defaultBg: 'Default background:',
        defaultOpacity: 'Default opacity (0-1):',
        currentBg: 'Current background:',
        currentOpacity: 'Current opacity (0-1):',
        textColor: 'Text color:',
        borderColor: 'Border color:',
        borderRadius: 'Border radius (px):',
        borderThickness: 'Border thickness (px):',
        padding: 'Padding (px):',
        save: 'Save',
        cancel: 'Cancel',
        lang: 'Interface language:'
    }
};

    // Загрузка настроек из localStorage, если есть
    const savedSettings = localStorage.getItem('pageSearcherSettings');
    if (savedSettings) {
        const parsed = JSON.parse(savedSettings);
        settings = { ...settings, ...parsed };
    }
     // Функция для конвертации rgb в rgba с opacity
    function rgbToRgba(rgb, opacity) {
        if (rgb.startsWith('rgb(')) {
            const [r, g, b] = rgb.match(/\d+/g).map(Number);
            return `rgba(${r}, ${g}, ${b}, ${opacity})`;
        }
        return rgb;
    }
    // Функция применения настроек к существующим подсветкам
   function applySettings() {
        // Применяем к существующим matches
        matches.forEach((mark, i) => {
            if (!mark) return;
            const isCurrent = i === currentIndex;
            mark.style.color = settings.highlightTextColor;
            mark.style.border = `${settings.highlightBorderThickness}px solid ${settings.highlightBorderColor}`;
            mark.style.borderRadius = `${settings.highlightBorderRadius}px`;
            mark.style.padding = `${settings.highlightPadding}px`;
            mark.style.backgroundColor = rgbToRgba(
                isCurrent ? settings.highlightCurrentBg : settings.highlightDefaultBg,
                isCurrent ? settings.highlightCurrentOpacity : settings.highlightDefaultOpacity
            );
        });
        // Сохранение в localStorage
        localStorage.setItem('pageSearcherSettings', JSON.stringify(settings));
    }
    // Создание wrapper (всегда видимый)
    const wrapper = document.createElement('div');
    wrapper.id = 'modifiedcstm-page-searcher-wrapper-r6ujr5jre5';
    wrapper.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        z-index: 555555;
        display: flex;
        flex-direction: column;
        gap: 2px;
    `;
    // Кнопка сворачивания (всегда видна)
    const btnToggle = document.createElement('button');
    btnToggle.id = 'searche4nmx7hjn-toggle8nme5h-btnjre6';
    btnToggle.style.cssText = `
           width: 30px;
           height: 30px;
           background: rgb(13, 61, 63);
           border: 1px solid rgb(139, 248, 194);
           border-radius: 5px;
           cursor: pointer;
           display: flex;
           align-items: center;
           justify-content: center;
           box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
           transition: background 0.3s;
           position: fixed;
           top: 10px;
           left: 1081px;
    `;
    btnToggle.addEventListener('click', () => {
        wrapper.classList.toggle('collapsed');
    });

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.style.transition = 'transform 0.3s ease';
    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', 'M7 10l5 5 5-5z');
    path.setAttribute('fill', 'rgb(139, 248, 194)');
    svg.appendChild(path);
    btnToggle.appendChild(svg);
    // Стили для анимации и скрытия (убрали стили для mark, так как теперь inline)
    const style = document.createElement('style');
    style.textContent = `
 #modifiedcstm-page-searcher-wrapper-r6ujr5jre5.collapsed #modifiedcstm-page-search-container-r6ujr5jre5 {
       display: none !important;
 }
 #searche4nmx7hjn-toggle8nme5h-btnjre6:hover {
      background: rgb(20, 80, 82);
 }
 #modifiedcstm-page-searcher-wrapper-r6ujr5jre5.collapsed #searche4nmx7hjn-toggle8nme5h-btnjre6 svg {
      transform: rotate(180deg);
 }
 input#input-searcj-on-paggeweb-text {
    background: #1f2a2f !important;
    border: 2px solid #46968d !important;
    color: antiquewhite !important;
 }
 #modifiedcstm-page-search-container-r6ujr5jre5 {
    display: flex !important;
}
 #searcherUp-btnUp-5en8h4w5en8m {
    padding: 5px !important;
    background: rgb(20 29 43) !important;
    color: rgb(139, 248, 194) !important;
    border: 1px solid rgb(139, 248, 194) !important;
    border-radius: 3px !important;
    cursor: pointer !important;
    width: 28px !important;
    height: 28px !important;
}

#searcherDown-btnDown-5en8h4w5en8m {
    padding: 5px !important;
    background: rgb(20 29 43) !important;
    color: rgb(139, 248, 194) !important;
    border: 1px solid rgb(139, 248, 194) !important;
    border-radius: 3px !important;
    cursor: pointer !important;
    width: 28px !important;
    height: 28px !important;
}

 #searche4nmx7hjn-toggle8nme5h-btnjre6:hover,
#searcherUp-btnUp-5en8h4w5en8m:hover,
#closeSsearchWrapper-8n5e85egh8n-he5hedhe5d:hover,
#searcherDown-btnDown-5en8h4w5en8m:hover,
#settings-btnCstm-page-searcher:hover {
      background-color: rgb(39 118 117 / 96%) !important;
    transform: scale(1.3)  !important;
    transition: all 0.3s ease  !important;
    fill: #70f4a4 !important;
}
 #settingsuje-modalhjer5mr6f-for-webSeracherfor {
    background: rgb(13, 61, 63) !important;
    border: 1px solid rgb(139, 248, 194) !important;
    border-radius: 5px !important;
    padding: 20px !important;
    width: 300px !important;
    color: antiquewhite !important;
    font-family: Arial, sans-serif !important;
    position: absolute !important;
    top: 105px !important;
}

button#save-settings ,  
button#cancel-settings,   
input#current-opacity,   
input#default-opacity,   
input#padding, 
input#border-thickness,  
input#border-radius,   
input#border-color,   
input#text-color,   
input#current-bg, 
input#default-bg  {
    appearance: none !important;
    padding: 3px !important;
    background: rgb(20 29 43) !important;
    color: rgb(190 255 246) !important;
    border: 1px solid rgb(139, 248, 194) !important;
    border-radius: 5px !important;  
}

button#save-settings:hover, 
button#cancel-settings:hover,  
input#current-opacity:hover,  
input#default-opacity:hover,  
input#padding:hover,
input#border-thickness:hover, 
input#border-radius:hover,  
input#border-color:hover,  
input#text-color:hover,  
input#current-bg:hover,
input#default-bg:hover {
    appearance: none !important; 
    background: rgb(152 214 218 / 23%) !important;
    color: rgb(139, 248, 194) !important;
    border: 1px solid rgb(139, 248, 194) !important;
    border-radius: 5px !important; 
    scale: 1.5 !important; 
} 
    `;
    document.head.appendChild(style);
    // Создание контейнера (внутри wrapper)
    const container = document.createElement('div');
    container.id = 'modifiedcstm-page-search-container-r6ujr5jre5';
    container.style.cssText = `
        background: rgb(13, 61, 63);
        border: 1px solid rgb(139, 248, 194);
        border-radius: 5px;
        padding: 5px;
        display: flex;
        align-items: center;
        gap: 5px;
        font-family: Arial, sans-serif;
        font-size: 14px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        position: relative;
        top: 45px;
        left: -150px;
    `;

    // Поле поиска
    const input = document.createElement('input');
    input.id = 'input-searcj-on-paggeweb-text';
    input.type = 'text';
    input.placeholder = translations[settings.lang].searchPlaceholder;

    input.style.cssText = 'padding: 5px; border: 1px solid #ccc; border-radius: 3px; width: 150px;';

      // Кнопки
    const btnNext = document.createElement('button');
    btnNext.id = 'searcherDown-btnDown-5en8h4w5en8m';
    btnNext.innerHTML = '↓';
    btnNext.style.cssText = 'padding: 5px; background: rgb(20 29 43); color: rgb(139, 248, 194); border: 1px solid rgb(139, 248, 194); border-radius: 3px; cursor: pointer; width: 28px; height: 28px;';

    const btnPrev = document.createElement('button');
    btnPrev.id = 'searcherUp-btnUp-5en8h4w5en8m';
    btnPrev.innerHTML = '↑';
    btnPrev.style.cssText = 'padding: 5px; background: rgb(20 29 43); color: rgb(139, 248, 194); border: 1px solid rgb(139, 248, 194); border-radius: 3px; cursor: pointer; width: 28px; height: 28px;';

    const btnClose = document.createElement('button');
     btnClose.id = 'closeSsearchWrapper-8n5e85egh8n-he5hedhe5d';
    btnClose.innerHTML = '×';
    btnClose.style.cssText = 'padding: 5px 8px; background: rgb(20 29 43); color: rgb(139, 248, 194); border: 1px solid rgb(139, 248, 194); border-radius: 3px; cursor: pointer; font-size: 16px; width: 28px; height: 28px;';

    // Кнопка настроек с SVG-иконкой шестеренки
    const btnSettings = document.createElement('button');
    btnSettings.id = 'settings-btnCstm-page-searcher';
    btnSettings.style.cssText = 'padding: 5px; background: rgb(20 29 43); color: rgb(139, 248, 194); border: 1px solid rgb(139, 248, 194); border-radius: 3px; cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;';

    const svgSettings = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svgSettings.setAttribute('viewBox', '0 0 24 24');
    svgSettings.setAttribute('width', '16');
    svgSettings.setAttribute('height', '16');
    const pathSettings = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    pathSettings.setAttribute('d', 'M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.37-.29-.59-.22l-2.49.87c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-.87c-.23-.07-.47 0-.59.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.37.29.59.22l2.49-.87c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49.87c.23.07.47 0 .59-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z');
    pathSettings.setAttribute('fill', 'rgb(139, 248, 194)');
    svgSettings.appendChild(pathSettings);
    btnSettings.appendChild(svgSettings);

    // Обработчик для кнопки настроек
    btnSettings.addEventListener('click', () => {
        // Создание модального окна настроек
        const modal = document.createElement('div');
        modal.id = 'settings-modal-page-searcher';
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 999999;
        `;
        const modalContent = document.createElement('div');
        modalContent.id = 'settingsuje-modalhjer5mr6f-for-webSeracherfor';
        modalContent.style.cssText = `
            background: rgb(13, 61, 63);
            border: 1px solid rgb(139, 248, 194);
            border-radius: 5px;
            padding: 20px;
            width: 300px;
            color: antiquewhite;
            font-family: Arial, sans-serif;
        `;
       modalContent.innerHTML = `
    <h3>${translations[settings.lang].settingsTitle}</h3>

    <label>${translations[settings.lang].defaultBg}
        <input type="color" id="default-bg" value="${rgbToHex(settings.highlightDefaultBg)}">
    </label><br><br>

    <label>${translations[settings.lang].defaultOpacity}
        <input type="number" id="default-opacity" value="${settings.highlightDefaultOpacity}" min="0" max="1" step="0.1">
    </label><br><br>

    <label>${translations[settings.lang].currentBg}
        <input type="color" id="current-bg" value="${rgbToHex(settings.highlightCurrentBg)}">
    </label><br><br>

    <label>${translations[settings.lang].currentOpacity}
        <input type="number" id="current-opacity" value="${settings.highlightCurrentOpacity}" min="0" max="1" step="0.1">
    </label><br><br>

    <label>${translations[settings.lang].textColor}
        <input type="color" id="text-color" value="${rgbToHex(settings.highlightTextColor)}">
    </label><br><br>

    <label>${translations[settings.lang].borderColor}
        <input type="color" id="border-color" value="${rgbToHex(settings.highlightBorderColor)}">
    </label><br><br>

    <label>${translations[settings.lang].borderRadius}
        <input type="number" id="border-radius" value="${settings.highlightBorderRadius}" min="0" max="50">
    </label><br><br>

    <label>${translations[settings.lang].borderThickness}
        <input type="number" id="border-thickness" value="${settings.highlightBorderThickness}" min="1" max="10">
    </label><br><br>

    <label>${translations[settings.lang].padding}
        <input type="number" id="padding" value="${settings.highlightPadding}" min="0" max="20">
    </label><br><br>

    <label>${translations[settings.lang].lang}
        <select id="lang-select">
            <option value="ru" ${settings.lang==='ru'?'selected':''}>Русский</option>
            <option value="en" ${settings.lang==='en'?'selected':''}>English</option>
        </select>
    </label><br><br>

    <button id="save-settings">${translations[settings.lang].save}</button>
    <button id="cancel-settings">${translations[settings.lang].cancel}</button>
`;

        modal.appendChild(modalContent);
        document.body.appendChild(modal);

        // Обработчики в модальном окне
        document.getElementById('save-settings').addEventListener('click', () => {
            const defaultBgInput = document.getElementById('default-bg').value;
            const currentBgInput = document.getElementById('current-bg').value;
            const textColorInput = document.getElementById('text-color').value;
            const borderColorInput = document.getElementById('border-color').value;
            const defaultOpacityInput = document.getElementById('default-opacity').value;
            const currentOpacityInput = document.getElementById('current-opacity').value;
            settings.highlightDefaultBg = defaultBgInput ? hexToRgb(defaultBgInput) : settings.highlightDefaultBg;
            settings.highlightCurrentBg = currentBgInput ? hexToRgb(currentBgInput) : settings.highlightCurrentBg;
            settings.highlightTextColor = textColorInput ? hexToRgb(textColorInput) : settings.highlightTextColor;
            settings.highlightBorderColor = borderColorInput ? hexToRgb(borderColorInput) : settings.highlightBorderColor;
            settings.highlightBorderRadius = parseInt(document.getElementById('border-radius').value) || settings.highlightBorderRadius;
            settings.highlightBorderThickness = parseInt(document.getElementById('border-thickness').value) || settings.highlightBorderThickness;
            settings.highlightPadding = parseInt(document.getElementById('padding').value) || settings.highlightPadding;
            settings.lang = document.getElementById('lang-select').value; 
            settings.highlightDefaultOpacity = parseFloat(defaultOpacityInput) || settings.highlightDefaultOpacity;
            settings.highlightCurrentOpacity = parseFloat(currentOpacityInput) || settings.highlightCurrentOpacity;
            applySettings();
            // Пересоздаем подсветку, если нужно (или обновляем существующие)
            if (input.value.trim()) {
                searchAndHighlight(input.value);
            }
            input.placeholder = translations[settings.lang].searchPlaceholder;

            document.body.removeChild(modal);
        });
        document.getElementById('cancel-settings').addEventListener('click', () => {
            document.body.removeChild(modal);
        });
        // Закрытие по клику вне модала
        modal.addEventListener('click', (e) => {
            if (e.target === modal) document.body.removeChild(modal);
        });
    });

    container.appendChild(input);
    container.appendChild(btnPrev);
    container.appendChild(btnNext);
    container.appendChild(btnClose);
    container.appendChild(btnSettings); // Добавляем кнопку настроек рядом с контейнером
    wrapper.appendChild(btnToggle);
    wrapper.appendChild(container);
    document.body.appendChild(wrapper);

      // Функция очистки подсветки
    function clearHighlights() {
        matches.forEach(mark => {
            const parent = mark.parentNode;
            if (!parent) return;
            parent.replaceChild(document.createTextNode(mark.textContent), mark);
            parent.normalize();
        });
        matches = [];
        currentIndex = -1;
    }

     // Функция поиска и подсветки
    function searchAndHighlight(query) {
        clearHighlights();
        if (!query.trim()) return;
        const regex = new RegExp(`(${query})`, 'gi');

        function walk(node) {
            if (node.nodeType === Node.TEXT_NODE && node.parentNode.tagName !== 'SCRIPT' && node.parentNode.tagName !== 'STYLE') {
                const text = node.textContent;
                const frag = document.createDocumentFragment();
                let lastIndex = 0;
                let match;
                while ((match = regex.exec(text)) !== null) {
                    if (match.index > lastIndex) {
                        frag.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
                    }
                    // Mark для совпадения
                    const mark = document.createElement('mark');
                    mark.id = 'markSearch-he5ngw4n-webpagebody';
                    mark.textContent = match[1];
                    mark.style.fontWeight = 'bold';
                    // Применяем стили подсветки
                    mark.style.color = settings.highlightTextColor;
                    mark.style.border = `${settings.highlightBorderThickness}px solid ${settings.highlightBorderColor}`;
                    mark.style.borderRadius = `${settings.highlightBorderRadius}px`;
                    mark.style.padding = `${settings.highlightPadding}px`;
                    mark.style.backgroundColor = rgbToRgba(settings.highlightDefaultBg, settings.highlightDefaultOpacity);
                    frag.appendChild(mark);
                    matches.push(mark);
                    lastIndex = match.index + match[1].length;
                }
                // Остаток текста
                if (lastIndex < text.length) {
                    frag.appendChild(document.createTextNode(text.slice(lastIndex)));
                }
                if (matches.length) node.parentNode.replaceChild(frag, node);
            } else if (node.nodeType === Node.ELEMENT_NODE) {
                Array.from(node.childNodes).forEach(child => walk(child));
            }
        }

        walk(document.body);
    }


    // Функция навигации
    function navigate(direction) {
        if (!matches.length) return;
        currentIndex += direction;
        if (currentIndex >= matches.length) currentIndex = 0;
        if (currentIndex < 0) currentIndex = matches.length - 1;
        matches.forEach((m, i) => {
           if (m) {
                const isCurrent = i === currentIndex;
                m.style.backgroundColor = rgbToRgba(
                    isCurrent ? settings.highlightCurrentBg : settings.highlightDefaultBg,
                    isCurrent ? settings.highlightCurrentOpacity : settings.highlightDefaultOpacity
                );
            }
        });
        matches[currentIndex].scrollIntoView({ behavior: 'smooth', block: 'center' });
    }

     // Обработчики событий
    input.addEventListener('input', e => searchAndHighlight(e.target.value));
    btnNext.addEventListener('click', () => navigate(1));
    btnPrev.addEventListener('click', () => navigate(-1));
    btnClose.addEventListener('click', () => {
        clearHighlights();
        wrapper.classList.add('collapsed');
        container.style.display = 'none';
    });

 // Горячие клавиши
    document.addEventListener('keydown', e => {
        if (e.ctrlKey && e.key === 'f') {
            e.preventDefault();
            wrapper.classList.remove('collapsed');
            input.focus();
        }
        if (!wrapper.classList.contains('collapsed') && container.style.display !== 'none' && input === document.activeElement) {
            if (e.key === 'Enter') navigate(e.shiftKey ? -1 : 1);
            if (e.key === 'Escape') btnClose.click();
        }
    });

    container.style.display = 'flex';

     // Вспомогательные функции для цветов
    function rgbToHex(rgb) {
        if (rgb.startsWith('rgb(')) {
            const [r, g, b] = rgb.match(/\d+/g);
            return '#' + ((1 << 24) + (parseInt(r) << 16) + (parseInt(g) << 8) + parseInt(b)).toString(16).slice(1);
        }
        return rgb;
    }
    function hexToRgb(hex) {
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)})` : hex;
    }
})();