Ozon Sort by Reviews

Добавляет кнопку для сортировки товаров по количеству отзывов

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Ozon Sort by Reviews
// @version      0.7
// @description  Добавляет кнопку для сортировки товаров по количеству отзывов
// @author       Jipok
// @match        https://www.ozon.ru/*
// @grant        none
// @license      MIT
// @namespace    https://gist.github.com/Jipok/cda67abb99078c85ca452a7a261384ac
// ==/UserScript==


(function() {
'use strict';

    function extractReviewCount(element) {
        const reviewText = Array.from(element.querySelectorAll('*'))
            .map(el => el.textContent)
            .find(text => text && text.includes('отзыв'));

        if (!reviewText) return 0;

        const match = reviewText.match(/(\d+[\s,]?\d*)\s*отзыв/);
        return match ? parseInt(match[1].replace(/[\s,]/g, '')) : 0;
    }

    function sortByReviews() {
        const products = Array.from(document.querySelectorAll('div[data-index]')).filter(el => {
            return el.querySelector('img') &&
                   el.querySelector('a[href*="/product/"]') &&
                   el.textContent.includes('₽');
        });

        if (!products.length) {
            console.log('Товары не найдены');
            return;
        }

        const container = products[0].parentElement;
        if (!container) return;

        const sortedProducts = [...products].sort((a, b) => {
            const reviewsA = extractReviewCount(a);
            const reviewsB = extractReviewCount(b);
            return reviewsB - reviewsA;
        });

        container.innerHTML = '';
        sortedProducts.forEach(product => {
            container.appendChild(product);
        });

        window.scrollTo(0, 0);
    }

    // Стили для попапа
    const style = document.createElement('style');
    style.textContent = `
        .loading-popup {
            position: fixed;
            bottom: 2rem;
            right: 1rem;
            background: white;
            padding: 1.25rem;
            border-radius: 0.75rem;
            box-shadow: 0 0.125rem 0.625rem rgba(0,0,0,0.2);
            z-index: 9999;
            display: none;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            font-size: 1rem;
        }
        .loading-popup.visible {
            display: block;
        }
        .loading-popup-content {
            margin-bottom: 0.75rem;
        }
        .loading-popup-cancel {
            background: #ff4d4d;
            color: white;
            border: none;
            padding: 0.5rem 1rem;
            border-radius: 0.375rem;
            cursor: pointer;
            font-size: 0.875rem;
            width: 100%;
        }
        .loading-popup-cancel:hover {
            background: #ff3333;
        }

        @media (min-width: 768px) {
            .desktop-buttons-container {
                display: inline-flex;
                align-items: center;
                gap: 8px;
                margin-left: 16px;
                position: absolute;
                right: 0;
                top: 50%;
                transform: translateY(-50%);
            }
            .desktop-buttons-container button {
                padding: 8px 16px !important;
                height: 40px !important;
                line-height: 20px !important;
                border-radius: 4px !important;
                font-size: 14px !important;
            }
        }

        @media (max-width: 767px) {
            .mobile-buttons-container {
                display: flex;
                justify-content: center;
                gap: 0.5rem;
                padding: 0.5rem 1rem;
                margin-bottom: 1rem;
            }
        }
    `;
    document.head.appendChild(style);

    // Создаем попап
    const popup = document.createElement('div');
    popup.className = 'loading-popup';
    popup.innerHTML = `
        <div class="loading-popup-content">
            Загружено товаров: <span id="pagesCount">0</span>
        </div>
        <button class="loading-popup-cancel">Отменить загрузку</button>
    `;
    document.body.appendChild(popup);

    let loadingCancelled = false;

    // Обработчики отмены
    popup.querySelector('.loading-popup-cancel').addEventListener('click', () => {
        loadingCancelled = true;
    });

    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
            loadingCancelled = true;
        }
    });

    async function loadPages(pages = 7) {
        const loadButton = document.querySelector('#ozonLoadMore');
        loadButton.disabled = true;
        loadButton.innerHTML = 'Загрузка...';
        popup.classList.add('visible');
        const pagesCount = document.getElementById('pagesCount');

        let loadedPages = 0;
        let initialProducts = document.querySelectorAll('div[data-index]').length;
        pagesCount.textContent = initialProducts;

        const scrollInterval = setInterval(() => {
            if (!loadingCancelled) {
                window.scrollTo(0, document.body.scrollHeight);
                const currentProducts = document.querySelectorAll('div[data-index]').length;
                const newProducts = currentProducts
                pagesCount.textContent = newProducts;
            } else {
                clearInterval(scrollInterval);
                window.scrollTo(0, 0);
                loadButton.disabled = false;
                loadButton.innerHTML = 'Загрузить больше';
                popup.classList.remove('visible');
            }
        }, 500);

        return new Promise((resolve) => {
            const observer = new MutationObserver((mutations) => {
                const hasNewProducts = mutations.some(mutation =>
                    Array.from(mutation.addedNodes).some(node =>
                        node.nodeType === 1 && node.querySelector?.('[data-index]')
                    )
                );

                if (hasNewProducts) {
                    loadedPages++;
                    const currentProducts = document.querySelectorAll('div[data-index]').length;
                    const newProducts = currentProducts;
                    pagesCount.textContent = newProducts;

                    if (loadedPages >= pages || loadingCancelled) {
                        clearInterval(scrollInterval);
                        observer.disconnect();
                        window.scrollTo(0, 0);
                        loadButton.disabled = false;
                        loadButton.innerHTML = 'Загрузить больше';
                        popup.classList.remove('visible');
                        resolve();
                    }
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    function createButtons() {
        const buttonsContainer = document.createElement('div');

        const buttonStyle = `
            font-family: Sans;
            font-size: 0.875rem;
            font-weight: 400;
            height: 2rem;
            line-height: 2rem;
            border-radius: 1rem;
            padding: 0 1rem;
            background: #005bff;
            color: white;
            border: none;
            cursor: pointer;
            position: relative;
            transition: 0.2s cubic-bezier(0.4,0,0.2,1);
            white-space: nowrap;
        `;

        const loadButton = document.createElement('button');
        loadButton.id = 'ozonLoadMore';
        loadButton.innerHTML = 'Загрузить больше';
        loadButton.style.cssText = buttonStyle;

        const sortButton = document.createElement('button');
        sortButton.id = 'ozonCustomSort';
        sortButton.innerHTML = 'Сортировать по отзывам';
        sortButton.style.cssText = buttonStyle;

        [loadButton, sortButton].forEach(button => {
            button.addEventListener('mouseover', () => {
                button.style.opacity = '0.8';
            });
            button.addEventListener('mouseout', () => {
                button.style.opacity = '1';
            });
        });

        loadButton.addEventListener('click', () => loadPages(7));
        sortButton.addEventListener('click', sortByReviews);

        buttonsContainer.appendChild(loadButton);
        buttonsContainer.appendChild(sortButton);

        return buttonsContainer;
    }

    function addButtons() {
        if (document.querySelector('#ozonCustomSort')) return;

        const buttons = createButtons();

        // Пробуем десктопное расположение
        const sortContainer = document.querySelector('div[data-widget="searchResultsSort"]');
        if (sortContainer) {
            buttons.className = 'desktop-buttons-container';
            const parentContainer = sortContainer;
            parentContainer.style.position = 'relative';
            parentContainer.appendChild(buttons);
            return;
        }

        // Если не вышло, пробуем мобильное
        const paginator = document.querySelector('div#paginator');
        if (paginator) {
            buttons.className = 'mobile-buttons-container';
            paginator.parentNode.insertBefore(buttons, paginator);
        }
    }

    for (let i = 1; i < 5; i++) {
        setTimeout(addButtons, i * 550);
    }

    let lastUrl = location.href;
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            for (let i = 0; i < 5; i++) {
                setTimeout(addButtons, i * 550);
            }
        }
    }).observe(document, {subtree: true, childList: true});

})();