├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── background.html ├── background ├── dict.coffee ├── dict.js ├── dict_resources.coffee ├── dict_resources.js ├── dictwindow.coffee ├── dictwindow.js ├── ext.coffee ├── ext.js ├── main.coffee ├── main.js ├── message.coffee ├── message.js ├── plain-lookup.coffee ├── plain-lookup.js ├── setting.coffee ├── setting.js ├── storage.coffee └── storage.js ├── bower.json ├── build └── fairy-dict.crx ├── content ├── inject-fontello.css ├── inject.coffee ├── inject.css └── inject.js ├── css ├── 91dict.css ├── apidict.css ├── baidu-dict.css ├── bing.css ├── bing.less ├── bootstrap-theme.css ├── bootstrap.css ├── cambridgeenglish.css ├── cambridgeenglish.less ├── collins.css ├── collins.less ├── dict-cn.css ├── dict-cn.less ├── dictheader.css ├── dictheader.less ├── dictionary-com.css ├── dictionary-com.less ├── eudic.css ├── eudic.less ├── font-awesome.css ├── hjenglish.css ├── iciba.css ├── iciba.less ├── longmanenglish.css ├── longmanenglish.less ├── macmilland.css ├── macmilland.less ├── merriamwebster.css ├── merriamwebster.less ├── options.css ├── options.less ├── oxfordlearner.css ├── oxfordlearner.less ├── oxfordliving.css ├── oxfordliving.less ├── urban.css ├── urban.less ├── youdao.css ├── youdao.less └── zdic.css ├── dict.coffee ├── dict.js ├── fonts ├── FontAwesome.otf ├── fairydict-font.woff ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff ├── fontawesome-webfont.woff2 ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── helper.coffee ├── helper.js ├── images ├── books-128.png ├── books-48.png ├── books-96.png ├── books-b-48.png ├── books-b-96.png ├── gplus32.png ├── tom.jpg ├── twitter32.jpg └── wordpress32.jpg ├── js ├── bootstrap.js ├── ion.sound.js ├── jquery-1.10.2.js ├── jquery.scoped.js └── starrr.js ├── loader.coffee ├── loader.js ├── manifest.json ├── needsharebutton.min.css ├── needsharebutton.min.js ├── option ├── option.coffee └── option.js ├── options.html ├── pack.sh ├── readme_images ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── template ├── apidict.html ├── apidict.js └── header.html ├── utils.coffee └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "coffeescript.compile": { 3 | "bare": true, 4 | "sourceMap": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Important: 此扩展的升级版 [Dictionaries](https://github.com/revir/dictionaries) 已发布, 添加了管理单词表和词典列表的功能, 支持日语查询, 还有更多的功能和设置, 欢迎去下载使用 [Dictionaries](https://github.com/revir/dictionaries). FairyDict 将不再更新!** 2 | 3 | ---- 4 | 5 | *8月11日更新:新增百度、汉典等汉语词典;快速查词的显示框位置可以设置了!* 6 | 7 | *7月8日更新:期待的鼠标悬停取词的功能终于来了。* 8 | 9 | *7月6日更新:快速查词添加了音标和自动发音功能。* 10 | 11 | *7月5日更新:增强快速查词体验,增加了一个设置项,点击图标可以打开查词典的窗口了。* 12 | 13 | *6月28日更新:新增快速查词功能,选中单词后在鼠标附近显示词义。可设置开关。* 14 | 15 | *5月5日更新: 重新排好了Toolbar上的元素,布局更整洁了。金山词霸替换成了网页版。* 16 | 17 | *5月2日更新: 已添加大量常见的在线词典,包括欧陆词典, Longman, Oxford, Cambridge, Merriam-webster, Collins, Macmilland 等等。还增加了一个设置: 鼠标取词时可以选择必须按住一个按键(Ctrl, Shift, Alt 等)。* 18 | 19 | 20 | # Fairy Dict 21 | 22 | 查词典的 chrome extension, 方便中国人阅读英文网站,我自己每天都依赖它,功能和使用体验上我尽量做得更好, 欢迎大家提供反馈。 23 | 24 | # 安装地址 25 | 26 | * [chrome web store](https://chrome.google.com/webstore/detail/fairydict/gpdpcfgfmgkmljmhhnedefdaadgehaah) 27 | 28 | # Feature 29 | 30 | * 支持多种词典,金山词霸、海词、必应,Urban, Dictionary.com, Longman, Oxford, Cambridge, Merriam-webster, Collins, Macmilland 等几乎所有的在线词典;理论上可以支持所有在线词典; 31 | * 支持鼠标取词、键盘快捷键查词、鼠标右键查词等查询方式; 32 | * 支持快速查词功能,选中单词后在鼠标附近显示词义; 33 | * 历史记录保存,自动同步; 34 | * 窗口大小可自动记忆; 35 | 36 | # TODO 37 | 38 | * 支持更多词典,目前通过请求网页的方式可以支持几乎所有在线词典,还可以添加 Google,Bing 等翻译; 39 | * 目前的历史记录最多仅保存200个,受限于 chrome storage 的容量限制,如需保存更多,需要搭建服务器,或者保存至有道等网站的生词本里; 40 | * 卡片式单词记忆; 41 | 42 | # FAQ 43 | 44 | 1. 怎样鼠标取词? 45 | 在 Chrome 浏览器上,用鼠标双击选中单词,或按住左键拖动选择一段,等你鼠标松开后(触发了 mouseup event)才会查询。 46 | 47 | 2. 安装后怎么没有反应? 48 | 安装后必须刷新一下页面才可以取词,一般来说 Chrome extension 都这样。但无需重启浏览器。 49 | 有极少数网站可能无法用鼠标或快捷键取词,可以试试鼠标右键。 50 | 更改了快捷键设置后页面也需要刷新一下才会生效。 51 | 52 | 3. 弹窗太烦了,可以关闭吗? 53 | 点击 FairyDict 的图标(一般在浏览器右上角处)即可 打开/关闭 鼠标取词, 还可以在选项里设置必须按住一个功能键才能鼠标取词。默认安装后鼠标取词是开的。 54 | 55 | 4. 窗口太大了,可以小一点吗? 56 | 窗口大小可以拖动,改变大小后,只要你再查一个单词,这个设置就记住了,请不要问为什么还要“再查一个单词”哦 ~ 57 | 58 | 5. 360,腾讯,搜狗浏览器可以安装吗? 59 | 只要是 Chromium 内核的浏览器,通过[本地下载](https://github.com/revir/FairyDict/raw/master/build/fairy-dict.crx) CRX 文件,拖到浏览器的扩展页面里,应该是可以用的,如果不能用,请向我反馈。 60 | 61 | 6. 能支持我喜欢的**词典吗? 62 | 告诉我词典名字,只要网上有 Web 版就能加进来。 63 | 64 | 65 | # Screenshot 66 | ![Alt text](https://github.com/revir/FairyDict/raw/master/readme_images/5.png) 67 | ![Alt text](https://github.com/revir/FairyDict/raw/master/readme_images/4.png) 68 | ![Alt text](https://github.com/revir/FairyDict/raw/master/readme_images/3.png) 69 | ![Alt text](https://github.com/revir/FairyDict/raw/master/readme_images/2.png) 70 | -------------------------------------------------------------------------------- /background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Background Main 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /background/dict.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils"], ($, utils)-> 3 | console.log "[dict] init" 4 | 5 | allDicts = [{ 6 | 'dictName': "必应词典", 7 | 'windowUrl': 'http://cn.bing.com/dict/search?q=', 8 | 'windowUrlMatch': '[^\\w]q=([^&]+)', 9 | "resources": { 10 | styles: ['css/bing.css'] 11 | } 12 | }, { 13 | 'dictName': '金山词霸', 14 | 'windowUrl': 'http://www.iciba.com/', 15 | 'windowUrlMatch': 'iciba.com/([^&]+)', 16 | "resources": { 17 | styles: ['css/iciba.css'] 18 | } 19 | # 'entry': 'Iciba', 20 | # 'baseUrl': 'http://dict-co.iciba.com/api/dictionary.php', 21 | # 'queryType': 'get', 22 | # 'params': { 23 | # 'key': '0AAE477DB66EC58D12E1451877045CA5' 24 | # }, 25 | # 'queryKey': 'w' 26 | }, { 27 | 'dictName': '有道词典', 28 | 'entry': 'youdao', 29 | 'windowUrl': 'http://dict.youdao.com/w/eng/', 30 | 'windowUrlMatch': '/eng/([^&/?]+)' 31 | "resources": { 32 | styles: ['css/youdao.css'] 33 | } 34 | }, { 35 | 'dictName': '海词词典', 36 | 'entry': 'dict-cn', 37 | 'windowUrl': 'http://dict.cn/', 38 | 'windowUrlMatch': 'dict.cn/([^&/?]+)' 39 | "resources": { 40 | styles: ['css/dict-cn.css'] 41 | } 42 | }, { 43 | 'dictName': '沪江词典', 44 | 'entry': 'hjenglish', 45 | 'windowUrl': 'https://dict.hjenglish.com/w/', 46 | 'windowUrlMatch': '/w/([^&/?]+)' 47 | "resources": { 48 | styles: ['css/hjenglish.css'] 49 | } 50 | }, { 51 | 'dictName': '人人词典', 52 | 'entry': '91dict', 53 | 'windowUrl': 'http://www.91dict.com/words?w=', 54 | 'windowUrlMatch': '[^\\w]w=([^&]+)' 55 | "resources": { 56 | styles: ['css/91dict.css'] 57 | } 58 | }, { 59 | 'dictName': '欧陆词典', 60 | 'entry': 'eudic', 61 | 'windowUrl': 'https://dict.eudic.net/dicts/en/', 62 | 'windowUrlMatch': '/en/([^&/?]+)' 63 | "resources": { 64 | styles: ['css/eudic.css'] 65 | } 66 | }, { 67 | 'dictName': '汉典', 68 | 'entry': 'zdic', 69 | 'windowUrl': 'http://www.zdic.net/search/?c=3&q=', 70 | 'windowUrlMatch': '[^\\w]q=([^&]+)' 71 | "resources": { 72 | styles: ['css/zdic.css'] 73 | } 74 | }, { 75 | 'dictName': '百度词典(汉语)', 76 | 'entry': 'baidu-dict', 77 | 'windowUrl': 'http://dict.baidu.com/s?wd=', 78 | 'windowUrlMatch': '[^\\w]wd=([^&]+)' 79 | "resources": { 80 | styles: ['css/baidu-dict.css'] 81 | } 82 | }, { 83 | 'dictName': 'Oxford Learner', 84 | 'entry': 'OxfordLearner', 85 | 'windowUrl': 'http://www.oxfordlearnersdictionaries.com/definition/english/' 86 | 'windowUrlMatch': '/english/([^&/?]+)' 87 | "resources": { 88 | styles: ['css/oxfordlearner.css'] 89 | } 90 | }, { 91 | 'dictName': 'Oxford Living Dictionaries', 92 | 'entry': 'Oxfordliving', 93 | 'windowUrl': 'https://en.oxforddictionaries.com/definition/' 94 | 'windowUrlMatch': '/definition/([^&/?]+)' 95 | "resources": { 96 | styles: ['css/oxfordliving.css'] 97 | } 98 | }, { 99 | 'dictName': 'Cambridge English' 100 | 'entry': 'CambridgeEnglish' 101 | 'windowUrl': 'http://dictionary.cambridge.org/dictionary/english/' 102 | 'windowUrlMatch': '/english/([^&/?]+)', 103 | "resources": { 104 | styles: ['css/cambridgeenglish.css'] 105 | } 106 | }, { 107 | 'dictName': 'Longman English' 108 | 'entry': 'LongmanEnglish' 109 | 'windowUrl': 'http://www.ldoceonline.com/dictionary/' 110 | 'windowUrlMatch': '/dictionary/([^&/?]+)', 111 | "resources": { 112 | styles: ['css/longmanenglish.css'] 113 | } 114 | }, { 115 | 'dictName': 'Urban Dictionary', 116 | 'windowUrl': 'http://zh.urbandictionary.com/define.php?term=', 117 | 'windowUrlMatch': '[^\\w]term=([^&]+)', 118 | "resources": { 119 | styles: ['css/urban.css'] 120 | } 121 | }, { 122 | 'dictName': 'Dictionary.com', 123 | 'windowUrl': 'http://www.dictionary.com/browse/', 124 | 'windowUrlMatch': '/browse/([^&/?]+)', 125 | "resources": { 126 | styles: ['css/dictionary-com.css'] 127 | } 128 | }, { 129 | 'dictName': 'Thesaurus.com', 130 | 'windowUrl': 'http://www.thesaurus.com/browse/', 131 | 'windowUrlMatch': '/browse/([^&/?]+)', 132 | "resources": { 133 | styles: ['css/dictionary-com.css'] 134 | } 135 | }, { 136 | 'dictName': 'Macmilland Dictionary', 137 | 'windowUrl': 'http://www.macmillandictionary.com/dictionary/british/', 138 | 'windowUrlMatch': '/british/([^&/?]+)', 139 | "resources": { 140 | styles: ['css/macmilland.css'] 141 | } 142 | }, { 143 | 'dictName': 'Merriam-webster Dictionary', 144 | 'windowUrl': 'https://www.merriam-webster.com/dictionary/', 145 | 'windowUrlMatch': '/dictionary/([^&/?]+)', 146 | "resources": { 147 | styles: ['css/merriamwebster.css'] 148 | } 149 | }, { 150 | 'dictName': 'Merriam-webster Thesaurus', 151 | 'windowUrl': 'https://www.merriam-webster.com/thesaurus/', 152 | 'windowUrlMatch': '/thesaurus/([^&/?]+)', 153 | "resources": { 154 | styles: ['css/merriamwebster.css'] 155 | } 156 | }, { 157 | 'dictName': 'Collins English Dictionary', 158 | 'windowUrl': 'https://www.collinsdictionary.com/dictionary/english/', 159 | 'windowUrlMatch': '/english/([^&/?]+)', 160 | "resources": { 161 | styles: ['css/collins.css'] 162 | } 163 | }, { 164 | 'dictName': 'Collins English Thesaurus', 165 | 'windowUrl': 'https://www.collinsdictionary.com/dictionary/english-thesaurus/', 166 | 'windowUrlMatch': '/english-thesaurus/([^&/?]+)', 167 | "resources": { 168 | styles: ['css/collins.css'] 169 | } 170 | }, { 171 | 'dictName': 'Easton\'s 1897 Bible Dictionary', 172 | 'entry': 'Aonaware', 173 | 'baseUrl': 'http://services.aonaware.com/DictService/DictService.asmx/DefineInDict', 174 | 'queryType': 'post', 175 | 'params': { 176 | 'dictId': 'easton' 177 | }, 178 | 'queryKey': 'word' 179 | }] 180 | 181 | dictManager = 182 | allDicts: allDicts, 183 | getDict: (dictName)-> 184 | dict = allDicts.find (d)-> 185 | d.dictName == dictName 186 | return dict or allDicts[0] 187 | 188 | getDictResources: (dictName)-> 189 | dict = @getDict(dictName) 190 | if dict.windowUrl 191 | # web dict 192 | return dict.resources 193 | 194 | getWordFromUrl: (url, dictName)-> 195 | dict = @getDict(dictName) 196 | if dict.windowUrlMatch 197 | m = new RegExp(dict.windowUrlMatch) 198 | s = url?.match(m)?[1] 199 | if s 200 | s = s.replace(/[+_]/g, ' ') 201 | return decodeURI(s) 202 | return 203 | 204 | query: (word, dictName)-> 205 | dfd = $.Deferred() 206 | dict = @getDict(dictName) 207 | params = $.extend(true, {}, dict.params) 208 | params[dict.queryKey] = word if dict.queryKey 209 | url = dict.baseUrl if dict.baseUrl 210 | url = dict.headerUrl + encodeURI(word) if dict.headerUrl 211 | if url 212 | return $[dict.queryType || 'get'](url, params, null, 'text').then (res)=> 213 | return {html: @['parse' + dict.entry](res)} 214 | else if dict.windowUrl 215 | return dfd.resolve {windowUrl: dict.windowUrl.replace('', word)} 216 | else 217 | return dfd.resolve({html: @['parse' + dict.entry](word)}) 218 | 219 | queryWordPain: (word) -> 220 | url = "http://xtk.azurewebsites.net/BingDictService.aspx?Word=#{word}&Samples=false" 221 | return $.get(url) 222 | 223 | parseAonaware: (text)-> 224 | xml = $.parseXML(text) 225 | return '
' + $('Definitions WordDefinition', xml).text() + '
' 226 | parseIciba: (text)-> 227 | d = $(document.createElement('div')) 228 | xml = $.parseXML(text) 229 | d.append('

') 230 | $('ps', xml).each (index, el)-> 231 | t = $(el).text() 232 | audio = $(el).next('pron').text() 233 | n = '' + t + ' ' + '    ' + '' 234 | $('h4', d).append(n) 235 | 236 | $('pos', xml).each (i, el)-> 237 | m = $(el).next('acceptation').text() 238 | s = '

' + $(el).text() + '' + m + '' + '

' 239 | d.append(s) 240 | 241 | $('orig, fy', xml).each (i, el)-> 242 | m = $(el).next('trans').text() 243 | s = '

' + $(el).text() + '

' 244 | s2 = '

' + m + '

' 245 | d.append(s) 246 | d.append(s2) 247 | 248 | return d.html() 249 | 250 | parseWebster: (text)-> 251 | wrapper = $(document.createElement('div')) 252 | wrapper.addClass('webster') 253 | xml = $.parseXML(text) 254 | $('entry', xml).each (index, entry)-> 255 | entryId = $(entry).attr('id') 256 | wrapper.append('
') 257 | d = $('div[entry]', wrapper).last() 258 | 259 | pro = $('pr', entry).text() 260 | 261 | soundEl = $('sound', entry) 262 | audio = $('wav', soundEl).text() 263 | wpr = $('wpr', soundEl).text() 264 | 265 | baseUrl = 'http://media.merriam-webster.com/soundc11/' 266 | subp = audio[0] 267 | ms = audio.match(/^bix|^gg|^\d+/) 268 | if ms 269 | subp = ms[0] 270 | 271 | url = baseUrl + subp + '/' + audio 272 | 273 | 274 | n = '

' +entryId+ 275 | '' + 276 | '' + pro + 277 | ' ' + 278 | ' ' + wpr + 279 | '    

' 280 | d.append(n) 281 | 282 | et = $('et', entry).html() 283 | n2 = '

'+et+'

' 284 | d.append(n2) 285 | 286 | $(entry).children('def').each (j, defEl)-> 287 | vt = $('vt', defEl).text() 288 | date = $('date', defEl).text() 289 | d.append('

'+vt+', '+date + '

') 290 | $('sn', defEl).each (k, snEl)-> 291 | sntext = $(snEl).nextUtil('sn').text() 292 | d.append('

'+sntext+'

') 293 | 294 | return wrapper.htm() 295 | 296 | 297 | return dictManager 298 | -------------------------------------------------------------------------------- /background/dict.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | define(["jquery", "utils"], function($, utils) { 3 | var allDicts, dictManager; 4 | console.log("[dict] init"); 5 | allDicts = [ 6 | { 7 | 'dictName': "必应词典", 8 | 'windowUrl': 'http://cn.bing.com/dict/search?q=', 9 | 'windowUrlMatch': '[^\\w]q=([^&]+)', 10 | "resources": { 11 | styles: ['css/bing.css'] 12 | } 13 | }, { 14 | 'dictName': '金山词霸', 15 | 'windowUrl': 'http://www.iciba.com/', 16 | 'windowUrlMatch': 'iciba.com/([^&]+)', 17 | "resources": { 18 | styles: ['css/iciba.css'] 19 | } 20 | }, { 21 | 'dictName': '有道词典', 22 | 'entry': 'youdao', 23 | 'windowUrl': 'http://dict.youdao.com/w/eng/', 24 | 'windowUrlMatch': '/eng/([^&/?]+)', 25 | "resources": { 26 | styles: ['css/youdao.css'] 27 | } 28 | }, { 29 | 'dictName': '海词词典', 30 | 'entry': 'dict-cn', 31 | 'windowUrl': 'http://dict.cn/', 32 | 'windowUrlMatch': 'dict.cn/([^&/?]+)', 33 | "resources": { 34 | styles: ['css/dict-cn.css'] 35 | } 36 | }, { 37 | 'dictName': '沪江词典', 38 | 'entry': 'hjenglish', 39 | 'windowUrl': 'https://dict.hjenglish.com/w/', 40 | 'windowUrlMatch': '/w/([^&/?]+)', 41 | "resources": { 42 | styles: ['css/hjenglish.css'] 43 | } 44 | }, { 45 | 'dictName': '人人词典', 46 | 'entry': '91dict', 47 | 'windowUrl': 'http://www.91dict.com/words?w=', 48 | 'windowUrlMatch': '[^\\w]w=([^&]+)', 49 | "resources": { 50 | styles: ['css/91dict.css'] 51 | } 52 | }, { 53 | 'dictName': '欧陆词典', 54 | 'entry': 'eudic', 55 | 'windowUrl': 'https://dict.eudic.net/dicts/en/', 56 | 'windowUrlMatch': '/en/([^&/?]+)', 57 | "resources": { 58 | styles: ['css/eudic.css'] 59 | } 60 | }, { 61 | 'dictName': '汉典', 62 | 'entry': 'zdic', 63 | 'windowUrl': 'http://www.zdic.net/search/?c=3&q=', 64 | 'windowUrlMatch': '[^\\w]q=([^&]+)', 65 | "resources": { 66 | styles: ['css/zdic.css'] 67 | } 68 | }, { 69 | 'dictName': '百度词典(汉语)', 70 | 'entry': 'baidu-dict', 71 | 'windowUrl': 'http://dict.baidu.com/s?wd=', 72 | 'windowUrlMatch': '[^\\w]wd=([^&]+)', 73 | "resources": { 74 | styles: ['css/baidu-dict.css'] 75 | } 76 | }, { 77 | 'dictName': 'Oxford Learner', 78 | 'entry': 'OxfordLearner', 79 | 'windowUrl': 'http://www.oxfordlearnersdictionaries.com/definition/english/', 80 | 'windowUrlMatch': '/english/([^&/?]+)', 81 | "resources": { 82 | styles: ['css/oxfordlearner.css'] 83 | } 84 | }, { 85 | 'dictName': 'Oxford Living Dictionaries', 86 | 'entry': 'Oxfordliving', 87 | 'windowUrl': 'https://en.oxforddictionaries.com/definition/', 88 | 'windowUrlMatch': '/definition/([^&/?]+)', 89 | "resources": { 90 | styles: ['css/oxfordliving.css'] 91 | } 92 | }, { 93 | 'dictName': 'Cambridge English', 94 | 'entry': 'CambridgeEnglish', 95 | 'windowUrl': 'http://dictionary.cambridge.org/dictionary/english/', 96 | 'windowUrlMatch': '/english/([^&/?]+)', 97 | "resources": { 98 | styles: ['css/cambridgeenglish.css'] 99 | } 100 | }, { 101 | 'dictName': 'Longman English', 102 | 'entry': 'LongmanEnglish', 103 | 'windowUrl': 'http://www.ldoceonline.com/dictionary/', 104 | 'windowUrlMatch': '/dictionary/([^&/?]+)', 105 | "resources": { 106 | styles: ['css/longmanenglish.css'] 107 | } 108 | }, { 109 | 'dictName': 'Urban Dictionary', 110 | 'windowUrl': 'http://zh.urbandictionary.com/define.php?term=', 111 | 'windowUrlMatch': '[^\\w]term=([^&]+)', 112 | "resources": { 113 | styles: ['css/urban.css'] 114 | } 115 | }, { 116 | 'dictName': 'Dictionary.com', 117 | 'windowUrl': 'http://www.dictionary.com/browse/', 118 | 'windowUrlMatch': '/browse/([^&/?]+)', 119 | "resources": { 120 | styles: ['css/dictionary-com.css'] 121 | } 122 | }, { 123 | 'dictName': 'Thesaurus.com', 124 | 'windowUrl': 'http://www.thesaurus.com/browse/', 125 | 'windowUrlMatch': '/browse/([^&/?]+)', 126 | "resources": { 127 | styles: ['css/dictionary-com.css'] 128 | } 129 | }, { 130 | 'dictName': 'Macmilland Dictionary', 131 | 'windowUrl': 'http://www.macmillandictionary.com/dictionary/british/', 132 | 'windowUrlMatch': '/british/([^&/?]+)', 133 | "resources": { 134 | styles: ['css/macmilland.css'] 135 | } 136 | }, { 137 | 'dictName': 'Merriam-webster Dictionary', 138 | 'windowUrl': 'https://www.merriam-webster.com/dictionary/', 139 | 'windowUrlMatch': '/dictionary/([^&/?]+)', 140 | "resources": { 141 | styles: ['css/merriamwebster.css'] 142 | } 143 | }, { 144 | 'dictName': 'Merriam-webster Thesaurus', 145 | 'windowUrl': 'https://www.merriam-webster.com/thesaurus/', 146 | 'windowUrlMatch': '/thesaurus/([^&/?]+)', 147 | "resources": { 148 | styles: ['css/merriamwebster.css'] 149 | } 150 | }, { 151 | 'dictName': 'Collins English Dictionary', 152 | 'windowUrl': 'https://www.collinsdictionary.com/dictionary/english/', 153 | 'windowUrlMatch': '/english/([^&/?]+)', 154 | "resources": { 155 | styles: ['css/collins.css'] 156 | } 157 | }, { 158 | 'dictName': 'Collins English Thesaurus', 159 | 'windowUrl': 'https://www.collinsdictionary.com/dictionary/english-thesaurus/', 160 | 'windowUrlMatch': '/english-thesaurus/([^&/?]+)', 161 | "resources": { 162 | styles: ['css/collins.css'] 163 | } 164 | }, { 165 | 'dictName': 'Easton\'s 1897 Bible Dictionary', 166 | 'entry': 'Aonaware', 167 | 'baseUrl': 'http://services.aonaware.com/DictService/DictService.asmx/DefineInDict', 168 | 'queryType': 'post', 169 | 'params': { 170 | 'dictId': 'easton' 171 | }, 172 | 'queryKey': 'word' 173 | } 174 | ]; 175 | dictManager = { 176 | allDicts: allDicts, 177 | getDict: function(dictName) { 178 | var dict; 179 | dict = allDicts.find(function(d) { 180 | return d.dictName === dictName; 181 | }); 182 | return dict || allDicts[0]; 183 | }, 184 | getDictResources: function(dictName) { 185 | var dict; 186 | dict = this.getDict(dictName); 187 | if (dict.windowUrl) { 188 | return dict.resources; 189 | } 190 | }, 191 | getWordFromUrl: function(url, dictName) { 192 | var dict, m, ref, s; 193 | dict = this.getDict(dictName); 194 | if (dict.windowUrlMatch) { 195 | m = new RegExp(dict.windowUrlMatch); 196 | s = url != null ? (ref = url.match(m)) != null ? ref[1] : void 0 : void 0; 197 | if (s) { 198 | s = s.replace(/[+_]/g, ' '); 199 | return decodeURI(s); 200 | } 201 | } 202 | }, 203 | query: function(word, dictName) { 204 | var dfd, dict, params, url; 205 | dfd = $.Deferred(); 206 | dict = this.getDict(dictName); 207 | params = $.extend(true, {}, dict.params); 208 | if (dict.queryKey) { 209 | params[dict.queryKey] = word; 210 | } 211 | if (dict.baseUrl) { 212 | url = dict.baseUrl; 213 | } 214 | if (dict.headerUrl) { 215 | url = dict.headerUrl + encodeURI(word); 216 | } 217 | if (url) { 218 | return $[dict.queryType || 'get'](url, params, null, 'text').then((function(_this) { 219 | return function(res) { 220 | return { 221 | html: _this['parse' + dict.entry](res) 222 | }; 223 | }; 224 | })(this)); 225 | } else if (dict.windowUrl) { 226 | return dfd.resolve({ 227 | windowUrl: dict.windowUrl.replace('', word) 228 | }); 229 | } else { 230 | return dfd.resolve({ 231 | html: this['parse' + dict.entry](word) 232 | }); 233 | } 234 | }, 235 | queryWordPain: function(word) { 236 | var url; 237 | url = "http://xtk.azurewebsites.net/BingDictService.aspx?Word=" + word + "&Samples=false"; 238 | return $.get(url); 239 | }, 240 | parseAonaware: function(text) { 241 | var xml; 242 | xml = $.parseXML(text); 243 | return '
' + $('Definitions WordDefinition', xml).text() + '
'; 244 | }, 245 | parseIciba: function(text) { 246 | var d, xml; 247 | d = $(document.createElement('div')); 248 | xml = $.parseXML(text); 249 | d.append('

'); 250 | $('ps', xml).each(function(index, el) { 251 | var audio, n, t; 252 | t = $(el).text(); 253 | audio = $(el).next('pron').text(); 254 | n = '' + t + ' ' + '    ' + ''; 255 | return $('h4', d).append(n); 256 | }); 257 | $('pos', xml).each(function(i, el) { 258 | var m, s; 259 | m = $(el).next('acceptation').text(); 260 | s = '

' + $(el).text() + '' + m + '' + '

'; 261 | return d.append(s); 262 | }); 263 | $('orig, fy', xml).each(function(i, el) { 264 | var m, s, s2; 265 | m = $(el).next('trans').text(); 266 | s = '

' + $(el).text() + '

'; 267 | s2 = '

' + m + '

'; 268 | d.append(s); 269 | return d.append(s2); 270 | }); 271 | return d.html(); 272 | }, 273 | parseWebster: function(text) { 274 | var wrapper, xml; 275 | wrapper = $(document.createElement('div')); 276 | wrapper.addClass('webster'); 277 | xml = $.parseXML(text); 278 | $('entry', xml).each(function(index, entry) { 279 | var audio, baseUrl, d, entryId, et, ms, n, n2, pro, soundEl, subp, url, wpr; 280 | entryId = $(entry).attr('id'); 281 | wrapper.append('
'); 282 | d = $('div[entry]', wrapper).last(); 283 | pro = $('pr', entry).text(); 284 | soundEl = $('sound', entry); 285 | audio = $('wav', soundEl).text(); 286 | wpr = $('wpr', soundEl).text(); 287 | baseUrl = 'http://media.merriam-webster.com/soundc11/'; 288 | subp = audio[0]; 289 | ms = audio.match(/^bix|^gg|^\d+/); 290 | if (ms) { 291 | subp = ms[0]; 292 | } 293 | url = baseUrl + subp + '/' + audio; 294 | n = '

' + entryId + '' + '' + pro + ' ' + ' ' + wpr + '    

'; 295 | d.append(n); 296 | et = $('et', entry).html(); 297 | n2 = '

' + et + '

'; 298 | d.append(n2); 299 | return $(entry).children('def').each(function(j, defEl) { 300 | var date, vt; 301 | vt = $('vt', defEl).text(); 302 | date = $('date', defEl).text(); 303 | d.append('

' + vt + ', ' + date + '

'); 304 | return $('sn', defEl).each(function(k, snEl) { 305 | var sntext; 306 | sntext = $(snEl).nextUtil('sn').text(); 307 | return d.append('

' + sntext + '

'); 308 | }); 309 | }); 310 | }); 311 | return wrapper.htm(); 312 | } 313 | }; 314 | return dictManager; 315 | }); 316 | -------------------------------------------------------------------------------- /background/dict_resources.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/background/dict_resources.coffee -------------------------------------------------------------------------------- /background/dict_resources.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | 3 | -------------------------------------------------------------------------------- /background/dictwindow.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils", 3 | "background/setting", 4 | "background/dict.js", 5 | "background/storage"], ($, utils, setting, dict, storage)-> 6 | console.log "[dictwindow] init" 7 | defaultWindowUrl = chrome.extension.getURL('template/apidict.html') 8 | updateWindowDfd = null 9 | injectContentDfd = null 10 | dictInitedDfd = null 11 | windowType = 'popup' 12 | 13 | dictWindowManager = 14 | w: null 15 | tid: null 16 | url: null 17 | word: null 18 | open: ()-> 19 | dfd = $.Deferred() 20 | 21 | # bugfix: dont know how why, windowWidth and windowHeight are saved as number, need integer here. 22 | width = parseInt(setting.getValue('windowWidth')) 23 | height = parseInt(setting.getValue('windowHeight')) 24 | left = Math.round((screen.width / 2) - (width / 2)) 25 | top = Math.round((screen.height / 2) - (height / 2)) 26 | url = 'http://blog.riverrun.xyz/fairydict.html' 27 | if !@w 28 | @beforeUpdateUrl() 29 | chrome.windows.create({ 30 | url: defaultWindowUrl, 31 | # url: 'http://cn.bing.com/dict/search?q=elephant', 32 | type: windowType, 33 | width: width, 34 | height: height, 35 | left: left, 36 | top: top 37 | }, (win)=> 38 | @w = win 39 | @tid = @w.tabs[0].id 40 | @url = defaultWindowUrl 41 | dfd.resolve() 42 | ) 43 | else 44 | chrome.windows.update(dictWindowManager.w.id, { 45 | focused: true 46 | }) 47 | dfd.resolve() 48 | 49 | return dfd 50 | 51 | sendMessage: (msg)-> 52 | chrome.tabs.sendMessage(@tid, msg) if @tid 53 | 54 | lookup: (text)-> 55 | dictName = setting.getValue('dictionary') 56 | @open().done ()=> 57 | if text 58 | @sendMessage({type: 'querying', text}) 59 | @queryDict(text, dictName) 60 | 61 | queryDict: (text, dictName, inHistory)-> 62 | @word = text 63 | if not inHistory 64 | @sendMessage({type: 'history', history: storage.history}) 65 | 66 | console.log "[dictwindow] query #{@word} from #{dictName}" 67 | dict.query(text, dictName).then @sendQueryResult.bind(this) 68 | 69 | saveWindowSize: ()-> 70 | chrome.windows.get @w.id, null, (w)=> 71 | if w?.width and w?.height 72 | if Math.abs(w.width-setting.getValue('windowWidth')) > 10 or Math.abs(w.height-setting.getValue('windowHeight')) > 10 73 | console.log "[dictwindow] update window size: #{w.width} * #{w.height}" 74 | setting.setValue 'windowWidth', w.width 75 | setting.setValue 'windowHeight', w.height 76 | 77 | sendQueryResult: (result)-> 78 | @saveWindowSize() 79 | item = storage.isInHistory(@word) 80 | if result 81 | udfd = @updateUrl(result.windowUrl or defaultWindowUrl) 82 | 83 | $.when(updateWindowDfd, dictInitedDfd, udfd).then ()=> 84 | console.log "[dictwindow] send query result" 85 | @sendMessage({ 86 | type: 'queryResult', 87 | result: result, 88 | text: @word, 89 | inHistory: item?, 90 | rating: item?[@word]}) 91 | 92 | # if @word is a long sentence, don't keep in history 93 | if not item and @word.split(/\s+/).length <= 5 94 | storage.addHistory(@word) 95 | 96 | injectResources: ()-> 97 | styles = [ 98 | "css/bootstrap.css", 99 | "bower_components/angular-ui/build/angular-ui.css", 100 | "bower_components/angular-bootstrap/ui-bootstrap-csp.css", 101 | "css/font-awesome.css", 102 | "css/dictheader.css" 103 | ] 104 | scripts = [ 105 | 'bower_components/jquery/dist/jquery.js', 106 | 'bower_components/underscore/underscore.js', 107 | "bower_components/angular/angular.js", 108 | "bower_components/angular-ui/build/angular-ui.js", 109 | "bower_components/angular-sanitize/angular-sanitize.js", 110 | "bower_components/angular-bootstrap/ui-bootstrap.js", 111 | "utils.js", 112 | "js/starrr.js", 113 | "loader.js", 114 | "dict.js" 115 | ] 116 | 117 | inject = (t, files, index)=> 118 | files ?= [] 119 | index ?= 0 120 | dfd = $.Deferred() 121 | file = files[index] 122 | if file and t and @tid 123 | console.log "[dictwindow] inject #{file}" 124 | chrome.tabs[t] @tid, {file: file}, ()=> 125 | inject(t, files, index+1).then ()=> 126 | dfd.resolve() 127 | else 128 | dfd.resolve() 129 | return dfd 130 | 131 | res = dict.getDictResources(setting.getValue('dictionary')) 132 | 133 | # chrome-extension:// can't inject files, permission denied. 134 | if @url.indexOf('chrome-extension://') == 0 135 | return inject(null) 136 | 137 | return inject('insertCSS', res?.styles).then ()=> 138 | return inject('insertCSS', styles).then ()=> 139 | return inject('executeScript', scripts).then ()=> 140 | return inject('executeScript', res?.scripts) 141 | 142 | updateUrl: (url)-> 143 | outDfd = $.Deferred() 144 | if url and @url != url 145 | console.log "[dictwindow] update url: #{url}" 146 | @url = url 147 | @beforeUpdateUrl().then ()=> 148 | console.log "[dictwindow] updated url: #{url}" 149 | outDfd.resolve(true) 150 | chrome.tabs.update @tid, {url, active: true} 151 | return outDfd 152 | else 153 | return outDfd.resolve(false) 154 | 155 | beforeUpdateUrl: ()-> 156 | injectContentDfd = $.Deferred((dfd)=> 157 | dfd.then ()=> 158 | return @injectResources().then ()=> 159 | updateWindowDfd?.resolve() 160 | ) 161 | dictInitedDfd = $.Deferred() 162 | updateWindowDfd = $.Deferred() 163 | return updateWindowDfd 164 | 165 | onContentInjected: (url, tabId)-> 166 | console.log "[dictwindow] manifest's content scripts injected from url: #{url}" 167 | if injectContentDfd?.state() == 'pending' 168 | injectContentDfd.resolve() 169 | 170 | # page was reloaded. 171 | else if url and tabId == @tid 172 | d = setting.getValue('dictionary') 173 | w = dict.getWordFromUrl(url, d) 174 | @url = url 175 | if w 176 | @word = w 177 | else 178 | w = @word 179 | 180 | dictInitedDfd = $.Deferred() 181 | updateWindowDfd = $.Deferred() 182 | console.log "[dictwindow] reload #{w} url #{url}" 183 | 184 | @injectResources().then ()=> 185 | if @url == defaultWindowUrl 186 | dict.query(@word, d).then @sendQueryResult.bind(@) 187 | else 188 | @sendQueryResult() 189 | updateWindowDfd.resolve() 190 | 191 | onDictInited: ()-> 192 | console.log "[dictwindow] dict inited" 193 | dictInitedDfd?.resolve() 194 | @sendMessage({type: 'history', history: storage.history}) 195 | 196 | 197 | chrome.windows.onRemoved.addListener (wid)-> 198 | if dictWindowManager.w?.id == wid 199 | dictWindowManager.w = null 200 | dictWindowManager.tid = null 201 | updateWindowDfd = null 202 | injectContentDfd = null 203 | dictInitedDfd = null 204 | 205 | # chrome.tabs.onUpdated.addListener (tabId, info)-> 206 | # if dictWindowManager.tid == tabId 207 | # dictWindowManager.onReload(info.url) 208 | 209 | 210 | return dictWindowManager 211 | -------------------------------------------------------------------------------- /background/dictwindow.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | define(["jquery", "utils", "background/setting", "background/dict.js", "background/storage"], function($, utils, setting, dict, storage) { 3 | var defaultWindowUrl, dictInitedDfd, dictWindowManager, injectContentDfd, updateWindowDfd, windowType; 4 | console.log("[dictwindow] init"); 5 | defaultWindowUrl = chrome.extension.getURL('template/apidict.html'); 6 | updateWindowDfd = null; 7 | injectContentDfd = null; 8 | dictInitedDfd = null; 9 | windowType = 'popup'; 10 | dictWindowManager = { 11 | w: null, 12 | tid: null, 13 | url: null, 14 | word: null, 15 | open: function() { 16 | var dfd, height, left, top, url, width; 17 | dfd = $.Deferred(); 18 | width = parseInt(setting.getValue('windowWidth')); 19 | height = parseInt(setting.getValue('windowHeight')); 20 | left = Math.round((screen.width / 2) - (width / 2)); 21 | top = Math.round((screen.height / 2) - (height / 2)); 22 | url = 'http://blog.riverrun.xyz/fairydict.html'; 23 | if (!this.w) { 24 | this.beforeUpdateUrl(); 25 | chrome.windows.create({ 26 | url: defaultWindowUrl, 27 | type: windowType, 28 | width: width, 29 | height: height, 30 | left: left, 31 | top: top 32 | }, (function(_this) { 33 | return function(win) { 34 | _this.w = win; 35 | _this.tid = _this.w.tabs[0].id; 36 | _this.url = defaultWindowUrl; 37 | return dfd.resolve(); 38 | }; 39 | })(this)); 40 | } else { 41 | chrome.windows.update(dictWindowManager.w.id, { 42 | focused: true 43 | }); 44 | dfd.resolve(); 45 | } 46 | return dfd; 47 | }, 48 | sendMessage: function(msg) { 49 | if (this.tid) { 50 | return chrome.tabs.sendMessage(this.tid, msg); 51 | } 52 | }, 53 | lookup: function(text) { 54 | var dictName; 55 | dictName = setting.getValue('dictionary'); 56 | return this.open().done((function(_this) { 57 | return function() { 58 | if (text) { 59 | _this.sendMessage({ 60 | type: 'querying', 61 | text: text 62 | }); 63 | return _this.queryDict(text, dictName); 64 | } 65 | }; 66 | })(this)); 67 | }, 68 | queryDict: function(text, dictName, inHistory) { 69 | this.word = text; 70 | if (!inHistory) { 71 | this.sendMessage({ 72 | type: 'history', 73 | history: storage.history 74 | }); 75 | } 76 | console.log("[dictwindow] query " + this.word + " from " + dictName); 77 | return dict.query(text, dictName).then(this.sendQueryResult.bind(this)); 78 | }, 79 | saveWindowSize: function() { 80 | return chrome.windows.get(this.w.id, null, (function(_this) { 81 | return function(w) { 82 | if ((w != null ? w.width : void 0) && (w != null ? w.height : void 0)) { 83 | if (Math.abs(w.width - setting.getValue('windowWidth')) > 10 || Math.abs(w.height - setting.getValue('windowHeight')) > 10) { 84 | console.log("[dictwindow] update window size: " + w.width + " * " + w.height); 85 | setting.setValue('windowWidth', w.width); 86 | return setting.setValue('windowHeight', w.height); 87 | } 88 | } 89 | }; 90 | })(this)); 91 | }, 92 | sendQueryResult: function(result) { 93 | var item, udfd; 94 | this.saveWindowSize(); 95 | item = storage.isInHistory(this.word); 96 | if (result) { 97 | udfd = this.updateUrl(result.windowUrl || defaultWindowUrl); 98 | } 99 | return $.when(updateWindowDfd, dictInitedDfd, udfd).then((function(_this) { 100 | return function() { 101 | console.log("[dictwindow] send query result"); 102 | _this.sendMessage({ 103 | type: 'queryResult', 104 | result: result, 105 | text: _this.word, 106 | inHistory: item != null, 107 | rating: item != null ? item[_this.word] : void 0 108 | }); 109 | if (!item && _this.word.split(/\s+/).length <= 5) { 110 | return storage.addHistory(_this.word); 111 | } 112 | }; 113 | })(this)); 114 | }, 115 | injectResources: function() { 116 | var inject, res, scripts, styles; 117 | styles = ["css/bootstrap.css", "bower_components/angular-ui/build/angular-ui.css", "bower_components/angular-bootstrap/ui-bootstrap-csp.css", "css/font-awesome.css", "css/dictheader.css"]; 118 | scripts = ['bower_components/jquery/dist/jquery.js', 'bower_components/underscore/underscore.js', "bower_components/angular/angular.js", "bower_components/angular-ui/build/angular-ui.js", "bower_components/angular-sanitize/angular-sanitize.js", "bower_components/angular-bootstrap/ui-bootstrap.js", "utils.js", "js/starrr.js", "loader.js", "dict.js"]; 119 | inject = (function(_this) { 120 | return function(t, files, index) { 121 | var dfd, file; 122 | if (files == null) { 123 | files = []; 124 | } 125 | if (index == null) { 126 | index = 0; 127 | } 128 | dfd = $.Deferred(); 129 | file = files[index]; 130 | if (file && t && _this.tid) { 131 | console.log("[dictwindow] inject " + file); 132 | chrome.tabs[t](_this.tid, { 133 | file: file 134 | }, function() { 135 | return inject(t, files, index + 1).then(function() { 136 | return dfd.resolve(); 137 | }); 138 | }); 139 | } else { 140 | dfd.resolve(); 141 | } 142 | return dfd; 143 | }; 144 | })(this); 145 | res = dict.getDictResources(setting.getValue('dictionary')); 146 | if (this.url.indexOf('chrome-extension://') === 0) { 147 | return inject(null); 148 | } 149 | return inject('insertCSS', res != null ? res.styles : void 0).then((function(_this) { 150 | return function() { 151 | return inject('insertCSS', styles).then(function() { 152 | return inject('executeScript', scripts).then(function() { 153 | return inject('executeScript', res != null ? res.scripts : void 0); 154 | }); 155 | }); 156 | }; 157 | })(this)); 158 | }, 159 | updateUrl: function(url) { 160 | var outDfd; 161 | outDfd = $.Deferred(); 162 | if (url && this.url !== url) { 163 | console.log("[dictwindow] update url: " + url); 164 | this.url = url; 165 | this.beforeUpdateUrl().then((function(_this) { 166 | return function() { 167 | console.log("[dictwindow] updated url: " + url); 168 | return outDfd.resolve(true); 169 | }; 170 | })(this)); 171 | chrome.tabs.update(this.tid, { 172 | url: url, 173 | active: true 174 | }); 175 | return outDfd; 176 | } else { 177 | return outDfd.resolve(false); 178 | } 179 | }, 180 | beforeUpdateUrl: function() { 181 | injectContentDfd = $.Deferred((function(_this) { 182 | return function(dfd) { 183 | return dfd.then(function() { 184 | return _this.injectResources().then(function() { 185 | return updateWindowDfd != null ? updateWindowDfd.resolve() : void 0; 186 | }); 187 | }); 188 | }; 189 | })(this)); 190 | dictInitedDfd = $.Deferred(); 191 | updateWindowDfd = $.Deferred(); 192 | return updateWindowDfd; 193 | }, 194 | onContentInjected: function(url, tabId) { 195 | var d, w; 196 | console.log("[dictwindow] manifest's content scripts injected from url: " + url); 197 | if ((injectContentDfd != null ? injectContentDfd.state() : void 0) === 'pending') { 198 | return injectContentDfd.resolve(); 199 | } else if (url && tabId === this.tid) { 200 | d = setting.getValue('dictionary'); 201 | w = dict.getWordFromUrl(url, d); 202 | this.url = url; 203 | if (w) { 204 | this.word = w; 205 | } else { 206 | w = this.word; 207 | } 208 | dictInitedDfd = $.Deferred(); 209 | updateWindowDfd = $.Deferred(); 210 | console.log("[dictwindow] reload " + w + " url " + url); 211 | return this.injectResources().then((function(_this) { 212 | return function() { 213 | if (_this.url === defaultWindowUrl) { 214 | dict.query(_this.word, d).then(_this.sendQueryResult.bind(_this)); 215 | } else { 216 | _this.sendQueryResult(); 217 | } 218 | return updateWindowDfd.resolve(); 219 | }; 220 | })(this)); 221 | } 222 | }, 223 | onDictInited: function() { 224 | console.log("[dictwindow] dict inited"); 225 | if (dictInitedDfd != null) { 226 | dictInitedDfd.resolve(); 227 | } 228 | return this.sendMessage({ 229 | type: 'history', 230 | history: storage.history 231 | }); 232 | } 233 | }; 234 | chrome.windows.onRemoved.addListener(function(wid) { 235 | var ref; 236 | if (((ref = dictWindowManager.w) != null ? ref.id : void 0) === wid) { 237 | dictWindowManager.w = null; 238 | dictWindowManager.tid = null; 239 | updateWindowDfd = null; 240 | injectContentDfd = null; 241 | return dictInitedDfd = null; 242 | } 243 | }); 244 | return dictWindowManager; 245 | }); 246 | -------------------------------------------------------------------------------- /background/ext.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils", 3 | "background/setting"], ($, utils, setting)-> 4 | console.log "[ext] init" 5 | 6 | return { 7 | setBrowserIcon: (enable)-> 8 | title = 'FairyDict: 已打开鼠标取词功能' 9 | imgPath = 'images/books-96.png' 10 | if !enable 11 | title = 'FairyDict: 已关闭鼠标取词功能' 12 | imgPath = 'images/books-96.png' 13 | 14 | chrome.browserAction.setTitle({ 15 | title: title 16 | }) 17 | chrome.browserAction.setIcon({ 18 | path: imgPath 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /background/ext.js: -------------------------------------------------------------------------------- 1 | define(["jquery", "utils", "background/setting"], function($, utils, setting) { 2 | console.log("[ext] init"); 3 | return { 4 | setBrowserIcon: function(enable) { 5 | var imgPath, title; 6 | title = 'FairyDict: 已打开鼠标取词功能'; 7 | imgPath = 'images/books-96.png'; 8 | if (!enable) { 9 | title = 'FairyDict: 已关闭鼠标取词功能'; 10 | imgPath = 'images/books-96.png'; 11 | } 12 | chrome.browserAction.setTitle({ 13 | title: title 14 | }); 15 | return chrome.browserAction.setIcon({ 16 | path: imgPath 17 | }); 18 | } 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /background/main.coffee: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: "" 3 | paths: 4 | "jquery": "bower_components/jquery/dist/jquery", 5 | "utils": "utils" 6 | shim: 7 | utils: 8 | "exports": "utils" 9 | 10 | }) 11 | 12 | require ["jquery", 13 | "utils", 14 | "background/setting", 15 | "background/ext", 16 | "background/storage", 17 | "background/dictwindow.js", 18 | "background/message.js"], ($, utils, setting, ext, storage, dictWindow, message)-> 19 | 20 | onClickedContextMenu = (info, tab)-> 21 | if info.selectionText 22 | dictWindow.lookup(info.selectionText) 23 | 24 | chrome.browserAction.onClicked.addListener (tab)-> 25 | if setting.getValue('browserActionType') == 'openDictWindow' 26 | return dictWindow.lookup() 27 | 28 | b = !setting.getValue('enableMinidict') 29 | setting.setValue('enableMinidict', b) 30 | ext.setBrowserIcon(b) 31 | 32 | setting.init().done (c)-> 33 | ext.setBrowserIcon(c.enableMinidict) 34 | 35 | storage.init() 36 | 37 | chrome.contextMenus.create { 38 | title: "使用 FairyDict 查询 '%s'", 39 | contexts: ["selection"], 40 | onclick: onClickedContextMenu 41 | } 42 | 43 | -------------------------------------------------------------------------------- /background/main.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | require.config({ 3 | baseUrl: "", 4 | paths: { 5 | "jquery": "bower_components/jquery/dist/jquery", 6 | "utils": "utils" 7 | }, 8 | shim: { 9 | utils: { 10 | "exports": "utils" 11 | } 12 | } 13 | }); 14 | 15 | require(["jquery", "utils", "background/setting", "background/ext", "background/storage", "background/dictwindow.js", "background/message.js"], function($, utils, setting, ext, storage, dictWindow, message) { 16 | var onClickedContextMenu; 17 | onClickedContextMenu = function(info, tab) { 18 | if (info.selectionText) { 19 | return dictWindow.lookup(info.selectionText); 20 | } 21 | }; 22 | chrome.browserAction.onClicked.addListener(function(tab) { 23 | var b; 24 | if (setting.getValue('browserActionType') === 'openDictWindow') { 25 | return dictWindow.lookup(); 26 | } 27 | b = !setting.getValue('enableMinidict'); 28 | setting.setValue('enableMinidict', b); 29 | return ext.setBrowserIcon(b); 30 | }); 31 | setting.init().done(function(c) { 32 | return ext.setBrowserIcon(c.enableMinidict); 33 | }); 34 | storage.init(); 35 | return chrome.contextMenus.create({ 36 | title: "使用 FairyDict 查询 '%s'", 37 | contexts: ["selection"], 38 | onclick: onClickedContextMenu 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /background/message.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils", 3 | "background/setting", 4 | "background/ext", 5 | "background/storage", 6 | "background/dict.js", 7 | "background/plain-lookup.js", 8 | "background/dictwindow.js"], ($, utils, setting, ext, storage, dict, plainLookup, dictWindow)-> 9 | console.log "[message] init" 10 | 11 | chrome.runtime.onMessage.addListener (request, sender, sendResponse)-> 12 | if request.type == 'getJson' 13 | utils.getJson(request.url, request.data).then ((res)-> 14 | sendResponse(res)), sendResponse 15 | else if request.type == 'postJson' 16 | utils.postJson(request.url, request.data).then ((res)-> 17 | sendResponse(res)), sendResponse 18 | else if request.type == 'look up' 19 | if request.means == 'mouse' 20 | if not setting.getValue('enableMinidict') 21 | return true 22 | 23 | dictWindow.lookup(request.text) 24 | 25 | else if request.type == 'look up pain' 26 | res = dict.getDict('必应词典') 27 | url = res.windowUrl.replace('', request.text) 28 | plainLookup.parseBing(url).then sendResponse, sendResponse 29 | 30 | else if request.type == 'query' 31 | setting.setValue('dictionary', request.dictionary) if request.dictionary 32 | dictWindow.queryDict(request.text, request.dictionary, request.inHistory) 33 | 34 | else if request.type == 'dictionary' 35 | dictionary = setting.getValue('dictionary') 36 | history = storage.history 37 | sendResponse {allDicts: dict.allDicts, dictionary, history} 38 | dictWindow.onDictInited() 39 | 40 | else if request.type == 'setting' 41 | sendResponse setting.configCache 42 | 43 | else if request.type == 'save setting' 44 | setting.setValue(request.key, request.value) 45 | if request.key == 'enableMinidict' 46 | ext.setBrowserIcon request.value 47 | 48 | else if request.type == 'rating' 49 | storage.addRating request.text, request.value 50 | 51 | else if request.type == 'deleteHistory' 52 | storage.deleteHistory(request.text) 53 | 54 | else if request.type == 'injected' 55 | dictWindow.onContentInjected(request.url, sender.tab.id) 56 | 57 | # sendResponse becomes invalid when the event listener returns, 58 | # unless you return true from the event listener to indicate you wish to send a response asynchronously 59 | return true 60 | -------------------------------------------------------------------------------- /background/message.js: -------------------------------------------------------------------------------- 1 | define(["jquery", "utils", "background/setting", "background/ext", "background/storage", "background/dict.js", "background/plain-lookup.js", "background/dictwindow.js"], function($, utils, setting, ext, storage, dict, plainLookup, dictWindow) { 2 | console.log("[message] init"); 3 | return chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 4 | var dictionary, history, res, url; 5 | if (request.type === 'getJson') { 6 | utils.getJson(request.url, request.data).then((function(res) { 7 | return sendResponse(res); 8 | }), sendResponse); 9 | } else if (request.type === 'postJson') { 10 | utils.postJson(request.url, request.data).then((function(res) { 11 | return sendResponse(res); 12 | }), sendResponse); 13 | } else if (request.type === 'look up') { 14 | if (request.means === 'mouse') { 15 | if (!setting.getValue('enableMinidict')) { 16 | return true; 17 | } 18 | } 19 | dictWindow.lookup(request.text); 20 | } else if (request.type === 'look up pain') { 21 | res = dict.getDict('必应词典'); 22 | url = res.windowUrl.replace('', request.text); 23 | plainLookup.parseBing(url).then(sendResponse, sendResponse); 24 | } else if (request.type === 'query') { 25 | if (request.dictionary) { 26 | setting.setValue('dictionary', request.dictionary); 27 | } 28 | dictWindow.queryDict(request.text, request.dictionary, request.inHistory); 29 | } else if (request.type === 'dictionary') { 30 | dictionary = setting.getValue('dictionary'); 31 | history = storage.history; 32 | sendResponse({ 33 | allDicts: dict.allDicts, 34 | dictionary, 35 | history 36 | }); 37 | dictWindow.onDictInited(); 38 | } else if (request.type === 'setting') { 39 | sendResponse(setting.configCache); 40 | } else if (request.type === 'save setting') { 41 | setting.setValue(request.key, request.value); 42 | if (request.key === 'enableMinidict') { 43 | ext.setBrowserIcon(request.value); 44 | } 45 | } else if (request.type === 'rating') { 46 | storage.addRating(request.text, request.value); 47 | } else if (request.type === 'deleteHistory') { 48 | storage.deleteHistory(request.text); 49 | } else if (request.type === 'injected') { 50 | dictWindow.onContentInjected(request.url, sender.tab.id); 51 | } 52 | // sendResponse becomes invalid when the event listener returns, 53 | // unless you return true from the event listener to indicate you wish to send a response asynchronously 54 | return true; 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /background/plain-lookup.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "background/dictwindow.js"], ($, dictWindow)-> 3 | 4 | # most: 这个单词的 E-E 词典有 gl_none 这个 class; 5 | # 词组: 查询词组时没有音标,只有发音; 6 | # 网络词汇可能只有 web 释义,没有发音,如: https://cn.bing.com/dict/search?q=wantonly 7 | # 中文词: 查询结果上部是英文翻译,E-E 的地方却是 cn-cn, 如: https://cn.bing.com/dict/search?q=%E5%A4%A7%E5%8D%8A 8 | parseBing = (url) -> 9 | res = await $.get(url) 10 | nodes = $(res) 11 | 12 | prons = { 13 | ame: nodes.find('.hd_area .hd_prUS').text(), 14 | ameAudio: nodes.find('.hd_area .hd_prUS').next('.hd_tf').html()?.match(/https:.*?\.mp3/)[0] 15 | bre: nodes.find('.hd_area .hd_pr').text(), 16 | breAudio: nodes.find('.hd_area .hd_pr').next('.hd_tf').html()?.match(/https:.*?\.mp3/)[0] 17 | } 18 | 19 | enDefs = [] 20 | eeNodes = nodes.find('#homoid tr.def_row') 21 | eeNodes.each (i, el) -> 22 | enDefs.push({ 23 | pos: $(el).find('.pos').text(), 24 | def: $(el).find('.def_fl .de_li1') 25 | # https://cn.bing.com/dict/search?q=most 这个单词有 gl_none 的特例; 26 | .filter(() -> !$(this).parent().hasClass('gl_none')) 27 | .toArray().map (item) -> item.innerText 28 | }) 29 | 30 | cnDefs = [] 31 | defsNodes = nodes.find('.qdef ul') 32 | defsNodes.find('.pos').each (i, el) -> 33 | cnDefs.push({ 34 | pos: $(el).text(), 35 | def: $(el).next().text() 36 | }) 37 | 38 | # console.log prons, enDefs, cnDefs 39 | return {en: enDefs, cn: cnDefs, prons} 40 | 41 | # message.on 'look up plain', ({text})-> 42 | # res = await dictWindow.queryDict(text, '必应词典', false) 43 | # parseBing(res.windowUrl) 44 | 45 | return {parseBing} 46 | 47 | 48 | # parseBing('https://cn.bing.com/dict/search?q=most') -------------------------------------------------------------------------------- /background/plain-lookup.js: -------------------------------------------------------------------------------- 1 | define(["jquery", "background/dictwindow.js"], function($, dictWindow) { 2 | var parseBing; 3 | // most: 这个单词的 E-E 词典有 gl_none 这个 class; 4 | // 词组: 查询词组时没有音标,只有发音; 5 | // 网络词汇可能只有 web 释义,没有发音,如: https://cn.bing.com/dict/search?q=wantonly 6 | // 中文词: 查询结果上部是英文翻译,E-E 的地方却是 cn-cn, 如: https://cn.bing.com/dict/search?q=%E5%A4%A7%E5%8D%8A 7 | parseBing = async function(url) { 8 | var cnDefs, defsNodes, eeNodes, enDefs, nodes, prons, ref, ref1, res; 9 | res = (await $.get(url)); 10 | nodes = $(res); 11 | prons = { 12 | ame: nodes.find('.hd_area .hd_prUS').text(), 13 | ameAudio: (ref = nodes.find('.hd_area .hd_prUS').next('.hd_tf').html()) != null ? ref.match(/https:.*?\.mp3/)[0] : void 0, 14 | bre: nodes.find('.hd_area .hd_pr').text(), 15 | breAudio: (ref1 = nodes.find('.hd_area .hd_pr').next('.hd_tf').html()) != null ? ref1.match(/https:.*?\.mp3/)[0] : void 0 16 | }; 17 | enDefs = []; 18 | eeNodes = nodes.find('#homoid tr.def_row'); 19 | eeNodes.each(function(i, el) { 20 | return enDefs.push({ 21 | pos: $(el).find('.pos').text(), 22 | // https://cn.bing.com/dict/search?q=most 这个单词有 gl_none 的特例; 23 | def: $(el).find('.def_fl .de_li1').filter(function() { 24 | return !$(this).parent().hasClass('gl_none'); 25 | }).toArray().map(function(item) { 26 | return item.innerText; 27 | }) 28 | }); 29 | }); 30 | cnDefs = []; 31 | defsNodes = nodes.find('.qdef ul'); 32 | defsNodes.find('.pos').each(function(i, el) { 33 | return cnDefs.push({ 34 | pos: $(el).text(), 35 | def: $(el).next().text() 36 | }); 37 | }); 38 | return { 39 | // console.log prons, enDefs, cnDefs 40 | en: enDefs, 41 | cn: cnDefs, 42 | prons 43 | }; 44 | }; 45 | // message.on 'look up plain', ({text})-> 46 | // res = await dictWindow.queryDict(text, '必应词典', false) 47 | // parseBing(res.windowUrl) 48 | return {parseBing}; 49 | }); 50 | 51 | // parseBing('https://cn.bing.com/dict/search?q=most') 52 | 53 | 54 | //# sourceMappingURL=plain-lookup.js.map 55 | //# sourceURL=coffeescript -------------------------------------------------------------------------------- /background/setting.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils"], ($, utils)-> 3 | 4 | setting = 5 | configCache: { 6 | windowWidth: 630, 7 | windowHeight: 700, 8 | 9 | enableSelectionOnMouseMove: false, 10 | enableSelectionSK1: true, 11 | selectionSK1: 'Meta', 12 | selectionTimeout: 500, 13 | 14 | enablePlainLookup: true, 15 | enableAmeAudio: false, 16 | enableBreAudio: false, 17 | enablePlainSK1: false, 18 | plainSK1: 'Meta', 19 | 20 | enableMinidict: false, 21 | enableMouseSK1: false, 22 | mouseSK1: 'Ctrl', 23 | openSK1: 'Ctrl', 24 | openSK2: 'Shift' 25 | openKey: 'X', 26 | 27 | browserActionType: 'openDictWindow', 28 | 29 | prevDictSK1: 'Ctrl', 30 | prevDictKey: 'ArrowLeft', 31 | nextDictSK1: 'Ctrl', 32 | nextDictKey: 'ArrowRight', 33 | prevHistorySK1: 'Alt', 34 | prevHistoryKey: 'ArrowLeft', 35 | nextHistorySK1: 'Alt', 36 | nextHistoryKey: 'ArrowRight' 37 | dictionary: '' 38 | 39 | upgradeNotice: false 40 | } 41 | 42 | init: ()-> 43 | dfd = $.Deferred() 44 | chrome.storage.sync.get @configCache, (obj)=> 45 | @configCache = obj 46 | chrome.storage.sync.set(obj) 47 | dfd.resolve(obj) 48 | 49 | if not obj.upgradeNotice 50 | chrome.runtime.openOptionsPage() 51 | 52 | return dfd 53 | 54 | setValue: (key, value)-> 55 | if @configCache[key] != value 56 | @configCache[key] = value 57 | chrome.storage.sync.set(@configCache); 58 | return value 59 | 60 | getValue: (key, defaultValue)-> 61 | v = @configCache[key] 62 | v ?= defaultValue 63 | return v 64 | 65 | return setting 66 | -------------------------------------------------------------------------------- /background/setting.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.4.1 2 | define(["jquery", "utils"], function($, utils) { 3 | var setting; 4 | setting = { 5 | configCache: { 6 | windowWidth: 630, 7 | windowHeight: 700, 8 | enableSelectionOnMouseMove: false, 9 | enableSelectionSK1: true, 10 | selectionSK1: 'Meta', 11 | selectionTimeout: 500, 12 | enablePlainLookup: true, 13 | enableAmeAudio: false, 14 | enableBreAudio: false, 15 | enablePlainSK1: false, 16 | plainSK1: 'Meta', 17 | enableMinidict: false, 18 | enableMouseSK1: false, 19 | mouseSK1: 'Ctrl', 20 | openSK1: 'Ctrl', 21 | openSK2: 'Shift', 22 | openKey: 'X', 23 | browserActionType: 'openDictWindow', 24 | prevDictSK1: 'Ctrl', 25 | prevDictKey: 'ArrowLeft', 26 | nextDictSK1: 'Ctrl', 27 | nextDictKey: 'ArrowRight', 28 | prevHistorySK1: 'Alt', 29 | prevHistoryKey: 'ArrowLeft', 30 | nextHistorySK1: 'Alt', 31 | nextHistoryKey: 'ArrowRight', 32 | dictionary: '', 33 | upgradeNotice: false 34 | }, 35 | init: function() { 36 | var dfd; 37 | dfd = $.Deferred(); 38 | chrome.storage.sync.get(this.configCache, (obj) => { 39 | this.configCache = obj; 40 | chrome.storage.sync.set(obj); 41 | dfd.resolve(obj); 42 | if (!obj.upgradeNotice) { 43 | return chrome.runtime.openOptionsPage(); 44 | } 45 | }); 46 | return dfd; 47 | }, 48 | setValue: function(key, value) { 49 | if (this.configCache[key] !== value) { 50 | this.configCache[key] = value; 51 | chrome.storage.sync.set(this.configCache); 52 | } 53 | return value; 54 | }, 55 | getValue: function(key, defaultValue) { 56 | var v; 57 | v = this.configCache[key]; 58 | if (v == null) { 59 | v = defaultValue; 60 | } 61 | return v; 62 | } 63 | }; 64 | return setting; 65 | }); 66 | -------------------------------------------------------------------------------- /background/storage.coffee: -------------------------------------------------------------------------------- 1 | define ["jquery", 2 | "utils"], ($, utils)-> 3 | 4 | storage = { 5 | maxLength: 200, 6 | history: [], 7 | init: ()-> 8 | dfd = $.Deferred() 9 | chrome.storage.sync.get 'historyRating', (data)=> 10 | @history = data.historyRating or [] 11 | dfd.resolve(data) 12 | return dfd 13 | 14 | isInHistory: (word)-> 15 | return @history.find (item)-> 16 | return item[word]? 17 | 18 | addRating: (word, rating)-> 19 | item = @isInHistory(word) 20 | if item and rating? 21 | item[word] = rating 22 | chrome.storage.sync.set({historyRating: @history}) 23 | 24 | addHistory: (word)-> 25 | if not @isInHistory(word) 26 | if @history.length >= @maxLength 27 | @history.shift() 28 | item = {} 29 | item[word] = 0 30 | @history.push(item) 31 | chrome.storage.sync.set({historyRating: @history}) 32 | 33 | deleteHistory: (word)-> 34 | idx = @history.findIndex (item)-> 35 | return item[word]? 36 | if idx >= 0 37 | @history.splice(idx, 1) 38 | 39 | } 40 | 41 | return storage 42 | -------------------------------------------------------------------------------- /background/storage.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | define(["jquery", "utils"], function($, utils) { 3 | var storage; 4 | storage = { 5 | maxLength: 200, 6 | history: [], 7 | init: function() { 8 | var dfd; 9 | dfd = $.Deferred(); 10 | chrome.storage.sync.get('historyRating', (function(_this) { 11 | return function(data) { 12 | _this.history = data.historyRating || []; 13 | return dfd.resolve(data); 14 | }; 15 | })(this)); 16 | return dfd; 17 | }, 18 | isInHistory: function(word) { 19 | return this.history.find(function(item) { 20 | return item[word] != null; 21 | }); 22 | }, 23 | addRating: function(word, rating) { 24 | var item; 25 | item = this.isInHistory(word); 26 | if (item && (rating != null)) { 27 | item[word] = rating; 28 | return chrome.storage.sync.set({ 29 | historyRating: this.history 30 | }); 31 | } 32 | }, 33 | addHistory: function(word) { 34 | var item; 35 | if (!this.isInHistory(word)) { 36 | if (this.history.length >= this.maxLength) { 37 | this.history.shift(); 38 | } 39 | item = {}; 40 | item[word] = 0; 41 | this.history.push(item); 42 | return chrome.storage.sync.set({ 43 | historyRating: this.history 44 | }); 45 | } 46 | }, 47 | deleteHistory: function(word) { 48 | var idx; 49 | idx = this.history.findIndex(function(item) { 50 | return item[word] != null; 51 | }); 52 | if (idx >= 0) { 53 | return this.history.splice(idx, 1); 54 | } 55 | } 56 | }; 57 | return storage; 58 | }); 59 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FairyDict", 3 | "homepage": "https://github.com/revir/FairyDict", 4 | "authors": [ 5 | "Revir " 6 | ], 7 | "description": "A fairy dictionary extension for chrome.", 8 | "main": "", 9 | "moduleType": [], 10 | "keywords": [ 11 | "dictionary", 12 | "chrome", 13 | "extension" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "bootstrap": "=3.0.3", 25 | "angular": "~1.4.8", 26 | "angular-ui": "~0.4.0", 27 | "angular-bootstrap": "~0.14.3", 28 | "angular-sanitize": "~1.5.7", 29 | "angular-route": "1.4.12", 30 | "jquery": "^3.1.1", 31 | "requirejs": "^2.3.2", 32 | "font-awesome": "^4.6.3", 33 | "underscore": "^1.8.3", 34 | "less": "^2.7.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /build/fairy-dict.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/build/fairy-dict.crx -------------------------------------------------------------------------------- /content/inject-fontello.css: -------------------------------------------------------------------------------- 1 | /* @font-face { 2 | font-family: "fairydict-font"; 3 | src: url("data:application/octet-stream;base64,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") 4 | format("woff"), 5 | url("data:application/octet-stream;base64,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") 6 | format("truetype"); 7 | } */ 8 | @font-face { 9 | font-family: "fairydict-font"; 10 | src: url("chrome-extension://__MSG_@@extension_id__/fonts/fairydict-font.woff?26686527") 11 | format("woff"); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 17 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 18 | /* 19 | @media screen and (-webkit-min-device-pixel-ratio:0) { 20 | @font-face { 21 | font-family: 'fairydict-font'; 22 | src: url('../font/fairydict-font.svg?83899544#fairydict-font') format('svg'); 23 | } 24 | } 25 | */ 26 | 27 | [class^="icon-fairydict"]:before, 28 | [class*=" icon-fairydict"]:before { 29 | font-family: "fairydict-font"; 30 | font-style: normal; 31 | font-weight: normal; 32 | speak: none; 33 | 34 | display: inline-block; 35 | text-decoration: inherit; 36 | width: 1em; 37 | margin-right: 0.2em; 38 | text-align: center; 39 | /* opacity: .8; */ 40 | 41 | /* For safety - reset parent styles, that can break glyph codes*/ 42 | font-variant: normal; 43 | text-transform: none; 44 | 45 | /* fix buttons height, for twitter bootstrap */ 46 | line-height: 1em; 47 | 48 | /* Animation center compensation - margins should be symmetric */ 49 | /* remove if not needed */ 50 | margin-left: 0.2em; 51 | 52 | /* you can be more comfortable with increased icons size */ 53 | /* font-size: 120%; */ 54 | 55 | /* Uncomment for 3D effect */ 56 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 57 | } 58 | .icon-fairydict-volume:before { 59 | content: "\e800"; 60 | } /* '' */ 61 | -------------------------------------------------------------------------------- /content/inject.coffee: -------------------------------------------------------------------------------- 1 | chrome.runtime.sendMessage { 2 | type: 'setting', 3 | }, (setting)-> 4 | mouseMoveTimer = null 5 | plainQuerying = null 6 | 7 | jQuery(document).ready ()-> 8 | jQuery(''' 9 |
10 |
11 |
12 |
13 |
14 |
15 |

16 |

17 |
18 | ''').appendTo('body') 19 | 20 | setupPlainContentPosition = (e) -> 21 | $el = jQuery('.fairydict-tooltip') 22 | if $el.length && e.pageX && e.pageY 23 | mousex = e.pageX + 20 24 | mousey = e.pageY + 10 25 | top = mousey 26 | left = mousex 27 | 28 | rect = window.document.body.getBoundingClientRect() 29 | domW = window.innerWidth - rect.left 30 | domH = window.innerHeight - rect.top 31 | 32 | if domW - left < 300 33 | left = domW - 300 34 | if domH - top < 200 35 | top = domH - 200 36 | 37 | $el.css({ top, left }) 38 | 39 | jQuery(document).mousemove (e)-> 40 | if setting.enableSelectionOnMouseMove 41 | if !setting.enableSelectionSK1 or (setting.enableSelectionSK1 and utils.checkEventKey(e, setting.selectionSK1)) 42 | handleSelectionWord(e) 43 | 44 | jQuery(document).mouseup (e)-> 45 | # 对 mouseup 事件做一个延时处理, 46 | # 以避免取消选中后getSelection依然能获得文字。 47 | setTimeout (()->handleMouseUp(e)), 1 48 | 49 | jQuery(document).bind 'keyup', (event)-> 50 | if utils.checkEventKey event, setting.openSK1, setting.openSK2, setting.openKey 51 | chrome.runtime.sendMessage({ 52 | type: 'look up', 53 | means: 'keyboard', 54 | text: window.getSelection().toString().trim() 55 | }) 56 | if event.key == "Escape" 57 | jQuery('.fairydict-tooltip').fadeOut().hide() 58 | plainQuerying = null 59 | 60 | jQuery(document).on 'click', '.fairydict-pron-audio', (e) -> 61 | e.stopPropagation() 62 | playAudios [jQuery(this).data('mp3')] 63 | return false 64 | 65 | handleSelectionWord = (e)-> 66 | clearTimeout(mouseMoveTimer) if mouseMoveTimer 67 | mouseMoveTimer = setTimeout (()-> 68 | word = getWordAtPoint(e.target, e.clientX, e.clientY) 69 | if word 70 | console.log(word) 71 | handleLookupByMouse(e) 72 | ), (setting.selectionTimeout or 500) 73 | 74 | playAudios = (urls) -> 75 | return unless urls?.length 76 | audios = urls.map (url)-> 77 | return new Audio(url) 78 | 79 | _play = (audio, timeout)-> 80 | timeout ?= 0 81 | return jQuery.Deferred (dfd)-> 82 | _func = ()-> 83 | setTimeout (()-> 84 | # console.log "play: ", audio.duration, timeout 85 | audio.play() 86 | dfd.resolve(audio.duration or 1) 87 | ), timeout 88 | 89 | if audio.duration 90 | _func() 91 | else 92 | audio.addEventListener 'loadedmetadata', _func 93 | 94 | __play = (idx, timeout)-> 95 | idx ?= 0 96 | if audios[idx] 97 | _play(audios[idx], timeout).then (duration)-> 98 | __play(idx+1, duration*1000) 99 | 100 | __play() 101 | 102 | getWordAtPoint = (elem, x, y)-> 103 | if elem.nodeType == elem.TEXT_NODE 104 | range = elem.ownerDocument.createRange() 105 | range.selectNodeContents(elem) 106 | currentPos = 0 107 | endPos = range.endOffset 108 | while currentPos+1 < endPos 109 | range.setStart(elem, currentPos) 110 | range.setEnd(elem, currentPos+1) 111 | if range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right >= x && 112 | range.getBoundingClientRect().top <= y && range.getBoundingClientRect().bottom >= y 113 | range.detach() 114 | sel = window.getSelection() 115 | sel.removeAllRanges() 116 | sel.addRange(range) 117 | sel.modify("move", "backward", "word") 118 | sel.collapseToStart() 119 | sel.modify("extend", "forward", "word") 120 | return sel.toString().trim() 121 | 122 | currentPos += 1 123 | else 124 | for el in elem.childNodes 125 | range = el.ownerDocument.createRange() 126 | range.selectNodeContents(el) 127 | react = range.getBoundingClientRect() 128 | if react.left <= x && react.right >= x && react.top <= y && react.bottom >= y 129 | range.detach() 130 | return getWordAtPoint el, x, y 131 | else 132 | range.detach() 133 | return 134 | 135 | handleMouseUp = (event)-> 136 | selObj = window.getSelection() 137 | text = selObj.toString().trim() 138 | unless text 139 | # click inside the dict 140 | if jQuery('.fairydict-tooltip').has(event.target).length 141 | return 142 | 143 | jQuery('.fairydict-tooltip').fadeOut().hide() 144 | plainQuerying = null 145 | return 146 | 147 | # issue #4 148 | including = jQuery(event.target).has(selObj.focusNode).length or jQuery(event.target).is(selObj.focusNode) 149 | 150 | if event.which == 1 and including 151 | handleLookupByMouse(event) 152 | 153 | renderQueryResult = (res) -> 154 | defTpl = (def) -> " #{def} " 155 | defsTpl = (defs) -> " #{defs} " 156 | posTpl = (pos) -> " #{pos} " 157 | contentTpl = (content) -> "
#{content}
" 158 | pronTpl = (pron) -> " #{pron} " 159 | pronAudioTpl = (src) -> "" 160 | pronsTpl = (prons) -> "
#{prons}
" 161 | 162 | html = '' 163 | if res?.prons 164 | pronHtml = '' 165 | pronHtml += pronTpl res.prons.ame if res.prons.ame 166 | pronHtml += pronAudioTpl res.prons.ameAudio if res.prons.ameAudio 167 | pronHtml += pronTpl res.prons.bre if res.prons.bre 168 | pronHtml += pronAudioTpl res.prons.breAudio if res.prons.breAudio 169 | html += pronsTpl pronHtml if pronHtml 170 | 171 | renderItem = (item) -> 172 | posHtml = posTpl item.pos 173 | 174 | defs = if Array.isArray(item.def) then item.def else [item.def] 175 | defsHtmls = defs.map (def) -> defTpl def 176 | 177 | defsHtml = defsTpl defsHtmls.join('
') 178 | 179 | html += contentTpl posHtml+defsHtml if defsHtml 180 | 181 | res.cn.forEach renderItem if res?.cn 182 | res.en.forEach renderItem if res?.en 183 | 184 | if html 185 | jQuery('.fairydict-tooltip .fairydict-spinner').hide() 186 | jQuery('.fairydict-tooltip .fairydict-tooltip-content').html(html) 187 | else 188 | jQuery('.fairydict-tooltip').fadeOut().hide() 189 | 190 | return html 191 | 192 | handleLookupByMouse = (event)-> 193 | text = window.getSelection().toString().trim() 194 | return unless text 195 | return if text.split(/\s/).length > 4 196 | 197 | return if $('.dictionaries-tooltip').length # ignore when find Dictionaries 198 | 199 | if setting.enablePlainLookup && text != plainQuerying 200 | if !setting.enablePlainSK1 or (setting.plainSK1 and utils.checkEventKey(event, setting.plainSK1)) 201 | jQuery('.fairydict-tooltip').fadeIn('slow') 202 | jQuery('.fairydict-tooltip .fairydict-spinner').show() 203 | jQuery('.fairydict-tooltip .fairydict-tooltip-content').empty() 204 | 205 | unless plainQuerying 206 | setupPlainContentPosition(event) 207 | 208 | plainQuerying = text 209 | 210 | chrome.runtime.sendMessage { 211 | type: 'look up pain', 212 | means: 'mouse', 213 | text: text 214 | }, (res)-> 215 | html = renderQueryResult res 216 | if !html 217 | plainQuerying = null 218 | 219 | if res.prons 220 | audios = [] 221 | 222 | if res.prons.ameAudio and setting.enableAmeAudio 223 | audios.push res.prons.ameAudio 224 | 225 | if res.prons.breAudio and setting.enableBreAudio 226 | audios.push res.prons.breAudio 227 | 228 | if audios.length 229 | playAudios audios 230 | 231 | if !setting.enableMouseSK1 or (setting.mouseSK1 and utils.checkEventKey(event, setting.mouseSK1)) 232 | chrome.runtime.sendMessage({ 233 | type: 'look up', 234 | means: 'mouse', 235 | text: text 236 | }) 237 | 238 | 239 | chrome.runtime.sendMessage { 240 | type: 'injected', 241 | url: location.href 242 | } 243 | -------------------------------------------------------------------------------- /content/inject.css: -------------------------------------------------------------------------------- 1 | .fairydict-tooltip { 2 | display: none; 3 | position: absolute; 4 | border: 1px solid #333; 5 | background-color: #161616; 6 | border-radius: 5px; 7 | padding: 10px; 8 | 9 | z-index: 99999; 10 | } 11 | 12 | .fairydict-tooltip p.fairydict-tooltip-content { 13 | color: #fff; 14 | font-size: 13px; 15 | line-height: normal; 16 | } 17 | 18 | .fairydict-tooltip .icon-fairydict-volume { 19 | color: goldenrod; 20 | } 21 | 22 | .fairydict-tooltip .fairydict-content { 23 | margin-top: 3px; 24 | } 25 | 26 | .fairydict-tooltip .fairydict-pos { 27 | display: table-cell; 28 | vertical-align: top; 29 | width: 32px; 30 | padding-top: 1px; 31 | } 32 | 33 | .fairydict-tooltip .fairydict-defs { 34 | display: table-cell; 35 | padding-top: 1px; 36 | } 37 | 38 | .fairydict-spinner { 39 | margin: 10px auto 0; 40 | width: 70px; 41 | text-align: center; 42 | } 43 | 44 | .fairydict-spinner > div { 45 | width: 18px; 46 | height: 18px; 47 | background-color: #333; 48 | 49 | border-radius: 100%; 50 | display: inline-block; 51 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; 52 | animation: sk-bouncedelay 1.4s infinite ease-in-out both; 53 | } 54 | 55 | .fairydict-spinner .fairydict-bounce1 { 56 | -webkit-animation-delay: -0.32s; 57 | animation-delay: -0.32s; 58 | } 59 | 60 | .fairydict-spinner .fairydict-bounce2 { 61 | -webkit-animation-delay: -0.16s; 62 | animation-delay: -0.16s; 63 | } 64 | 65 | @-webkit-keyframes sk-bouncedelay { 66 | 0%, 67 | 80%, 68 | 100% { 69 | -webkit-transform: scale(0); 70 | } 71 | 40% { 72 | -webkit-transform: scale(1); 73 | } 74 | } 75 | 76 | @keyframes sk-bouncedelay { 77 | 0%, 78 | 80%, 79 | 100% { 80 | -webkit-transform: scale(0); 81 | transform: scale(0); 82 | } 83 | 40% { 84 | -webkit-transform: scale(1); 85 | transform: scale(1); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /content/inject.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.4.1 2 | chrome.runtime.sendMessage({ 3 | type: 'setting' 4 | }, function(setting) { 5 | var getWordAtPoint, handleLookupByMouse, handleMouseUp, handleSelectionWord, mouseMoveTimer, plainQuerying, playAudios, renderQueryResult, setupPlainContentPosition; 6 | mouseMoveTimer = null; 7 | plainQuerying = null; 8 | jQuery(document).ready(function() { 9 | return jQuery('
\n
\n
\n
\n
\n
\n

\n

\n
').appendTo('body'); 10 | }); 11 | setupPlainContentPosition = function(e) { 12 | var $el, domH, domW, left, mousex, mousey, rect, top; 13 | $el = jQuery('.fairydict-tooltip'); 14 | if ($el.length && e.pageX && e.pageY) { 15 | mousex = e.pageX + 20; 16 | mousey = e.pageY + 10; 17 | top = mousey; 18 | left = mousex; 19 | rect = window.document.body.getBoundingClientRect(); 20 | domW = window.innerWidth - rect.left; 21 | domH = window.innerHeight - rect.top; 22 | if (domW - left < 300) { 23 | left = domW - 300; 24 | } 25 | if (domH - top < 200) { 26 | top = domH - 200; 27 | } 28 | return $el.css({top, left}); 29 | } 30 | }; 31 | jQuery(document).mousemove(function(e) { 32 | if (setting.enableSelectionOnMouseMove) { 33 | if (!setting.enableSelectionSK1 || (setting.enableSelectionSK1 && utils.checkEventKey(e, setting.selectionSK1))) { 34 | return handleSelectionWord(e); 35 | } 36 | } 37 | }); 38 | jQuery(document).mouseup(function(e) { 39 | // 对 mouseup 事件做一个延时处理, 40 | // 以避免取消选中后getSelection依然能获得文字。 41 | return setTimeout((function() { 42 | return handleMouseUp(e); 43 | }), 1); 44 | }); 45 | jQuery(document).bind('keyup', function(event) { 46 | if (utils.checkEventKey(event, setting.openSK1, setting.openSK2, setting.openKey)) { 47 | chrome.runtime.sendMessage({ 48 | type: 'look up', 49 | means: 'keyboard', 50 | text: window.getSelection().toString().trim() 51 | }); 52 | } 53 | if (event.key === "Escape") { 54 | jQuery('.fairydict-tooltip').fadeOut().hide(); 55 | return plainQuerying = null; 56 | } 57 | }); 58 | jQuery(document).on('click', '.fairydict-pron-audio', function(e) { 59 | e.stopPropagation(); 60 | playAudios([jQuery(this).data('mp3')]); 61 | return false; 62 | }); 63 | handleSelectionWord = function(e) { 64 | if (mouseMoveTimer) { 65 | clearTimeout(mouseMoveTimer); 66 | } 67 | return mouseMoveTimer = setTimeout((function() { 68 | var word; 69 | word = getWordAtPoint(e.target, e.clientX, e.clientY); 70 | if (word) { 71 | console.log(word); 72 | return handleLookupByMouse(e); 73 | } 74 | }), setting.selectionTimeout || 500); 75 | }; 76 | playAudios = function(urls) { 77 | var __play, _play, audios; 78 | if (!(urls != null ? urls.length : void 0)) { 79 | return; 80 | } 81 | audios = urls.map(function(url) { 82 | return new Audio(url); 83 | }); 84 | _play = function(audio, timeout) { 85 | if (timeout == null) { 86 | timeout = 0; 87 | } 88 | return jQuery.Deferred(function(dfd) { 89 | var _func; 90 | _func = function() { 91 | return setTimeout((function() { 92 | // console.log "play: ", audio.duration, timeout 93 | audio.play(); 94 | return dfd.resolve(audio.duration || 1); 95 | }), timeout); 96 | }; 97 | if (audio.duration) { 98 | return _func(); 99 | } else { 100 | return audio.addEventListener('loadedmetadata', _func); 101 | } 102 | }); 103 | }; 104 | __play = function(idx, timeout) { 105 | if (idx == null) { 106 | idx = 0; 107 | } 108 | if (audios[idx]) { 109 | return _play(audios[idx], timeout).then(function(duration) { 110 | return __play(idx + 1, duration * 1000); 111 | }); 112 | } 113 | }; 114 | return __play(); 115 | }; 116 | getWordAtPoint = function(elem, x, y) { 117 | var currentPos, el, endPos, i, len, range, react, ref, sel; 118 | if (elem.nodeType === elem.TEXT_NODE) { 119 | range = elem.ownerDocument.createRange(); 120 | range.selectNodeContents(elem); 121 | currentPos = 0; 122 | endPos = range.endOffset; 123 | while (currentPos + 1 < endPos) { 124 | range.setStart(elem, currentPos); 125 | range.setEnd(elem, currentPos + 1); 126 | if (range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right >= x && range.getBoundingClientRect().top <= y && range.getBoundingClientRect().bottom >= y) { 127 | range.detach(); 128 | sel = window.getSelection(); 129 | sel.removeAllRanges(); 130 | sel.addRange(range); 131 | sel.modify("move", "backward", "word"); 132 | sel.collapseToStart(); 133 | sel.modify("extend", "forward", "word"); 134 | return sel.toString().trim(); 135 | } 136 | currentPos += 1; 137 | } 138 | } else { 139 | ref = elem.childNodes; 140 | for (i = 0, len = ref.length; i < len; i++) { 141 | el = ref[i]; 142 | range = el.ownerDocument.createRange(); 143 | range.selectNodeContents(el); 144 | react = range.getBoundingClientRect(); 145 | if (react.left <= x && react.right >= x && react.top <= y && react.bottom >= y) { 146 | range.detach(); 147 | return getWordAtPoint(el, x, y); 148 | } else { 149 | range.detach(); 150 | } 151 | } 152 | } 153 | }; 154 | handleMouseUp = function(event) { 155 | var including, selObj, text; 156 | selObj = window.getSelection(); 157 | text = selObj.toString().trim(); 158 | if (!text) { 159 | // click inside the dict 160 | if (jQuery('.fairydict-tooltip').has(event.target).length) { 161 | return; 162 | } 163 | jQuery('.fairydict-tooltip').fadeOut().hide(); 164 | plainQuerying = null; 165 | return; 166 | } 167 | // issue #4 168 | including = jQuery(event.target).has(selObj.focusNode).length || jQuery(event.target).is(selObj.focusNode); 169 | if (event.which === 1 && including) { 170 | return handleLookupByMouse(event); 171 | } 172 | }; 173 | renderQueryResult = function(res) { 174 | var contentTpl, defTpl, defsTpl, html, posTpl, pronAudioTpl, pronHtml, pronTpl, pronsTpl, renderItem; 175 | defTpl = function(def) { 176 | return ` ${def} `; 177 | }; 178 | defsTpl = function(defs) { 179 | return ` ${defs} `; 180 | }; 181 | posTpl = function(pos) { 182 | return ` ${pos} `; 183 | }; 184 | contentTpl = function(content) { 185 | return `
${content}
`; 186 | }; 187 | pronTpl = function(pron) { 188 | return ` ${pron} `; 189 | }; 190 | pronAudioTpl = function(src) { 191 | return ``; 192 | }; 193 | pronsTpl = function(prons) { 194 | return `
${prons}
`; 195 | }; 196 | html = ''; 197 | if (res != null ? res.prons : void 0) { 198 | pronHtml = ''; 199 | if (res.prons.ame) { 200 | pronHtml += pronTpl(res.prons.ame); 201 | } 202 | if (res.prons.ameAudio) { 203 | pronHtml += pronAudioTpl(res.prons.ameAudio); 204 | } 205 | if (res.prons.bre) { 206 | pronHtml += pronTpl(res.prons.bre); 207 | } 208 | if (res.prons.breAudio) { 209 | pronHtml += pronAudioTpl(res.prons.breAudio); 210 | } 211 | if (pronHtml) { 212 | html += pronsTpl(pronHtml); 213 | } 214 | } 215 | renderItem = function(item) { 216 | var defs, defsHtml, defsHtmls, posHtml; 217 | posHtml = posTpl(item.pos); 218 | defs = Array.isArray(item.def) ? item.def : [item.def]; 219 | defsHtmls = defs.map(function(def) { 220 | return defTpl(def); 221 | }); 222 | defsHtml = defsTpl(defsHtmls.join('
')); 223 | if (defsHtml) { 224 | return html += contentTpl(posHtml + defsHtml); 225 | } 226 | }; 227 | if (res != null ? res.cn : void 0) { 228 | res.cn.forEach(renderItem); 229 | } 230 | if (res != null ? res.en : void 0) { 231 | res.en.forEach(renderItem); 232 | } 233 | if (html) { 234 | jQuery('.fairydict-tooltip .fairydict-spinner').hide(); 235 | jQuery('.fairydict-tooltip .fairydict-tooltip-content').html(html); 236 | } else { 237 | jQuery('.fairydict-tooltip').fadeOut().hide(); 238 | } 239 | return html; 240 | }; 241 | return handleLookupByMouse = function(event) { 242 | var text; 243 | text = window.getSelection().toString().trim(); 244 | if (!text) { 245 | return; 246 | } 247 | if (text.split(/\s/).length > 4) { 248 | return; 249 | } 250 | if ($('.dictionaries-tooltip').length) { // ignore when find Dictionaries 251 | return; 252 | } 253 | if (setting.enablePlainLookup && text !== plainQuerying) { 254 | if (!setting.enablePlainSK1 || (setting.plainSK1 && utils.checkEventKey(event, setting.plainSK1))) { 255 | jQuery('.fairydict-tooltip').fadeIn('slow'); 256 | jQuery('.fairydict-tooltip .fairydict-spinner').show(); 257 | jQuery('.fairydict-tooltip .fairydict-tooltip-content').empty(); 258 | if (!plainQuerying) { 259 | setupPlainContentPosition(event); 260 | } 261 | plainQuerying = text; 262 | chrome.runtime.sendMessage({ 263 | type: 'look up pain', 264 | means: 'mouse', 265 | text: text 266 | }, function(res) { 267 | var audios, html; 268 | html = renderQueryResult(res); 269 | if (!html) { 270 | plainQuerying = null; 271 | } 272 | if (res.prons) { 273 | audios = []; 274 | if (res.prons.ameAudio && setting.enableAmeAudio) { 275 | audios.push(res.prons.ameAudio); 276 | } 277 | if (res.prons.breAudio && setting.enableBreAudio) { 278 | audios.push(res.prons.breAudio); 279 | } 280 | if (audios.length) { 281 | return playAudios(audios); 282 | } 283 | } 284 | }); 285 | } 286 | } 287 | if (!setting.enableMouseSK1 || (setting.mouseSK1 && utils.checkEventKey(event, setting.mouseSK1))) { 288 | return chrome.runtime.sendMessage({ 289 | type: 'look up', 290 | means: 'mouse', 291 | text: text 292 | }); 293 | } 294 | }; 295 | }); 296 | 297 | chrome.runtime.sendMessage({ 298 | type: 'injected', 299 | url: location.href 300 | }); 301 | -------------------------------------------------------------------------------- /css/91dict.css: -------------------------------------------------------------------------------- 1 | .widescreenNav { 2 | margin-top: 55px !important; 3 | } 4 | .header { 5 | display: none; 6 | } 7 | -------------------------------------------------------------------------------- /css/apidict.css: -------------------------------------------------------------------------------- 1 | body { 2 | /*margin: 8px;*/ 3 | padding-top: 70px; 4 | padding-left: 15px; 5 | } 6 | -------------------------------------------------------------------------------- /css/baidu-dict.css: -------------------------------------------------------------------------------- 1 | #search-bar, #update_tips_div { 2 | display: none; 3 | } 4 | 5 | #header-detail label { 6 | // fix a bug of baidu; 7 | width: auto !important; 8 | } 9 | -------------------------------------------------------------------------------- /css/bing.css: -------------------------------------------------------------------------------- 1 | body { 2 | /*margin: 8px;*/ 3 | padding-left: 15px !important; 4 | padding-top: 70px !important; 5 | } 6 | body header { 7 | display: none !important; 8 | } 9 | body .contentPadding { 10 | padding-left: 0 !important; 11 | } 12 | -------------------------------------------------------------------------------- /css/bing.less: -------------------------------------------------------------------------------- 1 | body { 2 | /*margin: 8px;*/ 3 | padding-left: 15px !important; 4 | padding-top: 70px !important; 5 | 6 | header { 7 | display: none !important; 8 | } 9 | 10 | .contentPadding { 11 | padding-left: 0 !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 16 | } 17 | 18 | .btn-default:active, 19 | .btn-primary:active, 20 | .btn-success:active, 21 | .btn-info:active, 22 | .btn-warning:active, 23 | .btn-danger:active, 24 | .btn-default.active, 25 | .btn-primary.active, 26 | .btn-success.active, 27 | .btn-info.active, 28 | .btn-warning.active, 29 | .btn-danger.active { 30 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 31 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 32 | } 33 | 34 | .btn:active, 35 | .btn.active { 36 | background-image: none; 37 | } 38 | 39 | .btn-default { 40 | text-shadow: 0 1px 0 #fff; 41 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 42 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 43 | background-repeat: repeat-x; 44 | border-color: #dbdbdb; 45 | border-color: #ccc; 46 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 47 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 48 | } 49 | 50 | .btn-default:hover, 51 | .btn-default:focus { 52 | background-color: #e0e0e0; 53 | background-position: 0 -15px; 54 | } 55 | 56 | .btn-default:active, 57 | .btn-default.active { 58 | background-color: #e0e0e0; 59 | border-color: #dbdbdb; 60 | } 61 | 62 | .btn-primary { 63 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 64 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 65 | background-repeat: repeat-x; 66 | border-color: #2b669a; 67 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 68 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 69 | } 70 | 71 | .btn-primary:hover, 72 | .btn-primary:focus { 73 | background-color: #2d6ca2; 74 | background-position: 0 -15px; 75 | } 76 | 77 | .btn-primary:active, 78 | .btn-primary.active { 79 | background-color: #2d6ca2; 80 | border-color: #2b669a; 81 | } 82 | 83 | .btn-success { 84 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 85 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 86 | background-repeat: repeat-x; 87 | border-color: #3e8f3e; 88 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 89 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 90 | } 91 | 92 | .btn-success:hover, 93 | .btn-success:focus { 94 | background-color: #419641; 95 | background-position: 0 -15px; 96 | } 97 | 98 | .btn-success:active, 99 | .btn-success.active { 100 | background-color: #419641; 101 | border-color: #3e8f3e; 102 | } 103 | 104 | .btn-warning { 105 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 106 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 107 | background-repeat: repeat-x; 108 | border-color: #e38d13; 109 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 110 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 111 | } 112 | 113 | .btn-warning:hover, 114 | .btn-warning:focus { 115 | background-color: #eb9316; 116 | background-position: 0 -15px; 117 | } 118 | 119 | .btn-warning:active, 120 | .btn-warning.active { 121 | background-color: #eb9316; 122 | border-color: #e38d13; 123 | } 124 | 125 | .btn-danger { 126 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 127 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 128 | background-repeat: repeat-x; 129 | border-color: #b92c28; 130 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 131 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 132 | } 133 | 134 | .btn-danger:hover, 135 | .btn-danger:focus { 136 | background-color: #c12e2a; 137 | background-position: 0 -15px; 138 | } 139 | 140 | .btn-danger:active, 141 | .btn-danger.active { 142 | background-color: #c12e2a; 143 | border-color: #b92c28; 144 | } 145 | 146 | .btn-info { 147 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 148 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 149 | background-repeat: repeat-x; 150 | border-color: #28a4c9; 151 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 152 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 153 | } 154 | 155 | .btn-info:hover, 156 | .btn-info:focus { 157 | background-color: #2aabd2; 158 | background-position: 0 -15px; 159 | } 160 | 161 | .btn-info:active, 162 | .btn-info.active { 163 | background-color: #2aabd2; 164 | border-color: #28a4c9; 165 | } 166 | 167 | .thumbnail, 168 | .img-thumbnail { 169 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 170 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 171 | } 172 | 173 | .dropdown-menu > li > a:hover, 174 | .dropdown-menu > li > a:focus { 175 | background-color: #e8e8e8; 176 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 177 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 178 | background-repeat: repeat-x; 179 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 180 | } 181 | 182 | .dropdown-menu > .active > a, 183 | .dropdown-menu > .active > a:hover, 184 | .dropdown-menu > .active > a:focus { 185 | background-color: #357ebd; 186 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 187 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 188 | background-repeat: repeat-x; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 190 | } 191 | 192 | .navbar-default { 193 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 194 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 195 | background-repeat: repeat-x; 196 | border-radius: 4px; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 198 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 199 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 200 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 201 | } 202 | 203 | .navbar-default .navbar-nav > .active > a { 204 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 205 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 206 | background-repeat: repeat-x; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 208 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 209 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 210 | } 211 | 212 | .navbar-brand, 213 | .navbar-nav > li > a { 214 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 215 | } 216 | 217 | .navbar-inverse { 218 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 219 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 220 | background-repeat: repeat-x; 221 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 222 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 223 | } 224 | 225 | .navbar-inverse .navbar-nav > .active > a { 226 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%); 227 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 232 | } 233 | 234 | .navbar-inverse .navbar-brand, 235 | .navbar-inverse .navbar-nav > li > a { 236 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 237 | } 238 | 239 | .navbar-static-top, 240 | .navbar-fixed-top, 241 | .navbar-fixed-bottom { 242 | border-radius: 0; 243 | } 244 | 245 | .alert { 246 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 247 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 248 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 249 | } 250 | 251 | .alert-success { 252 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 253 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 254 | background-repeat: repeat-x; 255 | border-color: #b2dba1; 256 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 257 | } 258 | 259 | .alert-info { 260 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 261 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 262 | background-repeat: repeat-x; 263 | border-color: #9acfea; 264 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 265 | } 266 | 267 | .alert-warning { 268 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 269 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 270 | background-repeat: repeat-x; 271 | border-color: #f5e79e; 272 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 273 | } 274 | 275 | .alert-danger { 276 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 277 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 278 | background-repeat: repeat-x; 279 | border-color: #dca7a7; 280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 281 | } 282 | 283 | .progress { 284 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 285 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 286 | background-repeat: repeat-x; 287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 288 | } 289 | 290 | .progress-bar { 291 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 292 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 293 | background-repeat: repeat-x; 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 295 | } 296 | 297 | .progress-bar-success { 298 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 299 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 300 | background-repeat: repeat-x; 301 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 302 | } 303 | 304 | .progress-bar-info { 305 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 306 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 307 | background-repeat: repeat-x; 308 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 309 | } 310 | 311 | .progress-bar-warning { 312 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 313 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 314 | background-repeat: repeat-x; 315 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 316 | } 317 | 318 | .progress-bar-danger { 319 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 320 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 321 | background-repeat: repeat-x; 322 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 323 | } 324 | 325 | .list-group { 326 | border-radius: 4px; 327 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 328 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 329 | } 330 | 331 | .list-group-item.active, 332 | .list-group-item.active:hover, 333 | .list-group-item.active:focus { 334 | text-shadow: 0 -1px 0 #3071a9; 335 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 336 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 337 | background-repeat: repeat-x; 338 | border-color: #3278b3; 339 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 340 | } 341 | 342 | .panel { 343 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 344 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 345 | } 346 | 347 | .panel-default > .panel-heading { 348 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 349 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 350 | background-repeat: repeat-x; 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 352 | } 353 | 354 | .panel-primary > .panel-heading { 355 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 356 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 357 | background-repeat: repeat-x; 358 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 359 | } 360 | 361 | .panel-success > .panel-heading { 362 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 363 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 364 | background-repeat: repeat-x; 365 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 366 | } 367 | 368 | .panel-info > .panel-heading { 369 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 370 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 371 | background-repeat: repeat-x; 372 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 373 | } 374 | 375 | .panel-warning > .panel-heading { 376 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 377 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 378 | background-repeat: repeat-x; 379 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 380 | } 381 | 382 | .panel-danger > .panel-heading { 383 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 384 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 385 | background-repeat: repeat-x; 386 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 387 | } 388 | 389 | .well { 390 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 391 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 392 | background-repeat: repeat-x; 393 | border-color: #dcdcdc; 394 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 395 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 396 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 397 | } -------------------------------------------------------------------------------- /css/cambridgeenglish.css: -------------------------------------------------------------------------------- 1 | body #header, 2 | body #ad_topslot_a { 3 | display: none; 4 | } 5 | -------------------------------------------------------------------------------- /css/cambridgeenglish.less: -------------------------------------------------------------------------------- 1 | body { 2 | #header, #ad_topslot_a { 3 | display: none 4 | } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /css/collins.css: -------------------------------------------------------------------------------- 1 | body header { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /css/collins.less: -------------------------------------------------------------------------------- 1 | body { 2 | header { 3 | display: none 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /css/dict-cn.css: -------------------------------------------------------------------------------- 1 | body #header { 2 | display: none; 3 | } 4 | body #content { 5 | padding-top: 60px; 6 | } 7 | -------------------------------------------------------------------------------- /css/dict-cn.less: -------------------------------------------------------------------------------- 1 | body { 2 | #header { 3 | display: none; 4 | } 5 | #content { 6 | padding-top: 60px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /css/dictheader.css: -------------------------------------------------------------------------------- 1 | #fairy-dict { 2 | line-height: initial; 3 | box-sizing: border-box; 4 | /*Fix button and input styles on some websites*/ 5 | } 6 | #fairy-dict .navbar { 7 | background-color: rgba(248, 250, 251, 0.75); 8 | padding-bottom: 0; 9 | margin-bottom: 0; 10 | z-index: 9000000304; 11 | } 12 | #fairy-dict .pron { 13 | color: #3a78d5; 14 | } 15 | #fairy-dict .example { 16 | color: #a26f14; 17 | } 18 | #fairy-dict .fa-coffee, 19 | #fairy-dict .fa-volume-up { 20 | color: black; 21 | } 22 | #fairy-dict .modal-body { 23 | padding-top: 8px; 24 | } 25 | #fairy-dict .navbar-form { 26 | padding: 5px 10px; 27 | margin: 0; 28 | } 29 | #fairy-dict .fa-2x { 30 | vertical-align: middle; 31 | } 32 | #fairy-dict .caret { 33 | margin-left: 5px; 34 | } 35 | #fairy-dict .navbar-form .dropdown-toggle { 36 | overflow: hidden; 37 | } 38 | #fairy-dict iframe { 39 | border: none; 40 | height: 620px; 41 | width: 100%; 42 | } 43 | #fairy-dict .default-page { 44 | margin-right: 10px; 45 | } 46 | #fairy-dict .search-group { 47 | display: block; 48 | position: relative; 49 | overflow: hidden; 50 | } 51 | #fairy-dict .search-group .starrr { 52 | position: relative; 53 | left: -15px; 54 | top: 5px; 55 | } 56 | #fairy-dict .search-group .fa-search { 57 | position: relative; 58 | right: 30px; 59 | top: 10px; 60 | cursor: pointer; 61 | } 62 | #fairy-dict .search-group .search-wrapper { 63 | display: block; 64 | overflow: hidden; 65 | padding-left: 10px; 66 | } 67 | #fairy-dict .search-group .search-wrapper .dict-input { 68 | width: 100%; 69 | margin: 0; 70 | } 71 | #fairy-dict .history-group .dropdown-menu { 72 | min-width: 220px; 73 | text-align: left; 74 | height: auto; 75 | max-height: 600px; 76 | overflow-y: auto; 77 | } 78 | #fairy-dict .history-group .dropdown-menu li > a { 79 | padding-right: 2px; 80 | 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | width: 300px; 84 | white-space: nowrap; 85 | } 86 | #fairy-dict .history-group #history-btn { 87 | max-width: 100px; 88 | overflow: hidden; 89 | } 90 | #fairy-dict .history-group .vhide { 91 | visibility: hidden; 92 | } 93 | #fairy-dict .dict-group .dict-name { 94 | min-width: 180px; 95 | } 96 | #fairy-dict .dict-group .dict-list { 97 | text-align: left; 98 | min-width: 180px; 99 | overflow: hidden; 100 | } 101 | #fairy-dict .sound, 102 | #fairy-dict .default-page, 103 | #fairy-dict .dict_prev, 104 | #fairy-dict .dict_next { 105 | cursor: pointer; 106 | } 107 | #fairy-dict #fairy-stars { 108 | font-size: 20px; 109 | /*margin-left: 10px;*/ 110 | margin-right: 5px; 111 | } 112 | #fairy-dict #fairy-stars i.fa { 113 | padding-left: 3px; 114 | color: orange; 115 | } 116 | #fairy-dict .fa.fa-star { 117 | color: orange; 118 | } 119 | #fairy-dict .btn { 120 | height: 34px; 121 | margin: 0 0 0 -1px; 122 | border: #cccccc solid 0.65px; 123 | padding: 6px 12px; 124 | box-shadow: none; 125 | } 126 | #fairy-dict input { 127 | width: 100%; 128 | height: 34px; 129 | padding: 6px 12px; 130 | box-sizing: border-box; 131 | } 132 | -------------------------------------------------------------------------------- /css/dictheader.less: -------------------------------------------------------------------------------- 1 | #fairy-dict { 2 | 3 | line-height: initial; 4 | box-sizing: border-box; 5 | 6 | .navbar { 7 | background-color: rgba(248, 250, 251, 0.75); 8 | } 9 | .pron{ 10 | color: rgb(58, 120, 213); 11 | } 12 | .example{ 13 | color: rgb(162, 111, 20); 14 | } 15 | .fa-coffee, .fa-volume-up{ 16 | color: black; 17 | } 18 | 19 | .modal-body{ 20 | padding-top: 8px; 21 | } 22 | 23 | .navbar-form { 24 | padding: 5px 10px; 25 | } 26 | .form-group { 27 | // height: 34px; 28 | } 29 | 30 | .fa-2x{ 31 | vertical-align: middle; 32 | } 33 | 34 | .caret{ 35 | margin-left: 5px; 36 | } 37 | 38 | .navbar-form .dropdown-toggle { 39 | overflow: hidden; 40 | } 41 | 42 | iframe { 43 | border: none; 44 | height: 620px; 45 | width: 100%; 46 | } 47 | 48 | .default-page { 49 | margin-right: 10px; 50 | } 51 | 52 | .search-group { 53 | display: block; 54 | position: relative; 55 | overflow: hidden; 56 | .starrr { 57 | position: relative; 58 | left: -15px; 59 | top: 5px; 60 | } 61 | 62 | .fa-search { 63 | position: relative; 64 | right: 30px; 65 | top: 10px; 66 | cursor: pointer; 67 | } 68 | .search-wrapper { 69 | display: block; 70 | overflow: hidden; 71 | padding-left: 10px; 72 | .dict-input { 73 | width: 100% 74 | margin: 0; // override urban dictionary's style 75 | } 76 | 77 | } 78 | } 79 | 80 | .history-group { 81 | .dropdown-menu { 82 | min-width: 220px; 83 | text-align: left; 84 | height: auto; 85 | max-height: 600px; 86 | overflow-y: auto; 87 | } 88 | 89 | .dropdown-menu li>a { 90 | padding-right: 2px; 91 | } 92 | #history-btn { 93 | max-width: 100px; 94 | overflow: hidden; 95 | } 96 | .vhide { 97 | visibility: hidden; 98 | } 99 | } 100 | 101 | .dict-group { 102 | .dict-name { 103 | min-width: 180px; 104 | } 105 | .dict-list { 106 | text-align: left; 107 | min-width: 180px; 108 | overflow: hidden; 109 | } 110 | } 111 | 112 | .sound, .default-page, .dict_prev, .dict_next{ 113 | cursor: pointer; 114 | } 115 | 116 | #fairy-stars { 117 | font-size: 20px; 118 | /*margin-left: 10px;*/ 119 | margin-right: 5px; 120 | } 121 | 122 | #fairy-stars i.fa{ 123 | padding-left: 3px; 124 | color: orange; 125 | } 126 | 127 | .fa.fa-star { 128 | color: orange; 129 | } 130 | 131 | /*Fix button and input styles on some websites*/ 132 | .btn { 133 | height: 34px; 134 | margin: 0 0 0 -1px; 135 | border: rgb(204, 204, 204) solid 0.65px; 136 | 137 | padding: 6px 12px; 138 | box-shadow: none; 139 | } 140 | 141 | input { 142 | width: 100%; 143 | height: 34px; 144 | padding: 6px 12px; 145 | box-sizing: border-box; // fix on iciba.com 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /css/dictionary-com.css: -------------------------------------------------------------------------------- 1 | body .ham-nav, 2 | body .dicticon-hamburger-menu, 3 | body header.dictionary-header, 4 | body #fly-out, 5 | body iframe { 6 | display: none !important; 7 | } 8 | body #header-lb-container { 9 | padding-top: 100px; 10 | } 11 | -------------------------------------------------------------------------------- /css/dictionary-com.less: -------------------------------------------------------------------------------- 1 | body { 2 | // padding-top: 90px !important; 3 | 4 | // iframe is ad. 5 | .ham-nav, .dicticon-hamburger-menu, header.dictionary-header, #fly-out, iframe { 6 | display: none !important; 7 | } 8 | 9 | #header-lb-container { 10 | padding-top: 100px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /css/eudic.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px !important; 3 | } 4 | body header, 5 | body #head-bar, 6 | body #head-bk { 7 | display: none; 8 | } 9 | -------------------------------------------------------------------------------- /css/eudic.less: -------------------------------------------------------------------------------- 1 | body { 2 | header, #head-bar, #head-bk { 3 | display: none; 4 | } 5 | 6 | padding-top: 60px !important; 7 | } 8 | -------------------------------------------------------------------------------- /css/hjenglish.css: -------------------------------------------------------------------------------- 1 | .club_Header_pnl_newHead { 2 | display: none; 3 | } 4 | 5 | #xd_search { 6 | display: none; 7 | } 8 | 9 | #webbox-content { 10 | margin-top: 70px !important; 11 | } 12 | -------------------------------------------------------------------------------- /css/iciba.css: -------------------------------------------------------------------------------- 1 | body .search, 2 | body .common-top { 3 | display: none; 4 | } 5 | body .screen { 6 | padding-top: 80px; 7 | } 8 | -------------------------------------------------------------------------------- /css/iciba.less: -------------------------------------------------------------------------------- 1 | body { 2 | .search, .common-top { 3 | display: none; 4 | } 5 | .screen { 6 | padding-top: 80px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /css/longmanenglish.css: -------------------------------------------------------------------------------- 1 | body .header { 2 | display: none !important; 3 | } 4 | body .content { 5 | margin-top: 70px; 6 | } 7 | -------------------------------------------------------------------------------- /css/longmanenglish.less: -------------------------------------------------------------------------------- 1 | body { 2 | .header { 3 | display: none !important; 4 | } 5 | 6 | .content { 7 | margin-top: 70px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /css/macmilland.css: -------------------------------------------------------------------------------- 1 | body #header, 2 | body #search_container { 3 | display: none !important; 4 | } 5 | -------------------------------------------------------------------------------- /css/macmilland.less: -------------------------------------------------------------------------------- 1 | body { 2 | #header, #search_container { 3 | display: none !important; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /css/merriamwebster.css: -------------------------------------------------------------------------------- 1 | body header, 2 | body .home-top-creative-cont { 3 | display: none; 4 | } 5 | -------------------------------------------------------------------------------- /css/merriamwebster.less: -------------------------------------------------------------------------------- 1 | body { 2 | header, .home-top-creative-cont { 3 | display: none 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /css/options.css: -------------------------------------------------------------------------------- 1 | .indent { 2 | margin-left: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /css/options.less: -------------------------------------------------------------------------------- 1 | .indent { 2 | margin-left: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /css/oxfordlearner.css: -------------------------------------------------------------------------------- 1 | body .mainsearch { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /css/oxfordlearner.less: -------------------------------------------------------------------------------- 1 | body { 2 | .mainsearch { 3 | display: none 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /css/oxfordliving.css: -------------------------------------------------------------------------------- 1 | body #header { 2 | display: none; 3 | } 4 | body #main { 5 | padding-top: 0 !important; 6 | margin-top: 50px !important; 7 | } 8 | -------------------------------------------------------------------------------- /css/oxfordliving.less: -------------------------------------------------------------------------------- 1 | body { 2 | #header { 3 | display: none; 4 | } 5 | #main { 6 | padding-top: 0 !important; 7 | margin-top: 50px !important; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /css/urban.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 15px !important; 3 | } 4 | body #urban-top-bar { 5 | display: none !important; 6 | } 7 | -------------------------------------------------------------------------------- /css/urban.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 15px !important; 3 | 4 | #urban-top-bar { 5 | display: none !important; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /css/youdao.css: -------------------------------------------------------------------------------- 1 | body .c-topbar-wrapper { 2 | display: none; 3 | } 4 | body #scontainer { 5 | margin-top: 50px; 6 | padding-top: 0; 7 | } 8 | -------------------------------------------------------------------------------- /css/youdao.less: -------------------------------------------------------------------------------- 1 | body { 2 | .c-topbar-wrapper { 3 | display: none 4 | } 5 | #scontainer { 6 | margin-top: 50px; 7 | padding-top: 0; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /css/zdic.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | } 4 | -------------------------------------------------------------------------------- /dict.coffee: -------------------------------------------------------------------------------- 1 | dictApp = angular.module('fairyDictApp', ['ui.bootstrap', 'ngSanitize']) 2 | dictApp.run ($rootScope)-> 3 | $rootScope._ = _ 4 | 5 | loader.loadTemplate().then ()-> 6 | angular.bootstrap(document.getElementById('fairy-dict'), ['fairyDictApp']) 7 | 8 | dictApp.controller 'dictCtrl', ($scope, $sce) -> 9 | console.log "[dictCtrl] init" 10 | 11 | # change Bing dictionary's title 12 | document.title = 'Fairy Dict' 13 | baseNode = '#fairy-dict' 14 | $scope.initial = true 15 | $scope.querying = false 16 | $scope.queryResult = null 17 | $scope.historyIndex = -1 18 | 19 | chrome.runtime.sendMessage { 20 | type: 'dictionary' 21 | }, ({dictionary, allDicts, history})-> 22 | console.log "[dict] all dicts: ", allDicts 23 | $scope.allDicts = allDicts 24 | $scope.currentDictionary = allDicts.find (d)-> 25 | d.dictName == dictionary 26 | $scope.currentDictionary ?= allDicts[0] 27 | $scope.history = history.reverse() 28 | $scope.lastHistoryWord = $scope.history[0] 29 | $scope.$apply() 30 | 31 | chrome.runtime.sendMessage { 32 | type: 'setting' 33 | }, (setting)-> 34 | $scope.setting = setting 35 | 36 | $scope.changeDict = (dict)-> 37 | ci = $scope.allDicts.findIndex (d)-> 38 | d.dictName == $scope.currentDictionary.dictName 39 | 40 | if dict == 'next' 41 | idx = (ci+1) % $scope.allDicts.length 42 | $scope.currentDictionary = $scope.allDicts[idx] 43 | else if dict == 'prev' 44 | idx = if ci > 0 then ci-1 else ($scope.allDicts.length-1) 45 | $scope.currentDictionary = $scope.allDicts[idx] 46 | else 47 | $scope.currentDictionary = dict 48 | $scope.query(true) 49 | 50 | applyHistory = (index)-> 51 | $scope.historyIndex = index 52 | if index > -1 53 | $scope.word = _.keys($scope.history[index])[0] 54 | else 55 | $scope.word = $scope.lastQueryWord 56 | 57 | if index == $scope.history.length-1 58 | $scope.lastHistoryWord = $scope.history[0] 59 | else 60 | $scope.lastHistoryWord = $scope.history[index+1] 61 | 62 | $scope.selectHistory = (index)-> 63 | if index == $scope.historyIndex 64 | return 65 | if index == 'prev' 66 | if $scope.historyIndex == $scope.history.length-1 67 | return 68 | index = $scope.historyIndex + 1 69 | else if index == 'next' 70 | if $scope.historyIndex < 0 71 | return 72 | index = $scope.historyIndex - 1 73 | 74 | if index >= $scope.history.length 75 | index = 0 76 | 77 | applyHistory(index) 78 | 79 | $scope.query(true) 80 | 81 | $scope.deleteHistory = (index)-> 82 | item = $scope.history.splice(index, 1) 83 | chrome.runtime.sendMessage({ 84 | type: 'deleteHistory', 85 | index: index, 86 | text: _.keys(item[0])[0] 87 | }) 88 | return false 89 | 90 | $scope.query = (inHistory)-> 91 | if not $scope.word or not $scope.currentDictionary 92 | $scope.initial = true 93 | return 94 | 95 | console.log "[dictCtrl] query `#{$scope.word}` from #{$scope.currentDictionary.dictName}" 96 | $scope.initial = false 97 | $scope.querying = true 98 | 99 | chrome.runtime.sendMessage({ 100 | type: 'query', 101 | text: $scope.word, 102 | dictionary: $scope.currentDictionary.dictName, 103 | inHistory: inHistory 104 | }) 105 | 106 | chrome.runtime.onMessage?.addListener (request, sender, sendResponse)-> 107 | if request.type == 'querying' 108 | $scope.initial = false 109 | $scope.querying = true 110 | $scope.queryResult = null 111 | $scope.word = request.text 112 | 113 | else if request.type == 'queryResult' 114 | console.log "[dictCtrl] got query result for word: #{request.text}" 115 | if $scope.word == request.text or !$scope.word 116 | $scope.initial = false 117 | $scope.word = request.text 118 | $scope.querying = false 119 | if request.result?.html? 120 | $scope.queryResult = $sce.trustAsHtml(request.result.html) 121 | $scope.rating = request.rating 122 | updateRating(request.rating) 123 | if not request.inHistory 124 | $scope.lastQueryWord = $scope.word 125 | else 126 | $scope.history.forEach (item, idx)-> 127 | itemText = _.keys(item)[0] 128 | if itemText == $scope.word 129 | applyHistory(idx) 130 | 131 | else if request.type == 'history' 132 | console.log "history", request.history 133 | $scope.history = request.history.reverse() 134 | $scope.lastHistoryWord = $scope.history[0] 135 | $scope.historyIndex = -1 136 | 137 | $scope.$apply() 138 | 139 | $('#fairy-stars', baseNode).on 'starrr:change', (e, value)-> 140 | if $scope.word 141 | value ?= 0 142 | console.log "[dictCtrl] rating word: #{$scope.word} #{value}" 143 | chrome.runtime.sendMessage { 144 | type: 'rating', 145 | value: value, 146 | text: $scope.word 147 | } 148 | if $scope.historyIndex >= 0 149 | item = $scope.history[$scope.historyIndex] 150 | if item and item[$scope.word]? 151 | item[$scope.word] = value 152 | 153 | $('.starrr', baseNode).starrr({numStars: 3}) 154 | 155 | updateRating = (value)-> 156 | obj = $(".starrr", baseNode).data('star-rating') 157 | obj.options.rating = value 158 | obj.syncRating() 159 | 160 | _handler = (evt)-> 161 | node = $(event.target) 162 | if node.is('.sound') 163 | a = node.next('audio') 164 | if a.length 165 | a[0].play() 166 | 167 | $(document).mouseover _handler 168 | $(document).click _handler 169 | 170 | $(document).keyup (evt)-> 171 | code = evt.charCode or evt.keyCode 172 | if code == 27 173 | $('input.dict-input', baseNode)[0].select() 174 | 175 | $(document).keydown (evt)-> 176 | code = evt.charCode or evt.keyCode 177 | prevSK = $scope.setting.prevDictSK1 178 | nextSK = $scope.setting.nextDictSK1 179 | prevKey = $scope.setting.prevDictKey 180 | nextKey = $scope.setting.nextDictKey 181 | stop = false 182 | 183 | if window.utils.checkEventKey evt, prevSK, null, prevKey 184 | $scope.changeDict('prev') 185 | stop = true 186 | if window.utils.checkEventKey evt, nextSK, null, nextKey 187 | $scope.changeDict('next') 188 | stop = true 189 | if window.utils.checkEventKey evt, $scope.setting.prevHistorySK1, null, $scope.setting.prevHistoryKey 190 | $scope.selectHistory('prev') 191 | stop = true 192 | if window.utils.checkEventKey evt, $scope.setting.nextHistorySK1, null, $scope.setting.nextHistoryKey 193 | $scope.selectHistory('next') 194 | stop = true 195 | if stop 196 | evt.preventDefault() 197 | evt.stopPropagation() 198 | return 199 | 200 | -------------------------------------------------------------------------------- /dict.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var dictApp; 3 | 4 | dictApp = angular.module('fairyDictApp', ['ui.bootstrap', 'ngSanitize']); 5 | 6 | dictApp.run(function($rootScope) { 7 | return $rootScope._ = _; 8 | }); 9 | 10 | loader.loadTemplate().then(function() { 11 | return angular.bootstrap(document.getElementById('fairy-dict'), ['fairyDictApp']); 12 | }); 13 | 14 | dictApp.controller('dictCtrl', function($scope, $sce) { 15 | var _handler, applyHistory, baseNode, ref, updateRating; 16 | console.log("[dictCtrl] init"); 17 | document.title = 'Fairy Dict'; 18 | baseNode = '#fairy-dict'; 19 | $scope.initial = true; 20 | $scope.querying = false; 21 | $scope.queryResult = null; 22 | $scope.historyIndex = -1; 23 | chrome.runtime.sendMessage({ 24 | type: 'dictionary' 25 | }, function(arg) { 26 | var allDicts, dictionary, history; 27 | dictionary = arg.dictionary, allDicts = arg.allDicts, history = arg.history; 28 | console.log("[dict] all dicts: ", allDicts); 29 | $scope.allDicts = allDicts; 30 | $scope.currentDictionary = allDicts.find(function(d) { 31 | return d.dictName === dictionary; 32 | }); 33 | if ($scope.currentDictionary == null) { 34 | $scope.currentDictionary = allDicts[0]; 35 | } 36 | $scope.history = history.reverse(); 37 | $scope.lastHistoryWord = $scope.history[0]; 38 | return $scope.$apply(); 39 | }); 40 | chrome.runtime.sendMessage({ 41 | type: 'setting' 42 | }, function(setting) { 43 | return $scope.setting = setting; 44 | }); 45 | $scope.changeDict = function(dict) { 46 | var ci, idx; 47 | ci = $scope.allDicts.findIndex(function(d) { 48 | return d.dictName === $scope.currentDictionary.dictName; 49 | }); 50 | if (dict === 'next') { 51 | idx = (ci + 1) % $scope.allDicts.length; 52 | $scope.currentDictionary = $scope.allDicts[idx]; 53 | } else if (dict === 'prev') { 54 | idx = ci > 0 ? ci - 1 : $scope.allDicts.length - 1; 55 | $scope.currentDictionary = $scope.allDicts[idx]; 56 | } else { 57 | $scope.currentDictionary = dict; 58 | } 59 | return $scope.query(true); 60 | }; 61 | applyHistory = function(index) { 62 | $scope.historyIndex = index; 63 | if (index > -1) { 64 | $scope.word = _.keys($scope.history[index])[0]; 65 | } else { 66 | $scope.word = $scope.lastQueryWord; 67 | } 68 | if (index === $scope.history.length - 1) { 69 | return $scope.lastHistoryWord = $scope.history[0]; 70 | } else { 71 | return $scope.lastHistoryWord = $scope.history[index + 1]; 72 | } 73 | }; 74 | $scope.selectHistory = function(index) { 75 | if (index === $scope.historyIndex) { 76 | return; 77 | } 78 | if (index === 'prev') { 79 | if ($scope.historyIndex === $scope.history.length - 1) { 80 | return; 81 | } 82 | index = $scope.historyIndex + 1; 83 | } else if (index === 'next') { 84 | if ($scope.historyIndex < 0) { 85 | return; 86 | } 87 | index = $scope.historyIndex - 1; 88 | } 89 | if (index >= $scope.history.length) { 90 | index = 0; 91 | } 92 | applyHistory(index); 93 | return $scope.query(true); 94 | }; 95 | $scope.deleteHistory = function(index) { 96 | var item; 97 | item = $scope.history.splice(index, 1); 98 | chrome.runtime.sendMessage({ 99 | type: 'deleteHistory', 100 | index: index, 101 | text: _.keys(item[0])[0] 102 | }); 103 | return false; 104 | }; 105 | $scope.query = function(inHistory) { 106 | if (!$scope.word || !$scope.currentDictionary) { 107 | $scope.initial = true; 108 | return; 109 | } 110 | console.log("[dictCtrl] query `" + $scope.word + "` from " + $scope.currentDictionary.dictName); 111 | $scope.initial = false; 112 | $scope.querying = true; 113 | return chrome.runtime.sendMessage({ 114 | type: 'query', 115 | text: $scope.word, 116 | dictionary: $scope.currentDictionary.dictName, 117 | inHistory: inHistory 118 | }); 119 | }; 120 | if ((ref = chrome.runtime.onMessage) != null) { 121 | ref.addListener(function(request, sender, sendResponse) { 122 | var ref1; 123 | if (request.type === 'querying') { 124 | $scope.initial = false; 125 | $scope.querying = true; 126 | $scope.queryResult = null; 127 | $scope.word = request.text; 128 | } else if (request.type === 'queryResult') { 129 | console.log("[dictCtrl] got query result for word: " + request.text); 130 | if ($scope.word === request.text || !$scope.word) { 131 | $scope.initial = false; 132 | $scope.word = request.text; 133 | $scope.querying = false; 134 | if (((ref1 = request.result) != null ? ref1.html : void 0) != null) { 135 | $scope.queryResult = $sce.trustAsHtml(request.result.html); 136 | } 137 | $scope.rating = request.rating; 138 | updateRating(request.rating); 139 | if (!request.inHistory) { 140 | $scope.lastQueryWord = $scope.word; 141 | } else { 142 | $scope.history.forEach(function(item, idx) { 143 | var itemText; 144 | itemText = _.keys(item)[0]; 145 | if (itemText === $scope.word) { 146 | return applyHistory(idx); 147 | } 148 | }); 149 | } 150 | } 151 | } else if (request.type === 'history') { 152 | console.log("history", request.history); 153 | $scope.history = request.history.reverse(); 154 | $scope.lastHistoryWord = $scope.history[0]; 155 | $scope.historyIndex = -1; 156 | } 157 | return $scope.$apply(); 158 | }); 159 | } 160 | $('#fairy-stars', baseNode).on('starrr:change', function(e, value) { 161 | var item; 162 | if ($scope.word) { 163 | if (value == null) { 164 | value = 0; 165 | } 166 | console.log("[dictCtrl] rating word: " + $scope.word + " " + value); 167 | chrome.runtime.sendMessage({ 168 | type: 'rating', 169 | value: value, 170 | text: $scope.word 171 | }); 172 | if ($scope.historyIndex >= 0) { 173 | item = $scope.history[$scope.historyIndex]; 174 | if (item && (item[$scope.word] != null)) { 175 | return item[$scope.word] = value; 176 | } 177 | } 178 | } 179 | }); 180 | $('.starrr', baseNode).starrr({ 181 | numStars: 3 182 | }); 183 | updateRating = function(value) { 184 | var obj; 185 | obj = $(".starrr", baseNode).data('star-rating'); 186 | obj.options.rating = value; 187 | return obj.syncRating(); 188 | }; 189 | _handler = function(evt) { 190 | var a, node; 191 | node = $(event.target); 192 | if (node.is('.sound')) { 193 | a = node.next('audio'); 194 | if (a.length) { 195 | return a[0].play(); 196 | } 197 | } 198 | }; 199 | $(document).mouseover(_handler); 200 | $(document).click(_handler); 201 | $(document).keyup(function(evt) { 202 | var code; 203 | code = evt.charCode || evt.keyCode; 204 | if (code === 27) { 205 | return $('input.dict-input', baseNode)[0].select(); 206 | } 207 | }); 208 | $(document).keydown(function(evt) { 209 | var code, nextKey, nextSK, prevKey, prevSK, stop; 210 | code = evt.charCode || evt.keyCode; 211 | prevSK = $scope.setting.prevDictSK1; 212 | nextSK = $scope.setting.nextDictSK1; 213 | prevKey = $scope.setting.prevDictKey; 214 | nextKey = $scope.setting.nextDictKey; 215 | stop = false; 216 | if (window.utils.checkEventKey(evt, prevSK, null, prevKey)) { 217 | $scope.changeDict('prev'); 218 | stop = true; 219 | } 220 | if (window.utils.checkEventKey(evt, nextSK, null, nextKey)) { 221 | $scope.changeDict('next'); 222 | stop = true; 223 | } 224 | if (window.utils.checkEventKey(evt, $scope.setting.prevHistorySK1, null, $scope.setting.prevHistoryKey)) { 225 | $scope.selectHistory('prev'); 226 | stop = true; 227 | } 228 | if (window.utils.checkEventKey(evt, $scope.setting.nextHistorySK1, null, $scope.setting.nextHistoryKey)) { 229 | $scope.selectHistory('next'); 230 | stop = true; 231 | } 232 | if (stop) { 233 | evt.preventDefault(); 234 | return evt.stopPropagation(); 235 | } 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /fonts/fairydict-font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/fairydict-font.woff -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /helper.coffee: -------------------------------------------------------------------------------- 1 | helperFn = (window, $)-> 2 | gConfig = window.gConfig 3 | 4 | window.helper = { 5 | getRandomInt: (min, max)-> 6 | min ?= 1 7 | max ?= 10 8 | min = Math.ceil(min) 9 | max = Math.floor(max) 10 | return Math.floor(Math.random() * (max - min)) + min 11 | 12 | sendMessage: (config)-> 13 | dfd = $.Deferred() 14 | chrome.runtime.sendMessage(config, (response)-> 15 | dfd.resolve(response) 16 | ) 17 | return dfd 18 | 19 | getJson: (url, data)-> 20 | return this.sendMessage({url, data, type: 'getJson'}) 21 | 22 | postJson: (url, data)-> 23 | return this.sendMessage({url, data, type: 'postJson'}) 24 | 25 | } 26 | 27 | helperFn(this, $) 28 | -------------------------------------------------------------------------------- /helper.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var helperFn; 3 | 4 | helperFn = function(window, $) { 5 | var gConfig; 6 | gConfig = window.gConfig; 7 | return window.helper = { 8 | getRandomInt: function(min, max) { 9 | if (min == null) { 10 | min = 1; 11 | } 12 | if (max == null) { 13 | max = 10; 14 | } 15 | min = Math.ceil(min); 16 | max = Math.floor(max); 17 | return Math.floor(Math.random() * (max - min)) + min; 18 | }, 19 | sendMessage: function(config) { 20 | var dfd; 21 | dfd = $.Deferred(); 22 | chrome.runtime.sendMessage(config, function(response) { 23 | return dfd.resolve(response); 24 | }); 25 | return dfd; 26 | }, 27 | getJson: function(url, data) { 28 | return this.sendMessage({ 29 | url: url, 30 | data: data, 31 | type: 'getJson' 32 | }); 33 | }, 34 | postJson: function(url, data) { 35 | return this.sendMessage({ 36 | url: url, 37 | data: data, 38 | type: 'postJson' 39 | }); 40 | } 41 | }; 42 | }; 43 | 44 | helperFn(this, $); 45 | -------------------------------------------------------------------------------- /images/books-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/books-128.png -------------------------------------------------------------------------------- /images/books-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/books-48.png -------------------------------------------------------------------------------- /images/books-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/books-96.png -------------------------------------------------------------------------------- /images/books-b-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/books-b-48.png -------------------------------------------------------------------------------- /images/books-b-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/books-b-96.png -------------------------------------------------------------------------------- /images/gplus32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/gplus32.png -------------------------------------------------------------------------------- /images/tom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/tom.jpg -------------------------------------------------------------------------------- /images/twitter32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/twitter32.jpg -------------------------------------------------------------------------------- /images/wordpress32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/images/wordpress32.jpg -------------------------------------------------------------------------------- /js/ion.sound.js: -------------------------------------------------------------------------------- 1 | // Ion.Sound 2 | // version 1.3.0 Build: 20 3 | // © 2013 Denis Ineshin | IonDen.com 4 | // 5 | // Project page: http://ionden.com/a/plugins/ion.sound/en.html 6 | // GitHub page: https://github.com/IonDen/ion.sound 7 | // 8 | // Released under MIT licence: 9 | // http://ionden.com/a/plugins/licence-en.html 10 | // ===================================================================================================================== 11 | 12 | (function ($) { 13 | 14 | if ($.ionSound) { 15 | return; 16 | } 17 | 18 | 19 | var settings = {}, 20 | soundsNum, 21 | canMp3, 22 | url, 23 | i, 24 | 25 | sounds = {}, 26 | playing = false, 27 | 28 | VERSION = "1.3.0"; 29 | 30 | 31 | var createSound = function (soundInfo) { 32 | var name, 33 | volume; 34 | 35 | if (soundInfo.indexOf(":") !== -1) { 36 | name = soundInfo.split(":")[0]; 37 | volume = soundInfo.split(":")[1]; 38 | } else { 39 | name = soundInfo; 40 | } 41 | 42 | sounds[name] = new Audio(); 43 | canMp3 = sounds[name].canPlayType("audio/mp3"); 44 | if (canMp3 === "probably" || canMp3 === "maybe") { 45 | url = settings.path + name + ".mp3"; 46 | } else { 47 | url = settings.path + name + ".ogg"; 48 | } 49 | 50 | $(sounds[name]).prop("src", url); 51 | sounds[name].load(); 52 | sounds[name].preload = "auto"; 53 | sounds[name].volume = volume || settings.volume; 54 | }; 55 | 56 | 57 | var playSound = function (info) { 58 | var $sound, 59 | name, 60 | volume, 61 | playing_int; 62 | 63 | if (info.indexOf(":") !== -1) { 64 | name = info.split(":")[0]; 65 | volume = info.split(":")[1]; 66 | } else { 67 | name = info; 68 | } 69 | 70 | $sound = sounds[name]; 71 | 72 | if (typeof $sound !== "object" || $sound === null) { 73 | return; 74 | } 75 | 76 | 77 | if (volume) { 78 | $sound.volume = volume; 79 | } 80 | 81 | if (!settings.multiPlay && !playing) { 82 | 83 | $sound.play(); 84 | playing = true; 85 | 86 | playing_int = setInterval(function () { 87 | if ($sound.ended) { 88 | clearInterval(playing_int); 89 | playing = false; 90 | } 91 | }, 250); 92 | 93 | } else if (settings.multiPlay) { 94 | 95 | if ($sound.ended) { 96 | $sound.play(); 97 | } else { 98 | try { 99 | $sound.currentTime = 0; 100 | } catch (e) {} 101 | $sound.play(); 102 | } 103 | 104 | } 105 | }; 106 | 107 | 108 | var stopSound = function (name) { 109 | var $sound = sounds[name]; 110 | 111 | if (typeof $sound !== "object" || $sound === null) { 112 | return; 113 | } 114 | 115 | $sound.pause(); 116 | try { 117 | $sound.currentTime = 0; 118 | } catch (e) {} 119 | }; 120 | 121 | 122 | var killSound = function (name) { 123 | var $sound = sounds[name]; 124 | 125 | if (typeof $sound !== "object" || $sound === null) { 126 | return; 127 | } 128 | 129 | try { 130 | sounds[name].src = ""; 131 | } catch (e) {} 132 | sounds[name] = null; 133 | }; 134 | 135 | 136 | // Plugin methods 137 | $.ionSound = function (options) { 138 | 139 | settings = $.extend({ 140 | sounds: [ 141 | "water_droplet" 142 | ], 143 | path: "static/sounds/", 144 | multiPlay: true, 145 | volume: "0.5" 146 | }, options); 147 | 148 | soundsNum = settings.sounds.length; 149 | 150 | if (typeof Audio === "function" || typeof Audio === "object") { 151 | for (i = 0; i < soundsNum; i += 1) { 152 | createSound(settings.sounds[i]); 153 | } 154 | } 155 | 156 | $.ionSound.play = function (name) { 157 | playSound(name); 158 | }; 159 | $.ionSound.stop = function (name) { 160 | stopSound(name); 161 | }; 162 | $.ionSound.kill = function (name) { 163 | killSound(name); 164 | }; 165 | }; 166 | 167 | 168 | $.ionSound.destroy = function () { 169 | for (i = 0; i < soundsNum; i += 1) { 170 | sounds[settings.sounds[i]] = null; 171 | } 172 | soundsNum = 0; 173 | $.ionSound.play = function () {}; 174 | $.ionSound.stop = function () {}; 175 | $.ionSound.kill = function () {}; 176 | }; 177 | 178 | }(jQuery)); -------------------------------------------------------------------------------- /js/jquery.scoped.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Scoped CSS plugin 3 | * This adds support for the CSS scoped attribute to limit a block of style declarations 4 | * to a specific area of the HTML. You can also use @import and media filters in scoped blocks 5 | * http://www.w3.org/TR/html5/semantics.html#the-style-element 6 | * 7 | * Simon Madine, 1 February 2011 8 | * 9 | * Use: 10 | * Include this plugin file (minified, ideally) and call $.scoped() on load 11 | * 12 | * Limitations: 13 | * - If you're using multiple nested declarations, Webkit might apply different inheritance 14 | * specificity rules from the other engines. I don't know who's right. 15 | * - Sometimes there are delays parsing externally loaded stylesheets (via @import) and they 16 | * might get skipped. Not often but it happens. 17 | * 18 | * Notes: 19 | * - If the browser natively supports 32 | FairyDict 选项 33 | 34 | 35 |
36 |
37 |

FairyDict

38 |

Notice: FairyDict 的升级版 39 | Dictionaries 40 | 已发布, 添加了管理单词表和词典列表的功能, 更多的设置和功能, 欢迎去下载使用 41 | Dictionaries. 42 | 43 | 44 | FairyDict 将不再更新!

45 | 46 |

47 | 此软件是开源软件,喜欢请帮忙评价或点赞,有任何问题或建议请在 48 | Github 49 | 或 50 | V2MM 51 | 上联系我 ;) 52 |

53 | 54 |
55 | 56 |
57 |

功能设置

58 | 59 |
60 | 64 |
65 |
66 | 67 | 同时必须按住 68 | 69 |
70 | 74 | 75 | 80 |
81 |
82 |
83 | 悬停延时检测时间 84 | 毫秒 88 | 89 |
90 |
91 | 92 |
93 | 96 |
97 |
98 | 102 |
103 | 107 |
108 |
109 | 113 | 114 |
115 | 119 | 120 | 125 |
126 |
127 | 128 |
129 | 130 |
131 | 134 |
135 |
136 | 140 | 141 |
142 | 146 | 147 | 152 |
153 |
154 |
155 | 156 |
157 | 单击图标时的功能选择 158 |
159 | 162 |
163 | 166 |
167 |
168 | 169 |
170 | 171 |
172 | 173 |

快捷键设置

174 | 175 |
176 |
177 |
178 | 呼出词典: 179 |
180 |
181 | 185 | 186 | 191 |
192 | 193 |
194 | 198 | 199 | 204 |
205 | 206 |
207 | 211 | 212 | 217 |
218 |
219 |
220 |
221 | 查找上一个词典: 222 |
223 |
224 | 228 | 229 | 234 |
235 |
236 | 240 | 241 | 246 |
247 |
248 |
249 |
250 | 查找下一个词典: 251 |
252 |
253 | 257 | 258 | 263 |
264 |
265 | 269 | 270 | 275 |
276 |
277 | 278 |
279 |
280 | 查找上一个历史记录: 281 |
282 |
283 | 287 | 288 | 293 |
294 |
295 | 299 | 300 | 305 |
306 |
307 |
308 |
309 | 查找下一个历史记录: 310 |
311 |
312 | 316 | 317 | 322 |
323 |
324 | 328 | 329 | 334 |
335 |
336 | 337 |
338 |
339 | 340 |
341 |

好用就分享

342 |
343 | 361 |
362 | 363 |
364 |
365 | 366 | 367 | -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | cd .. 2 | rm -f FairyDict.zip 3 | zip -x '*.DS_Store*' -x '*build/*' -x '*readme_images/*' -x '*node_modules/*' -x '*.git*' -x '*/test/*' -x '*.less' -x '*.coffee' -x '*.scss' -r FairyDict.zip FairyDict/ 4 | -------------------------------------------------------------------------------- /readme_images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/readme_images/2.png -------------------------------------------------------------------------------- /readme_images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/readme_images/3.png -------------------------------------------------------------------------------- /readme_images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/readme_images/4.png -------------------------------------------------------------------------------- /readme_images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revir/FairyDict/ac7722ceda402af1cf420cf7077a06c341e5d5d9/readme_images/5.png -------------------------------------------------------------------------------- /template/apidict.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fairy Dict 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /template/apidict.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.sendMessage({ 2 | type: 'injected', 3 | url: location.href 4 | }); 5 | -------------------------------------------------------------------------------- /template/header.html: -------------------------------------------------------------------------------- 1 |
2 | 67 | 68 |
69 | 70 |
71 |
72 |

欢迎使用 Fairy Dict!

73 | 74 |

向作者反馈请到 75 | Github 76 | 或 77 | 78 | V2MM 79 | 80 | 上留言, 感谢。

81 | 82 |

开源地址: 83 | Github 84 | 你的点赞就是我前进的动力 :) 85 |

86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | -------------------------------------------------------------------------------- /utils.coffee: -------------------------------------------------------------------------------- 1 | window.utils = { 2 | getRandomInt: (min, max)-> 3 | min ?= 1 4 | max ?= 10 5 | min = Math.ceil(min) 6 | max = Math.floor(max) 7 | return Math.floor(Math.random() * (max - min)) + min 8 | 9 | postJson: (url, data) -> 10 | return $.ajax { 11 | type: 'POST', 12 | url: url, 13 | data: JSON.stringify(data), 14 | contentType: 'application/json' 15 | } 16 | getJson: (url, data) -> 17 | return $.ajax { 18 | type: 'GET', 19 | url: url, 20 | data: data, 21 | contentType: 'application/json' 22 | } 23 | 24 | tryGet: (url, obj, message, times, timeout)-> 25 | me = this 26 | dfd = $.Deferred() 27 | params = {url} 28 | params = $.extend params, obj if obj 29 | times = 3 unless times? 30 | timeout = 3*1000 unless timeout? 31 | t = 0 32 | _get = ()-> 33 | t += 1 34 | $.ajax(params).then ((res)-> 35 | dfd.resolve(res) 36 | ), (jqXHR, textStatus)-> 37 | msg = "ajax failed (#{t}/#{times})" 38 | if t < times 39 | console.warn msg, params, jqXHR 40 | setTimeout _get, timeout 41 | else 42 | console.error msg, params, jqXHR 43 | me.postJson(gConfig.logUrl, {message: message or msg, description: {params, jqXHR}}).always (res)-> 44 | dfd.reject(res) 45 | 46 | _get() 47 | return dfd 48 | 49 | extraKeyMap: { 50 | Enter: 13, 51 | Space: 32, 52 | Tab: 9, 53 | End: 35, 54 | Home: 36, 55 | PageDown: 34, 56 | PageUp: 33, 57 | ArrowDown: 40, 58 | ArrowLeft: 37, 59 | ArrowRight: 39, 60 | ArrowUp: 38, 61 | Escape: 27, 62 | } 63 | checkEventKey: (event, sk1, sk2, key)-> 64 | if sk1 and not event[sk1.toLowerCase() + 'Key'] 65 | return false 66 | if sk2 and not event[sk2.toLowerCase() + 'Key'] 67 | return false 68 | if @extraKeyMap[key] 69 | if event.keyCode != @extraKeyMap[key] 70 | return false 71 | 72 | else if key and event.keyCode != key.charCodeAt(0) 73 | return false 74 | 75 | return true 76 | } 77 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | window.utils = { 3 | getRandomInt: function(min, max) { 4 | if (min == null) { 5 | min = 1; 6 | } 7 | if (max == null) { 8 | max = 10; 9 | } 10 | min = Math.ceil(min); 11 | max = Math.floor(max); 12 | return Math.floor(Math.random() * (max - min)) + min; 13 | }, 14 | postJson: function(url, data) { 15 | return $.ajax({ 16 | type: 'POST', 17 | url: url, 18 | data: JSON.stringify(data), 19 | contentType: 'application/json' 20 | }); 21 | }, 22 | getJson: function(url, data) { 23 | return $.ajax({ 24 | type: 'GET', 25 | url: url, 26 | data: data, 27 | contentType: 'application/json' 28 | }); 29 | }, 30 | tryGet: function(url, obj, message, times, timeout) { 31 | var _get, dfd, me, params, t; 32 | me = this; 33 | dfd = $.Deferred(); 34 | params = { 35 | url: url 36 | }; 37 | if (obj) { 38 | params = $.extend(params, obj); 39 | } 40 | if (times == null) { 41 | times = 3; 42 | } 43 | if (timeout == null) { 44 | timeout = 3 * 1000; 45 | } 46 | t = 0; 47 | _get = function() { 48 | t += 1; 49 | return $.ajax(params).then((function(res) { 50 | return dfd.resolve(res); 51 | }), function(jqXHR, textStatus) { 52 | var msg; 53 | msg = "ajax failed (" + t + "/" + times + ")"; 54 | if (t < times) { 55 | console.warn(msg, params, jqXHR); 56 | return setTimeout(_get, timeout); 57 | } else { 58 | console.error(msg, params, jqXHR); 59 | return me.postJson(gConfig.logUrl, { 60 | message: message || msg, 61 | description: { 62 | params: params, 63 | jqXHR: jqXHR 64 | } 65 | }).always(function(res) { 66 | return dfd.reject(res); 67 | }); 68 | } 69 | }); 70 | }; 71 | _get(); 72 | return dfd; 73 | }, 74 | extraKeyMap: { 75 | Enter: 13, 76 | Space: 32, 77 | Tab: 9, 78 | End: 35, 79 | Home: 36, 80 | PageDown: 34, 81 | PageUp: 33, 82 | ArrowDown: 40, 83 | ArrowLeft: 37, 84 | ArrowRight: 39, 85 | ArrowUp: 38, 86 | Escape: 27 87 | }, 88 | checkEventKey: function(event, sk1, sk2, key) { 89 | if (sk1 && !event[sk1.toLowerCase() + 'Key']) { 90 | return false; 91 | } 92 | if (sk2 && !event[sk2.toLowerCase() + 'Key']) { 93 | return false; 94 | } 95 | if (this.extraKeyMap[key]) { 96 | if (event.keyCode !== this.extraKeyMap[key]) { 97 | return false; 98 | } 99 | } else if (key && event.keyCode !== key.charCodeAt(0)) { 100 | return false; 101 | } 102 | return true; 103 | } 104 | }; 105 | --------------------------------------------------------------------------------