您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Convert nodejs_anywhere file directory into thumbnail view with image/video preview, fullscreen viewer, and keyboard navigation.
// ==UserScript== // @name nodejsAnywhereBetterPage // @namespace http://leizingyiu.net/ // @version 20250305 // @description Convert nodejs_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== const originHtml = document.getElementsByTagName('html')[0].outerHTML; function globalKeyDown(event) { console.log(event); if (event.ctrlKey && event.altKey && event.code === 'KeyS') { const content = originHtml.replace('</html>', '') + `<script>(${String(nodejsAnywhereBetterPage)})()</script><\/html>`; downloadThisHTML(content); } } function downloadThisHTML(content) { const blob = new Blob([content], { type: 'text/html' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'index.html'; link.click(); URL.revokeObjectURL(link.href); } function nodejsAnywhereBetterPage() { if (document.body.hasAttribute('yiu_nodejsAnywhereBetterPage')) { return; } '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; } let backgroundBrightness = 255; let accumulatedDelta = 0; overlay.addEventListener('wheel', (e) => { e.preventDefault(); if (e.target === overlay) { accumulatedDelta += e.deltaY * 0.02; backgroundBrightness = Math.floor( 127.5 + 127.5 * Math.sin(accumulatedDelta * 0.1) ); overlay.style.backgroundColor = `rgba(${backgroundBrightness}, ${backgroundBrightness}, ${backgroundBrightness}, 1)`; } }); overlay.appendChild(content); const fileNameP = document.createElement('p'); let _fileName = fileList[currentIndex].url.split('/'); fileNameP.classList.add('fileNameP'); fileNameP.textContent = decodeURIComponent(_fileName[_fileName.length - 1]); overlay.appendChild(fileNameP); const closeButton = document.createElement('div'); closeButton.textContent = '×'; closeButton.classList.add('close-button'); closeButton.addEventListener('click', () => overlay.remove()); overlay.appendChild(closeButton); const downloadBtn = document.createElement('div'); downloadBtn.textContent = '⬇️'; downloadBtn.classList.add('download-button'); overlay.appendChild(downloadBtn); // 绑定点击事件 downloadBtn.addEventListener('click', function () { // 指定要下载的文件链接 const fileUrl = fileList[currentIndex].url; // 替换为实际文件链接 let pathGroup = fileUrl.split('/'); const fileName = decodeURIComponent(pathGroup[pathGroup.length - 1]); // 下载后的文件名 const a = document.createElement('a'); a.href = fileUrl; a.download = fileName; // 设置下载的文件名 a.style.display = 'none'; // 隐藏 <a> 标签 // 将 <a> 标签添加到文档中 document.body.appendChild(a); // 触发点击事件以开始下载 a.click(); // 移除 <a> 标签 document.body.removeChild(a); }); 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 debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); // 清除之前的定时器 timer = setTimeout(() => func.apply(this, args), delay); // 设置新的定时器 }; } function toggleBodyClass(cls, t) { const body = document.body; body.classList.add(cls); setTimeout(() => { body.classList.remove(cls); }, t); } // 使用防抖包装 toggleBodyClass 函数 const debouncedToggleBodyClass = debounce(() => { toggleBodyClass('hiliArrow', 3000) }, 500); function debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => func.apply(this, args), delay); }; } function addBodyClass(cls) { const body = document.body; if (!body.classList.contains(cls)) { body.classList.add(cls); } } function removeBodyClass(cls) { const body = document.body; body.classList.remove(cls); } function createDebouncedRemoveBodyClass(cls, delay) { return debounce(() => removeBodyClass(cls), delay); } function handleKeyDown(event) { if (event.key === 'ArrowLeft') { showPrevious(); const className = 'hiliArrowLeft'; addBodyClass(className); const debouncedRemoveBodyClass = createDebouncedRemoveBodyClass(className, 500); debouncedRemoveBodyClass(); } else if (event.key === 'ArrowRight') { showNext(); const className = 'hiliArrowRight'; addBodyClass(className); const debouncedRemoveBodyClass = createDebouncedRemoveBodyClass(className, 500); debouncedRemoveBodyClass(); } else if (event.key === 'Escape' || event.keyCode === 27) { // 检测 Esc 键 const overlay = document.querySelector('.fullscreen-overlay'); if (overlay) { overlay.remove(); } } } document.addEventListener('keydown', typeof globalKeyDown != 'undefined' ? globalKeyDown : () => { }); // 显示上一张 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; } let _fileName = newUrl.split('/'); const fileNameP = overlay.querySelector('.fileNameP'); fileNameP.textContent = decodeURIComponent(_fileName[_fileName.length - 1]) } // 样式化 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; display: flex; flex-direction: column; justify-content: space-between; } .thumbnail-wrapper img, .thumbnail-wrapper video { width: 100%; height: auto; flex-grow: 1; object-fit: contain; } .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-overlay:after { content: '>'; right: 2em; } .fullscreen-overlay:before { content: "<"; left: 2em; } .fullscreen-overlay:before, .fullscreen-overlay:after { position: absolute; top: 50%; opacity: 0.2; transform: translate(0, -50%); font-size: 2em; transition:opacity 0.3s ease; } .hiliArrowLeft .fullscreen-overlay:before, .hiliArrowRight .fullscreen-overlay:after { opacity: 1; } .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; } .download-button { position: absolute; bottom: 10px; right: 20px; font-size: 30px; color: #fff; cursor: pointer; 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{ font-size:1.5em; 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; } .fileNameP{ position:absolute; bottom:2em; left:50%; transform:translate(-50%,0); mix-blend-mode: difference; user-select: none; } /* 针对 Webkit 内核浏览器(如 Chrome、Edge、Safari) */ ::-webkit-scrollbar { width: 4px; /* 水平滚动条的高度 */ height: 4px; /* 垂直滚动条的宽度 */ } ::-webkit-scrollbar-track { background: transparent; /* 滚动条轨道背景 */ } ::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.3); /* 滚动条滑块颜色 */ border-radius: 2px; /* 滚动条滑块圆角 */ } ::-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(); } document.body.setAttribute('yiu_nodejsAnywhereBetterPage', true); } window.onload = () => { nodejsAnywhereBetterPage(); }; nodejsAnywhereBetterPage();