baiduCloudInput

input method in browser based on baidu online input method.

Устаревшая версия за 19.10.2015. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==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.0
// @grant       GM_xmlhttpRequest
// ==/UserScript==
//
// DONE:
// : 弹窗相对于body的位置
// : 插入词而不是在结束时附加
// : 最上层!!
//
// TODO: CHIANFIND_RES特性
// TODO: 边沿检测特性
// TODO: 完善中文标点


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]);
  }
}, 3000); // 为了等待文本框装载进DOM

function initIME(tt) {
  //console.log("[DEBUG]", tt);
  var IME = {
    status: 'hidden',
    output: '',
    inputString: '',
    TEXTS: [],
    page: 0
  }
  
  var imePop = document.createElement('div');
  
  initImePop();
  tt.addEventListener('keydown', intercept); 
  
  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.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"
    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 (e.key) {
        case " ":
        case "1":
        case "2":
        case "3":
        case "4":
        case "5":
          e.preventDefault();
          var index = e.key == " "?0:parseInt(e.key) - 1;
          var curStart = tt.selectionStart;
          var selectedText = imePop.querySelector('ol').children[index].textContent;
          tt.value = tt.value.substring(0, curStart) + selectedText + tt.value.substring(curStart, tt.value.length);
          tt.selectionStart = curStart + selectedText.length;
          tt.selectionEnd = curStart + selectedText.length;
          IME.inputString = "";
          IME.status = 'hidden';
          showImePop(false);
          break;
        case "Backspace":
          e.preventDefault();
  
          IME.inputString = IME.inputString.substr(0, IME.inputString.length - 1);
          if (IME.inputString.length == 0) {
            IME.status = 'hidden';
            showImePop(false);
          }
          break;
        case "Enter":
          e.preventDefault();
          var curStart = tt.selectionStart;
          tt.value = tt.value.substring(0, curStart) + IME.inputString + tt.value.substring(curStart, tt.value.length);
          tt.selectionStart = curStart + IME.inputString.length;
          tt.selectionEnd = curStart + IME.inputString.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 += e.key;
          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 (e.key) {
        case ",":
          e.preventDefault();
          var curStart = tt.selectionStart;
          tt.value = tt.value.substring(0, curStart) + ',' + tt.value.substring(curStart, tt.value.length);
          tt.selectionStart = curStart + ','.length;
          tt.selectionEnd = curStart + ','.length;
          return;
          break;
        case ".":
          e.preventDefault();
          var curStart = tt.selectionStart;
          tt.value = tt.value.substring(0, curStart) + '。' + tt.value.substring(curStart, 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 += e.key;
            IME.status = 'POPUP';
            showImePop(true);
          }
          IME.page = 0;
          break;
        default:
          void(0);
      }
    }
    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=20&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 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;
  }
}