translator

划词翻译

当前为 2018-02-05 提交的版本,查看 最新版本

  1. 'use strict';
  2.  
  3. var css = "._panel_nvbn4_1 {\n position: fixed;\n max-width: 300px;\n z-index: 10000;\n}\n ._panel_nvbn4_1 * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n._body_nvbn4_11 {\n position: relative;\n padding: 8px;\n border-radius: 4px;\n border: 1px solid #eaeaea;\n line-height: 24px;\n color: #555;\n background-color: #fff;\n font-family: monospace, consolas;\n font-size: 14px;\n text-align: left;\n word-break: break-all;\n}\n._header_nvbn4_24 {\n padding: 0 0 8px;\n border-bottom: 1px dashed #aaa;\n color: #333;\n}\n._header_nvbn4_24 > a {\n margin-left: 8px;\n color: #7cbef0;\n cursor: pointer;\n font-size: 13px;\n }\n._detail_nvbn4_35 {\n margin: 8px 0 0;\n line-height: 22px;\n list-style: none;\n font-size: 13px;\n}\n._detail_nvbn4_35 > li {\n font-size: 13px;\n line-height: 26px;\n }\n";
  4. var classMap = {
  5. "panel": "_panel_nvbn4_1",
  6. "body": "_body_nvbn4_11",
  7. "header": "_header_nvbn4_24",
  8. "detail": "_detail_nvbn4_35"
  9. };
  10.  
  11. // ==UserScript==
  12. // @name translator
  13. // @namespace https://lufei.so
  14. // @supportURL https://github.com/intellilab/translator.user.js
  15. // @description 划词翻译
  16. // @version 1.5.10
  17. // @run-at document-start
  18. // @grant GM_addStyle
  19. // @grant GM_xmlhttpRequest
  20. // @include *
  21. // ==/UserScript==
  22. GM_addStyle(css);
  23. var translator = initialize();
  24.  
  25. function createElement(tagName, props, attrs) {
  26. var el = document.createElement(tagName);
  27.  
  28. if (props) {
  29. Object.keys(props).forEach(function (key) {
  30. el[key] = props[key];
  31. });
  32. }
  33.  
  34. if (attrs) {
  35. Object.keys(attrs).forEach(function (key) {
  36. el.setAttribute(key, attrs[key]);
  37. });
  38. }
  39.  
  40. return el;
  41. }
  42.  
  43. function render(data) {
  44. var body = translator.body,
  45. audio = translator.audio;
  46. body.innerHTML = '';
  47. var basic = data.basic,
  48. query = data.query,
  49. translation = data.translation;
  50.  
  51. if (basic) {
  52. var explains = basic.explains,
  53. us = basic['us-phonetic'],
  54. uk = basic['uk-phonetic'];
  55. var noPhonetic = '♥';
  56. var header = createElement('div', {
  57. className: classMap.header
  58. });
  59. header.appendChild(createElement('span', {
  60. textContent: query
  61. }));
  62. header.appendChild(createElement('a', {
  63. innerHTML: `uk: [${uk || noPhonetic}]`
  64. }, {
  65. 'data-type': 1
  66. }));
  67. header.appendChild(createElement('a', {
  68. innerHTML: `us: [${us || noPhonetic}]`
  69. }, {
  70. 'data-type': 2
  71. }));
  72. header.appendChild(createElement('a', {
  73. textContent: '详情'
  74. }, {
  75. target: '_blank',
  76. href: `http://dict.youdao.com/search?q=${encodeURIComponent(query)}`
  77. }));
  78. body.appendChild(header);
  79. header.addEventListener('click', function (e) {
  80. var type = e.target.dataset.type;
  81.  
  82. if (type) {
  83. audio.src = `https://dict.youdao.com/dictvoice?audio=${encodeURIComponent(query)}&type=${type}`;
  84. }
  85. });
  86.  
  87. if (explains) {
  88. var ul = createElement('ul', {
  89. className: classMap.detail
  90. });
  91.  
  92. for (var i = 0; i < explains.length; i += 1) {
  93. var li = createElement('li', {
  94. innerHTML: explains[i]
  95. });
  96. ul.appendChild(li);
  97. }
  98.  
  99. body.appendChild(ul);
  100. }
  101. } else if (translation) {
  102. var div = createElement('div', {
  103. innerHTML: translation[0]
  104. });
  105. body.appendChild(div);
  106. }
  107. }
  108.  
  109. function translate(e) {
  110. var sel = window.getSelection();
  111. var text = sel.toString();
  112. if (/^\s*$/.test(text)) return;
  113. var _document = document,
  114. activeElement = _document.activeElement;
  115. if (['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) < 0 && !activeElement.contains(sel.getRangeAt(0).startContainer)) return;
  116. var query = {
  117. type: 'data',
  118. doctype: 'json',
  119. version: '1.1',
  120. relatedUrl: 'http://fanyi.youdao.com/',
  121. keyfrom: 'fanyiweb',
  122. key: null,
  123. translate: 'on',
  124. q: text,
  125. ts: Date.now()
  126. };
  127. var qs = Object.keys(query).map(function (key) {
  128. return `${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`;
  129. }).join('&');
  130. GM_xmlhttpRequest({
  131. method: 'GET',
  132. url: `https://fanyi.youdao.com/openapi.do?${qs}`,
  133.  
  134. onload(res) {
  135. var data = JSON.parse(res.responseText);
  136.  
  137. if (!data.errorCode) {
  138. render(data);
  139. var panel = translator.panel;
  140. var _window = window,
  141. innerWidth = _window.innerWidth,
  142. innerHeight = _window.innerHeight;
  143.  
  144. if (e.clientY > innerHeight * 0.5) {
  145. panel.style.top = 'auto';
  146. panel.style.bottom = `${innerHeight - e.clientY + 10}px`;
  147. } else {
  148. panel.style.top = `${e.clientY + 10}px`;
  149. panel.style.bottom = 'auto';
  150. }
  151.  
  152. if (e.clientX > innerWidth * 0.5) {
  153. panel.style.left = 'auto';
  154. panel.style.right = `${innerWidth - e.clientX}px`;
  155. } else {
  156. panel.style.left = `${e.clientX}px`;
  157. panel.style.right = 'auto';
  158. }
  159.  
  160. document.body.appendChild(panel);
  161. }
  162. }
  163.  
  164. });
  165. }
  166.  
  167. function debounce(func, delay) {
  168. var timer;
  169.  
  170. function exec() {
  171. timer = null;
  172. func.apply(void 0, arguments);
  173. }
  174.  
  175. return function () {
  176. if (timer) clearTimeout(timer);
  177.  
  178. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  179. args[_key] = arguments[_key];
  180. }
  181.  
  182. timer = setTimeout.apply(void 0, [exec, delay].concat(args));
  183. };
  184. }
  185.  
  186. function initialize() {
  187. var audio = createElement('audio', {
  188. autoplay: true
  189. });
  190. var panel = createElement('div', {
  191. className: classMap.panel
  192. });
  193. var panelBody = createElement('div', {
  194. className: classMap.body
  195. });
  196. panel.appendChild(panelBody);
  197. var debouncedTranslate = debounce(translate);
  198. var isSelecting;
  199. document.addEventListener('mousedown', function (e) {
  200. isSelecting = false;
  201. if (panel.contains(e.target)) return;
  202. if (panel.parentNode) panel.parentNode.removeChild(panel);
  203. panelBody.innerHTML = '';
  204. }, true);
  205. document.addEventListener('mousemove', function () {
  206. isSelecting = true;
  207. }, true);
  208. document.addEventListener('mouseup', function (e) {
  209. if (panel.contains(e.target) || !isSelecting) return;
  210. debouncedTranslate(e);
  211. }, true);
  212. document.addEventListener('dblclick', function (e) {
  213. if (panel.contains(e.target)) return;
  214. debouncedTranslate(e);
  215. }, true);
  216. return {
  217. audio,
  218. panel,
  219. body: panelBody
  220. };
  221. }