// ==UserScript== // @name 能不能好好说话?(手机端) // @namespace https://lab.magiconch.com/nbnhhsh // @version 0.17 // @description 首字母缩写划词翻译工具,适配手机端浏览器 // @author itorr // @license MIT // @icon https://lab.magiconch.com/favicon.ico // @match *://weibo.com/* // @match *://*.weibo.com/* // @match *://*.weibo.cn/* // @match *://tieba.baidu.com/* // @match *://*.bilibili.com/ // @match *://*.bilibili.com/* // @match *://*.douban.com/group/* // @require https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js // @inject-into content // @grant none // @downloadURL none // ==/UserScript== let Nbnhhsh = ((htmlText, cssText) => { const API_URL = 'https://lab.magiconch.com/api/nbnhhsh/'; const request = (method, url, data, onOver) => { let x = new XMLHttpRequest(); x.open(method, url); x.setRequestHeader('content-type', 'application/json'); x.withCredentials = true; x.onload = () => onOver(x.responseText ? JSON.parse(x.responseText) : null); x.send(JSON.stringify(data)); return x; }; const Guess = {}; const guess = (text, onOver) => { text = text.match(/[a-z0-9]{2,}/ig)?.join(','); if (!text) { app.show = false; return; } if (Guess[text]) { return onOver(Guess[text]); } if (guess._request) { guess._request.abort(); } app.loading = true; guess._request = request('POST', API_URL + 'guess', { text }, data => { Guess[text] = data; onOver(data); app.loading = false; }); }; const submitTran = name => { let text = prompt('输入缩写对应文字 末尾可通过括号包裹(简略注明来源)', ''); if (!text || !text.trim()) { return; } request('POST', API_URL + 'translation/' + name, { text }, () => { alert('感谢对好好说话项目的支持!审核通过后这条对应将会生效'); }); }; const transArrange = trans => { return trans.map(tran => { const match = tran.match(/^(.+?)([(\(](.+?)[)\)])?$/); if (match && match.length === 4) { return { text: match[1], sub: match[3] }; } return { text: tran }; }); }; const getSelectionText = () => { let text = window.getSelection().toString().trim(); if (text && /[a-z0-9]/i.test(text)) { return text; } return null; }; const fixPosition = () => { let selection = window.getSelection(); if (!selection.rangeCount) { app.show = false; return; } let rect = selection.getRangeAt(0).getBoundingClientRect(); let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; let viewportHeight = window.innerHeight; let viewportWidth = window.innerWidth; let top = scrollTop + rect.bottom + 20; // 增加20px余量以避免被光标遮挡 let left = rect.left; // 确保弹窗不超出屏幕 if (top + 200 > scrollTop + viewportHeight) { top = scrollTop + rect.top - 200; // 显示在选中文字上方 } if (left + 300 > viewportWidth) { left = viewportWidth - 300; // 防止水平溢出 } if (left < 10) { left = 10; // 左边距 } if (top <= 0 || left <= 0) { app.show = false; return; } app.top = Math.floor(top); app.left = Math.floor(left); }; const timer = () => { if (getSelectionText()) { setTimeout(timer, 300); } else { app.show = false; } }; const nbnhhsh = () => { let text = getSelectionText(); app.show = !!text && /[a-z0-9]/i.test(text); if (!app.show) { return; } fixPosition(); guess(text, data => { if (!data || !data.length) { app.show = false; } else { app.tags = data; } }); setTimeout(timer, 300); }; // 触摸事件处理 let touchTimeout; let isProcessing = false; const handleTouchEnd = (e) => { if (isProcessing) return; isProcessing = true; clearTimeout(touchTimeout); touchTimeout = setTimeout(() => { if (getSelectionText()) { nbnhhsh(); } isProcessing = false; }, 100); // 减少延迟到100ms以提高响应速度 }; document.body.addEventListener('touchend', handleTouchEnd); // 防止触摸滑动时误触发 document.body.addEventListener('touchmove', () => clearTimeout(touchTimeout)); // 触摸开始时重置处理状态 document.body.addEventListener('touchstart', () => { isProcessing = false; }); const createEl = html => { createEl._el.innerHTML = html; let el = createEl._el.children[0]; document.body.appendChild(el); return el; }; createEl._el = document.createElement('div'); createEl(``); const el = createEl(htmlText); const app = new Vue({ el, data: { tags: [], show: false, loading: false, top: 0, left: 0, }, methods: { submitTran, transArrange, } }); return { guess, submitTran, transArrange, }; })(`