- // ==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();