Image Uploader to Markdown to CloudFlare-ImgBed

适配于 CloudFlare-ImgBed 的粘贴上传并生成markdown的脚本. CloudFlare-ImgBed : https://github.com/MarSeventh/CloudFlare-ImgBed

目前为 2025-03-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Image Uploader to Markdown to CloudFlare-ImgBed
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description 适配于 CloudFlare-ImgBed 的粘贴上传并生成markdown的脚本. CloudFlare-ImgBed : https://github.com/MarSeventh/CloudFlare-ImgBed
  6. // @author calg
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_addStyle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @license MIT
  14. // @icon https://raw.githubusercontent.com/MarSeventh/CloudFlare-ImgBed/refs/heads/main/logo.png
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. // 默认配置信息
  21. const DEFAULT_CONFIG = {
  22. AUTH_CODE: 'AUTH_CODE', // 替换为你的认证码
  23. SERVER_URL: 'https://SERVER_URL', // 替换为实际的服务器地址
  24. UPLOAD_PARAMS: {
  25. serverCompress: true,
  26. uploadChannel: 'telegram', // 可选 telegram 和 cfr2
  27. autoRetry: true,
  28. uploadNameType: 'index', // 可选值为[default, index, origin, short]
  29. returnFormat: 'full',
  30. uploadFolder: 'apiupload' // 指定上传目录,用相对路径表示,例如上传到img/test目录需填img/test
  31. },
  32. NOTIFICATION_DURATION: 3000, // 通知显示时间(毫秒)
  33. MARKDOWN_TEMPLATE: '![{filename}]({url})', // Markdown 模板
  34. AUTO_COPY_URL: false, // 是否自动复制URL到剪贴板
  35. ALLOWED_HOSTS: ['*'], // 允许在哪些网站上运行,* 表示所有网站
  36. MAX_FILE_SIZE: 5 * 1024 * 1024 // 最大文件大小(5MB)
  37. };
  38.  
  39. // 获取用户配置并确保所有必需的字段都存在
  40. const userConfig = GM_getValue('userConfig', {});
  41. let CONFIG = {};
  42. // 深度合并配置
  43. function mergeConfig(target, source) {
  44. for (const key in source) {
  45. if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
  46. target[key] = target[key] || {};
  47. mergeConfig(target[key], source[key]);
  48. } else {
  49. target[key] = source[key];
  50. }
  51. }
  52. return target;
  53. }
  54. // 确保所有默认配置项都存在
  55. CONFIG = mergeConfig({...DEFAULT_CONFIG}, userConfig);
  56. // 验证配置的完整性
  57. function validateConfig() {
  58. if (!Array.isArray(CONFIG.ALLOWED_HOSTS)) {
  59. CONFIG.ALLOWED_HOSTS = DEFAULT_CONFIG.ALLOWED_HOSTS;
  60. }
  61. if (typeof CONFIG.NOTIFICATION_DURATION !== 'number') {
  62. CONFIG.NOTIFICATION_DURATION = DEFAULT_CONFIG.NOTIFICATION_DURATION;
  63. }
  64. if (typeof CONFIG.MAX_FILE_SIZE !== 'number') {
  65. CONFIG.MAX_FILE_SIZE = DEFAULT_CONFIG.MAX_FILE_SIZE;
  66. }
  67. if (typeof CONFIG.MARKDOWN_TEMPLATE !== 'string') {
  68. CONFIG.MARKDOWN_TEMPLATE = DEFAULT_CONFIG.MARKDOWN_TEMPLATE;
  69. }
  70. if (typeof CONFIG.AUTO_COPY_URL !== 'boolean') {
  71. CONFIG.AUTO_COPY_URL = DEFAULT_CONFIG.AUTO_COPY_URL;
  72. }
  73. }
  74. validateConfig();
  75.  
  76. // 添加通知样式
  77. GM_addStyle(`
  78. .img-upload-notification {
  79. position: fixed;
  80. top: 20px;
  81. right: 20px;
  82. padding: 15px 20px;
  83. border-radius: 5px;
  84. z-index: 9999;
  85. max-width: 300px;
  86. font-size: 14px;
  87. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  88. transition: all 0.3s ease;
  89. opacity: 0;
  90. transform: translateX(20px);
  91. }
  92. .img-upload-notification.show {
  93. opacity: 1;
  94. transform: translateX(0);
  95. }
  96. .img-upload-success {
  97. background-color: #4caf50;
  98. color: white;
  99. }
  100. .img-upload-error {
  101. background-color: #f44336;
  102. color: white;
  103. }
  104. .img-upload-info {
  105. background-color: #2196F3;
  106. color: white;
  107. }
  108. .img-upload-close {
  109. float: right;
  110. margin-left: 10px;
  111. cursor: pointer;
  112. opacity: 0.8;
  113. }
  114. .img-upload-close:hover {
  115. opacity: 1;
  116. }
  117.  
  118. .img-upload-modal {
  119. position: fixed;
  120. top: 50%;
  121. left: 50%;
  122. transform: translate(-50%, -50%);
  123. background: white;
  124. padding: 20px;
  125. border-radius: 8px;
  126. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  127. z-index: 10000;
  128. max-width: 600px;
  129. width: 90%;
  130. max-height: 80vh;
  131. overflow-y: auto;
  132. }
  133. .img-upload-modal-overlay {
  134. position: fixed;
  135. top: 0;
  136. left: 0;
  137. right: 0;
  138. bottom: 0;
  139. background: rgba(0, 0, 0, 0.5);
  140. z-index: 9999;
  141. }
  142. .img-upload-modal h2 {
  143. margin: 0 0 20px;
  144. color: #333;
  145. font-size: 18px;
  146. }
  147. .img-upload-form-group {
  148. margin-bottom: 20px;
  149. }
  150. .img-upload-form-group label {
  151. display: block;
  152. margin-bottom: 8px;
  153. color: #333;
  154. font-weight: 500;
  155. }
  156. .img-upload-help-text {
  157. margin-top: 4px;
  158. color: #666;
  159. font-size: 12px;
  160. }
  161. .img-upload-form-group input[type="text"],
  162. .img-upload-form-group input[type="number"],
  163. .img-upload-form-group textarea {
  164. width: 100%;
  165. padding: 8px;
  166. border: 1px solid #ddd;
  167. border-radius: 4px;
  168. font-size: 14px;
  169. box-sizing: border-box;
  170. }
  171. .img-upload-form-group textarea {
  172. min-height: 100px;
  173. font-family: monospace;
  174. }
  175. .img-upload-form-group input[type="checkbox"] {
  176. margin-right: 8px;
  177. }
  178. .img-upload-buttons {
  179. display: flex;
  180. justify-content: flex-end;
  181. gap: 10px;
  182. margin-top: 20px;
  183. }
  184. .img-upload-button {
  185. padding: 8px 16px;
  186. border: none;
  187. border-radius: 4px;
  188. cursor: pointer;
  189. font-size: 14px;
  190. transition: background-color 0.2s;
  191. }
  192. .img-upload-button-primary {
  193. background: #2196F3;
  194. color: white;
  195. }
  196. .img-upload-button-secondary {
  197. background: #e0e0e0;
  198. color: #333;
  199. }
  200. .img-upload-button:hover {
  201. opacity: 0.9;
  202. }
  203. .img-upload-error {
  204. color: #ffffff;
  205. font-size: 12px;
  206. margin-top: 4px;
  207. }
  208. .img-upload-info-icon {
  209. display: inline-block;
  210. width: 16px;
  211. height: 16px;
  212. background: #2196F3;
  213. color: white;
  214. border-radius: 50%;
  215. text-align: center;
  216. line-height: 16px;
  217. font-size: 12px;
  218. margin-left: 4px;
  219. cursor: help;
  220. }
  221. .img-upload-form-group select {
  222. width: 100%;
  223. padding: 8px;
  224. border: 1px solid #ddd;
  225. border-radius: 4px;
  226. font-size: 14px;
  227. background-color: white;
  228. }
  229. .img-upload-input-group {
  230. display: flex;
  231. align-items: center;
  232. }
  233. .img-upload-input-group input {
  234. flex: 1;
  235. border-top-right-radius: 0;
  236. border-bottom-right-radius: 0;
  237. }
  238. .img-upload-input-group-text {
  239. padding: 8px 12px;
  240. background: #f5f5f5;
  241. border: 1px solid #ddd;
  242. border-left: none;
  243. border-radius: 0 4px 4px 0;
  244. color: #666;
  245. }
  246. .img-upload-checkbox-label {
  247. display: flex !important;
  248. align-items: center;
  249. font-weight: normal !important;
  250. }
  251. .img-upload-checkbox-label input {
  252. margin-right: 8px;
  253. }
  254.  
  255. .img-upload-dropzone {
  256. display: none;
  257. position: fixed;
  258. top: 0;
  259. left: 0;
  260. width: 100%;
  261. height: 100%;
  262. background: rgba(33, 150, 243, 0.2);
  263. border: 3px dashed #2196F3;
  264. z-index: 9998;
  265. box-sizing: border-box;
  266. }
  267. .img-upload-dropzone.active {
  268. display: block;
  269. }
  270. .img-upload-dropzone-message {
  271. position: absolute;
  272. top: 50%;
  273. left: 50%;
  274. transform: translate(-50%, -50%);
  275. background: white;
  276. padding: 20px 40px;
  277. border-radius: 8px;
  278. font-size: 18px;
  279. color: #2196F3;
  280. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  281. }
  282. `);
  283.  
  284. // 显示通知的函数
  285. function showNotification(message, type = 'info') {
  286. const notification = document.createElement('div');
  287. notification.className = `img-upload-notification img-upload-${type}`;
  288. const closeBtn = document.createElement('span');
  289. closeBtn.className = 'img-upload-close';
  290. closeBtn.textContent = '✕';
  291. closeBtn.onclick = () => removeNotification(notification);
  292. const messageSpan = document.createElement('span');
  293. messageSpan.textContent = message;
  294. notification.appendChild(closeBtn);
  295. notification.appendChild(messageSpan);
  296. document.body.appendChild(notification);
  297.  
  298. // 添加显示动画
  299. setTimeout(() => notification.classList.add('show'), 10);
  300.  
  301. // 自动消失
  302. const timeout = setTimeout(() => removeNotification(notification), CONFIG.NOTIFICATION_DURATION);
  303. // 鼠标悬停时暂停消失
  304. notification.addEventListener('mouseenter', () => clearTimeout(timeout));
  305. notification.addEventListener('mouseleave', () => setTimeout(() => removeNotification(notification), 1000));
  306. }
  307.  
  308. // 移除通知
  309. function removeNotification(notification) {
  310. notification.classList.remove('show');
  311. setTimeout(() => {
  312. if (notification.parentNode) {
  313. notification.parentNode.removeChild(notification);
  314. }
  315. }, 300);
  316. }
  317.  
  318. // 复制文本到剪贴板
  319. function copyToClipboard(text) {
  320. const textarea = document.createElement('textarea');
  321. textarea.value = text;
  322. textarea.style.position = 'fixed';
  323. textarea.style.opacity = '0';
  324. document.body.appendChild(textarea);
  325. textarea.select();
  326. try {
  327. document.execCommand('copy');
  328. showNotification('链接已复制到剪贴板!', 'success');
  329. } catch (err) {
  330. showNotification('复制失败:' + err.message, 'error');
  331. }
  332. document.body.removeChild(textarea);
  333. }
  334.  
  335. // 检查文件大小
  336. function checkFileSize(file) {
  337. if (file.size > CONFIG.MAX_FILE_SIZE) {
  338. showNotification(`文件大小超过限制(${Math.round(CONFIG.MAX_FILE_SIZE/1024/1024)}MB)`, 'error');
  339. return false;
  340. }
  341. return true;
  342. }
  343.  
  344. // 检查当前网站是否允许上传
  345. function isAllowedHost() {
  346. const currentHost = window.location.hostname;
  347. return CONFIG.ALLOWED_HOSTS.includes('*') || CONFIG.ALLOWED_HOSTS.includes(currentHost);
  348. }
  349.  
  350. // 监听所有文本输入区域的粘贴事件
  351. function addPasteListener() {
  352. document.addEventListener('paste', async function(event) {
  353. if (!isAllowedHost()) return;
  354.  
  355. const activeElement = document.activeElement;
  356. if (!activeElement || !['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
  357. return;
  358. }
  359.  
  360. const items = event.clipboardData.items;
  361. let hasImage = false;
  362. for (let item of items) {
  363. if (item.type.startsWith('image/')) {
  364. hasImage = true;
  365. event.preventDefault();
  366. const blob = item.getAsFile();
  367. if (!checkFileSize(blob)) {
  368. return;
  369. }
  370.  
  371. showNotification('正在上传图片,请稍候...', 'info');
  372. await uploadImage(blob, activeElement);
  373. break;
  374. }
  375. }
  376.  
  377. if (!hasImage) {
  378. return;
  379. }
  380. });
  381. }
  382.  
  383. // 上传图片
  384. async function uploadImage(blob, targetElement) {
  385. const formData = new FormData();
  386. const filename = `pasted-image-${Date.now()}.png`;
  387. formData.append('file', blob, filename);
  388.  
  389. const queryParams = new URLSearchParams({
  390. authCode: CONFIG.AUTH_CODE,
  391. ...CONFIG.UPLOAD_PARAMS
  392. }).toString();
  393.  
  394. try {
  395. GM_xmlhttpRequest({
  396. method: 'POST',
  397. url: `${CONFIG.SERVER_URL}/upload?${queryParams}`,
  398. data: formData,
  399. onload: function(response) {
  400. if (response.status === 200) {
  401. try {
  402. const result = JSON.parse(response.responseText);
  403. if (result && result.length > 0) {
  404. const imageUrl = result[0].src;
  405. insertMarkdownImage(imageUrl, targetElement, filename);
  406. showNotification('图片上传成功!', 'success');
  407. if (CONFIG.AUTO_COPY_URL) {
  408. copyToClipboard(imageUrl);
  409. }
  410. } else {
  411. showNotification('上传成功但未获取到图片链接,请检查服务器响应', 'error');
  412. }
  413. } catch (e) {
  414. showNotification('解析服务器响应失败:' + e.message, 'error');
  415. }
  416. } else {
  417. let errorMsg = '上传失败';
  418. try {
  419. const errorResponse = JSON.parse(response.responseText);
  420. errorMsg += ':' + (errorResponse.message || response.statusText);
  421. } catch (e) {
  422. errorMsg += `(状态码:${response.status})`;
  423. }
  424. showNotification(errorMsg, 'error');
  425. }
  426. },
  427. onerror: function(error) {
  428. showNotification('网络错误:无法连接到图床服务器', 'error');
  429. }
  430. });
  431. } catch (error) {
  432. showNotification('上传过程发生错误:' + error.message, 'error');
  433. }
  434. }
  435.  
  436. // 在输入框中插入 Markdown 格式的图片链接
  437. function insertMarkdownImage(imageUrl, element, filename) {
  438. const markdownImage = CONFIG.MARKDOWN_TEMPLATE
  439. .replace('{url}', imageUrl)
  440. .replace('{filename}', filename.replace(/\.[^/.]+$/, '')); // 移除文件扩展名
  441. const start = element.selectionStart;
  442. const end = element.selectionEnd;
  443. const text = element.value;
  444.  
  445. element.value = text.substring(0, start) + markdownImage + text.substring(end);
  446. element.selectionStart = element.selectionEnd = start + markdownImage.length;
  447. element.focus();
  448. }
  449.  
  450. // 创建配置界面
  451. function createConfigModal() {
  452. const overlay = document.createElement('div');
  453. overlay.className = 'img-upload-modal-overlay';
  454. const modal = document.createElement('div');
  455. modal.className = 'img-upload-modal';
  456. const content = `
  457. <h2>图床上传配置</h2>
  458. <form id="img-upload-config-form">
  459. <div class="img-upload-form-group">
  460. <label>认证码</label>
  461. <input type="text" name="AUTH_CODE" value="${CONFIG.AUTH_CODE}" required>
  462. <div class="img-upload-help-text">用于验证上传请求的密钥</div>
  463. </div>
  464. <div class="img-upload-form-group">
  465. <label>服务器地址</label>
  466. <input type="text" name="SERVER_URL" value="${CONFIG.SERVER_URL}" required>
  467. <div class="img-upload-help-text">图床服务器的URL地址</div>
  468. </div>
  469. <div class="img-upload-form-group">
  470. <label>上传通道</label>
  471. <select name="uploadChannel">
  472. <option value="cfr2" ${CONFIG.UPLOAD_PARAMS.uploadChannel === 'cfr2' ? 'selected' : ''}>CloudFlare R2</option>
  473. <option value="telegram" ${CONFIG.UPLOAD_PARAMS.uploadChannel === 'telegram' ? 'selected' : ''}>Telegram</option>
  474. </select>
  475. <div class="img-upload-help-text">选择图片上传的存储通道</div>
  476. </div>
  477. <div class="img-upload-form-group">
  478. <label>文件命名方式</label>
  479. <select name="uploadNameType">
  480. <option value="default" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'default' ? 'selected' : ''}>默认(前缀_原名)</option>
  481. <option value="index" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'index' ? 'selected' : ''}>仅前缀</option>
  482. <option value="origin" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'origin' ? 'selected' : ''}>仅原名</option>
  483. <option value="short" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'short' ? 'selected' : ''}>短链接</option>
  484. </select>
  485. <div class="img-upload-help-text">选择上传后的文件命名方式</div>
  486. </div>
  487. <div class="img-upload-form-group">
  488. <label>上传目录</label>
  489. <input type="text" name="uploadFolder" value="${CONFIG.UPLOAD_PARAMS.uploadFolder}">
  490. <div class="img-upload-help-text">指定上传目录,使用相对路径,例如:img/test</div>
  491. </div>
  492. <div class="img-upload-form-group">
  493. <label>通知显示时间</label>
  494. <input type="number" name="NOTIFICATION_DURATION" value="${CONFIG.NOTIFICATION_DURATION}" min="1000" step="500">
  495. <div class="img-upload-help-text">通知消息显示的时间(毫秒)</div>
  496. </div>
  497. <div class="img-upload-form-group">
  498. <label>Markdown模板</label>
  499. <input type="text" name="MARKDOWN_TEMPLATE" value="${CONFIG.MARKDOWN_TEMPLATE}">
  500. <div class="img-upload-help-text">支持 {filename} {url} 两个变量</div>
  501. </div>
  502. <div class="img-upload-form-group">
  503. <label>允许的网站</label>
  504. <input type="text" name="ALLOWED_HOSTS" value="${CONFIG.ALLOWED_HOSTS.join(',')}">
  505. <div class="img-upload-help-text">输入域名,用逗号分隔。使用 * 表示允许所有网站</div>
  506. </div>
  507. <div class="img-upload-form-group">
  508. <label>最大文件大小</label>
  509. <div class="img-upload-input-group">
  510. <input type="number" name="MAX_FILE_SIZE" value="${CONFIG.MAX_FILE_SIZE / 1024 / 1024}" min="1" step="1">
  511. <span class="img-upload-input-group-text">MB</span>
  512. </div>
  513. </div>
  514. <div class="img-upload-form-group">
  515. <label class="img-upload-checkbox-label">
  516. <input type="checkbox" name="AUTO_COPY_URL" ${CONFIG.AUTO_COPY_URL ? 'checked' : ''}>
  517. 自动复制URL到剪贴板
  518. </label>
  519. </div>
  520. <div class="img-upload-buttons">
  521. <button type="button" class="img-upload-button img-upload-button-secondary" id="img-upload-cancel">取消</button>
  522. <button type="button" class="img-upload-button img-upload-button-secondary" id="img-upload-reset">重置默认值</button>
  523. <button type="submit" class="img-upload-button img-upload-button-primary">保存</button>
  524. </div>
  525. </form>
  526. `;
  527. modal.innerHTML = content;
  528. document.body.appendChild(overlay);
  529. document.body.appendChild(modal);
  530.  
  531. // 事件处理
  532. const form = modal.querySelector('#img-upload-config-form');
  533. const cancelBtn = modal.querySelector('#img-upload-cancel');
  534. const resetBtn = modal.querySelector('#img-upload-reset');
  535.  
  536. function closeModal() {
  537. document.body.removeChild(overlay);
  538. document.body.removeChild(modal);
  539. }
  540.  
  541. overlay.addEventListener('click', closeModal);
  542. cancelBtn.addEventListener('click', closeModal);
  543. resetBtn.addEventListener('click', () => {
  544. if (confirm('确定要重置所有配置到默认值吗?')) {
  545. CONFIG = {...DEFAULT_CONFIG};
  546. GM_setValue('userConfig', {});
  547. showNotification('配置已重置为默认值!', 'success');
  548. closeModal();
  549. }
  550. });
  551.  
  552. form.addEventListener('submit', (e) => {
  553. e.preventDefault();
  554. try {
  555. const formData = new FormData(form);
  556. const newConfig = {
  557. AUTH_CODE: formData.get('AUTH_CODE'),
  558. SERVER_URL: formData.get('SERVER_URL'),
  559. UPLOAD_PARAMS: {
  560. ...DEFAULT_CONFIG.UPLOAD_PARAMS,
  561. uploadChannel: formData.get('uploadChannel'),
  562. uploadNameType: formData.get('uploadNameType'),
  563. uploadFolder: formData.get('uploadFolder')
  564. },
  565. NOTIFICATION_DURATION: parseInt(formData.get('NOTIFICATION_DURATION')),
  566. MARKDOWN_TEMPLATE: formData.get('MARKDOWN_TEMPLATE'),
  567. ALLOWED_HOSTS: formData.get('ALLOWED_HOSTS').split(',').map(h => h.trim()),
  568. MAX_FILE_SIZE: parseFloat(formData.get('MAX_FILE_SIZE')) * 1024 * 1024,
  569. AUTO_COPY_URL: formData.get('AUTO_COPY_URL') === 'on'
  570. };
  571.  
  572. CONFIG = mergeConfig({...DEFAULT_CONFIG}, newConfig);
  573. GM_setValue('userConfig', CONFIG);
  574. showNotification('配置已更新!', 'success');
  575. closeModal();
  576. } catch (error) {
  577. showNotification('配置格式错误:' + error.message, 'error');
  578. }
  579. });
  580.  
  581. // 防止点击模态框时关闭
  582. modal.addEventListener('click', (e) => e.stopPropagation());
  583. }
  584.  
  585. // 修改注册配置菜单函数
  586. function registerMenuCommands() {
  587. GM_registerMenuCommand('配置图床参数', createConfigModal);
  588. }
  589.  
  590. // 创建拖拽区域
  591. function createDropZone() {
  592. const dropZone = document.createElement('div');
  593. dropZone.className = 'img-upload-dropzone';
  594. const message = document.createElement('div');
  595. message.className = 'img-upload-dropzone-message';
  596. message.textContent = '释放鼠标上传图片';
  597. dropZone.appendChild(message);
  598. document.body.appendChild(dropZone);
  599. return dropZone;
  600. }
  601.  
  602. // 处理拖拽上传
  603. function handleDragAndDrop() {
  604. const dropZone = createDropZone();
  605. let activeElement = null;
  606.  
  607. // 处理拖拽文件
  608. async function handleFiles(files, targetElement) {
  609. for (const file of files) {
  610. if (file.type.startsWith('image/')) {
  611. if (!checkFileSize(file)) {
  612. continue;
  613. }
  614. showNotification('正在上传图片,请稍候...', 'info');
  615. await uploadImage(file, targetElement);
  616. } else {
  617. showNotification('只能上传图片文件', 'error');
  618. }
  619. }
  620. }
  621.  
  622. // 监听拖拽事件
  623. document.addEventListener('dragenter', (e) => {
  624. e.preventDefault();
  625. activeElement = document.activeElement;
  626. if (activeElement && ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
  627. dropZone.classList.add('active');
  628. }
  629. });
  630.  
  631. document.addEventListener('dragover', (e) => {
  632. e.preventDefault();
  633. });
  634.  
  635. document.addEventListener('dragleave', (e) => {
  636. e.preventDefault();
  637. // 检查是否真的离开了文档区域
  638. const rect = document.documentElement.getBoundingClientRect();
  639. if (e.clientX <= rect.left || e.clientX >= rect.right ||
  640. e.clientY <= rect.top || e.clientY >= rect.bottom) {
  641. dropZone.classList.remove('active');
  642. }
  643. });
  644.  
  645. document.addEventListener('drop', async (e) => {
  646. e.preventDefault();
  647. dropZone.classList.remove('active');
  648. if (!isAllowedHost()) return;
  649. if (activeElement && ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
  650. const files = Array.from(e.dataTransfer.files);
  651. await handleFiles(files, activeElement);
  652. }
  653. });
  654. }
  655.  
  656. // 修改初始化函数
  657. function init() {
  658. if (!isAllowedHost()) return;
  659. addPasteListener();
  660. handleDragAndDrop();
  661. registerMenuCommands();
  662. }
  663.  
  664. init();
  665. })();