码桶

发现社区成员的开源项目

飞扬 / LB_Down 公开
main
LB_Down/assets/js/main.js
main.js28.7 KB


(function () {
    'use strict';



    var TypechoComment = {
        _originParent: null,

        dom: function (id) {
            return document.getElementById(id);
        },



        getRespond: function () {
            return document.querySelector('[id^="respond-"]');
        },

        reply: function (cid, coid, el) {
            var comment = this.dom(cid);          

            var respond = this.getRespond();       

            if (!comment || !respond) return false;



            if (!this._originParent) {
                this._originParent = respond.parentNode;
            }



            comment.appendChild(respond);



            var form = respond.querySelector('form');
            var input = this.dom('comment-parent');
            if (!input) {
                input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'parent';
                input.id = 'comment-parent';
                if (form) {
                    form.appendChild(input);
                } else {
                    respond.appendChild(input);
                }
            }
            input.value = coid;



            var cancel = this.dom('cancel-comment-reply-link');
            if (cancel) cancel.style.display = '';



            respond.scrollIntoView({ behavior: 'smooth', block: 'center' });
            return false;
        },

        cancelReply: function () {
            var respond = this.getRespond();
            var wrap = this.dom('comment-form-wrap');
            if (respond && this._originParent) {
                this._originParent.appendChild(respond);
            } else if (respond && wrap) {
                wrap.appendChild(respond);
            }
            var input = this.dom('comment-parent');
            if (input) input.value = 0;
            var cancel = this.dom('cancel-comment-reply-link');
            if (cancel) cancel.style.display = 'none';
            return false;
        }
    };
    window.TypechoComment = TypechoComment;



    var lbPlatform = '';
    if (navigator.userAgentData && typeof navigator.userAgentData.getHighEntropyValues === 'function') {
        try {
            navigator.userAgentData.getHighEntropyValues(['platformVersion'])
                .then(function (data) {
                    var pv = parseFloat(data.platformVersion);
                    if (data.platform === 'Windows') {
                        if (!isNaN(pv)) {
                            lbPlatform = pv >= 13 ? 'Win11' : 'Win10';
                        }
                    }
                    var input = document.getElementById('lb-platform');
                    if (input) input.value = lbPlatform;
                })
                .catch(function () {});
        } catch (e) {}
    }



    const typechoRoot = (function () {
        const scripts = document.getElementsByTagName('script');
        for (let i = 0; i < scripts.length; i++) {
            const src = scripts[i].src;
            if (src && src.indexOf('lb_down') > -1) {
                const match = src.match(/^(.*?)\/usr/);
                if (match) return match[1];
            }
        }
        return window.location.origin;
    })();



    function showToast(message, type = '') {
        let toast = document.querySelector('.lb-toast');
        if (!toast) {
            toast = document.createElement('div');
            toast.className = 'lb-toast';
            document.body.appendChild(toast);
        }
        toast.textContent = message;
        toast.className = 'lb-toast ' + type;
        requestAnimationFrame(() => toast.classList.add('visible'));
        setTimeout(() => toast.classList.remove('visible'), 2500);
    }



    function ajaxPost(url, data) {
        return fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams(data).toString()
        }).then(res => res.json());
    }

    document.addEventListener('DOMContentLoaded', function () {



        const menuToggle = document.querySelector('.menu-toggle');
        const navMenu = document.querySelector('.nav-menu');
        const menuOverlay = document.getElementById('menu-overlay');
        function lbCloseMenu() {
            if (navMenu) navMenu.classList.remove('open');
            if (menuOverlay) menuOverlay.classList.remove('open');
            if (menuToggle) menuToggle.setAttribute('aria-expanded', 'false');
        }
        if (menuToggle && navMenu) {
            menuToggle.addEventListener('click', function () {
                const isOpen = navMenu.classList.toggle('open');
                if (menuOverlay) menuOverlay.classList.toggle('open', isOpen);
                menuToggle.setAttribute('aria-expanded', isOpen);
            });
            if (menuOverlay) {
                menuOverlay.addEventListener('click', lbCloseMenu);
            }
            navMenu.querySelectorAll('a').forEach(function (a) {
                a.addEventListener('click', lbCloseMenu);
            });
        }



        const backToTop = document.getElementById('back-to-top');
        if (backToTop) {
            window.addEventListener('scroll', function () {
                if (window.scrollY > 300) {
                    backToTop.classList.add('visible');
                } else {
                    backToTop.classList.remove('visible');
                }
            }, { passive: true });

            backToTop.addEventListener('click', function () {
                window.scrollTo({ top: 0, behavior: 'smooth' });
            });
        }



        var noticeModal = document.getElementById('notice-modal');
        if (noticeModal) {
            var noticeClose = document.getElementById('notice-modal-close');
            var noticeBtn = document.getElementById('notice-modal-btn');
            function lbCloseNotice() { noticeModal.classList.remove('open'); }


            setTimeout(function () { noticeModal.classList.add('open'); }, 600);
            if (noticeClose) noticeClose.addEventListener('click', lbCloseNotice);
            if (noticeBtn) noticeBtn.addEventListener('click', lbCloseNotice);
            noticeModal.addEventListener('click', function (e) {
                if (e.target === noticeModal) lbCloseNotice();
            });
            document.addEventListener('keydown', function (e) {
                if (e.key === 'Escape') lbCloseNotice();
            });
        }



        var themeToggle = document.getElementById('theme-toggle');
        if (themeToggle) {
            themeToggle.addEventListener('click', function () {
                var root = document.documentElement;
                var isDark = root.getAttribute('data-theme') === 'dark';
                if (isDark) {
                    root.removeAttribute('data-theme');
                } else {
                    root.setAttribute('data-theme', 'dark');
                }
                try { localStorage.setItem('lb_theme', isDark ? 'light' : 'dark'); } catch (e) {}
            });
        }



        document.querySelectorAll('.float-back-top').forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.preventDefault();
                window.scrollTo({ top: 0, behavior: 'smooth' });
            });
        });



        var searchToggle = document.getElementById('search-toggle');
        var searchModal = document.getElementById('search-modal');
        if (searchToggle && searchModal) {
            searchToggle.addEventListener('click', function () {
                searchModal.classList.add('open');
                var input = searchModal.querySelector('input');
                if (input) input.focus();
            });
            searchModal.addEventListener('click', function (e) {
                if (e.target === searchModal) searchModal.classList.remove('open');
            });
            document.addEventListener('keydown', function (e) {
                if (e.key === 'Escape') searchModal.classList.remove('open');
            });
        }



        if (searchModal) {
            var liveInput = searchModal.querySelector('input');
            var liveBox = searchModal.querySelector('.search-modal-box');
            var liveResults = document.createElement('div');
            liveResults.className = 'search-live-results';
            liveBox.appendChild(liveResults);

            function lbEscapeRegExp(str) {
                return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            }

            var liveTimer = null;
            liveInput.addEventListener('input', function () {
                clearTimeout(liveTimer);
                var kw = liveInput.value.trim();
                if (!kw) {
                    liveResults.innerHTML = '';
                    liveResults.classList.remove('show');
                    return;
                }
                liveTimer = setTimeout(function () {
                    fetch((window.LB_SITE_URL || '') + '?lb_action=lb_live_search&kw=' + encodeURIComponent(kw))
                        .then(function (r) { return r.json(); })
                        .then(function (data) {
                            if (!data.items || data.items.length === 0) {
                                liveResults.innerHTML = '<div class="search-live-empty">无匹配资源</div>';
                            } else {
                                var re = new RegExp(lbEscapeRegExp(kw), 'gi');
                                liveResults.innerHTML = data.items.map(function (item) {
                                    var title = item.title.replace(re, function (m) {
                                        return '<mark>' + m + '</mark>';
                                    });
                                    return '<a class="search-live-item" href="' + item.url + '" target="_blank" rel="noopener">' + title + '</a>';
                                }).join('');
                            }
                            liveResults.classList.add('show');
                        })
                        .catch(function () {
                            liveResults.innerHTML = '<div class="search-live-empty">搜索失败</div>';
                            liveResults.classList.add('show');
                        });
                }, 300);
            });



            var closeLiveSearch = function () {
                liveResults.innerHTML = '';
                liveResults.classList.remove('show');
            };
            searchModal.addEventListener('click', function (e) {
                if (e.target === searchModal) closeLiveSearch();
            });
            document.addEventListener('keydown', function (e) {
                if (e.key === 'Escape') closeLiveSearch();
            });
        }



        const tabBtns = document.querySelectorAll('.tab-btn');
        const tabPanels = document.querySelectorAll('.tab-panel');
        if (tabBtns.length > 0) {
            tabBtns.forEach(function (btn) {
                btn.addEventListener('click', function () {
                    const tabName = btn.dataset.tab;
                    tabBtns.forEach(function (b) { b.classList.remove('active'); });
                    tabPanels.forEach(function (p) { p.classList.remove('active'); });
                    btn.classList.add('active');
                    const panel = document.getElementById('tab-' + tabName);
                    if (panel) panel.classList.add('active');
                });
            });
        }



        window.lb_copy_text = function (elementId, btn) {
            const el = document.getElementById(elementId);
            if (!el) return;
            const text = el.textContent || el.innerText;

            if (navigator.clipboard) {
                navigator.clipboard.writeText(text).then(function () {
                    handleCopySuccess(btn);
                });
            } else {
                const textarea = document.createElement('textarea');
                textarea.value = text;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();
                try {
                    document.execCommand('copy');
                    handleCopySuccess(btn);
                } finally {
                    document.body.removeChild(textarea);
                }
            }
        };

        function handleCopySuccess(btn) {
            const original = btn.textContent;
            btn.textContent = '已复制';
            btn.classList.add('copied');
            showToast('复制成功', 'success');
            setTimeout(function () {
                btn.textContent = original;
                btn.classList.remove('copied');
            }, 2000);
        }



        window.lb_track_download = function (linkEl) {
            const cid = linkEl.dataset.cid;
            if (!cid) return true;

            const actionUrl = typechoRoot + '/?lb_action=lb_download';
            ajaxPost(actionUrl, { cid: cid }).then(function (res) {
                if (res && res.code === 0) {
                    const countEl = document.getElementById('download-count-display');
                    if (countEl) countEl.textContent = res.count;
                }
            }).catch(function () {});

            return true;
        };



        const ratingContainer = document.getElementById('resource-rating');
        if (ratingContainer) {
            const cid = ratingContainer.dataset.cid;
            const starBtns = ratingContainer.querySelectorAll('.star-btn');
            const scoreEl = document.getElementById('rating-score');
            const countEl = document.getElementById('rating-count');
            let isSubmitting = false;



            starBtns.forEach(function (btn, index) {
                btn.addEventListener('mouseenter', function () {
                    starBtns.forEach(function (b, i) {
                        const svg = b.querySelector('svg');
                        if (i <= index) {
                            svg.setAttribute('fill', 'currentColor');
                        }
                    });
                });

                btn.addEventListener('mouseleave', function () {
                    const currentScore = parseFloat(scoreEl.textContent) || 0;
                    starBtns.forEach(function (b, i) {
                        const svg = b.querySelector('svg');
                        if (i < Math.round(currentScore)) {
                            svg.setAttribute('fill', 'currentColor');
                        } else {
                            svg.setAttribute('fill', 'none');
                        }
                    });
                });

                btn.addEventListener('click', function () {
                    if (isSubmitting) return;
                    const rating = parseInt(btn.dataset.rating, 10);
                    if (!rating || rating < 1 || rating > 5) return;

                    isSubmitting = true;
                    const actionUrl = typechoRoot + '/?lb_action=lb_rating';

                    ajaxPost(actionUrl, { cid: cid, rating: rating }).then(function (res) {
                        if (res.code === 0) {
                            scoreEl.textContent = res.avg;
                            countEl.textContent = '(' + res.count + ' 人评分)';
                            showToast('评分成功,感谢您的反馈!', 'success');



                            starBtns.forEach(function (b, i) {
                                const svg = b.querySelector('svg');
                                if (i < Math.round(res.avg)) {
                                    svg.setAttribute('fill', 'currentColor');
                                    b.setAttribute('aria-checked', 'true');
                                } else {
                                    svg.setAttribute('fill', 'none');
                                    b.setAttribute('aria-checked', 'false');
                                }
                            });
                        } else if (res.code === 2) {
                            showToast(res.msg || '您今天已经评分过了', 'error');
                        } else {
                            showToast('评分失败,请稍后重试', 'error');
                        }
                    }).catch(function () {
                        showToast('网络错误,请稍后重试', 'error');
                    }).finally(function () {
                        isSubmitting = false;
                    });
                });
            });
        }



        const filterLinks = document.querySelectorAll('.filter-bar .filter-link[data-filter]');
        filterLinks.forEach(function (link) {
            link.addEventListener('click', function (e) {
                e.preventDefault();
                const group = document.querySelectorAll('.filter-link[data-filter="' + link.dataset.filter + '"]');
                group.forEach(function (l) { l.classList.remove('active'); });
                link.classList.add('active');
                showToast('筛选功能可通过分类/标签实现', '');
            });
        });

        const sortLinks = document.querySelectorAll('.filter-link[data-sort]');
        sortLinks.forEach(function (link) {
            link.addEventListener('click', function (e) {
                e.preventDefault();
                sortLinks.forEach(function (l) { l.classList.remove('active'); });
                link.classList.add('active');
            });
        });



        var recSwiper = document.getElementById('recSwiper');
        if (recSwiper) {
            var swiperWrapper = recSwiper.querySelector('.swiper-wrapper');
            var slides = recSwiper.querySelectorAll('.swiper-slide');
            var recItems = document.querySelectorAll('.rec-item');
            var prevBtn = recSwiper.querySelector('.rec-prev');
            var nextBtn = recSwiper.querySelector('.rec-next');
            var recCarousel = document.querySelector('.rec-carousel');
            var total = slides.length;
            var currentIdx = 0;
            var d = 0;              

            var linerTimer = null;   

            function goToSlide(idx) {
                if (idx < 0) idx = total - 1;
                if (idx >= total) idx = 0;
                currentIdx = idx;

                swiperWrapper.style.transform = 'translateX(-' + (idx * 100) + '%)';



                recItems.forEach(function (item, i) {
                    item.classList.toggle('active', i === idx);
                });



                if (recCarousel) {
                    var itemHeight = recItems[0]?.offsetHeight || 70;
                    var translateY = 0;
                    if (idx < 3) {
                        translateY = 0;
                    } else if (idx > total - 4) {
                        translateY = itemHeight * Math.max(0, total - 6);
                    } else {
                        translateY = itemHeight * (idx - 2);
                    }
                    recCarousel.style.transform = 'translateY(-' + translateY + 'px)';
                }
            }

            function nextSlide() { goToSlide(currentIdx + 1); }
            function prevSlide() { goToSlide(currentIdx - 1); }



            function startProgress(e) {
                stopProgress();
                d = 0;


                recItems.forEach(function (item) {
                    var liner = item.querySelector('.liner');
                    if (liner) liner.style.width = '0';
                });
                linerTimer = setInterval(function () {
                    d += 0.4;
                    var activeItem = recItems[e];
                    var liner = activeItem ? activeItem.querySelector('.liner') : null;
                    if (liner) liner.style.width = d + '%';
                    if (d > 100) {
                        clearInterval(linerTimer);
                        linerTimer = null;
                        d = 0;
                        var nextIdx = e + 1 >= total ? 0 : e + 1;
                        goToSlide(nextIdx);
                        startProgress(nextIdx);
                    }
                }, 20);
            }

            function stopProgress() {
                if (linerTimer) {
                    clearInterval(linerTimer);
                    linerTimer = null;
                }
            }



            prevBtn && prevBtn.addEventListener('click', function () {
                prevSlide();
                startProgress(currentIdx);
            });
            nextBtn && nextBtn.addEventListener('click', function () {
                nextSlide();
                startProgress(currentIdx);
            });



            recItems.forEach(function (item, idx) {
                item.addEventListener('click', function () {
                    var url = item.getAttribute('data-url');
                    if (url) window.location.href = url;
                });
            });



            recSwiper.addEventListener('mouseenter', stopProgress);
            recSwiper.addEventListener('mouseleave', function () {
                startProgress(currentIdx);
            });



            goToSlide(0);
            startProgress(0);
        }



        document.querySelectorAll('img[onerror]').forEach(function (img) {
            img.addEventListener('error', function () {
                this.onerror = null;
            });
        });



        document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
            anchor.addEventListener('click', function (e) {
                const targetId = this.getAttribute('href');
                if (targetId.length > 1) {
                    const target = document.querySelector(targetId);
                    if (target) {
                        e.preventDefault();
                        const headerHeight = document.querySelector('.site-header')?.offsetHeight || 0;
                        const top = target.getBoundingClientRect().top + window.scrollY - headerHeight - 10;
                        window.scrollTo({ top: top, behavior: 'smooth' });
                    }
                }
            });
        });



        function initTilt(card) {
            if (!card || card.getAttribute('data-tilt-init')) return;
            card.setAttribute('data-tilt-init', '1');

            var max = parseFloat(card.getAttribute('data-tilt-max')) || 25;
            var scale = parseFloat(card.getAttribute('data-tilt-scale')) || 1.08;
            var speed = parseFloat(card.getAttribute('data-tilt-speed')) || 300;
            var perspective = parseFloat(card.getAttribute('data-tilt-perspective')) || 500;



            var inner = card.querySelector('.grid-card-inner, .resource-card-inner') || card;

            function setTransform(rotateX, rotateY) {
                inner.style.transform =
                    'perspective(' + perspective + 'px) ' +
                    'rotateX(' + rotateX.toFixed(2) + 'deg) ' +
                    'rotateY(' + rotateY.toFixed(2) + 'deg) ' +
                    'scale3d(' + scale + ', ' + scale + ', ' + scale + ')';
            }



            function resetTransform() {
                inner.style.transform =
                    'perspective(' + perspective + 'px) ' +
                    'rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)';
            }

            var rafId = null;
            function handleMove(e) {
                if (rafId) return;
                rafId = requestAnimationFrame(function () {
                    rafId = null;
                    var rect = inner.getBoundingClientRect();
                    var x = e.clientX - rect.left;
                    var y = e.clientY - rect.top;
                    var centerX = rect.width / 2;
                    var centerY = rect.height / 2;

                    var rotateY = ((x - centerX) / centerX) * max;
                    var rotateX = ((y - centerY) / centerY) * -max;

                    inner.style.transition = 'transform 0.05s ease-out, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease';
                    setTransform(rotateX, rotateY);
                });
            }

            card.addEventListener('mouseenter', function (e) {
                inner.style.transition = 'transform 0.05s ease-out, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease';
                handleMove(e);
            });
            card.addEventListener('mousemove', handleMove);

            card.addEventListener('mouseleave', function () {
                if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
                inner.style.transition = 'transform ' + speed + 'ms ease, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease';
                resetTransform();
            });
        }

        document.querySelectorAll('[data-tilt]').forEach(function (card) {
            initTilt(card);
        });



        function filterNewestByCat(item) {
            var cards = document.querySelectorAll('.resource-grid .grid-card');
            if (!cards.length) return;   

            var catMid = item.getAttribute('data-cat-mid');
            var allItems = document.querySelectorAll('.newest-cat-item');

            allItems.forEach(function (i) { i.classList.remove('active'); });
            item.classList.add('active');



            var moreBtn = document.querySelector('.newest-more');
            if (moreBtn) {
                var catUrl = item.getAttribute('href');
                if (catUrl && catUrl !== 'javascript:;') {
                    moreBtn.href = catUrl;
                }
            }

            var visibleCount = 0;
            cards.forEach(function (card) {
                var cardCat = card.getAttribute('data-cat-mid');
                var visible = String(cardCat) === String(catMid);
                card.style.display = visible ? '' : 'none';
                if (visible) visibleCount++;
            });



            var emptyTip = document.querySelector('.cat-empty');
            if (emptyTip) {
                emptyTip.style.display = visibleCount === 0 ? '' : 'none';
            }
        }

        document.addEventListener('click', function (e) {
            var item = e.target.closest('.newest-cat-item');
            if (!item) return;
            e.preventDefault();
            filterNewestByCat(item);
        });



        var scrollMore = document.getElementById('scroll-more');
        if (scrollMore && 'IntersectionObserver' in window) {
            var loadUrl = scrollMore.getAttribute('data-url') || '';
            var totalPages = parseInt(scrollMore.getAttribute('data-pages')) || 1;
            var currentPage = parseInt(scrollMore.getAttribute('data-page')) || 1;
            var scrollLoading = false;

            function scrollAppend(html) {
                var container = document.querySelector('.resource-grid, .resource-list');
                if (!container || !html || !html.trim()) return 0;
                var tmp = document.createElement('div');
                tmp.innerHTML = html;
                var items = tmp.querySelectorAll('.grid-card, .resource-card');
                items.forEach(function (item) {
                    container.appendChild(item);
                    if (item.hasAttribute('data-tilt')) initTilt(item);
                });
                return items.length;
            }

            function scrollLoadMore() {
                if (scrollLoading) return;
                if (currentPage >= totalPages) {
                    scrollMore.textContent = '已加载全部';
                    return;
                }
                scrollLoading = true;
                scrollMore.textContent = '加载中…';
                var nextPage = currentPage + 1;
                fetch(loadUrl + '&page=' + nextPage)
                    .then(function (r) { return r.text(); })
                    .then(function (html) {
                        scrollLoading = false;
                        if (scrollAppend(html) > 0) {
                            currentPage = nextPage;
                            scrollMore.setAttribute('data-page', currentPage);
                            scrollMore.textContent = (currentPage >= totalPages) ? '已加载全部' : '加载更多…';
                        } else {
                            currentPage = totalPages;
                            scrollMore.textContent = '已加载全部';
                        }
                    })
                    .catch(function () {
                        scrollLoading = false;
                        scrollMore.textContent = '加载失败,点击重试';
                    });
            }

            scrollMore.addEventListener('click', scrollLoadMore);
            new IntersectionObserver(function (entries) {
                if (entries[0].isIntersecting) scrollLoadMore();
            }, { rootMargin: '200px' }).observe(scrollMore);
        }



        var defaultCat = document.querySelector('.newest-cat-item.active');
        if (defaultCat) filterNewestByCat(defaultCat);
    });

})();