// ==UserScript== // @name amobbs 论坛增强:视频播放按钮 + 粘贴上传图片 // @namespace https://www.amobbs.com/ // @version 2.0.0 // @description 视频:为哔哩哔哩视频链接(b23.tv / bilibili.com)后面添加「▶ 播放」按钮,点击弹窗播放,支持横竖屏自适应、手动切换与最大化、Windows 风格窗口按钮,播放器 iframe 加 sandbox 点击倍速/弹幕等按钮不再跳转 B 站官网,PC 下页面最大宽度 1200px 居中。图片:帖子页(快速回复)和发帖页编辑框内直接粘贴截图,自动上传为论坛附件并插入 [attachimg],大于 1MB 自动压缩(最长边 1920px、JPEG),带进度提示与错误原因 // @author you // @match https://www.amobbs.com/* // @match https://amobbs.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect b23.tv // @connect api.bilibili.com // @connect bilibili.com // @connect www.bilibili.com // @run-at document-idle // ==/UserScript== (function () { 'use strict'; var LINK_RE = /^(https?:\/\/)?((www\.)?bilibili\.com\/video\/(BV[0-9A-Za-z]+|av\d+)|b23\.tv\/[0-9A-Za-z]+)/i; var POPUP_ID = 'tm-video-popup'; var PAGE_MAX_WIDTH = 1200; var ICON_MAX = ''; var ICON_RESTORE = ''; var ICON_CLOSE = ''; var UPLOAD_URL = 'https://www.amobbs.com/misc.php?mod=swfupload&action=swfupload&operation=upload'; var STATUS_MSG = { 1: '不允许的文件扩展名', 2: '文件为空或读取失败', 3: '超过单文件大小上限', 4: '该扩展名被禁止', 5: '超过该文件类型的大小限制', 6: '附件数量超限或没有上传权限', 7: '不是有效的图片文件', 8: '服务器保存失败', 9: '服务器保存错误', 10: '上传参数(hash)校验失败,请刷新页面重试', 11: '超过每天附件总大小上限', 12: '文件名含敏感词', 13: '图片像素超出限制' }; var DEFAULT_OPTS = { maxBytes: 1024 * 1024, maxDim: 1920, quality: 0.85 }; function extractPlayId(url) { var m; m = url.match(/bilibili\.com\/video\/(BV[0-9A-Za-z]+)/i); if (m) return { bvid: m[1] }; m = url.match(/bilibili\.com\/video\/av(\d+)/i); if (m) return { aid: m[1] }; m = url.match(/b23\.tv\/([0-9A-Za-z]+)/i); if (m) return { short: m[1] }; return null; } function buildEmbedUrl(id) { if (id.bvid) { return 'https://player.bilibili.com/player.html?bvid=' + id.bvid + '&page=1&high_quality=1&danmaku=0'; } if (id.aid) { return 'https://player.bilibili.com/player.html?aid=' + id.aid + '&page=1&high_quality=1&danmaku=0'; } return null; } function resolveShort(code) { var cacheKey = 'b23_' + code; var cached = GM_getValue(cacheKey, null); if (cached) return Promise.resolve(cached); return new Promise(function (resolve, reject) { GM_xmlhttpRequest({ method: 'GET', url: 'https://b23.tv/' + code, redirect: 'follow', timeout: 15000, onload: function (resp) { var finalUrl = (resp.finalUrl && resp.finalUrl !== '') ? resp.finalUrl : ''; if (!finalUrl) { var m = resp.responseText && resp.responseText.match(/url\s*=\s*["']([^"']+)["']/i); if (m) finalUrl = m[1]; } var id = extractPlayId(finalUrl); if (id && (id.bvid || id.aid)) { GM_setValue(cacheKey, id); resolve(id); } else { reject(new Error('短链接解析失败')); } }, onerror: function (e) { reject(new Error('短链接请求失败:' + ((e && e.error) ? e.error : '网络错误'))); }, ontimeout: function () { reject(new Error('短链接请求超时')); } }); }); } function fetchVideoMeta(id) { var url; if (id.bvid) { url = 'https://api.bilibili.com/x/web-interface/view?bvid=' + id.bvid; } else if (id.aid) { url = 'https://api.bilibili.com/x/web-interface/view?aid=' + id.aid; } else { return Promise.resolve(null); } return new Promise(function (resolve) { GM_xmlhttpRequest({ method: 'GET', url: url, timeout: 10000, onload: function (resp) { try { var data = JSON.parse(resp.responseText); var dim = data && data.data && data.data.dimension; resolve(dim || null); } catch (e) { resolve(null); } }, onerror: function () { resolve(null); }, ontimeout: function () { resolve(null); } }); }); } function applyOrientationSize(dim, box, mode) { var vw = window.innerWidth; var vh = window.innerHeight; var barH = (box._bar && box._bar.offsetHeight) || 44; var body = box._body; var ratio = 16 / 9; if (dim && dim.width && dim.height) { var w = dim.width, h = dim.height, r = dim.rotate; if (r === 90 || r === 270 || r === -90 || r === -270) { var t = w; w = h; h = t; } if (w > 0 && h > 0) ratio = w / h; } if (mode === 'landscape') { ratio = 16 / 9; } else if (mode === 'portrait') { ratio = 9 / 16; } var availW = vw - 32; var availH = vh - barH - 48; var bw, bh; if (ratio < 1) { bh = Math.min(availH, Math.round(vh * 0.9)); bw = Math.round(bh * ratio); if (bw < 280) bw = 280; if (bw > availW) { bw = availW; bh = Math.round(bw / ratio); } } else { bw = Math.min(availW, 1400); bh = Math.round(bw / ratio); if (bh > availH) { bh = availH; bw = Math.round(bh * ratio); } } body.style.width = '100%'; body.style.height = bh + 'px'; box.style.width = bw + 'px'; box.style.height = (bh + barH) + 'px'; box.style.marginLeft = ''; box.style.marginTop = ''; box._maximized = false; var overlay = box.parentNode; if (overlay && overlay.style) { overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; } if (box._btnAuto && box._btnLand && box._btnPort) { box._btnAuto.style.background = (!mode || mode === 'auto') ? '#fb7299' : '#333'; box._btnLand.style.background = (mode === 'landscape') ? '#fb7299' : '#333'; box._btnPort.style.background = (mode === 'portrait') ? '#fb7299' : '#333'; } if (box._btnMax) { box._btnMax.innerHTML = ICON_MAX; box._btnMax.style.background = 'transparent'; box._btnMax.title = '最大化'; } } function getOrCreatePopup() { var popup = document.getElementById(POPUP_ID); if (popup) return popup; var overlay = document.createElement('div'); overlay.id = POPUP_ID; overlay.style.cssText = [ 'position:fixed', 'left:0', 'top:0', 'width:100%', 'height:100%', 'background:rgba(0,0,0,0.65)', 'z-index:2147483000', 'display:none', 'align-items:center', 'justify-content:center' ].join(';'); var box = document.createElement('div'); box.style.cssText = [ 'position:relative', 'width:min(860px,92vw)', 'background:#0b0b0f', 'border-radius:10px', 'box-shadow:0 12px 48px rgba(0,0,0,0.7)', 'overflow:hidden', 'display:flex', 'flex-direction:column' ].join(';'); var bar = document.createElement('div'); bar.style.cssText = 'display:flex;align-items:stretch;background:#18181c;color:#eee;font-size:14px;cursor:move;'; var title = document.createElement('span'); title.textContent = '视频播放'; title.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'; function makeModeBtn(text, titleText) { var b = document.createElement('button'); b.type = 'button'; b.textContent = text; b.title = titleText; b.style.cssText = 'cursor:pointer;padding:2px 10px;border:0;border-radius:6px;background:#333;color:#eee;font-size:12px;line-height:20px;'; b.addEventListener('mouseenter', function () { if (b.style.background === 'rgb(51, 51, 51)') b.style.background = '#555'; }); b.addEventListener('mouseleave', function () { if (b.style.background === 'rgb(85, 85, 85)') b.style.background = '#333'; }); return b; } var btnAuto = makeModeBtn('自动', '按视频原始比例自适应'); var btnLand = makeModeBtn('横屏', '切换为横屏 16:9'); var btnPort = makeModeBtn('竖屏', '切换为竖屏 9:16'); var btnGroup = document.createElement('div'); btnGroup.style.cssText = 'display:flex;gap:6px;'; btnGroup.appendChild(btnAuto); btnGroup.appendChild(btnLand); btnGroup.appendChild(btnPort); btnAuto.addEventListener('click', function () { box._mode = 'auto'; applyOrientationSize(box._meta, box, 'auto'); }); btnLand.addEventListener('click', function () { box._mode = 'landscape'; applyOrientationSize(box._meta, box, 'landscape'); }); btnPort.addEventListener('click', function () { box._mode = 'portrait'; applyOrientationSize(box._meta, box, 'portrait'); }); function makeWinBtn(html, titleText) { var b = document.createElement('button'); b.type = 'button'; b.innerHTML = html; b.title = titleText; b.style.cssText = 'display:flex;align-items:center;justify-content:center;width:44px;border:0;background:transparent;cursor:default;flex-shrink:0;'; return b; } var btnMax = makeWinBtn(ICON_MAX, '最大化'); btnMax.addEventListener('mouseenter', function () { btnMax.style.background = '#e81123'; }); btnMax.addEventListener('mouseleave', function () { btnMax.style.background = 'transparent'; }); btnMax.addEventListener('click', function () { if (box._maximized) { applyOrientationSize(box._meta, box, box._mode); } else { box._maximized = true; var barH = (box._bar && box._bar.offsetHeight) || 44; box.style.width = '100%'; box.style.height = '100%'; box.style.marginLeft = ''; box.style.marginTop = ''; var overlay = box.parentNode; if (overlay && overlay.style) { overlay.style.alignItems = 'stretch'; overlay.style.justifyContent = 'stretch'; } box._body.style.width = '100%'; box._body.style.height = 'calc(100vh - ' + barH + 'px)'; btnMax.innerHTML = ICON_RESTORE; btnMax.title = '还原'; box._btnAuto.style.background = '#333'; box._btnLand.style.background = '#333'; box._btnPort.style.background = '#333'; } }); var close = makeWinBtn(ICON_CLOSE, '关闭 (Esc)'); close.addEventListener('mouseenter', function () { close.style.background = '#e81123'; }); close.addEventListener('mouseleave', function () { close.style.background = 'transparent'; }); close.addEventListener('click', hidePopup); var leftGroup = document.createElement('div'); leftGroup.style.cssText = 'display:flex;align-items:center;gap:10px;flex:1;min-width:0;padding:10px 14px;cursor:move;'; leftGroup.appendChild(title); leftGroup.appendChild(btnGroup); bar.appendChild(leftGroup); bar.appendChild(btnMax); bar.appendChild(close); var body = document.createElement('div'); body.style.cssText = 'position:relative;width:100%;height:360px;background:#000;'; var msg = document.createElement('div'); msg.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:14px;'; msg.textContent = '解析视频链接中…'; body.appendChild(msg); box.appendChild(bar); box.appendChild(body); overlay.appendChild(box); box._title = title; box._body = body; box._msg = msg; box._bar = bar; box._btnAuto = btnAuto; box._btnLand = btnLand; box._btnPort = btnPort; box._btnMax = btnMax; box._mode = 'auto'; box._meta = null; box._maximized = false; overlay._box = box; document.documentElement.appendChild(overlay); return overlay; } function showPopup(url) { var overlay = getOrCreatePopup(); var box = overlay._box; box._mode = 'auto'; box._meta = null; overlay.style.display = 'flex'; box._body.querySelectorAll('iframe').forEach(function (f) { f.remove(); }); box._msg.style.display = 'flex'; box._msg.textContent = '解析视频链接中…'; applyOrientationSize(null, box, 'auto'); var id = extractPlayId(url); var promise; if (id && id.short) { promise = resolveShort(id.short); } else if (id && (id.bvid || id.aid)) { promise = Promise.resolve(id); } else { promise = Promise.reject(new Error('无法识别的视频链接')); } promise.then(function (resolved) { var embedUrl = buildEmbedUrl(resolved); if (!embedUrl) throw new Error('无法生成播放地址'); return Promise.all([embedUrl, fetchVideoMeta(resolved)]); }).then(function (pair) { var embedUrl = pair[0]; box._meta = pair[1]; applyOrientationSize(pair[1], box, box._mode); var iframe = document.createElement('iframe'); iframe.src = embedUrl; iframe.setAttribute('scrolling', 'no'); iframe.setAttribute('frameborder', '0'); iframe.setAttribute('allowfullscreen', 'true'); iframe.setAttribute('allow', 'autoplay; fullscreen'); iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin'); iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:0;'; box._msg.style.display = 'none'; box._body.appendChild(iframe); }).catch(function (err) { box._msg.style.display = 'flex'; box._msg.innerHTML = ''; var line = document.createElement('div'); line.style.cssText = 'text-align:center;'; line.textContent = '播放失败:' + err.message; var openLink = document.createElement('a'); openLink.href = url; openLink.target = '_blank'; openLink.rel = 'noopener'; openLink.textContent = ' 在新窗口打开链接'; openLink.style.cssText = 'display:inline-block;margin-top:8px;color:#5ad;'; line.appendChild(openLink); box._msg.appendChild(line); }); } function hidePopup() { var overlay = document.getElementById(POPUP_ID); if (!overlay) return; overlay.style.display = 'none'; var box = overlay._box; if (box) { box._body.querySelectorAll('iframe').forEach(function (f) { f.remove(); }); } } function makePlayButton(url) { var btn = document.createElement('button'); btn.type = 'button'; btn.textContent = '▶ 播放'; btn.title = '弹窗播放:' + url; btn.style.cssText = [ 'display:inline-block', 'margin:0 4px', 'padding:2px 10px', 'background:#fb7299', 'color:#fff', 'border:0', 'border-radius:6px', 'font-size:12px', 'line-height:18px', 'cursor:pointer', 'vertical-align:middle', 'box-shadow:0 1px 3px rgba(0,0,0,0.25)' ].join(';'); btn.addEventListener('mouseenter', function () { btn.style.background = '#ff85a8'; }); btn.addEventListener('mouseleave', function () { btn.style.background = '#fb7299'; }); btn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); showPopup(url); }); return btn; } function attachButtons() { document.querySelectorAll('a[href]').forEach(function (a) { if (a.dataset.tmPlayBtn === '1') return; var href = a.href || ''; if (!LINK_RE.test(href)) return; a.dataset.tmPlayBtn = '1'; a.insertAdjacentElement('afterend', makePlayButton(href)); }); } function applyPageWidthLimit() { var style = document.getElementById('tm-page-width-style'); if (!style) { style = document.createElement('style'); style.id = 'tm-page-width-style'; document.documentElement.appendChild(style); } style.textContent = '@media (min-width: 769px) {' + 'html body {' + 'max-width: ' + PAGE_MAX_WIDTH + 'px !important;' + 'margin-left: auto !important;' + 'margin-right: auto !important;' + '}' + '}'; } function getOpts() { var saved = {}; try { saved = GM_getValue('amobbs_paste_opts', {}) || {}; } catch (e) { } var opts = {}; for (var k in DEFAULT_OPTS) { opts[k] = (typeof saved[k] === 'number') ? saved[k] : DEFAULT_OPTS[k]; } return opts; } var toastEl = null; var toastTimer = null; function showToast(text, isError, stayMs) { if (!toastEl) { toastEl = document.createElement('div'); toastEl.style.cssText = [ 'position:fixed', 'right:24px', 'bottom:32px', 'z-index:2147483647', 'max-width:440px', 'padding:10px 16px', 'border-radius:8px', 'font-size:13px', 'line-height:1.5', 'color:#fff', 'box-shadow:0 4px 18px rgba(0,0,0,0.35)', 'display:none' ].join(';'); document.documentElement.appendChild(toastEl); } toastEl.textContent = text; toastEl.style.background = isError ? '#c0392b' : '#20242b'; toastEl.style.display = 'block'; if (toastTimer) clearTimeout(toastTimer); if (stayMs) { toastTimer = setTimeout(function () { toastEl.style.display = 'none'; }, stayMs); } } var cachedParams = null; function collectPageText() { var out = []; document.querySelectorAll('script').forEach(function (s) { out.push(s.textContent || ''); }); document.querySelectorAll('form').forEach(function (f) { out.push(f.getAttribute('action') || ''); }); return out.join('\n'); } function extractParams() { if (cachedParams) return cachedParams; var all = collectPageText(); var uid = null; var hash = null; var fid = null; var m = all.match(/"uid"\s*:\s*["'](\d+)["']\s*,\s*"hash"\s*:\s*["']([0-9a-fA-F]{32})["']/); if (m) { uid = m[1]; hash = m[2]; } if (!hash) { m = all.match(/"hash"\s*:\s*["']([0-9a-fA-F]{32})["']\s*,\s*"uid"\s*:\s*["'](\d+)["']/); if (m) { hash = m[1]; uid = uid || m[2]; } } if (!hash) { m = all.match(/[?&]hash=([0-9a-fA-F]{32})/); if (m) hash = m[1]; } if (!uid) { m = all.match(/discuz_uid\s*=\s*['"](\d+)['"]/); if (m) uid = m[1]; } m = all.match(/var\s+fid\s*=\s*parseInt\('(\d+)'\)/); if (!m) m = all.match(/fid=(\d+)/); if (!m) m = location.search.match(/[?&]fid=(\d+)/); if (m) fid = m[1]; if (uid && hash && fid && uid !== '0') { cachedParams = { uid: uid, hash: hash, fid: fid }; return cachedParams; } return null; } function findEditorTextarea(target) { var tas = document.querySelectorAll('textarea[name="message"]'); if (!tas.length) return null; for (var i = 0; i < tas.length; i++) { if (tas[i] === target || (target && tas[i].contains(target))) return tas[i]; } for (i = 0; i < tas.length; i++) { if (tas[i].offsetWidth > 0 || tas[i].offsetHeight > 0) return tas[i]; } return tas[0]; } function insertIntoEditor(ta, txt) { if (ta.id === 'fastpostmessage' && typeof window.seditor_insertunit === 'function') { try { window.seditor_insertunit('fastpost', txt); return; } catch (e) { } } var start = (ta.selectionStart != null) ? ta.selectionStart : ta.value.length; var end = (ta.selectionEnd != null) ? ta.selectionEnd : ta.value.length; var pre = ta.value.slice(0, start); var post = ta.value.slice(end); var nl = (pre && pre.charAt(pre.length - 1) !== '\n') ? '\n' : ''; ta.value = pre + nl + txt + post; var pos = start + nl.length + txt.length; ta.selectionStart = ta.selectionEnd = pos; try { ta.dispatchEvent(new Event('input', { bubbles: true })); } catch (e) { } ta.focus(); } function addAttachHiddenFields(form, aid) { ['description', 'readperm', 'price'].forEach(function (f) { var name = 'attachnew[' + aid + '][' + f + ']'; if (form.querySelector('input[name="' + name + '"]')) return; var inp = document.createElement('input'); inp.type = 'hidden'; inp.name = name; inp.value = ''; form.appendChild(inp); }); } function compressBlob(blob, opts) { return new Promise(function (resolve, reject) { var url = URL.createObjectURL(blob); var img = new Image(); img.onload = function () { var w = img.naturalWidth || img.width; var h = img.naturalHeight || img.height; var scale = Math.min(1, opts.maxDim / Math.max(w, h)); var cw = Math.max(1, Math.round(w * scale)); var ch = Math.max(1, Math.round(h * scale)); var canvas = document.createElement('canvas'); canvas.width = cw; canvas.height = ch; var ctx = canvas.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, cw, ch); ctx.drawImage(img, 0, 0, cw, ch); URL.revokeObjectURL(url); canvas.toBlob(function (b) { if (b && b.size) resolve(b); else reject(new Error('压缩失败')); }, 'image/jpeg', opts.quality); }; img.onerror = function () { URL.revokeObjectURL(url); reject(new Error('图片解码失败,无法压缩')); }; img.src = url; }); } function uploadBlob(blob, params, onProgress) { return new Promise(function (resolve, reject) { var fd = new FormData(); fd.append('uid', params.uid); fd.append('hash', params.hash); fd.append('Filedata', blob, blob.name || 'clipboard.png'); var url = UPLOAD_URL + '&fid=' + params.fid + '&type=image&uid=' + params.uid + '&hash=' + params.hash; var xhr = new XMLHttpRequest(); xhr.open('POST', url); xhr.timeout = 90000; if (xhr.upload && onProgress) { xhr.upload.onprogress = function (e) { if (e.lengthComputable) onProgress(Math.min(0.95, e.loaded / e.total)); }; } xhr.onload = function () { resolve(xhr.responseText || ''); }; xhr.onerror = function () { reject(new Error('网络错误')); }; xhr.ontimeout = function () { reject(new Error('上传超时')); }; xhr.send(fd); }); } function parseUploadResult(text) { var t = String(text).trim(); if (!t) throw new Error('上传接口无响应,可能登录已过期,请刷新页面重试'); var parts = t.split('|'); if (parts[0] === 'DISCUZUPLOAD') { var st = parseInt(parts[1], 10); var aid = parseInt(parts[2], 10); if (st === 0 && aid > 0) return { aid: aid, status: 0 }; return { aid: 0, status: (isNaN(st) ? -99 : st) }; } var n = parseInt(t, 10); if (!isNaN(n)) { if (n > 0) return { aid: n, status: 0 }; return { aid: 0, status: -n }; } throw new Error('上传响应无法识别'); } function handleImageFile(file, ta, form, total, index) { var opts = getOpts(); var label = (total > 1) ? ('图片 ' + index + '/' + total + ' ') : ''; return Promise.resolve().then(function () { if (file.size > opts.maxBytes) { showToast(label + '图片较大,压缩中…', false, 0); return compressBlob(file, opts).then(function (b) { return { blob: b, name: 'clipboard_compressed.jpg' }; }); } return { blob: file, name: file.name || 'clipboard.png' }; }).then(function (prepared) { showToast(label + '上传中…', false, 0); var onP = function (p) { showToast(label + '上传中… ' + Math.round(p * 100) + '%', false, 0); }; return uploadBlob(prepared.blob, cachedParams, onP).then(function (respText) { var r = parseUploadResult(respText); if (r.status === 0) return { aid: r.aid }; if (r.status === 3 || r.status === 5) { showToast(label + '体积超限,自动压缩后重试…', false, 0); return compressBlob(prepared.blob, { maxDim: opts.maxDim, quality: 0.7 }).then(function (b2) { return uploadBlob(b2, cachedParams, onP).then(function (respText2) { var r2 = parseUploadResult(respText2); if (r2.status === 0) return { aid: r2.aid }; throw new Error(STATUS_MSG[r2.status] || ('上传失败(状态' + r2.status + ')')); }); }); } if (r.status === 10) cachedParams = null; throw new Error(STATUS_MSG[r.status] || ('上传失败(状态' + r.status + ')')); }).then(function (ok) { insertIntoEditor(ta, '[attachimg]' + ok.aid + '[/attachimg]\n'); addAttachHiddenFields(form, ok.aid); showToast(label + '上传成功,已插入图片(附件号 ' + ok.aid + ')', false, 2600); }); }); } var uploadQueue = Promise.resolve(); function onPaste(e) { var ta = findEditorTextarea(e.target); if (!ta) return; var cd = e.clipboardData; if (!cd) return; var items = cd.items || []; var files = []; for (var i = 0; i < items.length; i++) { if (items[i].kind === 'file' && items[i].type.indexOf('image/') === 0) { var f = items[i].getAsFile(); if (f && f.size) files.push(f); } } if (!files.length && cd.files && cd.files.length) { for (var j = 0; j < cd.files.length; j++) { if (cd.files[j].type.indexOf('image/') === 0) files.push(cd.files[j]); } } if (!files.length) return; e.preventDefault(); e.stopPropagation(); var params = extractParams(); if (!params) { var all = collectPageText(); if (!/"uid"\s*:\s*["']\d+["']/.test(all) || /discuz_uid\s*=\s*['"]0['"]/.test(all)) { showToast('未登录,无法上传图片', true, 4000); } else { showToast('未在页面中找到上传参数(uid/hash),请刷新页面后重试', true, 5000); } return; } if (typeof window.wysiwyg !== 'undefined' && parseInt(window.wysiwyg, 10) === 1) { showToast('当前是所见即所得模式,请先切换到纯文本模式再粘贴图片', true, 5000); return; } var form = ta.closest ? ta.closest('form') : ta.form; if (!form) { showToast('未找到发帖表单', true, 4000); return; } var plainText = ''; try { plainText = cd.getData('text/plain') || ''; } catch (ex) { } if (plainText) insertIntoEditor(ta, plainText); var total = files.length; for (var k = 0; k < files.length; k++) { (function (file, idx) { uploadQueue = uploadQueue.then(function () { return handleImageFile(file, ta, form, total, idx + 1); }).catch(function (err) { showToast('图片上传失败:' + (err && err.message ? err.message : err), true, 6000); }); })(files[k], k); } } document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { var overlay = document.getElementById(POPUP_ID); if (overlay && overlay.style.display === 'flex') hidePopup(); } }); window.addEventListener('click', function (e) { var overlay = document.getElementById(POPUP_ID); if (overlay && e.target === overlay) hidePopup(); }); document.addEventListener('dragstart', function (e) { var overlay = document.getElementById(POPUP_ID); if (!overlay || overlay.style.display !== 'flex') return; var box = overlay._box; if (box._maximized) return; var bar = box.querySelector('div'); if (!bar || !bar.contains(e.target)) return; var shiftX = e.clientX - box.getBoundingClientRect().left; var shiftY = e.clientY - box.getBoundingClientRect().top; function move(ev) { var x = ev.clientX - shiftX; var y = ev.clientY - shiftY; overlay.style.alignItems = 'flex-start'; overlay.style.justifyContent = 'flex-start'; box.style.marginLeft = Math.max(0, x) + 'px'; box.style.marginTop = Math.max(0, y) + 'px'; } function up() { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); } document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); }); document.addEventListener('paste', onPaste, true); attachButtons(); var observerStarted = false; function startObserver() { var root = document.body || document.documentElement; if (!root) { document.addEventListener('DOMContentLoaded', startObserver, { once: true }); return; } if (observerStarted) return; observerStarted = true; new MutationObserver(function () { attachButtons(); applyPageWidthLimit(); }).observe(root, { childList: true, subtree: true }); } startObserver(); applyPageWidthLimit(); })();