// ==UserScript== // @name NodeSeek 编辑器增强 // @namespace https://www.nodeseek.com/ // @version 0.0.5 // @description 为 NodeSeek 编辑器增加图片上传功能 // @author TomyJan // @match *://www.nodeseek.com/* // @icon https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png // @grant GM_xmlhttpRequest // @license MPL-2.0 License // @supportURL https://www.nodeseek.com/post-74493-1 // @homepageURL https://www.nodeseek.com/post-74493-1 // @downloadURL none // ==/UserScript== /** * * * 当前版本更新日志 * 0.0.5 - 2024.03.02 !!!更新前注意备份您的配置!!! * - 修复 尝试修复某些情况下图片按钮替换失败 */ (function () { 'use strict'; // 图床配置, 默认提供的是这位大佬的 https://www.nodeseek.com/post-38305-1 , 这个图床上传限制 5p / IP / 小时 const imgHost = { type: "LskyPro", // 图床类型, 暂只支持 LskyPro url: "https://image.dooo.ng", // 图床地址, 带上协议头 token: null, // 图床 token, 可选, 不填则为游客上传, 在 /user/tokens 生成 storageId: null, // 图床存储策略ID, 可选项, 不填则为默认策略, 普通用户可在上传页抓包得到, 管理员可以在后台看到 }; const mdImgName = 0; // 0: 使用图床返回的原始名称, 其他值则名称固定为该值 // 页面加载完毕后载入功能 window.addEventListener('load', initEditorEnhancer, false); function initEditorEnhancer() { // 监听粘贴事件 document.addEventListener('paste', (event) => handlePasteEvt(event)); // 给编辑器绑定拖拽事件 var dropZone = document.getElementById('code-mirror-editor'); // 阻止默认行为 dropZone.addEventListener('dragover', function(e) { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; // 显示为复制图标 }); // 处理文件拖放 dropZone.addEventListener('drop', function(e) { e.preventDefault(); e.stopPropagation(); log('正在处理拖放内容...'); let imageFiles = []; for(let file of e.dataTransfer.files) { if (/^image\//i.test(file.type)) { // 确保只处理图片文件 imageFiles.push(file); log(`拖放的文件名: ${file.name}`); } } log(`拖放的图片数量: ${imageFiles.length}`); if (imageFiles.length === 0) { log('你拖放的内容好像没有图片哦', 'red'); return; } // 调整uploadImage函数以接受File对象数组而不是DataTransferItemList uploadImage(imageFiles.map(file => { return { kind: 'file', type: file.type, getAsFile: () => file }; })); }); // 修改图片按钮的行为 // 图片按钮 let checkExist = setInterval(function() { const oldElement = document.querySelector('.toolbar-item.i-icon.i-icon-pic[title="图片"]'); if (oldElement) { clearInterval(checkExist); const newElement = oldElement.cloneNode(true); oldElement.parentNode.replaceChild(newElement, oldElement); newElement.addEventListener('click', handleImgBtnClick); } }, 200); } // 粘贴事件处理 function handlePasteEvt(event) { log('正在处理粘贴内容...'); const items = (event.clipboardData || event.originalEvent.clipboardData).items; if (items.length === 0) { log('你粘贴的内容好像没有图片哦', 'red'); return; } uploadImage(items) } // 图片按钮点击事件处理 function handleImgBtnClick() { // 创建一个隐藏的文件输入元素 const input = document.createElement('input'); input.type = 'file'; input.multiple = true; // 允许多选文件 input.accept = 'image/*'; // 仅接受图片文件 // 当文件被选择后的处理 input.onchange = e => { const files = e.target.files; // 获取用户选择的文件列表 if (files.length) { const items = [...files].map(file => ({ kind: 'file', type: file.type, getAsFile: () => file })); uploadImage(items); } }; // 触发文件输入框的点击事件,打开文件选择窗口 input.click(); } // 处理并上传图片 async function uploadImage(items) { let imageFiles = []; for (let item of items) { if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { let blob = item.getAsFile(); imageFiles.push(blob); } } if (imageFiles.length > 0) { event.preventDefault(); for (let i = 0; i < imageFiles.length; i++) { if (imageFiles.length >1) log(`上传第 ${i + 1} / ${imageFiles.length} 张图片...`); else log(`上传图片...`); let file = imageFiles[i]; let formData = new FormData(); formData.append('file', file); if (imgHost.storageId) formData.append('strategy_id', imgHost.storageId); if (imgHost.type === 'LskyPro') { await uploadToLsky(formData); } else { log(`暂不支持的图床类型: ${imgHost.type}, 取消上传`, 'red'); return; } } } else { log('你粘贴的内容好像没有图片哦', 'red'); } } async function uploadToLsky(formData) { return new Promise((resolve, reject) => { let headers = { 'Accept': 'application/json' }; if (imgHost.token) headers['Authorization'] = `Bearer ${imgHost.token}`; GM_xmlhttpRequest({ method: 'POST', url: `${imgHost.url}/api/v1/upload`, headers: headers, data: formData, onload: (rsp) => { let rspJson = JSON.parse(rsp.responseText); if(rsp.status !== 200) { log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red'); reject(rspJson.message); } if (rspJson.status === true) { // 图片上传成功 if(rspJson?.data?.links?.markdown) insertToEditor(mdImgName === 0 ? rspJson.data.links.markdown : ``); else { log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red'); insertToEditor(``); } } else log(`图片上传失败: ${rspJson.message}`, 'red'); resolve(); }, onerror: (error) => { log(`图片上传失败: ${error.status} ${error.statusText}`, 'red'); reject(error); } }); }); } function insertToEditor(markdownLink) { const codeMirrorElement = document.querySelector('.CodeMirror'); if (codeMirrorElement) { const codeMirrorInstance = codeMirrorElement.CodeMirror; if (codeMirrorInstance) { const cursor = codeMirrorInstance.getCursor(); codeMirrorInstance.replaceRange(`\n${markdownLink} \n`, cursor); } } if (markdownLink.startsWith('![')) log('图片已插入到编辑器~', 'green'); } // 在编辑器打印日志 function log(message, color = '') { if(!document.getElementById('editor-enhance-logs')) { initEditorLogDiv(); } const logDiv = document.getElementById('editor-enhance-logs'); logDiv.innerHTML = `