├── LICENSE ├── README.md └── translate ├── translate-android.png ├── translate-dictionary-android.png ├── translate-dictionary.js ├── translate-dictionary.png ├── translate.js └── translate.png /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tampermonkey 油猴子脚本 2 | 3 | ## translate.js 4 | 5 | ![translate.png](translate/translate.png) 6 | 7 | ![translate.png](translate/translate-android.png) 8 | 9 | 特色: 10 | 11 | * 划词翻译调用“金山词霸、有道词典(有道翻译)、Google Translate(谷歌翻译)、沪江小D、搜狗翻译、必应词典(必应翻译)、Microsoft Translator(必应在线翻译)、DeepL翻译、海词词典、百度翻译、Oxford Learner's Dictionaries、Oxford Dictionaries、Merriam-Webster、PDF 划词翻译、Google Search、Bing Search(必应搜索)、百度搜索、Wikipedia Search(维基百科搜索)”网页翻译 12 | * 支持浏览器:Google Chrome、Firefox、Safari、Firefox for Android(无拖动) 13 | * 支持顺序自定义 14 | * 支持显示更多图标 15 | * 支持图标拖动 16 | 17 | ### FAQ 18 | 19 | For advanced users: 20 | 21 | How do I add more sites? 22 | 23 | Just add it to the JSON array `iconArray`. 24 | 25 | ```javascript 26 | { 27 | name: 'Site name', 28 | id: 'Unique ID (combination of upper and lower case letters)', 29 | image: 'Convert image to Base64', 30 | host: ['example.com', 'dict.example.com', 'more.example.com'], 31 | popup(text) { 32 | popupCenter(`https://dict.example.com/search=${encodeURIComponent(text)}`, null, 800, screen.height); 33 | }, 34 | custom(text) { /*JavaScript code that is executed after the above domains page is loaded.*/ } 35 | } 36 | ``` 37 | 38 | Don't forget to change the version number (`@version`) to prevent automatic updates. 39 | 40 | ## translate-dictionary.js 41 | 42 | ![translate-dictionary.png](translate/translate-dictionary.png) 43 | 44 | ![translate-dictionary.png](translate/translate-dictionary-android.png) 45 | 46 | 特色: 47 | 48 | * 划词翻译调用“有道词典(有道翻译)、金山词霸、Bing 词典(必应词典)、剑桥高阶、沪江小D、谷歌翻译” 49 | * 支持浏览器:Google Chrome、Firefox、Safari(无发音)、Firefox for Android(无拖动) 50 | * 支持发音 51 | * 支持面板拖动 52 | * 支持面板自动调整位置 53 | 54 | ## 同时安装 55 | 56 | ① 划词翻译:多词典查询 57 | 58 | [https://greasyfork.org/zh-CN/scripts/376313-%E5%88%92%E8%AF%8D%E7%BF%BB%E8%AF%91-%E5%A4%9A%E8%AF%8D%E5%85%B8%E6%9F%A5%E8%AF%A2](https://greasyfork.org/zh-CN/scripts/376313-%E5%88%92%E8%AF%8D%E7%BF%BB%E8%AF%91-%E5%A4%9A%E8%AF%8D%E5%85%B8%E6%9F%A5%E8%AF%A2) 59 | 60 | ② Translate 61 | 62 | [https://greasyfork.org/zh-CN/scripts/34921-translate](https://greasyfork.org/zh-CN/scripts/34921-translate) 63 | 64 | 可以使用更多的翻译引擎! 65 | 66 | ## 脚本下载地址 67 | 68 | [https://greasyfork.org/zh-CN/users/158488-barrer](https://greasyfork.org/zh-CN/users/158488-barrer) 69 | 70 | ### 使用前 71 | 72 | [https://tampermonkey.net/](https://tampermonkey.net/) 73 | 74 | 下载对应浏览器的扩展程序 75 | 76 | Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器,它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。 77 | 78 | 虽然有些受支持的浏览器拥有原生的用户脚本支持,但 Tampermonkey 将在您的用户脚本管理方面提供更多的便利。 它提供了诸如便捷脚本安装、自动更新检查、标签中的脚本运行状况速览、内置的编辑器等众多功能, 同时Tampermonkey还有可能正常运行原本并不兼容的脚本。 79 | 80 | 它可以很快的安装好,来试一试吧! 81 | 82 | 脚本名 | 用途 83 | -|- 84 | translate.js|划词翻译调用“金山词霸、有道词典(有道翻译)、Google Translate(谷歌翻译)、沪江小D、搜狗翻译、必应词典(必应翻译)、Microsoft Translator(必应在线翻译)、DeepL翻译、海词词典、百度翻译、Oxford Learner's Dictionaries、Oxford Dictionaries、Merriam-Webster、PDF 划词翻译、Google Search、Bing Search(必应搜索)、百度搜索、Wikipedia Search(维基百科搜索)”网页翻译 85 | translate-dictionary.js|划词翻译调用“有道词典(有道翻译)、金山词霸、Bing 词典(必应词典)、剑桥高阶、沪江小D、谷歌翻译” 86 | -------------------------------------------------------------------------------- /translate/translate-android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrer/tampermonkey-script/e9f53bf9408ac54aab1740c109596fde374d8b8d/translate/translate-android.png -------------------------------------------------------------------------------- /translate/translate-dictionary-android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barrer/tampermonkey-script/e9f53bf9408ac54aab1740c109596fde374d8b8d/translate/translate-dictionary-android.png -------------------------------------------------------------------------------- /translate/translate-dictionary.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 划词翻译:多词典查询 3 | // @namespace http://tampermonkey.net/ 4 | // @version 10.17 5 | // @description 划词翻译调用“有道词典(有道翻译)、金山词霸、Bing 词典(必应词典)、剑桥高阶、沪江小D、谷歌翻译” 6 | // @author https://github.com/barrer 7 | // @license https://www.apache.org/licenses/LICENSE-2.0 8 | // @match http://*/* 9 | // @include https://*/* 10 | // @include file:///* 11 | // @connect youdao.com 12 | // @connect iciba.com 13 | // @connect translate.google.com 14 | // @connect hjenglish.com 15 | // @connect bing.com 16 | // @connect chinacloudapi.cn 17 | // @connect cambridge.org 18 | // @grant GM_xmlhttpRequest 19 | // ==/UserScript== 20 | 21 | /* 22 | * Copyright 2019-2024 https://github.com/barrer. 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * https://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | (() => { 38 | 'use strict'; 39 | 40 | // Your code here... 41 | /**联网权限*/ 42 | // @connect youdao.com 有道词典 43 | // @connect iciba.com 金山词霸 44 | // @connect translate.google.com 谷歌翻译 45 | // @connect hjenglish.com 沪江小D 46 | // @connect bing.com 必应词典 47 | // @connect chinacloudapi.cn 必应词典-发音 48 | // @connect cambridge.org 剑桥高阶 49 | // 注意:自定义变量修改后把 “@version” 版本号改为 “10000” 防止自动更新 50 | // >---- 可以自定义的变量 ----- 51 | const fontSize = 14; // 字体大小[可自定义] 52 | const iconWidth = 300; // 整个面板宽度[可自定义] 53 | const iconHeight = 400; // 整个面板高度[可自定义] 54 | // ----- 可以自定义的变量 ----< 55 | /**样式*/ 56 | const style = document.createElement('style'); 57 | const trContentWidth = iconWidth - 16; // 整个面板宽度 - 边距间隔 = 翻译正文宽度 58 | const trContentHeight = iconHeight - 35; // 整个面板高度 - 边距间隔 = 翻译正文高度 59 | const zIndex = '2147483647'; // 渲染图层 60 | style.textContent = ` 61 | /*组件样式*/ 62 | :host{all:unset!important} 63 | :host{all:initial!important} 64 | *{word-wrap:break-word!important;word-break:break-word!important} 65 | a{color:#326891;text-decoration:none;cursor:pointer} 66 | a:hover{text-decoration:none} 67 | a:active{text-decoration:none} 68 | img{cursor:pointer;display:inline-block;width:20px;height:20px;border:1px solid #dfe1e5;border-radius:4px;background-color:rgba(255,255,255,1);padding:2px;margin:0;margin-right:5px;box-sizing:content-box;vertical-align:middle} 69 | img:last-of-type{margin-right:auto} 70 | img:hover{border:1px solid #ff9900} 71 | img[activate]{border:1px solid #ff9900} 72 | img[activate]:hover{border:1px solid #ff9900} 73 | tr-icon{display:none;position:absolute;padding:0;margin:0;cursor:move;box-sizing:content-box;font-size:${fontSize}px;text-align:left;border:0;border-radius:4px;z-index:${zIndex};background:transparent} 74 | tr-icon[activate]{color:#121212;background:#ffffff;-webkit-box-shadow:0 3px 8px 0 rgba(0,0,0,0.2),0 0 0 0 rgba(0,0,0,0.08);box-shadow:0 3px 8px 0 rgba(0,0,0,0.2),0 0 0 0 rgba(0,0,0,0.08)} 75 | tr-audio{display:block;margin-bottom:5px} 76 | tr-audio a{margin-right:1em;font-size:80%} 77 | tr-audio a:last-of-type{margin-right:auto} 78 | tr-content{display:none;width:${trContentWidth}px;height:${trContentHeight}px;overflow-x:hidden;overflow-y:scroll;padding:2px 8px;margin-top:5px;box-sizing:content-box;font-family:"Helvetica Neue","Helvetica","Microsoft Yahei","微软雅黑","Arial","sans-serif";font-size:${fontSize}px;font-weight:normal;line-height:normal;-webkit-font-smoothing:auto;font-smoothing:auto;text-rendering:auto} 79 | tr-engine~tr-engine{margin-top:1em} 80 | tr-engine .title{color:#121212;display:inline-block;font-size:110%;font-weight:bold} 81 | /*各引擎样式*/ 82 | .google .sentences,.google .trans,.google .orig,.google .dict,.google .pos,.none{display:block} 83 | .google .backend,.google .entry,.google .base_form,.google .pos_enum,.google .src,.google .confidence,.google .ld_result,.google .translation_engine_debug_info,.none{display:none} 84 | .google .orig{color:#808080} 85 | .google .pos{margin-top:1em} 86 | .google .pos:before{content:"<"} 87 | .google .pos:after{content:">"} 88 | .google .terms:before{content:"〔"} 89 | .google .terms:after{content:"〕"} 90 | .google .terms{margin-right:.2em} 91 | .youdao .pron{margin-right:1em} 92 | .youdao .phone{color:#808080;margin-right:1em} 93 | .youdao .phone:before{content:"["} 94 | .youdao .phone:after{content:"]"} 95 | .youdao .pos:before{content:"<"} 96 | .youdao .pos:after{content:">"} 97 | .youdao .phrs{display:none} 98 | .youdao .trs>.tr>.exam{display:none} 99 | .youdao .trs>.tr>.l{display:block} 100 | .youdao [class="#text"]{font-style:italic} 101 | .youdao [class="@action"],.none{display:none} 102 | .hjenglish dl,.hjenglish dt,.hjenglish dd,.hjenglish p,.hjenglish ul,.hjenglish li,.hjenglish h3{margin:0;padding:0;margin-block-start:0;margin-block-end:0;margin-inline-start:0;margin-inline-end:0} 103 | .hjenglish h3{font-size:1em;font-weight:normal} 104 | .hjenglish .detail-pron,.hjenglish .pronounces{color:#808080} 105 | .hjenglish .def-sentence-from,.hjenglish .def-sentence-to{display:none} 106 | .hjenglish .detail-groups dd h3:before{counter-increment:eq;content:counter(eq) ".";display:inline} 107 | .hjenglish .detail-groups dd h3 p{display:inline} 108 | .hjenglish .detail-groups dd:first-of-type:last-of-type h3:before{content:""} 109 | .hjenglish .detail-groups dl{counter-reset:eq;margin-bottom:.5em;clear:both} 110 | .hjenglish ol,.hjenglish ul{list-style:none} 111 | .hjenglish dd>p{display:none} 112 | .bing h1,.bing strong,.bing td{font-size:1em;font-weight:normal;margin:0;padding:0} 113 | .bing .concise ul{list-style:none;margin:0;padding:0} 114 | .bing .hd_tf{margin-right:1em} 115 | .bing .concise .pos{margin-right:.2em} 116 | .bing .concise .web{margin-right:auto} 117 | .bing .concise .web:after{content:":"} 118 | .bing .oald{margin-top:.4em} 119 | .bing .hd_tf_lh div{display:inline;color:#808080} 120 | .bing .def_row{vertical-align:top} 121 | .bing .se_d{display:inline;margin-right:.25em} 122 | .bing #authid .only .se_d{display:none} 123 | .bing .bil_dis,.bing .val_dis{padding-right:.25em} 124 | .bing .li_sens div{display:inline} 125 | .bing .li_sens div.li_exs,.bing .li_exs{display:none} 126 | .bing .li_id{border:0;padding:.2em} 127 | .bing .infor,.bing .sen_com,.bing .com_sep,.bing .bil,.bing .gra{padding-right:.25em} 128 | .bing .infor,.bing .label{padding-left:.25em} 129 | .bing .each_seg+.each_seg{margin-top:.5em} 130 | .bing .de_co,.bing .de_co div{display:inline} 131 | .bing .idm_seg,.bing .li_ids_co{margin-left:1em} 132 | .bing .sim{display:inline} 133 | .bing .val{display:none} 134 | .cambridge .entry~.entry{margin-top:1em} 135 | .cambridge p,.cambridge h2,.cambridge h3{padding:0;margin:0} 136 | .cambridge h2,.cambridge h3{font-size:1em;font-weight:normal} 137 | .cambridge .headword .hw{display:block} 138 | .cambridge .pron{color:#808080;margin-right:1em} 139 | .cambridge b.def{font-weight:normal} 140 | .cambridge .examp,.cambridge .extraexamps,.cambridge .cols,.cambridge .xref,.cambridge .fcdo,.cambridge div[fallback],.cambridge .i-volume-up,.cambridge .daccord{display:none} 141 | .cambridge .entry-body__el+.entry-body__el{margin-top:1em} 142 | .cambridge .epp-xref,.cambridge .db,.cambridge .bb,.cambridge .i-caret-right,.cambridge .dsense_h{display:none} 143 | .cambridge .dphrase-block{margin-left:1em} 144 | .cambridge .dphrase-title b{font-weight:normal} 145 | .cambridge .ddef_h,.cambridge .def-body{display:inline} 146 | .iciba h1,.iciba p,.iciba [class^="Mean_word"]{margin:0;padding:0;font-size:1em;font-weight:normal} 147 | .iciba ul{list-style:none;margin:0;padding:0} 148 | .iciba li>i{font-style:normal} 149 | .iciba ul[class^="Mean_symbols"] li{display:inline;color:#808080;margin-right:1em} 150 | .iciba ul[class^="Mean_part"] li>i{margin-right:0.2em} 151 | .iciba ul[class^="Mean_part"] li>div{display:inline} 152 | `; 153 | // iframe 工具库 154 | const iframe = document.createElement('iframe'); 155 | let iframeWin = null; 156 | let iframeDoc = null; 157 | iframe.style.display = 'none'; 158 | const icon = document.createElement('tr-icon');//翻译图标 159 | const content = document.createElement('tr-content');// 内容面板 160 | const contentList = document.createElement('div');//翻译内容结果集(HTML内容)列表 161 | let selected;// 当前选中文本 162 | let engineId;// 当前翻译引擎 163 | let engineTriggerTime;// 引擎触发时间(milliseconds) 164 | let idsType;// 当前翻译面板内容列表数组 165 | let pageX;// 图标显示的 X 坐标 166 | let pageY; // 图标显示的 Y 坐标 167 | // 初始化内容面板 168 | content.appendChild(contentList); 169 | // 发音缓存 170 | let audioCache = {}; // {'mp3 download url': data} 171 | // 翻译引擎结果集 172 | let engineResult = {}; // id: DOM 173 | // 唯一 ID 174 | const ids = { 175 | ICIBA: 'iciba', 176 | ICIBA_LOWER_CASE: 'icibaLowerCase', 177 | YOUDAO: 'youdao', 178 | YOUDAO_LOWER_CASE: 'youdaoLowerCase', 179 | BING: 'bing', 180 | HJENGLISH: 'hjenglish', 181 | GOOGLE: 'google', 182 | CAMBRIDGE: 'cambridge' 183 | }; 184 | // 唯一 ID 扩展 185 | const idsExtension = { 186 | // ID 组 187 | LIST_DICT: [ids.ICIBA, ids.YOUDAO, ids.BING, ids.HJENGLISH, ids.CAMBRIDGE], 188 | LIST_DICT_LOWER_CASE: [ids.ICIBA, ids.ICIBA_LOWER_CASE, ids.YOUDAO, ids.YOUDAO_LOWER_CASE, ids.BING, ids.HJENGLISH, ids.CAMBRIDGE], 189 | LIST_GOOGLE: [ids.GOOGLE], 190 | // 去重比对(大小写翻译可能一样) 191 | lowerCaseMap: (() => { 192 | const obj = {}; 193 | obj[ids.ICIBA_LOWER_CASE] = ids.ICIBA; 194 | obj[ids.YOUDAO_LOWER_CASE] = ids.YOUDAO; 195 | return obj; 196 | })(), 197 | // 标题 198 | names: (() => { 199 | const obj = {}; 200 | obj[ids.ICIBA] = '金山词霸'; 201 | obj[ids.ICIBA_LOWER_CASE] = ''; 202 | obj[ids.YOUDAO] = '有道词典'; 203 | obj[ids.YOUDAO_LOWER_CASE] = ''; 204 | obj[ids.BING] = 'Bing 词典'; 205 | obj[ids.HJENGLISH] = '沪江小D'; 206 | obj[ids.GOOGLE] = '谷歌翻译'; 207 | obj[ids.CAMBRIDGE] = '剑桥高阶'; 208 | return obj; 209 | })(), 210 | // 跳转到网站(“%q%”占位符或者 function text -> return URL) 211 | links: (() => { 212 | const obj = {}; 213 | obj[ids.ICIBA] = 'https://www.iciba.com/word?w=%q%'; 214 | obj[ids.ICIBA_LOWER_CASE] = ''; 215 | obj[ids.YOUDAO] = 'https://dict.youdao.com/w/eng/%q%'; 216 | obj[ids.YOUDAO_LOWER_CASE] = ''; 217 | obj[ids.BING] = 'https://cn.bing.com/dict/search?q=%q%'; 218 | obj[ids.HJENGLISH] = 'https://dict.hjenglish.com/w/%q%'; 219 | obj[ids.GOOGLE] = text => { 220 | let rst = ''; 221 | if (hasChineseByRange(text)) { 222 | rst = `https://translate.google.com/#view=home&op=translate&sl=auto&tl=en&text=${encodeURIComponent(text)}`; 223 | } else { 224 | rst = `https://translate.google.com/#view=home&op=translate&sl=auto&tl=zh-CN&text=${encodeURIComponent(text)}`; 225 | } 226 | return rst; 227 | }; 228 | obj[ids.CAMBRIDGE] = 'https://dictionary.cambridge.org/search/english-chinese-simplified/direct/?q=%q%'; 229 | return obj; 230 | })(), 231 | // 翻译引擎 232 | engines: (() => { 233 | const obj = {}; 234 | obj[ids.ICIBA] = (text, time) => { 235 | ajax(`https://www.iciba.com/word?w=${encodeURIComponent(text)}`, rst => { 236 | putEngineResult(ids.ICIBA, parseIciba(rst), time); 237 | showContent(); 238 | }, rst => { 239 | putEngineResult(ids.ICIBA, htmlToDom('error: 无法连接翻译服务'), time); 240 | showContent(); 241 | }); 242 | }; 243 | obj[ids.ICIBA_LOWER_CASE] = (text, time) => { 244 | ajax(`https://www.iciba.com/word?w=${encodeURIComponent(text.toLowerCase())}`, rst => { 245 | putEngineResult(ids.ICIBA_LOWER_CASE, parseIciba(rst), time); 246 | showContent(); 247 | }, rst => { 248 | putEngineResult(ids.ICIBA_LOWER_CASE, htmlToDom('error: 无法连接翻译服务'), time); 249 | showContent(); 250 | }); 251 | }; 252 | obj[ids.YOUDAO] = (text, time) => { 253 | ajax(`https://dict.youdao.com/jsonapi?xmlVersion=5.1&jsonversion=2&q=${encodeURIComponent(text)}`, rst => { 254 | putEngineResult(ids.YOUDAO, parseYoudao(rst), time) 255 | showContent(); 256 | }, rst => { 257 | putEngineResult(ids.YOUDAO, htmlToDom('error: 无法连接翻译服务'), time); 258 | showContent(); 259 | }); 260 | }; 261 | obj[ids.YOUDAO_LOWER_CASE] = (text, time) => { 262 | ajax(`https://dict.youdao.com/jsonapi?xmlVersion=5.1&jsonversion=2&q=${encodeURIComponent(text.toLowerCase())}`, rst => { 263 | putEngineResult(ids.YOUDAO_LOWER_CASE, parseYoudao(rst), time); 264 | showContent(); 265 | }, rst => { 266 | putEngineResult(ids.YOUDAO_LOWER_CASE, htmlToDom('error: 无法连接翻译服务'), time) 267 | showContent(); 268 | }); 269 | }; 270 | obj[ids.BING] = (text, time) => { 271 | ajax(`https://cn.bing.com/dict/search?q=${encodeURIComponent(text)}`, rst => { 272 | putEngineResult(ids.BING, parseBing(rst), time); 273 | showContent(); 274 | }, rst => { 275 | putEngineResult(ids.BING, htmlToDom('error: 无法连接翻译服务'), time); 276 | showContent(); 277 | }, { 278 | headers: { 279 | 'Accept-Language': 'zh-CN,zh;q=0.9' 280 | } 281 | }); 282 | }; 283 | obj[ids.HJENGLISH] = (text, time) => { 284 | ajax(`https://dict.hjenglish.com/w/${encodeURIComponent(text)}`, rst => { 285 | putEngineResult(ids.HJENGLISH, parseHjenglish(rst), time); 286 | showContent(); 287 | }, rst => { 288 | putEngineResult(ids.HJENGLISH, htmlToDom('error: 无法连接翻译服务'), time); 289 | showContent(); 290 | }, { 291 | headers: { 292 | 'Cookie': `HJ_SID=${uuid()}; HJ_SSID_3=${uuid()}; HJ_CST=1; HJ_CSST_3=1; HJ_UID=${uuid()}`, 293 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15' 294 | } 295 | }); 296 | }; 297 | obj[ids.GOOGLE] = (text, time) => { 298 | let url = 'https://translate.google.com/translate_a/single?client=gtx&dt=t&dt=bd&dj=1&source=input&hl=zh-CN&sl=auto'; 299 | url += `&tk=${token(text)}`; 300 | if (hasChineseByRange(text)) { 301 | url += `&tl=en&q=${encodeURIComponent(text)}`; 302 | } else { 303 | url += `&tl=zh-CN&q=${encodeURIComponent(text)}`; 304 | } 305 | ajax(url, rst => { 306 | putEngineResult(ids.GOOGLE, parseGoogle(rst), time); 307 | showContent(); 308 | }, rst => { 309 | putEngineResult(ids.GOOGLE, htmlToDom('error: 无法连接翻译服务'), time); 310 | showContent(); 311 | }); 312 | }; 313 | obj[ids.CAMBRIDGE] = (text, time) => { 314 | const url = `https://dictionary.cambridge.org/dictionary/english-chinese-simplified/${encodeURIComponent(text)}`; 315 | ajax(url, rst => { 316 | putEngineResult(ids.CAMBRIDGE, parseCambridge(rst), time); 317 | showContent(); 318 | }, rst => { 319 | putEngineResult(ids.CAMBRIDGE, htmlToDom('error: 无法连接翻译服务'), time); 320 | showContent(); 321 | }); 322 | }; 323 | return obj; 324 | })() 325 | }; 326 | // 绑定图标拖动事件 327 | const iconDrag = new Drag(icon); 328 | const dragFluctuation = 16;// 当拖动多少像素以上时不触发查询 329 | // 图标数组 330 | const iconArray = [{ 331 | name: '多词典查询', 332 | id: 'icon-dict', 333 | image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAANjklEQVR4Ae2dBVgjyRZG67m7u7u7u/t7yLqOC0lgDBjFdgjj7u7ubkDQcXc3nKSjxO+7N2ts0bCwkw4t9X/fqbEIU31S2t1hekhqTs6be2YV/rVnVlF2j0zr+p7Z1ms9s6xRBFTOfWRbj6yiMb2zx/0zNWPS2xCWSKjQND2yx/4MK/ECAjrgas9M668Rliio0CRPDhn/DqywKUgEAR0RxdZsOv3/EKY0VGiOnJyc12Ml2ajCdExZauq6NyBMSajQHNjHD6NK0ju9sopGIUxJqNAWw8d/GysngIABCD2bPfYnCFMKKjSFAZp+jqKzCFMKKjSDyTTtLVgpQQSMRI9hRR9HmBJQoRmoOaQKMRyZ1kfENBDBRR6T8QQgCmciTAmo0AokwCIjCtAr03ocYUpAhVagccCaRFb8sbOXoLahuQ3lx84kWoJLCFMCKjRE4gQYWjgLolGQTTgcAVPuFD0LIARYu/MQdJTFG3bpWQAhwI07NdBRzl25oVcBhACZRbOBj9fXAq0TiUTAkj9VjwIIATbsLuEOdrTN31GWbtqjRwGEALfv10HrXL9zX7ZVuHDtlt4EEAIMnzAX+Ow4VEn/RlPANi1DesE0PQkgBNi0txT4jJu3KvZv+8uPAp/lW/bqSQAhwN3aBmidQDAIfUeMj/3b5EXrgM+lG3f0IoAQYOSk+cDn3OWXp3v9R02AYCgErRONRmHQczP0IIAQYOv+MuCzdsfBVzzm9MVrwGfVtv16EEAIUFPfxH26AYZaZ73iMQvWbgc+V27e1boAQoAxUxYCn2u377V5XNqYyTLdAMCQwplCAC0LsP1gRaeb9uPnLgOfNdsPCAG0LEBdo73Tg7s5q7YAn+u37wsBtCpA7rTFwOfi9dvtPr7/qIngDwTlxwtCAO0JsKu4Cvgs29zxOv/hUxeAz7qdh4QAWhSg0S613enL63inb8ayjcDn5t0aIYDWBMifsRT4nL18/VWf13fkePD5/cCHNo2EABoSYK/tMPBZuG5Hp55bcfws8KFtYyGARgTolW2FZocTWicUCkNazuROPX/K4vXAh7aShQAaEWDsrOXAhzaDqH/vDLNXbolNF/nQlrIQQAMC0PauAqEtZbULIASg5t/hdIMCoVZE7QIIAaxzVoCSGTFxvpoFEAIcqjwue6r31gPlXUauJdmyv0ytAggBemUXgdPtAT550xa/ptfbXVoNfO7XN6pVACHAeDzHj09DsyOui0mUUZMXqFEAIUBJ9Ungs6ukKp7LyRTaYlabAEKA3sPHgdvja9v8T1/yQK+7p+2KIp1GrjYBhACTFq6NT/PPUTBTvhvImbpITQIIAWxHTwMXGsTF5ZPa5HACn53FVWoRQAjQZ8Q48HhbgE/+jCWKbSw1NDlUIYAQQH7zhgZvcZuuPTdzGVBkxhdqEEAIQNu3cW/+OWh3Uf49hACJF4A/gaPFr1Tzz3cDfCvT3QIIAaYv3dDRgVG6G6BZQncKIASgc/zpOr/WrNy6L+4C0C7j0TMX27zX/LXbhQAPCn6zxlyqEKPRI8t6BGFKQIVGIAkKexlRABIfYUpAhSzA2Oskk+kLSIorzTzWaTLvdpnMp7qTc5bBlwwpQLa1N8KUgIo22C2Wb+MBP4aAmpCQ/pmFRrxX8PcScq9gwG/fcposuVjZQQTUyMwhY4wmQE2fPnPfhDAliBVE7OCnmaupktVMjSkd0jPHGkaA3pnWvyBMKWIF4TJZrFTBWqAiY5hRmv7pCFOSWOG2WH6NFRtBQCssHDJK518YZT2TiC+SpKb/jVihtxDQGgczsiBNf91BBJmSsO8NdKSnf5cqU6vcNafDpKE5ejn453sNK/opwhIFk0yW3lSRWuemZRCUZWTCysEjoWhoLgwdVgBDOqBPZuF97P5udR/Wm7i4VYHT7hk9sot60lQvB1tjhCUQEsA8nyrQaEhp5n4IMzoMp34nFavoQYPBlZUN7tFjwJ1fAJ6iceAutIIrM6v7JUgzL0GY0WHxHAC2rF4NwcoqCF+/DlG3GzpMMAiRxkYIX7sGwWPHwL9zF0mSQAlMaxBmdOIqQKS+Hh404Vu3wDNlquEEEAJwoZbEabYIAbQuQMRuh/CNmxC6cAGCJ07EDmzw5MlYNxFpaACIRKC9BIqLhQBaF8A7f36Hz3GNGAn+7Tsg0twMcmlZs7ZbBUgubvpqcpn90WSbfXyyzXEAuYHcUjWlUlWyTZqVYpN6pZZJP+hzDN6EMBm6WQBuxkCtAh9qIdx5eQkX4L/Fjs9iZe5HQPvYLyXZ7L9AGId6BCBoehiprQU+/t17EiYAA3hdks1hworzIKAjIsmljqlP7q17B8JeRE0CENTky80MEiYAHvwVVGG6pcxxNrXy7tsQRqhOAFos4hN1uRIiQEqp/XGqJAMwHWGEugQgLOkAQe5GzH6/4gKklts/jRUiIWAMnH9B1CeAa1gm8N/aHL59W3EBUmyOHVQxBqKGZgeqE8A7axbwCVZXKyrAb4vhjThtajGYAEBTRNUJECgrBy4khaIC4Oj4e1QhxsPeX1UC+BYvAT6hy5cVXwhKLpP6GFEAnPEsUo0AvgUL2w7+fD7aPlZeAJt9nhoOyKPlEvQ77ITMky4YfdoN5mMueKJCUvI9z3SrAK7BQ8A7dx6E79wFPrSd7LEW0eMSIcCaRB/sFGToCResvNkC56UQ+MJRaC/0b/e8YTjjCMGu+37IP+uGh8vi8nPcUlwAWt4NHj/xMiee3wiKOp3QXkLnznHnBuhHAPpEr7nVAo5AFB4kLSjFpIteNQvQ9dCGkGfCRO519SHAY3jg6dPuDkUhXsk47tKHANz2Ma390y6hbgSgvvy+LwKdiT8chQZ/hJr8DruFu96wiruAOITGAN5ZszUvgPW8p8MDedEZgtXYJWSfdMMj5ZJsl5GOAhWc9cDWu/6YHJS1t1u0IYBn/ARwFzz3Ep5Jk3G6txhaNm+BgM0GUUnqwIIotQaaFWDmZS9EQT5Hm4M0CHxNr0uzhN7VTm0IQEu7r7b271u4MDbfby/eefM1J8DwU26Q6+5pDJB31h1f2bQqAI9/3z6QCe0G0unlmhGAPp2OQAT43PGGYeCRdj+5QgAiUFwCMqElYs0IcMoeAj5SMNqZZlsIQETq6oBPpKZGEwKMOu0GPlEk50ynm30hQMBWJntuIJ03qHYBaFWPz54af1deQwjgW7QY5EIzCjULQOv3fGgG2PewUwjQFWgZWH42ME/VAtA6PZ8DtYGuvo4QwDt/gRZbANnVvtcy5RNjgIMHZReFaCqoVgFoG1eu+X+s69u5QoDwjRvAhy4vU/MsYMZlL/C57Ap19XWEAN7Zc2jEL3+JmIoFoPV8PgfrutT/CwE8EydBNBAAPqErV+hKYRULID8A3Hy3U9M/IQDN733Ll0PU6wU+9HfuUaNVvxdQ2RgEPstu+IQAnhdvCWO2vOJUMHdePninTcMB3yE6yO3uBvoWLdLEbuA5mQWgWVe8QoDWBzPa0hK7yqczodvHeKZO08j5APLr/4uvixagy4k0NYF/505qJTR1RpCtPgB8NtxpMZYAofPnaeuWRvFdOvOHBnmBkhLqErjBnnYE2H7PD3z21hhsEMhf4+fOyY2t3tGVPb4lS2Nr/DTN80yeEjvl2zV8hNxzNSkAnfDJhTaGNCHAJQQMBH+fwLgIMOWSF/gEI0Dn+alcgDTzauMJQJjS49kC9Kx2glxoh1DVAlBFGFEAl9n8c4QR8doMuu0JA5/96t8NNP3UgAKEICPjbQgj4iXAtnt+2W6gZ7WkXgHAZHoLVkiTwZr/SoTFezuYzu2naGhJ+BbDgklplmQDCRCUBqZ/H2EvEi8BiBP2IPCJRAFGnnarVwACK2aZEQSQTJbhCGtFXAUYdsIFcmkORODZKkm9Atj79HkPtyagRyogNfUNCGsNL4ASG0MUOmOIrgtQpQCEu1+/D+OoeIM+P/nmhY709PcijCPuAjxZKbV7MagrGKXr+9UlAI9kNj+EldaAgA64KQ20/Alh7RB3AQjTUSd4OrgUnG72kHWy69cGPl4hwcSLHhiIr6+YAIQzI+P9JIKUZh6Pn54SrEg3AhqgCdmPP3uR02x+pGHAgHcirAPiKwB3mrisBFy3sPO+H8ae88SkeQpbjxR6PvI0/t5yzAVjzrhjF5oeaQpCMBIFCi09KyoAD+TkvJ6aUDXTbDK9G2FdRDEBiP7Y5193h6ErCUdjdJizjlDcBBAoKABB+wE77vlpUShuodd6rFwSAqhdAP6qYVotpLuAPEjo6dQC0BVHGhBACMBD/Tzd4InOGm70R4CL7F3C6JYwVY1BmHbJS2MDZWcBRoNumJhIAeRahsHHXbGriCajGNMveyH3TCLuEyhdYFgISu0DqUIMR5m0kmEhKJd+aNBbxaYzLAwP3TbdiHcLTylt/jnDQkAY7/sCnPTVMQwLAZJU4fgMVYphmv8yx5MIY1QInielzPGUMQZ/9rUII6hojcDmWIeAbil13HusTHofwggqWiFIXQdvSLLZs7GyArob9NkcW/9d3vhxhL0IFTII/lds/yZW2nGdTPca6etvEcZDhaAd6Mukkkocv0splYYk2aTVWJlXkSgC6kaqTS6VdiEF+Pvk1Ern+xEmx/8B/uTnd+kxPtUAAAAASUVORK5CYII=', 334 | trigger(text, time) { 335 | idsType = idsExtension.LIST_DICT; 336 | if (text != text.toLowerCase()) { 337 | idsType = idsExtension.LIST_DICT_LOWER_CASE; // 改为大小写 ID 组(大小写各请求一次) 338 | } 339 | idsType.forEach(id => { 340 | idsExtension.engines[id](text, time); 341 | }); 342 | initContent(); // 初始化翻译面板 343 | displayContent(); // 立马显示翻译面板 344 | } 345 | }, { 346 | name: '谷歌翻译', 347 | id: 'icon-google', 348 | image: 'data:image/png;base64,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', 349 | trigger(text, time) { 350 | idsType = idsExtension.LIST_GOOGLE; 351 | idsType.forEach(id => { 352 | idsExtension.engines[id](text, time); 353 | }); 354 | initContent(); // 初始化翻译面板 355 | displayContent(); // 立马显示翻译面板 356 | } 357 | }]; 358 | // 添加翻译引擎图标 359 | iconArray.forEach(obj => { 360 | const img = document.createElement('img'); 361 | img.setAttribute('src', obj.image); 362 | img.setAttribute('alt', obj.name); 363 | img.setAttribute('title', obj.name); 364 | img.setAttribute('icon-id', obj.id); 365 | img.addEventListener('mouseup', () => { 366 | if (engineId == obj.id) { 367 | // 已经是当前翻译引擎,不做任何处理 368 | } else if (!isDrag(dragFluctuation)) { 369 | icon.setAttribute('activate', 'activate'); // 标注面板展开 370 | engineId = obj.id; // 翻译引擎 ID 371 | engineTriggerTime = new Date().getTime(); // 引擎触发时间 372 | engineActivateShow(); // 显示翻译引擎指示器 373 | audioCache = {}; // 清空发音缓存 374 | engineResult = {}; // 清空翻译引擎结果集 375 | obj.trigger(selected, engineTriggerTime); // 启动翻译引擎 376 | } 377 | }); 378 | icon.appendChild(img); 379 | }); 380 | // 添加内容面板(放图标后面) 381 | icon.appendChild(content); 382 | // 添加样式、翻译图标到 DOM 383 | const root = document.createElement('div'); 384 | document.documentElement.appendChild(root); 385 | const shadow = root.attachShadow({ 386 | mode: 'closed' 387 | }); 388 | // iframe 工具库加入 Shadow 389 | shadow.appendChild(iframe); 390 | iframeWin = iframe.contentWindow; 391 | iframeDoc = iframe.contentDocument; 392 | // 外部样式表 393 | const link = document.createElement('link'); 394 | link.rel = 'stylesheet'; 395 | link.type = 'text/css'; 396 | link.href = createObjectURLWithTry(new Blob(['\ufeff', style.textContent], { 397 | type: 'text/css;charset=UTF-8' 398 | })); 399 | // 多种方式最大化兼容:Content Security Policy 400 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 401 | shadow.appendChild(style); // 内部样式表 402 | shadow.appendChild(link); // 外部样式表 403 | adoptedStyleSheets(shadow, style.textContent); // CSSStyleSheet 样式 404 | // 翻译图标加入 Shadow 405 | shadow.appendChild(icon); 406 | // 鼠标事件:防止选中的文本消失 407 | document.addEventListener('mousedown', e => { 408 | log('mousedown event:', e); 409 | if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon)) { // 点击了翻译图标 410 | e.preventDefault(); 411 | } 412 | }); 413 | // 鼠标事件:防止选中的文本消失;显示、隐藏翻译图标 414 | document.addEventListener('mouseup', showIcon); 415 | // 选中变化事件 416 | document.addEventListener('selectionchange', showIcon); 417 | document.addEventListener('touchend', showIcon); 418 | // 内容面板滚动事件 419 | content.addEventListener('scroll', e => { 420 | if (content.scrollHeight - content.scrollTop === content.clientHeight) { 421 | log('scroll bottom', e); 422 | e.preventDefault(); 423 | e.stopPropagation(); 424 | } else if (content.scrollTop === 0) { 425 | log('scroll top', e); 426 | e.preventDefault(); 427 | e.stopPropagation(); 428 | } 429 | }); 430 | /**日志输出*/ 431 | function log(...args) { 432 | const debug = false; 433 | if (!debug) { 434 | return; 435 | } 436 | if (args) { 437 | for (let i = 0; i < args.length; i++) { 438 | console.log(args[i]); 439 | } 440 | } 441 | } 442 | /**鼠标拖动*/ 443 | function Drag(element) { 444 | this.dragging = false; 445 | this.startDragTime = 0; 446 | this.stopDragTime = 0; 447 | this.mouseDownPositionX = 0; 448 | this.mouseDownPositionY = 0; 449 | this.elementOriginalLeft = parseInt(element.style.left); 450 | this.elementOriginalTop = parseInt(element.style.top); 451 | this.backAndForthLeftMax = 0; 452 | this.backAndForthTopMax = 0; 453 | const ref = this; 454 | this.startDrag = e => { 455 | e.preventDefault(); 456 | ref.dragging = true; 457 | ref.startDragTime = new Date().getTime(); 458 | ref.mouseDownPositionX = e.clientX; 459 | ref.mouseDownPositionY = e.clientY; 460 | ref.elementOriginalLeft = parseInt(element.style.left); 461 | ref.elementOriginalTop = parseInt(element.style.top); 462 | ref.backAndForthLeftMax = 0; 463 | ref.backAndForthTopMax = 0; 464 | // set global mouse events 465 | window.addEventListener('mousemove', ref.dragElement); 466 | window.addEventListener('mouseup', ref.stopDrag); 467 | log('startDrag'); 468 | }; 469 | this.unsetMouseMove = () => { 470 | // unset global mouse events 471 | window.removeEventListener('mousemove', ref.dragElement); 472 | window.removeEventListener('mouseup', ref.stopDrag); 473 | }; 474 | this.stopDrag = e => { 475 | e.preventDefault(); 476 | ref.dragging = false; 477 | ref.stopDragTime = new Date().getTime(); 478 | ref.unsetMouseMove(); 479 | log('stopDrag'); 480 | }; 481 | this.dragElement = e => { 482 | log('dragging'); 483 | if (!ref.dragging) { 484 | return; 485 | } 486 | e.preventDefault(); 487 | // move element 488 | element.style.left = `${ref.elementOriginalLeft + (e.clientX - ref.mouseDownPositionX)}px`; 489 | element.style.top = `${ref.elementOriginalTop + (e.clientY - ref.mouseDownPositionY)}px`; 490 | // get max move 491 | let left = Math.abs(ref.elementOriginalLeft - parseInt(element.style.left)); 492 | let top = Math.abs(ref.elementOriginalTop - parseInt(element.style.top)); 493 | if (left > ref.backAndForthLeftMax) ref.backAndForthLeftMax = left; 494 | if (top > ref.backAndForthTopMax) ref.backAndForthTopMax = top; 495 | log('dragElement'); 496 | }; 497 | element.onmousedown = this.startDrag; 498 | element.onmouseup = this.stopDrag; 499 | } 500 | /** 501 | * 是否拖动图标 502 | * @param fluctuate 位移波动允许范围 503 | */ 504 | function isDrag(fluctuate) { 505 | return (iconDrag.elementOriginalLeft != parseInt(icon.style.left) 506 | && Math.abs(iconDrag.elementOriginalLeft - parseInt(icon.style.left)) >= fluctuate) || 507 | (iconDrag.elementOriginalTop != parseInt(icon.style.top) 508 | && Math.abs(iconDrag.elementOriginalTop - parseInt(icon.style.top)) >= fluctuate) || 509 | iconDrag.backAndForthLeftMax >= fluctuate || 510 | iconDrag.backAndForthTopMax >= fluctuate; 511 | } 512 | /**强制结束拖动*/ 513 | function forceStopDrag() { 514 | if (iconDrag) { 515 | // 强制设置鼠标拖动事件结束,防止由于网页本身的其它鼠标事件冲突而导致没有侦测到:mouseup 516 | iconDrag.dragging = false; 517 | iconDrag.unsetMouseMove(); 518 | } 519 | } 520 | /**是否包含汉字*/ 521 | function hasChineseByRange(str) { 522 | return /[\u4e00-\u9fa5]/ig.test(str); 523 | } 524 | /**uuid*/ 525 | function uuid() { 526 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { 527 | const r = Math.random() * 16 | 0; 528 | const v = c == 'x' ? r : (r & 0x3 | 0x8); 529 | return v.toString(16); 530 | }); 531 | } 532 | /**遍历删除元素*/ 533 | function iterElementRemove(arr) { 534 | for (let i = arr.length - 1; i >= 0; i--) { 535 | if (arr[i] !== undefined && arr[i] !== null && arr[i] instanceof Element) { 536 | arr[i].remove(); 537 | } 538 | } 539 | } 540 | /**对象转 xml*/ 541 | function objToXml(obj) { 542 | let xml = ''; 543 | for (const prop in obj) { 544 | if (obj[prop] instanceof iframeWin.Function) { 545 | continue; 546 | } 547 | xml += obj[prop] instanceof iframeWin.Array ? '' : `<${prop}>`; 548 | if (obj[prop] instanceof iframeWin.Array) { 549 | for (const array in obj[prop]) { 550 | if (obj[prop][array] instanceof iframeWin.Function) { 551 | continue; 552 | } 553 | xml += `<${prop}>`; 554 | xml += objToXml(new iframeWin.Object(obj[prop][array])); 555 | xml += ``; 556 | } 557 | } else if (obj[prop] instanceof iframeWin.Object) { 558 | xml += objToXml(new iframeWin.Object(obj[prop])); 559 | } else { 560 | xml += obj[prop]; 561 | } 562 | xml += obj[prop] instanceof iframeWin.Array ? '' : ``; 563 | } 564 | return xml.replace(/<\/?[0-9]{1,}>/g, ''); 565 | } 566 | /**xml 转 html*/ 567 | function xmlToHtml(xml, tag) { 568 | return xml.replace(/<([^/]+?)>/g, `<${tag} class="$1">`) 569 | .replace(/<\/(.+?)>/g, ``); 570 | } 571 | /**html 字符串转 DOM*/ 572 | function htmlToDom(html) { 573 | const div = document.createElement('div'); 574 | div.innerHTML = html; 575 | return div; 576 | } 577 | /**替换html标签 578 | * @param tag 匹配的时候不区分大小写 579 | */ 580 | function replaceHtmlTag(str, tag, newTag) { 581 | if (true) { // 开始标签 582 | let newStr = ''; 583 | let index = 0; 584 | let matches = str.matchAll(/<(((?![<\/>])[\S])+)(((?![<>])[\S\s])*)>/g); 585 | for (const match of matches) { 586 | if (tag.toLowerCase() !== match[1].toLowerCase()) continue; 587 | newStr += str.substring(index, match.index); 588 | newStr += match[3] ? `<${newTag}${match[3]}>` : `<${newTag}>`; 589 | index = match.index + match[0].length; 590 | } 591 | newStr += str.substring(index, str.length); 592 | str = newStr; 593 | } 594 | if (true) { // 结束标签 595 | let newStr = ''; 596 | let index = 0; 597 | let matches = str.matchAll(/<\/(((?![<>])[\S])+)(((?![<>])[\S\s])*)>/g); 598 | for (const match of matches) { 599 | if (tag.toLowerCase() !== match[1].toLowerCase()) continue; 600 | newStr += str.substring(index, match.index); 601 | newStr += ``; 602 | index = match.index + match[0].length; 603 | } 604 | newStr += str.substring(index, str.length); 605 | str = newStr; 606 | } 607 | return str; 608 | } 609 | /**清理 html*/ 610 | function cleanHtml(html) { 611 | html = html.replace(//ig, '') 612 | .replace(//ig, '') 613 | .replace(//ig, '') 614 | .replace(//ig, '') 615 | .replace(//ig, '') 616 | .replace(//ig, '') 617 | .replace(//ig, '') 618 | .replace(//ig, '') 619 | .replace(//ig, '') 620 | .replace(//ig, '') 621 | .replace(//ig, '') 622 | .replace(//ig, '') 623 | .replace(//ig, '') 624 | .replace(//ig, '') 625 | .replace(//ig, '') 626 | .replace(//ig, '') 627 | .replace(//ig, '') 628 | .replace(//ig, '') 629 | .replace(//ig, '') 630 | .replace(//ig, '') 631 | .replace(//ig, '') 632 | .replace(//ig, ''); 633 | html = cleanAttr(html, /on[a-z]*/ig); 634 | return html; 635 | } 636 | /** 637 | * 清理指定属性(忽略大小写) 638 | * @param rmAttrKey 正则表达式 639 | */ 640 | function cleanAttr(html, rmAttrKey) {// @param rmAttrKey 如“/on[a-z]*/ig”,表示清理“on”、“ON”开头的属性:onclick、onmove等 641 | let str = html; 642 | // 开始标签中的属性 643 | let newStr = ''; 644 | let index = 0; 645 | let matches = str.matchAll(/<(((?![<\/>])[\S])+)(((?![<>])[\S\s])*)>/g); 646 | for (const match of matches) { 647 | // 属性匹配 648 | let attrStr = match[0]; 649 | let attrNewStr = ''; 650 | let attrIndex = 0; 651 | let attrMs = attrStr 652 | .matchAll( // 此正则会匹配“key=value”[1]这种形式,属性中仅有单“key”[2]不匹配,属性中混合[1][2]仅匹配[1]且[2]会算作其之前邻近[1]的value 653 | /([\s][a-zA-Z0-9-]+(((?![=<>])[\s])*))=(((?!([\s][a-zA-Z0-9-]+(((?![=<>])[\s])*))=)(?!<|>|\/>)[\s\S])*)/g 654 | ) 655 | for (const am of attrMs) { 656 | let key = am[1].trim(); 657 | let value = am[4].trim(); 658 | if (rmAttrKey.test(key)) { 659 | attrNewStr += attrStr.substring(attrIndex, am.index); 660 | attrIndex = am.index + am[0].length; 661 | } 662 | } 663 | attrNewStr += attrStr.substring(attrIndex, attrStr.length); 664 | attrStr = attrNewStr; 665 | // 删减属性后更新标签 666 | if (match[0] === attrStr) continue; // 无删减属性 667 | newStr += str.substring(index, match.index); 668 | newStr += attrStr; 669 | index = match.index + match[0].length; 670 | } 671 | newStr += str.substring(index, str.length); 672 | str = newStr; 673 | return str; 674 | } 675 | /**带异常处理的 createObjectURL*/ 676 | function createObjectURLWithTry(blob) { 677 | try { 678 | return iframeWin.URL.createObjectURL(blob); 679 | } catch (error) { 680 | log(error); 681 | } 682 | return ''; 683 | } 684 | /**解决 Content-Security-Policy 样式文件加载问题(Chrome 实验功能)*/ 685 | function adoptedStyleSheets(bindDocumentOrShadowRoot, cssText) { 686 | try { 687 | if (bindDocumentOrShadowRoot.adoptedStyleSheets) { 688 | cssText = cssText.replace(/\/\*.*?\*\//ig, ''); // remove CSS comments 689 | const cssSheet = new CSSStyleSheet(); 690 | const styleArray = cssText.split('\n'); 691 | for (let i = 0; i < styleArray.length; i++) { 692 | const line = styleArray[i].trim(); 693 | if (line.length > 0) { 694 | cssSheet.insertRule(line); 695 | } 696 | } 697 | bindDocumentOrShadowRoot.adoptedStyleSheets = [cssSheet]; 698 | } 699 | } catch (error) { 700 | log(error); 701 | } 702 | } 703 | /**ajax 跨域访问公共方法*/ 704 | function ajax(url, success, error, obj) { 705 | if (!!!obj) { 706 | obj = {}; 707 | } 708 | if (!!!obj.method) { 709 | obj.method = 'GET'; 710 | } 711 | if (!!!obj.headers) { 712 | obj.headers = {}; 713 | } 714 | // Tampermonkey(其它浏览器扩展同理)代理跨域访问(a.com)时会自动携带对应域名(a.com)的对应cookie, 715 | // 不会携带当前域名的cookie,GM_xmlhttpRequest【不存在】cookie跨域访问安全性问题。 716 | if (!!!obj.anonymous) { 717 | obj.anonymous = true;// 默认不发送cookie 718 | } 719 | GM_xmlhttpRequest({ 720 | method: obj.method, 721 | url, 722 | headers: obj.headers, 723 | anonymous: obj.anonymous, 724 | responseType: obj.responseType, 725 | data: obj.data, 726 | onload(res) { 727 | success(res.responseText, res, obj); 728 | }, 729 | onerror(res) { 730 | error(res.responseText, res, obj); 731 | }, 732 | onabort(res) { 733 | error('the request was aborted', res, obj); 734 | }, 735 | ontimeout(res) { 736 | error('the request failed due to a timeout', res, obj); 737 | }, 738 | onreadystatechange(...args) { 739 | log('ajax:', args); 740 | } 741 | }); 742 | } 743 | /**放入翻译引擎结果集*/ 744 | function putEngineResult(id, value, time) { 745 | if (time == engineTriggerTime) { // 是本次触发的异步ajax请求 746 | engineResult[id] = value; 747 | } 748 | } 749 | /**初始化面板*/ 750 | function initContent() { 751 | contentList.innerHTML = ''; // 清空翻译内容列表 752 | // 发音 753 | const audio = document.createElement('tr-audio'); 754 | audio.appendChild(getPlayButton({ 755 | name: 'US', 756 | url: `https://dict.youdao.com/dictvoice?audio=${selected}&type=2` 757 | })); 758 | audio.appendChild(getPlayButton({ 759 | name: 'UK', 760 | url: `https://dict.youdao.com/dictvoice?audio=${selected}&type=1` 761 | })); 762 | if (engineId != 'icon-google') { // 谷歌翻译不显示发音图标 763 | contentList.appendChild(audio); 764 | } 765 | // 初始化翻译引擎结构(此时内容暂未填充) 766 | idsType.forEach(id => { 767 | if (id in idsExtension.names) { 768 | const engine = document.createElement('tr-engine'); 769 | engine.setAttribute('data-id', id); 770 | engine.style.display = 'none'; // 暂无内容默认隐藏 771 | // 标题 772 | if (idsExtension.names[id]) { 773 | const title = document.createElement('a'); 774 | title.innerHTML = idsExtension.names[id]; 775 | title.setAttribute('class', 'title'); 776 | let href = 'javascript:void(0)'; 777 | if (idsExtension.links[id]) { 778 | const link = idsExtension.links[id]; 779 | if (typeof link == 'string') { 780 | if (link.length > 0) { 781 | href = link.replace(/%q%/ig, encodeURIComponent(selected)); 782 | } 783 | } else if (typeof link == 'function') { 784 | const fnHref = link(selected); 785 | if (fnHref.length > 0) { 786 | href = fnHref; 787 | } 788 | } 789 | } 790 | title.setAttribute('rel', 'noreferrer noopener'); 791 | title.setAttribute('target', '_blank'); 792 | title.setAttribute('href', href); 793 | title.setAttribute('title', '打开源网站'); 794 | title.addEventListener('click', e => { 795 | if (isDrag(dragFluctuation)) { 796 | e.preventDefault(); 797 | } 798 | }); 799 | engine.appendChild(title); 800 | } 801 | contentList.appendChild(engine); 802 | } 803 | }); 804 | } 805 | /**显示内容面板*/ 806 | function displayContent() { 807 | const panelWidth = iconWidth + 8; // icon 展开后总宽度(8:冗余距离) 808 | const panelHeight = iconHeight + 8; // icon 展开后总高度(8:冗余距离) 809 | // 计算位置 810 | log('content position:', 811 | 'window.scrollY', window.scrollY, 812 | 'document.documentElement.scrollTop', document.documentElement.scrollTop, 813 | 'document.body.scrollTop', document.body.scrollTop, 814 | 'window.innerHeight', window.innerHeight, 815 | 'document.documentElement.clientHeight', document.documentElement.clientHeight, 816 | 'document.body.clientHeight', document.body.clientHeight, 817 | 'icon.style.top', icon.style.top, 818 | 'window.scrollX', window.scrollX, 819 | 'document.documentElement.scrollLeft', document.documentElement.scrollLeft, 820 | 'document.body.scrollLeft', document.body.scrollLeft, 821 | 'window.innerWidth', window.innerWidth, 822 | 'document.documentElement.clientWidth', document.documentElement.clientWidth, 823 | 'document.body.clientWidth', document.body.clientWidth, 824 | 'icon.style.left', icon.style.left 825 | ); 826 | const scrollTop = Math.max(parseInt(document.documentElement.scrollTop), parseInt(document.body.scrollTop)); 827 | const scrollLeft = Math.max(parseInt(document.documentElement.scrollLeft), parseInt(document.body.scrollLeft)); 828 | let clientHeight = [parseInt(document.documentElement.clientHeight), parseInt(document.body.clientHeight)].filter(x => x <= parseInt(window.innerHeight)).sort((a, b) => a > b ? -1 : (a == b ? 0 : 1))[0]; // 找出最大值且小于等于 window 的高度 829 | if (!clientHeight) { // 网页缩放导致可能数组为空([0] 为 undefined) 830 | clientHeight = parseInt(window.innerHeight); 831 | } 832 | let clientWidth = [parseInt(document.documentElement.clientWidth), parseInt(document.body.clientWidth)].filter(x => x <= parseInt(window.innerWidth)).sort((a, b) => a > b ? -1 : (a == b ? 0 : 1))[0]; // 找出最大值且小于等于 window 的宽度 833 | if (!clientWidth) { // 网页缩放导致可能数组为空([0] 为 undefined) 834 | clientWidth = parseInt(window.innerWidth); 835 | } 836 | // 设置新的位置 837 | let iconNewTop = -1; 838 | if (parseInt(icon.style.top) < scrollTop) { // 面板在滚动条顶部可见部分之上(隐藏了部分或全部) 839 | log('Y adjust top'); 840 | iconNewTop = scrollTop; // 设置为滚动条顶部可见部分位置 841 | } else if (parseInt(icon.style.top) + panelHeight > scrollTop + clientHeight) { // 面板在滚动条滚到最底部时之下(隐藏了部分或全部) 842 | log('Y adjust bottom'); 843 | iconNewTop = parseInt(scrollTop + clientHeight - panelHeight); // 设置面板底部不超过滚动条滚到最底部时可见部分位置 844 | if (iconNewTop < scrollTop) { // 如果此时又出现:面板在滚动条顶部可见部分之上(隐藏了部分或全部) 845 | log('Y adjust bottom top'); 846 | iconNewTop = scrollTop; // 设置为滚动条顶部可见部分位置 847 | } 848 | } 849 | if (iconNewTop != -1 && Math.abs(iconNewTop - parseInt(icon.style.top)) <= panelHeight) { 850 | log('Y set iconNewTop', iconNewTop); 851 | icon.style.top = `${iconNewTop}px`; 852 | } 853 | let iconNewLeft = -1; 854 | if (parseInt(icon.style.left) < scrollLeft) { 855 | log('X adjust left'); 856 | iconNewLeft = scrollLeft; 857 | } else if (parseInt(icon.style.left) + panelWidth > scrollLeft + clientWidth) { 858 | log('X adjust right'); 859 | iconNewLeft = parseInt(scrollLeft + clientWidth - panelWidth); 860 | if (iconNewLeft < scrollLeft) { 861 | log('X adjust right left'); 862 | iconNewLeft = scrollLeft; 863 | } 864 | } 865 | if (iconNewLeft != -1 && Math.abs(iconNewLeft - parseInt(icon.style.left)) <= panelWidth) { 866 | log('X set iconNewLeft', iconNewLeft); 867 | icon.style.left = `${iconNewLeft}px`; 868 | } 869 | content.scrollTop = 0; // 翻译面板滚动到顶端 870 | content.scrollLeft = 0; // 翻译面板滚动到左端 871 | content.style.display = 'block'; 872 | } 873 | /**内容面板填充数据*/ 874 | function showContent() { 875 | // 填充已有结果集引擎内容 876 | idsType.forEach(id => { 877 | if (engineResult[id] && !(id in idsExtension.lowerCaseMap)) { // 跳过小写的内容填充 878 | const engine = contentList.querySelector(`tr-engine[data-id="${id}"]`); 879 | if (engine) { 880 | engine.appendChild(engineResult[id]); 881 | engine.removeAttribute('data-id'); 882 | engine.style.display = 'block'; 883 | } 884 | } 885 | }); 886 | // 比较大小写内容 887 | for (const id in idsExtension.lowerCaseMap) { 888 | if (engineResult[id] && 889 | engineResult[idsExtension.lowerCaseMap[id]] && 890 | engineResult[id].innerHTML != engineResult[idsExtension.lowerCaseMap[id]].innerHTML && 891 | engineResult[id].innerHTML.toLowerCase() != engineResult[idsExtension.lowerCaseMap[id]].innerHTML.toLowerCase()) { 892 | const engine = contentList.querySelector(`tr-engine[data-id="${id}"]`); 893 | if (engine) { 894 | engine.appendChild(engineResult[id]); 895 | engine.removeAttribute('data-id'); 896 | engine.style.display = 'block'; 897 | } 898 | } 899 | } 900 | } 901 | /**隐藏翻译引擎指示器*/ 902 | function engineActivateHide() { 903 | icon.querySelectorAll('img[activate]').forEach(ele => { 904 | ele.removeAttribute('activate'); 905 | }); 906 | } 907 | /**显示翻译引擎指示器*/ 908 | function engineActivateShow() { 909 | engineActivateHide(); 910 | icon.querySelector(`img[icon-id="${engineId}"]`).setAttribute('activate', 'activate'); 911 | } 912 | /**显示 icon*/ 913 | function showIcon(e) { 914 | log('showIcon event:', e); 915 | let offsetX = 4; // 横坐标翻译图标偏移 916 | let offsetY = 8; // 纵坐标翻译图标偏移 917 | // 更新翻译图标 X、Y 坐标 918 | if (e.pageX && e.pageY) { // 鼠标 919 | log('mouse pageX/Y'); 920 | pageX = e.pageX; 921 | pageY = e.pageY; 922 | } 923 | if (e.changedTouches) { // 触屏 924 | if (e.changedTouches.length > 0) { // 多点触控选取第 1 个 925 | log('touch pageX/Y'); 926 | pageX = e.changedTouches[0].pageX; 927 | pageY = e.changedTouches[0].pageY; 928 | // 触屏修改翻译图标偏移(Android、iOS 选中后的动作菜单一般在当前文字顶部,翻译图标则放到底部) 929 | offsetX = -26; // 单个翻译图标块宽度 930 | offsetY = 16 * 3; // 一般字体高度的 3 倍,距离系统自带动作菜单、选择光标太近会导致无法点按 931 | } 932 | } 933 | log(`selected:${selected}, pageX:${pageX}, pageY:${pageY}`) 934 | if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon)) { // 点击了翻译图标 935 | e.preventDefault(); 936 | return; 937 | } 938 | selected = window.getSelection().toString().trim(); // 当前选中文本 939 | log(`selected:${selected}, icon display:${icon.style.display}`); 940 | if (selected && icon.style.display != 'block' && pageX && pageY) { // 显示翻译图标 941 | log('show icon'); 942 | icon.style.top = `${pageY + offsetY}px`; 943 | icon.style.left = `${pageX + offsetX}px`; 944 | icon.style.display = 'block'; 945 | // 兼容部分 Content Security Policy 946 | icon.style.position = 'absolute'; 947 | icon.style.zIndex = zIndex; 948 | } else if (!selected) { // 隐藏翻译图标 949 | hideIcon(); 950 | } 951 | } 952 | /**隐藏 icon*/ 953 | function hideIcon() { 954 | log('hide icon'); 955 | icon.style.display = 'none'; 956 | icon.removeAttribute('activate'); // 标注面板关闭 957 | content.style.display = 'none'; 958 | engineId = ''; 959 | engineTriggerTime = 0; 960 | pageX = 0; 961 | pageY = 0; 962 | engineActivateHide(); 963 | audioCache = {}; 964 | engineResult = {}; 965 | forceStopDrag(); 966 | } 967 | /**发音*/ 968 | function play(obj) { 969 | if (obj.url in audioCache) { // 查找缓存 970 | log('audio in cache', obj, audioCache); 971 | playArrayBuffer(audioCache[obj.url]); // 播放 972 | } else { 973 | ajax(obj.url, (rst, { response }) => { 974 | audioCache[obj.url] = response; // 放入缓存 975 | playArrayBuffer(audioCache[obj.url]); // 播放 976 | }, rst => { 977 | log(rst); 978 | }, { 979 | responseType: 'arraybuffer' 980 | }); 981 | } 982 | } 983 | /**播放 ArrayBuffer 音频*/ 984 | function playArrayBuffer(arrayBuffer) { 985 | const context = new iframeWin.AudioContext() || new iframeWin.webkitAudioContext(); 986 | context.decodeAudioData(arrayBuffer.slice(0), audioBuffer => { // `slice(0)`克隆一份(`decodeAudioData`后原数组清空) 987 | const bufferSource = context.createBufferSource(); 988 | bufferSource.buffer = audioBuffer; 989 | bufferSource.connect(context.destination); 990 | bufferSource.start(); 991 | }); 992 | } 993 | /**得到发音按钮*/ 994 | function getPlayButton(obj) { 995 | const type = document.createElement('a'); 996 | type.innerHTML = obj.name; 997 | type.setAttribute('href', 'javascript:void(0)'); 998 | type.setAttribute('class', 'audio-button'); 999 | type.setAttribute('title', '点击发音'); 1000 | type.addEventListener('mouseup', () => { 1001 | if (!isDrag(dragFluctuation)) { 1002 | play(obj); 1003 | } 1004 | }); 1005 | return type; 1006 | } 1007 | /**有道词典排版*/ 1008 | function parseYoudao(rst) { 1009 | let html = ''; 1010 | try { 1011 | const rstJson = iframeWin.JSON.parse(rst); 1012 | const phoneStyle = 'color:#808080;'; 1013 | if (rstJson.ec) { 1014 | const word = rstJson.ec.word[0]; 1015 | let tr = ''; 1016 | const trs = word.trs; 1017 | const ukphone = word.ukphone; 1018 | const usphone = word.usphone; 1019 | const phone = word.phone; 1020 | const returnPhrase = word['return-phrase']; 1021 | if (returnPhrase && returnPhrase.l && returnPhrase.l.i) { 1022 | html += `
${returnPhrase.l.i}
`; 1023 | } 1024 | html += '
'; 1025 | if (ukphone && ukphone.length != 0) { 1026 | html += `英 [${ukphone}] `; 1027 | } 1028 | if (usphone && usphone.length != 0) { 1029 | html += `美 [${usphone}] `; 1030 | } 1031 | html += '
'; 1032 | if (phone && phone.length != 0) { 1033 | html += `
[${phone}]
`; 1034 | } 1035 | trs.forEach(element => { 1036 | tr += `
${element.tr[0].l.i[0]}
`; 1037 | }); 1038 | html += tr; 1039 | } 1040 | // 网络释义 1041 | if (rstJson.web_trans && 1042 | rstJson.web_trans['web-translation'] && 1043 | rstJson.web_trans['web-translation'].length > 0 && 1044 | rstJson.web_trans['web-translation'][0]['@same'] && 1045 | rstJson.web_trans['web-translation'][0]['@same'] == 'true' && 1046 | rstJson.web_trans['web-translation'][0].trans && 1047 | rstJson.web_trans['web-translation'][0].trans.length > 0) { 1048 | let webTrans = '网络:'; 1049 | rstJson.web_trans['web-translation'][0].trans.forEach(({ value, cls }, i) => { 1050 | if (value) { 1051 | if (cls && cls.cl && cls.cl.length > 0) { 1052 | cls.cl.forEach(cl => { 1053 | webTrans += `[${cl}]`; 1054 | }); 1055 | } 1056 | webTrans += value; 1057 | if (rstJson.web_trans['web-translation'][0].trans.length - 1 != i) { 1058 | webTrans += ';'; 1059 | } 1060 | } 1061 | }); 1062 | html += `
${webTrans}
`; 1063 | } 1064 | // 中英翻译 1065 | if (rstJson.ce_new && rstJson.ce_new.word) { 1066 | const arr = new iframeWin.Array(); 1067 | rstJson.ce_new.word.forEach(d => { 1068 | if (d.phone) { 1069 | const obj = new iframeWin.Object(); 1070 | obj['phone'] = d.phone; 1071 | arr.push(objToXml(obj)); 1072 | } 1073 | if (d['return-phrase']) { 1074 | const obj = new iframeWin.Object(); 1075 | obj['return-phrase'] = d['return-phrase']; 1076 | arr.push(objToXml(obj)); 1077 | } 1078 | if (d.trs) { 1079 | const obj = new iframeWin.Object(); 1080 | obj['trs'] = d.trs; 1081 | arr.push(objToXml(obj)); 1082 | } 1083 | }); 1084 | html += `
《${rstJson.ce_new.source && rstJson.ce_new.source.name ? rstJson.ce_new.source.name : ''}》
${xmlToHtml(objToXml(arr), 'div')}
`; 1085 | } 1086 | // 中文翻译 1087 | if (rstJson.hh && rstJson.hh.word) { 1088 | html += `
《现代汉语大词典》
${xmlToHtml(objToXml(rstJson.hh.word), 'span')}
`; 1089 | } 1090 | // 长句翻译 1091 | if (rstJson.fanyi && rstJson.fanyi.tran) { 1092 | html += rstJson.fanyi.tran; 1093 | } 1094 | } catch (error) { 1095 | log(error); 1096 | html += error; 1097 | } 1098 | const dom = document.createElement('div'); 1099 | dom.setAttribute('class', ids.YOUDAO); 1100 | dom.innerHTML = html; 1101 | return dom; 1102 | } 1103 | /**金山词霸排版*/ 1104 | function parseIciba(rst) { 1105 | let dom = document.createElement('div'); 1106 | dom.setAttribute('class', ids.ICIBA); 1107 | try { 1108 | let doc = htmlToDom(cleanHtml(rst)); 1109 | let mean = doc.querySelector('div[class^="Mean_mean"]'); 1110 | if (mean) { 1111 | mean.querySelectorAll('ul[class^="Mean_symbols"] li').forEach(el => { 1112 | el.innerHTML = el.innerHTML.replace(/英/g, '英 ').replace(/美/g, '美 '); 1113 | }); 1114 | let mt = mean.querySelector('p[class^="Mean_desc"]') 1115 | && mean.querySelector('p[class^="Mean_desc"]').innerHTML.includes('以上结果来自机器翻译。') 1116 | ? ',p[class^="Mean_desc"],h2[class^="Mean_sentence"]' : ''; 1117 | iterElementRemove(mean.querySelectorAll(`p[class^="Mean_tag"],p[class^="Mean_else"],ul[class^="TabList_tab"],h3[class^="Mean_title"]${mt}`));// 其它 1118 | let ky = []; 1119 | mean.querySelectorAll('a[href*="translate.iciba.com"]').forEach(el => ky.push(el.parentElement));// 快译 1120 | iterElementRemove(ky); 1121 | mean.innerHTML = mean.innerHTML.replace(/
  • <\/li>/g, '');// GNU、MODE 1122 | dom.appendChild(mean); 1123 | } 1124 | } catch (error) { 1125 | log(error); 1126 | dom.appendChild(htmlToDom(error)); 1127 | } 1128 | return dom; 1129 | } 1130 | /**沪江小D排版*/ 1131 | function parseHjenglish(rst) { 1132 | let dom = document.createElement('div'); 1133 | dom.setAttribute('class', ids.HJENGLISH); 1134 | try { 1135 | rst = cleanHtml(rst); 1136 | let doc = htmlToDom(rst); 1137 | let label = doc.querySelector('.word-details-item-content header'); 1138 | let entry = doc.querySelector('.word-text h2'); 1139 | let collins = doc.querySelector('div[data-id="detail"] .word-details-item-content .detail-groups'); 1140 | if (entry) { 1141 | let entryDom = document.createElement('div'); 1142 | entryDom.setAttribute('class', 'entry'); 1143 | entryDom.innerHTML = entry.innerHTML; 1144 | dom.appendChild(entryDom); 1145 | if (collins) { 1146 | if (label) { 1147 | let regex = /(《.*?》)/ig; 1148 | let match = regex.exec(label.innerHTML); 1149 | if (match && match[1]) { 1150 | dom.appendChild(htmlToDom(`
    ${match[1]}
    `)); 1151 | } 1152 | } 1153 | dom.appendChild(collins); 1154 | } 1155 | } 1156 | } catch (error) { 1157 | log(error); 1158 | dom.appendChild(htmlToDom(error)); 1159 | } 1160 | return dom; 1161 | } 1162 | /**必应词典排版*/ 1163 | function parseBing(rst) { 1164 | let html = ''; 1165 | try { 1166 | rst = rst.replace(/onmouseover/ig, 'data-sound'); // 发音链接预处理 1167 | rst = cleanHtml(rst) 1168 | .replace(/(?:a>)/ig, 'span>') 1169 | .replace(/(?:${entry.innerHTML}`; 1178 | if (concise) { 1179 | html += `
    ${concise.outerHTML}
    `; 1180 | } 1181 | if (tense) { 1182 | html += `
    ${tense.outerHTML}
    `; 1183 | } 1184 | if (oald) { 1185 | // 单条释义不显示序号 1186 | oald.querySelectorAll('.se_lis').forEach(({ parentElement, classList }) => { 1187 | if (parentElement.querySelectorAll('.se_lis').length == 1) { 1188 | classList.add('only'); 1189 | } 1190 | }); 1191 | let oaldHtml = oald.outerHTML; 1192 | oaldHtml = replaceHtmlTag(oaldHtml, 'table', 'div'); 1193 | oaldHtml = replaceHtmlTag(oaldHtml, 'tbody', 'div'); 1194 | oaldHtml = replaceHtmlTag(oaldHtml, 'tr', 'div'); 1195 | oaldHtml = replaceHtmlTag(oaldHtml, 'td', 'span'); 1196 | html += `
    《牛津高阶英汉双解词典第八版》
    ${oaldHtml}
    `; 1197 | } 1198 | } 1199 | // 计算机翻译 1200 | let machineTrans = doc.querySelector('.smt_hw'); 1201 | if (machineTrans && (machineTrans.innerHTML.includes('计算机翻译') || machineTrans.innerHTML.includes('Machine Translation'))) { 1202 | let parent = machineTrans.parentNode; 1203 | let zhText = parent.querySelector('.p1-11'); 1204 | if (zhText) { 1205 | html += `
    ${zhText.outerHTML}
    `; 1206 | } 1207 | } 1208 | } catch (error) { 1209 | log(error); 1210 | html += error; 1211 | } 1212 | let dom = document.createElement('div'); 1213 | dom.setAttribute('class', ids.BING); 1214 | dom.innerHTML = html; 1215 | // 发音 1216 | dom.querySelectorAll('#bigaud_us,#bigaud_uk').forEach(ele => { 1217 | let str = ele.getAttribute('data-mp3link'); 1218 | let regex = /(https:\/\/.+)/ig; 1219 | let match = regex.exec(str); 1220 | if (match && match.length >= 1) { 1221 | ele.appendChild(getPlayButton({ 1222 | name: '♫', 1223 | url: match[1] 1224 | })); 1225 | } 1226 | }); 1227 | return dom; 1228 | } 1229 | /**谷歌翻译排版*/ 1230 | function parseGoogle(rst) { 1231 | const dom = document.createElement('div'); 1232 | dom.setAttribute('class', ids.GOOGLE); 1233 | try { 1234 | dom.appendChild(htmlToDom(xmlToHtml(objToXml(iframeWin.JSON.parse(rst)), 'span'))); 1235 | } catch (error) { 1236 | log(error); 1237 | dom.appendChild(htmlToDom(error)); 1238 | } 1239 | return dom; 1240 | } 1241 | /**剑桥高阶排版*/ 1242 | function parseCambridge(rst) { 1243 | const dom = document.createElement('div'); 1244 | dom.setAttribute('class', ids.CAMBRIDGE); 1245 | try { 1246 | rst = rst.replace(/