nodejsAnywhereBetterPage

Convert anywhere file directory into thumbnail view with image/video preview, fullscreen viewer, and keyboard navigation.

当前为 2025-02-25 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         nodejsAnywhereBetterPage
// @namespace    http://leizingyiu.net/
// @version      20250225
// @description  Convert anywhere file directory into thumbnail view with image/video preview, fullscreen viewer, and keyboard navigation.
// @author       leizingyiu
// @match        http://*.*:8000/*
// @match        https://*.*:8001/*
// @license     GNU AGPLv3 
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let currentIndex = 0;  
    let fileList = [];  

     function isTargetPage() {
        const urlPath = window.location.pathname.toLowerCase();
        const isAnyWhereFileView = document.querySelector('#files') !== null;
        return isAnyWhereFileView;
    }

     function setupLazyGifLoading() {
        const observer = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    const gifSrc = img.dataset.gifSrc;

                    if (gifSrc) {
                        img.src = gifSrc;
                        img.classList.remove('gif-lazy');
                        img.classList.add('gif-loaded');
                        observer.unobserve(img);
                    }
                } else {
                    const img = entry.target;
                    if (img.classList.contains('gif-loaded')) {
                        img.src = '';
                        img.classList.remove('gif-loaded');
                        img.classList.add('gif-lazy');
                    }
                }
            });
        }, {
            rootMargin: '50px',
            threshold: 0.01
        });

        return observer;
    }

     function createThumbnailView() {
        const fileListElements = Array.from(document.querySelectorAll('#files li a'));
        fileList = fileListElements.map(file => ({
            url: file.href,
            name: file.querySelector('.name').textContent
        }));

        const container = document.createElement('div');
        container.classList.add('thumbnail-container');

        const lazyLoadObserver = setupLazyGifLoading();

        fileList.forEach((file, index) => {
            const { url, name } = file;

            const wrapper = document.createElement('div');
            wrapper.classList.add('thumbnail-wrapper');

          let hintTxt='双击预览'
            if (url.match(/\.(png|jpe?g|gif|webp)$/i)) {
                const img = document.createElement('img');
                img.alt = name;
                img.setAttribute('draggable', 'false');

                if (url.match(/\.gif$/i)) {
                    img.classList.add('gif-lazy');
                    img.dataset.gifSrc = url;
                    const gifLabel = document.createElement('div');
                    gifLabel.textContent = 'GIF';
                    gifLabel.classList.add('gif-label');
                    wrapper.appendChild(gifLabel);
                } else {
                    img.src = url;
                }

                wrapper.appendChild(img);
                wrapper.addEventListener('dblclick', () => openFullscreen(index));

                if (url.match(/\.gif$/i)) {
                    lazyLoadObserver.observe(img);
                }

            } else if (url.match(/\.(mp4|webm|ogg)$/i)) {
                const video = document.createElement('video');
                video.src = url;
                video.classList.add('video');
                video.muted = true;
                video.playsInline = true;
                video.preload = 'metadata';
                video.onloadedmetadata = () => video.currentTime = 0.1;
                wrapper.appendChild(video);

                wrapper.addEventListener('dblclick', () => openFullscreen(index));

            } else {
               hintTxt='双击下载';

                wrapper.addEventListener('dblclick', () => {
                    window.location.href = url;
                });


            }
                 const downloadTip = document.createElement('div');
                downloadTip.classList.add('downloadTip');
                downloadTip.textContent = hintTxt;
                downloadTip.classList.add('download_tip');  

                wrapper.appendChild(downloadTip);


            const caption = document.createElement('div');
            caption.textContent = name;
            caption.classList.add('thumbnail-caption');
            wrapper.appendChild(caption);

            container.appendChild(wrapper);
        });

        document.body.innerHTML = '';
        document.body.appendChild(container);
    }

    // 打开全屏预览
    function openFullscreen(index) {
        currentIndex = index;

        const overlay = document.createElement('div');
        overlay.classList.add('fullscreen-overlay');

        const content = document.createElement(fileList[currentIndex].url.match(/\.(mp4|webm|ogg)$/i) ? 'video' : 'img');
        content.src = fileList[currentIndex].url;
        content.classList.add('fullscreen-content');
        content.setAttribute('draggable', 'false');
        if (content.tagName === 'VIDEO') {
            content.controls = true;
            content.autoplay = true;
        }
        overlay.appendChild(content);

        const closeButton = document.createElement('div');
        closeButton.textContent = '×';
        closeButton.classList.add('close-button');
        closeButton.addEventListener('click', () => overlay.remove());
        overlay.appendChild(closeButton);

        let scale = 1;
        let isDragging = false;
        let startX, startY;

        content.addEventListener('wheel', (e) => {
            e.preventDefault();
            scale += e.deltaY > 0 ? -0.1 : 0.1;
            scale = Math.max(0.1, Math.min(scale, 5));
            content.style.transform = `scale(${scale})`;
        });

        content.addEventListener('mousedown', (e) => {
            isDragging = true;
            startX = e.clientX - content.offsetLeft;
            startY = e.clientY - content.offsetTop;
            content.style.cursor = 'grabbing';
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const x = e.clientX - startX;
                const y = e.clientY - startY;
                content.style.left = `${x}px`;
                content.style.top = `${y}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            content.style.cursor = 'grab';
        });

        document.body.appendChild(overlay);


         document.addEventListener('keydown', handleKeyDown);

         overlay.addEventListener('remove', () => {
            document.removeEventListener('keydown', handleKeyDown);
        });

      overlay.addEventListener('dblclick',()=>{
        const overlay = document.querySelector('.fullscreen-overlay');
            if (overlay) {
                overlay.remove();
            }
      })
    }




     function handleKeyDown(event) {
        if (event.key === 'ArrowLeft') {
            showPrevious();
        } else if (event.key === 'ArrowRight') {
            showNext();
        }else if (event.key === 'Escape' || event.keyCode === 27) { // 检测 Esc 键
            const overlay = document.querySelector('.fullscreen-overlay');
            if (overlay) {
                overlay.remove();  
            }
        }
    }

    // 显示上一张
    function showPrevious() {
        if (currentIndex > 0) {
            currentIndex--;
            updateFullscreenContent();
        }
    }

    // 显示下一张
    function showNext() {
        if (currentIndex < fileList.length - 1) {
            currentIndex++;
            updateFullscreenContent();
        }
    }

    // 更新全屏内容
    function updateFullscreenContent() {
        const overlay = document.querySelector('.fullscreen-overlay');
        if (!overlay) return;

        const content = overlay.querySelector('.fullscreen-content');
        const newUrl = fileList[currentIndex].url;

        if (content.tagName === 'VIDEO' && !newUrl.match(/\.(mp4|webm|ogg)$/i)) {
            // 如果当前是视频但新内容是图片,则替换为图片
            const img = document.createElement('img');
            img.src = newUrl;
            img.classList.add('fullscreen-content');
            img.setAttribute('draggable', 'false');
            overlay.replaceChild(img, content);
        } else if (content.tagName === 'IMG' && newUrl.match(/\.(mp4|webm|ogg)$/i)) {
            // 如果当前是图片但新内容是视频,则替换为视频
            const video = document.createElement('video');
            video.src = newUrl;
            video.classList.add('fullscreen-content');
            video.controls = true;
            video.autoplay = true;
            overlay.replaceChild(video, content);
        } else {
            // 同类型内容更新
            content.src = newUrl;
        }
    }

    // 样式化
    function styling() {
        const styleTag = document.createElement('style');
        styleTag.textContent = `
        .thumbnail-container {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
            gap: 10px;
            padding: 10px;
        }
        .thumbnail-wrapper {
            position: relative;
            overflow: hidden;
            border: 1px solid #ddd;
            border-radius: 5px;
            text-align: center;
            cursor: pointer;
            max-height: 40vh;
            overflow-y: scroll;
            padding: 0 !important;
            margin: 0 !important;
        }
        .thumbnail-wrapper img,
        .thumbnail-wrapper video {
            width: 100%;
            height: auto;
        }
        .gif-label {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(0, 0, 0, 0.7);
            color: #fff;
            font-size: 12px;
            padding: 2px 5px;
            border-radius: 3px;
        }
        .thumbnail-caption {
            font-size: 12px;
            padding: 5px;
            position: sticky;
            bottom: 0;
            width: 96%;
            overflow: hidden;
            word-break: break-all;
            background: #ffffffaa;
        }
        .fullscreen-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(255, 255, 255, 1);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 999999999;
        }
        .fullscreen-content {
            max-width: 90%;
            max-height: 90%;
            position: absolute;
            user-select: none;
        }
        .close-button {
            position: absolute;
            top: 10px;
            right: 20px;
            font-size: 30px;
            color: #fff;
            cursor: pointer;
            mix-blend-mode: difference;
            user-select: none;
        }
        .fullscreen-content.video {
            pointer-events: none;
        }
        .gif-lazy {
            opacity: 0;
            transition: opacity 0.3s ease-in-out;
        }
        .gif-loaded {
            opacity: 1;
        }

.download_tip{
opacity:0;

transition: opacity 0.2s ease;
    position: absolute;
    width: 100%;
    text-align-last: center;
    top: 50%;
    transform: translate(0, -50%);
    z-index: 9999;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: space-around;

        background: #ffffff99;
    backdrop-filter: blur(1px);

}
.download_tip:hover{
opacity:1;
}


        /* 针对 Webkit 内核浏览器(如 Chrome、Edge、Safari) */
::-webkit-scrollbar {
    width: 6px; /* 水平滚动条的高度 */
    height: 6px; /* 垂直滚动条的宽度 */
}

::-webkit-scrollbar-track {
    background: transparent; /* 滚动条轨道背景 */
}

::-webkit-scrollbar-thumb {
    background-color: rgba(0, 0, 0, 0.3); /* 滚动条滑块颜色 */
    border-radius: 3px; /* 滚动条滑块圆角 */
}

::-webkit-scrollbar-thumb:hover {
    background-color: rgba(0, 0, 0, 0.5); /* 滑块悬停时的颜色 */
}

/* 针对 Firefox 浏览器 */
* {
    scrollbar-width: thin; /* 设置滚动条为细 */
    scrollbar-color: rgba(0, 0, 0, 0.3) transparent; /* 滑块颜色和轨道颜色 */
}


        `;
        document.head.appendChild(styleTag);
    }

    // 主逻辑
    if (!isTargetPage()) {
        console.log('Not a target page, exiting...');
        return;
    } else {
        styling();
        createThumbnailView();
    }
})();