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