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;
}
`);