// ==UserScript==
// @name Custom search and highlighting aka (Ctrl+F)
// @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: {
toggleTooltip: 'Свернуть/развернуть поисковую панель',
searchPlaceholder: 'Поиск по странице...',
settingsTitle: 'Настройки подсветки',
defaultBg: 'Цвет фона (обычный):',
defaultOpacity: 'Прозрачность обычного фона (0-1):',
currentBg: 'Цвет фона (текущий):',
currentOpacity: 'Прозрачность текущего фона (0-1):',
textColor: 'Цвет текста:',
borderColor: 'Цвет бордера:',
borderRadius: 'Скругление (px):',
borderThickness: 'Толщина бордера (px):',
padding: 'Padding (px):',
save: 'Сохранить',
cancel: 'Отмена',
lang: 'Язык интерфейса:'
},
en: {
toggleTooltip: 'Toggle search-wrapper-panel' ,
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: 999999 !important;
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: 1%;
left: 58%;
z-index: 999999 !important;
`;
// Обработчики для tooltip и клика
btnToggle.addEventListener('mouseenter', () => {
tooltip.classList.add('show');
});
btnToggle.addEventListener('mouseleave', () => {
tooltip.classList.remove('show');
});
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);
// Создание кастомного tooltip
const tooltip = document.createElement('div');
tooltip.id = 'customherjr6-tooltip-toggle';
tooltip.textContent = translations[settings.lang].toggleTooltip || 'Свернуть/развернуть поиск'; // Текст из translations (добавьте ключ, если ещё не)
tooltip.style.cssText = `
position: absolute;
bottom: -35px; /* Позиция относительно кнопки: снизу */
left: 50%;
transform: translateX(-50%);
`;
btnToggle.appendChild(tooltip); // Вставляем tooltip как дочерний элемент кнопки
// Стили для анимации и скрытия (убрали стили для mark, так как теперь inline)
const style = document.createElement('style');
style.textContent = `
/* Кастомный tooltip для кнопки сворачивания */
#customherjr6-tooltip-toggle {
position: absolute;
background: rgb(13, 61, 63);
color: rgb(139, 248, 194);
padding: 3px 7px;
border-radius: 5px;
border: 1px solid rgb(139, 248, 194);
font-size: 12px;
font-family: Arial, sans-serif;
white-space: nowrap;
z-index: 999999 !important
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
pointer-events: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.43);
}
#customherjr6-tooltip-toggle.show {
opacity: 1;
visibility: visible;
z-index: 999999 !important
}
#searche4nmx7hjn-toggle8nme5h-btnjre6:hover #customherjr6-tooltip-toggle {
display: block;
}
#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:rgb(255, 254, 221) !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,
select#lang-select,
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(52, 77, 85) !important;
color: rgb(139, 248, 194) !important;
border: 1px solid rgb(139, 248, 194) !important;
border-radius: 5px !important;
scale: 1.5 !important;
}
select#lang-select:hover {
appearance: none !important;
background: rgb(52, 77, 85) !important;
color: rgb(139, 248, 194) !important;
border: 1px solid rgb(139, 248, 194) !important;
border-radius: 3px !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);
wrapper.classList.add('collapsed');
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();
}
});
// Вспомогательные функции для цветов
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;
}
})();