您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Telegraph.ph 拖拽传图,可以一次拖拽传多图,自动排序,自动切割分批次上传,并带有进度反馈、错误处理、拖放视觉反馈、缓存管理等功能。
// ==UserScript== // @name Telegra.ph 上传加强 // @namespace MOASE // @version 1.1 // @description Telegraph.ph 拖拽传图,可以一次拖拽传多图,自动排序,自动切割分批次上传,并带有进度反馈、错误处理、拖放视觉反馈、缓存管理等功能。 // @author MOASE // @match https://telegra.ph/* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const MAX_BATCH_SIZE = 5 * 1024 * 1024; // 5 MB let uploadCache = new Set(); // 缓存已上传文件 let errorCount = 0; // 错误计数 const errorLogs = []; // 错误日志 // 创建并显示错误弹窗 function showErrorPopup(message) { errorCount++; const timestamp = new Date().toLocaleTimeString(); const errorMessage = `${timestamp}: ${message}`; const errorPopup = document.createElement('div'); errorPopup.classList.add('error-popup'); errorPopup.innerHTML = `<span>${errorMessage}</span><button class="close-btn">关闭</button>`; errorPopup.style.position = 'fixed'; errorPopup.style.left = '10px'; errorPopup.style.bottom = `${10 + errorCount * 60}px`; // 底部对齐,每个弹窗间隔 60px // 添加动画效果 errorPopup.style.opacity = 0; errorPopup.style.transition = 'opacity 0.5s ease'; document.body.appendChild(errorPopup); setTimeout(() => errorPopup.style.opacity = 1, 10); // 关闭按钮事件 errorPopup.querySelector('.close-btn').addEventListener('click', () => { errorPopup.remove(); errorCount--; adjustErrorPopups(); }); // 添加错误日志 errorLogs.push(errorMessage); // 如果错误超过5个,提示下载 if (errorLogs.length >= 5) { if (confirm('错误信息过多,是否下载?')) { downloadErrorLogs(); } } } // 调整所有弹窗的位置 function adjustErrorPopups() { const popups = document.querySelectorAll('.error-popup'); popups.forEach((popup, index) => { popup.style.bottom = `${10 + (popups.length - 1 - index) * 60}px`; }); } // 下载错误日志 function downloadErrorLogs() { const blob = new Blob([errorLogs.join('\n')], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'error_logs.txt'; a.click(); URL.revokeObjectURL(url); } // 文件排序 function sortFiles(files) { return files.sort((a, b) => { const nameA = a.name.replace(/\D/g, '').padStart(10, '0'); const nameB = b.name.replace(/\D/g, '').padStart(10, '0'); return nameA.localeCompare(nameB) || a.name.localeCompare(b.name); }); } // 批次划分 function splitIntoBatches(files) { let batches = []; let currentBatch = []; let currentBatchSize = 0; for (const file of files) { if (uploadCache.has(file.name)) { continue; // 跳过已上传文件 } if (currentBatchSize + file.size > MAX_BATCH_SIZE) { batches.push(currentBatch); currentBatch = []; currentBatchSize = 0; } currentBatch.push(file); currentBatchSize += file.size; } if (currentBatch.length > 0) { batches.push(currentBatch); } return batches; } // 单个文件处理 function processFile(file) { return new Promise((resolve, reject) => { updatePhoto(file, function (e) { if (quill.fileSizeLimit && e.size > quill.fileSizeLimit) { quill.fileSizeLimitCallback && quill.fileSizeLimitCallback(); reject(new Error('文件大于5M')); return; } const reader = new FileReader(); reader.onload = function (event) { const result = getFigureValueByUrl(event.target.result); if (result) { const selection = quill.getSelection(true); quill.updateContents(new Delta().retain(selection.index)["delete"](selection.length).insert({ blockFigure: result }), Quill.sources.USER); uploadCache.add(file.name); // 添加到缓存 resolve(); } else { showErrorPopup("无效的文件格式"); reject(new Error('无效的文件格式')); } }; reader.readAsDataURL(e); }); }); } // 批次处理 async function processBatch(batch, batchIndex) { for (const [index, file] of batch.entries()) { try { await processFile(file); updateProgress(batchIndex, index + 1, batch.length); // 更新进度 } catch (error) { handleError(file, error); // 处理错误 } } } // 全部批次处理 async function processAllBatches(batches) { for (const [batchIndex, batch] of batches.entries()) { await processBatch(batch, batchIndex); } finalizeUpload(); // 上传完成 } // 显示进度条 function createProgressBar() { let progressBar = document.createElement('div'); progressBar.id = 'uploadProgressBar'; progressBar.style.position = 'fixed'; progressBar.style.bottom = '10px'; progressBar.style.left = '10px'; progressBar.style.width = '300px'; progressBar.style.height = '20px'; progressBar.style.backgroundColor = '#ccc'; progressBar.style.borderRadius = '5px'; progressBar.style.overflow = 'hidden'; progressBar.style.zIndex = '10000'; let progress = document.createElement('div'); progress.style.height = '100%'; progress.style.width = '0'; progress.style.backgroundColor = '#4caf50'; progressBar.appendChild(progress); document.body.appendChild(progressBar); return progress; } let progressBar = createProgressBar(); function updateProgress(batchIndex, fileIndex, totalFiles) { let totalBatches = parseInt(progressBar.getAttribute('data-total-batches'), 10); let currentProgress = ((batchIndex + (fileIndex / totalFiles)) / totalBatches) * 100; progressBar.style.width = currentProgress + '%'; } function finalizeUpload() { uploadCache.clear(); // 清空缓存 progressBar.style.width = '100%'; setTimeout(() => progressBar.style.display = 'none', 2000); } // 错误处理 function handleError(file, error) { const message = `上传 ${file.name} 时发生错误: ${error.message}`; console.error(message); showErrorPopup(message); } // 拖放视觉反馈 function addDragDropVisualFeedback() { const highlightClass = 'drag-over-highlight'; const dropZone = document.querySelector("[contenteditable]"); dropZone.addEventListener('dragenter', () => { dropZone.classList.add(highlightClass); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove(highlightClass); }); dropZone.addEventListener('drop', () => { dropZone.classList.remove(highlightClass); }); // 添加一些基础的高亮样式 const style = document.createElement('style'); style.innerHTML = ` .${highlightClass} { border: 2px dashed #4caf50; background-color: rgba(76, 175, 80, 0.1); } .error-popup { padding: 10px; background-color: #ffdddd; border: 1px solid #f44336; border-radius: 5px; box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); margin-bottom: 5px; z-index: 10001; } .error-popup .close-btn { margin-left: 10px; background-color: #f44336; color: white; border: none; border-radius: 3px; padding: 3px 6px; cursor: pointer; } .error-popup .close-btn:hover { background-color: #d32f2f; } `; document.head.appendChild(style); } // 主逻辑 document.addEventListener('drop', async function (event) { event.preventDefault(); const files = Array.from(event.dataTransfer.files); const sortedFiles = sortFiles(files); const batches = splitIntoBatches(sortedFiles); progressBar.setAttribute('data-total-batches', batches.length); progressBar.style.display = 'block'; await processAllBatches(batches); }); document.addEventListener('dragover', function (event) { event.preventDefault(); }); addDragDropVisualFeedback(); })();