PreviewCart

предпросмотр корзины

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Advertisement:

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

Advertisement:

// ==UserScript==
// @name         PreviewCart
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  предпросмотр корзины
// @author       HashBrute
// @match        https://lzt.market/*
// @grant        GM_addStyle
// @run-at       document-end
// @license      DDM
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
    .cart-hover-preview {
        position: absolute;
        width: 280px;
        max-width: 280px;
        background: #1f1f1f;
        border: 1px solid #363636;
        border-radius: 8px;
        box-shadow: 0 4px 12px rgba(0,0,0,0.5);
        z-index: 999999;
        overflow: hidden;
        display: none;
        pointer-events: auto !important;
        color: #ffffff;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
        box-sizing: border-box;
        max-height: 90vh;
    }

    .cart-hover-preview * {
        box-sizing: border-box;
    }

    .cart-hover-arrow {
        position: absolute;
        top: -8px;
        left: 50%;
        margin-left: -7px;
        width: 14px;
        height: 14px;
        background: #1f1f1f;
        transform: rotate(45deg);
        border-left: 1px solid #363636;
        border-top: 1px solid #363636;
        z-index: 1;
    }

    .cart-hover-content {
        padding: 12px;
        position: relative;
        z-index: 2;
        background: #1f1f1f;
        border-radius: 8px;
        max-height: calc(90vh - 24px);
        overflow-y: auto;
    }

    .cart-hover-header {
        text-align: center;
        display: flex;
        justify-content: center;
        align-items: center;
        margin-bottom: 12px;
        padding-bottom: 12px;
        border-bottom: 1px solid #363636;
    }

    .cart-hover-title {
        font-size: 15px;
        font-weight: 600;
        white-space: nowrap;
    }

    .cart-hover-count {
        background: #0daf77;
        color: white;
        border-radius: 12px;
        padding: 3px 9px;
        font-size: 12px;
        font-weight: 600;
        flex-shrink: 0;
        margin-left: 10px;
    }

    .cart-hover-items {
        max-height: 200px;
        overflow-y: auto;
        margin-bottom: 12px;
        scrollbar-width: thin;
        scrollbar-color: #363636 #1f1f1f;
    }

    .cart-hover-items::-webkit-scrollbar {
        width: 4px;
    }

    .cart-hover-items::-webkit-scrollbar-track {
        background: #1f1f1f;
    }

    .cart-hover-items::-webkit-scrollbar-thumb {
        background-color: #363636;
        border-radius: 3px;
    }

    .cart-hover-item {
        padding: 9px 0;
        border-bottom: 1px solid #363636;
        font-size: 13px;
    }

    .cart-hover-item:hover {
        background: rgba(255,255,255,0.03);
    }

    .cart-hover-item:last-child {
        border-bottom: none;
    }

    .cart-hover-item-title {
        color: #fff;
        margin-bottom: 4px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        line-height: 1.3;
    }

    .cart-hover-item-details {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 8px;
    }

    .cart-hover-item-seller {
        color: #949494;
        font-size: 12px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 65%;
    }

    .cart-hover-item-price {
        color: #0daf77;
        font-weight: 700;
        font-size: 12px;
        background: rgba(13, 175, 119, 0.15);
        padding: 4px 8px;
        border-radius: 4px;
        white-space: nowrap;
        flex-shrink: 0;
    }

    .cart-hover-empty {
        text-align: center;
        padding: 20px 0;
        color: #949494;
        font-size: 13px;
    }

    .cart-hover-footer {
        flex-direction: column;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding-top: 12px;
        border-top: 1px solid #363636;
    }

    .cart-hover-total {
        font-size: 15px;
        font-weight: 600;
        display: flex;
        align-items: center;
        width: 100%;
        justify-content: space-between;
        margin-bottom: 12px;
    }

    .cart-hover-total-price {
        background: #0daf77;
        color: white;
        padding: 5px 12px;
        border-radius: 5px;
        font-size: 14px;
    }

    .cart-hover-buttons {
        display: flex;
        gap: 10px;
        width: 100%;
    }

    .cart-hover-button {
        text-align: center;
        background: #0daf77;
        color: white !important;
        border-radius: 5px;
        font-weight: 600;
        text-decoration: none !important;
        transition: background 0.2s;
        font-size: 13px;
        border: none;
        cursor: pointer;
        padding: 9px 15px;
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .cart-hover-button-cart {
        padding: 9px;
        flex: 0 0 40px;
    }

    .cart-hover-button:hover {
        background: #09965e;
    }

    .cart-loader {
        text-align: center;
        padding: 20px;
    }

    .cart-loader-spinner {
        display: inline-block;
        width: 20px;
        height: 20px;
        border: 2px solid rgba(13, 175, 119, 0.3);
        border-radius: 50%;
        border-top-color: #0daf77;
        animation: cart-spin 1s linear infinite;
    }

    @keyframes cart-spin {
        to { transform: rotate(360deg); }
    }

    @media (max-width: 768px) {
        .cart-hover-preview {
            width: calc(100vw - 20px);
            max-width: 320px;
        }

        .cart-hover-item-price {
            font-size: 11px;
            padding: 3px 6px;
        }

        .cart-hover-button {
            font-size: 12px;
            padding: 8px 12px;
        }
    }
`);


    let cartPreviewEl = null;
    let cartData = null;
    let lastFetchTime = 0;
    let previewTimeout = null;
    const FETCH_COOLDOWN = 5000; 


    let isInitialized = false;

    const DEBUG_MODE = false; 


    function debugLog(...args) {
        if (DEBUG_MODE) {
            console.log('[LZT Cart Preview]', ...args);
        }
    }

    function errorLog(...args) {
        console.error('[LZT Cart Preview]', ...args);
    }


    function findCartElement() {
        try {
            const selectors = [
                '.navCart.navLink',
                '.navTab.PopupClosed a[href*="cart"]',
                '.navTab a[href*="cart"]',
                'a[href*="cart"]',
                'a[href*="mass-buy/cart"]',
                '.link--internal[href*="cart"]',
                '.p-navgroup-link[href*="cart"]'
            ];

            for (const selector of selectors) {
                const element = document.querySelector(selector);
                if (element) {
                    return element;
                }
            }

            const iconSelectors = [
                'svg.feather-shopping-cart',
                '.fa-shopping-cart',
                'i.fa-cart'
            ];

            for (const selector of iconSelectors) {
                const icon = document.querySelector(selector);
                if (icon) {
                    const parent = icon.closest('a') || icon.closest('button') || icon.closest('.navTab') || icon.parentElement;
                    if (parent) {
                        return parent;
                    }
                    return icon;
                }
            }

            return null;
        } catch (error) {
            return null;
        }
    }


    function initCartPreview() {
        if (isInitialized) {
            return;
        }

        const cartElement = findCartElement();
        if (!cartElement) {
            setTimeout(initCartPreview, 2000);
            return;
        }

        cartPreviewEl = document.createElement('div');
        cartPreviewEl.className = 'cart-hover-preview';
        cartPreviewEl.innerHTML = '<div class="cart-hover-arrow"></div><div class="cart-hover-content"><div class="cart-loader"><div class="cart-loader-spinner"></div></div></div>';

        document.body.appendChild(cartPreviewEl);

        function updatePosition() {
            try {
                const rect = cartElement.getBoundingClientRect();
                if (!rect || rect.width === 0) {
                    return;
                }

                const scrollTop = window.scrollY || document.documentElement.scrollTop;

                cartPreviewEl.style.position = 'absolute';
                cartPreviewEl.style.zIndex = '999999';

                const windowWidth = window.innerWidth;
                const previewWidth = 280;

                let leftPos = rect.left + (rect.width / 2) - (previewWidth / 2);

                if (leftPos < 10) leftPos = 10;
                if (leftPos + previewWidth > windowWidth - 10) {
                    leftPos = windowWidth - previewWidth - 10;
                }

                cartPreviewEl.style.top = (rect.bottom + scrollTop + 5) + 'px';
                cartPreviewEl.style.left = leftPos + 'px';

                const arrowEl = cartPreviewEl.querySelector('.cart-hover-arrow');
                if (arrowEl) {
                    const arrowLeftPos = (rect.left + (rect.width / 2) - leftPos);

                    const minArrowPos = 15; 
                    const maxArrowPos = previewWidth - 15 - 14; 

                    let finalArrowPos = Math.max(minArrowPos, Math.min(arrowLeftPos, maxArrowPos));
                    arrowEl.style.left = finalArrowPos + 'px';
                    arrowEl.style.right = 'auto';
                    arrowEl.style.marginLeft = '0';
                }

                debugLog('Обновлена позиция окна:', {
                    top: cartPreviewEl.style.top,
                    left: cartPreviewEl.style.left
                });
            } catch (error) {
                errorLog('Ошибка при обновлении позиции:', error);
            }
        }

        window.addEventListener('resize', updatePosition);
        document.addEventListener('scroll', updatePosition, { passive: true });

        const enterHandler = function(e) {
            clearTimeout(previewTimeout);
            updatePosition(); 
            handleCartHover();
        };

        const leaveHandler = function(e) {
            previewTimeout = setTimeout(() => {
                if (cartPreviewEl) {
                    cartPreviewEl.style.display = 'none';
                }
            }, 300);
        };


        cartElement.addEventListener('mouseenter', enterHandler);
        cartElement.addEventListener('mouseleave', leaveHandler);


        cartElement.addEventListener('touchstart', enterHandler, { passive: true });


        cartPreviewEl.addEventListener('mouseenter', function(e) {
            clearTimeout(previewTimeout);
        });

        cartPreviewEl.addEventListener('mouseleave', function(e) {
            if (cartPreviewEl) {
                cartPreviewEl.style.display = 'none';
            }
        });

        window.showCartPreview = function() {
            if (cartPreviewEl && cartElement) {
                updatePosition();
                handleCartHover();
                return;
            } else {
                return;
            }
        };

        isInitialized = true;
    }

    async function handleCartHover() {
        if (!cartPreviewEl) {
            return;
        }

        cartPreviewEl.style.display = 'block';
        cartPreviewEl.style.visibility = 'visible';

        const now = Date.now();
        if (!cartData || now - lastFetchTime > FETCH_COOLDOWN) {
            try {
                cartPreviewEl.querySelector('.cart-hover-content').innerHTML = '<div class="cart-loader"><div class="cart-loader-spinner"></div></div>';

                debugLog('Запрос данных корзины...');
                const response = await fetch('https://lzt.market/user/cart', {
                    credentials: 'include',
                    cache: 'no-cache' 
                });

                if (!response.ok) {
                    throw new Error(`Ошибка HTTP: ${response.status}`);
                }

                const html = await response.text();
                cartData = parseCartData(html);
                lastFetchTime = Date.now();
                renderCartPreview();
            } catch (error) {
                if (cartPreviewEl && cartPreviewEl.querySelector('.cart-hover-content')) {
                    cartPreviewEl.querySelector('.cart-hover-content').innerHTML = '<div class="cart-hover-empty">Ошибка загрузки</div>';
                }
            }
        } else {
            debugLog('Используем закешированные данные корзины');
            renderCartPreview();
        }
    }

    function parseCartData(html) {
        try {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const items = [];
            const itemElements = doc.querySelectorAll('.marketIndexItem');


            itemElements.forEach((item, index) => {
                try {
                    const titleEl = item.querySelector('.marketIndexItem--Title');
                    const priceEl = item.querySelector('.marketIndexItem--Price .Value');
                    const sellerEl = item.querySelector('.username');

                    if (titleEl && priceEl) {
                        items.push({
                            title: titleEl.textContent.trim(),
                            price: priceEl.textContent.trim(),
                            seller: sellerEl ? sellerEl.textContent.trim() : 'Продавец'
                        });
                    }
                } catch (e) {
                    errorLog('Ошибка при парсинге товара:', e);
                }
            });

            if (items.length === 0) {
                try {
                    const alternativeItems = doc.querySelectorAll('.structItem--product, .cart-item, [class*="cart-item"]');
                    debugLog('Альтернативный поиск товаров:', alternativeItems.length);

                    alternativeItems.forEach((item, index) => {
                        try {
                            const titleEl = item.querySelector('[class*="title"], .item-title, h3, h4, .structItem-title');
                            const priceEl = item.querySelector('[class*="price"], .product-price, .price, .cost');
                            const sellerEl = item.querySelector('.username, .seller, [class*="seller"]');

                            if (titleEl && priceEl) {
                                items.push({
                                    title: titleEl.textContent.trim(),
                                    price: priceEl.textContent.trim().replace(/[^\d.,]/g, ''),
                                    seller: sellerEl ? sellerEl.textContent.trim() : 'Продавец'
                                });
                            }
                        } catch (e) {
                            errorLog('Ошибка при парсинге альтернативного товара:', e);
                        }
                    });
                } catch (e) {
                    errorLog('Ошибка при альтернативном парсинге товаров:', e);
                }
            }

            let totalCount = '0';
            try {
                const countSelectors = [
                    '.marketCart-subtitle span',
                    '.cart-count',
                    '[class*="cart-count"]',
                    '.badge',
                    '.counter'
                ];

                for (const selector of countSelectors) {
                    const countEl = doc.querySelector(selector);
                    if (countEl) {
                        totalCount = countEl.textContent.trim();
                        break;
                    }
                }

                if (totalCount === '0' && items.length > 0) {
                    totalCount = items.length.toString();
                }
            } catch (e) {
                errorLog('Ошибка при парсинге количества:', e);
                if (items.length > 0) {
                    totalCount = items.length.toString();
                }
            }

            let totalPrice = '0 ₽';
            try {
                const priceSelectors = [
                    '.marketCart-title-green',
                    '.cart-total',
                    '[class*="cart-total"]',
                    '.total-price',
                    '.price.total'
                ];

                for (const selector of priceSelectors) {
                    const priceEl = doc.querySelector(selector);
                    if (priceEl) {
                        totalPrice = priceEl.textContent.trim();
                        break;
                    }
                }

                if ((totalPrice === '0 ₽' || totalPrice === '0₽') && items.length > 0) {
                    let sum = 0;
                    items.forEach(item => {
                        const price = parseFloat(item.price.replace(/[^\d.,]/g, '').replace(',', '.'));
                        if (!isNaN(price)) {
                            sum += price;
                        }
                    });

                    if (sum > 0) {
                        totalPrice = sum + ' ₽';
                    }
                }

                if (!totalPrice.includes('₽')) {
                    totalPrice += ' ₽';
                }

                if (totalPrice.includes('₽') && !totalPrice.includes(' ₽')) {
                    totalPrice = totalPrice.replace('₽', ' ₽');
                }
            } catch (e) {
                errorLog('Ошибка при парсинге цены:', e);
            }

            return {
                items: items,
                count: totalCount,
                totalPrice: totalPrice
            };
        } catch (error) {
            errorLog('Ошибка при парсинге данных корзины:', error);
            return {
                items: [],
                count: '0',
                totalPrice: '0 ₽'
            };
        }
    }

    function renderCartPreview() {
        if (!cartPreviewEl || !cartData) {
            errorLog('Невозможно отрендерить предпросмотр: отсутствуют необходимые данные');
            return;
        }

        const contentEl = cartPreviewEl.querySelector('.cart-hover-content');
        if (!contentEl) {
            errorLog('Не найден элемент содержимого');
            return;
        }

        if (!cartData.items || cartData.items.length === 0) {
            contentEl.innerHTML = '<div class="cart-hover-empty">Ваша корзина пуста</div>';
            return;
        }

        let html = `
            <div class="cart-hover-header">
                <div class="cart-hover-title">Корзина</div>
                <div class="cart-hover-count">${cartData.count}</div>
            </div>
            <div class="cart-hover-items">
        `;

        cartData.items.forEach(item => {
            let price = item.price || '0';

            if (!price.includes('₽')) {
                price += ' ₽';
            }

            if (price.includes('₽') && !price.includes(' ₽')) {
                price = price.replace('₽', ' ₽');
            }

            html += `
                <div class="cart-hover-item">
                    <div class="cart-hover-item-title">${item.title || 'Товар'}</div>
                    <div class="cart-hover-item-details">
                        <span class="cart-hover-item-seller">${item.seller || 'Продавец'}</span>
                        <span class="cart-hover-item-price">${price}</span>
                    </div>
                </div>
            `;
        });

        let formattedTotal = cartData.totalPrice || '0 ₽';

        if (!formattedTotal.includes('₽')) {
            formattedTotal += ' ₽';
        }

        if (formattedTotal.includes('₽') && !formattedTotal.includes(' ₽')) {
            formattedTotal = formattedTotal.replace('₽', ' ₽');
        }

        html += `
            </div>
            <div class="cart-hover-footer">
                <div class="cart-hover-total">Итого: <span class="cart-hover-total-price">${formattedTotal}</span></div>
            </div>
            <div class="cart-hover-buttons">
                <a href="https://lzt.market/mass-buy/cart" class="cart-hover-button cart-hover-button-mass">Массовая покупка</a>
                <a href="https://lzt.market/user/cart" class="cart-hover-button cart-hover-button-cart">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="9" cy="21" r="1"></circle>
                        <circle cx="20" cy="21" r="1"></circle>
                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
                    </svg>
                </a>
            </div>
        `;

        contentEl.innerHTML = html;
        debugLog('Предпросмотр корзины отрендерен');

        cartPreviewEl.style.display = 'block';
        cartPreviewEl.style.visibility = 'visible';
    }


    function checkPageReady() {
        try {
            if (document.body) {
                const cartElement = findCartElement();

                if (cartElement) {
                    debugLog('Страница загружена, корзина найдена, запускаем скрипт');
                    initScript();
                } else {
                    debugLog('Страница загружена, но корзина не найдена. Ожидаем...');
                    setTimeout(checkPageReady, 1000);
                }
            } else {
                debugLog('Страница еще не полностью загружена, ожидаем...');
                setTimeout(checkPageReady, 500);
            }
        } catch (error) {
            errorLog('Ошибка при проверке загрузки страницы:', error);
            setTimeout(checkPageReady, 1000);
        }
    }

    function initScript() {
        try {
            debugLog('Запуск скрипта предпросмотра корзины...');
            // Проверяем, есть ли уже элемент предпросмотра
            if (document.querySelector('.cart-hover-preview') || isInitialized) {
                debugLog('Элемент предпросмотра уже существует');
                return;
            }

            initCartPreview();
        } catch (error) {
            errorLog('Ошибка при инициализации скрипта:', error);
        }
    }

    setTimeout(() => {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => setTimeout(checkPageReady, 500));
        } else {
            checkPageReady();
        }
    }, 500);

    const observer = new MutationObserver((mutations) => {
        if (!isInitialized) {
            const cartElement = findCartElement();
            if (cartElement) {
                debugLog('DOM изменился, корзина найдена, инициализируем предпросмотр');
                initScript();
            }
        }
    });

    try {
        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: true });
            debugLog('MutationObserver запущен');
        } else {
            document.addEventListener('DOMContentLoaded', () => {
                observer.observe(document.body, { childList: true, subtree: true });
                debugLog('MutationObserver запущен после загрузки DOM');
            });
        }
    } catch (error) {
        errorLog('Ошибка при установке MutationObserver:', error);
        setInterval(() => {
            if (!isInitialized) {
                checkPageReady();
            }
        }, 3000);
    }

})();