- // ==UserScript==
- // @name baiduCloudInput
- // @name:zh-CN 百度云输入法
- // @namespace baiduIME@reverland.org
- // @description input method in browser based on baidu online input method.
- // @description:zh-CN 在浏览器中自由使用百度在线输入法
- // @include *
- // @version 1.2
- // @grant GM_xmlhttpRequest
- // ==/UserScript==
- //
- // DONE:
- // : 弹窗相对于body的位置
- // : 插入词而不是在结束时附加
- // : 最上层!!
- // : ff/chromium兼容
- //
- // TODO: CHIANFIND_RES特性
- // TODO: 边沿检测特性
- // TODO: 完善中文标点
- //
- // `+/-` 翻页
- // `Space/1/2/3/4/5` 选词
- // `Shift` 全角/半角逗号句号
- //
-
- document.body.addEventListener('keydown', configQuanjiao);
- function configQuanjiao(e) {
- if (e.which == 16) {
- IME.quanjiao = !IME.quanjiao;
- console.log(e);
- e.preventDefault();
- }
- }
-
- var IME = {
- status: 'hidden',
- output: '',
- inputString: '',
- TEXTS: [],
- page: 0,
- quanjiao: true,
- }
-
-
- setTimeout(function() {
- var tts = document.getElementsByTagName("textarea");
- for(var i = 0; i < tts.length; i++) {
- initIME(tts[i]);
- }
- var tts = document.getElementsByTagName("input");
- for(var i = 0; i < tts.length; i++) {
- initIME(tts[i]);
- }
- }, 2000); // 为了等待文本框装载进DOM
-
- function initIME(tt) {
- console.log("[DEBUG]", tt);
-
- var imePop = document.createElement('div');
-
- initImePop();
-
- tt.addEventListener('keydown', checkNonCharacter);
- tt.addEventListener('keyup', reqAndRefresh);
-
- tt.addEventListener('keypress', intercept);
-
- function checkNonCharacter(e) {
- if (IME.status == 'POPUP') {
- switch (String.fromCharCode(e.which)) {
- case String.fromCharCode(8):
- e.preventDefault();
-
- IME.inputString = IME.inputString.substr(0, IME.inputString.length - 1);
- if (IME.inputString.length == 0) {
- IME.status = 'hidden';
- showImePop(false);
- }
- break;
- case String.fromCharCode(13):
- e.preventDefault();
- var curStart = tt.selectionStart;
- var curEnd = tt.selectionEnd;
- tt.value = tt.value.substring(0, curStart) + IME.inputString + tt.value.substring(curEnd, tt.value.length);
- tt.selectionStart = curStart + IME.inputString.length;
- tt.selectionEnd = curStart + IME.inputString.length;
-
- IME.inputString = "";
- IME.status = 'hidden';
- showImePop(false);
- break;
- }
- }
- imePop.querySelector('p').innerHTML = IME.inputString;
- }
-
- function reqAndRefresh(e) {
- imePop.querySelector('p').innerHTML = IME.inputString;
- // reconize key finish
- // console.log("[IME.inputString] ", IME.inputString);
-
- var p = new Promise(function(resolve, reject) {
- var ret = GM_xmlhttpRequest({
- method: "GET",
- url: `http://olime.baidu.com/py?input=${IME.inputString}&inputtype=py&bg=0&ed=100&result=hanzi&resultcoding=unicode&ch_en=0&clientinfo=web&version=1`,
- onload: function(res) {
- //console.log("[DEBUG connect]")
- resolve(res.responseText);
- }
- })
- });
-
- p.then(parseJSON).then(parseRes, printError);
- }
-
- function initImePop() {
- imePop.setAttribute('id', 'baidu-cloud-input-imePop');
- imePop.style.position = "absolute";
- imePop.style.width = "300px";
- //imePop.style.height = "80px";
- imePop.style.background = "lightblue";
- imePop.style.borderRadius = "5px";
- imePop.style.display = "none";
- imePop.style.boxShadow = "0 0 3px 0px black"
- imePop.style.zIndex = "9999999";
- var echo = document.createElement('p');
- echo.style.height = "1.5em"; //只为防止抖动
- echo.style.lineHeight = "1.5em";
- echo.style.fontSize = "1em";
- echo.style.margin = "0";
- echo.style.padding = "0";
- echo.style.paddingLeft = "0.5em";
- echo.style.color = "darkblue";
- echo.style.fontStyle = "bold";
- imePop.appendChild(echo);
- var tips = document.createElement('ol');
- tips.style.margin = "0px";
- tips.style.padding = "0px";
- tips.style.color = "black";
- var tip = [];
- for (var i = 0; i < 5; i++) {
- tip[i] = document.createElement('li');
- tip[i].style.margin = "0px";
- tip[i].style.padding = "0px";
- tip[i].style.marginLeft = "2em";
- tip[i].style.listStyleType = "decimal";
- tips.appendChild(tip[i]);
- }
- document.body.appendChild(imePop);
- var hr = document.createElement('hr')
- hr.style.marginTop = "0";
- hr.style.marginBottom = "0.2em"
- hr.style.color = "grey";
- imePop.appendChild(hr);
- imePop.appendChild(tips);
- }
-
- function showImePop(state) {
- if (state) {
- var coordinates = getCaretCoordinates(tt, tt.selectionEnd);
- var textAreaTop = findPos(tt)[1] + 20;
- var textAreaLeft = findPos(tt)[0];
- imePop.style.left = textAreaLeft + coordinates.left + "px";
- imePop.style.top = textAreaTop -tt.scrollTop + coordinates.top + "px";
- imePop.style.display = "block";
- } else {
- imePop.style.display = 'none';
- }
- }
-
- function findPos(obj) {
- var curleft = curtop = 0;
- if (obj.offsetParent) {
- do {
- curleft += obj.offsetLeft;
- curtop += obj.offsetTop;
- } while (obj = obj.offsetParent);
- }
- return [curleft,curtop];
- }
-
-
- function intercept(e){
- // control keys
- if (e.ctrlKey) {
- return;
- }
- if (IME.status == 'POPUP') {
- switch (String.fromCharCode(e.which)) {
- case " ":
- case "1":
- case "2":
- case "3":
- case "4":
- case "5":
- e.preventDefault();
- var index = String.fromCharCode(e.which) == " "?0:parseInt(String.fromCharCode(e.which)) - 1;
- console.log(index);
- var curStart = tt.selectionStart;
- var curEnd = tt.selectionEnd;
- var selectedText = imePop.querySelector('ol').children[index].textContent;
- tt.value = tt.value.substring(0, curStart) + selectedText + tt.value.substring(curEnd, tt.value.length);
- tt.selectionStart = curStart + selectedText.length;
- tt.selectionEnd = curStart + selectedText.length;
- IME.inputString = "";
- IME.status = 'hidden';
- showImePop(false);
- break;
- case "a":
- case "b":
- case "c":
- case "d":
- case "e":
- case "f":
- case "g":
- case "h":
- case "i":
- case "j":
- case "k":
- case "l":
- case "m":
- case "n":
- case "o":
- case "p":
- case "q":
- case "r":
- case "s":
- case "t":
- case "u":
- case "v":
- case "w":
- case "x":
- case "y":
- case "z":
- case "'":
- e.preventDefault();
- IME.inputString += String.fromCharCode(e.which);
- break;
- // {
- case "=":
- e.preventDefault();
- IME.page += 1;
- //console.log("[DEBUG]", IME.page);
- if (IME.page < IME.TEXTS.length / 5) {
- updateList(IME.page);
- } else {
- IME.page -= 1;
- }
- return;
- case "-":
- e.preventDefault();
- IME.page = IME.page == 0?IME.page:IME.page - 1;
- //console.log("[DEBUG]", IME.page);
- updateList(IME.page);
- return;
- // }
- default:
- e.preventDefault();
- }
- } else if (IME.status == 'hidden') {
- switch (String.fromCharCode(e.which)) {
- case ",":
- if (IME.quanjiao) {
- e.preventDefault();
- var curStart = tt.selectionStart;
- var curEnd = tt.selectionEnd;
- tt.value = tt.value.substring(0, curStart) + ',' + tt.value.substring(curEnd, tt.value.length);
- tt.selectionStart = curStart + ','.length;
- tt.selectionEnd = curStart + ','.length;
- return;
- }
- break;
- case ".":
- if (IME.quanjiao) {
- e.preventDefault();
- var curStart = tt.selectionStart;
- var curEnd = tt.selectionEnd;
- tt.value = tt.value.substring(0, curStart) + '。' + tt.value.substring(curEnd, tt.value.length);
- tt.selectionStart = curStart + '。'.length;
- tt.selectionEnd = curStart + '。'.length;
- return;
- }
- break;
- case "a":
- case "b":
- case "c":
- case "d":
- case "e":
- case "f":
- case "g":
- case "h":
- case "i":
- case "j":
- case "k":
- case "l":
- case "m":
- case "n":
- case "o":
- case "p":
- case "q":
- case "r":
- case "s":
- case "t":
- case "u":
- case "v":
- case "w":
- case "x":
- case "y":
- case "z":
- case "'":
- e.preventDefault();
- if (IME.inputString.length == 0) {
- IME.inputString += String.fromCharCode(e.which);
- IME.status = 'POPUP';
- showImePop(true);
- }
- IME.page = 0;
- break;
- default:
- void(0);
- }
- }
- };
-
- function printError(err) {
- console.log(err);
- };
-
- function parseRes(resObj) {
- // console.log("[resObj]", resObj);
- if (resObj['errno'] != 0) {
- return;
- }
- var text = resObj['result'][0];
- // console.log("[text]", text[0][0])
- for (var i = 0; i < text.length; i++) {
- IME.TEXTS[i] = text[i][0];
- }
- updateList(IME.page);
- }
-
- function updateList(page) {
- for (var i = 0; i < 5; i++) {
- imePop.querySelector('ol').children[i].innerHTML = IME.TEXTS[page * 5 + i];
- if (page * 5 + i >= IME.TEXTS.length) {
- imePop.querySelector('ol').children[i].innerHTML = "--"
- }
- }
- }
-
- function parseJSON(text) {
- // console.log("JSON response from baidu: ", text);
- var resObj = JSON.parse(text);
- return resObj;
- }
-
- // this function comes from https://github.com/component/textarea-caret-position/blob/master/index.js
- function getCaretCoordinates(element, position) {
- var properties = [
- 'direction', // RTL support
- 'boxSizing',
- 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
- 'height',
- 'overflowX',
- 'overflowY', // copy the scrollbar for IE
-
- 'borderTopWidth',
- 'borderRightWidth',
- 'borderBottomWidth',
- 'borderLeftWidth',
- 'borderStyle',
-
- 'paddingTop',
- 'paddingRight',
- 'paddingBottom',
- 'paddingLeft',
-
- // https://developer.mozilla.org/en-US/docs/Web/CSS/font
- 'fontStyle',
- 'fontVariant',
- 'fontWeight',
- 'fontStretch',
- 'fontSize',
- 'fontSizeAdjust',
- 'lineHeight',
- 'fontFamily',
-
- 'textAlign',
- 'textTransform',
- 'textIndent',
- 'textDecoration', // might not make a difference, but better be safe
-
- 'letterSpacing',
- 'wordSpacing',
-
- 'tabSize',
- 'MozTabSize'
-
- ];
- // mirrored div
- var div = document.createElement('div');
- div.id = 'input-textarea-caret-position-mirror-div';
- document.body.appendChild(div);
-
- var style = div.style;
- var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
-
- // default textarea styles
- style.whiteSpace = 'pre-wrap';
- if (element.nodeName !== 'INPUT')
- style.wordWrap = 'break-word'; // only for textarea-s
-
- // position off-screen
- style.position = 'absolute'; // required to return coordinates properly
- style.visibility = 'hidden'; // not 'display: none' because we want rendering
-
- // transfer the element's properties to the div
- properties.forEach(function (prop) {
- style[prop] = computed[prop];
- });
-
- var isFirefox = window.mozInnerScreenX != null;
- if (isFirefox) {
- // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
- if (element.scrollHeight > parseInt(computed.height))
- style.overflowY = 'scroll';
- } else {
- style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
- }
-
- div.textContent = element.value.substring(0, position);
- // the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
- if (element.nodeName === 'INPUT')
- div.textContent = div.textContent.replace(/\s/g, "\u00a0");
-
- var span = document.createElement('span');
- // Wrapping must be replicated *exactly*, including when a long word gets
- // onto the next line, with whitespace at the end of the line before (#7).
- // The *only* reliable way to do that is to copy the *entire* rest of the
- // textarea's content into the <span> created at the caret position.
- // for inputs, just '.' would be enough, but why bother?
- span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
- div.appendChild(span);
-
- var coordinates = {
- top: span.offsetTop + parseInt(computed['borderTopWidth']),
- left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
- };
-
- document.body.removeChild(div);
-
- return coordinates;
- }
- }
-
-