nodejsAnywhereBetterPage

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

目前為 2025-02-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
    }
})();