Showing preview only (655K chars total). Download the full file or copy to clipboard to get everything.
Repository: ryanker/dream_translate
Branch: main
Commit: 34a941fb0578
Files: 76
Total size: 602.5 KB
Directory structure:
gitextract_au70of8y/
├── LICENSE
├── README.md
├── release.sh
├── src/
│ ├── conf/
│ │ ├── conf.json
│ │ ├── langSpeak.json
│ │ ├── language.json
│ │ └── searchText.txt
│ ├── css/
│ │ ├── content.css
│ │ ├── dmx_dialog.css
│ │ ├── longman.css
│ │ ├── main.css
│ │ └── popup.css
│ ├── html/
│ │ ├── favorite.html
│ │ ├── history.html
│ │ ├── more.html
│ │ ├── popup.html
│ │ ├── record.html
│ │ ├── setting.html
│ │ ├── speak.html
│ │ └── video.html
│ ├── js/
│ │ ├── background.js
│ │ ├── common.js
│ │ ├── content.js
│ │ ├── db.js
│ │ ├── dictionary/
│ │ │ ├── bing.js
│ │ │ ├── cambridge.js
│ │ │ ├── collins.js
│ │ │ ├── dictcn.js
│ │ │ ├── dictionary.js
│ │ │ ├── dreye.js
│ │ │ ├── etymonline.js
│ │ │ ├── eudic.js
│ │ │ ├── hjdict.js
│ │ │ ├── iciba.js
│ │ │ ├── lexico.js
│ │ │ ├── longman.js
│ │ │ ├── macmillan.js
│ │ │ ├── merriam.js
│ │ │ ├── oxford.js
│ │ │ ├── rrdict.js
│ │ │ ├── thefree.js
│ │ │ ├── urban.js
│ │ │ ├── vocabulary.js
│ │ │ ├── wordreference.js
│ │ │ └── youdao.js
│ │ ├── favorite.js
│ │ ├── frame.js
│ │ ├── history.js
│ │ ├── more.js
│ │ ├── player.js
│ │ ├── popup.js
│ │ ├── record.js
│ │ ├── setting.js
│ │ ├── speak.js
│ │ ├── translate/
│ │ │ ├── alibaba.js
│ │ │ ├── baidu.js
│ │ │ ├── bing.js
│ │ │ ├── deepl.js
│ │ │ ├── frdic.js
│ │ │ ├── google.js
│ │ │ ├── local.js
│ │ │ ├── qq.js
│ │ │ ├── so.js
│ │ │ ├── sogou.js
│ │ │ └── youdao.js
│ │ └── video.js
│ └── manifest.json
└── tool/
├── alibaba.html
├── baidu.html
├── bd.js
├── bing.html
├── google.html
├── lang.html
├── qq.html
├── sogou.html
└── youdao.html
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Ryan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# 梦想划词翻译(Chrome & Firefox 扩展程序)
这是一款精心雕琢并免费开源的划词翻译扩展,也是一款致力于改善「中国式哑巴英语」而设计的一款英语练习扩展程序。
### 扩展安装
- Chrome 安装:[梦想划词翻译—聚合词典搜索 - Chrome 网上应用店](https://chrome.google.com/webstore/detail/odfgigmpkhhhieicogijogfobfipijjh)
- Edge 安装:[梦想划词翻译—聚合词典搜索 - Microsoft Edge 扩展商店](https://microsoftedge.microsoft.com/addons/detail/ogleeipbbcokpeabliinildngejmdneg)
- Firefox 安装:[梦想划词翻译—聚合词典搜索 – Firefox 扩展商店](https://addons.mozilla.org/zh-CN/firefox/addon/dream_translate/)
### 程序特点
考虑到程序的维护时间成本,本程序采用纯原生 javascript 开发,拥有更好的性能,同时占用内存更小,没有对各种技术栈的依赖,减少了 bug 的产生和维护时间的投入。
自主设计的轻量级翻译查词对话框,占用内存小,且可拖动大小,移动位置,固定和全屏窗口,使用起来简单便捷,顺心应手。
自主设计了大量字体图标,零图片使用,不仅减少了 IO 请求,内存占用也更低,还降低了程序业务逻辑和维护成本。
程序代码短小精干,没有那么多花里胡哨的功能;也没有像剥洋葱一样的代码,读源码不会丈二和尚摸不着头脑。
### 快捷键
目前支持 6 个快捷键,为便于记忆,默认预设置 4 个快捷键。
划词翻译开关 Ctrl+Shift+X 「常用,很多时候都不需要开启划词翻译,这时关闭就行;类似"剪切"快捷键,记忆技巧:剪一下开,剪一下关,剪一下开啊,剪一下关。」
打开翻译窗口 Ctrl+Shift+9 「较常用,这是一个全局快捷键,在使用任何软件时,都可以通过这个快捷键召唤翻译小窗口;记忆技巧:久的谐音,长长久久。」(目前 Firefox 不支持全局快捷键)
截图识别翻译 Ctrl+Shift+A 「较常用,对页面局部切图后,识别图片内容,然后进行翻译。」
打开翻译面板 Alt+D 「不常用,一般通过鼠标点击就可以了,但也提供给喜欢使用快捷键人群一个方便;记忆技巧:字母 D 是 Dream 首字母,梦想成真!」
剪贴板内容翻译 「需要自定义启用,从剪贴板获取文本内容进行翻译。」
停止播放声音 「需要自定义启用,有时朗读声音时,想要停止播放,可以通过此快捷键,快速停止播放声音。」
最后,如果这些快捷键和你电脑快捷键冲突了,那就只能自定义符合自己习惯的快捷键了。^_^
## 聚合的词典&翻译
### 在线翻译(国内)
百度翻译 https://fanyi.baidu.com
谷歌翻译 https://translate.google.cn
必应翻译 https://cn.bing.com/translator
有道翻译 https://fanyi.youdao.com
腾讯翻译 https://fanyi.qq.com
搜狗翻译 https://fanyi.sogou.com
阿里翻译 https://translate.alibaba.com
360翻译 https://fanyi.so.com
### 在线翻译(国外)
DeepL翻译 https://www.deepl.com/translator (维基百科:专家认为它比 Google 翻译更准确自然)
### 在线词典(国内)
有道词典 https://www.youdao.com
必应词典 https://cn.bing.com/dict
海词词典 https://dict.cn
金山词典 https://www.iciba.com
欧路词典 https://dict.eudic.net
人人词典 https://www.91dict.com
沪江词典 https://www.hjdict.com
译典词典 https://www.dreye.com.cn
### 在线词典(国外)
朗文词典 https://www.ldoceonline.com
剑桥词典 https://dictionary.cambridge.org
韦氏词典 https://www.merriam-webster.com
柯林斯词典 https://www.collinsdictionary.com
牛津词典 https://www.oxfordlearnersdictionaries.com
自由词典 https://www.thefreedictionary.com
词曲词典 https://www.lexico.com
在线词典 https://www.dictionary.com
词汇词典 https://www.vocabulary.com
语言词典 https://www.wordreference.com
词源词典 https://www.etymonline.com
城市词典 https://www.urbandictionary.com
麦克米伦词典 https://www.macmillandictionary.com
================================================
FILE: release.sh
================================================
#!/bin/bash
DIST=./dist
DIST2=./dist_firefox
# ======== Chrome & Edge ========
# 拷贝
echo "cp to ... $DIST"
mkdir -p $DIST
\cp -af ./src/ $DIST
# 打包 (Chrome Edge)
sed -i "" -e "s/const isDebug = true/const isDebug = false/g" $DIST/js/common.js
zip -rq dist.zip $DIST
# ======== Firefox ========
# 拷贝
echo "cp to ... $DIST2"
mkdir -p $DIST2
\cp -af ./src/ $DIST2
# 替换
sed -i "" -e "s/const isDebug = true/const isDebug = false/g" $DIST2/js/common.js
sed -i "" -e '16,21d' $DIST2/css/content.css # 清理 CSS 样式
sed -i "" -e '/"tts",/d' $DIST2/manifest.json
sed -i "" -e '/"global": true,/d' $DIST2/manifest.json
# 打包 (Firefox)
zip -rq dist_firefox.zip $DIST2
# 清理目录
rm -rf $DIST
rm -rf $DIST2
================================================
FILE: src/conf/conf.json
================================================
{
"setting": {
"scribble": "direct",
"excludeChinese": "",
"excludeSymbol": "",
"excludeNumber": "",
"position": "fixed",
"allowSelect": "",
"autoCopy": "",
"autoPaste": "",
"autoChange": "",
"autoWords": "",
"cutHumpName": "on",
"translateList": [
"baidu",
"google",
"youdao"
],
"translateTTSList": [],
"localSoundReplace": "",
"translateOCR": "",
"ocrType": "auto",
"translateThin": "",
"hideOriginal": "",
"autoLanguage": "on",
"autoConfirm": "on",
"dictionaryList": [
"youdao",
"bing",
"longman"
],
"dictionarySoundList": [],
"dictionaryReader": "us",
"searchList": [
"百度搜索",
"谷歌搜索",
"必应搜索",
"360搜索",
"搜狗搜索",
"谷歌图片",
"必应图片",
"百度图片",
"维基百科",
"百度百科"
],
"searchMenus": [],
"searchSide": [],
"ttsConf": {}
},
"translateList": {
"baidu": "百度翻译",
"google": "谷歌翻译",
"youdao": "有道翻译",
"bing": "必应翻译",
"qq": "腾讯翻译",
"alibaba": "阿里翻译",
"sogou": "搜狗翻译",
"so": "360翻译",
"deepl": "DeepL翻译"
},
"translateTTSList": {
"baidu": "百度朗读",
"google": "谷歌朗读",
"youdao": "有道朗读",
"bing": "必应朗读",
"qq": "腾讯朗读",
"sogou": "搜狗朗读",
"frdic": "欧路朗读",
"local": "本机朗读"
},
"dictionaryList": {
"youdao": "有道词典",
"bing": "必应词典",
"dictcn": "海词词典",
"iciba": "金山词典",
"eudic": "欧路词典",
"rrdict": "人人词典",
"hjdict": "沪江词典",
"dreye": "译典词典",
"longman": "朗文词典",
"cambridge": "剑桥词典",
"merriam": "韦氏词典",
"collins": "柯林斯词典",
"oxford": "牛津词典",
"thefree": "自由词典",
"lexico": "词曲词典",
"dictionary": "在线词典",
"vocabulary": "词汇词典",
"wordreference": "语言词典",
"etymonline": "词源词典",
"urban": "城市词典",
"macmillan": "麦克米伦词典"
},
"dictionaryCSS": [
"longman"
],
"dictionarySoundExcluded": [
"etymonline",
"urban"
],
"ttsList": {
"ara": "ar-SA",
"cs": "cs-CZ",
"dan": "da-DK",
"de": "de-DE",
"el": "el-GR",
"en": "en-US",
"spa": "es-ES",
"fin": "fi-FI",
"frn": "fr-CA",
"fra": "fr-FR",
"heb": "he-IL",
"hi": "hi-IN",
"hu": "hu-HU",
"id": "id-ID",
"it": "it-IT",
"jp": "ja-JP",
"kor": "ko-KR",
"nor": "nb-NO",
"nl": "nl-NL",
"pl": "pl-PL",
"pot": "pt-BR",
"pt": "pt-PT",
"rom": "ro-RO",
"ru": "ru-RU",
"sk": "sk-SK",
"swe": "sv-SE",
"th": "th-TH",
"tr": "tr-TR",
"zh": "zh-CN",
"yue": "zh-HK",
"cht": "zh-TW"
},
"dialogConf": {
"width": 500,
"height": 470,
"source": "auto",
"target": "zh",
"action": "translate"
}
}
================================================
FILE: src/conf/langSpeak.json
================================================
{
"ar-AE": {
"zhName": "阿拉伯语(阿拉伯联合酋长国)",
"enName": "Arabic(United Arab Emirates)"
},
"ar-BH": {
"zhName": "阿拉伯语(巴林)",
"enName": "Arabic(Bahrain)"
},
"ar-EG": {
"zhName": "阿拉伯语(埃及)",
"enName": "Arabic(Egypt)"
},
"ar-IQ": {
"zhName": "阿拉伯语(伊拉克)",
"enName": "Arabic(Iraq)"
},
"ar-JO": {
"zhName": "阿拉伯语(约旦)",
"enName": "Arabic(Jordan)"
},
"ar-KW": {
"zhName": "阿拉伯语(科威特)",
"enName": "Arabic(Kuwait)"
},
"ar-LB": {
"zhName": "阿拉伯语(黎巴嫩)",
"enName": "Arabic(Lebanon)"
},
"ar-OM": {
"zhName": "阿拉伯语(阿曼)",
"enName": "Arabic(Oman)"
},
"ar-QA": {
"zhName": "阿拉伯语(卡塔尔)",
"enName": "Arabic(Qatar)"
},
"ar-SA": {
"zhName": "阿拉伯语(沙特阿拉伯)",
"enName": "Arabic(Saudi Arabia)"
},
"ar-SY": {
"zhName": "阿拉伯语(叙利亚)",
"enName": "Arabic(Syria)"
},
"bg-BG": {
"zhName": "保加利亚语(保加利亚)",
"enName": "Bulgarian(Bulgaria)"
},
"ca-ES": {
"zhName": "加泰罗尼亚语(西班牙)",
"enName": "Catalan(Spain)"
},
"cs-CZ": {
"zhName": "捷克语(捷克共和国)",
"enName": "Czech(Czech Republic)"
},
"da-DK": {
"zhName": "丹麦语(丹麦)",
"enName": "Danish(Denmark)"
},
"de-AT": {
"zhName": "德语(奥地利)",
"enName": "German(Austria)"
},
"de-CH": {
"zhName": "德语(瑞士)",
"enName": "German(Switzerland)"
},
"de-DE": {
"zhName": "德语(德国)",
"enName": "German(Germany)"
},
"el-GR": {
"zhName": "希腊语(希腊)",
"enName": "Greek(Greece)"
},
"en": {
"zhName": "英语",
"enName": "English"
},
"en-AU": {
"zhName": "英语(澳大利亚)",
"enName": "English(Australia)"
},
"en-CA": {
"zhName": "英语(加拿大)",
"enName": "English(Canada)"
},
"en-GB": {
"zhName": "英语(英国)",
"enName": "English(United Kingdom)"
},
"en-HK": {
"zhName": "英语(香港)",
"enName": "English(Hong Kong)"
},
"en-IE": {
"zhName": "英语(爱尔兰)",
"enName": "English(Ireland)"
},
"en-IN": {
"zhName": "英语(印度)",
"enName": "English(India)"
},
"en-NZ": {
"zhName": "英语(新西兰)",
"enName": "English(New Zealand)"
},
"en-PH": {
"zhName": "英语(菲律宾)",
"enName": "English(Philippines)"
},
"en-SG": {
"zhName": "英语(新加坡)",
"enName": "English(Singapore)"
},
"en-US": {
"zhName": "英语(美国)",
"enName": "English(United States)"
},
"en-ZA": {
"zhName": "英语(南非)",
"enName": "English(South Africa)"
},
"es": {
"zhName": "西班牙语",
"enName": "Spanish"
},
"es-AR": {
"zhName": "西班牙语(阿根廷)",
"enName": "Spanish(Argentina)"
},
"es-BO": {
"zhName": "西班牙语(玻利维亚)",
"enName": "Spanish(Bolivia)"
},
"es-CL": {
"zhName": "西班牙语(智利)",
"enName": "Spanish(Chile)"
},
"es-CO": {
"zhName": "西班牙语(哥伦比亚)",
"enName": "Spanish(Colombia)"
},
"es-CR": {
"zhName": "西班牙语(哥斯达黎加)",
"enName": "Spanish(Costa Rica)"
},
"es-CU": {
"zhName": "西班牙语(古巴)",
"enName": "Spanish(Cuba)"
},
"es-DO": {
"zhName": "西班牙语(多米尼加共和国)",
"enName": "Spanish(Dominican Republic)"
},
"es-EC": {
"zhName": "西班牙语(厄瓜多尔)",
"enName": "Spanish(Ecuador)"
},
"es-ES": {
"zhName": "西班牙语(西班牙)",
"enName": "Spanish(Spain)"
},
"es-GT": {
"zhName": "西班牙语(危地马拉)",
"enName": "Spanish(Guatemala)"
},
"es-HN": {
"zhName": "西班牙语(洪都拉斯)",
"enName": "Spanish(Honduras)"
},
"es-MX": {
"zhName": "西班牙语(墨西哥)",
"enName": "Spanish(Mexico)"
},
"es-NI": {
"zhName": "西班牙语(尼加拉瓜)",
"enName": "Spanish(Nicaragua)"
},
"es-PA": {
"zhName": "西班牙语(巴拿马)",
"enName": "Spanish(Panama)"
},
"es-PE": {
"zhName": "西班牙语(秘鲁)",
"enName": "Spanish(Peru)"
},
"es-PR": {
"zhName": "西班牙语(波多黎各)",
"enName": "Spanish(Puerto Rico)"
},
"es-PY": {
"zhName": "西班牙语(巴拉圭)",
"enName": "Spanish(Paraguay)"
},
"es-SV": {
"zhName": "西班牙语(萨尔瓦多)",
"enName": "Spanish(El Salvador)"
},
"es-US": {
"zhName": "西班牙语(美国)",
"enName": "Spanish(USA)"
},
"es-UY": {
"zhName": "西班牙语(乌拉圭)",
"enName": "Spanish(Uruguay)"
},
"es-VE": {
"zhName": "西班牙语(委内瑞拉)",
"enName": "Spanish(Venezuela)"
},
"et-EE": {
"zhName": "爱沙尼亚语(爱沙尼亚)",
"enName": "Estonian(Estonia)"
},
"fi-FI": {
"zhName": "芬兰语(芬兰)",
"enName": "Finnish(Finland)"
},
"fr-CA": {
"zhName": "法语(加拿大)",
"enName": "French(Canada)"
},
"fr-CH": {
"zhName": "法语(瑞士)",
"enName": "French(Switzerland)"
},
"fr-FR": {
"zhName": "法语(法国)",
"enName": "French(France)"
},
"ga-IE": {
"zhName": "爱尔兰语(爱尔兰)",
"enName": "Irish(Ireland)"
},
"gu-IN": {
"zhName": "古吉拉特语(印度)",
"enName": "Gujarati(Indian)"
},
"he-IL": {
"zhName": "希伯来语(以色列)",
"enName": "Hebrew(Israel)"
},
"hi-IN": {
"zhName": "印地语(印度)",
"enName": "Hindi(India)"
},
"hr-HR": {
"zhName": "克罗地亚语(克罗地亚)",
"enName": "Croatian(Croatia)"
},
"hu-HU": {
"zhName": "匈牙利语(匈牙利)",
"enName": "Hungarian(Hungary)"
},
"id-ID": {
"zhName": "印度尼西亚语(印度尼西亚)",
"enName": "Indonesian(Indonesia)"
},
"it-IT": {
"zhName": "意大利语(意大利)",
"enName": "Italian(Italy)"
},
"ja-JP": {
"zhName": "日语(日本)",
"enName": "Japanese(Japan)"
},
"ko-KR": {
"zhName": "韩语(韩国)",
"enName": "Korean(Korea)"
},
"lt-LT": {
"zhName": "立陶宛语(立陶宛)",
"enName": "Lithuanian(Lithuania)"
},
"lv-LV": {
"zhName": "拉脱维亚语(拉脱维亚)",
"enName": "Latvian(Latvia)"
},
"mr-IN": {
"zhName": "马拉地语(印度)",
"enName": "Marathi(India)"
},
"ms-MY": {
"zhName": "马来语(马来西亚)",
"enName": "Malay(Malaysia)"
},
"mt-MT": {
"zhName": "马耳他语(马耳他)",
"enName": "Maltese(Malta)"
},
"nb-NO": {
"zhName": "挪威语(博克马尔语,挪威)",
"enName": "Norwegian(Bokmål, Norway)"
},
"nl": {
"zhName": "荷兰语",
"enName": "Dutch"
},
"nl-BE": {
"zhName": "荷兰语(比利时)",
"enName": "Dutch(Belgium)"
},
"nl-NL": {
"zhName": "荷兰语(荷兰)",
"enName": "Dutch(Netherlands)"
},
"pl-PL": {
"zhName": "波兰语(波兰)",
"enName": "Polish(Poland)"
},
"pt-BR": {
"zhName": "葡萄牙语(巴西)",
"enName": "Portuguese(Brazil)"
},
"pt-PT": {
"zhName": "葡萄牙语(葡萄牙)",
"enName": "Portuguese(Portugal)"
},
"ro-RO": {
"zhName": "罗马尼亚语(罗马尼亚)",
"enName": "Romanian(Romania)"
},
"ru-RU": {
"zhName": "俄语(俄罗斯)",
"enName": "Russian(Russia)"
},
"sk-SK": {
"zhName": "斯洛伐克语(斯洛伐克)",
"enName": "Slovak(Slovakia)"
},
"sl-SI": {
"zhName": "斯洛文尼亚语(斯洛文尼亚)",
"enName": "Slovenian(Slovenia)"
},
"sv-SE": {
"zhName": "瑞典语(瑞典)",
"enName": "Swedish(Sweden)"
},
"ta-IN": {
"zhName": "泰米尔语(印度)",
"enName": "Tamil(India)"
},
"te-IN": {
"zhName": "泰卢固语(印度)",
"enName": "Telugu(India)"
},
"th-TH": {
"zhName": "泰语(泰国)",
"enName": "Thai(Thailand)"
},
"tr-TR": {
"zhName": "土耳其语(土耳其)",
"enName": "Turkish(Turkey)"
},
"vi-VN": {
"zhName": "越南语(越南)",
"enName": "Vietnamese(Vietnam)"
},
"zh-CN": {
"zhName": "中文(简体)",
"enName": "Chinese(Simplified)"
},
"zh-HK": {
"zhName": "中文(粤语)",
"enName": "Chinese(Cantonese)"
},
"zh-TW": {
"zhName": "中文(繁体)",
"enName": "Chinese(Traditional)"
}
}
================================================
FILE: src/conf/language.json
================================================
{
"auto": {
"zhName": "自动检测",
"enName": "Auto Detect"
},
"zh": {
"zhName": "中文(简体)",
"enName": "Chinese"
},
"en": {
"zhName": "英语",
"enName": "English"
},
"jp": {
"zhName": "日语",
"enName": "Japanese"
},
"th": {
"zhName": "泰语",
"enName": "Thai"
},
"spa": {
"zhName": "西班牙语",
"enName": "Spanish"
},
"ara": {
"zhName": "阿拉伯语",
"enName": "Arabic"
},
"fra": {
"zhName": "法语",
"enName": "French"
},
"kor": {
"zhName": "韩语",
"enName": "Korean"
},
"ru": {
"zhName": "俄语",
"enName": "Russian"
},
"de": {
"zhName": "德语",
"enName": "German"
},
"pt": {
"zhName": "葡萄牙语",
"enName": "Portuguese"
},
"it": {
"zhName": "意大利语",
"enName": "Italian"
},
"el": {
"zhName": "希腊语",
"enName": "Greek"
},
"nl": {
"zhName": "荷兰语",
"enName": "Dutch"
},
"pl": {
"zhName": "波兰语",
"enName": "Polish"
},
"fin": {
"zhName": "芬兰语",
"enName": "Finnish"
},
"cs": {
"zhName": "捷克语",
"enName": "Czech"
},
"bul": {
"zhName": "保加利亚语",
"enName": "Bulgarian"
},
"dan": {
"zhName": "丹麦语",
"enName": "Danish"
},
"est": {
"zhName": "爱沙尼亚语",
"enName": "Estonian"
},
"hu": {
"zhName": "匈牙利语",
"enName": "Hungarian"
},
"rom": {
"zhName": "罗马尼亚语",
"enName": "Romanian"
},
"slo": {
"zhName": "斯洛文尼亚语",
"enName": "Slovenian"
},
"swe": {
"zhName": "瑞典语",
"enName": "Swedish"
},
"vie": {
"zhName": "越南语",
"enName": "Vietnamese"
},
"yue": {
"zhName": "中文(粤语)",
"enName": "Cantonese"
},
"cht": {
"zhName": "中文(繁体)",
"enName": "Traditional Chinese"
},
"wyw": {
"zhName": "中文(文言文)",
"enName": "Classical Chinese"
},
"afr": {
"zhName": "南非荷兰语",
"enName": "Afrikaans"
},
"alb": {
"zhName": "阿尔巴尼亚语",
"enName": "Albanian"
},
"amh": {
"zhName": "阿姆哈拉语",
"enName": "Amharic"
},
"arm": {
"zhName": "亚美尼亚语",
"enName": "Armenian"
},
"asm": {
"zhName": "阿萨姆语",
"enName": "Assamese"
},
"ast": {
"zhName": "阿斯图里亚斯语",
"enName": "Asturian"
},
"aze": {
"zhName": "阿塞拜疆语",
"enName": "Azerbaijani"
},
"baq": {
"zhName": "巴斯克语",
"enName": "Basque"
},
"bel": {
"zhName": "白俄罗斯语",
"enName": "Belarusian"
},
"ben": {
"zhName": "孟加拉语",
"enName": "Bengali"
},
"bos": {
"zhName": "波斯尼亚语",
"enName": "Bosnian"
},
"bur": {
"zhName": "缅甸语",
"enName": "Burmese"
},
"cat": {
"zhName": "加泰罗尼亚语",
"enName": "Catalan"
},
"ceb": {
"zhName": "宿务语",
"enName": "Cebuano"
},
"hrv": {
"zhName": "克罗地亚语",
"enName": "Croatian"
},
"epo": {
"zhName": "世界语",
"enName": "Esperanto"
},
"fao": {
"zhName": "法罗语",
"enName": "Faroese"
},
"fil": {
"zhName": "菲律宾语",
"enName": "Filipino"
},
"glg": {
"zhName": "加利西亚语",
"enName": "Galician"
},
"geo": {
"zhName": "格鲁吉亚语",
"enName": "Georgian"
},
"guj": {
"zhName": "古吉拉特语",
"enName": "Gujarati"
},
"hau": {
"zhName": "豪萨语",
"enName": "Hausa"
},
"heb": {
"zhName": "希伯来语",
"enName": "Hebrew"
},
"hi": {
"zhName": "印地语",
"enName": "Hindi"
},
"ice": {
"zhName": "冰岛语",
"enName": "Icelandic"
},
"ibo": {
"zhName": "伊博语",
"enName": "Igbo"
},
"id": {
"zhName": "印尼语",
"enName": "Indonesian"
},
"gle": {
"zhName": "爱尔兰语",
"enName": "Irish"
},
"kan": {
"zhName": "卡纳达语",
"enName": "Kannada"
},
"kaz": {
"zhName": "哈萨克语",
"enName": "Kazakh"
},
"kli": {
"zhName": "克林贡语",
"enName": "Klingon"
},
"kur": {
"zhName": "库尔德语",
"enName": "Kurdish"
},
"lao": {
"zhName": "老挝语",
"enName": "Lao"
},
"lat": {
"zhName": "拉丁语",
"enName": "Latin"
},
"lav": {
"zhName": "拉脱维亚语",
"enName": "Latvian"
},
"lit": {
"zhName": "立陶宛语",
"enName": "Lithuanian"
},
"ltz": {
"zhName": "卢森堡语",
"enName": "Luxembourgish"
},
"mac": {
"zhName": "马其顿语",
"enName": "Macedonian"
},
"mg": {
"zhName": "马拉加斯语",
"enName": "Malagasy"
},
"may": {
"zhName": "马来语",
"enName": "Malay"
},
"mal": {
"zhName": "马拉雅拉姆语",
"enName": "Malayalam"
},
"mlt": {
"zhName": "马耳他语",
"enName": "Maltese"
},
"mar": {
"zhName": "马拉地语",
"enName": "Marathi"
},
"nep": {
"zhName": "尼泊尔语",
"enName": "Nepali"
},
"nno": {
"zhName": "新挪威语",
"enName": "Nynorsk"
},
"per": {
"zhName": "波斯语",
"enName": "Persian"
},
"srd": {
"zhName": "萨丁尼亚语",
"enName": "Sardinian"
},
"srp": {
"zhName": "塞尔维亚语(拉丁文)",
"enName": "Serbian"
},
"sin": {
"zhName": "僧伽罗语",
"enName": "Sinhala"
},
"sk": {
"zhName": "斯洛伐克语",
"enName": "Slovak"
},
"som": {
"zhName": "索马里语",
"enName": "Somali"
},
"swa": {
"zhName": "斯瓦希里语",
"enName": "Swahili"
},
"tgl": {
"zhName": "他加禄语",
"enName": "Tagalog"
},
"tgk": {
"zhName": "塔吉克语",
"enName": "Tajik"
},
"tam": {
"zhName": "泰米尔语",
"enName": "Tamil"
},
"tat": {
"zhName": "鞑靼语",
"enName": "Tatar"
},
"tel": {
"zhName": "泰卢固语",
"enName": "Telugu"
},
"tr": {
"zhName": "土耳其语",
"enName": "Turkish"
},
"tuk": {
"zhName": "土库曼语",
"enName": "Turkmen"
},
"ukr": {
"zhName": "乌克兰语",
"enName": "Ukrainian"
},
"urd": {
"zhName": "乌尔都语",
"enName": "Urdu"
},
"uzb": {
"zhName": "乌兹别克语",
"enName": "Uzbek"
},
"oci": {
"zhName": "奥克语",
"enName": "Occitan"
},
"kir": {
"zhName": "吉尔吉斯语",
"enName": "Kyrgyz"
},
"pus": {
"zhName": "普什图语",
"enName": "Pashto"
},
"hkm": {
"zhName": "高棉语",
"enName": "Khmer"
},
"ht": {
"zhName": "海地语",
"enName": "Haitian Creole"
},
"nob": {
"zhName": "书面挪威语",
"enName": "Bokmål"
},
"pan": {
"zhName": "旁遮普语",
"enName": "Punjabi"
},
"arq": {
"zhName": "阿尔及利亚阿拉伯语",
"enName": "Algerian Arabic"
},
"bis": {
"zhName": "比斯拉马语",
"enName": "Bislama"
},
"frn": {
"zhName": "加拿大法语",
"enName": "Canadian French"
},
"hak": {
"zhName": "哈卡钦语",
"enName": "Hakha Chin"
},
"hup": {
"zhName": "胡帕语",
"enName": "Hupa"
},
"ing": {
"zhName": "印古什语",
"enName": "Ingush"
},
"lag": {
"zhName": "拉特加莱语",
"enName": "Latgalian"
},
"mau": {
"zhName": "毛里求斯克里奥尔语",
"enName": "Mauritian Creole"
},
"mot": {
"zhName": "黑山语",
"enName": "Montenegrin"
},
"pot": {
"zhName": "巴西葡萄牙语",
"enName": "Brazilian Portuguese"
},
"ruy": {
"zhName": "卢森尼亚语",
"enName": "Rusyn"
},
"sec": {
"zhName": "塞尔维亚-克罗地亚语",
"enName": "Serbo-Croatian"
},
"sil": {
"zhName": "西里西亚语",
"enName": "Silesian"
},
"tua": {
"zhName": "突尼斯阿拉伯语",
"enName": "Tunisian Arabic"
},
"ach": {
"zhName": "亚齐语",
"enName": "Achinese"
},
"aka": {
"zhName": "阿肯语",
"enName": "Akan"
},
"arg": {
"zhName": "阿拉贡语",
"enName": "Aragonese"
},
"aym": {
"zhName": "艾马拉语",
"enName": "Aymara"
},
"bal": {
"zhName": "俾路支语",
"enName": "Baluchi"
},
"bak": {
"zhName": "巴什基尔语",
"enName": "Bashkir"
},
"bem": {
"zhName": "本巴语",
"enName": "Bemba"
},
"ber": {
"zhName": "柏柏尔语",
"enName": "Berber languages"
},
"bho": {
"zhName": "博杰普尔语",
"enName": "Bhojpuri"
},
"bli": {
"zhName": "比林语",
"enName": "Blin"
},
"bre": {
"zhName": "布列塔尼语",
"enName": "Breton"
},
"chr": {
"zhName": "切罗基语",
"enName": "Cherokee"
},
"nya": {
"zhName": "齐切瓦语",
"enName": "Chichewa"
},
"chv": {
"zhName": "楚瓦什语",
"enName": "Chuvash"
},
"cor": {
"zhName": "康瓦尔语",
"enName": "Cornish"
},
"cos": {
"zhName": "科西嘉语",
"enName": "Corsican"
},
"cre": {
"zhName": "克里克语",
"enName": "Creek"
},
"cri": {
"zhName": "克里米亚鞑靼语",
"enName": "Crimean Tatar"
},
"div": {
"zhName": "迪维希语",
"enName": "Divehi"
},
"eno": {
"zhName": "古英语",
"enName": "Old English"
},
"frm": {
"zhName": "中古法语",
"enName": "Middle French"
},
"fri": {
"zhName": "弗留利语",
"enName": "Friulian"
},
"ful": {
"zhName": "富拉尼语",
"enName": "Fulani"
},
"gla": {
"zhName": "盖尔语",
"enName": "Gaelic"
},
"lug": {
"zhName": "卢干达语",
"enName": "Luganda"
},
"gra": {
"zhName": "古希腊语",
"enName": "Ancient Greek"
},
"grn": {
"zhName": "瓜拉尼语",
"enName": "Guarani"
},
"haw": {
"zhName": "夏威夷语",
"enName": "Hawaiian"
},
"hil": {
"zhName": "希利盖农语",
"enName": "Hiligaynon"
},
"ido": {
"zhName": "伊多语",
"enName": "Ido"
},
"ina": {
"zhName": "因特语",
"enName": "Interlingua"
},
"iku": {
"zhName": "伊努克提图特语",
"enName": "Inuktitut"
},
"jav": {
"zhName": "爪哇语",
"enName": "Javanese"
},
"kab": {
"zhName": "卡拜尔语",
"enName": "Kabyle"
},
"kal": {
"zhName": "格陵兰语",
"enName": "Kalaallisut"
},
"kau": {
"zhName": "卡努里语",
"enName": "Kanuri"
},
"kas": {
"zhName": "克什米尔语",
"enName": "Kashmiri"
},
"kah": {
"zhName": "卡舒比语",
"enName": "Kashubian"
},
"kin": {
"zhName": "卢旺达语",
"enName": "Kinyarwanda"
},
"kon": {
"zhName": "刚果语",
"enName": "Kongo"
},
"kok": {
"zhName": "孔卡尼语",
"enName": "Konkani"
},
"lim": {
"zhName": "林堡语",
"enName": "Limburgish"
},
"lin": {
"zhName": "林加拉语",
"enName": "Lingala"
},
"loj": {
"zhName": "逻辑语",
"enName": "Lojban"
},
"log": {
"zhName": "低地德语",
"enName": "Low German"
},
"los": {
"zhName": "下索布语",
"enName": "Lower Sorbian"
},
"mai": {
"zhName": "迈蒂利语",
"enName": "Maithili"
},
"glv": {
"zhName": "曼克斯语",
"enName": "Manx"
},
"mao": {
"zhName": "毛利语",
"enName": "Maori"
},
"mah": {
"zhName": "马绍尔语",
"enName": "Marshallese"
},
"nbl": {
"zhName": "南恩德贝莱语",
"enName": "Southern Ndebele"
},
"nea": {
"zhName": "那不勒斯语",
"enName": "Neapolitan"
},
"nqo": {
"zhName": "西非书面语",
"enName": "N'Ko"
},
"sme": {
"zhName": "北方萨米语",
"enName": "Northern Sami"
},
"nor": {
"zhName": "挪威语",
"enName": "Norwegian"
},
"oji": {
"zhName": "奥杰布瓦语",
"enName": "Ojibwa"
},
"ori": {
"zhName": "奥里亚语",
"enName": "Oriya"
},
"orm": {
"zhName": "奥罗莫语",
"enName": "Oromo"
},
"oss": {
"zhName": "奥塞梯语",
"enName": "Ossetian"
},
"pam": {
"zhName": "邦板牙语",
"enName": "Pampanga"
},
"pap": {
"zhName": "帕皮阿门托语",
"enName": "Papiamento"
},
"ped": {
"zhName": "北索托语",
"enName": "Northern Sotho"
},
"que": {
"zhName": "克丘亚语",
"enName": "Quechua"
},
"roh": {
"zhName": "罗曼什语",
"enName": "Romansh"
},
"ro": {
"zhName": "罗姆语",
"enName": "Romany"
},
"sm": {
"zhName": "萨摩亚语",
"enName": "Samoan"
},
"san": {
"zhName": "梵语",
"enName": "Sanskrit"
},
"sco": {
"zhName": "苏格兰语",
"enName": "Scots"
},
"sha": {
"zhName": "掸语",
"enName": "Shan"
},
"sna": {
"zhName": "修纳语",
"enName": "Shona"
},
"snd": {
"zhName": "信德语",
"enName": "Sindhi"
},
"sol": {
"zhName": "桑海语",
"enName": "Songhai languages"
},
"sot": {
"zhName": "南索托语",
"enName": "Southern Sotho"
},
"syr": {
"zhName": "叙利亚语",
"enName": "Syriac"
},
"tet": {
"zhName": "德顿语",
"enName": "Tetum"
},
"tir": {
"zhName": "提格利尼亚语",
"enName": "Tigrinya"
},
"tso": {
"zhName": "聪加语",
"enName": "Tsonga"
},
"twi": {
"zhName": "契维语",
"enName": "Twi"
},
"ups": {
"zhName": "高地索布语",
"enName": "Upper Sorbian"
},
"ven": {
"zhName": "文达语",
"enName": "Venda"
},
"wln": {
"zhName": "瓦隆语",
"enName": "Walloon"
},
"wel": {
"zhName": "威尔士语",
"enName": "Welsh"
},
"fry": {
"zhName": "西弗里斯语",
"enName": "Western Frisian"
},
"wol": {
"zhName": "沃洛夫语",
"enName": "Wolof"
},
"xho": {
"zhName": "科萨语",
"enName": "Xhosa"
},
"yid": {
"zhName": "意第绪语",
"enName": "Yiddish"
},
"yor": {
"zhName": "约鲁巴语",
"enName": "Yoruba"
},
"zaz": {
"zhName": "扎扎其语",
"enName": "Zaza"
},
"zul": {
"zhName": "祖鲁语",
"enName": "Zulu"
},
"sun": {
"zhName": "巽他语",
"enName": "BasaSunda"
},
"hmn": {
"zhName": "苗语",
"enName": "Hmong"
},
"src": {
"zhName": "塞尔维亚语(西里尔文)",
"enName": "Serb(Cyrillic)"
}
}
================================================
FILE: src/conf/searchText.txt
================================================
百度搜索|https://www.baidu.com/s?wd={0}
谷歌搜索|https://www.google.com/search?q={0}
必应搜索|https://cn.bing.com/search?q={0}
360搜索|https://www.so.com/s?q={0}
搜狗搜索|https://www.sogou.com/web?query={0}
谷歌图片|https://www.google.com/search?q={0}&tbm=isch
必应图片|https://cn.bing.com/images/search?q={0}&form=BESBTB&first=1&tsc=ImageBasicHover&ensearch=1
百度图片|https://image.baidu.com/search/index?tn=baiduimage&fm=result&ie=utf-8&word={0}
维基百科|https://zh.wikipedia.org/w/index.php?search={0}
百度百科|https://baike.baidu.com/search/word?word={0}
必应词典|https://cn.bing.com/dict/search?q={0}
有道词典|https://www.youdao.com/w/eng/{0}
金山词典|https://www.iciba.com/word?w={0}
牛津词典|https://www.oxfordlearnersdictionaries.com/search/english/?q={0}
剑桥词典|https://dictionary.cambridge.org/dictionary/english-chinese-simplified/{0}
格林斯词典|https://www.collinsdictionary.com/search/?dictCode=english&q={0}
豆瓣读书|https://search.douban.com/book/subject_search?search_text={0}
京东搜索|https://search.jd.com/Search?keyword={0}
淘宝搜索|https://s.taobao.com/search?q={0}
MDN搜索|https://developer.mozilla.org/zh-CN/search?q={0}
StackOverflow|https://stackoverflow.com/search?q={0}
================================================
FILE: src/css/content.css
================================================
.dmx_unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.dmx_overflow_hidden {
overflow: hidden;
}
div[mx-name="dream-translation"] {
all: initial;
}
@font-face {
font-family: "dmx-icon";
/*src: url('dmx_icon.woff2') format('woff2');*/
src: url('chrome-extension://__MSG_@@extension_id__/css/dmx_icon.woff2') format('woff2');
}
/* Firefox */
@-moz-document url-prefix() {
@font-face {
font-family: "dmx-icon";
src: url('moz-extension://__MSG_@@extension_id__/css/dmx_icon.woff2') format('woff2');
}
}
.dmx-icon {
font-family: "dmx-icon", serif !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
user-select: none;
}
.dmx-icon-crop:before {
content: "\e6c8";
}
.dmx-icon-logo:before {
content: "\e6c7";
}
.dmx-icon-move:before {
content: "\e6c6";
}
.dmx-icon-folder:before {
content: "\e6c4";
}
.dmx-icon-folder-open:before {
content: "\e6c5";
}
.dmx-icon-delete:before {
content: "\e6c2";
}
.dmx-icon-edit:before {
content: "\e6c3";
}
.dmx-icon-export:before {
content: "\e6c0";
}
.dmx-icon-import:before {
content: "\e6c1";
}
.dmx-icon-history:before {
content: "\e6bf";
}
.dmx-icon-oxford:before {
content: "\e6be";
}
.dmx-icon-thefree:before {
content: "\e6bd";
}
.dmx-icon-wikipedia:before {
content: "\e6b0";
}
.dmx-icon-dreye:before {
content: "\e6bc";
}
.dmx-icon-eudic:before {
content: "\e6bb";
}
.dmx-icon-etymonline:before {
content: "\e6b1";
}
.dmx-icon-rrdict:before {
content: "\e6b9";
}
.dmx-icon-lexico:before {
content: "\e6ba";
}
.dmx-icon-urban:before {
content: "\e6b8";
}
.dmx-icon-macmillan:before {
content: "\e6b7";
}
.dmx-icon-vocabulary:before {
content: "\e6b6";
}
.dmx-icon-wordreference:before {
content: "\e6b5";
}
.dmx-icon-collins:before {
content: "\e6b4";
}
.dmx-icon-iciba:before {
content: "\e6b3";
}
.dmx-icon-hjdict:before {
content: "\e6b2";
}
.dmx-icon-dictionary:before {
content: "\e6af";
}
.dmx-icon-so:before {
content: "\e6aa";
}
.dmx-icon-merriam:before {
content: "\e6ab";
}
.dmx-icon-longman:before {
content: "\e6ac";
}
.dmx-icon-cambridge:before {
content: "\e6ad";
}
.dmx-icon-dictcn:before {
content: "\e6ae";
}
.dmx-icon-sound-us:before {
content: "\e674";
}
.dmx-icon-sound-zh:before {
content: "\e675";
}
.dmx-icon-sound-fr:before {
content: "\e676";
}
.dmx-icon-sound-de:before {
content: "\e677";
}
.dmx-icon-sound-th:before {
content: "\e69c";
}
.dmx-icon-sound-ru:before {
content: "\e69d";
}
.dmx-icon-sound-pt:before {
content: "\e69e";
}
.dmx-icon-sound-uk:before {
content: "\e69f";
}
.dmx-icon-sound-jp:before {
content: "\e6a0";
}
.dmx-icon-sound-el:before {
content: "\e6a1";
}
.dmx-icon-sound-cn:before {
content: "\e6a2";
}
.dmx-icon-sound-nl:before {
content: "\e6a3";
}
.dmx-icon-sound-it:before {
content: "\e6a4";
}
.dmx-icon-sound-sp:before {
content: "\e6a5";
}
.dmx-icon-sound-ko:before {
content: "\e6a6";
}
.dmx-icon-sound-ar:before {
content: "\e6a7";
}
.dmx-icon-sound-en:before {
content: "\e6a8";
}
.dmx-icon-sound-pl:before {
content: "\e6a9";
}
.dmx-icon-youdao:before {
content: "\e693";
}
.dmx-icon-sound-square:before {
content: "\e69b";
}
.dmx-icon-sound:before {
content: "\e67a";
}
.dmx-icon-user:before {
content: "\e699";
}
.dmx-icon-angle-double-right:before {
content: "\e696";
}
.dmx-icon-angle-double-left:before {
content: "\e697";
}
.dmx-icon-angle-double-up:before {
content: "\e698";
}
.dmx-icon-angle-double-down:before {
content: "\e69a";
}
.dmx-icon-youdaonote:before {
content: "\e691";
}
.dmx-icon-youtube:before {
content: "\e692";
}
.dmx-icon-windows:before {
content: "\e694";
}
.dmx-icon-yahoo:before {
content: "\e695";
}
.dmx-icon-voice-off:before {
content: "\e68a";
}
.dmx-icon-voice-on:before {
content: "\e68b";
}
.dmx-icon-voice:before {
content: "\e68c";
}
.dmx-icon-video:before {
content: "\e68d";
}
.dmx-icon-vip-crown:before {
content: "\e68e";
}
.dmx-icon-vip-fill:before {
content: "\e68f";
}
.dmx-icon-vip-card:before {
content: "\e690";
}
.dmx-icon-up:before {
content: "\e688";
}
.dmx-icon-up-circle:before {
content: "\e689";
}
.dmx-icon-trash:before {
content: "\e682";
}
.dmx-icon-twitter:before {
content: "\e683";
}
.dmx-icon-tool:before {
content: "\e684";
}
.dmx-icon-telegram:before {
content: "\e685";
}
.dmx-icon-tinder:before {
content: "\e686";
}
.dmx-icon-twitter-circle:before {
content: "\e687";
}
.dmx-icon-switch:before {
content: "\e67f";
}
.dmx-icon-switch-on:before {
content: "\e680";
}
.dmx-icon-switch-off:before {
content: "\e681";
}
.dmx-icon-success:before {
content: "\e67d";
}
.dmx-icon-suse:before {
content: "\e67e";
}
.dmx-icon-star:before {
content: "\e67b";
}
.dmx-icon-stop:before {
content: "\e67c";
}
.dmx-icon-sound-big:before {
content: "\e673";
}
.dmx-icon-sound-line:before {
content: "\e678";
}
.dmx-icon-sound-horn:before {
content: "\e679";
}
.dmx-icon-sogou:before {
content: "\e66d";
}
.dmx-icon-smile:before {
content: "\e66e";
}
.dmx-icon-skull:before {
content: "\e66f";
}
.dmx-icon-search:before {
content: "\e670";
}
.dmx-icon-s:before {
content: "\e671";
}
.dmx-icon-setting:before {
content: "\e672";
}
.dmx-icon-reply:before {
content: "\e668";
}
.dmx-icon-redhat:before {
content: "\e669";
}
.dmx-icon-right:before {
content: "\e66a";
}
.dmx-icon-reload:before {
content: "\e66b";
}
.dmx-icon-refresh:before {
content: "\e66c";
}
.dmx-icon-plus:before {
content: "\e665";
}
.dmx-icon-play:before {
content: "\e666";
}
.dmx-icon-qq:before {
content: "\e667";
}
.dmx-icon-plane-send:before {
content: "\e65c";
}
.dmx-icon-plane-departure:before {
content: "\e65d";
}
.dmx-icon-plane-up:before {
content: "\e65e";
}
.dmx-icon-plane-paper:before {
content: "\e65f";
}
.dmx-icon-plane:before {
content: "\e660";
}
.dmx-icon-plane-fly:before {
content: "\e661";
}
.dmx-icon-plane-up-fill:before {
content: "\e662";
}
.dmx-icon-plane-send-fill:before {
content: "\e663";
}
.dmx-icon-plane-takeoff:before {
content: "\e664";
}
.dmx-icon-picture-fill:before {
content: "\e658";
}
.dmx-icon-pin:before {
content: "\e659";
}
.dmx-icon-pin-left:before {
content: "\e65a";
}
.dmx-icon-picture:before {
content: "\e65b";
}
.dmx-icon-microsoft:before {
content: "\e651";
}
.dmx-icon-m:before {
content: "\e652";
}
.dmx-icon-modx:before {
content: "\e653";
}
.dmx-icon-meetup:before {
content: "\e654";
}
.dmx-icon-mule:before {
content: "\e655";
}
.dmx-icon-music:before {
content: "\e656";
}
.dmx-icon-more:before {
content: "\e657";
}
.dmx-icon-loading:before {
content: "\e650";
}
.dmx-icon-l:before {
content: "\e64a";
}
.dmx-icon-itunes:before {
content: "\e64b";
}
.dmx-icon-info:before {
content: "\e64c";
}
.dmx-icon-laugh:before {
content: "\e64d";
}
.dmx-icon-left:before {
content: "\e64e";
}
.dmx-icon-list:before {
content: "\e64f";
}
.dmx-icon-help:before {
content: "\e647";
}
.dmx-icon-horn:before {
content: "\e648";
}
.dmx-icon-home:before {
content: "\e649";
}
.dmx-icon-headset:before {
content: "\e644";
}
.dmx-icon-headset-c:before {
content: "\e645";
}
.dmx-icon-heart:before {
content: "\e646";
}
.dmx-icon-good:before {
content: "\e642";
}
.dmx-icon-google:before {
content: "\e643";
}
.dmx-icon-fullscreen-exit:before {
content: "\e63e";
}
.dmx-icon-fullscreen:before {
content: "\e63f";
}
.dmx-icon-fan:before {
content: "\e640";
}
.dmx-icon-flag:before {
content: "\e641";
}
.dmx-icon-eye-left:before {
content: "\e639";
}
.dmx-icon-error:before {
content: "\e63a";
}
.dmx-icon-eye-right:before {
content: "\e63b";
}
.dmx-icon-exchange:before {
content: "\e63c";
}
.dmx-icon-edge:before {
content: "\e63d";
}
.dmx-icon-down:before {
content: "\e634";
}
.dmx-icon-diaspora:before {
content: "\e635";
}
.dmx-icon-devil:before {
content: "\e636";
}
.dmx-icon-degree:before {
content: "\e637";
}
.dmx-icon-d:before {
content: "\e638";
}
.dmx-icon-close:before {
content: "\e62e";
}
.dmx-icon-circle:before {
content: "\e62f";
}
.dmx-icon-cry-fill:before {
content: "\e630";
}
.dmx-icon-copy:before {
content: "\e631";
}
.dmx-icon-cry:before {
content: "\e632";
}
.dmx-icon-close-bg:before {
content: "\e633";
}
.dmx-icon-bing:before {
content: "\e62b";
}
.dmx-icon-bell:before {
content: "\e62c";
}
.dmx-icon-baidu:before {
content: "\e62d";
}
.dmx-icon-arrow-right:before {
content: "\e625";
}
.dmx-icon-arrow-left:before {
content: "\e626";
}
.dmx-icon-arrow-up:before {
content: "\e627";
}
.dmx-icon-arrow-down:before {
content: "\e628";
}
.dmx-icon-arrow-thin-down:before {
content: "\e629";
}
.dmx-icon-arrow-thin-up:before {
content: "\e62a";
}
.dmx-icon-android:before {
content: "\e622";
}
.dmx-icon-apple:before {
content: "\e623";
}
.dmx-icon-angular:before {
content: "\e624";
}
.dmx-icon-alibaba:before {
content: "\e621";
}
================================================
FILE: src/css/dmx_dialog.css
================================================
p, h1, h2, h3, h4, h5, h6, ul, li, ol, dl, dt, dd {
margin: 0;
padding: 0;
list-style: none;
}
h1, h2, h3, h4, h5, h6 {
font-size: 18px;
}
blockquote {
margin: 10px 0;
padding-left: 5px;
border-left: 6px solid #e4dfca;
}
.dmx_unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.dmx_overflow_hidden {
overflow: hidden;
}
.dmx_hide {
display: none;
}
.dmx_show {
display: block;
}
#dmx_mouse_icon, #dmx_crop_bg, #dmx_crop_fg {
display: none;
position: fixed;
top: 0;
left: 0;
}
#dmx_mouse_icon {
z-index: 999999999999;
font-family: "dmx-icon", serif !important;
width: 32px;
height: 32px;
font-size: 18px;
font-style: normal;
background: #4395ff;
border: 1px solid #3589ff;
box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);
border-radius: 50%;
color: #fff;
box-sizing: border-box;
justify-content: center;
align-items: center;
cursor: pointer;
transition: transform 0.25s ease;
}
#dmx_mouse_icon:before {
content: "\e661";
}
#dmx_mouse_icon:hover {
background: linear-gradient(#FF416C, #FF4B2B);
border: 1px solid #FF416C;
}
#dmx_mouse_icon:hover:before {
transform: rotate(-30deg);
}
#dmx_crop_bg {
z-index: 9999999999998;
width: 100%;
height: 100%;
background: #000;
opacity: .2;
cursor: crosshair;
}
#dmx_crop_fg {
z-index: 9999999999999;
width: 1px;
height: 1px;
background: #fff;
opacity: .7;
border: 1px dashed #000;
}
#dmx_dialog {
font-family: Roboto, 'Segoe UI', Arial, 'Microsoft Yahei', sans-serif;
/*font-family: Helvetica Neue, PingFang SC, Microsoft YaHei, sans-serif;*/
all: initial;
position: fixed;
z-index: 999999999998;
width: 500px;
height: 500px;
/*overflow: hidden;*/
background: #f5f8fa;
border: 1px solid #cfd0d2;
border-radius: 6px;
/*top: calc(50% - 250px);*/
/*left: calc(50% - 250px);*/
right: 0;
bottom: 0;
box-sizing: border-box;
box-shadow: 0 0 12px -2px rgba(32, 32, 32, .5);
display: none;
}
#dmx_dialog.dmx_keep_right {
height: auto;
top: 0;
right: 0;
bottom: 0;
}
#dmx_dialog.dmx_popup {
top: 0;
left: 0;
width: 520px;
height: 500px;
border: 0;
border-radius: 0;
}
#dmx_dialog.dmx_popup.fullscreen {
width: 100%;
height: 100%;
min-width: 450px;
min-height: 300px;
}
#dmx_dialog_resize_n {
cursor: ns-resize;
position: absolute;
z-index: 999999999999;
left: 0;
top: -5px;
width: 100%;
height: 8px
}
#dmx_dialog_resize_e {
cursor: ew-resize;
position: absolute;
z-index: 999999999999;
right: -7px;
top: 0;
width: 8px;
height: 100%
}
#dmx_dialog_resize_s {
cursor: ns-resize;
position: absolute;
z-index: 999999999999;
left: 0;
bottom: -5px;
width: 100%;
height: 8px
}
#dmx_dialog_resize_w {
cursor: ew-resize;
position: absolute;
z-index: 999999999999;
left: -5px;
top: 0;
width: 8px;
height: 100%
}
#dmx_dialog_resize_nw {
cursor: nwse-resize;
position: absolute;
z-index: 999999999999;
left: -5px;
top: -5px;
width: 17px;
height: 17px;
}
#dmx_dialog_resize_ne {
cursor: nesw-resize;
position: absolute;
z-index: 999999999999;
right: -5px;
top: -5px;
width: 10px;
height: 10px
}
#dmx_dialog_resize_sw {
cursor: nesw-resize;
position: absolute;
z-index: 999999999999;
left: -5px;
bottom: -5px;
width: 10px;
height: 10px
}
#dmx_dialog_resize_se {
cursor: nwse-resize;
position: absolute;
z-index: 999999999999;
right: -5px;
bottom: -5px;
width: 17px;
height: 17px;
}
#dmx_dialog_title {
height: 32px;
line-height: 32px;
padding: 0 10px;
background: #f5f8fa;
border-bottom: 1px solid #cfd0d2;
border-radius: 6px 6px 0 0;
cursor: move;
}
#dmx_dialog_title .dmx-icon {
cursor: pointer;
color: #232B33;
}
#dmx_dialog_title .dmx-icon:hover {
opacity: 0.7;
}
#dmx_dialog_title .dmx_left {
float: left;
}
#dmx_dialog_title .dmx_left > div {
float: left;
margin-right: 8px;
}
#dmx_dialog_title #dmx_close .dmx-icon {
color: #FC4A4A;
font-size: 18px;
}
#dmx_dialog_title #dmx_pin:before {
content: "\e65a";
padding: 2px;
font-size: 18px;
}
#dmx_dialog_title #dmx_pin.active:before {
content: "\e659";
background: #e0e3e6;
border-radius: 2px;
}
#dmx_dialog_title #dmx_fullscreen:before {
content: "\e63f";
}
#dmx_dialog_title #dmx_fullscreen.active:before {
content: "\e63e";
}
#dmx_dialog_title .dmx-icon-voice {
font-size: 18px;
}
#dmx_dialog_title .dmx-icon-heart {
color: #FC4A4A;
}
#dmx_dialog_title #dmx_history {
margin: 2px 0 0 15px;
border: 1px solid gray;
border-radius: 30px;
height: 26px;
overflow: hidden;
}
#dmx_dialog_title #dmx_history .dmx-icon {
display: block;
float: left;
margin: 0;
line-height: 26px;
}
#dmx_dialog_title #dmx_history .dmx-icon:hover {
background: #252525;
color: #fff;
}
#dmx_dialog_title #dmx_history .dmx-icon-left {
padding: 0 8px;
border-right: 1px solid gray;
}
#dmx_dialog_title #dmx_history .dmx-icon-right {
padding: 0 8px;
}
#dmx_dialog_title #dmx_history .dmx-icon.disabled {
color: #9EA0A3;
background: #F3F6F9;
opacity: 0.8;
}
#dmx_dialog.dmx_popup #dmx_history {
margin-left: 0;
}
#dmx_dialog_title #dmx_navigate {
margin: 1px 0 0 15px;
}
#dmx_dialog_title #dmx_navigate u {
float: left;
display: block;
background: #F3F6F9;
border: 1px solid #C4C5C8;
border-radius: 5px 5px 0 0;
padding: 0 8px;
margin-right: 5px;
height: 30px;
line-height: 30px;
overflow: hidden;
font-size: 15px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
}
#dmx_dialog_title #dmx_navigate u.active {
border-bottom: 1px solid #F3F6F9;
opacity: 1;
}
#dmx_dialog_title .dmx_right {
float: right;
}
#dmx_dialog_title .dmx_right > div {
float: right;
margin-left: 8px;
}
#dmx_dialog_content {
box-sizing: border-box;
position: relative;
margin-top: 2px;
width: 100%;
height: calc(100% - 35px);
overflow: auto;
}
#dmx_iframe {
box-sizing: border-box;
width: 100%;
height: calc(100% - 6px);
border: none;
}
#dmx_head {
/*position: sticky;*/
box-sizing: border-box;
width: 100%;
height: 45px;
padding: 2px 10px 5px;
background: #F3F6F9;
border-bottom: 1px solid rgba(32, 33, 36, 0.28);
box-shadow: 0 1px 4px 0 rgba(32, 33, 36, 0.18);
}
.dmx_content {
box-sizing: border-box;
position: absolute;
left: 0;
top: 45px;
width: 100%;
height: calc(100% - 45px);
overflow: auto;
}
.dmx_main {
margin: 0 auto;
padding: 10px;
min-width: 100px;
font-size: 15px;
line-height: 1.8;
}
.dmx_main_trans {
padding-bottom: 0;
}
.dmx_main_search {
padding: 10px 0 0 10px;
}
#dmx_dialog_left {
position: absolute;
bottom: 0;
left: -37px;
width: 37px;
}
#dmx_dialog_left a {
display: block;
width: 32px;
height: 32px;
line-height: 30px;
font-size: 16px;
background: #4395ff;
border: 1px solid #3589ff;
box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);
border-radius: 50%;
color: #fff;
box-sizing: border-box;
text-align: center;
margin-top: 5px;
}
.dmx_loading {
border: 3px solid #bdc3c7;
border-top: 3px solid #2c3e50;
border-bottom: 3px solid #2c3e50;
border-radius: 50%;
display: block;
width: 16px;
height: 16px;
animation: spin 1.2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
/*background-color: #E2E4E6;*/
}
::-webkit-scrollbar-thumb {
background-color: #bdc3c7;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #9da5ac;
}
::-webkit-scrollbar-thumb:active {
background-color: #2c3e50;
}
p, h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: #333;
}
a:hover {
color: #1a73e8;
}
.fx:after {
display: block;
content: '';
clear: both;
}
.mt-1 {
margin-top: 10px;
}
.mb-1 {
margin-bottom: 10px;
}
/* ==================== translate ==================== */
#translate_input {
background: #fff;
color: #4e5053;
min-height: 26px;
line-height: 1.6;
padding: 5px 10px;
outline: none;
overflow: auto;
}
#translate_input:focus {
border-color: #409eff;
}
#translate_input.big {
min-height: 52px;
}
.language_box {
position: relative;
}
.language_box > div {
float: left;
}
.language_button {
background: #ffffff;
border: 1px solid #cfd0d2;
border-radius: 8px;
margin-top: 10px;
padding: 2px 10px;
min-width: 80px;
height: 28px;
line-height: 28px;
text-align: center;
user-select: none;
cursor: pointer;
}
.language_button:hover {
color: #1a73e8;
box-shadow: -2px -2px 2px 0 #d0d7de;
}
.language_button:after {
font-size: 8px;
margin-left: 6px;
content: "\e629";
}
.language_button.active {
z-index: 999;
position: relative;
border-radius: 8px 8px 0 0;
border-bottom: 1px solid #fff;
box-shadow: -2px 0 0 0 #d0d7de;
height: 31px;
}
.language_button.active:after {
content: "\e62a";
}
#language_dropdown {
z-index: 998;
position: absolute;
left: 0;
top: 46px;
box-sizing: border-box;
width: 100%;
background: #fff;
border: 1px solid #cfd0d2;
border-radius: 0 8px 8px 8px;
padding: 10px 10px 5px 10px;
margin-bottom: 3px;
box-shadow: 0 2px 2px 2px #d0d7de;
display: none;
}
#language_dropdown.dropdown_target {
border-radius: 8px;
}
#language_dropdown.dropdown_target u:first-child {
display: none;
}
#language_dropdown u {
float: left;
display: block;
text-decoration: none;
background: #f5f5f5;
border: 1px solid #d3d3d3;
border-radius: 5px;
color: rgba(0, 0, 0, 0.87);
padding: 2px 5px;
margin: 0 5px 5px 0;
cursor: pointer;
}
#language_dropdown u:hover {
background: #414345;
border: 1px solid #232526;
color: #fff;
}
#language_dropdown u.active {
background: #e8f0fe;
border: 1px solid #185abc;
color: #185abc;
}
#language_dropdown u.disabled {
background: #f5f5f5;
border: 1px solid #d3d3d3;
color: #ddd;
cursor: not-allowed;
}
#language_exchange {
margin-top: 10px;
padding: 2px 8px;
cursor: pointer;
}
#language_exchange.disabled {
color: #ddd;
cursor: not-allowed;
}
#translate_crop {
float: right;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
border-radius: 8px;
height: 28px;
line-height: 28px;
padding: 2px 7px;
margin: 10px 10px 0 0;
cursor: pointer;
}
#translate_button {
float: right;
background: #4395ff;
border: 1px solid #3589ff;
border-radius: 8px;
color: #fff;
height: 28px;
line-height: 28px;
margin-top: 10px;
padding: 2px 15px;
cursor: pointer;
user-select: none;
transition: .1s;
}
#translate_button:hover, #translate_crop:hover {
box-shadow: -2px -2px 2px 0 #d0d7de;
}
#translate_button:active {
opacity: .7;
}
.case {
font-size: 16px;
margin-top: 10px;
border-radius: 8px;
background: #ffffff;
border: 1px solid #cfd0d2;
transition: all .2s linear;
}
.case:hover {
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
/*transform: scale(1.005);*/
}
.case:first-child {
margin-top: 0;
}
.case .dmx-icon {
cursor: pointer;
}
.case .dmx-icon:hover {
color: #1a73e8;
}
.case_copy, .case_bilingual {
display: none;
}
.case:hover .case_copy, .case:hover .case_bilingual {
display: block;
}
.case_content {
padding: 7px 10px;
line-height: 1.6;
}
.case_content > div, .case_dd > div {
margin-top: 5px;
}
.case_content > div:first-child, .case_dd > div:first-child {
padding-top: 0 !important;
}
.case_content [data-search="true"] {
cursor: pointer;
}
.case_content p.source_text {
color: #7398e6;
margin-top: 5px;
}
.case_content p.source_text:first-child {
margin-top: 0;
}
.case_dd {
margin-top: 10px;
border-top: thin dashed #ccc;
}
.case_dd_head {
padding: 10px 0 0;
font-weight: bold;
font-size: 110%;
}
.case_dd_ph .dmx-icon {
margin-left: 5px;
}
.case_dd_parts p {
padding: 3px 0 0;
}
.case_dd_parts p:first-child {
padding: 0;
}
.case_dd_parts p b {
color: #409eff;
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 4px;
padding: 0 5px;
margin-right: 10px;
}
.case_dd_exchange b {
font-weight: 500;
}
.case_dd_exchange u {
text-decoration: none;
margin-right: 10px;
}
.case_dd_exchange u a {
margin-left: 5px;
}
.case_dd_chart {
color: #e6a23c;
background-color: #fdf6ec;
border: 1px solid #faecd8;
border-radius: 5px;
padding: 5px;
}
.case_dd_chart u {
text-decoration: none;
margin: 0 10px 0 5px;
color: #ffc942;
}
.case_dd_example em {
font-style: normal;
color: #e6a23c;
}
.case_dd_tags u {
text-decoration: none;
display: inline-block;
margin-right: 5px;
padding: 0 5px;
color: #909399;
background: #f4f4f5;
border: 1px solid #e9e9eb;
border-radius: 4px;
font-size: 90%;
}
.case_dd_img a {
margin-right: 5px;
}
.case_dd_img img {
border: 1px solid #ccc;
width: 80px;
height: 80px;
object-fit: contain;
}
.case_top {
padding: 2px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.case_bottom {
padding: 2px 10px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.case_left {
float: left;
margin-right: 10px;
}
.case_left:last-child {
margin-right: 0;
}
.case_right {
float: right;
margin-left: 15px;
}
.case_right:last-child {
margin-left: 0;
}
.case_right a {
font-weight: bold;
}
.case_right a .dmx-icon {
margin-right: 3px;
}
.case_bilingual {
cursor: pointer;
}
.case_bilingual:after {
font-size: 22px;
line-height: 1;
display: inline-block;
vertical-align: middle;
margin-left: 3px;
content: "\e681";
color: rgba(0, 0, 0, 0.26);
}
.case_bilingual.active:after {
content: "\e680";
color: #1a73e8;
}
.dmx_ripple {
position: relative;
display: inline-block;
vertical-align: middle;
text-align: center;
width: 26px;
height: 26px;
line-height: 26px;
font-size: 20px;
color: #535353;
cursor: pointer;
transition: all .2s;
}
.dmx_ripple:hover {
color: #1a73e8;
transform: scale(1.3);
}
.dmx_ripple.active {
color: #e53935;
}
.dmx_ripple.active:before, .dmx_ripple.active:after {
display: block;
position: absolute;
content: '';
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
border: 1px solid #e35d5b;
}
.dmx_ripple.active:before {
animation: dmx_ripple 1s linear infinite;
}
.dmx_ripple.active:after {
animation: dmx_ripple 1s linear 0.5s infinite;
}
.dmx_reverse:before, .dmx_reverse:after {
animation-direction: reverse !important;
}
@keyframes dmx_ripple {
0% {
transform: scale(1);
}
50% {
transform: scale(1.75);
opacity: 0.5;
}
75% {
transform: scale(1.95);
opacity: 0.2;
}
100% {
transform: scale(2.05);
opacity: 0;
}
}
.dmx_pink {
color: deeppink;
}
/* ==================== search ==================== */
.search_box {
background: #fff;
color: #4e5053;
height: 30px;
line-height: 30px;
padding: 3px 60px 3px 8px;
position: relative;
}
.search_box:hover {
border-color: #409eff;
}
.search_box > input {
display: block;
width: 100%;
height: 28px;
font-size: 17px;
outline: none;
border: none;
}
.search_box #search_but {
position: absolute;
top: calc(50% - 15px);
right: 10px;
}
.search_box #search_remove {
position: absolute;
top: calc(50% - 15px);
right: 35px;
display: none;
}
.search_box:hover #search_remove {
display: block;
}
/* ==================== alert ==================== */
#dmx_alert {
z-index: 999999999999;
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 300px;
}
#dmx_alert > div {
position: relative;
padding: 10px;
font-size: 15px;
line-height: 1.5;
box-sizing: border-box;
color: #ffffff;
background-color: #414345;
border: 1px solid #232526;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
transition: all .3s;
overflow: hidden;
}
#dmx_alert > div.an_top {
margin-top: 10px;
}
#dmx_alert > div.an_delete {
opacity: 0.1;
}
#dmx_alert > div .dmx-icon {
margin-right: 5px;
}
#dmx_alert .dxm_alert_success {
background-color: #6FBD39;
border: 1px solid #56ab2f;
}
#dmx_alert .dxm_alert_error {
background-color: #ef473a;
border: 1px solid #b31217;
}
/* ==================== button ==================== */
.dmx_button {
display: inline-block;
background: #4395ff;
border: 1px solid #3589ff;
border-radius: 6px;
color: #fff;
line-height: 1;
margin: 0 10px 10px 0;
padding: 8px 12px;
white-space: nowrap;
text-align: center;
cursor: pointer;
user-select: none;
transition: .1s;
}
.dmx_button:last-child {
margin: 0;
}
.dmx_button:hover {
box-shadow: 1px 4px 6px 0 #d0d7de;
}
.dmx_button:active {
opacity: .7;
}
.dmx_button .dmx-icon {
margin-right: 5px;
}
.dmx_button_default {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
}
.dmx_button_success {
background-color: #67c23a;
border-color: #67c23a;
color: #fff;
}
.dmx_button_warning {
background-color: #e6a23c;
border-color: #e6a23c;
color: #fff;
}
.dmx_button_danger {
background-color: #ef473a;
border-color: #dd1419;
color: #fff;
}
/* dict */
.dict_cambridge .sense-body {
margin-top: 10px;
}
.dict_cambridge .dxref {
margin-right: 3px;
padding: 2px 6px;
color: #fff;
font-weight: 700;
font-size: .75rem;
text-align: center;
background: #1d2956;
border-radius: 50px;
}
.dict_cambridge .ddef_h {
margin-bottom: 10px;
font-weight: 700;
}
.dict_cambridge .dsense_h, .dict_cambridge .ca_h {
margin-top: 10px;
font-weight: 700;
color: #5d2fc1;
}
.dict_cambridge .dtrans {
display: block;
color: #0580e8;
}
.dict_collins .dictlink {
margin-top: 10px;
}
.dict_collins .dictlink:first-child {
margin-top: 0;
}
.dict_collins .sensenum, .dict_collins .def, .dict_collins .word-frequency-container .label {
display: inline-block;
}
.dict_collins .title_frequency_container {
color: #c12d30;
}
.dict_collins .pos {
text-transform: uppercase;
font-weight: bold;
color: #c12d30;
}
.dict_collins .hom {
margin-top: 10px;
}
.dict_collins .hom:first-of-type {
margin-top: 0;
}
.dict_collins .imageRight {
float: right;
width: 30%;
min-width: 150px;
padding: 2px;
border: 1px solid #bbb;
border-radius: 5px;
margin: 0 0 5px 5px;
}
.dict_etymonline [class^="word__name--"] {
font-size: 120%;
font-weight: 700;
color: #0580e8;
}
.dict_oxford .examples {
margin-left: 10px;
}
.dict_wr .rh_pdef {
display: block;
}
.dict_macmillan .dflex {
display: flex;
flex: 1 1 auto;
flex-wrap: nowrap;
}
.dict_macmillan .SENSE-NUM, .dict_macmillan .note {
display: inline-block;
min-width: 1.2em;
height: 1.2em;
line-height: 1.2em;
padding: .2em;
margin-right: .2em;
border-radius: 1em;
background: #d70a20;
text-align: center;
font-weight: bold;
color: white;
}
================================================
FILE: src/css/longman.css
================================================
.longman_dict .Sense img {
float: right;
width: 30%;
max-width: 400px;
margin-left: 5px;
}
.longman_dict .Sense:after {
display: block;
content: '';
clear: both;
}
.longman_dict .EXAMPLE .dmx-icon {
margin-right: 5px;
}
.longman_dict .infls,
.longman_dict .description,
.longman_dict .title,
.longman_dict .url,
.longman_dict .summary,
.longman_dict .og,
.longman_dict .infls,
.longman_dict .description,
.longman_dict .title,
.longman_dict .url,
.longman_dict .summary,
.longman_dict .og,
.longman_dict .assetref,
.longman_dict .assetref,
.longman_dict .dictentry,
.longman_dict .dictionary_intro,
.longman_dict .topic_intro,
.longman_dict .exaGroup,
.longman_dict .exaGroup .exaEntry,
.longman_dict .exaGroup .title,
.longman_dict .exaGroup .exa,
.ldoceEntry .Entry,
.ldoceEntry .ErrorBox,
.ldoceEntry .EXAMPLE,
.ldoceEntry .GramExa,
.ldoceEntry .PhrVbEntry,
.ldoceEntry .RunOn,
.ldoceEntry .Sense,
.ldoceEntry .Subsense,
.ldoceEntry .ColloBox,
.ldoceEntry .ThesBox,
.ldoceEntry .F2NBox,
.ldoceEntry .GramBox,
.ldoceEntry .HEADING.newline,
.ldoceEntry .Collocate,
.ldoceEntry .Exponent,
.ldoceEntry .EXPL,
.ldoceEntry .COLLEXA,
.ldoceEntry .THESEXA,
.ldoceEntry .LearnerItem,
.ldoceEntry .ColloExa,
.bussdictEntry .Box,
.bussdictEntry .Entry,
.bussdictEntry .ColloExa,
.bussdictEntry .ErrorBox,
.bussdictEntry .EXAMPLE,
.bussdictEntry .GramExa,
.bussdictEntry .PhrVbEntry,
.bussdictEntry .Sense,
.bussdictEntry .Subsense,
.bussdictEntry .ColloBox,
.bussdictEntry .ThesBox,
.bussdictEntry .F2NBox,
.bussdictEntry .GramBox,
.bussdictEntry .UsageBox,
.bussdictEntry .HEADING.newline,
.bussdictEntry .SECHEADING,
.bussdictEntry .subheading,
.bussdictEntry .Collocate.newline,
.bussdictEntry .Exponent,
.bussdictEntry .GramBox .EXPL,
.bussdictEntry .EXPL.newline,
.bussdictEntry .LearnerItem,
.bussdictEntry .CompareWord,
.bussdictEntry .EXP,
.bussdictEntry .boxheader,
.bussdictEntry .SubEntry {
display: block;
}
.ldoceEntry .FIELD,
.ldoceEntry .HWD,
.ldoceEntry .NOTE,
.ldoceEntry .Noteprompt,
.ldoceEntry .PIC,
.ldoceEntry .PICCAL,
.ldoceEntry .ACTIV,
.ldoceEntry .BOX,
.ldoceEntry .COMMENT,
.ldoceEntry .USAGE,
.bussdictEntry .ACTIV,
.bussdictEntry .COMMENT,
.bussdictEntry .FIELD,
.bussdictEntry .HWD,
.bussdictEntry .NOTE,
.bussdictEntry .Noteprompt,
.bussdictEntry .PIC,
.bussdictEntry .PICCAL,
.bussdictEntry .FIELDXX,
.bussdictEntry .USAGE,
.bussdictEntry .Crossrefto .REFLEX,
.longman_dict .suppressed,
.longman_dict .verbTable .view_less,
.longman_dict .verbTable .next_tenses {
display: none;
}
.bussdictEntry .SubEntry .HWD,
.bussdictEntry .GramBox.nobox,
.bussdictEntry .GramBox.nobox .EXPL,
.bussdictEntry .Collocate.inline {
display: inline;
}
.longman_dict .Crossrefto,
.longman_dict .assettype,
.longman_dict .exaGroup .title,
.longman_dict .exaGroup .NodeW,
.longman_dict .wordfams,
.longman_dict .etym .Head,
.longman_dict .verbTable .lemma,
.longman_dict .verbTable .Simple_Form .aux,
.longman_dict .verbTable .col1,
.longman_dict .verbTable .view_more,
.longman_dict .verbTable .view_less,
.longman_dict .verbTable .verb_form,
.ldoceEntry .ABBR,
.ldoceEntry .AMEQUIV,
.ldoceEntry .BREQUIV,
.ldoceEntry .COLLO,
.ldoceEntry .COLLOINEXA,
.ldoceEntry .COMP,
.ldoceEntry .Crossrefto,
.ldoceEntry .DERIV,
.ldoceEntry .FULLFORM,
.ldoceEntry .GRAM,
.ldoceEntry .HINTBOLD,
.ldoceEntry .HINTTITLE,
.ldoceEntry .HOMNUM,
.ldoceEntry .HYPHENATION,
.ldoceEntry .PHRVBHWD,
.ldoceEntry .LEXUNIT,
.ldoceEntry .LEXVAR,
.ldoceEntry .OPP,
.ldoceEntry .ORTHVAR,
.ldoceEntry .PASTPART,
.ldoceEntry .PASTTENSE,
.ldoceEntry .PLURALFORM,
.ldoceEntry .POS,
.ldoceEntry .PRESPART,
.ldoceEntry .PRESPARTX,
.ldoceEntry .PROPFORM,
.ldoceEntry .PROPFORMPREP,
.ldoceEntry .PTandPP,
.ldoceEntry .PTandPPX,
.ldoceEntry .REFHWD,
.ldoceEntry .REFLEX,
.ldoceEntry .RELATEDWD,
.ldoceEntry .SIGNPOST,
.ldoceEntry .SUPERL,
.ldoceEntry .SYN,
.ldoceEntry .T3PERSSING,
.ldoceEntry .T3PERSSINGX,
.ldoceEntry .UNCLASSIFIED,
.ldoceEntry .warning,
.ldoceEntry .sensenum,
.ldoceEntry .synopp,
.ldoceEntry .FREQ,
.ldoceEntry .AC,
.ldoceEntry .HEADING,
.ldoceEntry .heading,
.ldoceEntry .SECHEADING,
.ldoceEntry .subheading,
.ldoceEntry .COLLOC,
.ldoceEntry .EXP,
.ldoceEntry .EXPR,
.ldoceEntry .keycollo,
.ldoceEntry .THESPROPFORM,
.ldoceEntry .DEFBOLD,
.bussdictEntry .GRAM,
.bussdictEntry .POS,
.bussdictEntry .ABBR,
.bussdictEntry .AMEQUIV,
.bussdictEntry .BREQUIV,
.bussdictEntry .COLLO,
.bussdictEntry .COMP,
.bussdictEntry .COLLOINEXA,
.bussdictEntry .DERIV,
.bussdictEntry .FREQ,
.bussdictEntry .LEVEL,
.bussdictEntry .FULLFORM,
.bussdictEntry .HINTBOLD,
.bussdictEntry .HINTTITLE,
.bussdictEntry .HOMNUM,
.bussdictEntry .HYPHENATION,
.bussdictEntry .LEXUNIT,
.bussdictEntry .LEXVAR,
.bussdictEntry .OPP,
.bussdictEntry .ORTHVAR,
.bussdictEntry .PASTPART,
.bussdictEntry .PASTTENSE,
.bussdictEntry .PHRVBHWD,
.bussdictEntry .PLURALFORM,
.bussdictEntry .PRESPART,
.bussdictEntry .PRESPARTX,
.bussdictEntry .PROPFORM,
.bussdictEntry .PROPFORMPREP,
.bussdictEntry .PTandPP,
.bussdictEntry .PTandPPX,
.bussdictEntry .REFLEX,
.bussdictEntry .RELATEDWD,
.bussdictEntry .SIGNPOST,
.bussdictEntry .SUPERL,
.bussdictEntry .SYN,
.bussdictEntry .T3PERSSING,
.bussdictEntry .T3PERSSINGX,
.bussdictEntry .UNCLASSIFIED,
.bussdictEntry .THESPROPFORM,
.bussdictEntry .SECHEADING,
.bussdictEntry .subheading,
.bussdictEntry .HEADING,
.bussdictEntry .heading,
.bussdictEntry .warning,
.bussdictEntry .sensenum,
.bussdictEntry .synopp,
.bussdictEntry .Gramref,
.bussdictEntry .COLLOC,
.bussdictEntry .EXP,
.bussdictEntry .EXPR,
.bussdictEntry .keycollo,
.bussdictEntry .DEFBOLD,
.bussdictEntry .boxheader,
.bussdictEntry .SubEntry .HWD {
font-weight: bold;
}
.ldoceEntry .REFHOMNUM,
.bussdictEntry .REFHOMNUM {
vertical-align: super;
font-size: 60%;
}
.bussdictEntry .HOMNUM,
.longman_dict .etym .Head .HOMNUM {
vertical-align: super;
font-size: 12pt;
}
.ldoceEntry .AC,
.ldoceEntry .synopp {
color: #fff;
border-color: #f1d600;
background-color: #f1d600;
}
.ldoceEntry .EXPL .cross,
.ldoceEntry .GramBox .EXPL .dont_say,
.ldoceEntry .BADEXA {
color: red;
}
.ldoceEntry .FREQ,
.bussdictEntry .FREQ,
.bussdictEntry .LEVEL {
color: red;
border-color: red;
}
.ldoceEntry .LEVEL {
color: red;
font-size: 120%;
}
.ldoceEntry .frequent .HOMNUM,
.ldoceEntry .HOMNUM {
color: red;
vertical-align: super;
font-size: 12pt;
}
.ldoceEntry .frequent .HYPHENATION,
.ldoceEntry .HYPHENATION,
.ldoceEntry .PHRVBHWD {
color: red;
font-size: 160%;
}
.ldoceEntry .warning,
.bussdictEntry .warning {
color: red;
font-style: normal;
}
.ldoceEntry .GramBox .CROSS .neutral {
color: red;
margin-right: 10px;
}
.Entry .topic,
.topicCloud .topic_other {
color: red;
text-decoration: underline;
}
.ldoceEntry .GRAM,
.bussdictEntry .GRAM {
color: green;
margin: 0 5px 10px 3px;
}
.ldoceEntry .POS,
.bussdictEntry .POS {
color: green;
margin: 0 0 0 10px;
}
.longman_dict .pos {
color: green;
font-weight: normal;
}
.ldoceEntry .REGISTERLAB {
color: purple;
font-style: italic;
}
.ldoceEntry .SIGNPOST {
background-color: #f18500;
color: white;
font-size: 79%;
text-transform: uppercase;
border-radius: 5px;
padding: 0 3px;
letter-spacing: 1px;
}
.longman_dict .Crossrefto,
.ldoceEntry .RELATEDWD {
color: blue;
}
.bussdictEntry .synopp {
font-style: normal;
color: darkblue;
}
.longman_dict .assettype,
.ldoceEntry .Thesref,
.ldoceEntry .GEO,
.ldoceEntry .geo,
.ldoceEntry .GLOSS,
.ldoceEntry .LINKWORD,
.ldoceEntry .keycollo,
.bussdictEntry .LINKWORD,
.bussdictEntry .COLLOC.key,
.bussdictEntry .keycollo {
color: #364395;
}
.bussdictEntry .Box,
.ldoceEntry .ColloBox,
.ldoceEntry .ThesBox,
.ldoceEntry .F2NBox,
.ldoceEntry .GramBox {
border-radius: 5px;
border: 1px solid #364395;
padding: 10px;
margin: 8px 0;
}
.ldoceEntry .HEADING,
.ldoceEntry .heading,
.bussdictEntry .SubEntry .HWD {
color: #364395;
font-size: 120%;
}
.bussdictEntry .GEO,
.bussdictEntry .geo,
.bussdictEntry .REGISTERLAB {
color: #364395;
font-style: italic;
}
.bussdictEntry .GLOSS {
color: #364395;
font-weight: normal;
font-style: normal;
}
.bussdictEntry .PHRVBHWD,
.bussdictEntry .HEADING,
.bussdictEntry .heading,
.bussdictEntry .Gramref {
color: #364395;
font-size: 120%;
}
.bussdictEntry .boxheader {
background-color: #364395;
color: #fff;
}
.longman_dict .dictionary_intro,
.longman_dict .topic_intro {
background-color: #35a3ff;
color: #fff;
padding-left: 10px;
margin: 5px 0 10px -7px;
}
.longman_dict .assets_intro,
.longman_dict .asset_intro {
border: 1px solid #f1d600;
background-color: #f1d600;
color: #fff;
font-weight: normal;
border-radius: 5px;
padding-left: 3px;
padding-right: 3px;
}
.longman_dict .verbTable table {
background-color: #fae660;
margin-bottom: 10px;
border-collapse: collapse;
width: 100%;
}
.ldoceEntry .SECHEADING,
.ldoceEntry .subheading {
display: table;
border-radius: 5px;
border: solid #6f469d 2px;
padding-left: 4px;
padding-right: 15px;
margin: 10px 0;
color: white;
text-transform: uppercase;
background-color: #6f469d;
}
.ldoceEntry .amefile {
color: #4693db;
font-size: 130%;
padding-left: 5px;
}
.ldoceEntry .brefile {
color: #fa6360;
font-size: 130%;
padding-left: 5px;
}
.ldoceEntry .neutral,
.verbTable .aux,
.verbTable .verb_form,
.ldoceEntry .sensenum {
color: black;
}
.ldoceEntry .sensenum {
font-style: normal;
}
.longman_dict .GramBox {
color: black;
background-color: #fff;
}
.bussdictEntry .Box .EXAMPLE {
color: black;
display: inline;
margin-left: 0;
font-style: italic;
}
.verbTable .lemma {
color: black;
font-size: 120%;
margin: 0 0 0 10px;
}
.verbTable .view_more,
.verbTable .view_less {
color: black;
text-align: center;
padding: 20px 0 10px;
}
.verbTable .geo {
color: black;
font-style: italic;
font-weight: normal;
}
.longman_dict .exaGroup .title {
color: black;
font-size: 110%;
margin: 10px 0 0 5px;
}
.longman_dict .Entry .related_topics {
color: black;
padding: 0 4px 1px 0;
font-size: 16px;
}
.ldoceEntry .COLLEXA,
.ldoceEntry .THESEXA {
color: gray;
}
.ldoceEntry .exafile {
color: gray;
font-style: normal;
font-size: 120%;
padding: 5px;
}
.ldoceEntry .EXAMPLE,
.bussdictEntry .EXAMPLE,
.longman_dict .exaGroup .exa {
color: gray;
margin-left: 10px;
}
.bussdictEntry .supp,
.bussdictEntry .SIGNPOST {
background-color: gray;
}
.longman_dict .verbTable .col2 {
color: gray;
width: 150px
}
.longman_dict a {
border-bottom: thin dotted gray;
}
.ldoceEntry .RunOn {
margin-bottom: 10px;
}
.longman_dict .right_col .yellow_box {
margin-bottom: 10px;
display: inline-block;
}
.longman_dict .dictentry,
.longman_dict .exaGroup .exaEntry,
.longman_dict .exaGroup .exaGroup,
.longman_dict .verbTable .entry {
margin-bottom: 20px;
}
.ldoceEntry .Sense,
.bussdictEntry .Sense,
.longman_dict .verbTable .entry {
margin-left: 20px;
margin-bottom: 15px;
}
.bussdictEntry .SubEntry.embedded {
margin-top: -5px;
margin-left: 20px;
margin-bottom: 0;
}
.bussdictEntry .SubEntry {
margin-top: 2px;
margin-bottom: 2px;
}
.longman_dict .yellow_box {
margin-top: 20px;
display: table;
}
.ldoceEntry .Entry,
.bussdictEntry .Entry {
font-size: 12pt;
text-align: justify;
margin-top: 8px;
}
.ldoceEntry .Subsense,
.bussdictEntry .ColloExa,
.bussdictEntry .GramExa,
.bussdictEntry .Subsense {
margin-left: 10px;
}
.ldoceEntry .PROPFORM,
.ldoceEntry .PROPFORMPREP,
.ldoceEntry .COLLO,
.bussdictEntry .SubEntry {
margin-left: 20px;
}
.ldoceEntry .Crossrefto,
.ldoceEntry .DERIV {
font-size: 120%;
}
.ldoceEntry .Entry {
font-size: 11pt;
text-align: justify;
}
.ldoceEntry .HINTITALIC,
.ldoceEntry .STRONG,
.ldoceEntry .GOODCOLLO,
.bussdictEntry .COLLOINEXA,
.bussdictEntry .HINTITALIC,
.bussdictEntry .STRONG,
.bussdictEntry .COLLEXA,
.bussdictEntry .THESEXA,
.bussdictEntry .GOODCOLLO,
.bussdictEntry .SECHEADING,
.bussdictEntry .subheading {
font-style: italic;
}
.ldoceEntry .italic,
.ldoceEntry .infllab,
.bussdictEntry .italic,
.bussdictEntry .infllab {
font-style: italic;
font-weight: normal;
}
.bussdictEntry .SECHEADING,
.bussdictEntry .subheading,
.bussdictEntry .UNDERLINE {
text-decoration: underline;
}
.ldoceEntry .OBJECT {
font-weight: normal;
}
.ldoceEntry .synopp,
.ldoceEntry .FREQ,
.ldoceEntry .AC {
display: inline-block;
font-style: normal;
text-transform: uppercase;
border-radius: 5px;
border: solid 1px;
padding-left: 4px;
padding-right: 4px;
}
.longman_dict .GramBox .boxheader,
.longman_dict .ThesBox .heading,
.longman_dict .ColloBox .heading {
line-height: 2em;
}
.longman_dict .ColloBox .heading {
margin: 0 10px 0 0;
}
.ldoceEntry .Collocate,
.ldoceEntry .Exponent {
margin: 15px 0 0 5px;
}
.ldoceEntry .BADCOLLO {
text-decoration: line-through;
}
.bussdictEntry .DERIV {
font-size: 120%;
}
.bussdictEntry .HYPHENATION {
font-size: 160%;
}
.bussdictEntry .OBJECT {
font-weight: normal;
}
.bussdictEntry .REFHWD {
text-transform: lowercase;
font-style: normal;
/*font-variant: small-caps;*/
}
.bussdictEntry .neutral {
font-style: normal;
font-weight: normal;
font-variant: normal;
}
.bussdictEntry .sensenum {
font-style: normal;
margin-right: 5px;
margin-left: 3px;
}
.bussdictEntry .gramref {
margin: 5px 0;
}
.bussdictEntry .BADCOLLO,
.bussdictEntry .BADEXA {
text-decoration: line-through;
}
.verbTable td {
padding: 0 10px;
}
.verbTable .col1 {
color: #333333;
padding: 20px 10px 0;
font-size: 14px;
text-decoration: underline
}
.verbTable .view_more span,
.verbTable .view_less span {
cursor: pointer;
}
.verbTable .header {
background-color: #333333;
color: #fff;
padding: 0 0 0 10px;
font-size: 120%;
}
.longman_dict .wordfams .crossRef,
.longman_dict .wordfams .w {
margin: 0 6px;
}
.longman_dict .asset {
margin-top: 20px;
}
.longman_dict .Entry .topics_container {
display: table;
}
================================================
FILE: src/css/main.css
================================================
* {
box-sizing: border-box;
outline-color: #448aff;
}
html, body, p, h1, h2, h3, h4, h5, h6, ul, li {
margin: 0;
padding: 0;
}
ul, li {
list-style: none;
}
body {
font-family: Roboto, 'Segoe UI', Arial, 'Microsoft Yahei', sans-serif;
/*font-family: Helvetica Neue, PingFang SC, Microsoft YaHei, sans-serif;*/
background: #f5f8fa;
color: #333;
line-height: 1.8;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
/*background-color: #E2E4E6;*/
}
::-webkit-scrollbar-thumb {
background-color: #bdc3c7;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #9da5ac;
}
::-webkit-scrollbar-thumb:active {
background-color: #2c3e50;
}
a {
text-decoration: none;
color: #576b95;
}
a:hover {
color: #1a73e8;
}
.fx:after {
display: block;
content: '';
clear: both;
}
.dmx_hide {
display: none !important;
}
.dmx_show {
display: block !important;
}
.mt_1 {
margin-top: 10px;
}
.mt_2 {
margin-top: 20px;
}
.ml_1 {
margin-left: 10px;
}
.ml_2 {
margin-left: 20px;
}
.main {
margin: 0 auto;
padding: 10px;
min-width: 300px;
font-size: 15px;
}
label {
position: relative;
display: inline-block;
margin-right: 10px;
line-height: 1.6;
/*user-select: none;*/
}
label u {
text-decoration: none;
margin-left: 20px;
color: #a2a2a2;
font-size: 14px;
}
.label_block label {
display: block;
margin: 0;
}
input[type=radio], input[type=checkbox] {
width: 18px;
height: 18px;
margin: 0 5px 0 5px;
position: relative;
bottom: -4px;
}
/* Firefox */
@-moz-document url-prefix() {
input[type=radio], input[type=checkbox] {
bottom: auto;
}
}
select {
min-width: 100px;
max-width: 160px;
height: 30px;
padding: 3px;
font-size: 15px;
border: 1px solid #cfd0d2;
border-radius: 4px;
}
.dmx_button {
display: inline-block;
background: #4395ff;
border: 1px solid #3589ff;
border-radius: 4px;
color: #fff;
line-height: 1;
padding: 8px 12px;
font-size: 14px;
white-space: nowrap;
text-align: center;
outline: none;
box-sizing: border-box;
user-select: none;
cursor: pointer;
transition: .1s;
}
.dmx_button:hover {
box-shadow: 2px 2px 5px 0 #a6aab0;
}
.dmx_button:active {
opacity: .7;
}
.dmx_button.medium {
font-size: 105%;
padding: 10px 13px;
}
.dmx_button.big {
font-size: 125%;
padding: 10px 16px;
}
.dmx_button .dmx-icon {
margin-right: 5px;
}
.dmx_button_default {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
}
.dmx_button_success {
background-color: #67c23a;
border-color: #67c23a;
color: #fff;
}
.dmx_button_warning {
background-color: #e6a23c;
border-color: #e6a23c;
color: #fff;
}
.dmx_button_danger {
background-color: #ef473a;
border-color: #dd1419;
color: #fff;
}
.dmx_button:disabled, .dmx_button:disabled:hover {
cursor: not-allowed;
opacity: .3;
box-shadow: none;
}
.divider {
position: relative;
text-align: center;
margin: 10px 0;
}
.divider:after {
position: absolute;
top: 50%;
display: block;
content: '';
width: 100%;
border-bottom: 1px solid #b9b9b9;
}
.divider b {
z-index: 1;
display: inline-block;
padding: 0 10px;
position: relative;
background: #F1F4F6;
font-size: 120%;
font-weight: 400;
}
.item_input, .item_textarea {
background: #fff;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
height: 40px;
line-height: 40px;
outline: none;
font-size: 16px;
padding: 0 15px;
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
width: 100%;
}
.item_input:hover, .item_textarea:hover {
border-color: #c0c4cc;
}
.item_input:focus, .item_textarea:focus {
border-color: #409eff;
}
.item_textarea {
padding: 5px 15px;
height: auto;
min-height: 42px;
line-height: 26px;
resize: vertical;
}
.dmx_form_item {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
margin-top: 10px;
}
.dmx_form_item .item_label {
padding: 0 10px 0 0;
color: #606266;
font-size: 16px;
box-sizing: border-box;
white-space: nowrap;
}
.dmx_form_item .item_content {
width: 100%;
}
.dmx_form_item .item_content.number {
width: 100px;
}
.dmx_form_item .item_content.number input[type="number"] {
padding-right: 2px;
}
.dmx_form_item.small .item_label {
color: #333;
font-size: 14px;
}
.dmx_form_item.small .item_input {
height: 32px;
line-height: 32px;
font-size: 14px;
padding: 0 8px;
}
/* ==================== player ==================== */
.dmx_player {
margin-top: 10px;
position: relative;
width: 100%;
height: 120px;
max-width: 920px;
background: #d7dde8;
border-radius: 5px;
}
.dmx_player .dmx_surfer {
width: 100%;
height: calc(100% - 26px);
overflow: hidden;
border-radius: 0 0 5px 5px;
}
.dmx_player .dmx_controls {
display: flex;
position: absolute;
top: 26px;
left: 0;
width: 100%;
height: calc(100% - 26px);
z-index: 999;
}
.dmx_player .dmx_p_top {
background: #757F9A;
color: #FFFFFF;
height: 26px;
line-height: 26px;
padding: 0 10px;
border-radius: 5px 5px 0 0;
}
.dmx_player .dmx_p_top .dmx_p_title {
float: left;
font-size: 16px;
}
.dmx_player .dmx_p_top .dmx_p_time {
float: right;
font-size: 14px;
font-family: Consolas, serif;
}
.dmx_player .dmx_controls button {
display: flex;
justify-content: center;
align-items: center;
position: relative;
top: 50%;
left: 50%;
min-width: 100px;
height: 60px;
white-space: nowrap;
cursor: pointer;
border-radius: 10px;
outline: 0;
padding: 0 10px;
margin-top: -30px;
margin-left: -50px;
font-size: 32px;
background-color: #FF4B2B;
border-color: #FF4B2B;
color: #FFF;
}
.dmx_player .dmx_controls button:hover {
background: #f78989;
border-color: #f78989;
color: #FFF;
}
.dmx_player .dmx_controls button.disabled {
cursor: not-allowed;
background: #fab6b6;
border-color: #fab6b6;
color: #FFF;
}
.dmx_circle {
display: flex;
justify-content: center;
align-items: center;
height: 64px;
width: 64px;
position: relative;
top: 50%;
left: 50%;
border-radius: 50%;
background: linear-gradient(#e9eaeb, #D3CCE3);
border: 1px solid #bdc3c7;
box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);
transition: height 0.25s ease, width 0.25s ease;
transform: translate(-50%, -50%);
}
.dmx_circle:hover {
outline: none;
}
.dmx_circle:before, .dmx_circle:after {
display: block;
position: absolute;
content: "";
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
}
.dmx_circle .dmx-icon {
font-size: 46px;
color: #333;
}
.dmx_circle.dmx_on {
background: linear-gradient(#FF416C, #FF4B2B);
border: 1px solid #FF4B2B;
}
.dmx_circle.dmx_on .dmx-icon {
color: #fff;
}
.dmx_circle.dmx_on:before, .dmx_circle.dmx_on:after {
border: 0.015625rem solid #FF4B2B;
}
.dmx_circle.dmx_on:before {
animation: dmx_ripple 2s linear infinite;
}
.dmx_circle.dmx_on:after {
animation: dmx_ripple 2s linear 1s infinite;
}
.dmx_reverse.dmx_on:before, .dmx_reverse.dmx_on:after {
animation-direction: reverse;
}
.dmx_reverse.dmx_on {
cursor: pointer;
}
@keyframes dmx_ripple {
0% {
transform: scale(1);
}
50% {
transform: scale(1.5);
opacity: 0.5;
}
75% {
transform: scale(1.75);
opacity: 0.2;
}
100% {
transform: scale(1.95);
opacity: 0;
}
}
/* ==================== setting ==================== */
.tab u {
display: block;
float: left;
text-decoration: none;
background: #fff;
border: 1px solid #D5D7DF;
border-left: 0;
padding: 6px 10px;
line-height: 1;
cursor: pointer;
}
.tab u:first-child {
border-radius: 5px 0 0 5px;
border-left: 1px solid #D5D7DF;
}
.tab u:last-child {
border-radius: 0 5px 5px 0;
}
.tab u:hover {
color: #1a73e8;
}
.tab u.active {
color: #fff;
background-color: #409eff;
border-color: #409eff;
}
.setting_box {
padding: 0 3px;
display: none;
}
.setting_option {
margin-top: 15px;
}
.setting_option .option_title {
position: relative;
}
.setting_option .option_title:after {
position: absolute;
top: 50%;
display: block;
content: '';
width: 100%;
border-bottom: 1px solid #b9b9b9;
}
.setting_option b {
z-index: 1;
position: relative;
background: #F1F4F6;
padding-right: 5px;
}
.setting_option .option_box {
padding-top: 10px;
}
.setting_option .second_box {
margin: 10px 0 0 5px;
}
.setting_option .dmx-icon-setting {
margin-left: 5px;
color: #7b848c;
cursor: pointer;
}
.setting_dialog {
display: none;
z-index: 99;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f5f8fa;
overflow: auto;
padding: 10px;
box-sizing: border-box;
}
#local_tts_dialog select {
float: left;
max-width: 180px;
}
#local_tts_reset_setting {
margin-left: 220px;
}
.local_list_name {
display: inline-block;
float: left;
min-width: 220px;
text-align: right;
padding-right: 10px;
}
.local_list_box {
display: inline-block;
}
.lang_list {
color: #9da5ac;
}
.lang_list_err {
color: #FF416C;
}
.dialog_back {
position: fixed;
left: 10px;
top: 2px;
cursor: pointer;
}
.dialog_back .dmx-icon {
font-size: 20px;
}
/* ==================== speak ==================== */
.speak_body {
margin: 62px 10px 10px;
}
#speak_input, textarea {
display: block;
border-radius: 4px;
border: 1px solid #cfd0d2;
background: #fff;
color: #4e5053;
min-height: 88px;
line-height: 22px;
padding: 5px 10px;
font-size: 16px;
outline: none;
overflow: auto;
resize: vertical;
}
#search_list_dialog textarea {
width: 100%;
min-height: 280px;
}
#speak_input:focus {
border-color: #409eff;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
}
.speak_box select {
margin: 10px 10px 0 0;
}
#speak_button {
float: right;
}
/* ==================== history ==================== */
.history_box {
padding: 60px 10px 10px;
}
.history_box > .card {
margin: 0 auto;
min-width: 860px;
max-width: 1200px;
}
/* ==================== more ==================== */
.more_list .dmx_button {
float: left;
margin: 0 10px 10px 0;
}
/* ==================== record ==================== */
.record_main {
margin: 0 auto;
padding: 0 10px 10px;
min-width: 500px;
max-width: 920px;
}
.dmx_center {
margin: 20px 0;
text-align: center;
}
.dmx_left {
margin: 20px 0;
text-align: left;
}
.dmx_right {
margin: 20px 0;
text-align: right;
}
.learn_points {
margin-top: 10px;
font-size: 16px;
line-height: 1.8;
color: #b3b3b3;
}
.learn_points .title {
position: relative;
}
.learn_points .title:after {
position: absolute;
top: 50%;
display: block;
content: '';
width: 100%;
border-bottom: 1px solid #b9b9b9;
}
.learn_points b {
z-index: 1;
position: relative;
background: #F1F4F6;
padding-right: 5px;
}
.learn_points .case {
padding: 5px;
}
/* ==================== favorite ==================== */
.head {
z-index: 2;
position: fixed;
top: 0;
background: #F6F7F9;
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 2px 6px 2px rgba(60, 64, 67, .15);
width: 100%;
height: 50px;
overflow: hidden;
}
.head .logo {
float: left;
color: #1a73e8;
text-shadow: -1px -1px white, 1px 1px #175fbf;
letter-spacing: 2px;
font-size: x-large;
font-weight: bold;
height: 50px;
line-height: 50px;
margin: 0 15px;
}
.head .logo .dmx-icon {
font-size: x-large;
text-shadow: none;
margin-right: 5px;
}
.head .nav {
float: left;
display: flex;
align-items: center;
margin-left: 25px;
}
.head .nav li {
padding: 0 15px;
}
.head .nav li a {
position: relative;
display: inline-block;
font-size: 16px;
height: 50px;
line-height: 50px;
color: #8590a6;
}
.head .nav li a:hover, .head .nav li a.active {
color: #121212;
font-weight: 600;
}
.head .nav li a.active:after {
position: absolute;
right: 0;
bottom: -1px;
left: 0;
height: 4px;
background: #06f;
content: '';
}
.head .tool {
float: right;
margin-right: 10px;
height: 50px;
display: flex;
align-items: center;
}
.head .tool li {
padding: 0 10px;
font-size: 15px;
cursor: pointer;
}
.head .tool li .dmx-icon {
margin-right: 5px;
}
.cate {
z-index: 1;
position: fixed;
top: 50px;
bottom: 0;
width: 250px;
background: #F3F6F9;
border-right: 1px solid #AFB3B5;
overflow: auto;
}
.cate .dmx-icon:hover, .dmx_hover .dmx-icon:hover {
color: #06f;
cursor: pointer;
}
#create_cate_but {
position: fixed;
top: 52px;
left: 217px;
}
#create_cate_but .dmx-icon {
font-size: 20px;
}
.cate ul {
padding: 5px 0;
}
.cate ul li {
line-height: 32px;
font-size: 15px;
padding: 0 10px 0 15px;
cursor: pointer;
}
.cate ul li .dmx-icon {
color: #9EA9B7;
margin-right: 5px;
}
.cate ul li:hover {
background: #EBECEE;
}
.cate ul li.active {
background: #EBECEE;
}
.cate ul li.active a, .cate ul li.active .dmx-icon {
color: #1a73e8;
}
.cate ul li.active .dmx-icon:before {
content: "\e6c5";
}
.favorite_box {
min-width: 1000px;
margin: 60px 10px 10px 260px;
}
.card {
background: #FFFFFF;
border: 1px solid #CCCFD0;
border-radius: 10px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
}
.card_head {
display: flex;
justify-content: left;
align-items: center;
height: 56px;
padding: 10px 15px;
font-size: 18px;
border-bottom: 1px solid #CCCFD0;
}
.card_head > div {
margin-right: 15px;
}
.card_head .dmx_vis {
visibility: hidden;
}
.card_head:hover .dmx_vis {
visibility: visible;
}
.card_head .item_num {
font-size: 14px;
color: #b1b1b1;
}
.card_head .dmx_button {
margin-left: 10px;
}
.card_head .extra {
display: none;
}
.card_body {
padding-bottom: 5px;
}
table.dmx_table {
width: 100%;
min-width: 800px;
border-collapse: collapse;
}
table.dmx_table tr:hover td {
background: #F2F5F9;
}
table.dmx_table th, table.dmx_table td {
text-align: left;
background: #fff;
border-top: 1px solid #ddd;
padding: 8px;
word-break: break-all;
font-size: 15px;
}
table.dmx_table th {
background: #F5F6F9;
}
table.dmx_table thead th {
border-top: none;
}
table.dmx_table .tb_checkbox {
width: 44px;
}
table.dmx_table .tb_index {
width: 80px;
}
table.dmx_table .tb_words {
width: 240px;
}
table.dmx_table .tb_records, table.dmx_table .tb_days {
width: 90px;
}
table.dmx_table .tb_date {
width: 110px;
}
table.dmx_table .tb_operate {
width: 200px;
}
table.dmx_table .tb_operate2 {
width: 140px;
}
table.dmx_table .table_empty {
padding: 15px 10px;
text-align: center;
/*font-weight: bold;*/
}
.player_box {
min-width: 500px;
max-width: 920px;
margin: 0 auto;
}
#player_sentence {
font-size: 20px;
margin-top: 10px;
padding: 5px;
}
#player_sentence.hide {
color: #F1F4F6;
}
#player_sentence.hide:hover {
color: #333;
}
#player_sentence u, .tb_sentence u {
text-decoration: none;
color: red;
}
#player_sentence.hide u {
color: inherit;
}
#player_sentence.hide:hover u {
color: red;
}
/* ==================== dal ==================== */
.dal, .dal_bg, .ddi, .ddi_bg {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.dal_bg, .ddi_bg {
z-index: 999999999998;
background: rgba(0, 0, 0, .6);
}
.dal, .ddi {
z-index: 999999999999;
display: flex;
align-items: center;
justify-content: center;
}
.dal_modal, .ddi_modal {
position: relative;
width: 500px;
background: #fff;
border-radius: 5px;
padding: 20px;
font-size: 16px;
max-height: 95%;
overflow: auto;
}
.dal_text {
margin: 15px 0;
}
.dal_text .dmx-icon {
margin-right: 8px;
font-size: 130%;
}
.dal_text .dmx-icon-info {
color: #e6a23c;
}
.dal_text .dmx-icon-close {
color: #D50007;
}
.dal_text .dmx-icon-success {
color: #67c23a;
}
.dal_foot {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
}
.dal_foot .dmx_button {
margin-left: 10px;
}
.ddi_dialog {
width: 600px;
padding: 0;
}
.ddi_dialog.fullscreen {
width: 100%;
height: 100%;
max-height: 100%;
border-radius: 0;
background: #F1F4F6;
}
.ddi_head {
width: 100%;
height: 38px;
line-height: 38px;
padding-left: 10px;
font-weight: bold;
}
.ddi_head .dmx-icon-close {
display: block;
width: 20px;
height: 20px;
line-height: 20px;
position: absolute;
top: 10px;
right: 10px;
color: #FC4A4A;
font-size: 18px;
}
.ddi_head .dmx-icon:hover {
opacity: .8;
cursor: pointer;
}
.ddi_body {
margin: 0 15px 10px;
}
.ddi_loading {
color: #fff;
font-size: 18px;
text-align: center;
}
.ddi_loading_inner {
border: 5px solid #bdc3c7;
border-top: 5px solid #2c3e50;
border-bottom: 3px solid #2c3e50;
border-radius: 50%;
display: block;
width: 64px;
height: 64px;
margin: 0 auto;
animation: spin 1.2s linear infinite;
}
.dmx-icon-loading {
animation: spin 1.2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media only screen and (max-width: 789px) {
.dal_modal {
width: 430px;
}
}
================================================
FILE: src/css/popup.css
================================================
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: #f5f8fa;
}
.dmx_popup {
width: 520px;
height: 500px;
}
.dmx_popup body {
width: 520px;
}
================================================
FILE: src/html/favorite.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer">
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
<title>我的收藏</title>
</head>
<body>
<div class="head fx">
<div class="logo"><i class="dmx-icon dmx-icon-logo"></i>梦想划词翻译</div>
<ul class="nav">
<li><a href="favorite.html" class="active">我的收藏</a></li>
<li><a href="history.html">历史记录</a></li>
<li><a href="speak.html">朗读助手</a></li>
</ul>
<ul class="tool">
<li><a id="export"><i class="dmx-icon dmx-icon-export"></i>导出</a></li>
<li><a id="import"><i class="dmx-icon dmx-icon-import"></i>导入</a></li>
<li><a id="setting"><i class="dmx-icon dmx-icon-setting"></i>设置</a></li>
</ul>
</div>
<div class="cate">
<div id="create_cate_but"><i class="dmx-icon dmx-icon-plus" title="新增分类"></i></div>
<ul id="cate_box"></ul>
</div>
<div class="card favorite_box">
<div class="card_head">
<div id="cate_name"></div>
<div class="item_num"><span id="sentences">0</span> 条</div>
<div id="cate_edit" class="dmx_vis dmx_hover" title="编辑分类"><i class="dmx-icon dmx-icon-edit"></i></div>
<div id="cate_delete" class="dmx_vis dmx_hover" title="删除分类"><i class="dmx-icon dmx-icon-delete"></i></div>
<div class="extra" id="extra_but">
<div class="dmx_button dmx_button_warning" id="sentence_move"><i class="dmx-icon dmx-icon-move"></i>移动</div>
<div class="dmx_button dmx_button_danger" id="sentence_delete"><i class="dmx-icon dmx-icon-delete"></i>删除</div>
</div>
</div>
<div class="card_body">
<table class="dmx_table" id="sentence_box">
<thead>
<tr>
<th class="tb_checkbox"><input type="checkbox" id="selectAll"></th>
<th class="tb_sentence">句子</th>
<th class="tb_records">练习次数</th>
<th class="tb_days">练习天数</th>
<th class="tb_date">添加时间</th>
<th class="tb_operate">操作</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</body>
<script type="text/javascript" src="../js/lib/wavesurfer.min.js"></script>
<script type="text/javascript" src="../js/lib/wavesurfer.microphone.min.js"></script>
<script type="text/javascript" src="../js/lib/RecordRTC.min.js"></script>
<script type="text/javascript" src="../js/lib/jszip.min.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/player.js"></script>
<script type="text/javascript" src="../js/content.js"></script>
<script type="text/javascript" src="../js/db.js"></script>
<script type="text/javascript" src="../js/favorite.js"></script>
</html>
================================================
FILE: src/html/history.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
<title>历史记录</title>
</head>
<body>
<div class="head fx">
<div class="logo"><i class="dmx-icon dmx-icon-logo"></i>梦想划词翻译</div>
<ul class="nav">
<li><a href="favorite.html">我的收藏</a></li>
<li><a href="history.html" class="active">历史记录</a></li>
<li><a href="speak.html">朗读助手</a></li>
</ul>
<ul class="tool">
<li><a id="setting"><i class="dmx-icon dmx-icon-setting"></i>设置</a></li>
</ul>
</div>
<div class="history_box">
<div class="card">
<div class="card_head">
<div>历史记录</div>
<div class="item_num"><span id="historyNum">0</span> 条</div>
<div class="extra" id="extra_but">
<div class="dmx_button dmx_button_danger" id="delete_multiple"><i class="dmx-icon dmx-icon-delete"></i>删除</div>
</div>
</div>
<div class="card_body">
<table class="dmx_table" id="history_box">
<thead>
<tr>
<th class="tb_checkbox"><input type="checkbox" id="selectAll"></th>
<th class="tb_sentence">内容</th>
<th class="tb_date">时间</th>
<th class="tb_operate2">操作</th>
</tr>
</thead>
<tbody id="history_body"></tbody>
</table>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/content.js"></script>
<script type="text/javascript" src="../js/db.js"></script>
<script type="text/javascript" src="../js/history.js"></script>
</html>
================================================
FILE: src/html/more.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>More</title>
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
</head>
<body>
<div class="main">
<div class="more_list fx">
<div class="dmx_button" id="trans_window"><i class="dmx-icon dmx-icon-plane-paper"></i>翻译窗口</div>
<div class="dmx_button" id="allow_select" title="解除页面选中文字和右键限制"><i class="dmx-icon dmx-icon-devil"></i>解除限制</div>
<div class="dmx_button" data-href="favorite.html"><i class="dmx-icon dmx-icon-heart"></i>我的收藏</div>
<div class="dmx_button" data-href="history.html"><i class="dmx-icon dmx-icon-history"></i>历史记录</div>
<div class="dmx_button" data-href="speak.html"><i class="dmx-icon dmx-icon-headset-c"></i>朗读助手</div>
<div class="dmx_button" data-href="https://mengxiang.net/tool/phonetic/"><i class="dmx-icon dmx-icon-sound"></i>英语音标</div>
<div class="dmx_button" data-href="https://mengxiang.net/tool/kana/"><i class="dmx-icon dmx-icon-sound-square"></i>日语假名</div>
<div class="dmx_button" data-href="https://mengxiang.net/tool/pdfjs/"><i class="dmx-icon dmx-icon-folder"></i>PDF阅读器</div>
</div>
</div>
</body>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/more.js"></script>
</html>
================================================
FILE: src/html/popup.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>梦想翻译小助手</title>
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/popup.css">
</head>
<body>
</body>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/popup.js"></script>
<script type="text/javascript" src="../js/content.js"></script>
</html>
================================================
FILE: src/html/record.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer">
<title>练习发音</title>
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
</head>
<body>
<div class="record_main" id="record_box">
<div id="player_listen"></div>
<div id="player_record"></div>
<div id="player_compare"></div>
<div class="divider"><b>练习 <span id="practice_num">0</span> 次</b></div>
<div class="dmx_center">
<div class="dmx_button dmx_button_danger big" id="favorite_but">添加收藏</div>
<a class="dmx_button dmx_button_default big ml_1" href="favorite.html" target="_blank">我的收藏</a>
</div>
</div>
<div class="record_main dmx_hide" id="favorite_form">
<div id="player_listen2"></div>
<form class="mt_2" id="sentence_form">
<div class="dmx_form_item">
<div class="item_label">句子</div>
<div class="item_content"><input name="sentence" type="text" autocomplete="off" required class="item_input" placeholder="如:She has been extremely difficult every step of the way."></div>
</div>
<div class="dmx_form_item">
<div class="item_label">生词</div>
<div class="item_content"><textarea name="words" autocomplete="off" class="item_textarea" placeholder="一行一词。如:extremely"></textarea></div>
</div>
<div class="dmx_form_item">
<div class="item_label">音频</div>
<div class="item_content"><input name="url" type="url" autocomplete="off" required class="item_input"></div>
</div>
<div class="dmx_center">
<button class="dmx_button big" type="submit"><i class="dmx-icon dmx-icon-plus"></i>添加</button>
<div class="dmx_button dmx_button_default big ml_1" id="back_but"><i class="dmx-icon dmx-icon-reply"></i>返回</div>
</div>
</form>
</div>
</body>
<script type="text/javascript" src="../js/lib/wavesurfer.min.js"></script>
<script type="text/javascript" src="../js/lib/wavesurfer.microphone.min.js"></script>
<script type="text/javascript" src="../js/lib/RecordRTC.min.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/player.js"></script>
<script type="text/javascript" src="../js/db.js"></script>
<script type="text/javascript" src="../js/record.js"></script>
</html>
================================================
FILE: src/html/setting.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Setting</title>
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
</head>
<body>
<div class="main">
<div id="navigate" class="tab fx">
<u target="base_box" class="active">基本设置</u>
<u target="translate_box">翻译设置</u>
<u target="dictionary_box">词典设置</u>
<u target="search_box">搜索设置</u>
<u target="help_box">联系作者</u>
</div>
<div id="base_box" class="setting_box">
<div class="setting_option">
<div class="option_title"><b>划词功能</b></div>
<div class="option_box label_block">
<label><input type="radio" name="scribble" value="off">关闭划词</label>
<label><input type="radio" name="scribble" value="direct">直接显示<u>选中文字后,即显示结果</u></label>
<label><input type="radio" name="scribble" value="clickIcon">点击图标<u>选中文字后,先显示图标,点击图标再显示结果</u></label>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>划词过滤</b></div>
<div class="option_box label_block">
<label><input type="checkbox" name="excludeChinese" value="on">排除中文<u>如果选中的内容中包含中文,不激活划词功能</u></label>
<label><input type="checkbox" name="excludeSymbol" value="on">排除符号<u>如果选中的内容中只有符号,不激活划词功能</u></label>
<label><input type="checkbox" name="excludeNumber" value="on">排除数字<u>如果选中的内容中只有数字,不激活划词功能</u></label>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>对话框位置</b></div>
<div class="option_box">
<label><input type="radio" name="position" value="fixed">固定位置</label>
<label><input type="radio" name="position" value="follow">跟随选区</label>
<label><input type="radio" name="position" value="right">右侧靠边</label>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>实用功能</b></div>
<div class="option_box label_block">
<label><input type="checkbox" name="autoWords" value="on">鼠标取词<u>鼠标停留在英文单词上时,自动查词</u></label>
<label><input type="checkbox" name="allowSelect" value="on">自动解限<u>自动解除访问页面选中文字和右键限制</u></label>
<label><input type="checkbox" name="autoCopy" value="on">自动复制<u>选中文字后,自动复制选中内容</u></label>
<label><input type="checkbox" name="autoPaste" value="on">自动粘贴<u>打开翻译窗口时,自动粘贴内容到输入框</u></label>
<label><input type="checkbox" name="autoChange" value="on">自动切换<u>根据空格数,自动切换词典或翻译查询</u></label>
<label><input type="checkbox" name="cutHumpName" value="on">驼峰断词<u>自动切分驼峰命名词组,程序员的小助手</u></label>
</div>
</div>
<div id="clearSetting" class="dmx_button dmx_button_default mt_2">恢复默认设置</div>
</div>
<div id="translate_box" class="setting_box">
<div class="setting_option">
<div class="option_title"><b>启用翻译</b></div>
<div id="setting_translate_list" class="option_box"></div>
<div id="setting_translate_sort" class="second_box"></div>
</div>
<div class="setting_option">
<div class="option_title"><b>自动朗读</b></div>
<div id="setting_translate_tts_list" class="option_box"></div>
<div id="setting_translate_tts_sort" class="second_box"></div>
</div>
<div class="setting_option" id="local_box">
<div class="option_title"><b>本机朗读</b></div>
<div class="option_box label_block">
替换朗读
<select name="localSoundReplace" class="ml_1"></select>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>截图识别</b></div>
<div class="option_box label_block">
识别语言
<select name="translateOCR" class="ml_1">
<option value="CHN_ENG">中英文混合</option>
<option value="ENG">英文</option>
<option value="JAP">日语</option>
<option value="KOR">韩语</option>
<option value="FRE">法语</option>
<option value="SPA">西班牙语</option>
<option value="POR">葡萄牙语</option>
<option value="GER">德语</option>
<option value="ITA">意大利语</option>
<option value="RUS">俄语</option>
</select>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>识别引擎</b></div>
<div class="option_box label_block">
<label><input type="radio" name="ocrType" value="auto">免费文字识别<u>可能存在不稳定或失效问题</u></label>
<label><input type="radio" name="ocrType" value="baidu">百度文字识别<u>需要到百度云平台人工申请,<a href="https://mengxiang.net/post/baidu_ocr.html" target="_blank">申请教程</a></u></label>
</div>
<div class="dmx_hide" id="baidu_ocr_box">
<div class="dmx_form_item small">
<div class="item_label">百度云应用AK</div>
<div class="item_content"><input name="baidu_orc_ak" type="text" autocomplete="off" class="item_input" placeholder="请填写 API Key"></div>
</div>
<div class="dmx_form_item small">
<div class="item_label">百度云应用SK</div>
<div class="item_content"><input name="baidu_orc_sk" type="text" autocomplete="off" class="item_input" placeholder="请填写 Secret Key"></div>
</div>
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>更多功能</b></div>
<div class="option_box label_block">
<label><input type="checkbox" name="translateThin" value="on">精简显示<u>仅显示最基本的翻译内容</u></label>
<label><input type="checkbox" name="hideOriginal" value="on">隐藏原文<u>隐藏原文框减小占用空间</u></label>
<label><input type="checkbox" name="autoLanguage" value="on">检测中文<u>如果为中文自动中英切换</u></label>
<label><input type="checkbox" name="autoConfirm" value="on">自动确认<u>键盘输入停止 2 秒后自动确认翻译</u></label>
</div>
</div>
</div>
<div id="dictionary_box" class="setting_box">
<div class="setting_option">
<div class="option_title"><b>启用词典</b></div>
<div id="setting_dictionary_list" class="option_box"></div>
<div id="setting_dictionary_sort" class="second_box"></div>
</div>
<div class="setting_option">
<div class="option_title"><b>自动朗读</b></div>
<div id="setting_dictionary_sound_list" class="option_box"></div>
<div id="setting_dictionary_sound_sort" class="second_box"></div>
<div id="setting_dictionary_reader" class="second_box">
朗读:
<label><input type="radio" name="dictionaryReader" value="uk">英音</label>
<label><input type="radio" name="dictionaryReader" value="us">美音</label>
</div>
</div>
</div>
<div id="search_box" class="setting_box">
<div class="setting_option">
<div class="option_title"><b>启用搜索</b></div>
<div id="setting_search_list" class="option_box"></div>
<div id="setting_search_sort" class="second_box"></div>
</div>
<div class="setting_option">
<div class="option_title"><b>右键菜单</b></div>
<div id="setting_search_menus" class="option_box"></div>
<div id="setting_search_menus_sort" class="second_box"></div>
</div>
<div class="setting_option">
<div class="option_title"><b>左侧漂浮</b></div>
<div id="setting_search_side" class="option_box"></div>
<div id="setting_search_side_sort" class="second_box"></div>
</div>
<div id="search_setting_but" class="dmx_button dmx_button_default mt_2">搜索管理</div>
</div>
<div id="help_box" class="setting_box">
<div class="setting_option">
<div class="option_title"><b>联系作者</b></div>
<div class="option_box">
如果您有任何建议和问题反馈,请邮件发送到 Dream39999@gmail.com
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>开发原因</b></div>
<div class="option_box">
每当看到一些容易误导人的背单词学习英语广告,文章,视频,软件,就实在忍不住想做点什么,于是就有了这款程序。
</div>
</div>
<div class="setting_option">
<div class="option_title"><b>学习方法</b></div>
<div class="option_box">
如果你对「正确学习英语的方法」感兴趣,请阅读《<a href="https://mengxiang.net/post/english.html" target="_blank">英语学习秘籍</a>》。
</div>
</div>
</div>
<div id="local_tts_dialog" class="setting_dialog">
<div class="dialog_back"><i class="dmx-icon dmx-icon-arrow-left"></i></div>
<div class="fx">
<div class="local_list_name">朗读速度</div>
<select key="speak_rate">
<option value="">默认</option>
<option value="0.5">0.5X</option>
<option value="0.75">0.75X</option>
<option value="1">1X</option>
<option value="1.25">1.25X</option>
<option value="1.5">1.5X</option>
<option value="1.75">1.75X</option>
<option value="2">2X</option>
<option value="2.5">2.5X</option>
<option value="3">3X</option>
</select>
</div>
<div class="fx mt_1">
<div class="local_list_name">朗读音调</div>
<select key="speak_pitch">
<option value="">默认</option>
<option value="0.5">0.5</option>
<option value="0.75">0.75</option>
<option value="1">1</option>
<option value="1.25">1.25</option>
<option value="1.5">1.5</option>
<option value="1.75">1.75</option>
<option value="2">2</option>
</select>
</div>
<div id="local_tts_list"></div>
<div id="local_tts_reset_setting" class="dmx_button dmx_button_default mt_1">恢复默认设置</div>
</div>
<div id="search_list_dialog" class="setting_dialog">
<div class="search_list_text"><textarea name="search_text"></textarea></div>
<div id="search_list_save" class="dmx_button mt_1">保存</div>
<div id="search_list_back" class="dmx_button dmx_button_default mt_1">返回</div>
<div class="learn_points">
<div class="title"><b>设置说明</b></div>
<div class="case">每行一条,“|”分隔,前面为名称,后面为搜索链接,{0} 代表搜索关键字。</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/setting.js"></script>
</html>
================================================
FILE: src/html/speak.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>朗读助手</title>
<link rel="stylesheet" href="../css/content.css">
<link rel="stylesheet" href="../css/main.css">
</head>
<body>
<div class="head fx">
<div class="logo"><i class="dmx-icon dmx-icon-logo"></i>梦想划词翻译</div>
<ul class="nav">
<li><a href="favorite.html">我的收藏</a></li>
<li><a href="history.html">历史记录</a></li>
<li><a href="speak.html" class="active">朗读助手</a></li>
</ul>
</div>
<div class="speak_body">
<div id="speak_input" contenteditable="true"></div>
<div class="speak_box fx">
<select id="speak_voice">
<option value="">朗读名称</option>
</select>
<select id="speak_rate">
<option value="">朗读速度</option>
<option value="0.5">0.5X</option>
<option value="0.75">0.75X</option>
<option value="1">1X</option>
<option value="1.25">1.25X</option>
<option value="1.5">1.5X</option>
<option value="1.75">1.75X</option>
<option value="2">2X</option>
<option value="2.5">2.5X</option>
<option value="3">3X</option>
</select>
<select id="speak_pitch">
<option value="">朗读音调</option>
<option value="0.5">0.5</option>
<option value="0.75">0.75</option>
<option value="1">1</option>
<option value="1.25">1.25</option>
<option value="1.5">1.5</option>
<option value="1.75">1.75</option>
<option value="2">2</option>
</select>
<div id="speak_button" class="dmx_button mt_1">朗读</div>
</div>
</div>
</body>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../js/speak.js"></script>
</html>
================================================
FILE: src/html/video.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer">
<title>Video</title>
</head>
<body style="margin:0;padding:0;overflow:hidden;background:#000"></body>
<script type="text/javascript" src="../js/video.js"></script>
</html>
================================================
FILE: src/js/background.js
================================================
'use strict'
/**
* Dream Translate
* https://github.com/ryanker/dream_translate
* @Author Ryan <dream39999@gmail.com>
* @license MIT License
*/
let conf, setting, sdk = {}
let searchText, searchList
let ocrToken = '', ocrExpires = 0
var textTmp = ''
var historyMax = 3000
document.addEventListener('DOMContentLoaded', async function () {
let languageList = '', dialogCSS = '', dictionaryCSS = {}
await fetch('../conf/conf.json').then(r => r.json()).then(r => {
conf = r
})
await fetch('../conf/searchText.txt').then(r => r.text()).then(str => {
searchText = str
})
await fetch('../conf/language.json').then(r => r.text()).then(s => {
languageList += s
})
await fetch('../css/dmx_dialog.css').then(r => r.text()).then(s => {
dialogCSS += minCss(s)
})
for (let name of conf.dictionaryCSS) {
await fetch(`../css/${name}.css`).then(r => r.text()).then(s => {
dictionaryCSS[name] = minCss(s)
})
}
storageLocalSet({conf, languageList, dialogCSS, dictionaryCSS}).catch(err => debug(`save error: ${err}`))
await storageSyncGet(['setting', 'searchText']).then(function (r) {
saveSettingAll(r.setting, true) // 初始设置参数
searchList = getSearchList(r.searchText || searchText)
if (!r.searchText) saveSearchText('') // 如果为空,设置默认值
})
// 最大保存历史记录数
if (localStorage['historyMax']) historyMax = Number(localStorage['historyMax'])
// 加载 js
loadJs(uniqueArray(Object.keys(conf.translateList).concat(Object.keys(conf.translateTTSList))), 'translate')
loadJs(Object.keys(conf.dictionaryList), 'dictionary')
// 添加菜单
setting.searchMenus.forEach(name => {
let url = searchList[name]
url && addMenu(name, name, url)
})
// 初始数据库
idb('favorite', 1, initFavorite) // 否则第一次安装时,"我的收藏"需要刷新一下才能正常看到数据。
// 查看全部数据
storageShowAll()
})
// 添加上下文菜单
B.contextMenus.create({
title: "梦想翻译“%s”",
contexts: ["selection"],
onclick: function (info, tab) {
if (!info.selectionText) return
let msg = {action: 'contextMenus', text: info.selectionText}
if (tab && tab.id > 0) {
sendTabMessage(tab.id, msg)
} else {
getActiveTabId().then(tabId => sendTabMessage(tabId, msg))
}
}
})
// 监听消息
B.onMessage.addListener(function (m, sender, sendResponse) {
sendResponse()
debug('request:', m)
debug('sender:', sender && sender.url ? sender.url : sender)
let tabId = getJSONValue(sender, 'tab.id')
if (!tabId) tabId = 'popup'
if (m.action === 'translate') {
createHistory(m) // 保存历史记录
runTranslate(tabId, m)
} else if (m.action === 'translateTTS') {
runTranslateTTS(tabId, m)
} else if (m.action === 'dictionary') {
runDictionary(tabId, m)
} else if (m.action === 'playSound') {
runPlaySound(tabId, m)
} else if (m.action === 'menu') {
changeMenu(m.name, m.isAdd)
} else if (m.action === 'saveSetting') {
saveSettingAll(m.setting, m.updateIcon, m.resetDialog)
} else if (m.action === 'copy') {
execCopy(m.text) // 后台复制,页面才不会失去焦点
} else if (m.action === 'transWindow') {
openTransWindow()
} else if (m.action === 'onRecord') {
openRecord()
} else if (m.action === 'openUrl') {
openTab(m.url)
} else if (m.action === 'onAllowSelect') {
sendAllowSelect()
} else if (m.action === 'onCropImg') {
cropImageSendMsg()
} else if (m.action === 'onSaveSearchText') {
saveSearchText(m.searchText)
} else if (m.action === 'onCapture') {
setTimeout(_ => capturePic(sender.tab, m), 100)
} else if (m.action === 'img2text') {
getOcrText(tabId, m.base64).catch()
} else if (m.action === 'textTmp') {
textTmp = m.text // 划词文字缓存
}
})
// 监听快捷键
B.commands.onCommand.addListener(function (command) {
command = command + ''
if (command === 'openWindow') {
openTransWindow()
} else if (command === 'cropImage') {
cropImageSendMsg()
} else if (command === 'stopPlayAudio') {
stopAudio()
} else if (command === 'clipboardTrans') {
clipboardTrans()
} else if (command === 'toggleScribble') {
let scribbleTmp = window.scribbleTmp || 'off'
if (['direct', 'clickIcon'].includes(setting.scribble)) scribbleTmp = 'off';
else if (setting.scribble === 'off' && scribbleTmp === 'off') scribbleTmp = 'direct';
[setting.scribble, scribbleTmp] = [scribbleTmp, setting.scribble] // 交换
window.scribbleTmp = scribbleTmp
saveSettingAll(setting, true) // 保存
}
})
async function runTranslate(tabId, m) {
let {action, text, srcLan, tarLan} = m
if (srcLan === 'auto') {
srcLan = await autoLang(text)
if (srcLan === tarLan) tarLan = srcLan === 'zh' ? 'en' : 'zh'
} else if (setting.autoLanguage) {
if (/\p{Script=Han}/u.test(text)) srcLan = 'zh'
if (srcLan === tarLan) tarLan = srcLan === 'zh' ? 'en' : 'zh'
}
setting.translateList.forEach(name => {
sdkInit(`${name}Translate`).then(sd => {
sd.query(text, srcLan, tarLan).then(result => {
debug(`${name}:`, result)
sandFgMessage(tabId, {action, name, result})
}).catch(error => {
sandFgMessage(tabId, {action, name, text, error})
})
// 链接
let link = sd.link(text, srcLan, tarLan)
sandFgMessage(tabId, {action: 'link', type: action, name, link})
})
})
// 自动朗读
autoPlayTTS(tabId, text, srcLan).then(_ => null)
}
function runTranslateTTS(tabId, m) {
let list = conf.translateList
let tList = conf.translateTTSList
let {name, type, text, lang} = m
let message = {action: 'playSound', nav: 'translate', name, type, status: 'end'}
playTTS(name === setting.localSoundReplace ? 'local' : name, text, lang).then(() => {
sandFgMessage(tabId, message)
}).catch(err => {
debug(`${name} sound error:`, err)
let errMsg = `${tList[name] ? tList[name] : list[name] + '朗读'}出错`
sandFgMessage(tabId, Object.assign({}, message, {error: errMsg}))
})
}
function runDictionary(tabId, m) {
let {action, text} = m
window.dictionarySounds = {} // 返回的发音缓存
setting.dictionaryList.forEach(name => {
sdkInit(`${name}Dictionary`).then(sd => {
sd.query(text).then(result => {
debug(`${name}:`, result)
let {sound} = result
if (sound && sound.length > 0) dictionarySounds[name] = sound // 记录发音
sandFgMessage(tabId, {action, name, result})
}).catch(error => {
sandFgMessage(tabId, {action, name, text, error})
})
// 链接
sandFgMessage(tabId, {action: 'link', type: action, name, link: sd.link(text)})
})
})
// 自动朗读
setTimeout(() => {
autoPlayAudio(tabId, text).then(_ => null)
}, 300)
}
function runPlaySound(tabId, m) {
let {action, nav, name, type, url} = m
playAudio(url).then(() => {
sandFgMessage(tabId, {action, nav, name, type, status: 'end'})
}).catch(err => {
debug(`${name} sound error:`, err)
let title = conf.dictionaryList[name] || conf.translateList[name] || ''
sandFgMessage(tabId, {action, nav, name, type, error: `${title}发音出错`})
})
}
function cropImageSendMsg() {
getActiveTabId().then(tabId => tabId && sendTabMessage(tabId, {action: 'onCrop'}))
}
function capturePic(tab, m) {
B.tabs.captureVisibleTab(tab.windowId, {}, function (data) {
let im = document.createElement("img")
im.onload = function () {
let ca = document.createElement("canvas")
ca.width = m.width
ca.height = m.height
let ca2d = ca.getContext("2d")
let t = im.height / m.innerHeight
ca2d.drawImage(im, m.startX * t, m.startY * t, m.width * t, m.height * t, 0, 0, m.width, m.height)
let b = ca.toDataURL("image/jpeg")
getOcrText(tab.id, b).catch()
}
im.src = data
})
}
// 图片文字识别
async function getOcrText(tabId, base64) {
// 获取 token
let access_token = ''
await getOcrToken().then(token => {
access_token = token
}).catch(err => {
sandFgMessage(tabId, {action: 'onAlert', message: err, type: 'error'})
})
if (!access_token) return
// see https://cloud.baidu.com/doc/OCR/s/zk3h7xz52
let url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=' + access_token
let b = base64.substr(base64.indexOf(",") + 1)
let p = new URLSearchParams(`image=${encodeURIComponent(b)}&detect_language=true&language_type=${setting.translateOCR || 'CHN_ENG'}`)
httpPost({url, body: p.toString()}).then(r => {
let wordsRes = getJSONValue(r, 'words_result')
if (wordsRes && wordsRes.length > 0) {
let text = ''
for (let v of wordsRes) text += v.words + '\n'
sandFgMessage(tabId, {action: 'contextMenus', text: text.trim()})
} else {
sandFgMessage(tabId, {action: 'onAlert', message: '百度图片识别失败', type: 'error'})
}
}).catch(e => {
sandFgMessage(tabId, {action: 'onAlert', message: '百度图片识别 API 出错', type: 'error'})
debug('baidu ocr error:', e)
})
}
function getOcrToken() {
return new Promise((resolve, reject) => {
if (localStorage['clearOcrExpires'] !== 'true') {
localStorage['clearOcrExpires'] = 'false'
ocrExpires = 0
}
if (ocrExpires - 10 > getTimestamp()) return resolve(ocrToken)
if (setting.ocrType === 'baidu') {
let url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${setting.baidu_orc_ak}&client_secret=${setting.baidu_orc_sk}`
httpGet(url, 'json').then(r => {
if (r.expires_in > 0 && r.access_token) {
ocrExpires = getTimestamp() + r.expires_in
ocrToken = r.access_token
resolve(ocrToken)
} else if (r.error_description) {
reject(r.error_description)
} else {
reject('请求百度接口网路错误')
}
}).catch(e => {
debug('aip.baidubce.com api error!', e)
reject('百度获取 TOKEN 接口错误')
})
} else {
httpGet('https://mengxiang.net/api/getBaiduOcrToken.json', 'json').then(r => {
if (r.expires > 0 && r.token) {
ocrExpires = r.expires
ocrToken = r.token
resolve(ocrToken)
} else {
reject('请求官方接口网路错误')
}
}).catch(e => {
debug('mengxiang.net api error!', e)
reject('获取免费 TOKEN 接口错误')
})
}
})
}
function saveSearchText(s) {
if (!s) s = searchText
storageSyncSet({searchText: s})
searchList = getSearchList(s)
}
function saveSettingAll(data, updateIcon, resetDialog) {
setting = Object.assign({}, conf.setting, data)
updateIcon && changeBrowserIcon(setting.scribble) // 是否显示关闭划词图标
let options = resetDialog ? {setting, dialogConf: {}} : {setting}
if (resetDialog) saveSearchText('')
storageSyncSet(options)
}
function changeBrowserIcon(scribble) {
B.browserAction.setIcon({path: `icon/128${scribble === 'off' ? '_off' : ''}.png`})
// setBrowserAction(scribble === 'off' ? 'OFF' : '')
}
function setBrowserAction(text) {
B.browserAction.setBadgeText({text: text || ''})
B.browserAction.setBadgeBackgroundColor({color: 'red'})
isFirefox && B.browserAction.setBadgeTextColor({color: 'white'})
}
function changeMenu(name, isAdd) {
let url = searchList[name]
if (url) isAdd ? addMenu(name, name, url) : removeMenu(name)
}
function addMenu(name, title, url) {
// {type: "separator"}
let mid = md5(name)
B.contextMenus.create({
id: 'page_' + mid,
title: title + '首页',
contexts: ["page"],
onclick: function () {
B.tabs.create({url: (new URL(url)).origin})
}
})
B.contextMenus.create({
id: 'selection_' + mid,
title: title + "“%s”",
contexts: ["selection"],
onclick: function (info) {
B.tabs.create({url: url.format(decodeURIComponent(info.selectionText))})
}
})
}
function removeMenu(name) {
let mid = md5(name)
B.contextMenus.remove('page_' + mid)
B.contextMenus.remove('selection_' + mid)
}
function openTransWindow() {
openWindow('trans', 600, 520, B.root + 'html/popup.html?fullscreen=1')
}
function clipboardTrans() {
openWindow('trans', 600, 520, B.root + 'html/popup.html?fullscreen=1&clipboardRead=1', true)
}
function openRecord() {
openWindow('record', 600, 520, B.root + 'html/record.html', true)
}
function openWindow(wid, width, height, url, reopen) {
let name = `_window_${wid}`
let openFn = function (width, height, left, top) {
let o = {type: 'popup', width, height, url}
// 居中
let screen = window.screen
o.left = Math.floor(left > 0 ? left : (screen.width - o.width) / 2)
o.top = Math.floor(top > 0 ? top : (screen.height - o.height) / 2)
B.windows.create(o, w => window[name] = w.id)
}
let id = window[name]
if (id) {
B.windows.get(id, function (w) {
if (!B.runtime.lastError && w.id) {
if (reopen) {
B.windows.remove(w.id)
setTimeout(() => openFn(w.width, w.height, w.left, w.top), 100)
} else {
B.windows.update(w.id, {focused: true})
}
} else {
openFn(width, height)
}
})
} else {
openFn(width, height)
}
}
function openTab(url) {
B.tabs.create({url})
}
function sendAllowSelect() {
getActiveTabId().then(tabId => {
tabId && sendTabMessage(tabId, {action: 'allowSelect'})
})
}
// 保存历史记录
function createHistory(m) {
if (historyMax < 1) return // 如果为 0,则不再保存历史记录
let {text, formUrl, formTitle} = m
// 空内容不保存
text = text.trim()
if (!text) return
// 排除和最后一次记录相同内容
if (window.lastHistory === text) return
window.lastHistory = text
// 排除扩展内查询
if (formUrl) {
if (formUrl.indexOf(B.root + 'html/favorite.html') === 0) return
if (formUrl.indexOf(B.root + 'html/history.html') === 0) return
}
idb('history', 1, initHistory).then(db => {
db.create('history', {
content: text,
formTitle: formTitle || '',
formUrl: formUrl || '',
createDate: new Date().toJSON(),
}).then(() => {
// 清理早期数据
db.count('history').then(n => {
if (n <= historyMax) return
db.find('history', {direction: 'prev', offset: historyMax}).then(arr => {
for (let v of arr) db.delete('history', v.id)
})
})
}).catch(e => {
debug('history create error:', e)
})
})
}
// 历史记录设置
function settingHistory(n) {
historyMax = Number(n)
localStorage.setItem('historyMax', n)
}
function minCss(s) {
s = s.replace(/\/\*.*?\*\//g, '')
s = s.replace(/\s+/g, ' ')
s = s.replace(/\s*([:;{}!,])\s*/g, '$1')
s = s.replace(/;}/g, '}')
s = s.replace(/;}/g, '}')
return s
}
async function autoLang(text) {
let lang = 'en' // 默认值
await httpPost({
url: `https://fanyi.baidu.com/langdetect`,
body: `query=${encodeURI(text)}`
}).then(r => {
if (r && r.lan) lang = r.lan
}).catch(err => {
debug(err)
})
return lang
}
async function autoPlayTTS(tabId, text, lang) {
let list = conf.translateTTSList || {}
let arr = setting.translateTTSList || []
if (lang === 'auto') lang = await autoLang(text)
for (let name of arr) {
let message = {action: 'playSound', nav: 'translate', name, type: 'source', status: 'end'}
await sandFgMessage(tabId, Object.assign({}, message, {status: 'start'}))
await playTTS(name, text, lang).then(() => {
sandFgMessage(tabId, message)
}).catch(err => {
debug(`${name} sound error:`, err)
sandFgMessage(tabId, Object.assign({}, message, {error: `${list[name] || '发音'}出错`}))
})
}
}
function playTTS(name, text, lang) {
return new Promise((resolve, reject) => {
stopAudio()
sdkInit(`${name}Translate`).then(sd => {
sd.tts(text, lang).then(val => {
if (name === 'local') return resolve()
if (Array.isArray(val)) {
(async function () {
let ok = false
let err = new Error()
for (let i = 0; i < val.length; i++) {
await playAudio(val[i]).then(() => {
if (!ok) ok = true // 为更好的兼容,只要有一次播放成功就算播放成功
}).catch(e => {
err = e
})
}
ok ? resolve() : reject(err)
})()
} else {
playAudio(val).then(() => {
resolve()
}).catch(err => {
reject(err)
})
}
}).catch(err => {
reject(err)
})
})
})
}
async function autoPlayAudio(tabId, text) {
let list = conf.dictionaryList || {}
let sounds = window.dictionarySounds
let type = setting.dictionaryReader || 'us'
let arr = setting.dictionarySoundList || []
for (let name of arr) {
let message = {action: 'playSound', nav: 'dictionary', name, type, status: 'end'}
await sandFgMessage(tabId, Object.assign({}, message, {status: 'start'})) // 显示开始朗读图标
let url = ''
if (sounds[name]) {
url = getSoundUrl(sounds[name], type) // 缓存中获取
} else {
let sdkRun = {}
await sdkInit(`${name}Dictionary`).then(r => {
sdkRun = r
})
await sdkRun.query(text).then(r => {
if (r.sound) url = getSoundUrl(r.sound, type) // 接口中获取
}).catch(error => {
debug(`${name} dictionary error:`, error)
})
}
debug('_playAudio_', name, type, url)
if (!url) continue // 没有发音跳过
// 播放声音
await playAudio(url).then(() => {
sandFgMessage(tabId, message)
}).catch(err => {
debug(`${name} sound error:`, err)
sandFgMessage(tabId, Object.assign({}, message, {error: `${list[name] || ''}发音出错`}))
})
}
debug('_playAudio_ finish.')
}
function getSoundUrl(arr, type) {
for (let v of arr) if (v.type === type && !v.isWoman) return v.url
return ''
}
function stopAudio() {
let a = window._Audio
if (a) a.pause()
if (B.tts && B.tts.stop) B.tts.stop()
}
function playAudio(url) {
return new Promise((resolve, reject) => {
if (!window._Audio) window._Audio = new Audio()
let a = window._Audio
let blobUrl = null
if (typeof url === 'string') {
a.src = url
} else if (typeof url === 'object') {
blobUrl = URL.createObjectURL(url)
a.src = blobUrl
} else {
return reject('Audio url error:', url)
}
a.onended = function () {
// if (blobUrl) URL.revokeObjectURL(blobUrl) // 释放内存
resolve()
let url = a.src // 记录最后一次播放的链接
window.audioSrc = {url}
getAudioBlob(url).then(b => window.audioSrc.blob = b)
}
a.onerror = function (err) {
reject(err)
}
let playPromise = a.play()
if (playPromise !== undefined) {
playPromise.catch(err => {
// reject(err)
resolve()
})
}
})
}
// 缓存音频文件
async function getAudioBlob(url, retry) {
let b
retry = retry || 3
for (let i = 0; i < retry; i++) {
await httpGet(url, 'blob').then(blob => {
// console.log(blob)
b = blob
}).catch(err => {
console.warn('httpGet:' + err)
})
if (b) return b
}
return null
}
function sdkInit(name) {
return new Promise((resolve, reject) => {
if (sdk[name]) return resolve(sdk[name])
if (typeof window[name] === 'function') {
sdk[name] = new window[name]().init()
resolve(sdk[name])
} else {
let err = name + ' not exist!'
debug('sdkInit error:', err)
reject(err)
}
})
}
function loadJs(arr, type) {
arr.forEach(k => {
let el = document.createElement("script")
el.type = 'text/javascript'
el.src = `/js/${type || 'translate'}/${k}.js`
document.head.appendChild(el)
})
}
function invertObject(obj) {
let r = {}
for (const [key, value] of Object.entries(obj)) {
r[value] = key
}
return r
}
// 清理元素属性
function cleanAttr(el, attrs) {
el.querySelectorAll('*').forEach(e => {
for (let i = e.attributes.length - 1; i >= 0; i--) {
let v = e.attributes[i]
if (typeof attrs === 'object') {
if (!attrs.includes(v.name)) e.removeAttribute(v.name) // 过滤白名单
} else {
e.removeAttribute(v.name) // 全部删除
}
}
})
}
// 检测返回结果是否正确,如果不正确,则重试
async function checkRetry(callback, times) {
times = times || 3 // 默认 3 次
let isOk = false
let p
for (let i = 0; i < times; i++) {
p = callback(i)
await p.then(r => {
if (r.data && r.data.length > 0) isOk = true
}).catch(_ => null)
if (isOk) return p
await sleep(300)
}
return p
}
/*function openBgPage(id, url, timeout) {
isFirefox ? openIframe(id, url, timeout) : openPopup(id, url, timeout)
}
function removeBgPage(id) {
isFirefox ? removeIframe(id) : removePopup(id)
}
// 打开一个几乎不可见的 popup (此方法只在 macOS 下有用,在 windows 系统下无效,firefox 浏览器也无效)
function openPopup(id, url, timeout) {
timeout = timeout || 20 * 1000 // 默认 20 秒
removePopup(id)
let popupId = `_popup_${id || 'one'}`
B.windows.create({type: 'popup', focused: false, width: 1, height: 1, url}, w => window[popupId] = w.id)
// 定时关闭窗口,减少内存占用
_setTimeout(id, () => {
removePopup(id)
cleanPopup(url) // 清理所有小窗口
}, timeout)
}
function removePopup(id) {
let popupId = `_popup_${id || 'one'}`
let wid = window[popupId]
if (!wid) return
B.windows.remove(wid, () => B.runtime.lastError) // 关闭
window[popupId] = null
}
function cleanPopup(url) {
B.windows.getAll({populate: true}, function (windows) {
let curOri = new URL(url).origin
windows.forEach(w => {
if (w.type === 'popup' && w.width === 1 && w.tabs.length === 1) {
// console.log('url:', w.tabs[0].url)
if (!w.tabs[0].url || new URL(w.tabs[0].url).origin === curOri) {
B.windows.remove(w.id, () => B.runtime.lastError) // 关闭
}
}
})
})
}*/
// 创建一个临时标签
function createTmpTab(id, url, timeout) {
let tabName = `_tmp_tab_${id || 'one'}`
removeTmpTab(id) // 关闭
B.tabs.create({active: false, url}, tab => window[tabName] = tab.id)
if (timeout > 1000) _setTimeout(id, () => removeTmpTab(id), timeout) // 定时关闭窗口,减少内存占用
}
// 关闭临时标签
function removeTmpTab(id) {
let tabName = `_tmp_tab_${id || 'one'}`
let tabId = window[tabName]
if (!tabId) return
B.tabs.remove(tabId, () => B.runtime.lastError) // 关闭
window[tabName] = null
}
// 强制刷新临时标签
function reloadTmpTab(id) {
let tabName = `_tmp_tab_${id || 'one'}`
let tabId = window[tabName]
if (tabId) B.tabs.reload(tabId, {bypassCache: true}, () => B.runtime.lastError) // 重新加载
}
function openIframe(id, url, timeout) {
timeout = timeout || 20 * 1000 // 默认 20 秒
let ifrId = `_iframe_${id || 'one'}`
let el = document.getElementById(ifrId)
if (!el) {
el = document.createElement('iframe')
el.id = ifrId
el.src = url
document.body.appendChild(el)
} else {
el.src = url
}
// 定时删除,减小内存占用
_setTimeout(id, () => el && el.remove(), timeout)
return el
}
function removeIframe(id) {
let ifrId = `_iframe_${id || 'one'}`
let el = document.getElementById(ifrId)
el && el.remove()
}
function sliceStr(text, maxLen) {
let r = []
if (text.length <= maxLen) {
r.push(text)
} else {
// 根据优先级截取字符串,详细符号见:https://zh.wikipedia.org/wiki/%E6%A0%87%E7%82%B9%E7%AC%A6%E5%8F%B7
let separators = `?!;.-…,/"`
separators += `?!;。--_~﹏·,:、`
separators += `“”﹃﹄「」﹁﹂『』﹃﹄()[]〔〕【】《》〈〉()[]{}`
let separatorArr = [...separators]
let arr = text.split('\n')
arr.forEach(s => {
s = s.trim()
if (!s) return
if (s.length <= maxLen) {
r.push(s)
} else {
do {
if (s.length <= maxLen) {
r.push(s)
break
}
let end = false
for (let i = 0; i < separatorArr.length; i++) {
if (i + 1 === separatorArr.length) end = true
let symbol = separatorArr[i]
let n = s.indexOf(symbol)
if (n === -1) continue
if (n > maxLen) continue
let s2 = s.substring(0, n).trim()
s2 && r.push(s2)
s = s.substring(n + 1).trim()
break
}
if (!end) continue
if (!s) break
if (s.length <= maxLen) {
r.push(s)
break
}
let s1 = s.substring(0, maxLen)
let s2 = s.substring(maxLen)
let n = s1.lastIndexOf(' ')
if (n !== -1) {
// 处理英文
let s3 = s1.substring(0, n)
let s4 = s1.substring(n)
r.push(s3)
s = (s4 + s2).trim()
} else {
// 没有空格,就硬切(这种情况一般是中文)
r.push(s1)
s = s2
}
} while (s)
}
})
}
return r
}
================================================
FILE: src/js/common.js
================================================
'use strict'
/**
* Dream Translate
* https://github.com/ryanker/dream_translate
* @Author Ryan <dream39999@gmail.com>
* @license MIT License
*/
/*!
* 浏览器统一兼容
* 参考:
* https://github.com/mozilla/webextension-polyfill
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities
* https://developer.chrome.com/docs/extensions/reference/
* https://crxdoc-zh.appspot.com/extensions/
*/
const isDebug = true
window.isFirefox = navigator.userAgent.includes("Firefox")
// window.isFirefox = typeof browser !== "undefined" && Object.getPrototypeOf(browser) === Object.prototype
const B = {
extension: chrome.extension,
getBackgroundPage: chrome.extension.getBackgroundPage,
windows: chrome.windows,
commands: chrome.commands,
runtime: chrome.runtime,
id: chrome.runtime.id,
root: chrome.runtime.getURL(''),
onMessage: chrome.runtime.onMessage,
sendMessage: chrome.runtime.sendMessage,
storage: chrome.storage,
browserAction: chrome.browserAction,
contextMenus: chrome.contextMenus,
webRequest: chrome.webRequest,
cookies: chrome.cookies,
tabs: chrome.tabs,
tts: chrome.tts,
app: chrome.app,
}
String.prototype.format = function () {
let args = arguments
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined' ? args[number] : match
})
}
function storageLocalGet(options) {
return storage('local', 'get', options)
}
function storageLocalSet(options) {
return storage('local', 'set', options)
}
function storageSyncGet(options) {
return storage('sync', 'get', options)
}
function storageSyncSet(options) {
return storage('sync', 'set', options)
}
function storageShowAll() {
if (!isDebug) return
!isFirefox && storageSyncGet(null).then(function (r) {
debug(`all sync storage:`, r)
})
storageLocalGet(null).then(function (r) {
debug(`all local storage:`, r)
})
}
function storage(type, method, options) {
return new Promise((resolve, reject) => {
if (!isFirefox) {
if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')
let callback = function (r) {
let err = B.runtime.lastError
err ? reject(err) : resolve(r)
}
let api = type === 'sync' ? B.storage.sync : B.storage.local
if (method === 'get') {
api.get(options, callback)
} else if (method === 'set') {
api.set(options, callback)
}
} else {
let api = isDebug ? browser.storage.local : type === 'sync' ? browser.storage.sync : browser.storage.local
if (method === 'get') {
api.get(options).then(r => resolve(r), err => reject(err))
} else if (method === 'set') {
api.set(options).then(r => resolve(r), err => reject(err))
}
}
})
}
function cookies(method, options) {
return new Promise((resolve, reject) => {
if (!isFirefox) {
if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')
let callback = function (r) {
let err = B.runtime.lastError
err ? reject(err) : resolve(r)
}
if (method === 'get') {
B.cookies.get(options, callback)
} else if (method === 'getAll') {
B.cookies.getAll(options, callback)
} else if (method === 'set') {
B.cookies.set(options, callback)
} else if (method === 'remove') {
B.cookies.remove(options, callback)
}
} else {
if (method === 'get') {
browser.cookies.get(options).then(r => resolve(r), err => reject(err))
} else if (method === 'getAll') {
browser.cookies.getAll(options).then(r => resolve(r), err => reject(err))
} else if (method === 'set') {
browser.cookies.set(options).then(r => resolve(r), err => reject(err))
} else if (method === 'remove') {
browser.cookies.remove(options).then(r => resolve(r), err => reject(err))
}
}
})
}
function sendMessage(message) {
return new Promise((resolve, reject) => {
if (!isFirefox) {
if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')
B.sendMessage(message, r => B.runtime.lastError ? reject(B.runtime.lastError) : resolve(r))
} else {
browser.runtime.sendMessage(message).then(r => resolve(r), err => reject(err))
}
})
}
function sendTabMessage(tabId, message) {
return new Promise((resolve, reject) => {
if (!isFirefox) {
if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')
tabId && B.tabs.sendMessage(tabId, message, r => B.runtime.lastError ? reject(B.runtime.lastError) : resolve(r))
} else {
tabId && browser.tabs.sendMessage(tabId, message).catch(err => debug('send error:', err))
}
resolve()
})
}
function sandFgMessage(id, message) {
if (id === 'popup') {
let popup = B.extension.getViews({type: 'popup'})
if (popup.length > 0) {
return sendMessage(message)
} else {
return Promise.resolve()
}
} else {
return sendTabMessage(id, message)
}
}
function getActiveTabId() {
return new Promise((resolve, reject) => {
if (!isFirefox) {
B.tabs.query({currentWindow: true, active: true}, tab => {
resolve(getJSONValue(tab, '0.id')) // todo: 还有优化空间
})
} else {
browser.tabs.query({currentWindow: true, active: true}).then(tab => {
let tabId = tab[0] && resolve(tab[0].id)
resolve(tabId)
}, err => reject(err))
}
})
}
function onBeforeSendHeadersAddListener(callback, filter, opt_extraInfoSpec) {
if (!opt_extraInfoSpec) opt_extraInfoSpec = Object.values(B.webRequest.OnBeforeSendHeadersOptions)
B.webRequest.onBeforeSendHeaders.addListener(callback, filter, opt_extraInfoSpec)
}
function onBeforeSendHeadersRemoveListener(callback) {
B.webRequest.onBeforeSendHeaders.removeListener(callback)
}
function requestHeadersFormat(s) {
let r = []
let arr = s.split('\n')
arr && arr.forEach(v => {
v = v.trim()
if (!v) return
let a = v.split(': ')
if (a.length === 2) r.push({name: a[0].trim(), value: a[1].trim()})
})
return r
}
function onBeforeRequestAddListener(callback, filter, extraInfoSpec) {
if (!extraInfoSpec) {
extraInfoSpec = ["blocking", "extraHeaders", "requestBody"] // 解决 chrome 审核机制太垃圾,提示没有使用到 webRequestBlocking
extraInfoSpec = Object.values(B.webRequest.OnBeforeRequestOptions)
}
B.webRequest.onBeforeRequest.addListener(callback, filter, extraInfoSpec)
}
function onBeforeRequestRemoveListener(callback) {
B.webRequest.onBeforeRequest.removeListener(callback)
}
function onHeadersReceivedAddListener(callback, filter, extraInfoSpec) {
if (!extraInfoSpec) extraInfoSpec = Object.values(B.webRequest.OnHeadersReceivedOptions)
B.webRequest.onHeadersReceived.addListener(callback, filter, extraInfoSpec)
}
function onHeadersReceivedRemoveListener(callback) {
B.webRequest.onHeadersReceived.removeListener(callback)
}
function onCompletedAddListener(callback, filter, extraInfoSpec) {
if (!extraInfoSpec) extraInfoSpec = Object.values(B.webRequest.OnCompletedOptions)
B.webRequest.onCompleted.addListener(callback, filter, extraInfoSpec)
}
function onCompletedRemoveListener(callback) {
B.webRequest.onCompleted.removeListener(callback)
}
function onRemoveFrame(details) {
let headers = Object.assign([], details.responseHeaders)
for (let i = 0; i < headers.length; i++) {
let name = headers[i].name.toLowerCase()
if (name.includes('frame-options') || name.includes('content-security-policy')) {
headers.splice(i, 1)
// break
}
}
return {responseHeaders: headers}
}
function onRemoveCross(details) {
let headers = Object.assign([], details.responseHeaders)
for (let i = 0; i < headers.length; i++) {
let name = headers[i].name.toLowerCase()
if (name.includes('cross-origin-resource-policy') || name.includes('cross-origin-opener-policy')) {
headers.splice(i, 1)
// break
}
}
return {responseHeaders: headers}
}
// 获得所有语音的列表 (firefox 不支持)
function getVoices() {
return new Promise((resolve, reject) => {
if (!B.tts || !B.tts.getVoices) return reject("I won't support it!")
B.tts.getVoices(function (voices) {
let list = {}
for (let i = 0; i < voices.length; i++) {
// debug('Voice ' + i + ':', JSON.stringify(voices[i]))
let v = voices[i]
if (!list[v.lang]) list[v.lang] = []
list[v.lang].push({lang: v.lang, voiceName: v.voiceName, remote: v.remote})
}
resolve(list)
})
})
}
function getTimestamp() {
return Date.parse(new Date() + '') / 1000
}
function addClass(el, className) {
if (!el || !className) return
className = className.trim()
let oldClassName = el.className.trim()
if (!oldClassName) {
el.className = className
} else if (` ${oldClassName} `.indexOf(` ${className} `) === -1) {
el.className += ' ' + className
}
}
function rmClass(el, className) {
if (!el.className) return
className = className.trim()
let newClassName = el.className.trim()
if ((` ${newClassName} `).indexOf(` ${className} `) === -1) return
newClassName = newClassName.replace(new RegExp('(?:^|\\s)' + className + '(?:\\s|$)', 'g'), ' ').trim()
if (newClassName) {
el.className = newClassName
} else {
el.removeAttribute('class')
}
}
function hasClass(el, className) {
if (!el.className) return false
return (` ${el.className.trim()} `).indexOf(` ${className.trim()} `) > -1
}
function sleep(delay) {
return new Promise(r => setTimeout(r, delay))
}
function getDate(value, isDate) {
let d = value ? new Date(value) : new Date()
d.setMinutes(-d.getTimezoneOffset() + d.getMinutes(), d.getSeconds(), 0)
let s = d.toISOString()
if (isDate) {
s = s.substring(0, 10)
} else {
s = s.replace('T', ' ')
s = s.replace('.000Z', '')
}
return s
}
// 补零
function zero(value, digits) {
digits = digits || 2
let isNegative = Number(value) < 0
let s = value.toString()
if (isNegative) s = s.slice(1)
let size = digits - s.length + 1
s = new Array(size).join('0').concat(s)
return (isNegative ? '-' : '') + s
}
function $(id) {
return document.getElementById(id)
}
function N(name) {
return document.getElementsByName(name)
}
function S(s) {
return document.querySelector(s)
}
function D(s) {
return document.querySelectorAll(s)
}
function onD(el, type, listener, options) {
el.forEach(v => {
v.addEventListener(type, listener, options)
})
}
function unD(el, type, listener, options) {
el.forEach(v => {
v.removeEventListener(type, listener, options)
})
}
function removeD(el) {
el.forEach(e => e.remove())
}
function rmClassD(el, className) {
el.forEach(v => rmClass(v, className))
}
function inArray(val, arr) {
// return arr.indexOf(val) !== -1
return arr.includes(val)
}
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]'
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]'
}
function isString(o) {
return Object.prototype.toString.call(o) === '[object String]'
}
function isNumber(o) {
return Object.prototype.toString.call(o) === '[object Number]'
}
function isDate(o) {
return Object.prototype.toString.call(o) === '[object Date]'
}
function isRegExp(o) {
return Object.prototype.toString.call(o) === '[object RegExp]'
}
function isError(o) {
return Object.prototype.toString.call(o) === '[object Error]'
}
function isSymbol(o) {
return Object.prototype.toString.call(o) === '[object Symbol]'
}
function isArrayBuffer(o) {
return Object.prototype.toString.call(o) === '[object ArrayBuffer]'
}
function isFunction(o) {
return Object.prototype.toString.call(o) === '[object Function]'
}
function getSearchList(s) {
s = s.trim()
let arr = s.split('\n')
let r = {}
for (let v of arr) {
v = v.trim()
let a = v.split('|')
let key = a[0] && a[0].trim()
let val = a[1] && a[1].trim()
if (key && val) r[key] = val
}
return r
}
// 解决 JSON 太深问题
function getJSONValue(data, keys, value) {
// if (!data || !isObject(data)) return value // 默认值
if (!data) return value // 默认值
keys = keys.trim()
let arr = keys.split('.')
let val = Object.assign({}, data)
for (let key of arr) {
if (!val[key]) return value // 默认值
val = val[key]
}
return val
}
// 添加 DOM 元素
function addEl(options) {
let {tagName, id, className, text, title, onClick} = options
let el = document.createElement(tagName)
if (id) el.id = id
if (className) el.className = className
if (text) el.textContent = text
if (title) el.title = title
if (onClick) el.addEventListener('click', onClick)
return el
}
function createTextarea() {
let t = document.createElement("textarea")
t.style.position = 'fixed'
t.style.top = '-200%'
document.body.appendChild(t)
return t
}
function execCopy(s) {
let t = createTextarea()
t.value = s
t.select()
document.execCommand("copy")
document.body.removeChild(t)
}
function execPaste() {
let t = createTextarea()
t.focus()
document.execCommand("paste")
let v = t.value
document.body.removeChild(t)
return v
}
// dream alert
function dal(text, type, onSubmit) {
let icon = {
info: '<i class="dmx-icon dmx-icon-info"></i>',
error: '<i class="dmx-icon dmx-icon-close"></i>',
success: '<i class="dmx-icon dmx-icon-success"></i>',
}
D('.dal_bg,.dal').forEach(e => e.remove()) // 只允许存在一个
document.body.insertAdjacentHTML('beforeend', `<div class="dal_bg"></div>
<div class="dal">
<div class="dal_modal">
<div class="dal_text">${(icon[type] || icon.info) + text}</div>
<div class="dal_foot">
<button class="dmx_button" data-type="submit">确认</button>
</div>
</div>
</div>`)
let rmFn = () => D('.dal_bg,.dal').forEach(e => e.remove())
S('[data-type="submit"]').addEventListener('click', rmFn)
if (typeof onSubmit === 'function') S('[data-type="submit"]').addEventListener('click', onSubmit)
}
// dream confirm
function dco(text, onSubmit, onCancel) {
D('.dal_bg,.dal').forEach(e => e.remove()) // 只允许存在一个
document.body.insertAdjacentHTML('beforeend', `<div class="dal_bg"></div>
<div class="dal">
<div class="dal_modal">
<div class="dal_text"><i class="dmx-icon dmx-icon-info"></i>${text}</div>
<div class="dal_foot">
<button class="dmx_button" data-type="submit">确认</button>
<button class="dmx_button dmx_button_default" data-type="cancel">取消</button>
</div>
</div>
</div>`)
let submitEl = S('[data-type="submit"]')
let cancelEl = S('[data-type="cancel"]')
let rmFn = () => D('.dal_bg,.dal').forEach(e => e.remove())
submitEl.addEventListener('click', rmFn)
cancelEl.addEventListener('click', rmFn)
if (typeof onSubmit === 'function') submitEl.addEventListener('click', onSubmit)
if (typeof onCancel === 'function') cancelEl.addEventListener('click', onCancel)
}
// dream dialog
function ddi(option) {
let o = Object.assign({
title: '',
body: '',
fullscreen: false,
onClose: null,
}, option || {})
let el = S('.ddi .ddi_body')
if (el) {
el.innerHTML = o.body
} else {
document.body.insertAdjacentHTML('beforeend', `<div class="ddi_bg"></div>
<div class="ddi">
<div class="ddi_modal ddi_dialog${o.fullscreen ? ' fullscreen' : ''}">
<div class="ddi_head">${o.title}<i class="dmx-icon dmx-icon-close"></i></div>
<div class="ddi_body">${o.body}</div>
</div>
</div>`)
addClass(document.body, 'dmx_overflow_hidden')
S('.ddi_head .dmx-icon-close').addEventListener('click', () => {
rmClass(document.body, 'dmx_overflow_hidden')
removeDdi()
if (typeof o.onClose === 'function') o.onClose()
})
}
}
// remove dream dialog
function removeDdi() {
rmClass(document.body, 'dmx_overflow_hidden')
D('.ddi_bg,.ddi').forEach(e => e.remove())
}
function loading(text) {
document.body.insertAdjacentHTML('beforeend', `<div class="ddi_bg"></div>
<div class="ddi">
<div class="ddi_loading">
<div class="ddi_loading_inner"></div>
<div class="mt_2">${text || 'loading...'}</div>
</div>
</div>`)
}
// 过滤 HTML,防止XSS
function HTMLEncode(s) {
let d = document.createElement('div')
d.textContent = s
return d.innerHTML || ''
}
function uniqueArray(arr) {
return [...new Set(arr)]
}
function httpGet(url, type, headers, notStrict) {
return new Promise((resolve, reject) => {
let c = new XMLHttpRequest()
c.responseType = type || 'text'
c.timeout = 20000
c.onload = function (e) {
if (notStrict) {
resolve(this.response)
} else {
if (this.status === 200) {
resolve(this.response)
} else {
reject(e)
}
}
}
c.ontimeout = function (e) {
reject(e)
}
c.onerror = function (e) {
reject(e)
}
c.open("GET", url)
headers && headers.forEach(v => {
c.setRequestHeader(v.name, v.value)
})
c.send()
})
}
function httpPost(options) {
let o = Object.assign({
url: '',
responseType: 'json',
type: 'form',
body: null,
timeout: 30000,
headers: [],
}, options)
return new Promise((resolve, reject) => {
let c = new XMLHttpRequest()
c.responseType = o.responseType
c.timeout = o.timeout
c.onload = function (e) {
if (this.status === 200 && this.response !== null) {
resolve(this.response)
} else {
reject(e)
}
}
c.ontimeout = function (e) {
reject(e)
}
c.onerror = function (e) {
reject(e)
}
c.open("POST", o.url)
if (o.type === 'form') {
c.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
} else if (o.type === 'json') {
c.setRequestHeader("Content-Type", "application/json; charset=UTF-8")
} else if (o.type === 'xml') {
c.setRequestHeader("Content-Type", "application/ssml+xml")
}
o.headers.length > 0 && o.headers.forEach(v => {
c.setRequestHeader(v.name, v.value)
})
c.send(o.body)
})
}
// 时间范围内,只执行最后一次回调函数
function _setTimeout(tid, callback, timeout) {
tid = `mx_timeoutId_${tid}`
_clearTimeout(tid)
return window[tid] = setTimeout(callback, timeout)
}
function _clearTimeout(tid) {
let id = window[tid]
if (id) {
clearTimeout(id)
window[tid] = null
}
}
function encodeURI(s) {
s = encodeURIComponent(s)
s = s.replace(/#/g, '%23')
s = s.replace(/&/g, '%26')
return s
}
function debug(...data) {
isDebug && console.log('[DMX DEBUG]', ...data)
}
================================================
FILE: src/js/content.js
================================================
'use strict'
/**
* Dream Translate
* https://github.com/ryanker/dream_translate
* @Author Ryan <dream39999@gmail.com>
* @license MIT License
*/
let isPopup = window.isPopup
let isFullscreen, isClipboardRead, isSome
let dialog, shadow,
setting, conf, dialogConf,
languageList, dialogCSS = '', dictionaryCSS = {},
iconBut, iconText,
msgList = {},
root = B.root
let dQuery = {action: '', text: '', source: '', target: ''}
let textTmp = ''
let history = [], historyIndex = 0, disHistory = false
let searchText
document.addEventListener('DOMContentLoaded', async function () {
let u = new URL(location.href)
isFullscreen = u.searchParams.get('fullscreen') === '1'
isClipboardRead = u.searchParams.get('clipboardRead') === '1'
isSome = location.href.indexOf(root) === 0
await storageLocalGet(['conf', 'languageList', 'dialogCSS', 'dictionaryCSS']).then(function (r) {
conf = r.conf
languageList = JSON.parse(r.languageList)
dialogCSS = r.dialogCSS
dictionaryCSS = r.dictionaryCSS
})
await storageSyncGet(['setting', 'dialogConf', 'searchText']).then(function (r) {
setting = r.setting
dialogConf = Object.assign({}, conf.dialogConf, r.dialogConf)
searchText = r.searchText
})
// 初始对话框
initDialog()
// 初始对话框CSS
initDictionaryCSS()
// 是否开启自动解除选中现在
if (setting.allowSelect === 'on' && !isSome) allowUserSelect()
// 查看全部数据
storageShowAll()
})
// 监听消息
B.onMessage.addListener(function (m, sender, sendResponse) {
sendResponse()
debug('request:', m)
// debug('sender:', sender)
if (m.action === 'translate') {
msgList[m.name] = m.result
resultTranslate(m.name)
} else if (m.action === 'dictionary') {
resultDictionary(m)
} else if (m.action === 'playSound') {
resultSound(m)
} else if (m.action === 'link') {
resultLink(m)
} else if (m.action === 'allowSelect') {
allowUserSelect()
} else if (m.action === 'onCrop') {
initCrop()
} else if (m.action === 'onAlert') {
dmxAlert(m.message, m.type)
} else if (m.action === 'contextMenus') {
sendQuery(m.text) // 右键查询
showDialog()
}
})
// 监听 frame 消息
window.addEventListener("message", function (m) {
let d = m.data
if (d.text && typeof d.clientX === 'number' && typeof d.clientY === 'number') initQuery(d.text, d.clientX, d.clientY)
})
// 监听设置修改
B.storage.onChanged.addListener(function (data) {
let keys = Object.keys(data)
keys.forEach(k => {
let v = data[k].newValue
if (k === 'setting') {
setting = v
debug('new setting:', v)
// 初始对话框CSS
initDictionaryCSS()
} else if (k === 'searchText') {
searchText = v
debug('new searchText:', v)
}
})
})
// 初始对话框
function initDialog() {
let isChange = false
let options = {
cssText: dialogCSS,
width: dialogConf.width,
height: dialogConf.height,
minWidth: 450,
onResize: function (style) {
const {width, height} = style
if (width) dialogConf.width = width
if (height) dialogConf.height = height
isChange = true
}
}
if (isPopup) {
options.width = 'auto'
options.height = 'auto'
options.show = true
options.autoHide = false
options.isMove = false
options.isResize = false
options.onResize = null
}
dialog = dmxDialog(options)
// 保存窗口大小
dialog.el.addEventListener('mouseup', function () {
if (isChange) {
saveDialogConf()
isChange = false
}
})
// 影子元素
shadow = dialog.shadow
// 小屏窗口
if (isPopup) {
addClass(dialog.el, 'dmx_popup')
isFullscreen ? addClass(dialog.el, 'fullscreen') : addClass(document.documentElement, 'dmx_popup')
A('#dmx_close,#dmx_pin,#dmx_fullscreen').forEach(e => e.remove())
if (B.getBackgroundPage) textTmp = B.getBackgroundPage().textTmp // 读取后台缓存
}
// 划词查询
document.addEventListener('mouseup', function (e) {
let text = window.getSelection().toString().trim()
initQuery(text, e.clientX, e.clientY)
})
// 鼠标图标
iconBut = I('dmx_mouse_icon')
iconBut.onclick = function (e) {
iconBut.style.display = 'none'
sendQuery(iconText) // 点击图标查询
showDialog(e.clientX + 10, e.clientY - 35)
}
iconBut.onmousedown = function (e) {
e.preventDefault()
}
// 绑定事件
let nav = I('dmx_navigate')
let uEl = nav.querySelectorAll('u')
uEl.forEach(e => {
e.addEventListener('click', function () {
let action = this.getAttribute('action')
if (!['translate', 'dictionary', 'search'].includes(action)) return
if (dQuery.action === action) return
rmClassD(uEl, 'active')
addClass(this, 'active')
setDialogConf('action', action) // 保存设置
if (action === 'translate') {
initTranslate()
} else if (action === 'dictionary') {
initDictionary()
} else if (action === 'search') {
initSearch()
}
sendQuery(dQuery.text) // 切换导航查询
})
})
// 初始模块
let action = dialogConf.action
if (action) {
if (!['translate', 'dictionary', 'search'].includes(action)) action = 'translate'
let actionEl = nav.querySelector(`u[action="${action}"]`)
if (actionEl) actionEl.click()
}
// 设置按钮
I('dmx_setting').addEventListener('click', function () {
rmClassD(uEl, 'active')
initSetting()
dQuery.action = 'setting'
})
// 更多功能
I('dmx_more').addEventListener('click', function () {
rmClassD(uEl, 'active')
initMore()
dQuery.action = 'more'
})
// 录音练习
I('dmx_voice').addEventListener('click', function () {
sendMessage({action: 'onRecord'})
})
// 鼠标停留取词
document.addEventListener('mousemove', (e) => {
if (setting.autoWords && setting.scribble !== 'off') _setTimeout('_mouseWords', () => mouseWords(e), 300)
})
// 历史记录
let hEl = I('dmx_history')
let hlEl = hEl.querySelector('.dmx-icon-left')
let hrEl = hEl.querySelector('.dmx-icon-right')
let loadHistory = function (index) {
if (index < 0 || index >= history.length) return
disHistory = true
historyIndex = index
let className = 'disabled'
rmClass(hlEl, className)
rmClass(hrEl, className)
if (index === 0) {
addClass(hlEl, className)
} else if (index === history.length - 1) {
addClass(hrEl, className)
}
let data = history[index]
debug('current:', historyIndex, data, history)
let action = data.action
let text = data.text
dialogConf.action = action
dialogConf.source = data.source
dialogConf.target = data.target
dQuery.action !== action && setDialogConf('action', action) // 保存设置
rmClassD(uEl, 'active')
addClass(nav.querySelector(`u[action="${action}"]`), 'active')
if (action === 'translate') {
initTranslate()
} else if (action === 'dictionary') {
initDictionary()
} else if (action === 'search') {
initSearch()
}
sendQuery(text) // 历史记录查询
}
hlEl.addEventListener('click', () => loadHistory(historyIndex - 1))
hrEl.addEventListener('click', () => loadHistory(historyIndex + 1))
}
function initTranslate() {
let l = languageList, langList = ''
for (let k in l) {
if (l.hasOwnProperty(k)) langList += `<u value="${k}">${l[k].zhName}</u>`
}
dialog.contentHTML(`<div class="dmx_main dmx_main_trans">
<div class="case${isPopup || isFullscreen ? ' big' : ''}" id="translate_input" contenteditable="true"></div>
<div class="language_box fx">
<div id="language_source" class="language_button dmx-icon"></div>
<div id="language_exchange"><i class="dmx-icon dmx-icon-exchange"></i></div>
<div id="language_target" class="language_button dmx-icon"></div>
<div id="translate_button">翻 译</div>
<div id="translate_crop"><i class="dmx-icon dmx-icon-crop"></i></div>
<div id="language_dropdown" class="fx">${langList}</div>
</div>
</div>
<div id="case_list" class="dmx_main fx"></div>`)
// 绑定事件
let sourceEl = I('language_source')
let targetEl = I('language_target')
let exchangeEl = I('language_exchange')
let inputEl = I('translate_input')
let translateEl = I('translate_button')
let cropEl = I('translate_crop')
let dropdownEl = I('language_dropdown')
let dropdownU = dropdownEl.querySelectorAll('u')
let contentEl = I('dmx_dialog_content')
let tmpEl
let onButton = function () {
let el = this
if (tmpEl === el && dropdownEl.style.display === 'block') {
dropdownEl.style.display = 'none'
rmClass(el, 'active')
} else {
tmpEl = el
addClass(el, 'active')
let sourceVal = sourceEl.getAttribute('value')
let targetVal = targetEl.getAttribute('value')
let isSource = el === sourceEl
if (isSource) {
rmClass(targetEl, 'active')
rmClass(dropdownEl, 'dropdown_target')
} else {
rmClass(sourceEl, 'active')
addClass(dropdownEl, 'dropdown_target')
}
dropdownEl.style.display = 'block'
dropdownU.forEach(e => {
rmClass(e, 'active')
rmClass(e, 'disabled')
let val = e.getAttribute('value')
if (isSource) {
if (sourceVal === val) {
addClass(e, 'active')
} else if (targetVal === val) {
addClass(e, 'disabled')
}
} else {
if (targetVal === val) {
addClass(e, 'active')
} else if (sourceVal === val) {
addClass(e, 'disabled')
}
}
})
}
}
sourceEl.addEventListener('click', onButton)
targetEl.addEventListener('click', onButton)
exchangeEl.addEventListener('click', function () {
let sourceVal = sourceEl.getAttribute('value')
let sourceText = sourceEl.innerText
let targetVal = targetEl.getAttribute('value')
let targetText = targetEl.innerText
if (sourceVal === 'auto') return
sourceEl.setAttribute('value', targetVal)
sourceEl.innerText = targetText
targetEl.setAttribute('value', sourceVal)
targetEl.innerText = sourceText
setDialogConf('source', targetVal)
setDialogConf('target', sourceVal)
rmClass(sourceEl, 'active')
rmClass(targetEl, 'active')
dropdownEl.style.display = 'none'
})
translateEl.addEventListener('click', function () {
rmClass(sourceEl, 'active')
rmClass(targetEl, 'active')
dropdownEl.style.display = 'none'
let text = inputEl.innerText.trim()
sendQuery(text) // 翻译按钮查询
})
cropEl.addEventListener('click', function () {
sendBgMessage({action: 'onCropImg'}).then(_ => {
location.href.indexOf(root + 'html/popup.html') === 0 && window.close()
})
})
dropdownU.forEach(e => {
e.addEventListener('click', function () {
let v = this.getAttribute('value')
let s = this.innerText
let isSource = !hasClass(dropdownEl, 'dropdown_target')
let el = isSource ? sourceEl : targetEl
rmClass(el, 'active')
el.setAttribute('value', v)
el.innerText = s
dropdownEl.style.display = 'none'
contentEl.scrollTop = 0
if (isSource) {
(v === 'auto' ? addClass : rmClass)(exchangeEl, 'disabled')
setDialogConf('source', v)
} else {
setDialogConf('target', v)
}
})
})
// 粘贴事件
inputEl.addEventListener('paste', function (e) {
e.stopPropagation()
e.preventDefault()
let d = e.clipboardData || window.clipboardData
if (d && d.items.length > 0) {
let f = d.items[0].getAsFile()
if (f && f.type.indexOf('image') === 0) {
// 如果粘贴内容是图片,进行图片文字识别
let fr = new FileReader()
fr.readAsDataURL(f)
fr.onload = function (e) {
let base64 = e.target.result
sendBgMessage({action: 'img2text', base64})
}
} else {
// 如果是文本,则清理一下
this.innerText = d.getData('Text')
}
}
})
inputEl.addEventListener('blur', function () {
textTmp = this.innerText
})
inputEl.addEventListener('keyup', function () {
if (isPopup && setting.autoConfirm) _setTimeout('translate', () => translateEl.click(), 2000) // 定时自动开始翻译
})
isPopup && focusLast(inputEl) // 光标移到结尾
// 隐藏原文框,减少占用空间
if (setting.hideOriginal) {
if (!isPopup && !isFullscreen) { // 排除弹窗
// translateEl.style.display = 'none'
inputEl.style.display = 'none'
E('.dmx_main_trans').style.paddingTop = '0'
}
}
// 隐藏截图框(在独立窗口的时候)
if (isFullscreen) cropEl.style.display = 'none'
// 初始值
let source = dialogConf.source
let target = dialogConf.target
if (source === 'auto') addClass(exchangeEl, 'disabled')
sourceEl.setAttribute('value', source)
sourceEl.innerText = l[source].zhName
targetEl.setAttribute('value', target)
targetEl.innerText = l[target].zhName
}
function initDictionary() {
dialog.contentHTML(`<div id="dmx_head">
<div class="case search_box">
<input id="dictionary_input" type="text" maxlength="100" autocomplete="off">
<div id="search_remove"><i class="dmx-icon dmx-icon-error"></i></div>
<div id="search_but"><i class="dmx-icon dmx-icon-search"></i></div>
</div>
</div>
<div id="case_list" class="dmx_main dmx_content fx"></div>`)
let inpEl = I('dictionary_input')
let rmEl = I('search_remove')
let butEl = I('search_but')
rmEl.onclick = function () {
inpEl.value = ''
inpEl.focus()
}
butEl.onclick = function () {
let text = inpEl.value.trim()
sendQuery(text) // 词典按钮查询
}
inpEl.addEventListener('change', function () {
textTmp = this.value
})
inpEl.addEventListener('keyup', function (e) {
e.key === 'Enter' && butEl.click()
if (isPopup) _setTimeout('dictionary', () => butEl.click(), 1000) // 定时自动开始查词
})
setTimeout(() => inpEl.focus(), 100)
}
function initSearch() {
dialog.contentHTML(`<div id="dmx_head">
<div class="case search_box">
<input id="search_input" type="text" maxlength="100" autocomplete="off">
<div id="search_remove"><i class="dmx-icon dmx-icon-error"></i></div>
<div id="search_but"><i class="dmx-icon dmx-icon-search"></i></div>
</div>
</div>
<div id="case_list" class="dmx_main dmx_content dmx_main_search fx"></div>`)
let inpEl = I('search_input')
let rmEl = I('search_remove')
let butEl = I('search_but')
rmEl.onclick = function () {
inpEl.value = ''
inpEl.focus()
}
butEl.onclick = function () {
let el = I('case_list').querySelector('[data-search]')
if (el) el.click()
}
inpEl.addEventListener('change', function () {
textTmp = this.value
})
inpEl.addEventListener('keyup', function (e) {
e.key === 'Enter' && butEl.click()
})
setTimeout(() => inpEl.focus(), 100)
// 创建按钮
let s = ''
let sList = setting.searchList
let cList = getSearchList(searchText)
for (let name of sList) {
if (cList[name]) s += `<div class="dmx_button" data-search="${name}">${name}</div>`
}
I('case_list').innerHTML = s
// 绑定点击事件
onD(A('[data-search]'), 'click', function () {
let name = this.dataset.search
let url = cList[name]
if (!url) return
let text = I('search_input').value.trim()
if (text) {
open(url.format(decodeURIComponent(text)))
} else {
open((new URL(url)).origin)
}
})
}
function initSetting() {
dialog.contentHTML(`<iframe id="dmx_iframe" src="${root + 'html/setting.html'}" importance="high"></iframe>`)
}
function initMore() {
dialog.contentHTML(`<iframe id="dmx_iframe" src="${root + 'html/more.html?isSome=' + isSome}" importance="high"></iframe>`)
}
function initDictionaryCSS() {
let styleEl = E('style')
conf.dictionaryCSS.forEach(name => {
if (!setting.dictionaryList.includes(name) || !dictionaryCSS[name] || E(`style[data-name="${name}"]`)) return
let s = `<style data-name="${name}">${dictionaryCSS[name]}</style>`
styleEl.insertAdjacentHTML('afterend', s)
})
}
function initCrop() {
let startX = 0, startY = 0
let bgEl = I('dmx_crop_bg')
let fgEl = I('dmx_crop_fg')
bgEl.style.display = 'block'
let funDown = (e) => {
startX = e.clientX
startY = e.clientY
fgEl.style.display = 'block'
fgEl.style.left = startX + 'px'
fgEl.style.top = startY + 'px'
document.addEventListener('mousemove', funMove)
document.addEventListener('mouseup', funUp)
}
let funMove = (e) => {
let w = e.clientX - startX
let h = e.clientY - startY
if (w > 0) {
fgEl.style.width = w + 'px'
} else {
fgEl.style.left = e.clientX + 'px'
fgEl.style.width = -w + 'px'
}
if (h > 0) {
fgEl.style.height = h + 'px'
} else {
fgEl.style.top = e.clientY + 'px'
fgEl.style.height = -h + 'px'
}
}
let funUp = (e) => {
bgEl.removeAttribute('style')
fgEl.removeAttribute('style')
bgEl.removeEventListener('mousedown', funDown)
document.removeEventListener('mousemove', funMove)
document.removeEventListener('mouseup', funUp)
let width = e.clientX - startX
let height = e.clientY - startY
if (width < 0) {
width = -width
startX = e.clientX
}
if (height < 0) {
height = -height
startY = e.clientY
}
let innerHeight = window.innerHeight || document.documentElement.offsetHeight
if (width > 15 && height > 15) {
sendBgMessage({action: 'onCapture', startX, startY, width, height, innerHeight})
dmxAlert('截图文字识别中...', 'success')
} else {
dmxAlert('截图太小,取消识别', 'error')
}
}
bgEl.addEventListener('mousedown', funDown)
}
function loadingTranslate() {
let el = I('case_list')
let cList = conf.translateList
let sList = setting.translateList
if (sList.length < 1) {
el.innerHTML = `<div class="case case_content"><i class="dmx-icon dmx-icon-info"></i> 您未启用任何翻译模块</div>`
return
}
let s = ''
sList.forEach(name => {
s += `<div class="case" id="${name}_translate_case">
<div class="case_top fx">
<div class="case_left case_language"></div>
<div class="case_right">
<a class="case_link"><i class="dmx-icon dmx-icon-${name}"></i>${cList[name]}</a>
</div>
<div class="case_right case_copy" title="复制"><i class="dmx-icon dmx-icon-copy"></i></div>
<div class="case_right case_bilingual dmx-icon">双语</div>
</div>
<div class="case_content"><div class="dmx_loading"></div></div>
</div>`
})
el.innerHTML = s
// 绑定事件
sList.forEach(name => {
let caseEl = I(`${name}_translate_case`)
let copyEl = caseEl.querySelector('.case_copy')
let bilingualEl = caseEl.querySelector('.case_bilingual')
let contentEl = caseEl.querySelector('.case_content')
copyEl.addEventListener('click', () => {
let text = contentEl.innerText.replace(/\n{2}/g, '\n').trim()
execCopy(text)
dmxAlert('复制成功', 'success')
})
bilingualEl.addEventListener('click', () => {
if (hasClass(bilingualEl, 'active')) {
resultTranslate(name, false)
rmClass(bilingualEl, 'active')
} else {
resultTranslate(name, true)
addClass(bilingualEl, 'active')
}
})
})
}
function loadingDictionary() {
let el = I('case_list')
let cList = conf.dictionaryList
let sList = setting.dictionaryList
if (sList.length < 1) {
el.innerHTML = `<div class="case case_content"><i class="dmx-icon dmx-icon-info"></i> 您未启用任何词典模块</div>`
return
}
let s = ''
sList.forEach(name => {
s += `<div class="case" id="${name}_dictionary_case">
<div class="case_top fx">
<div class="case_right">
<a class="case_link"><i class="dmx-icon dmx-icon-${name}"></i>${cList[name]}</a>
</div>
<div class="case_left case_pronunciation"></div>
</div>
<div class="case_content"><div class="dmx_loading"></div></div>
</div>`
})
el.innerHTML = s
}
function resultTranslate(name, isBilingual) {
let el = I(`${name}_translate_case`)
if (!el) return
let {srcLan, tarLan, lanTTS, data, extra} = msgList[name] || {}
// 显示发音图标
if (srcLan && tarLan) {
let sourceStr = soundIconHTML(srcLan, lanTTS, 'source')
let targetStr = soundIconHTML(tarLan, lanTTS, 'target')
el.querySelector('.case_language').innerHTML = `${sourceStr} » ${targetStr}`
let sourceEl = el.querySelector('[data-type=source]')
let targetEl = el.querySelector('[data-type=target]')
sourceEl && sourceEl.addEventListener('click', function () {
activeRipple(this)
sendPlayTTS(name, 'source', srcLan, dQuery.text) // 播放原音
})
targetEl && targetEl.addEventListener('click', function () {
activeRipple(this)
let s = ''
data && data.forEach(v => {
s += v.tarText + '\n'
})
s && sendPlayTTS(name, 'target', tarLan, s) // 播放译音
})
}
// 显示翻译结果
let s = ''
data && data.forEach(v => {
if (isBilingual) {
s += `<p class="source_text">${v.srcText}</p><p>${v.tarText}</p>`
} else {
s += `<p>${v.tarText}</p>`
}
})
if (extra) s += extra // 重点词汇 && 单词含义
if (!s) s = '网络错误,请稍后再试'
el.querySelector('.case_content').innerHTML = s
// 绑定点击搜索
resultBindEvent(el, 'translate', name)
}
function resultDictionary(m) {
let {name, result, error} = m
let el = I(`${name}_dictionary_case`)
if (!el) return
let cEl = el.querySelector('.case_content')
if (error) {
cEl.innerHTML = '网络错误,请稍后再试'
return
}
let {html, phonetic, sound} = result || {}
// 音标
let pron = ''
if (phonetic) {
let {uk, us} = phonetic
if (uk && us) {
pron += `[${uk} $ ${us}]`
} else if (uk) {
pron += `[${uk}]`
} else if (us) {
pron += `[$ ${us}]`
}
}
// 发音
sound && sound.forEach(v => {
let {isWoman, type, url, title} = v
let className = isWoman ? 'dmx_pink' : ''
if (!title) title = type === 'uk' ? '英音' : type === 'us' ? '美音' : ''
pron += ` <i class="dmx-icon dmx_ripple ${className}" data-type="${type}" data-src-mp3="${url}" title="${title}"></i>`
})
if (!html) html = 'Sorry! 没有查询到结果。'
el.querySelector('.case_content').innerHTML = html
el.querySelector('.case_pronunciation').innerHTML = pron
resultBindEvent(el, 'dictionary', m.name)
}
function resultBindEvent(el, nav, name) {
// 绑定播放音频
el.querySelectorAll('[data-src-mp3]').forEach(e => {
let obj = {uk: '', us: '', en: '', other: ''}
let type = e.getAttribute('data-type')
if (!obj[type]) {
type = 'other'
e.setAttribute('data-type', type)
}
e.innerHTML = obj[type] // 喇叭字体
e.addEventListener('click', function () {
activeRipple(this)
let type = this.getAttribute('data-type')
let url = this.getAttribute('data-src-mp3')
sendPlaySound(nav, name, type, url)
})
})
// 绑定点击搜索
el.querySelectorAll('[data-search=true]').forEach(e => {
e.addEventListener('click', function () {
let text = this.innerText && this.innerText.trim()
sendQuery(text) // 结果点击查询
})
})
}
function resultLink(m) {
let el = I(`${m.name}_${m.type}_case`)
if (!el) return
let sEl = el.querySelector(`.case_link`)
if (sEl) {
sEl.setAttribute('href', m.link)
sEl.setAttribute('target', '_blank')
sEl.setAttribute('referrerPolicy', 'no-referrer')
}
}
function resultSound(m) {
let {nav, name, type, status, error} = m
// if (error) dmxAlert(error, 'error') // 播放声音出错提示
let el = I(`${name}_${nav}_case`)
if (!el) return
if (status === 'start') {
let sEl = el.querySelector(`[data-type=${type}]`)
if (sEl) addClass(sEl, 'active')
} else {
let dEl = el.querySelectorAll(`[data-type=${type}]`)
if (dEl) rmClassD(dEl, 'active')
}
addClass(I('dmx_voice'), 'dmx_show')
}
function activeRipple(el) {
rmClassD(A('.dmx_ripple'), 'active')
addClass(el, 'active')
}
function soundIconHTML(lan, lanArr, type) {
let title = languageList[lan] ? languageList[lan].zhName : ''
let arr = {
'zh': '',
'en': '',
'jp': '',
'th': '',
'spa': '',
'ara': '',
'fra': '',
'kor': '',
'ru': '',
'de': '',
'pt': '',
'it': '',
'el': '',
'nl': '',
'pl': ''
}
let iconStr = arr[lan] || ''
let s = title
if (!lanArr || inArray(lan, lanArr)) {
s += ` <i class="dmx-icon dmx_ripple" data-type="${type}" title="${title}发音">${iconStr}</i>`
}
return s
}
// 发送到后台缓存起来
function sendBgCache(text) {
if (window.textRepeat !== text) {
window.textRepeat = text
sendBgMessage({action: 'textTmp', text})
}
}
// 自动切换翻译或词典
function autoChangeAction(text) {
if (!setting.autoChange) return
let arr = text.match(/\s+/g)
let isWord = !arr || arr.length < 3 // 是否为单词活词组
A('#dmx_navigate > u').forEach(el => rmClass(el, 'active')) // 去掉选中
let onEl = E(`#dmx_navigate > u[action="${isWord ? 'dictionary' : 'translate'}"]`)
if (onEl) {
// 选中翻译或词典
addClass(onEl, 'active')
onEl.click()
}
}
function initQuery(text, clientX, clientY) {
// Unicode property escapes 正则表达式:
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
// https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt
// https://tc39.es/ecma262/#table-nonbinary-unicode-properties
// https://keqingrong.github.io/blog/2020-01-29-regexp-unicode-property-escapes
if (setting.excludeChinese && /\p{Script=Han}/u.test(text)) return // 排除中文
if (setting.excludeSymbol && /^[\p{S}\p{P}^$.*+\-?=!:|\\/!?。;-_~﹏,:、·;…,"“”﹃﹄「」﹁﹂『』﹃﹄()[]〔〕【】《》〈〉()\[\]{}<>\s]+$/u.test(text)) return // 排除纯符号
if (setting.excludeNumber && /^\d+$/u.test(text)) return // 排除纯数字
sendBgCache(text)
if (!text) {
iconBut.style.display = 'none'
return
}
debug('text:', text)
// 自动复制功能
if (setting.autoCopy === 'on') {
sendBgMessage({action: 'copy', text})
dmxAlert('复制成功', 'success')
}
if (setting.scribble === 'off') return
if (setting.scribble === 'direct') {
autoChangeAction(text) // 自动切换翻译或词典
sendQuery(text) // 划词查询
showDialog(clientX + 30, clientY - 60)
} else if (setting.scribble === 'clickIcon') {
iconText = text
let x = clientX + 10
let y = clientY - 45
x = x + 42 < document.documentElement.clientWidth ? x : clientX - 42
y = y > 10 ? y : clientY + 10
iconBut.style.transform = `translate(${x}px, ${y}px)`
iconBut.style.display = 'flex'
}
}
function sendQuery(text) {
if (!text) {
if (isClipboardRead) text = execPaste() // 自动读取粘贴板内容
if (!text && isPopup && setting.autoPaste === 'on') text = execPaste()
if (!text) text = textTmp
if (!text) return
}
let el = I('dmx_navigate')
let action = el.querySelector('.active') && el.querySelector('.active').getAttribute('action')
if (!action) {
action = dialogConf.action
let actionEl = el.querySelector(`u[action="${action}"]`)
if (actionEl) actionEl.click()
}
if (!checkChange(action, text)) return
let message = null
if (setting.cutHumpName === 'on' && action !== 'search') text = cutHumpName(text)
if (action === 'translate') {
let inputEl = I(`translate_input`)
inputEl.innerText = text
isPopup && focusLast(inputEl)
loadingTranslate()
message = {action: action, text: text, srcLan: dialogConf.source, tarLan: dialogConf.target}
} else if (action === 'dictionary') {
I(`dictionary_input`).value = text
loadingDictionary()
message = {action: action, text: text}
} else if (action === 'search') {
I(`search_input`).value = text
}
showSearchSide(text)
if (message) {
message = Object.assign({formTitle: document.title, formUrl: location.href}, message)
sendBgMessage(message)
}
}
// 切分驼峰词组
function cutHumpName(s) {
let isCapital = function (s) {
let c = s.charAt(0)
return c >= 'A' && c <= 'Z'
}
return s.replace(/\w+[A-Z]\w?/g, (...args) => {
let str = args[0]
let newStr = ''
for (let i = 0; i < str.length; i++) {
let l = str[i]
if (l === '_') l = ' '
else if (isCapital(l) && (i - 1 > 0 && str[i - 1] !== '_' && !isCapital(str[i - 1]))) newStr += ' '
newStr += l
}
return newStr
})
}
function mouseWords(e) {
let x = e.clientX
let y = e.clientY
if (!x || !y) return
let textNode, offset, arr
if (document.caretPositionFromPoint) {
let p = document.caretPositionFromPoint(x, y)
if (!p) return
textNode = p.offsetNode
offset = p.offset
} else if (document.caretRangeFromPoint) {
let p = document.caretRangeFromPoint(x, y)
if (!p) return
textNode = p.startContainer
offset = p.startOffset
}
if (!textNode || textNode.nodeType !== 3) return
let str = textNode.data
let before = (arr = str.slice(0, offset).match(/[a-z’']+$/i)) ? arr[0] : ''
let after = (arr = str.slice(offset).match(/^[a-z’']+/i)) ? arr[0] : ''
if (before.length === 0 && after.length === 0) return
let range = document.createRange()
range.setStart(textNode, offset - before.length)
range.setEnd(textNode, offset + after.length)
let bcr = range.getBoundingClientRect()
if (x >= bcr.left && x <= bcr.right && y >= bcr.top && y <= bcr.bottom) {
let se = window.getSelection()
se.removeAllRanges()
se.addRange(range)
let text = before + after
if (text && window.textRepeat !== text) {
sendBgCache(text)
sendQuery(text) // 鼠标停留获取单词
showDialog()
}
}
range.detach()
}
function showSearchSide(text) {
let arr = setting.searchSide
let s = ''
if (text && isArray(arr) && arr.length > 0) {
let sList = getSearchList(searchText)
for (let name of arr) {
let url = sList[name]
if (url) s += `<a href="${url.format(decodeURIComponent(text))}" title="${name}" target="_blank">${name[0]}</a>`
}
}
I('dmx_dialog_left').innerHTML = s
}
function showDialog(left, top) {
let options = null
let position = setting.position
if (isPopup) {
// 跳过
} else if (position === 'follow') {
options = {left, top}
} else if (position === 'right') {
dialog.el.removeAttribute('style')
dialog.el.style.width = dialogConf.width + 'px'
dialog.el.className = 'dmx_keep_right'
}
dialog.show(options)
}
function checkChange(action, text) {
let d = dQuery
let source = dialogConf.source
let target = dialogConf.target
if (d.action === action && d.text === text && d.source === source && d.target === target) return false
dQuery = {action, text, source, target}
addHistory(dQuery)
return true
}
function addHistory(dQuery) {
if (disHistory) return disHistory = false
if (!['translate', 'dictionary', 'search'].includes(dQuery.action)) return
if (historyIndex < history.length - 1) {
history.splice(historyIndex + 1, history.length)
} else if (history.length >= 1000) {
history.shift() // 最多只保留 1000 条
}
history.push(dQuery)
historyIndex = history.length - 1
debug('history:', history, historyIndex)
if (history.length > 1) {
let hEl = I('dmx_history')
rmClass(hEl.querySelector('.dmx-icon-left'), 'disabled')
addClass(hEl.querySelector('.dmx-icon-right'), 'disabled')
}
}
function focusLast(el) {
setTimeout(() => {
el.focus()
let range = window.getSelection()
range.selectAllChildren(el)
range.collapseToEnd() // 光标移到结尾
}, 200)
}
function I(id) {
return shadow.getElementById(id)
}
function E(s) {
return shadow.querySelector(s)
}
function A(s) {
return shadow.querySelectorAll(s)
}
function saveDialogConf() {
storageSyncSet({'dialogConf': dialogConf})
}
function setDialogConf(name, value) {
dialogConf[name] = value
saveDialogConf()
}
function allowUserSelect() {
dmxAlert('解除页面限制完成', 'success')
let styleEl = document.getElementById('_dream_style_all')
if (!styleEl) {
let sEl = document.createElement('style')
sEl.id = '_dream_style_all'
sEl.textContent = `* {-webkit-user-select:text!important;-moz-user-select:text!important;user-select:text!important;pointer-events:auto!important;}`
document.head.appendChild(sEl)
}
let onAllow = function (el, event) {
if (el.getAttribute && el.getAttribute(event)) el.setAttribute(event, () => true)
}
onAllow(document, 'oncontextmenu')
onAllow(document, 'onselectstart')
// 清除再添加,排除重复事件
let onClean = function (e) {
e.stopPropagation()
let el = e.target
while (el) {
onAllow(el, 'on' + e.type)
el = el.parentNode
}
}
document.removeEventListener('contextmenu', onClean, true)
document.removeEventListener('selectstart', onClean, true)
document.addEventListener('contextmenu', onClean, true)
document.addEventListener('selectstart', onClean, true)
}
function sendPlayTTS(name, type, lang, text) {
sendBgMessage({action: 'translateTTS', name, type, lang, text})
}
function sendPlaySound(nav, name, type, url) {
sendBgMessage({action: 'playSound', nav, name, type, url})
}
function sendBgMessage(message) {
return new Promise((resolve, reject) => {
sendMessage(message).then(_ => {
resolve()
}).catch(err => {
// 减少错误提示框
if (getTimestamp() > (window.dmxUpdateDate || 0)) {
window.dmxUpdateDate = getTimestamp() + 5
dmxAlert('梦想翻译已升级,请刷新页面激活。', 'error')
}
debug('sendBgMessage error:', err)
// reject(err)
resolve()
})
})
}
function dmxAlert(message, type, timeout) {
type = type || 'info'
timeout = timeout || 2500
let el = I('dmx_alert')
if (!el) {
el = document.createElement('div')
el.id = 'dmx_alert'
shadow.appendChild(el)
}
let icon = {
info: '<i class="dmx-icon dmx-icon-info"></i>',
error: '<i class="dmx-icon dmx-icon-close"></i>',
success: '<i class="dmx-icon dmx-icon-success"></i>',
}
gitextract_au70of8y/
├── LICENSE
├── README.md
├── release.sh
├── src/
│ ├── conf/
│ │ ├── conf.json
│ │ ├── langSpeak.json
│ │ ├── language.json
│ │ └── searchText.txt
│ ├── css/
│ │ ├── content.css
│ │ ├── dmx_dialog.css
│ │ ├── longman.css
│ │ ├── main.css
│ │ └── popup.css
│ ├── html/
│ │ ├── favorite.html
│ │ ├── history.html
│ │ ├── more.html
│ │ ├── popup.html
│ │ ├── record.html
│ │ ├── setting.html
│ │ ├── speak.html
│ │ └── video.html
│ ├── js/
│ │ ├── background.js
│ │ ├── common.js
│ │ ├── content.js
│ │ ├── db.js
│ │ ├── dictionary/
│ │ │ ├── bing.js
│ │ │ ├── cambridge.js
│ │ │ ├── collins.js
│ │ │ ├── dictcn.js
│ │ │ ├── dictionary.js
│ │ │ ├── dreye.js
│ │ │ ├── etymonline.js
│ │ │ ├── eudic.js
│ │ │ ├── hjdict.js
│ │ │ ├── iciba.js
│ │ │ ├── lexico.js
│ │ │ ├── longman.js
│ │ │ ├── macmillan.js
│ │ │ ├── merriam.js
│ │ │ ├── oxford.js
│ │ │ ├── rrdict.js
│ │ │ ├── thefree.js
│ │ │ ├── urban.js
│ │ │ ├── vocabulary.js
│ │ │ ├── wordreference.js
│ │ │ └── youdao.js
│ │ ├── favorite.js
│ │ ├── frame.js
│ │ ├── history.js
│ │ ├── more.js
│ │ ├── player.js
│ │ ├── popup.js
│ │ ├── record.js
│ │ ├── setting.js
│ │ ├── speak.js
│ │ ├── translate/
│ │ │ ├── alibaba.js
│ │ │ ├── baidu.js
│ │ │ ├── bing.js
│ │ │ ├── deepl.js
│ │ │ ├── frdic.js
│ │ │ ├── google.js
│ │ │ ├── local.js
│ │ │ ├── qq.js
│ │ │ ├── so.js
│ │ │ ├── sogou.js
│ │ │ └── youdao.js
│ │ └── video.js
│ └── manifest.json
└── tool/
├── alibaba.html
├── baidu.html
├── bd.js
├── bing.html
├── google.html
├── lang.html
├── qq.html
├── sogou.html
└── youdao.html
SYMBOL INDEX (239 symbols across 42 files)
FILE: src/js/background.js
function runTranslate (line 142) | async function runTranslate(tabId, m) {
function runTranslateTTS (line 170) | function runTranslateTTS(tabId, m) {
function runDictionary (line 184) | function runDictionary(tabId, m) {
function runPlaySound (line 209) | function runPlaySound(tabId, m) {
function cropImageSendMsg (line 220) | function cropImageSendMsg() {
function capturePic (line 224) | function capturePic(tab, m) {
function getOcrText (line 242) | async function getOcrText(tabId, base64) {
function getOcrToken (line 271) | function getOcrToken() {
function saveSearchText (line 311) | function saveSearchText(s) {
function saveSettingAll (line 317) | function saveSettingAll(data, updateIcon, resetDialog) {
function changeBrowserIcon (line 325) | function changeBrowserIcon(scribble) {
function setBrowserAction (line 330) | function setBrowserAction(text) {
function changeMenu (line 336) | function changeMenu(name, isAdd) {
function addMenu (line 341) | function addMenu(name, title, url) {
function removeMenu (line 362) | function removeMenu(name) {
function openTransWindow (line 368) | function openTransWindow() {
function clipboardTrans (line 372) | function clipboardTrans() {
function openRecord (line 376) | function openRecord() {
function openWindow (line 380) | function openWindow(wid, width, height, url, reopen) {
function openTab (line 411) | function openTab(url) {
function sendAllowSelect (line 415) | function sendAllowSelect() {
function createHistory (line 422) | function createHistory(m) {
function settingHistory (line 461) | function settingHistory(n) {
function minCss (line 466) | function minCss(s) {
function autoLang (line 475) | async function autoLang(text) {
function autoPlayTTS (line 488) | async function autoPlayTTS(tabId, text, lang) {
function playTTS (line 504) | function playTTS(name, text, lang) {
function autoPlayAudio (line 537) | async function autoPlayAudio(tabId, text) {
function getSoundUrl (line 573) | function getSoundUrl(arr, type) {
function stopAudio (line 578) | function stopAudio() {
function playAudio (line 584) | function playAudio(url) {
function getAudioBlob (line 618) | async function getAudioBlob(url, retry) {
function sdkInit (line 633) | function sdkInit(name) {
function loadJs (line 647) | function loadJs(arr, type) {
function invertObject (line 656) | function invertObject(obj) {
function cleanAttr (line 665) | function cleanAttr(el, attrs) {
function checkRetry (line 679) | async function checkRetry(callback, times) {
function createTmpTab (line 739) | function createTmpTab(id, url, timeout) {
function removeTmpTab (line 747) | function removeTmpTab(id) {
function reloadTmpTab (line 756) | function reloadTmpTab(id) {
function openIframe (line 762) | function openIframe(id, url, timeout) {
function removeIframe (line 780) | function removeIframe(id) {
function sliceStr (line 786) | function sliceStr(text, maxLen) {
FILE: src/js/common.js
function storageLocalGet (line 47) | function storageLocalGet(options) {
function storageLocalSet (line 51) | function storageLocalSet(options) {
function storageSyncGet (line 55) | function storageSyncGet(options) {
function storageSyncSet (line 59) | function storageSyncSet(options) {
function storageShowAll (line 63) | function storageShowAll() {
function storage (line 73) | function storage(type, method, options) {
function cookies (line 98) | function cookies(method, options) {
function sendMessage (line 129) | function sendMessage(message) {
function sendTabMessage (line 140) | function sendTabMessage(tabId, message) {
function sandFgMessage (line 152) | function sandFgMessage(id, message) {
function getActiveTabId (line 165) | function getActiveTabId() {
function onBeforeSendHeadersAddListener (line 180) | function onBeforeSendHeadersAddListener(callback, filter, opt_extraInfoS...
function onBeforeSendHeadersRemoveListener (line 185) | function onBeforeSendHeadersRemoveListener(callback) {
function requestHeadersFormat (line 189) | function requestHeadersFormat(s) {
function onBeforeRequestAddListener (line 201) | function onBeforeRequestAddListener(callback, filter, extraInfoSpec) {
function onBeforeRequestRemoveListener (line 209) | function onBeforeRequestRemoveListener(callback) {
function onHeadersReceivedAddListener (line 213) | function onHeadersReceivedAddListener(callback, filter, extraInfoSpec) {
function onHeadersReceivedRemoveListener (line 218) | function onHeadersReceivedRemoveListener(callback) {
function onCompletedAddListener (line 222) | function onCompletedAddListener(callback, filter, extraInfoSpec) {
function onCompletedRemoveListener (line 227) | function onCompletedRemoveListener(callback) {
function onRemoveFrame (line 231) | function onRemoveFrame(details) {
function onRemoveCross (line 243) | function onRemoveCross(details) {
function getVoices (line 256) | function getVoices() {
function getTimestamp (line 273) | function getTimestamp() {
function addClass (line 277) | function addClass(el, className) {
function rmClass (line 288) | function rmClass(el, className) {
function hasClass (line 301) | function hasClass(el, className) {
function sleep (line 306) | function sleep(delay) {
function getDate (line 310) | function getDate(value, isDate) {
function zero (line 324) | function zero(value, digits) {
function $ (line 334) | function $(id) {
function N (line 338) | function N(name) {
function S (line 342) | function S(s) {
function D (line 346) | function D(s) {
function onD (line 350) | function onD(el, type, listener, options) {
function unD (line 356) | function unD(el, type, listener, options) {
function removeD (line 362) | function removeD(el) {
function rmClassD (line 366) | function rmClassD(el, className) {
function inArray (line 370) | function inArray(val, arr) {
function isObject (line 375) | function isObject(o) {
function isArray (line 379) | function isArray(o) {
function isString (line 383) | function isString(o) {
function isNumber (line 387) | function isNumber(o) {
function isDate (line 391) | function isDate(o) {
function isRegExp (line 395) | function isRegExp(o) {
function isError (line 399) | function isError(o) {
function isSymbol (line 403) | function isSymbol(o) {
function isArrayBuffer (line 407) | function isArrayBuffer(o) {
function isFunction (line 411) | function isFunction(o) {
function getSearchList (line 415) | function getSearchList(s) {
function getJSONValue (line 430) | function getJSONValue(data, keys, value) {
function addEl (line 444) | function addEl(options) {
function createTextarea (line 455) | function createTextarea() {
function execCopy (line 463) | function execCopy(s) {
function execPaste (line 471) | function execPaste() {
function dal (line 481) | function dal(text, type, onSubmit) {
function dco (line 503) | function dco(text, onSubmit, onCancel) {
function ddi (line 525) | function ddi(option) {
function removeDdi (line 553) | function removeDdi() {
function loading (line 558) | function loading(text) {
function HTMLEncode (line 569) | function HTMLEncode(s) {
function uniqueArray (line 575) | function uniqueArray(arr) {
function httpGet (line 579) | function httpGet(url, type, headers, notStrict) {
function httpPost (line 609) | function httpPost(options) {
function _setTimeout (line 651) | function _setTimeout(tid, callback, timeout) {
function _clearTimeout (line 657) | function _clearTimeout(tid) {
function encodeURI (line 665) | function encodeURI(s) {
function debug (line 672) | function debug(...data) {
FILE: src/js/content.js
function initDialog (line 105) | function initDialog() {
function initTranslate (line 261) | function initTranslate() {
function initDictionary (line 430) | function initDictionary() {
function initSearch (line 461) | function initSearch() {
function initSetting (line 513) | function initSetting() {
function initMore (line 517) | function initMore() {
function initDictionaryCSS (line 521) | function initDictionaryCSS() {
function initCrop (line 530) | function initCrop() {
function loadingTranslate (line 587) | function loadingTranslate() {
function loadingDictionary (line 635) | function loadingDictionary() {
function resultTranslate (line 659) | function resultTranslate(name, isBilingual) {
function resultDictionary (line 703) | function resultDictionary(m) {
function resultBindEvent (line 743) | function resultBindEvent(el, nav, name) {
function resultLink (line 770) | function resultLink(m) {
function resultSound (line 781) | function resultSound(m) {
function activeRipple (line 796) | function activeRipple(el) {
function soundIconHTML (line 801) | function soundIconHTML(lan, lanArr, type) {
function sendBgCache (line 829) | function sendBgCache(text) {
function autoChangeAction (line 837) | function autoChangeAction(text) {
function initQuery (line 851) | function initQuery(text, clientX, clientY) {
function sendQuery (line 890) | function sendQuery(text) {
function cutHumpName (line 929) | function cutHumpName(s) {
function mouseWords (line 947) | function mouseWords(e) {
function showSearchSide (line 990) | function showSearchSide(text) {
function showDialog (line 1003) | function showDialog(left, top) {
function checkChange (line 1018) | function checkChange(action, text) {
function addHistory (line 1028) | function addHistory(dQuery) {
function focusLast (line 1046) | function focusLast(el) {
function I (line 1055) | function I(id) {
function E (line 1059) | function E(s) {
function A (line 1063) | function A(s) {
function saveDialogConf (line 1067) | function saveDialogConf() {
function setDialogConf (line 1071) | function setDialogConf(name, value) {
function allowUserSelect (line 1076) | function allowUserSelect() {
function sendPlayTTS (line 1107) | function sendPlayTTS(name, type, lang, text) {
function sendPlaySound (line 1111) | function sendPlaySound(nav, name, type, url) {
function sendBgMessage (line 1115) | function sendBgMessage(message) {
function dmxAlert (line 1132) | function dmxAlert(message, type, timeout) {
function dmxDialog (line 1161) | function dmxDialog(options) {
FILE: src/js/db.js
function idb (line 10) | function idb(dbName, version, onupgradeneeded) {
function rmIdb (line 126) | function rmIdb(dbName) {
function initFavorite (line 136) | function initFavorite(e) {
function initHistory (line 169) | function initHistory(e) {
FILE: src/js/dictionary/bing.js
function bingDictionary (line 10) | function bingDictionary() {
FILE: src/js/dictionary/cambridge.js
function cambridgeDictionary (line 10) | function cambridgeDictionary() {
FILE: src/js/dictionary/collins.js
function collinsDictionary (line 10) | function collinsDictionary() {
FILE: src/js/dictionary/dictcn.js
function dictcnDictionary (line 10) | function dictcnDictionary() {
FILE: src/js/dictionary/dictionary.js
function dictionaryDictionary (line 10) | function dictionaryDictionary() {
FILE: src/js/dictionary/dreye.js
function dreyeDictionary (line 10) | function dreyeDictionary() {
FILE: src/js/dictionary/etymonline.js
function etymonlineDictionary (line 10) | function etymonlineDictionary() {
FILE: src/js/dictionary/eudic.js
function eudicDictionary (line 10) | function eudicDictionary() {
FILE: src/js/dictionary/hjdict.js
function hjdictDictionary (line 10) | function hjdictDictionary() {
FILE: src/js/dictionary/iciba.js
function icibaDictionary (line 10) | function icibaDictionary() {
FILE: src/js/dictionary/lexico.js
function lexicoDictionary (line 10) | function lexicoDictionary() {
FILE: src/js/dictionary/longman.js
function longmanDictionary (line 10) | function longmanDictionary() {
FILE: src/js/dictionary/macmillan.js
function macmillanDictionary (line 10) | function macmillanDictionary() {
FILE: src/js/dictionary/merriam.js
function merriamDictionary (line 10) | function merriamDictionary() {
FILE: src/js/dictionary/oxford.js
function oxfordDictionary (line 10) | function oxfordDictionary() {
FILE: src/js/dictionary/rrdict.js
function rrdictDictionary (line 10) | function rrdictDictionary() {
FILE: src/js/dictionary/thefree.js
function thefreeDictionary (line 10) | function thefreeDictionary() {
FILE: src/js/dictionary/urban.js
function urbanDictionary (line 10) | function urbanDictionary() {
FILE: src/js/dictionary/vocabulary.js
function vocabularyDictionary (line 10) | function vocabularyDictionary() {
FILE: src/js/dictionary/wordreference.js
function wordreferenceDictionary (line 10) | function wordreferenceDictionary() {
FILE: src/js/dictionary/youdao.js
function youdaoDictionary (line 10) | function youdaoDictionary() {
FILE: src/js/favorite.js
function createCate (line 31) | function createCate() {
function updateCate (line 60) | function updateCate() {
function deleteCate (line 90) | function deleteCate() {
function moveSentence (line 103) | function moveSentence() {
function deleteBatchSentence (line 140) | function deleteBatchSentence() {
function initCate (line 156) | function initCate(id) {
function initSentence (line 181) | function initSentence(cateId) {
function exerciseSentence (line 225) | function exerciseSentence() {
function playerInit (line 312) | function playerInit(key, type) {
function pointSentence (line 444) | function pointSentence(sentence, words, isUnderscore) {
function editSentence (line 471) | function editSentence() {
function deleteSentence (line 522) | function deleteSentence() {
function selectBind (line 534) | function selectBind() {
function exportZip (line 546) | function exportZip() {
function downloadZip (line 580) | function downloadZip(blob) {
function importZip (line 588) | function importZip() {
function openSetting (line 760) | function openSetting() {
function selectAll (line 787) | function selectAll() {
function selectCancel (line 796) | function selectCancel() {
function shuffle (line 802) | function shuffle(arr) {
FILE: src/js/history.js
function historyList (line 21) | function historyList() {
function deleteMultiple (line 70) | function deleteMultiple() {
function selectAll (line 87) | function selectAll() {
function openSetting (line 96) | function openSetting() {
FILE: src/js/player.js
function playerListen (line 44) | function playerListen(id, options) {
function playerRecord (line 127) | function playerRecord(id, options) {
function playerCompare (line 349) | function playerCompare(id, options) {
function humanTime (line 430) | function humanTime(s, isSecond) {
FILE: src/js/record.js
function playerInit (line 110) | function playerInit() {
FILE: src/js/setting.js
function init (line 29) | function init() {
function initSearch (line 101) | function initSearch() {
function initLocalSoundReplace (line 118) | function initLocalSoundReplace() {
function getSearchKey (line 127) | function getSearchKey(s) {
function navigate (line 133) | function navigate(navId, contentSel) {
function setBindValue (line 156) | function setBindValue(name, value) {
function setValue (line 161) | function setValue(name, value) {
function bindValue (line 185) | function bindValue(name, value) {
function bindSearchMenus (line 217) | function bindSearchMenus() {
function bindSortHTML (line 226) | function bindSortHTML(textName, id, name, value, list) {
function sortShow (line 236) | function sortShow(textName, id, value, list) {
function bindShow (line 247) | function bindShow(id, name, value) {
function settingBoxHTML (line 257) | function settingBoxHTML(id, name, list) {
function settingOcr (line 266) | function settingOcr() {
function searchListSetting (line 291) | function searchListSetting() {
function localTtsSetting (line 340) | function localTtsSetting() {
function voiceListSort (line 431) | function voiceListSort(voices, specialLang) {
function setSetting (line 450) | function setSetting(name, value) {
function clearSetting (line 455) | function clearSetting() {
function sendSetting (line 464) | function sendSetting(setting, updateIcon, resetDialog) {
FILE: src/js/speak.js
function voiceListSort (line 86) | function voiceListSort(list) {
function speak (line 101) | function speak(text, options) {
function setConf (line 115) | function setConf(key, value) {
function loadConf (line 120) | function loadConf() {
FILE: src/js/translate/alibaba.js
function alibabaTranslate (line 10) | function alibabaTranslate() {
FILE: src/js/translate/baidu.js
function baiduTranslate (line 10) | function baiduTranslate() {
FILE: src/js/translate/bing.js
function bingTranslate (line 10) | function bingTranslate() {
FILE: src/js/translate/deepl.js
function deeplTranslate (line 10) | function deeplTranslate() {
FILE: src/js/translate/frdic.js
function frdicTranslate (line 10) | function frdicTranslate() {
FILE: src/js/translate/google.js
function googleTranslate (line 10) | function googleTranslate() {
FILE: src/js/translate/local.js
function localTranslate (line 10) | function localTranslate() {
FILE: src/js/translate/qq.js
function qqTranslate (line 10) | function qqTranslate() {
FILE: src/js/translate/so.js
function soTranslate (line 10) | function soTranslate() {
FILE: src/js/translate/sogou.js
function sogouTranslate (line 10) | function sogouTranslate() {
FILE: src/js/translate/youdao.js
function youdaoTranslate (line 10) | function youdaoTranslate() {
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (682K chars).
[
{
"path": "LICENSE",
"chars": 1061,
"preview": "MIT License\n\nCopyright (c) 2020 Ryan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof th"
},
{
"path": "README.md",
"chars": 2312,
"preview": "# 梦想划词翻译(Chrome & Firefox 扩展程序)\n这是一款精心雕琢并免费开源的划词翻译扩展,也是一款致力于改善「中国式哑巴英语」而设计的一款英语练习扩展程序。\n\n### 扩展安装\n- Chrome 安装:[梦想划词翻译—聚合词"
},
{
"path": "release.sh",
"chars": 694,
"preview": "#!/bin/bash\n\nDIST=./dist\nDIST2=./dist_firefox\n\n# ======== Chrome & Edge ========\n# 拷贝\necho \"cp to ... $DIST\"\nmkdir -p $D"
},
{
"path": "src/conf/conf.json",
"chars": 2709,
"preview": "{\n \"setting\": {\n \"scribble\": \"direct\",\n \"excludeChinese\": \"\",\n \"excludeSymbol\": \"\",\n \"excludeNumber\": \"\",\n "
},
{
"path": "src/conf/langSpeak.json",
"chars": 7211,
"preview": "{\n \"ar-AE\": {\n \"zhName\": \"阿拉伯语(阿拉伯联合酋长国)\",\n \"enName\": \"Arabic(United Arab Emirates)\"\n },\n \"ar-BH\": {\n \"zhNam"
},
{
"path": "src/conf/language.json",
"chars": 12837,
"preview": "{\n \"auto\": {\n \"zhName\": \"自动检测\",\n \"enName\": \"Auto Detect\"\n },\n \"zh\": {\n \"zhName\": \"中文(简体)\",\n \"enName\": \"Ch"
},
{
"path": "src/conf/searchText.txt",
"chars": 1122,
"preview": "百度搜索|https://www.baidu.com/s?wd={0}\n谷歌搜索|https://www.google.com/search?q={0}\n必应搜索|https://cn.bing.com/search?q={0}\n360搜索"
},
{
"path": "src/css/content.css",
"chars": 9628,
"preview": ".dmx_unselectable {\n -webkit-user-select: none !important;\n -moz-user-select: none !important;\n -ms-user-select"
},
{
"path": "src/css/dmx_dialog.css",
"chars": 20348,
"preview": "p, h1, h2, h3, h4, h5, h6, ul, li, ol, dl, dt, dd {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\nh1, h2, h3, "
},
{
"path": "src/css/longman.css",
"chars": 14359,
"preview": ".longman_dict .Sense img {\n float: right;\n width: 30%;\n max-width: 400px;\n margin-left: 5px;\n}\n\n.longman_dic"
},
{
"path": "src/css/main.css",
"chars": 18385,
"preview": "* {\n box-sizing: border-box;\n outline-color: #448aff;\n}\n\nhtml, body, p, h1, h2, h3, h4, h5, h6, ul, li {\n margi"
},
{
"path": "src/css/popup.css",
"chars": 185,
"preview": "html, body {\n margin: 0;\n padding: 0;\n overflow: hidden;\n background: #f5f8fa;\n}\n\n.dmx_popup {\n width: 52"
},
{
"path": "src/html/favorite.html",
"chars": 2848,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"referrer\" content=\"no-referrer\">\n "
},
{
"path": "src/html/history.html",
"chars": 1800,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <link rel=\"stylesheet\" href=\"../css/content.css\">"
},
{
"path": "src/html/more.html",
"chars": 1369,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>More</title>\n <link rel=\"stylesheet\" hr"
},
{
"path": "src/html/popup.html",
"chars": 420,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>梦想翻译小助手</title>\n <link rel=\"stylesheet\""
},
{
"path": "src/html/record.html",
"chars": 2410,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"referrer\" content=\"no-referrer\">\n "
},
{
"path": "src/html/setting.html",
"chars": 10986,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Setting</title>\n <link rel=\"stylesheet\""
},
{
"path": "src/html/speak.html",
"chars": 1846,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>朗读助手</title>\n <link rel=\"stylesheet\" hr"
},
{
"path": "src/html/video.html",
"chars": 292,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"referrer\" content=\"no-referrer\">\n "
},
{
"path": "src/js/background.js",
"chars": 27185,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/common.js",
"chars": 20171,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/content.js",
"chars": 47005,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/db.js",
"chars": 7620,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/bing.js",
"chars": 4856,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/cambridge.js",
"chars": 3427,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/collins.js",
"chars": 4456,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/dictcn.js",
"chars": 5112,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/dictionary.js",
"chars": 1671,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/dreye.js",
"chars": 4142,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/etymonline.js",
"chars": 1550,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/eudic.js",
"chars": 3543,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/hjdict.js",
"chars": 3779,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/iciba.js",
"chars": 4611,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/lexico.js",
"chars": 2604,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/longman.js",
"chars": 4596,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/macmillan.js",
"chars": 2418,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/merriam.js",
"chars": 4013,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/oxford.js",
"chars": 3302,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/rrdict.js",
"chars": 5189,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/thefree.js",
"chars": 3172,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/urban.js",
"chars": 1502,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/vocabulary.js",
"chars": 1640,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/wordreference.js",
"chars": 1888,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/dictionary/youdao.js",
"chars": 4500,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/favorite.js",
"chars": 30831,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/frame.js",
"chars": 882,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/history.js",
"chars": 4021,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/more.js",
"chars": 843,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/player.js",
"chars": 13265,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/popup.js",
"chars": 173,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/record.js",
"chars": 4395,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/setting.js",
"chars": 15040,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/speak.js",
"chars": 3478,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/alibaba.js",
"chars": 4681,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/baidu.js",
"chars": 12208,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/bing.js",
"chars": 10976,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/deepl.js",
"chars": 6481,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/frdic.js",
"chars": 4821,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/google.js",
"chars": 5638,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/local.js",
"chars": 2298,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/qq.js",
"chars": 7482,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/so.js",
"chars": 3246,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/sogou.js",
"chars": 12172,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/translate/youdao.js",
"chars": 15049,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/js/video.js",
"chars": 436,
"preview": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com"
},
{
"path": "src/manifest.json",
"chars": 1863,
"preview": "{\n \"name\": \"梦想划词翻译—聚合词典搜索\",\n \"description\": \"梦想划词翻译是为阅读和学习外语而开发的一款翻译和查词工具,聚合数十款在线词典和在线翻译。\",\n \"version\": \"1.6.27\",\n \""
},
{
"path": "tool/alibaba.html",
"chars": 6952,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/baidu.html",
"chars": 858,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/bd.js",
"chars": 34964,
"preview": "var bdList = {\n auto: {\n zhName: \"自动检测\",\n enName: \"Auto Detect\",\n pinyin: \"zidongjiance\",\n "
},
{
"path": "tool/bing.html",
"chars": 6667,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/google.html",
"chars": 18948,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/lang.html",
"chars": 78613,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n</head>\n<body>\n<table class="
},
{
"path": "tool/qq.html",
"chars": 2551,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/sogou.html",
"chars": 17620,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
},
{
"path": "tool/youdao.html",
"chars": 2785,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n <style>\n table {\n"
}
]
About this extraction
This page contains the full source code of the ryanker/dream_translate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (602.5 KB), approximately 181.1k tokens, and a symbol index with 239 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.