Greasy Fork is available in English.
首字母缩写划词翻译工具
// ==UserScript== // @name 能不能好好说话? // @namespace https://lab.magiconch.com/nbnhhsh // @version 0.15 // @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 // ==/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(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 || !text.trim()){ return; } request('POST',API_URL+'translation/'+name,{text},()=>{ alert('感谢对好好说话项目的支持!审核通过后这条对应将会生效'); }); }; const transArrange = trans=>{ return trans.map(tran=>{ const match = tran.match(/^(.+?)([(\(](.+?)[)\)])?$/); if(match.length === 4){ return { text:match[1], sub:match[3] } }else{ return { text:tran } } }) }; const getSelectionText = _=>{ let text = getSelection().toString().trim(); if(!!text && /[a-z0-9]/i.test(text)){ return text; }else{ return null; } }; const fixPosition = _=>{ let rect = getSelection().getRangeAt(0).getBoundingClientRect(); const activeEl = document.activeElement; if(['TEXTAREA','INPUT'].includes(activeEl.tagName)) rect = activeEl.getBoundingClientRect(); let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; let top = Math.floor( scrollTop + rect.top +rect.height ); let left = Math.floor( rect.left ); if(top === 0 && left === 0){ app.show = false; } app.top = top; app.left = 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.length){ app.show = false; }else{ app.tags = data; } }); setTimeout(timer,300); }; const _nbnhhsh = _=>{ setTimeout(nbnhhsh,1); }; document.body.addEventListener('mouseup',_nbnhhsh); document.body.addEventListener('keyup',_nbnhhsh); 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(`<style>${cssText}</style>`); 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, } })(` <div class="nbnhhsh-box nbnhhsh-box-pop" v-if="show" :style="{top:top+'px',left:left+'px'}" @mousedown.prevent> <div class="nbnhhsh-loading" v-if="loading"> 加载中… </div> <div class="nbnhhsh-tag-list" v-else-if="tags.length"> <div class="nbnhhsh-tag-item" v-for="tag in tags"> <h4>{{tag.name}}</h4> <div class="nbnhhsh-tran-list" v-if="tag.trans"> <span class="nbnhhsh-tran-item" v-for="tran in transArrange(tag.trans)"> {{tran.text}}<sub v-if="tran.sub">{{tran.sub}}</sub> </span> </div> <div class="nbnhhsh-notran-box" v-else-if="tag.trans===null"> 无对应文字 </div> <div v-else-if="tag.inputting && tag.inputting.length !==0"> <div class="nbnhhsh-inputting-list"> <h5>有可能是</h5> <span class="nbnhhsh-inputting-item" v-for="input in tag.inputting">{{input}}</span> </div> </div> <div class="nbnhhsh-notran-box" v-else @click.prevent="submitTran(tag.name)"> 尚未录入,我来提交对应文字 </div> <a v-if="tag.trans!==null" @click.prevent="submitTran(tag.name)" class="nbnhhsh-add-btn" title="我来提交对应文字"></a> </div> </div> </div> `, ` .nbnhhsh-box{ font:400 14px/1.4 sans-serif; color:#333; } .nbnhhsh-box-pop{ position: absolute; z-index:99999999999; width: 340px; background:#FFF; box-shadow: 0 3px 30px -4px rgba(0,0,0,.3); margin: 10px 0 100px 0; } .nbnhhsh-box-pop::before{ content: ''; position: absolute; top:-7px; left:8px; width: 0; height: 0; border:7px solid transparent; border-top:1px; border-bottom-color:#FFF; } .nbnhhsh-box sub{ vertical-align: middle; background: rgba(0,0,0,.07); color: #777; font-size: 12px; line-height:16px; display: inline-block; padding: 0 3px; margin:-2px 0 0 2px; border-radius: 2px; letter-spacing: -0.6px; bottom:0; } .nbnhhsh-tag-list{ /*padding:4px 0;*/ } .nbnhhsh-tag-item{ padding:4px 14px; position: relative; } .nbnhhsh-tag-item:nth-child(even){ background: rgba(0, 99, 255, 0.06); } .nbnhhsh-tag-item h4{ font-weight:bold; font-size:20px; line-height:28px; letter-spacing: 1.5px; margin:0; } .nbnhhsh-tran-list{ color:#444; padding: 0 0 4px 0; line-height:18px; } .nbnhhsh-tran-item{ display: inline-block; padding: 2px 15px 2px 0; } .nbnhhsh-inputting-list{ color:#222; padding: 0 0 4px 0; } .nbnhhsh-inputting-list h5{ font-size:12px; line-height:24px; color:#999; margin:0; } .nbnhhsh-inputting-item{ margin-right:14px; display:inline-block; } .nbnhhsh-notran-box{ padding:4px 0; color:#999; cursor: pointer; } .nbnhhsh-add-btn{ position: absolute; top:0; right:0; width: 30px; line-height: 30px; text-align: center; color: #0059ff; font-size: 16px; font-weight: bold; cursor: pointer; } .nbnhhsh-add-btn:after{ content: '+'; } .nbnhhsh-loading{ text-align: center; color:#999; padding:20px 0; } `);