Grok Code Filter Menu 1.21.19

Adds a filter menu to the code blocks in the Grok chat while maintaining the settings

Versione datata 31/08/2025. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Grok Code Filter Menu 1.21.19
// @namespace    http://tampermonkey.net/
// @version      1.21.19
// @description  Adds a filter menu to the code blocks in the Grok chat while maintaining the settings
// @author       tapeavion
// @license      MIT
// @match        https://grok.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grok.com
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue

// ==/UserScript==

(function() {
    'use strict';

    // Стили
    const style = document.createElement('style');
    style.textContent = `
        .filter-menu-btn {
            position: absolute;
            top: 4px;
            right: 460px;
            height: 31px !important;
            z-index: 1;
            padding: 4px 8px;
            background: #1d5752;
            color: #b9bcc1;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 12px;
            transition: background 0.2s ease, color 0.2s ease;
        }
        .filter-menu-btn:hover {
            background: #4a8983;
            color: #ffffff;
        }
        .filter-menu {
            position: absolute;
            top: 40px;
            right: 10px;
            background: #2d2d2d;
            border: 1px solid #444;
            border-radius: 8px;
            padding: 5px;
            z-index: 9999;
            display: none;
            box-shadow: 0 2px 4px rgba(0,0,0,0.3);
            width: 200px;
            max-height: 550px;
            overflow-y: auto;
        }
        .filter-item {
            display: flex;
            align-items: center;
            padding: 5px 0;
            color: #a0a0a0;
            font-size: 12px;
        }
        .filter-item input[type="checkbox"] {
            margin-right: 5px;
        }
        .filter-item label {
            flex: 1;
            cursor: pointer;
        }
        .filter-slider {
            display: none;
            margin: 5px 0 5px 20px;
            width: calc(100% - 20px);
        }
        .filter-slider-label {
            display: none;
            color: #a0a0a0;
            font-size: 12px;
            margin: 2px 0 2px 20px;
        }
        .language-select {
            width: 100%;
            padding: 5px;
            margin-bottom: 5px;
            background: #3a3a3a;
            color: #a0a0a0;
            border: none;
            border-radius: 4px;
            font-size: 12px;
        }
        .color-picker {
            margin: 5px 0 5px 20px;
            width: calc(100% - 20px);
        }
        .color-picker-label {
            display: block;
            color: #a0a0a0;
            font-size: 12px;
            margin: 2px 0 2px 20px;
        }
        button.inline-flex {
            background-color: #1d5752 !important;
            opacity: 0;
            animation: fadeIn 1s ease-in-out forwards;
        }
        button.inline-flex:hover {
            background-color: #1d5752 !important;
            opacity: 1;
        }
        @keyframes fadeIn {
            0% { opacity: 0; }
            100% { opacity: 1; }
        }
    `;
    document.head.appendChild(style);

    // Определение языка пользователя
    const userLang = navigator.language || navigator.languages[0];
    const isRussian = userLang.startsWith('ru');
    const defaultLang = isRussian ? 'ru' : 'en';
    const savedLang = localStorage.getItem('filterMenuLang') || defaultLang;

    // Локализация
    const translations = {
        ru: {
            filtersBtn: 'Фильтры',
            sliderLabel: 'Степень:',
            commentColorLabel: 'Цвет комментариев:',
            filters: [
                { name: 'Негатив', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Сепия', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Ч/Б', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Размытие', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
                { name: 'Контраст', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
                { name: 'Яркость', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
                { name: 'Поворот оттенка', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
                { name: 'Насыщенность', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
                { name: 'Прозрачность', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
            ],
            langSelect: 'Выберите язык:',
            langOptions: [
                { value: 'ru', label: 'Русский' },
                { value: 'en', label: 'English' }
            ]
        },
        en: {
            filtersBtn: 'Filters',
            sliderLabel: 'Level:',
            commentColorLabel: 'Comment color:',
            filters: [
                { name: 'Invert', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Sepia', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Grayscale', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
                { name: 'Blur', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
                { name: 'Contrast', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
                { name: 'Brightness', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
                { name: 'Hue Rotate', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
                { name: 'Saturate', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
                { name: 'Opacity', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
            ],
            langSelect: 'Select language:',
            langOptions: [
                { value: 'ru', label: 'Русский' },
                { value: 'en', label: 'English' }
            ]
        }
    };

    // Глобальная переменная для текущего цвета комментариев
    let currentCommentColor = localStorage.getItem('commentColor') || '#5c6370';
    // Массив для хранения всех пар headerBlock и codeContainer
    const codeBlockRegistry = [];

    // Функция создания меню фильтров
    function addFilterMenu(headerBlock, codeContainer) {
        if (headerBlock.querySelector('.filter-menu-btn')) {
            console.log('Фильтр уже существует для заголовка:', headerBlock);
            return;
        }

        // Сохраняем пару headerBlock и codeContainer
        codeBlockRegistry.push({ headerBlock, codeContainer });
        console.log('Добавлен кодовый блок в реестр:', codeContainer.outerHTML);

        let currentLang = savedLang;
        const filterBtn = document.createElement('button');
        filterBtn.className = 'filter-menu-btn';
        filterBtn.textContent = translations[currentLang].filtersBtn;

        const filterMenu = document.createElement('div');
        filterMenu.className = 'filter-menu';

        // Целевой блок — контейнер кода
        const targetBlock = codeContainer;

        // Загружаем сохраненные настройки
        const savedFilterStates = JSON.parse(localStorage.getItem('codeFilterStates') || '{}');
        const savedFilterValues = JSON.parse(localStorage.getItem('codeFilterValues') || '{}');

        // Инициализируем значения по умолчанию
        const filters = translations[currentLang].filters;
        filters.forEach(filter => {
            if (!(filter.value in savedFilterStates)) {
                savedFilterStates[filter.value] = false;
            }
            if (!(filter.value in savedFilterValues)) {
                savedFilterValues[filter.value] = filter.default;
            }
        });

        // Применяем сохраненные фильтры
        function applyFilters() {
            const activeFilters = filters
                .filter(filter => savedFilterStates[filter.value])
                .map(filter => {
                    const unit = filter.unit || '';
                    const value = savedFilterValues[filter.value];
                    return `${filter.value}(${value}${unit})`;
                });
            targetBlock.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
            console.log('Применены фильтры к контейнеру:', targetBlock, activeFilters);
        }

        // Применяем цвет комментариев
       // Альтернативный подход: применение цвета через глобальный стиль
function applyGlobalCommentColor() {
    const existingStyle = document.getElementById('custom-comment-style');
    if (existingStyle) existingStyle.remove();

    const style = document.createElement('style');
    style.id = 'custom-comment-style';
    style.textContent = `
        .hljs-comment, span[style*="color: rgb(92, 99, 112)"] {
            color: ${currentCommentColor} !important;
        }
    `;
    document.head.appendChild(style);
    console.log('Применен глобальный стиль для комментариев:', currentCommentColor);
}
        applyFilters();
        applyGlobalCommentColor();

        // Создаем выпадающий список для выбора языка
        const langSelect = document.createElement('select');
        langSelect.className = 'language-select';
        const langLabel = document.createElement('label');
        langLabel.textContent = translations[currentLang].langSelect;
        langLabel.style.color = '#a0a0a0';
        langLabel.style.fontSize = '12px';
        langLabel.style.marginBottom = '2px';
        langLabel.style.display = 'block';

        translations[currentLang].langOptions.forEach(option => {
            const opt = document.createElement('option');
            opt.value = option.value;
            opt.textContent = option.label;
            if (option.value === currentLang) {
                opt.selected = true;
            }
            langSelect.appendChild(opt);
        });

        // Создаем элемент для выбора цвета комментариев
        const colorPickerLabel = document.createElement('label');
        colorPickerLabel.className = 'color-picker-label';
        colorPickerLabel.textContent = translations[currentLang].commentColorLabel;

        const colorPicker = document.createElement('input');
        colorPicker.type = 'color';
        colorPicker.className = 'color-picker';
        colorPicker.value = currentCommentColor;

        colorPicker.addEventListener('input', () => {
            currentCommentColor = colorPicker.value;
            localStorage.setItem('commentColor', currentCommentColor);
            console.log('Изменен цвет комментариев:', currentCommentColor);
            refreshAllCodeBlocks();
        });

        // Функция обновления интерфейса при смене языка
        function updateLanguage(lang) {
            currentLang = lang;
            localStorage.setItem('filterMenuLang', currentLang);
            filterBtn.textContent = translations[currentLang].filtersBtn;
            langLabel.textContent = translations[currentLang].langSelect;
            colorPickerLabel.textContent = translations[currentLang].commentColorLabel;
            filterMenu.innerHTML = '';
            filterMenu.appendChild(langLabel);
            filterMenu.appendChild(langSelect);
            filterMenu.appendChild(colorPickerLabel);
            filterMenu.appendChild(colorPicker);
            renderFilters();
        }

        // Обработчик смены языка
        langSelect.addEventListener('change', () => {
            updateLanguage(langSelect.value);
        });

        // Рендеринг фильтров
        function renderFilters() {
            const filters = translations[currentLang].filters;
            filters.forEach(filter => {
                const filterItem = document.createElement('div');
                filterItem.className = 'filter-item';

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = savedFilterStates[filter.value];
                checkbox.id = `filter-${filter.value}`;

                const label = document.createElement('label');
                label.htmlFor = `filter-${filter.value}`;
                label.textContent = filter.name;

                const sliderLabel = document.createElement('label');
                sliderLabel.className = 'filter-slider-label';
                sliderLabel.textContent = translations[currentLang].sliderLabel;

                const slider = document.createElement('input');
                slider.type = 'range';
                slider.className = 'filter-slider';
                slider.min = filter.min;
                slider.max = filter.max;
                slider.step = filter.step;
                slider.value = savedFilterValues[filter.value];

                if (checkbox.checked && filter.hasSlider) {
                    slider.style.display = 'block';
                    sliderLabel.style.display = 'block';
                }

                checkbox.addEventListener('change', () => {
                    savedFilterStates[filter.value] = checkbox.checked;
                    localStorage.setItem('codeFilterStates', JSON.stringify(savedFilterStates));
                    if (filter.hasSlider) {
                        slider.style.display = checkbox.checked ? 'block' : 'none';
                        sliderLabel.style.display = checkbox.checked ? 'block' : 'none';
                    }
                    console.log('Изменен фильтр:', filter.value, checkbox.checked);
                    refreshAllCodeBlocks();
                });

                slider.addEventListener('input', () => {
                    savedFilterValues[filter.value] = slider.value;
                    localStorage.setItem('codeFilterValues', JSON.stringify(savedFilterValues));
                    console.log('Изменено значение фильтра:', filter.value, slider.value);
                    refreshAllCodeBlocks();
                });

                filterItem.appendChild(checkbox);
                filterItem.appendChild(label);
                filterMenu.appendChild(filterItem);
                filterMenu.appendChild(sliderLabel);
                filterMenu.appendChild(slider);
            });
        }

        // Инициализация
        filterMenu.appendChild(langLabel);
        filterMenu.appendChild(langSelect);
        filterMenu.appendChild(colorPickerLabel);
        filterMenu.appendChild(colorPicker);
        renderFilters();

        // Обработчики для кнопки
        filterBtn.addEventListener('click', () => {
            filterMenu.style.display = filterMenu.style.display === 'block' ? 'none' : 'block';
        });

        document.addEventListener('click', (e) => {
            if (!filterBtn.contains(e.target) && !filterMenu.contains(e.target)) {
                filterMenu.style.display = 'none';
            }
        });

        headerBlock.style.position = 'relative';
        headerBlock.appendChild(filterBtn);
        headerBlock.appendChild(filterMenu);
    }

    // Функция логирования структуры DOM для отладки
    function logDomStructure(headerBlock) {
        console.log('Заголовок блока кода:', headerBlock.outerHTML);
        console.log('Следующий элемент (nextElementSibling):', headerBlock.nextElementSibling?.outerHTML || 'Не найден');
        console.log('Родительский элемент:', headerBlock.parentElement.outerHTML);
        console.log('Все <code> в родителе:', Array.from(headerBlock.parentElement.querySelectorAll('code')).map(el => el.outerHTML));
        console.log('Все .not-prose в родителе:', Array.from(headerBlock.parentElement.querySelectorAll('.not-prose')).map(el => el.outerHTML));
    }

    // Функция для управления селекторами и контейнерами
    function manageSelectorsAndContainers(headerBlocks, headerSelectorUsed, containerSelectorUsed, codeContainer) {
        const savedSelectors = JSON.parse(localStorage.getItem('codeBlockSelectors') || '{}');

        // Сохраняем успешные селекторы и контейнеры
        if (headerBlocks.length > 0 && codeContainer) {
            savedSelectors[headerSelectorUsed] = savedSelectors[headerSelectorUsed] || {};
            savedSelectors[headerSelectorUsed].headerCount = headerBlocks.length;
            savedSelectors[headerSelectorUsed].containerSelector = containerSelectorUsed;
            savedSelectors[headerSelectorUsed].lastUsed = Date.now();
            localStorage.setItem('codeBlockSelectors', JSON.stringify(savedSelectors));
            console.log('Сохранены селекторы:', headerSelectorUsed, containerSelectorUsed);
        }

        // Очистка устаревших селекторов (старше 7 дней)
        const oneWeek = 7 * 24 * 60 * 60 * 1000;
        Object.keys(savedSelectors).forEach(selector => {
            if (Date.now() - savedSelectors[selector].lastUsed > oneWeek) {
                delete savedSelectors[selector];
            }
        });
        localStorage.setItem('codeBlockSelectors', JSON.stringify(savedSelectors));
        return savedSelectors;
    }

    // Функция для обновления всех кодовых блоков
    function refreshAllCodeBlocks() {
        const savedFilterStates = JSON.parse(localStorage.getItem('codeFilterStates') || '{}');
        const savedFilterValues = JSON.parse(localStorage.getItem('codeFilterValues') || '{}');
        const filters = [
            { value: 'invert', default: 1 },
            { value: 'sepia', default: 1 },
            { value: 'grayscale', default: 1 },
            { value: 'blur', unit: 'px', default: 2 },
            { value: 'contrast', default: 2 },
            { value: 'brightness', default: 1.5 },
            { value: 'hue-rotate', unit: 'deg', default: 90 },
            { value: 'saturate', default: 1 },
            { value: 'opacity', default: 1 }
        ];

        // Применяем к зарегистрированным блокам
        console.log('Реестр кодовых блоков:', codeBlockRegistry.length);
        codeBlockRegistry.forEach(({ codeContainer }) => {
            const activeFilters = filters
                .filter(filter => savedFilterStates[filter.value])
                .map(filter => {
                    const unit = filter.unit || '';
                    const value = savedFilterValues[filter.value] || filter.default;
                    return `${filter.value}(${value}${unit})`;
                });
            codeContainer.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
            console.log('Применены фильтры к зарегистрированному контейнеру:', codeContainer, activeFilters);

            const commentElements = codeContainer.querySelectorAll('span[style*="color: rgb(136, 136, 136)"], .hljs-comment');
            console.log('Найдено комментариев в зарегистрированном контейнере:', commentElements.length);
            commentElements.forEach(element => {
                element.style.color = currentCommentColor;
            });
        });

        // Глобальное применение к новым или незарегистрированным блокам
        const allCodeContainers = document.querySelectorAll('.not-prose div > code, .not-prose pre > code');
        allCodeContainers.forEach(codeContainer => {
            if (!codeBlockRegistry.some(reg => reg.codeContainer === codeContainer.parentElement)) {
                const activeFilters = filters
                    .filter(filter => savedFilterStates[filter.value])
                    .map(filter => {
                        const unit = filter.unit || '';
                        const value = savedFilterValues[filter.value] || filter.default;
                        return `${filter.value}(${value}${unit})`;
                    });
                codeContainer.parentElement.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
                console.log('Применены фильтры к глобальному .not-prose контейнеру:', codeContainer.parentElement, activeFilters);

                const commentElements = codeContainer.querySelectorAll('span[style*="color: rgb(136, 136, 136)"], .hljs-comment');
                console.log('Найдено комментариев в глобальном .not-prose контейнере:', commentElements.length);
                commentElements.forEach(element => {
                    element.style.color = currentCommentColor;
                });
            }
        });
    }

    // Функция поиска и обработки блоков кода
    function processCodeBlocks() {
        // Селекторы для заголовков блоков кода
        const headerSelectors = [
            'div[class*="flex"][class*="rounded-t"] > span.font-mono.text-xs',
            'div[class*="flex"][class*="bg-surface"] > span',
            'div > span[class*="font-mono"]'
        ];

        // Селекторы для контейнеров кода
        const containerSelectors = [
            'nextElementSibling',
            '.not-prose div[style*="overflow-x: auto"]',
            '.not-prose div > code',
            '.not-prose pre > code',
            '.not-prose div[style*="background: hsl"]'
        ];

        // Загружаем сохраненные селекторы
        const savedSelectors = JSON.parse(localStorage.getItem('codeBlockSelectors') || '{}');
        let headerBlocks = [];
        let headerSelectorUsed = null;
        let containerSelectorUsed = null;
        let codeContainer = null;

        // Сначала пытаемся использовать сохраненные селекторы
        for (const savedSelector of Object.keys(savedSelectors)) {
            const headers = Array.from(document.querySelectorAll(savedSelector))
                .filter(span => {
                    const text = span.textContent.toLowerCase();
                    return ['javascript', 'css', 'html', 'python', 'java',  'text', 'cpp', 'json', 'bash', 'sql', 'xml', 'yaml', 'markdown'].includes(text);
                })
                .map(span => span.closest('div'));
            if (headers.length > 0) {
                headerBlocks = [...new Set(headers)];
                headerSelectorUsed = savedSelector;
                const savedContainerSelector = savedSelectors[savedSelector].containerSelector;

                // Проверяем сохраненный селектор контейнера
                for (const headerBlock of headers) {
                    if (savedContainerSelector === 'nextElementSibling') {
                        codeContainer = headerBlock.nextElementSibling?.querySelector('code') ? headerBlock.nextElementSibling : null;
                    } else if (savedContainerSelector === '.not-prose div > code') {
                        codeContainer = headerBlock.parentElement.querySelector('.not-prose div > code')?.parentElement;
                    } else if (savedContainerSelector === '.not-prose pre > code') {
                        codeContainer = headerBlock.parentElement.querySelector('.not-prose pre > code')?.parentElement;
                    } else {
                        codeContainer = headerBlock.parentElement.querySelector(savedContainerSelector);
                    }
                    if (codeContainer) {
                        containerSelectorUsed = savedContainerSelector;
                        console.log('Использованы сохраненные селекторы:', headerSelectorUsed, containerSelectorUsed);
                        break;
                    }
                }
                if (codeContainer) break;
            }
        }

        // Если сохраненные селекторы не сработали, ищем новые
        if (headerBlocks.length === 0) {
            for (const selector of headerSelectors) {
                const headers = Array.from(document.querySelectorAll(selector))
                    .filter(span => {
                        const text = span.textContent.toLowerCase();
                        return ['javascript', 'css', 'html', 'python', 'java', 'text', 'cpp', 'json', 'bash', 'sql', 'xml', 'yaml', 'markdown'].includes(text);
                    })
                    .map(span => span.closest('div'));
                if (headers.length > 0) {
                    headerBlocks = [...new Set(headers)];
                    headerSelectorUsed = selector;
                    break;
                }
            }
        }

        console.log('Найдено заголовков блоков кода:', headerBlocks.length);

        headerBlocks.forEach(headerBlock => {
            const langSpan = headerBlock.querySelector('span.font-mono.text-xs');
            if (!langSpan) {
                console.log('Заголовок без span с языком:', headerBlock);
                return;
            }

            // Пытаемся найти контейнер кода
            codeContainer = null;
            if (headerBlock.nextElementSibling?.querySelector('code')) {
                codeContainer = headerBlock.nextElementSibling;
                containerSelectorUsed = 'nextElementSibling';
            } else if (headerBlock.parentElement.querySelector('.not-prose div[style*="overflow-x: auto"]')) {
                codeContainer = headerBlock.parentElement.querySelector('.not-prose div[style*="overflow-x: auto"]');
                containerSelectorUsed = '.not-prose div[style*="overflow-x: auto"]';
            } else if (headerBlock.parentElement.querySelector('.not-prose div > code')) {
                codeContainer = headerBlock.parentElement.querySelector('.not-prose div > code')?.parentElement;
                containerSelectorUsed = '.not-prose div > code';
            } else if (headerBlock.parentElement.querySelector('.not-prose pre > code')) {
                codeContainer = headerBlock.parentElement.querySelector('.not-prose pre > code')?.parentElement;
                containerSelectorUsed = '.not-prose pre > code';
            } else if (headerBlock.parentElement.querySelector('.not-prose div[style*="background: hsl"]')) {
                codeContainer = headerBlock.parentElement.querySelector('.not-prose div[style*="background: hsl"]');
                containerSelectorUsed = '.not-prose div[style*="background: hsl"]';
            }

            if (codeContainer) {
                console.log('Найден контейнер кода для заголовка:', codeContainer.outerHTML);
                addFilterMenu(headerBlock, codeContainer);
                // Сохраняем успешные селекторы
                manageSelectorsAndContainers(headerBlocks, headerSelectorUsed, containerSelectorUsed, codeContainer);
            } else {
                console.log('Контейнер кода не найден для заголовка:', headerBlock.outerHTML);
                logDomStructure(headerBlock);
            }
        });

        // Обновляем все кодовые блоки после обработки
        refreshAllCodeBlocks();
    }

    // Инициализация
    setTimeout(() => {
        console.log('Инициализация processCodeBlocks');
        processCodeBlocks();
    }, 2000);

    // Наблюдатель за изменениями DOM
    const observer = new MutationObserver((mutations) => {
        console.log('Обнаружены изменения DOM, вызывается processCodeBlocks');
        processCodeBlocks();
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class', 'style']
    });

    // Инициализируем глобальное применение фильтров
    refreshAllCodeBlocks();
})();