baiduCloudInput

input method in browser based on baidu online input method.

Od 20.10.2015.. Pogledajte najnovija verzija.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        baiduCloudInput
// @name:zh-CN  百度云输入法
// @namespace   [email protected]
// @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;
    }
}