├── .browserslistrc ├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── _bug_report_chs.md │ ├── _feature_request_chs.md │ ├── _new_dict_chs.md │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── config.yml └── no-response.yml ├── .gitignore ├── .neutrinorc.js ├── .prettierrc ├── .storybook ├── addons.ts ├── assets │ └── shewalksinbeauty.mp3 ├── config.ts ├── configs │ └── contexts.tsx ├── manager-head.html ├── preview-head.html ├── style.css └── webpack.config.js ├── .travis.yml ├── .vscode ├── locales.schema.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING-zh.md ├── CONTRIBUTING.md ├── LICENSE ├── README-zh.md ├── README.md ├── assets ├── content.css ├── default.pdf ├── fanyi.youdao.2.0 │ ├── all-packed.css │ ├── bar-sp-repeat-x.png │ ├── bar-sp.png │ ├── conn.html │ ├── conn.js │ ├── logo.png │ ├── main.js │ ├── swipe_hr.png │ ├── switch_button.png │ ├── switch_button_hover.png │ ├── trans_tip_submit_bg.png │ ├── trans_tip_submit_bg_hover.png │ └── ydd_tip.png ├── google-page-trans.js ├── icon-128.png ├── icon-16.png ├── icon-19.png ├── icon-24.png ├── icon-38.png ├── icon-48.png ├── icon-gray-128.png ├── icon-gray-16.png ├── icon-gray-19.png ├── icon-gray-24.png ├── icon-gray-38.png ├── icon-gray-48.png ├── inject-dict-panel.js └── vimium-c-injector.js ├── commitlint.config.js ├── config └── jest │ ├── cssTransform.js │ ├── fileTransform.js │ └── setupTests.js ├── docs └── saladict.jpg ├── jest.config.js ├── jsconfig.json ├── mac-app └── Saladict - Pop-up Dictionary and Page Translator │ ├── Saladict - Pop-up Dictionary and Page Translator Extension │ ├── Info.plist │ ├── SafariWebExtensionHandler.swift │ └── Saladict___Pop_up_Dictionary_and_Page_Translator_Extension.entitlements │ ├── Saladict - Pop-up Dictionary and Page Translator.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── crimx.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── crimx.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ └── Saladict - Pop-up Dictionary and Page Translator │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-128.png │ │ └── icon-16.png │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── Saladict___Pop_up_Dictionary_and_Page_Translator.entitlements │ └── ViewController.swift ├── package.json ├── postcss.config.js ├── scripts ├── after-build.js ├── build.js ├── firefox-fix.js ├── fixtures.js ├── pdf.js ├── setup-env.js ├── start.js ├── style-extractor.js └── test.js ├── src ├── _helpers │ ├── __mocks__ │ │ ├── browser-api.ts │ │ ├── config-manager.ts │ │ └── selection.ts │ ├── analytics │ │ ├── events.ts │ │ └── index.ts │ ├── browser-api.ts │ ├── check-update.ts │ ├── chs-to-chz.ts │ ├── config-manager.ts │ ├── dom.ts │ ├── fetch-dom.ts │ ├── getSuggests.ts │ ├── hooks.ts │ ├── i18n.ts │ ├── injectSaladictInternal.ts │ ├── integrity.ts │ ├── lang-check.ts │ ├── matchPatternToRegExpStr.ts │ ├── observables.ts │ ├── permission-manager.ts │ ├── profile-manager.ts │ ├── promise-more.ts │ ├── record-manager.ts │ ├── saladict.ts │ ├── scrollbar-width.ts │ ├── storybook.tsx │ ├── titlebar-offset.ts │ ├── translateCtx.ts │ ├── uniqueKey.ts │ └── wordoftheday.ts ├── _locales │ ├── en │ │ ├── background.ts │ │ ├── common.ts │ │ ├── content.ts │ │ ├── langcode.ts │ │ ├── menus.ts │ │ ├── options.ts │ │ ├── popup.ts │ │ └── wordpage.ts │ ├── es │ │ ├── background.ts │ │ ├── common.ts │ │ ├── content.ts │ │ ├── langcode.ts │ │ ├── menus.ts │ │ ├── options.ts │ │ ├── popup.ts │ │ └── wordpage.ts │ ├── manifest │ │ ├── en │ │ │ └── messages.json │ │ ├── np │ │ │ └── messages.json │ │ ├── zh_CN │ │ │ └── messages.json │ │ └── zh_TW │ │ │ └── messages.json │ ├── ne │ │ ├── background.ts │ │ ├── common.ts │ │ ├── content.ts │ │ ├── langcode.ts │ │ ├── menus.ts │ │ ├── options.ts │ │ ├── popup.ts │ │ └── wordpage.ts │ ├── zh-CN │ │ ├── background.ts │ │ ├── common.ts │ │ ├── content.ts │ │ ├── langcode.ts │ │ ├── menus.ts │ │ ├── options.ts │ │ ├── popup.ts │ │ └── wordpage.ts │ └── zh-TW │ │ ├── background.ts │ │ ├── common.ts │ │ ├── content.ts │ │ ├── langcode.ts │ │ ├── menus.ts │ │ ├── options.ts │ │ ├── popup.ts │ │ └── wordpage.ts ├── _sass_shared │ ├── _fancy-scrollbar.scss │ ├── _global │ │ ├── _interfaces.scss │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ └── _z-indices.scss │ ├── _namespace.scss │ ├── _reset.scss │ └── _theme.scss ├── app-config │ ├── auth.ts │ ├── context-menus.ts │ ├── dicts.ts │ ├── index.ts │ ├── merge-config.ts │ ├── merge-profile.ts │ └── profiles.ts ├── assets │ ├── Speaker.svg │ ├── bowl.svg │ ├── engines │ │ ├── bing.ico │ │ ├── howjsay.ico │ │ ├── ud.ico │ │ └── vocabulary.ico │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── leaf.svg │ ├── orange.svg │ ├── saladict.svg │ ├── symbol-defs.svg │ ├── tomato.svg │ └── wechat.png ├── audio-control │ ├── audio-control.scss │ └── index.tsx ├── background │ ├── __fake__ │ │ └── env.ts │ ├── __mocks__ │ │ └── database.ts │ ├── audio-manager.ts │ ├── badge.ts │ ├── clipboard-manager.ts │ ├── context-menus.ts │ ├── database │ │ ├── core.ts │ │ ├── index.ts │ │ ├── read.ts │ │ ├── sync-meta.ts │ │ └── write.ts │ ├── env.ts │ ├── i18n-manager.ts │ ├── index.ts │ ├── initialization.ts │ ├── page-translate │ │ └── caiyun.ts │ ├── pdf-sniffer.ts │ ├── server.ts │ ├── sync-manager │ │ ├── __mocks__ │ │ │ └── helpers.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ └── services │ │ │ ├── ankiconnect │ │ │ ├── _locales │ │ │ │ ├── en.ts │ │ │ │ ├── zh-CN.ts │ │ │ │ └── zh-TW.ts │ │ │ └── index.ts │ │ │ ├── eudic │ │ │ ├── _locales │ │ │ │ ├── en.ts │ │ │ │ ├── zh-CN.ts │ │ │ │ └── zh-TW.ts │ │ │ └── index.ts │ │ │ ├── shanbay │ │ │ ├── _locales │ │ │ │ ├── en.ts │ │ │ │ ├── zh-CN.ts │ │ │ │ └── zh-TW.ts │ │ │ └── index.ts │ │ │ └── webdav │ │ │ ├── _locales │ │ │ ├── en.ts │ │ │ ├── zh-CN.ts │ │ │ └── zh-TW.ts │ │ │ └── index.ts │ ├── types.ts │ └── windows-manager.ts ├── components │ ├── AntdRoot │ │ ├── AntdRootContainer.tsx │ │ ├── _style.scss │ │ └── index.tsx │ ├── EntryBox │ │ ├── EntryBox.scss │ │ ├── EntryBox.stories.tsx │ │ └── index.tsx │ ├── ErrorBoundary.tsx │ ├── FloatBox │ │ ├── FloatBox.scss │ │ ├── FloatBox.stories.tsx │ │ └── index.tsx │ ├── HoverBox │ │ ├── HoverBox.scss │ │ └── index.tsx │ ├── MachineTrans │ │ ├── MachineTrans.scss │ │ ├── MachineTrans.stories.tsx │ │ ├── MachineTrans.tsx │ │ └── engine.ts │ ├── ShadowPortal │ │ ├── ShadowPortal.scss │ │ └── index.tsx │ ├── Speaker │ │ ├── Speaker.scss │ │ ├── Speaker.stories.tsx │ │ └── index.tsx │ ├── StarRates │ │ └── index.tsx │ ├── StrElm │ │ └── index.tsx │ ├── Waveform │ │ ├── Waveform.scss │ │ ├── Waveform.stories.tsx │ │ └── Waveform.tsx │ ├── WordPage │ │ ├── ExportModal │ │ │ ├── Linebreak.tsx │ │ │ ├── PlaceholderTable.tsx │ │ │ └── index.tsx │ │ ├── Header.tsx │ │ ├── WordTable.tsx │ │ ├── _style.scss │ │ └── index.tsx │ └── dictionaries │ │ ├── ahdict │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── baidu │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── bing │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── caiyun │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── cambridge │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── cnki │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── cobuild │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── dictionaries.stories.tsx │ │ ├── etymonline │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── eudic │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── google │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── googledict │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── guoyu │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── helpers.ts │ │ ├── hjdict │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── jikipedia │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── jukuu │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── lexico │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── liangan │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── locales.ts │ │ ├── longman │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── macmillan │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── merriamwebster │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── mojidict │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── naver │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── oaldict │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── renren │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── shanbay │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── sogou │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── tencent │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── urban │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── vocabulary │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── weblio │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── weblioejje │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── websterlearner │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── wikipedia │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── youdao │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ ├── youdaotrans │ │ ├── View.tsx │ │ ├── _locales.ts │ │ ├── _style.shadow.scss │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png │ │ └── zdic │ │ ├── View.tsx │ │ ├── _locales.json │ │ ├── _style.shadow.scss │ │ ├── config.ts │ │ ├── engine.ts │ │ └── favicon.png ├── content │ ├── __fake__ │ │ ├── env-instant-capture.ts │ │ ├── env-select-text.ts │ │ └── env.ts │ ├── _style.scss │ ├── components │ │ ├── DictItem │ │ │ ├── DictItem.scss │ │ │ ├── DictItem.stories.tsx │ │ │ ├── DictItem.tsx │ │ │ ├── DictItemBody.tsx │ │ │ ├── DictItemContent.shadow.scss │ │ │ ├── DictItemHead.scss │ │ │ └── DictItemHead.tsx │ │ ├── DictList │ │ │ ├── DictList.container.tsx │ │ │ ├── DictList.scss │ │ │ ├── DictList.stories.tsx │ │ │ └── DictList.tsx │ │ ├── DictPanel │ │ │ ├── DictPanel.container.tsx │ │ │ ├── DictPanel.portal.tsx │ │ │ ├── DictPanel.scss │ │ │ ├── DictPanel.shadow.scss │ │ │ ├── DictPanel.stories.tsx │ │ │ ├── DictPanel.tsx │ │ │ ├── DictPanelStandalone.container.tsx │ │ │ ├── DictPanelStandalone.scss │ │ │ └── DictPanelStandalone.tsx │ │ ├── MenuBar │ │ │ ├── MenuBar.container.tsx │ │ │ ├── MenuBar.scss │ │ │ ├── MenuBar.stories.tsx │ │ │ ├── MenuBar.tsx │ │ │ ├── MenubarBtns.scss │ │ │ ├── MenubarBtns.stories.tsx │ │ │ ├── MenubarBtns.tsx │ │ │ ├── Profiles.scss │ │ │ ├── Profiles.stories.tsx │ │ │ ├── Profiles.tsx │ │ │ ├── SearchBox.scss │ │ │ ├── SearchBox.stories.tsx │ │ │ ├── SearchBox.tsx │ │ │ ├── Suggest.scss │ │ │ ├── Suggest.stories.tsx │ │ │ └── Suggest.tsx │ │ ├── MtaBox │ │ │ ├── MtaBox.container.tsx │ │ │ ├── MtaBox.scss │ │ │ ├── MtaBox.stories.tsx │ │ │ └── MtaBox.tsx │ │ ├── SaladBowl │ │ │ ├── SaladBowl.container.tsx │ │ │ ├── SaladBowl.portal.tsx │ │ │ ├── SaladBowl.shadow.scss │ │ │ ├── SaladBowl.stories.tsx │ │ │ └── SaladBowl.tsx │ │ ├── WaveformBox │ │ │ ├── WaveformBox.container.tsx │ │ │ ├── WaveformBox.scss │ │ │ ├── WaveformBox.stories.tsx │ │ │ └── WaveformBox.tsx │ │ └── WordEditor │ │ │ ├── CtxTransList.scss │ │ │ ├── CtxTransList.stories.tsx │ │ │ ├── CtxTransList.tsx │ │ │ ├── Notes.scss │ │ │ ├── Notes.tsx │ │ │ ├── WordCards.scss │ │ │ ├── WordCards.tsx │ │ │ ├── WordEditor.container.tsx │ │ │ ├── WordEditor.portal.tsx │ │ │ ├── WordEditor.scss │ │ │ ├── WordEditor.shadow.scss │ │ │ ├── WordEditor.stories.tsx │ │ │ ├── WordEditor.tsx │ │ │ ├── WordEditorPanel.scss │ │ │ ├── WordEditorPanel.stories.tsx │ │ │ ├── WordEditorPanel.tsx │ │ │ └── WordEditorStandalone.container.tsx │ ├── index.tsx │ └── redux │ │ ├── epics │ │ ├── index.ts │ │ ├── newSelection.epic.ts │ │ ├── searchStart.epic.ts │ │ └── utils.ts │ │ ├── index.ts │ │ ├── init.ts │ │ └── modules │ │ ├── action-catalog.ts │ │ ├── action-handlers │ │ ├── index.ts │ │ ├── new-selection.ts │ │ ├── open-qs-panel.ts │ │ └── search-start.ts │ │ ├── index.ts │ │ └── state.ts ├── history │ ├── env.ts │ └── index.tsx ├── manifest │ ├── chrome.manifest.json │ ├── common.manifest.js │ ├── edge.manifest.json │ ├── firefox.manifest.json │ └── safari.manifest.json ├── notebook │ ├── env.ts │ └── index.tsx ├── options │ ├── __fake__ │ │ └── env.ts │ ├── _style.scss │ ├── acknowledgement.ts │ ├── components │ │ ├── BtnPreview │ │ │ ├── PreviewIcon.tsx │ │ │ ├── _style.scss │ │ │ └── index.tsx │ │ ├── Entries │ │ │ ├── BlackWhiteList.tsx │ │ │ ├── ContextMenus │ │ │ │ ├── AddModal.tsx │ │ │ │ ├── EditeModal.tsx │ │ │ │ └── index.tsx │ │ │ ├── DictAuths.tsx │ │ │ ├── DictPanel.tsx │ │ │ ├── Dictionaries │ │ │ │ ├── AllDicts.tsx │ │ │ │ ├── DictTitle │ │ │ │ │ ├── _style.scss │ │ │ │ │ └── index.tsx │ │ │ │ ├── EditModal.tsx │ │ │ │ └── index.tsx │ │ │ ├── General.tsx │ │ │ ├── ImportExport.tsx │ │ │ ├── Notebook │ │ │ │ ├── index.tsx │ │ │ │ └── sync-services │ │ │ │ │ ├── ankiconnect.tsx │ │ │ │ │ ├── eudic.tsx │ │ │ │ │ ├── shanbay.tsx │ │ │ │ │ └── webdav.tsx │ │ │ ├── PDF.tsx │ │ │ ├── Permissions.tsx │ │ │ ├── Popup.tsx │ │ │ ├── Privacy.tsx │ │ │ ├── Profiles │ │ │ │ ├── EditNameModal.tsx │ │ │ │ └── index.tsx │ │ │ ├── Pronunciation.tsx │ │ │ ├── QuickSearch │ │ │ │ ├── StandaloneModal.tsx │ │ │ │ ├── TitlebarOffsetModal.tsx │ │ │ │ └── index.tsx │ │ │ └── SearchModes │ │ │ │ ├── index.tsx │ │ │ │ └── searchMode.tsx │ │ ├── EntryError.tsx │ │ ├── EntrySideBar │ │ │ ├── _style.scss │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── HeadInfo │ │ │ │ ├── AckList.tsx │ │ │ │ ├── _style.scss │ │ │ │ └── index.tsx │ │ │ ├── _style.scss │ │ │ └── index.tsx │ │ ├── InputNumberGroup │ │ │ ├── _style.scss │ │ │ └── index.tsx │ │ ├── MainEntry.tsx │ │ ├── MatchPatternModal │ │ │ ├── PatternItem.tsx │ │ │ └── index.tsx │ │ ├── SaladictForm │ │ │ ├── SaveBtn.tsx │ │ │ ├── _style.scss │ │ │ └── index.tsx │ │ ├── SaladictModalForm.tsx │ │ └── SortableList │ │ │ ├── _style.scss │ │ │ ├── index.tsx │ │ │ └── reorder.ts │ ├── env.ts │ ├── helpers │ │ ├── change-entry.ts │ │ ├── layout.ts │ │ ├── panel-store.ts │ │ ├── path-joiner.ts │ │ ├── upload.ts │ │ ├── use-check-dict-auth.ts │ │ └── use-form-dirty.ts │ └── index.tsx ├── popup │ ├── Notebook.tsx │ ├── Popup.tsx │ ├── __fake__ │ │ ├── _style.scss │ │ └── env.ts │ ├── _style.scss │ ├── env.ts │ └── index.tsx ├── quick-search │ ├── env.ts │ ├── index.tsx │ └── quick-search.scss ├── selection │ ├── helper.ts │ ├── index.ts │ ├── instant-capture.ts │ ├── message.ts │ ├── quick-search.ts │ └── select-text.ts ├── typings │ ├── css.d.ts │ ├── global.d.ts │ ├── helpers.ts │ └── message.ts └── word-editor │ ├── env.ts │ ├── index.tsx │ └── word-editor.scss ├── test ├── helper.ts └── specs │ ├── _helpers │ ├── browser-api.spec.ts │ ├── check-update.spec.ts │ ├── chs-to-chz.spec.ts │ ├── lang-check.spec.ts │ ├── profile-manager.spec.ts │ └── promise-more.spec.ts │ ├── background │ ├── audio-manager.spec.ts │ ├── context-menus.spec.ts │ ├── initialization.spec.ts │ ├── pdf-sniffer.spec.ts │ └── sync-manager │ │ └── services │ │ ├── ankiconnect.spec.ts │ │ └── webdav.spec.ts │ └── components │ └── dictionaries │ ├── ahdict │ ├── fixtures.js │ └── requests.mock.ts │ ├── bing │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── cambridge │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── cnki │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── cobuild │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── etymonline │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── eudic │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── googledict │ ├── fixtures.js │ └── requests.mock.ts │ ├── guoyu │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── helpers.ts │ ├── hjdict │ ├── fixtures.js │ └── requests.mock.ts │ ├── jikipedia │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── jukuu │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── lexico │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── liangan │ ├── fixtures.js │ └── requests.mock.ts │ ├── longman │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── macmillan │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── merriamwebster │ ├── engine.spec.ts │ ├── fixtures.js │ ├── requests.mock.ts │ └── testCases.ts │ ├── mojidict │ ├── fixtures.js │ └── requests.mock.ts │ ├── naver │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── oaldict │ ├── fixtures.js │ └── requests.mock.ts │ ├── renren │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── shanbay │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── urban │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── vocabulary │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── weblio │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── weblioejje │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── websterlearner │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── wikipedia │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ ├── youdao │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts │ └── zdic │ ├── engine.spec.ts │ ├── fixtures.js │ └── requests.mock.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | Firefox > 67 2 | Chrome >= 63 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # http://api.fanyi.baidu.com/api/trans/product/prodinfo 2 | BAIDU_APPID= 3 | BAIDU_KEY= 4 | 5 | # https://fanyi.caiyunapp.com/#/api 6 | CAIYUN_TOKEN= 7 | 8 | # https://cloud.google.com/translate/ 9 | GOOGLE_TOKEN= 10 | 11 | # https://deepi.sogou.com/?from=translatepc 12 | SOGOU_PID= 13 | SOGOU_KEY= 14 | 15 | # https://cloud.tencent.com/document/api/213/30654 16 | TENCENT_SECRETID= 17 | TENCENT_SECRETKEY= 18 | 19 | # http://ai.youdao.com/gw.s 20 | YOUDAO_APPKEY= 21 | YOUDAO_KEY= 22 | 23 | # moji dict 24 | MOJI_ID= 25 | 26 | # Download fixtures 27 | # PROXY_PROTOCAL=socks5 28 | # PROXY_HOST=0.0.0.0 29 | # PROXY_PORT=10808 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.min.js binary 2 | /public/** binary 3 | 4 | # For Github language details 5 | /test/**/response/*.html linguist-vendored 6 | /public/** linguist-vendored 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: saladict 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: saladict 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://saladict.crimx.com/support.html'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/_new_dict_chs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 词典推荐 3 | about: 请求沙拉查词添加新词典。 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ## 词典名称以及链接 26 | 27 | 28 | 29 | ## 沙拉查词的已有的词典为什么不能满足? 30 | 31 | 32 | 33 | 34 | ## 单词举例 35 | 41 | 42 | 43 | 44 | ## 截图 45 | 46 | 47 | 48 | 49 | ## 额外信息 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bug related issue. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Device info 11 | - OS: [e.g. Window10] 12 | - Browser Version [e.g. Chrome77] 13 | - Saladict Version [e.g. v7.0.0] 14 | 15 | ## Describe the bug 16 | 17 | 18 | ## To Reproduce 19 | 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | ## Expected behavior 26 | 27 | 28 | ## Screenshots 29 | 30 | 31 | ## Additional context 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Saladict 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: needs-more-info 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | tabWidth: 2 2 | semi: false 3 | singleQuote: true 4 | -------------------------------------------------------------------------------- /.storybook/addons.ts: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register' 2 | import '@storybook/addon-contexts/register' 3 | import '@storybook/addon-actions/register' 4 | import '@storybook/addon-backgrounds/register' 5 | import 'storybook-addon-jsx/register' 6 | import 'storybook-addon-react-docgen/register' 7 | 8 | import addons from '@storybook/addons' 9 | import { STORY_RENDERED } from '@storybook/core-events' 10 | 11 | addons.register('TitleAddon', api => { 12 | api.on(STORY_RENDERED, () => { 13 | const storyData = api.getCurrentStoryData() 14 | document.title = `${storyData.name} - Saladict Storybook` 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /.storybook/assets/shewalksinbeauty.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/.storybook/assets/shewalksinbeauty.mp3 -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /.storybook/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: unset; 3 | height: unset; 4 | overflow-y: scroll; 5 | margin: 0; 6 | padding: 0; 7 | box-sizing: border-box; 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | script: 5 | - yarn lint 6 | - yarn build 7 | # remove agnostic tests 8 | - yarn test --testPathIgnorePatterns 'components/dictionaries' 9 | -------------------------------------------------------------------------------- /.vscode/locales.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "name": { 5 | "$ref": "#/definitions/locale" 6 | }, 7 | "options": { 8 | "$ref": "#/definitions/locales" 9 | }, 10 | "helps": { 11 | "$ref": "#/definitions/locales" 12 | } 13 | }, 14 | "required": ["name"], 15 | "additionalProperties": false, 16 | 17 | "definitions": { 18 | "locale": { 19 | "type": "object", 20 | "properties": { 21 | "en": { "type": "string" }, 22 | "zh-CN": { "type": "string" }, 23 | "zh-TW": { "type": "string" } 24 | }, 25 | "required": ["en", "zh-CN", "zh-TW"], 26 | "additionalProperties": false 27 | }, 28 | "locales": { 29 | "type": "object", 30 | "patternProperties": { 31 | ".+": { 32 | "$ref": "#/definitions/locale" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "json.schemas": [ 3 | { 4 | "fileMatch": ["src/components/dictionaries/**/_locales.json"], 5 | "url": ".vscode/locales.schema.json" 6 | } 7 | ], 8 | "files.watcherExclude": { 9 | "**/.git/objects/**": true, 10 | "**/.git/subtree-cache/**": true, 11 | "**/node_modules/*/**": true, 12 | "**/build/*/**": true, 13 | "**/assets/*/**": true 14 | }, 15 | "conventionalCommits.scopes": [ 16 | "audio-control", 17 | "background", 18 | "components", 19 | "config", 20 | "dicts", 21 | "history", 22 | "locales", 23 | "notebook", 24 | "options", 25 | "panel", 26 | "popup", 27 | "selecion", 28 | "sync-services", 29 | "word-editor" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /assets/content.css: -------------------------------------------------------------------------------- 1 | .saladict-div, 2 | .saladict-div > .saladict-external, 3 | .saladict-div > .saladict-panel { 4 | display: block !important; 5 | width: 0 !important; 6 | height: 0 !important; 7 | margin: 0 !important; 8 | padding: 0 !important; 9 | border: none !important; 10 | outline: none !important; 11 | } 12 | -------------------------------------------------------------------------------- /assets/default.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/default.pdf -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/bar-sp-repeat-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/bar-sp-repeat-x.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/bar-sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/bar-sp.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/conn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/logo.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/swipe_hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/swipe_hr.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/switch_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/switch_button.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/switch_button_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/switch_button_hover.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/trans_tip_submit_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/trans_tip_submit_bg.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/trans_tip_submit_bg_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/trans_tip_submit_bg_hover.png -------------------------------------------------------------------------------- /assets/fanyi.youdao.2.0/ydd_tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/fanyi.youdao.2.0/ydd_tip.png -------------------------------------------------------------------------------- /assets/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-128.png -------------------------------------------------------------------------------- /assets/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-16.png -------------------------------------------------------------------------------- /assets/icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-19.png -------------------------------------------------------------------------------- /assets/icon-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-24.png -------------------------------------------------------------------------------- /assets/icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-38.png -------------------------------------------------------------------------------- /assets/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-48.png -------------------------------------------------------------------------------- /assets/icon-gray-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-128.png -------------------------------------------------------------------------------- /assets/icon-gray-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-16.png -------------------------------------------------------------------------------- /assets/icon-gray-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-19.png -------------------------------------------------------------------------------- /assets/icon-gray-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-24.png -------------------------------------------------------------------------------- /assets/icon-gray-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-38.png -------------------------------------------------------------------------------- /assets/icon-gray-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/assets/icon-gray-48.png -------------------------------------------------------------------------------- /assets/inject-dict-panel.js: -------------------------------------------------------------------------------- 1 | const manifest = browser.runtime.getManifest() 2 | if (manifest.content_scripts) { 3 | for (const script of manifest.content_scripts) { 4 | if (script.js) { 5 | for (const js of script.js) { 6 | const $script = document.createElement('script') 7 | $script.type = 'text/javascript' 8 | $script.src = /^\/|([a-z-]+:\/\/)/i.test(js) ? js : `/${js}` 9 | document.body.appendChild($script) 10 | } 11 | } 12 | if (script.css) { 13 | for (const css of script.css) { 14 | const $link = document.createElement('link') 15 | $link.rel = 'stylesheet' 16 | $link.href = /^\/|([a-z-]+:\/\/)/i.test(css) ? css : `/${css}` 17 | document.head.appendChild($link) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | } 4 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};' 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform' 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};` 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /config/jest/setupTests.js: -------------------------------------------------------------------------------- 1 | import browser from 'sinon-chrome/extensions' 2 | // import Enzyme from 'enzyme' 3 | // import Adapter from 'enzyme-adapter-react-16' 4 | import raf from 'raf' 5 | import fetch from 'node-fetch' 6 | import axios from 'axios' 7 | 8 | // force http in jsdom 9 | axios.defaults.adapter = require('axios/lib/adapters/http') 10 | 11 | window.browser = browser 12 | window.Request = fetch.Request 13 | window.Response = fetch.Response 14 | window.Headers = fetch.Headers 15 | 16 | if (process.env.CI) { 17 | window.FormData = require('form-data') 18 | window.fetch = fetch 19 | 20 | jest.setTimeout(30000) 21 | } 22 | 23 | // Enzyme.configure({ adapter: new Adapter() }) 24 | 25 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 26 | // We don't polyfill it in the browser--this is user's responsibility. 27 | raf.polyfill(global) 28 | -------------------------------------------------------------------------------- /docs/saladict.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/docs/saladict.jpg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const neutrino = require('neutrino') 2 | 3 | process.env.NODE_ENV = process.env.NODE_ENV || 'test' 4 | 5 | module.exports = neutrino().jest() 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator Extension/Saladict___Pop_up_Dictionary_and_Page_Translator_Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator.xcodeproj/project.xcworkspace/xcuserdata/crimx.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator.xcodeproj/project.xcworkspace/xcuserdata/crimx.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator.xcodeproj/xcuserdata/crimx.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Saladict - Pop-up Dictionary and Page Translator.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Saladict - Pop-up Dictionary and Page Translator 4 | // 5 | // Created by Jack Wong on 2021/5/31. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | func applicationDidFinishLaunching(_ notification: Notification) { 14 | // Insert code here to initialize your application 15 | } 16 | 17 | func applicationWillTerminate(_ notification: Notification) { 18 | // Insert code here to tear down your application 19 | } 20 | 21 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 22 | return true 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mac-app/Saladict - Pop-up Dictionary and Page Translator/Saladict - Pop-up Dictionary and Page Translator/Saladict___Pop_up_Dictionary_and_Page_Translator.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | plugins: [ 3 | require('postcss-flexbugs-fixes'), 4 | require('autoprefixer')({ 5 | browsers: ['Chrome >= 55', 'Firefox >= 56'] 6 | }) 7 | ] 8 | }) 9 | -------------------------------------------------------------------------------- /scripts/after-build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | 4 | module.exports = class AfterBuildPlugin { 5 | apply(compiler) { 6 | compiler.hooks.done.tapAsync( 7 | 'AfterBuildPlugin', 8 | (compilation, callback) => { 9 | firefoxFix().then(callback) 10 | } 11 | ) 12 | } 13 | } 14 | 15 | async function firefoxFix() { 16 | await removeYoudaoFanyi() 17 | await removeCaiyun() 18 | } 19 | 20 | async function removeYoudaoFanyi() { 21 | // FF policy 22 | await fs.remove( 23 | path.join(__dirname, '../build/firefox/assets/fanyi.youdao.2.0') 24 | ) 25 | // Stop FF extension check errors 26 | await fs.outputFile( 27 | path.join(__dirname, '../build/firefox/assets/fanyi.youdao.2.0/main.js'), 28 | '' 29 | ) 30 | } 31 | 32 | async function removeCaiyun() { 33 | // FF policy 34 | // caiyun trs is close-sourced 35 | await fs.remove(path.join(__dirname, '../build/firefox/assets/trs.js')) 36 | } 37 | -------------------------------------------------------------------------------- /src/_helpers/__mocks__/selection.ts: -------------------------------------------------------------------------------- 1 | export interface SelectionMock { 2 | hasSelection: jest.Mock 3 | getSelectionText: jest.Mock 4 | getSelectionSentence: jest.Mock 5 | getSelectionInfo: jest.Mock 6 | getDefaultSelectionInfo: jest.Mock 7 | } 8 | 9 | module.exports = jest.genMockFromModule('../selection') 10 | -------------------------------------------------------------------------------- /src/_helpers/analytics/events.ts: -------------------------------------------------------------------------------- 1 | export type GAEventBase = { 2 | category: string 3 | action: string 4 | label?: string 5 | value?: string 6 | } 7 | 8 | type GAEventFactory = T 9 | 10 | export type GAEvent = GAEventFactory< 11 | | { 12 | category: 'Page_Translate' 13 | action: 'Open_Google' | 'Open_Youdao' | 'Open_Caiyun' 14 | label: 15 | | 'From_Browser_Action' 16 | | 'From_Context_Menus' 17 | | 'From_Browser_Shortcut' 18 | } 19 | | { 20 | category: 'PDF_Viewer' 21 | action: 'Open_PDF_Viewer' 22 | label: 23 | | 'From_Browser_Action' 24 | | 'From_Context_Menus' 25 | | 'From_Browser_Shortcut' 26 | } 27 | > 28 | -------------------------------------------------------------------------------- /src/_helpers/dom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * xhtml returns small case 3 | */ 4 | export function isTagName(node: Node, tagName: string): boolean { 5 | return ( 6 | ((node as HTMLElement).tagName || '').toLowerCase() === 7 | tagName.toLowerCase() 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/_helpers/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | /** 4 | * Equivalent to useCallback(fn, []) 5 | */ 6 | export function useFixedCallback(fn: T): T { 7 | return useRef(fn).current 8 | } 9 | 10 | /** 11 | * Equivalent to useMemo(() => value, []) 12 | */ 13 | export function useFixedMemo(fn: () => T): T { 14 | const initedRef = useRef(false) 15 | const valueRef = useRef() 16 | if (!initedRef.current) { 17 | initedRef.current = true 18 | valueRef.current = fn() 19 | } 20 | return valueRef.current! 21 | } 22 | -------------------------------------------------------------------------------- /src/_helpers/integrity.ts: -------------------------------------------------------------------------------- 1 | export const isExtTainted = 2 | browser.runtime.id !== atob('Y2Rvbm5tZmZrZGFvYWpma25vZWVlY21jaGlicG1rbWc=') && 3 | browser.runtime.id !== atob('c2FsYWRpY3RAY3JpbXguY29t') && 4 | browser.runtime.id !== atob('aWRnaG9jYmJhaGFmcGZoam5maHBiZmJtcGVncGhtbXA=') && 5 | /apple/i.test(navigator.vendor) 6 | -------------------------------------------------------------------------------- /src/_helpers/scrollbar-width.ts: -------------------------------------------------------------------------------- 1 | import memoizeOne from 'memoize-one' 2 | 3 | export const getScrollbarWidth = memoizeOne(() => { 4 | if (typeof document === 'undefined') { 5 | return 0 6 | } 7 | 8 | const strut = document.createElement('div') 9 | const strutStyle = strut.style 10 | 11 | strutStyle.position = 'fixed' 12 | strutStyle.left = '0' 13 | strutStyle.overflowY = 'scroll' 14 | strutStyle.visibility = 'hidden' 15 | 16 | document.body.appendChild(strut) 17 | 18 | const width = strut.getBoundingClientRect().right 19 | 20 | strut.remove() 21 | 22 | return width 23 | }) 24 | 25 | export default getScrollbarWidth 26 | -------------------------------------------------------------------------------- /src/_helpers/uniqueKey.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a unique key 3 | */ 4 | export function genUniqueKey(): string { 5 | return ( 6 | Date.now() 7 | .toString() 8 | .slice(6) + 9 | Math.random() 10 | .toString() 11 | .slice(2, 8) 12 | ) 13 | } 14 | 15 | export function genUniqueKeyThunk() { 16 | return genUniqueKey 17 | } 18 | 19 | export function isGeneratedKey(key: unknown): boolean { 20 | return typeof key === 'string' && /^\d{13}$/.test(key) 21 | } 22 | -------------------------------------------------------------------------------- /src/_locales/en/background.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/background' 2 | 3 | export const locale: typeof _locale = { 4 | app: { 5 | off: 'Saladict disabled. (Quick Search Panel is still available)', 6 | tempOff: 7 | 'Saladict disabled on current tab. (Quick Search Panel is still available)', 8 | unsupported: 9 | 'Embedded Saladict panel is unsupported for current tab. Use Standalone Saladict panel instead.' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/_locales/en/langcode.ts: -------------------------------------------------------------------------------- 1 | import en from '@opentranslate/languages/locales/en.json' 2 | import { locale as _locale } from '../zh-CN/langcode' 3 | 4 | export const locale: typeof _locale = { 5 | ...en, 6 | default: 'Default', 7 | ne_NP: 'Nepali', 8 | ara: 'Arabic', 9 | 'bs-Latn': 'Bosnian', 10 | bul: 'Bulgarian', 11 | cht: 'Chinese (Traditional)', 12 | dan: 'Danish', 13 | est: 'Estonian', 14 | fin: 'Finnish', 15 | fra: 'French', 16 | iw: 'Hebrew', 17 | jp: 'Japanese', 18 | kor: 'Korean', 19 | kr: 'Korean', 20 | pt_BR: 'Brazilian', 21 | rom: 'Romanian', 22 | slo: 'Slovenian', 23 | spa: 'Spanish', 24 | swe: 'Swedish', 25 | tl: 'Tagalog (Filipino)', 26 | vie: 'Vietnamese', 27 | zh: 'Chinese (Simplified)', 28 | 'zh-CHS': 'Chinese (Simplified)', 29 | 'zh-CHT': 'Chinese (Traditional)' 30 | } 31 | -------------------------------------------------------------------------------- /src/_locales/en/popup.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/popup' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Saladict Browser Action Panel', 5 | app_active_title: 'Enable Inline Translator', 6 | app_temp_active_title: 'Temporary disabled to the page', 7 | instant_capture_pinned: ' (pinned) ', 8 | instant_capture_title: 'Enable Instant Capture', 9 | notebook_added: 'Added', 10 | notebook_empty: 'No selection found on the current page', 11 | notebook_error: 'Cannot add selected text to Notebook', 12 | page_no_response: 'Page no response', 13 | qrcode_title: 'Qrcode of the page' 14 | } 15 | -------------------------------------------------------------------------------- /src/_locales/es/background.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/background' 2 | 3 | export const locale: typeof _locale = { 4 | app: { 5 | off: 'Saladict desactivado. (El panel de búsqueda rápida sigue disponible)', 6 | tempOff: 7 | 'Saladict desactivado en la pestaña actual. (El panel de búsqueda rápida sigue disponible)', 8 | unsupported: 9 | 'El panel Saladict incrustado no es compatible con la pestaña actual. Utilice el panel Saladict independiente en su lugar.' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/_locales/es/langcode.ts: -------------------------------------------------------------------------------- 1 | import en from '@opentranslate/languages/locales/en.json' 2 | import { locale as _locale } from '../zh-CN/langcode' 3 | 4 | export const locale: typeof _locale = { 5 | ...en, 6 | default: 'Predeterminado', 7 | ara: 'Arabe', 8 | 'bs-Latn': 'Bosnio', 9 | bul: 'Búlgaro', 10 | cht: 'Chino (Tradicional)', 11 | dan: 'Danés', 12 | est: 'Estonio', 13 | fin: 'Finlandés', 14 | fra: 'Francés', 15 | iw: 'Hebreo', 16 | jp: 'Japonés', 17 | kor: 'Coreano', 18 | kr: 'Coreano', 19 | pt_BR: 'Brasileño', 20 | rom: 'Rumano', 21 | slo: 'Esloveno', 22 | spa: 'Español', 23 | swe: 'Sueco', 24 | tl: 'Tagalo (Filipino)', 25 | vie: 'Vietnamita', 26 | zh: 'Chino (Simplificado)', 27 | 'zh-CHS': 'Chino (Simplificado)', 28 | 'zh-CHT': 'Chino (Tradicional)' 29 | } 30 | -------------------------------------------------------------------------------- /src/_locales/es/popup.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/popup' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Panel de acciones del navegador de Saladict', 5 | app_active_title: 'Activar el traductor en línea', 6 | app_temp_active_title: 'Desactivación temporal de la página', 7 | instant_capture_pinned: ' (fijado) ', 8 | instant_capture_title: 'Activar captura instantánea', 9 | notebook_added: 'Añadido', 10 | notebook_empty: 'No se ha encontrado ninguna selección en la página actual', 11 | notebook_error: 'No se puede añadir el texto seleccionado al bloc de notas', 12 | page_no_response: 'La pagina no responde', 13 | qrcode_title: 'Qrcode de la página' 14 | } 15 | -------------------------------------------------------------------------------- /src/_locales/ne/background.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/background' 2 | 3 | export const locale: typeof _locale = { 4 | app: { 5 | off: 'सलाडिक्ट असक्षम। (द्रुत खोज प्यानल अझै उपलब्ध छ)', 6 | tempOff: 7 | 'हालको ट्याबमा सलाडिक्ट असक्षम पारियो। (द्रुत खोज प्यानल अझै उपलब्ध छ)', 8 | unsupported: 9 | 'हालको ट्याबका लागि एम्बेडेड सलाडिक प्यानल असमर्थित छ। यसको सट्टा स्ट्यान्डअलोन सलाडिक्ट प्यानल प्रयोग गर्नुहोस्।' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/_locales/ne/langcode.ts: -------------------------------------------------------------------------------- 1 | import en from '@opentranslate/languages/locales/en.json' 2 | import { locale as _locale } from '../zh-CN/langcode' 3 | 4 | export const locale: typeof _locale = { 5 | ...en, 6 | default: 'पुर्वनिर्धारित', 7 | ne_NP: 'नेपाली', 8 | ara: 'अरबी', 9 | 'bs-Latn': 'बोस्नियाली (ल्याटिन)', 10 | bul: 'बुल्गेरियाली', 11 | cht: 'चिनियाँ (परम्परागत)', 12 | dan: 'डेनिस', 13 | est: 'इस्टोनियाली', 14 | fin: 'फिनिस', 15 | fra: 'फ्रान्सेली', 16 | iw: 'हिब्रु', 17 | jp: 'जापानी', 18 | kor: 'कोरियाली', 19 | kr: 'कोरियाली', 20 | pt_BR: 'पोर्तुगी (ब्राजिल)', 21 | rom: 'रोमानियाली', 22 | slo: 'स्लोभाकियाली', 23 | spa: 'स्पेनिस', 24 | swe: 'स्विडिस', 25 | tl: 'फिलिपिनी (तागालोग)', 26 | vie: 'भियतनामी', 27 | zh: 'चिनियाँ (सरलिकृत)', 28 | 'zh-CHS': 'चिनियाँ (सरलिकृत)', 29 | 'zh-CHT': 'चिनियाँ (परम्परागत)', 30 | } 31 | -------------------------------------------------------------------------------- /src/_locales/ne/popup.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/popup' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'सलाडिक्ट ब्राउजर एक्सन प्यानल', 5 | app_active_title: 'ईनलाइन अनुवादक सक्षम गर्नुहोस्', 6 | app_temp_active_title: 'पृष्ठमा अस्थायी रूपमा असक्षम गरियो', 7 | instant_capture_pinned: ' (ताराङकित)', 8 | instant_capture_title: 'तत्काल क्याप्चर सक्षम गर्नुहोस्', 9 | notebook_added: 'थपियो', 10 | notebook_empty: 'हालको पृष्ठमा कुनै चयन फेला परेन', 11 | notebook_error: 'नोटबुकमा चयन गरिएको पाठ थप्न सकिएन', 12 | page_no_response: 'पृष्ठको कुनै प्रतिक्रिया छैन', 13 | qrcode_title: 'पृष्ठको क्युआर कोड' 14 | } 15 | -------------------------------------------------------------------------------- /src/_locales/zh-CN/background.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | app: { 3 | off: '沙拉查词已关闭(快捷查词依然可用)', 4 | tempOff: '沙拉查词已对当前标签关闭(快捷查词依然可用)', 5 | unsupported: '内嵌查词面板不支持此类页面(独立窗口查词面板依然可用)' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/_locales/zh-CN/langcode.ts: -------------------------------------------------------------------------------- 1 | import zhCN from '@opentranslate/languages/locales/zh-CN.json' 2 | 3 | export const locale = { 4 | ...zhCN, 5 | default: '随扩展语言', 6 | ara: '阿拉伯语', 7 | 'bs-Latn': '波斯尼亚语', 8 | bul: '保加利亚语', 9 | cht: '中文(繁体)', 10 | dan: '丹麦语', 11 | est: '爱沙尼亚语', 12 | fin: '芬兰语', 13 | fra: '法语', 14 | iw: '希伯来语', 15 | jp: '日语', 16 | kor: '韩语', 17 | kr: '韩语', 18 | pt_BR: '巴西语', 19 | rom: '罗马尼亚语', 20 | slo: '斯洛文尼亚语', 21 | spa: '西班牙语', 22 | swe: '瑞典语', 23 | tl: '塔加路语(菲律宾语)', 24 | vie: '越南语', 25 | zh: '中文(简体)', 26 | 'zh-CHS': '中文(简体)', 27 | 'zh-CHT': '中文(繁体)' 28 | } 29 | -------------------------------------------------------------------------------- /src/_locales/zh-CN/popup.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | title: '沙拉查词-右上弹框', 3 | app_active_title: '启用划词', 4 | app_temp_active_title: '对当前页暂时关闭划词', 5 | instant_capture_pinned: '(钉住)', 6 | instant_capture_title: '开启鼠标悬浮取词', 7 | notebook_added: '已添加', 8 | notebook_empty: '当前页面没有发现选词', 9 | notebook_error: '无法添加选词到生词本', 10 | page_no_response: '页面无响应', 11 | qrcode_title: '当前页面二维码' 12 | } 13 | -------------------------------------------------------------------------------- /src/_locales/zh-TW/background.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/background' 2 | 3 | export const locale: typeof _locale = { 4 | app: { 5 | off: '沙拉查詞已關閉(快捷查詞依然可用)', 6 | tempOff: '沙拉查詞已對當前標籤關閉(快捷查詞依然可用)', 7 | unsupported: '內嵌查字介面不支援此類頁面(獨立視窗查字介面依然可用)' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/_locales/zh-TW/langcode.ts: -------------------------------------------------------------------------------- 1 | import zhTW from '@opentranslate/languages/locales/zh-TW.json' 2 | import { locale as _locale } from '../zh-CN/langcode' 3 | 4 | export const locale: typeof _locale = { 5 | ...zhTW, 6 | default: '同介面語言', 7 | ara: '阿拉伯語', 8 | 'bs-Latn': '波斯尼亞語', 9 | bul: '保加利亞語', 10 | cht: '中文(繁體)', 11 | dan: '丹麥語', 12 | est: '愛沙尼亞語', 13 | fin: '芬蘭語', 14 | fra: '法語', 15 | iw: '希伯來語', 16 | jp: '日語', 17 | kor: '韓語', 18 | kr: '韓語', 19 | pt_BR: '巴西語', 20 | rom: '羅馬尼亞語', 21 | slo: '斯洛維尼亞語', 22 | spa: '西班牙語', 23 | swe: '瑞典語', 24 | tl: '他加祿語(菲律賓語)', 25 | vie: '越南語', 26 | zh: '中文(簡體)', 27 | 'zh-CHS': '中文(簡體)', 28 | 'zh-CHT': '中文(繁體)' 29 | } 30 | -------------------------------------------------------------------------------- /src/_locales/zh-TW/popup.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from '../zh-CN/popup' 2 | 3 | export const locale: typeof _locale = { 4 | title: '沙拉查詞-右上彈框', 5 | app_active_title: '啟用滑鼠選字', 6 | app_temp_active_title: '對目前頁面暫時關閉滑鼠選字', 7 | instant_capture_pinned: '(釘選)', 8 | instant_capture_title: '啟用滑鼠懸浮取詞', 9 | notebook_added: '已新增', 10 | notebook_empty: '目前頁面沒有發現選詞', 11 | notebook_error: '無法新增選詞到生字本', 12 | page_no_response: '頁面無回應', 13 | qrcode_title: '目前頁面二維條碼' 14 | } 15 | -------------------------------------------------------------------------------- /src/_sass_shared/_global/_variables.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/_sass_shared/_global/_variables.scss -------------------------------------------------------------------------------- /src/_sass_shared/_global/_z-indices.scss: -------------------------------------------------------------------------------- 1 | // Max 2^31 − 1 = 2147483647 2 | 3 | $global-zindex-dropdown-backdrop: 2147483639 !default; 4 | $global-zindex-navbar: 2147483640 !default; 5 | $global-zindex-dropdown: 2147483641 !default; 6 | $global-zindex-fixed: 2147483642 !default; 7 | $global-zindex-sticky: 2147483643 !default; 8 | $global-zindex-modal-backdrop: 2147483644 !default; 9 | $global-zindex-modal: 2147483645 !default; 10 | $global-zindex-popover: 2147483646 !default; 11 | $global-zindex-tooltip: 2147483647 !default; 12 | 13 | 14 | $global-zindex-dicteditor: 2147483646; 15 | $global-zindex-dictpanel-dragbg: 2147483646; 16 | $global-zindex-dictpanel: 2147483647; 17 | $global-zindex-bowl: 2147483647; 18 | -------------------------------------------------------------------------------- /src/_sass_shared/_namespace.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | -------------------------------------------------------------------------------- /src/_sass_shared/_reset.scss: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------*\ 2 | Custom reset 3 | \*-----------------------------------------------*/ 4 | 5 | @import '~normalize-scss'; 6 | 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | ul, 12 | li, 13 | button { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | img { 19 | display: block; 20 | max-width: 95%; 21 | } 22 | 23 | p { 24 | margin: 0.5em 0; 25 | } 26 | 27 | ul li { 28 | list-style-type: none; 29 | } 30 | 31 | button { 32 | background: transparent; 33 | border: none; 34 | 35 | &:hover { 36 | outline: none; 37 | } 38 | } 39 | 40 | a { 41 | color: #f9690e; 42 | text-decoration: none; 43 | 44 | &:hover { 45 | text-decoration: underline; 46 | } 47 | } 48 | 49 | select { 50 | color: #666; 51 | border: 1px solid rgba(133, 133, 133, 0.28); 52 | background: transparent; 53 | } 54 | -------------------------------------------------------------------------------- /src/_sass_shared/_theme.scss: -------------------------------------------------------------------------------- 1 | .saladict-theme { 2 | background-color: #fff; 3 | color: #333; 4 | --color-brand: #5caf9e; 5 | --color-background: #fff; 6 | --color-rgb-background: 255, 255, 255; 7 | --color-font: #333; 8 | --color-font-grey: #666; 9 | --color-divider: #ddd; 10 | 11 | @include isDarkMode { 12 | background-color: #222; 13 | color: #ddd; 14 | --color-brand: #1e947e; 15 | --color-background: #222; 16 | --color-rgb-background: 34, 34, 34; 17 | --color-font: #ddd; 18 | --color-font-grey: #aaa; 19 | --color-divider: #4d4748; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app-config/auth.ts: -------------------------------------------------------------------------------- 1 | import { auth as baidu } from '@/components/dictionaries/baidu/auth' 2 | import { auth as caiyun } from '@/components/dictionaries/caiyun/auth' 3 | import { auth as sogou } from '@/components/dictionaries/sogou/auth' 4 | import { auth as tencent } from '@/components/dictionaries/tencent/auth' 5 | import { auth as youdaotrans } from '@/components/dictionaries/youdaotrans/auth' 6 | 7 | export const defaultDictAuths = { 8 | baidu, 9 | caiyun, 10 | sogou, 11 | tencent, 12 | youdaotrans 13 | } 14 | 15 | export type DictAuths = typeof defaultDictAuths 16 | 17 | export const getDefaultDictAuths = (): DictAuths => 18 | JSON.parse(JSON.stringify(defaultDictAuths)) 19 | -------------------------------------------------------------------------------- /src/assets/bowl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/engines/bing.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/engines/bing.ico -------------------------------------------------------------------------------- /src/assets/engines/howjsay.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/engines/howjsay.ico -------------------------------------------------------------------------------- /src/assets/engines/ud.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/engines/ud.ico -------------------------------------------------------------------------------- /src/assets/engines/vocabulary.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/engines/vocabulary.ico -------------------------------------------------------------------------------- /src/assets/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/iconfont.woff -------------------------------------------------------------------------------- /src/assets/leaf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/tomato.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/assets/wechat.png -------------------------------------------------------------------------------- /src/audio-control/audio-control.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 165px; 4 | overflow: hidden; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | @import '@/_sass_shared/_theme.scss'; 10 | @import '@/components/Waveform/Waveform.scss'; 11 | -------------------------------------------------------------------------------- /src/audio-control/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Waveform from '@/components/Waveform/Waveform' 4 | 5 | import './audio-control.scss' 6 | 7 | const searchParams = new URL(document.URL).searchParams 8 | 9 | const darkMode = Boolean(searchParams.get('darkmode')) 10 | 11 | ReactDOM.render( 12 | , 13 | document.getElementById('root') 14 | ) 15 | -------------------------------------------------------------------------------- /src/background/__fake__/env.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import AxiosMockAdapter from 'axios-mock-adapter' 3 | 4 | browser.runtime.sendMessage['_sender'].callsFake(() => ({ 5 | tab: { 6 | id: 'saladict-page' 7 | } 8 | })) 9 | 10 | // mock dict search requests 11 | const dictMock = new AxiosMockAdapter(axios) 12 | const dictMockReq = require.context( 13 | '../../../test/specs/components/dictionaries/', 14 | true, 15 | /requests\.mock\.ts$/ 16 | ) 17 | dictMockReq.keys().forEach(filename => { 18 | const { mockRequest } = dictMockReq(filename) 19 | mockRequest(dictMock) 20 | }) 21 | dictMock.onAny().reply(config => { 22 | console.warn(`Unmatch url: ${config.url}`, config) 23 | return [404, {}] 24 | }) 25 | -------------------------------------------------------------------------------- /src/background/__mocks__/database.ts: -------------------------------------------------------------------------------- 1 | export const db = jest.fn() 2 | export const isInNotebook = jest.fn() 3 | export const saveWord = jest.fn() 4 | export const deleteWord = jest.fn() 5 | export const getWordsByText = jest.fn() 6 | export const getAllWords = jest.fn() 7 | -------------------------------------------------------------------------------- /src/background/database/sync-meta.ts: -------------------------------------------------------------------------------- 1 | import { getDB } from './core' 2 | 3 | export async function getSyncMeta(serviceID: string) { 4 | const db = await getDB() 5 | return db.syncmeta 6 | .where('id') 7 | .equals(serviceID) 8 | .first(record => record && record.json) 9 | .catch(e => { 10 | if (process.env.DEBUG) { 11 | console.error(e) 12 | } 13 | }) 14 | } 15 | 16 | export async function setSyncMeta(serviceID: string, text: string) { 17 | const db = await getDB() 18 | return db.syncmeta.put({ id: serviceID, json: text }) 19 | } 20 | 21 | export async function deleteSyncMeta(serviceID: string) { 22 | const db = await getDB() 23 | return db.syncmeta.delete(serviceID).catch(e => { 24 | if (process.env.DEBUG) { 25 | console.error(e) 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/background/database/write.ts: -------------------------------------------------------------------------------- 1 | import { Word, DBArea } from '@/_helpers/record-manager' 2 | import { Message } from '@/typings/message' 3 | import { getDB } from './core' 4 | 5 | export async function saveWord({ 6 | area, 7 | word 8 | }: Message<'SAVE_WORD'>['payload']) { 9 | const db = await getDB() 10 | return db[area].put(word) 11 | } 12 | 13 | export async function saveWords({ 14 | area, 15 | words 16 | }: { 17 | area: DBArea 18 | words: Word[] 19 | }) { 20 | if (process.env.DEBUG) { 21 | if (words.length !== new Set(words.map(w => w.date)).size) { 22 | console.error('save Words: duplicate records') 23 | } 24 | } 25 | const db = await getDB() 26 | return db[area].bulkPut(words) 27 | } 28 | 29 | export async function deleteWords({ 30 | area, 31 | dates 32 | }: Message<'DELETE_WORDS'>['payload']) { 33 | const db = await getDB() 34 | return Array.isArray(dates) ? db[area].bulkDelete(dates) : db[area].clear() 35 | } 36 | -------------------------------------------------------------------------------- /src/background/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | window.__SALADICT_BACKGROUND_PAGE__ = true 5 | -------------------------------------------------------------------------------- /src/background/sync-manager/__mocks__/helpers.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, Observable } from 'rxjs' 2 | 3 | const emptyPromise = (): Promise => Promise.resolve() 4 | 5 | export const setSyncConfig = jest.fn(emptyPromise) 6 | 7 | export const getSyncConfig = jest.fn(emptyPromise) 8 | 9 | export const createSyncConfigStream = jest.fn((): Observable => EMPTY) 10 | 11 | export const setMeta = jest.fn(emptyPromise) 12 | 13 | export const getMeta = jest.fn(emptyPromise) 14 | 15 | export const deleteMeta = jest.fn(emptyPromise) 16 | 17 | export const setNotebook = jest.fn(emptyPromise) 18 | 19 | export const getNotebook = jest.fn(emptyPromise) 20 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/ankiconnect/_locales/en.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Anki Connect', 5 | error: { 6 | server: 'Cannot connect to Anki Connect. Please make sure Anki is running.', 7 | deck: 'Deck not found in Anki.', 8 | notetype: 'Note type not found in Anki.', 9 | add: 'Failed to add word to Anki.' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/ankiconnect/_locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | title: 'Anki Connect', 3 | error: { 4 | server: '无法连接 Anki Connect,请确认 Anki 已在运行。', 5 | deck: 'Anki 中没有找到相应牌组。', 6 | notetype: 'Anki 中没有找到相应笔记类型。', 7 | add: '添加单词到 Anki 失败。' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/ankiconnect/_locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Anki Connect', 5 | error: { 6 | server: '無法連線 Anki Connect,請確認 Anki 已在執行。', 7 | deck: 'Anki 中沒有找到相應牌組。', 8 | notetype: 'Anki 中沒有找到相應筆記型別。', 9 | add: '新增單詞到 Anki 失敗。' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/eudic/_locales/en.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Eudic Word Syncing', 5 | open: 'Open', 6 | error: { 7 | network: 8 | 'Unable to access the new word book of Eudic, please check the network.', 9 | illegal_token: 'Please set legal Eudic authorization information.', 10 | no_wordbook: 11 | 'Unable to add to the new word book of European dictionary. Please go to the European official website to generate the default new word book first.' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/eudic/_locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | title: '欧路单词同步', 3 | open: '打开', 4 | error: { 5 | network: '无法访问欧路词典生词本,请检查网络。', 6 | illegal_token: '请设置合法的欧路词典授权信息', 7 | no_wordbook: '无法添加到欧路词典生词本,请先上欧路官网生成默认生词本' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/eudic/_locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: '歐路單字同步', 5 | open: '開啟', 6 | error: { 7 | network: '無法訪問歐路詞典生詞本,請檢查網絡。', 8 | illegal_token: '請設定合法的歐路詞典授權資訊', 9 | no_wordbook: '無法添加到歐路詞典生詞本,請先上歐路官網生成默認生詞本' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/shanbay/_locales/en.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'Shanbay Word Syncing', 5 | open: 'Open', 6 | error: { 7 | login: 'Shanbay login failed. Click to open shanbay.com.', 8 | network: 9 | 'Unable to access shanbay.com. Please check your network connection.', 10 | word: 11 | "Unable to add to Shanbay notebook. This word is not in Shanbay's vocabulary database." 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/shanbay/_locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | title: '扇贝单词同步', 3 | open: '打开', 4 | error: { 5 | login: '扇贝登录已失效,请点击打开官网重新登录。', 6 | network: '无法访问扇贝生词本,请检查网络。', 7 | word: '无法添加到扇贝生词本,扇贝单词库没有收录此单词。' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/shanbay/_locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: '扇貝單字同步', 5 | open: '開啟', 6 | error: { 7 | login: '扇貝登入已失效,請點選開啟官網重新登入。', 8 | network: '無法訪問扇貝生字本,請檢查網路。', 9 | word: '無法新增到扇貝生字本,扇貝單字庫沒有收錄此單字。' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/webdav/_locales/en.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'WebDAV Word Syncing', 5 | error: { 6 | dir: 'Incorrect "Saladict" directory on server.', 7 | download: 8 | 'Download failed. Unable to connect WebDAV Server. If browser proxy is enabled please adjust rules to bypass WebDAV server.', 9 | internal: 'Unable to save settings.', 10 | missing: 'Missing "Saladict" directory on server.', 11 | mkcol: 12 | 'Cannot create "Saladict" directory on server. Please create the directory manualy on server.', 13 | network: 'Network error. Cannot connect to server.', 14 | parse: 'Incorrect response XML from server.', 15 | unauthorized: 'Incorrect account or password.' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/webdav/_locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export const locale = { 2 | title: 'WebDAV 单词同步', 3 | error: { 4 | dir: '服务器上“Saladict”目录格式不正确,请检查。', 5 | download: 6 | '下载失败。无法访问 WebDAV 服务器。如启用了浏览器代理请调整规则,不要代理 WebDAV 服务器。', 7 | internal: '无法保存。', 8 | missing: '服务器上缺少“Saladict”目录。', 9 | mkcol: '无法在服务器创建“Saladict”目录。请手动在服务器上创建。', 10 | network: '连接服务器失败。', 11 | parse: '服务器返回 XML 格式不正确。', 12 | unauthorized: '账户或密码不正确。' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/background/sync-manager/services/webdav/_locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import { locale as _locale } from './zh-CN' 2 | 3 | export const locale: typeof _locale = { 4 | title: 'WebDAV 單字同步', 5 | error: { 6 | dir: '伺服器上“Saladict”目錄格式不正確,請檢查。', 7 | download: 8 | '下載失敗。無法訪問 WebDAV 伺服器。如啟用了瀏覽器代理請調整規則,不要代理 WebDAV 伺服器。', 9 | internal: '無法儲存。', 10 | missing: '伺服器上缺少“Saladict”目錄。', 11 | mkcol: '無法在伺服器建立“Saladict”目錄。請手動在伺服器上建立。', 12 | network: '連線伺服器失敗。', 13 | parse: '伺服器返回 XML 格式不正確。', 14 | unauthorized: '帳戶或密碼不正確。' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/background/types.ts: -------------------------------------------------------------------------------- 1 | import { AppConfig } from '@/app-config' 2 | import { Profile, ProfileIDList } from '@/app-config/profiles' 3 | 4 | declare global { 5 | interface Window { 6 | appConfig: AppConfig 7 | activeProfile: Profile 8 | profileIDList: ProfileIDList 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/EntryBox/EntryBox.scss: -------------------------------------------------------------------------------- 1 | .entryBox-Wrap { 2 | padding-top: 0.8em; 3 | } 4 | 5 | .entryBox { 6 | position: relative; 7 | border: 1px solid #c76e06; 8 | border-radius: 5px; 9 | margin-bottom: 1em; 10 | padding: 1em 0.5em 0.5em 0.5em; 11 | } 12 | 13 | .entryBox-Title { 14 | position: absolute; 15 | top: 0; 16 | left: 1em; 17 | transform: translateY(-50%); 18 | max-width: 90%; 19 | overflow: hidden; 20 | padding: 0 5px; 21 | white-space: nowrap; 22 | text-overflow: ellipsis; 23 | font-size: 1.2em; 24 | background: var(--color-background); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/EntryBox/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ComponentProps, ReactNode } from 'react' 2 | 3 | export interface EntryBoxProps extends Omit, 'title'> { 4 | title: ReactNode 5 | } 6 | 7 | /** 8 | * Box-wrapped content 9 | */ 10 | export const EntryBox: FC = props => { 11 | const { title, className, children, ...restProps } = props 12 | return ( 13 |
17 |
18 |

{title}

19 |
{children}
20 |
21 |
22 | ) 23 | } 24 | 25 | export default EntryBox 26 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ComponentType } from 'react' 2 | 3 | interface ErrorBoundaryProps { 4 | /** Reanders on error */ 5 | error?: ComponentType 6 | } 7 | 8 | interface ErrorBoundaryState { 9 | hasError: boolean 10 | } 11 | 12 | export class ErrorBoundary extends React.PureComponent< 13 | ErrorBoundaryProps, 14 | ErrorBoundaryState 15 | > { 16 | state: ErrorBoundaryState = { 17 | hasError: false 18 | } 19 | 20 | static getDerivedStateFromError() { 21 | return { hasError: true } 22 | } 23 | 24 | render() { 25 | return this.state.hasError 26 | ? this.props.error 27 | ? React.createElement(this.props.error) 28 | : null 29 | : this.props.children 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/HoverBox/HoverBox.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/FloatBox/FloatBox.scss'; 2 | 3 | .hoverBox-Container { 4 | display: inline-block; 5 | position: relative; 6 | } 7 | 8 | .hoverBox-FloatBox { 9 | position: absolute; 10 | z-index: $global-zindex-dictpanel; 11 | } 12 | 13 | .csst-hoverBox { 14 | @include isAnimate(-enter) { 15 | opacity: 0; 16 | transition: opacity 0.4s; 17 | } 18 | 19 | @include isAnimate(-enter-active, -exit) { 20 | opacity: 1; 21 | transition: opacity 0.4s; 22 | } 23 | 24 | @include isAnimate(-exit-active) { 25 | opacity: 0; 26 | transition: opacity 0.4s; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/ShadowPortal/ShadowPortal.scss: -------------------------------------------------------------------------------- 1 | .shadowPortal-appear, 2 | .shadowPortal-enter { 3 | opacity: 0; 4 | } 5 | .shadowPortal-appear-active, 6 | .shadowPortal-enter-active { 7 | opacity: 1; 8 | transition: opacity 0.4s; 9 | } 10 | 11 | .shadowPortal-exit { 12 | opacity: 1; 13 | } 14 | .shadowPortal-exit-active { 15 | opacity: 0; 16 | transition: opacity 0.1s; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Speaker/Speaker.scss: -------------------------------------------------------------------------------- 1 | $speaker-duration: 1s !default; 2 | 3 | .saladict-Speaker { 4 | display: inline-block; 5 | width: 1.1em; 6 | height: 1.1em; 7 | text-decoration: none; 8 | margin: 0 5px; 9 | padding: 0; 10 | line-height: 1; 11 | vertical-align: text-bottom; 12 | border: none; 13 | background: no-repeat left / cover url('~@/assets/Speaker.svg'); 14 | user-select: none; 15 | cursor: pointer; 16 | 17 | &:hover { 18 | outline: none; 19 | } 20 | } 21 | 22 | .saladict-Speaker.isActive { 23 | animation: saladict-Speaker-playing $speaker-duration steps(6) infinite; 24 | } 25 | 26 | @keyframes saladict-Speaker-playing { 27 | from { 28 | background-position-x: 0; 29 | } 30 | 70% { 31 | background-position-x: 100%; 32 | } 33 | 100% { 34 | background-position-x: 100%; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/WordPage/ExportModal/Linebreak.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { Select } from 'antd' 3 | import { TFunction } from 'i18next' 4 | 5 | export type LineBreakOption = '' | 'n' | 'p' | 'br' | 'space' 6 | 7 | export interface LineBreakProps { 8 | t: TFunction 9 | value: LineBreakOption 10 | onChange: (value: LineBreakOption) => void 11 | } 12 | 13 | export const LineBreak: FC = ({ t, value, onChange }) => ( 14 | 21 | ) 22 | 23 | export const LineBreakMemo = React.memo(LineBreak) 24 | -------------------------------------------------------------------------------- /src/components/dictionaries/ahdict/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Amercian Heritage Dict", 4 | "zh-CN": "美国传统词典", 5 | "zh-TW": "美國傳統詞典" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/ahdict/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type AhdictConfig = DictItem<{ 4 | resultnum: number 5 | }> 6 | 7 | export default (): AhdictConfig => ({ 8 | lang: '10000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: false, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 240, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | resultnum: 4 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/ahdict/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/ahdict/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales({ 4 | en: 'Baidu Translate', 5 | 'zh-CN': '百度翻译', 6 | 'zh-TW': '百度翻譯' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/auth.ts: -------------------------------------------------------------------------------- 1 | export const auth = { 2 | appid: '', 3 | key: '' 4 | } 5 | 6 | export const url = 'http://api.fanyi.baidu.com/api/trans/product/prodinfo' 7 | -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type BaiduLanguage = Subunion< 9 | Language, 10 | 'zh-CN' | 'zh-TW' | 'en' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'ru' | 'nl' 11 | > 12 | 13 | export type BaiduConfig = MachineDictItem 14 | 15 | export default (): BaiduConfig => 16 | machineConfig( 17 | ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'ru', 'nl'], 18 | {}, 19 | {}, 20 | {} 21 | ) 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/baidu/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/baidu/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/bing/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Bing Dict", 4 | "zh-CN": "必应词典", 5 | "zh-TW": "必應詞典" 6 | }, 7 | "options": { 8 | "tense": { 9 | "en": "Show sense", 10 | "zh-CN": "显示单词时态", 11 | "zh-TW": "展示單詞時態" 12 | }, 13 | "phsym": { 14 | "en": "Show pronunciation", 15 | "zh-CN": "显示单词发音", 16 | "zh-TW": "展示單詞發音" 17 | }, 18 | "cdef": { 19 | "en": "Show definitions", 20 | "zh-CN": "显示单词解释", 21 | "zh-TW": "展示單詞解釋" 22 | }, 23 | "related": { 24 | "en": "Show related results", 25 | "zh-CN": "失败时显示备选", 26 | "zh-TW": "失敗時顯示備選" 27 | }, 28 | "sentence": { 29 | "en": "Show sentences", 30 | "zh-CN": "显示例句", 31 | "zh-TW": "展示例句" 32 | }, 33 | "sentence_unit": { 34 | "en": "results", 35 | "zh-CN": "个", 36 | "zh-TW": "個" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/bing/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type BingConfig = DictItem<{ 4 | tense: boolean 5 | phsym: boolean 6 | cdef: boolean 7 | related: boolean 8 | sentence: number 9 | }> 10 | 11 | export default (): BingConfig => ({ 12 | lang: '11000000', 13 | selectionLang: { 14 | english: true, 15 | chinese: true, 16 | japanese: false, 17 | korean: false, 18 | french: false, 19 | spanish: false, 20 | deutsch: false, 21 | others: false, 22 | matchAll: false 23 | }, 24 | defaultUnfold: { 25 | english: true, 26 | chinese: true, 27 | japanese: true, 28 | korean: true, 29 | french: true, 30 | spanish: true, 31 | deutsch: true, 32 | others: true, 33 | matchAll: false 34 | }, 35 | preferredHeight: 240, 36 | selectionWC: { 37 | min: 1, 38 | max: 5 39 | }, 40 | options: { 41 | tense: true, 42 | phsym: true, 43 | cdef: true, 44 | related: true, 45 | sentence: 4 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /src/components/dictionaries/bing/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/bing/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales({ 4 | en: 'LingoCloud', 5 | 'zh-CN': '彩云小译', 6 | 'zh-TW': '彩雲小譯' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/auth.ts: -------------------------------------------------------------------------------- 1 | export const auth = { 2 | token: '' 3 | } 4 | 5 | export const url = 'https://fanyi.caiyunapp.com/#/api' 6 | -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type CaiyunLanguage = Subunion 9 | 10 | export type CaiyunConfig = MachineDictItem 11 | 12 | export default (): CaiyunConfig => 13 | machineConfig( 14 | ['zh-CN', 'en', 'ja'], 15 | { 16 | lang: '11010000' 17 | }, 18 | {}, 19 | {} 20 | ) 21 | -------------------------------------------------------------------------------- /src/components/dictionaries/caiyun/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/caiyun/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/cambridge/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Cambridge Dictionary", 4 | "zh-CN": "剑桥词典", 5 | "zh-TW": "劍橋詞典" 6 | }, 7 | "options": { 8 | "lang": { 9 | "en": "Language", 10 | "zh-CN": "语言", 11 | "zh-TW": "語言" 12 | }, 13 | "lang-default": { 14 | "en": "Default", 15 | "zh-CN": "随扩展", 16 | "zh-TW": "未設定" 17 | }, 18 | "lang-en": { 19 | "en": "English", 20 | "zh-CN": "English", 21 | "zh-TW": "English" 22 | }, 23 | "lang-en-chs": { 24 | "en": "English–Chinese (Simplified)", 25 | "zh-CN": "英简", 26 | "zh-TW": "英简" 27 | }, 28 | "lang-en-chz": { 29 | "en": "English–Chinese (Traditional)", 30 | "zh-CN": "英繁", 31 | "zh-TW": "英繁" 32 | }, 33 | "related": { 34 | "en": "Show related results", 35 | "zh-CN": "失败时显示备选", 36 | "zh-TW": "失敗時顯示備選" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/cambridge/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/cambridge/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/cnki/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "CNKI Dict", 4 | "zh-CN": "CNKI翻译助手", 5 | "zh-TW": "CNKI翻譯助手" 6 | }, 7 | "options": { 8 | "dict": { 9 | "en": "Show dict result", 10 | "zh-CN": "显示词典结果", 11 | "zh-TW": "展示字典結果" 12 | }, 13 | "senbi": { 14 | "en": "Show bilingual sentences", 15 | "zh-CN": "显示双语例句", 16 | "zh-TW": "展示雙語例句" 17 | }, 18 | "seneng": { 19 | "en": "Show English sentences", 20 | "zh-CN": "显示英文例句", 21 | "zh-TW": "展示英文例句" 22 | }, 23 | "digests": { 24 | "en": "Show relevant digests", 25 | "zh-CN": "显示相关文摘", 26 | "zh-TW": "展示相關文摘" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/dictionaries/cnki/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type CnkiConfig = DictItem<{ 4 | dict: boolean 5 | senbi: boolean 6 | seneng: boolean 7 | }> 8 | 9 | export default (): CnkiConfig => ({ 10 | lang: '11000000', 11 | selectionLang: { 12 | english: true, 13 | chinese: true, 14 | japanese: false, 15 | korean: false, 16 | french: false, 17 | spanish: false, 18 | deutsch: false, 19 | others: false, 20 | matchAll: false 21 | }, 22 | defaultUnfold: { 23 | english: true, 24 | chinese: true, 25 | japanese: true, 26 | korean: true, 27 | french: true, 28 | spanish: true, 29 | deutsch: true, 30 | others: true, 31 | matchAll: false 32 | }, 33 | preferredHeight: 300, 34 | selectionWC: { 35 | min: 1, 36 | max: 100 37 | }, 38 | options: { 39 | dict: true, 40 | senbi: true, 41 | seneng: true 42 | // digests: true, 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /src/components/dictionaries/cnki/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/cnki/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/cobuild/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "COBUILD", 4 | "zh-CN": "柯林斯高阶", 5 | "zh-TW": "柯林斯高階" 6 | }, 7 | "options": { 8 | "cibaFirst": { 9 | "en": "Prefer bilingual dictionary", 10 | "zh-CN": "优先查双语词典", 11 | "zh-TW": "優先查雙語詞典" 12 | } 13 | }, 14 | "helps": { 15 | "cibaFirst": { 16 | "en": "Bilingual server is less stable", 17 | "zh-CN": "双语词典服务器不稳定,解释没全英丰富", 18 | "zh-TW": "雙語詞典伺服器不穩定,解釋沒全英豐富" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/cobuild/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type CobuildConfig = DictItem<{ 4 | cibaFirst: boolean 5 | }> 6 | 7 | export default (): CobuildConfig => ({ 8 | lang: '10000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: false, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 300, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | cibaFirst: (browser.i18n.getUILanguage() || 'en').startsWith('zh-') 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/cobuild/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/cobuild/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/etymonline/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Etymonline", 4 | "zh-CN": "Etymonline", 5 | "zh-TW": "Etymonline" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | }, 18 | "chart": { 19 | "en": "Show Chart", 20 | "zh-CN": "显示图表", 21 | "zh-TW": "顯示圖表" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/dictionaries/etymonline/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictEtymonline-List { 2 | p { 3 | margin: 0 0 5px 0; 4 | } 5 | 6 | blockquote { 7 | margin: 0.5em 0; 8 | padding: 0 1em; 9 | font-style: italic; 10 | border-left: 2px solid #f9690e; 11 | } 12 | 13 | .foreign { 14 | font-style: italic; 15 | } 16 | 17 | .line-break { 18 | margin-bottom: 5px; 19 | } 20 | } 21 | 22 | .dictEtymonline-Item { 23 | margin: 10px 0; 24 | } 25 | 26 | .dictEtymonline-Title { 27 | margin: 0; 28 | font-size: 1em; 29 | } 30 | 31 | .dictEtymonline-Def { 32 | margin: 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/dictionaries/etymonline/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type EtymonlineConfig = DictItem<{ 4 | resultnum: number 5 | chart: boolean 6 | }> 7 | 8 | export default (): EtymonlineConfig => ({ 9 | lang: '10000000', 10 | selectionLang: { 11 | english: true, 12 | chinese: false, 13 | japanese: false, 14 | korean: false, 15 | french: false, 16 | spanish: false, 17 | deutsch: false, 18 | others: false, 19 | matchAll: false 20 | }, 21 | defaultUnfold: { 22 | english: true, 23 | chinese: true, 24 | japanese: true, 25 | korean: true, 26 | french: true, 27 | spanish: true, 28 | deutsch: true, 29 | others: true, 30 | matchAll: false 31 | }, 32 | preferredHeight: 265, 33 | selectionWC: { 34 | min: 1, 35 | max: 5 36 | }, 37 | options: { 38 | resultnum: 4, 39 | chart: true 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/components/dictionaries/etymonline/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/etymonline/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/eudic/View.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { EudicResult } from './engine' 3 | import Speaker from '@/components/Speaker' 4 | import { ViewPorps } from '@/components/dictionaries/helpers' 5 | 6 | export const DictEudic: FC> = ({ result }) => ( 7 |
    8 | {result.map(item => ( 9 |
  • 10 |

    11 | {item.eng} 12 |

    13 |

    {item.chs}

    14 |
    15 | {item.channel &&

    {item.channel}

    } 16 |
    17 |
  • 18 | ))} 19 |
20 | ) 21 | 22 | export default DictEudic 23 | -------------------------------------------------------------------------------- /src/components/dictionaries/eudic/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Eudic", 4 | "zh-CN": "双语例句", 5 | "zh-TW": "雙語例句" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/eudic/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictEudic-Item { 2 | margin-bottom: 10px; 3 | 4 | p { 5 | margin: 0; 6 | } 7 | } 8 | 9 | .dictEudic-Channel { 10 | color: #999; 11 | 12 | &::before { 13 | content: '《'; 14 | } 15 | 16 | &::after { 17 | content: '》'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/eudic/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type EudicConfig = DictItem<{ 4 | resultnum: number 5 | }> 6 | 7 | export default (): EudicConfig => ({ 8 | lang: '11000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: true, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 240, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | resultnum: 10 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/eudic/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/eudic/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/google/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/google/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales( 4 | { 5 | en: 'Google Translation', 6 | 'zh-CN': '谷歌翻译', 7 | 'zh-TW': '谷歌翻譯' 8 | }, 9 | { 10 | cnfirst: { 11 | en: 'Use google.cn first', 12 | 'zh-CN': '优先使用 google.cn', 13 | 'zh-TW': '優先使用 google.cn' 14 | }, 15 | concurrent: { 16 | en: 'Search concurrently', 17 | 'zh-CN': '并行查询', 18 | 'zh-TW': '並行查詢' 19 | } 20 | }, 21 | { 22 | concurrent: { 23 | en: 'Search .com and .cn concurrently', 24 | 'zh-CN': '同时搜索 .com 和 .cn 取最先返回的结果', 25 | 'zh-TW': '同時搜尋 .com 和 .cn 取最先返回的結果' 26 | } 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /src/components/dictionaries/google/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/google/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type GoogleLanguage = Subunion< 9 | Language, 10 | 'zh-CN' | 'zh-TW' | 'en' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'ru' | 'nl' 11 | > 12 | 13 | export type GoogleConfig = MachineDictItem< 14 | GoogleLanguage, 15 | { 16 | concurrent: boolean 17 | } 18 | > 19 | 20 | export default (): GoogleConfig => 21 | machineConfig( 22 | ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'ru', 'nl'], 23 | {}, 24 | { 25 | concurrent: false 26 | }, 27 | {} 28 | ) 29 | -------------------------------------------------------------------------------- /src/components/dictionaries/google/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/google/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/googledict/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Google Dictionary", 4 | "zh-CN": "谷歌词典", 5 | "zh-TW": "谷歌詞典" 6 | }, 7 | "options": { 8 | "enresult": { 9 | "en": "English Result", 10 | "zh-CN": "强制显示英文结果", 11 | "zh-TW": "強制顯示英文結果" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/dictionaries/googledict/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type GoogledictConfig = DictItem<{ 4 | enresult: boolean 5 | }> 6 | 7 | export default (): GoogledictConfig => ({ 8 | lang: '11110000', 9 | selectionLang: { 10 | english: true, 11 | chinese: true, 12 | japanese: true, 13 | korean: true, 14 | french: true, 15 | spanish: true, 16 | deutsch: true, 17 | others: true, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 240, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | enresult: true 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/googledict/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/googledict/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/guoyu/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "國語辭典", 4 | "zh-CN": "国语辞典", 5 | "zh-TW": "國語辭典" 6 | }, 7 | "options": { 8 | "trans": { 9 | "en": "Show Translation", 10 | "zh-CN": "显示翻译", 11 | "zh-TW": "顯示翻譯" 12 | } 13 | }, 14 | "helps": { 15 | "trans": { 16 | "en": "Show translation of other languages.", 17 | "zh-CN": "显示其它语言的翻译。", 18 | "zh-TW": "顯示其它語言的翻譯。" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/guoyu/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictMoe-H { 2 | margin-bottom: 10px; 3 | } 4 | 5 | .dictMoe-Title { 6 | display: inline; 7 | font-size: 1.6em; 8 | font-weight: normal; 9 | margin: 0 0.2em; 10 | } 11 | 12 | .dictMoe-Defs { 13 | margin: 0 0 10px; 14 | padding-left: 1.5em; 15 | } 16 | 17 | .dictMoe-Defs_F { 18 | margin: 0; 19 | } 20 | 21 | .dictMoe-Defs_E { 22 | margin: 0; 23 | color: var(--color-font-grey); 24 | } 25 | 26 | .dictMoe-Trans { 27 | display: table; 28 | } 29 | 30 | .dictMoe-Trans_Pos { 31 | display: table-cell; 32 | width: 2em; 33 | font-weight: bold; 34 | text-align: right; 35 | } 36 | 37 | .dictMoe-Trans_Def { 38 | display: table-cell; 39 | padding: 0 12px; 40 | } 41 | 42 | .dictMoe-Link { 43 | &:link, 44 | &:visited, 45 | &:hover, 46 | &:active { 47 | color: inherit; 48 | text-decoration: none; 49 | } 50 | 51 | &:focus, 52 | &:hover { 53 | background: #16a085; 54 | outline: 3px solid #16a085; 55 | color: #fff; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/dictionaries/guoyu/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type GuoyuConfig = DictItem<{ 4 | /** show translation */ 5 | trans: boolean 6 | }> 7 | 8 | export default (): GuoyuConfig => ({ 9 | lang: '00100000', 10 | selectionLang: { 11 | english: false, 12 | chinese: true, 13 | japanese: false, 14 | korean: false, 15 | french: false, 16 | spanish: false, 17 | deutsch: false, 18 | others: false, 19 | matchAll: false 20 | }, 21 | defaultUnfold: { 22 | english: true, 23 | chinese: true, 24 | japanese: true, 25 | korean: true, 26 | french: true, 27 | spanish: true, 28 | deutsch: true, 29 | others: true, 30 | matchAll: false 31 | }, 32 | preferredHeight: 265, 33 | selectionWC: { 34 | min: 1, 35 | max: 5 36 | }, 37 | options: { 38 | trans: true 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/components/dictionaries/guoyu/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/guoyu/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/hjdict/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/hjdict/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/jikipedia/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Jikipedia", 4 | "zh-CN": "小鸡词典", 5 | "zh-TW": "小雞詞典" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/jikipedia/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type UrbanConfig = DictItem<{ 4 | resultnum: number 5 | }> 6 | 7 | export default (): UrbanConfig => ({ 8 | lang: '01000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: true, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: true, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 380, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | resultnum: 4 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/jikipedia/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/jikipedia/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/jukuu/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Jukuu", 4 | "zh-CN": "句酷", 5 | "zh-TW": "句酷" 6 | }, 7 | "options": { 8 | "lang": { 9 | "en": "Language", 10 | "zh-CN": "语言", 11 | "zh-TW": "語言" 12 | }, 13 | "lang-zheng": { 14 | "en": "Chinese-English", 15 | "zh-CN": "中英", 16 | "zh-TW": "中英" 17 | }, 18 | "lang-engjp": { 19 | "en": "English-Japanese", 20 | "zh-CN": "英日", 21 | "zh-TW": "英日" 22 | }, 23 | "lang-zhjp": { 24 | "en": "Chinese-Japanese", 25 | "zh-CN": "中日", 26 | "zh-TW": "中日" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/dictionaries/jukuu/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictJukuu-Sens { 2 | padding-left: 20px; 3 | 4 | b { 5 | color: #f9690e; 6 | } 7 | 8 | p { 9 | margin: 0; 10 | } 11 | } 12 | 13 | .dictJukuu-Sen { 14 | list-style-type: disc; 15 | margin: 0.5em 0; 16 | } 17 | 18 | .dictJukuu-Ori { 19 | color: olive; 20 | } 21 | 22 | .dictJukuu-Src { 23 | font-size: 0.9em; 24 | text-align: right; 25 | color: #777; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/dictionaries/jukuu/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type JukuuConfig = DictItem<{ 4 | lang: 'zheng' | 'engjp' | 'zhjp' 5 | }> 6 | 7 | export default (): JukuuConfig => ({ 8 | lang: '11010000', 9 | selectionLang: { 10 | english: true, 11 | chinese: true, 12 | japanese: true, 13 | korean: true, 14 | french: true, 15 | spanish: true, 16 | deutsch: true, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 300, 32 | selectionWC: { 33 | min: 1, 34 | max: 99999 35 | }, 36 | options: { 37 | lang: 'zheng' 38 | }, 39 | options_sel: { 40 | lang: ['zheng', 'engjp', 'zhjp'] 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /src/components/dictionaries/jukuu/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/jukuu/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/lexico/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Lexico", 4 | "zh-CN": "Lexico", 5 | "zh-TW": "Lexico" 6 | }, 7 | "options": { 8 | "related": { 9 | "en": "Show related results", 10 | "zh-CN": "失败时显示备选", 11 | "zh-TW": "失敗時顯示備選" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/dictionaries/lexico/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type LexicoConfig = DictItem<{ 4 | related: boolean 5 | }> 6 | 7 | export default (): LexicoConfig => ({ 8 | lang: '10000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: false, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 265, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | related: true 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/lexico/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/lexico/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/View.tsx: -------------------------------------------------------------------------------- 1 | import { DictGuoyu } from '../guoyu/View' 2 | 3 | export const DictLiangAn = DictGuoyu 4 | 5 | DictLiangAn.displayName = 'LiangAn' 6 | 7 | export default DictLiangAn 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "兩岸詞典", 4 | "zh-CN": "两岸词典", 5 | "zh-TW": "兩岸詞典" 6 | }, 7 | "options": { 8 | "trans": { 9 | "en": "Show Translation", 10 | "zh-CN": "显示翻译", 11 | "zh-TW": "顯示翻譯" 12 | } 13 | }, 14 | "helps": { 15 | "trans": { 16 | "en": "Show translation of other languages.", 17 | "zh-CN": "显示其它语言的翻译。", 18 | "zh-TW": "顯示其它語言的翻譯。" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '../guoyu/_style.shadow.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type LianganConfig = DictItem<{ 4 | trans: boolean 5 | }> 6 | 7 | export default (): LianganConfig => ({ 8 | lang: '00100000', 9 | selectionLang: { 10 | english: false, 11 | chinese: true, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 265, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | trans: false 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/engine.ts: -------------------------------------------------------------------------------- 1 | import { SearchFunction, GetSrcPageFunction, getChsToChz } from '../helpers' 2 | import { moedictSearch, GuoYuResult } from '../guoyu/engine' 3 | 4 | export const getSrcPage: GetSrcPageFunction = async text => { 5 | const chsToChz = await getChsToChz() 6 | return `https://www.moedict.tw/~${chsToChz(text)}` 7 | } 8 | 9 | export type LiangAnResult = GuoYuResult 10 | 11 | export const search: SearchFunction = ( 12 | text, 13 | config, 14 | profile, 15 | payload 16 | ) => { 17 | return moedictSearch( 18 | 'c', 19 | text, 20 | config, 21 | profile.dicts.all.liangan.options 22 | ).then(result => { 23 | if (result.result.h) { 24 | result.result.h.forEach(h => { 25 | if (h.p) { 26 | h.p = h.p.replace('
陸⃝', ' [大陆]: ') 27 | } 28 | }) 29 | } 30 | return result 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/dictionaries/liangan/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/liangan/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/longman/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/longman/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/macmillan/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Macmillan", 4 | "zh-CN": "麦克米伦", 5 | "zh-TW": "麥克米倫" 6 | }, 7 | "options": { 8 | "related": { 9 | "en": "Show related results", 10 | "zh-CN": "失败时显示备选", 11 | "zh-TW": "失敗時顯示備選" 12 | }, 13 | "locale": { 14 | "en": "Locale", 15 | "zh-CN": "方言", 16 | "zh-TW": "方言" 17 | }, 18 | "locale-uk": { 19 | "en": "UK", 20 | "zh-CN": "英式", 21 | "zh-TW": "英式" 22 | }, 23 | "locale-us": { 24 | "en": "US", 25 | "zh-CN": "美式", 26 | "zh-TW": "美式" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/dictionaries/macmillan/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type MacmillanConfig = DictItem<{ 4 | related: boolean 5 | locale: 'uk' | 'us' 6 | }> 7 | 8 | export default (): MacmillanConfig => ({ 9 | lang: '10000000', 10 | selectionLang: { 11 | english: true, 12 | chinese: false, 13 | japanese: false, 14 | korean: false, 15 | french: false, 16 | spanish: false, 17 | deutsch: false, 18 | others: false, 19 | matchAll: false 20 | }, 21 | defaultUnfold: { 22 | english: true, 23 | chinese: true, 24 | japanese: true, 25 | korean: true, 26 | french: true, 27 | spanish: true, 28 | deutsch: true, 29 | others: true, 30 | matchAll: false 31 | }, 32 | preferredHeight: 465, 33 | selectionWC: { 34 | min: 1, 35 | max: 5 36 | }, 37 | options: { 38 | related: true, 39 | locale: 'uk' 40 | }, 41 | options_sel: { 42 | locale: ['uk', 'us'] 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /src/components/dictionaries/macmillan/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/macmillan/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/merriamwebster/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Merriam-Webster's Dictionary", 4 | "zh-CN": "韦氏词典", 5 | "zh-TW": "韋氏字典" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/merriamwebster/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type MerriamWebsterConfig = DictItem<{ 4 | resultnum: number 5 | }> 6 | 7 | export default (): MerriamWebsterConfig => ({ 8 | lang: '10000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: false, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 180, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | resultnum: 4 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/merriamwebster/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/merriamwebster/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/mojidict/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "MOJi辞書", 4 | "zh-CN": "MOJi辞書", 5 | "zh-TW": "MOJi辞書" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/mojidict/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictMojidict-Word_Title { 2 | margin-bottom: 0; 3 | } 4 | 5 | .dictMojidict-Word_Trans { 6 | margin-top: 0; 7 | padding-left: 0.5em; 8 | font-size: 0.9em; 9 | color: #999; 10 | } 11 | 12 | .dictMojidict-List { 13 | padding-left: 1em; 14 | } 15 | 16 | .dictMojidict-Sublist { 17 | padding-left: 0.5em; 18 | } 19 | 20 | .dictMojidict-ListItem_Disc { 21 | list-style-type: disc; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/dictionaries/mojidict/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type LianganConfig = DictItem 4 | 5 | export default (): LianganConfig => ({ 6 | lang: '10010000', 7 | selectionLang: { 8 | english: false, 9 | chinese: true, 10 | japanese: true, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 265, 30 | selectionWC: { 31 | min: 1, 32 | max: 5 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/mojidict/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/mojidict/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/naver/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Naver韩国语词典", 4 | "zh-CN": "Naver韩国语词典", 5 | "zh-TW": "Naver韩国语词典" 6 | }, 7 | "options": { 8 | "hanAsJa": { 9 | "en": "Search Chinese with Japanese Dictionary", 10 | "zh-CN": "用日语词典查中文", 11 | "zh-TW": "用日語詞典查漢字" 12 | }, 13 | "korAsJa": { 14 | "en": "Search Korean with Japanese Dictionary", 15 | "zh-CN": "用日语词典查韩语", 16 | "zh-TW": "用日语词典查韓語" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/naver/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type NaverConfig = DictItem<{ 4 | hanAsJa: boolean 5 | korAsJa: boolean 6 | }> 7 | 8 | export default (): NaverConfig => ({ 9 | lang: '01011000', 10 | selectionLang: { 11 | english: false, 12 | chinese: true, 13 | japanese: true, 14 | korean: true, 15 | french: false, 16 | spanish: false, 17 | deutsch: false, 18 | others: false, 19 | matchAll: false 20 | }, 21 | defaultUnfold: { 22 | english: true, 23 | chinese: true, 24 | japanese: true, 25 | korean: true, 26 | french: true, 27 | spanish: true, 28 | deutsch: true, 29 | others: true, 30 | matchAll: false 31 | }, 32 | preferredHeight: 465, 33 | selectionWC: { 34 | min: 1, 35 | max: 10 36 | }, 37 | options: { 38 | hanAsJa: false, 39 | korAsJa: false 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/components/dictionaries/naver/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/naver/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/oaldict/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Oxford Learner's Dict", 4 | "zh-CN": "牛津高阶词典", 5 | "zh-TW": "牛津高階詞典" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/oaldict/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type OalDictConfig = DictItem 4 | 5 | export default (): OalDictConfig => ({ 6 | lang: '10000000', 7 | selectionLang: { 8 | english: true, 9 | chinese: false, 10 | japanese: false, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 240, 30 | selectionWC: { 31 | min: 1, 32 | max: 5 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/oaldict/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/oaldict/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/renren/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "91dict", 4 | "zh-CN": "人人词典", 5 | "zh-TW": "人人詞典" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/renren/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type RenrenConfig = DictItem 4 | 5 | export default (): RenrenConfig => ({ 6 | lang: '11000000', 7 | selectionLang: { 8 | english: true, 9 | chinese: true, 10 | japanese: false, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 400, 30 | selectionWC: { 31 | min: 1, 32 | max: 999 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/renren/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/renren/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/shanbay/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Shanbay Dictionary", 4 | "zh-CN": "扇贝词典", 5 | "zh-TW": "扇貝詞典" 6 | }, 7 | "options": { 8 | "basic": { 9 | "en": "Show basic meaning", 10 | "zh-CN": "显示简单释义", 11 | "zh-TW": "顯示簡單解釋" 12 | }, 13 | "sentence": { 14 | "en": "Show sentences", 15 | "zh-CN": "显示权威例句", 16 | "zh-TW": "顯示權威例句" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/shanbay/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictShanbay-HeaderContainer { 2 | display: flex; 3 | align-items: center; 4 | flex-wrap: wrap; 5 | } 6 | 7 | .dictShanbay-Title { 8 | font-size: 1.5em; 9 | margin-right: 8px; 10 | } 11 | 12 | .dictShanbay-Pattern { 13 | margin-top: 0.5em; 14 | } 15 | 16 | .dictShanbay-SecTitle { 17 | font-size: 1.1em; 18 | margin-top: 1em; 19 | border-bottom: 1px solid rgba(199, 110, 6, 0.5); 20 | } 21 | 22 | .dictShanbay-Basic { 23 | margin: 0.5em 0; 24 | 25 | .definition-pos { 26 | padding-right: 0.5em; 27 | } 28 | } 29 | 30 | .dictShanbay-Sentence { 31 | margin: 0; 32 | padding: 0 0 0 1.5em; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/dictionaries/shanbay/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type ShanbayConfig = DictItem<{ 4 | basic: boolean 5 | sentence: boolean 6 | }> 7 | 8 | export default (): ShanbayConfig => ({ 9 | lang: '10000000', 10 | selectionLang: { 11 | english: true, 12 | chinese: false, 13 | japanese: false, 14 | korean: false, 15 | french: false, 16 | spanish: false, 17 | deutsch: false, 18 | others: false, 19 | matchAll: false 20 | }, 21 | defaultUnfold: { 22 | english: true, 23 | chinese: true, 24 | japanese: true, 25 | korean: true, 26 | french: true, 27 | spanish: true, 28 | deutsch: true, 29 | others: true, 30 | matchAll: false 31 | }, 32 | preferredHeight: 150, 33 | selectionWC: { 34 | min: 1, 35 | max: 30 36 | }, 37 | options: { 38 | basic: true, 39 | sentence: true 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/components/dictionaries/shanbay/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/shanbay/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales({ 4 | en: 'Sogou Translation', 5 | 'zh-CN': '搜狗翻译', 6 | 'zh-TW': '搜狗翻譯' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/auth.ts: -------------------------------------------------------------------------------- 1 | export const auth = { 2 | pid: '', 3 | key: '' 4 | } 5 | 6 | export const url = 'https://deepi.sogou.com/?from=translatepc' 7 | -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type SogouLanguage = Subunion< 9 | Language, 10 | 'zh-CN' | 'zh-TW' | 'en' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'ru' 11 | > 12 | 13 | export type SogouConfig = MachineDictItem 14 | 15 | export default (): SogouConfig => 16 | machineConfig( 17 | ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'ru'], 18 | {}, 19 | {}, 20 | {} 21 | ) 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/sogou/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/sogou/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales({ 4 | en: 'Tencent Translate', 5 | 'zh-CN': '腾讯翻译君', 6 | 'zh-TW': '騰訊翻譯君' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/auth.ts: -------------------------------------------------------------------------------- 1 | export const auth = { 2 | secretId: '', 3 | secretKey: '' 4 | } 5 | 6 | export const url = 'https://curl.qcloud.com/imsowZzT' 7 | -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type TencentLanguage = Subunion< 9 | Language, 10 | 'zh-CN' | 'en' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'ru' 11 | > 12 | 13 | export type TencentConfig = MachineDictItem 14 | 15 | export default (): TencentConfig => 16 | machineConfig( 17 | ['zh-CN', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'ru'], 18 | { 19 | lang: '11011111' 20 | }, 21 | {}, 22 | {} 23 | ) 24 | -------------------------------------------------------------------------------- /src/components/dictionaries/tencent/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/tencent/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/urban/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Urban", 4 | "zh-CN": "Urban", 5 | "zh-TW": "Urban" 6 | }, 7 | "options": { 8 | "resultnum": { 9 | "en": "Show", 10 | "zh-CN": "结果数量", 11 | "zh-TW": "結果數量" 12 | }, 13 | "resultnum_unit": { 14 | "en": "results", 15 | "zh-CN": "个", 16 | "zh-TW": "個" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/dictionaries/urban/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type UrbanConfig = DictItem<{ 4 | resultnum: number 5 | }> 6 | 7 | export default (): UrbanConfig => ({ 8 | lang: '10000000', 9 | selectionLang: { 10 | english: true, 11 | chinese: false, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 180, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | resultnum: 4 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/urban/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/urban/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/vocabulary/View.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { VocabularyResult } from './engine' 3 | import { ViewPorps } from '@/components/dictionaries/helpers' 4 | 5 | export const DictVocabulary: FC> = ({ result }) => ( 6 | <> 7 |

{result.short}

8 |

{result.long}

9 | 10 | ) 11 | 12 | export default DictVocabulary 13 | -------------------------------------------------------------------------------- /src/components/dictionaries/vocabulary/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Vocabulary.com", 4 | "zh-CN": "Vocabulary.com", 5 | "zh-TW": "Vocabulary.com" 6 | } 7 | } -------------------------------------------------------------------------------- /src/components/dictionaries/vocabulary/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | .dictVocabulary-Long { 2 | padding-left: 5px; 3 | border-left: 1px solid #666; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/dictionaries/vocabulary/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type VocabularyConfig = DictItem 4 | 5 | export default (): VocabularyConfig => ({ 6 | lang: '10000000', 7 | selectionLang: { 8 | english: true, 9 | chinese: false, 10 | japanese: false, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 180, 30 | selectionWC: { 31 | min: 1, 32 | max: 5 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/vocabulary/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/vocabulary/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/weblio/View.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { WeblioResult } from './engine' 3 | import { ViewPorps } from '@/components/dictionaries/helpers' 4 | import EntryBox from '@/components/EntryBox' 5 | import { StrElm } from '@/components/StrElm' 6 | 7 | export const DictWeblio: FC> = ({ result }) => ( 8 |
9 | {result.map(({ title, def }) => ( 10 | } 14 | > 15 | 16 | 17 | ))} 18 |
19 | ) 20 | 21 | export default DictWeblio 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblio/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Weblio", 4 | "zh-CN": "Weblio 辞書", 5 | "zh-TW": "Weblio 辞書" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblio/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type WeblioConfig = DictItem 4 | 5 | export default (): WeblioConfig => ({ 6 | lang: '00010000', 7 | selectionLang: { 8 | english: true, 9 | chinese: true, 10 | japanese: true, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 265, 30 | selectionWC: { 31 | min: 1, 32 | max: 20 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblio/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/weblio/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/weblioejje/View.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { WeblioejjeResult } from './engine' 3 | import EntryBox from '@/components/EntryBox' 4 | import { ViewPorps } from '@/components/dictionaries/helpers' 5 | import { StrElm } from '@/components/StrElm' 6 | 7 | export const DictWeblioejje: FC> = ({ result }) => ( 8 |
9 | {result.map((entry, i) => 10 | entry.title ? ( 11 | 12 | 13 | 14 | ) : ( 15 | 16 | ) 17 | )} 18 |
19 | ) 20 | 21 | export default DictWeblioejje 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblioejje/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Weblio ejje", 4 | "zh-CN": "Weblio 英和和英", 5 | "zh-TW": "Weblio 英和和英" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblioejje/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type VocabularyConfig = DictItem 4 | 5 | export default (): VocabularyConfig => ({ 6 | lang: '10010000', 7 | selectionLang: { 8 | english: true, 9 | chinese: false, 10 | japanese: true, 11 | korean: false, 12 | french: false, 13 | spanish: false, 14 | deutsch: false, 15 | others: false, 16 | matchAll: false 17 | }, 18 | defaultUnfold: { 19 | english: true, 20 | chinese: true, 21 | japanese: true, 22 | korean: true, 23 | french: true, 24 | spanish: true, 25 | deutsch: true, 26 | others: true, 27 | matchAll: false 28 | }, 29 | preferredHeight: 400, 30 | selectionWC: { 31 | min: 1, 32 | max: 999 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/weblioejje/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/weblioejje/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/websterlearner/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Merriam-Webster's Learner's Dictionary", 4 | "zh-CN": "韦氏学习词典", 5 | "zh-TW": "韋氏學習字典" 6 | }, 7 | "options": { 8 | "defs": { 9 | "en": "Show definitions", 10 | "zh-CN": "显示释义", 11 | "zh-TW": "顯示解釋" 12 | }, 13 | "phrase": { 14 | "en": "Show phrases", 15 | "zh-CN": "显示词组", 16 | "zh-TW": "顯示片語" 17 | }, 18 | "derived": { 19 | "en": "Show derived words", 20 | "zh-CN": "显示派生词", 21 | "zh-TW": "顯示衍生字" 22 | }, 23 | "arts": { 24 | "en": "Show pictures", 25 | "zh-CN": "显示图片释义", 26 | "zh-TW": "顯示圖片解釋" 27 | }, 28 | "related": { 29 | "en": "Show related results", 30 | "zh-CN": "失败时显示备选", 31 | "zh-TW": "失敗時顯示備選" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/dictionaries/websterlearner/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/websterlearner/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/wikipedia/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/wikipedia/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/youdao/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "Youdao Dictionary", 4 | "zh-CN": "有道词典", 5 | "zh-TW": "有道詞典" 6 | }, 7 | "options": { 8 | "basic": { 9 | "en": "Show basic meaning", 10 | "zh-CN": "显示简单释义", 11 | "zh-TW": "顯示簡單解釋" 12 | }, 13 | "collins": { 14 | "en": "Show Collins result", 15 | "zh-CN": "显示柯林斯双解", 16 | "zh-TW": "顯示柯林斯雙解" 17 | }, 18 | "discrimination": { 19 | "en": "Show discrimination", 20 | "zh-CN": "显示词语辨析", 21 | "zh-TW": "顯示詞語辨析" 22 | }, 23 | "sentence": { 24 | "en": "Show sentences", 25 | "zh-CN": "显示权威例句", 26 | "zh-TW": "顯示權威例句" 27 | }, 28 | "translation": { 29 | "en": "Show translation", 30 | "zh-CN": "显示有道翻译", 31 | "zh-TW": "顯示有道翻譯" 32 | }, 33 | "related": { 34 | "en": "Show related results", 35 | "zh-CN": "失败时显示备选", 36 | "zh-TW": "失敗時顯示備選" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdao/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/youdao/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/View.tsx: -------------------------------------------------------------------------------- 1 | export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans' 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/_locales.ts: -------------------------------------------------------------------------------- 1 | import { getMachineLocales } from '../locales' 2 | 3 | export const locales = getMachineLocales({ 4 | en: 'Youdao Translate', 5 | 'zh-CN': '有道翻译', 6 | 'zh-TW': '有道翻譯' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/_style.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/MachineTrans/MachineTrans.scss'; 2 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/auth.ts: -------------------------------------------------------------------------------- 1 | export const auth = { 2 | appKey: '', 3 | key: '' 4 | } 5 | 6 | export const url = 'http://ai.youdao.com/gw.s' 7 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MachineDictItem, 3 | machineConfig 4 | } from '@/components/MachineTrans/engine' 5 | import { Language } from '@opentranslate/translator' 6 | import { Subunion } from '@/typings/helpers' 7 | 8 | export type YoudaotransLanguage = Subunion< 9 | Language, 10 | 'zh-CN' | 'en' | 'pt' | 'es' | 'ja' | 'ko' | 'fr' | 'ru' 11 | > 12 | 13 | export type YoudaotransConfig = MachineDictItem 14 | 15 | export default (): YoudaotransConfig => 16 | machineConfig( 17 | ['zh-CN', 'en', 'pt', 'es', 'ja', 'ko', 'fr', 'ru'], 18 | { 19 | lang: '11011111' 20 | }, 21 | {}, 22 | {} 23 | ) 24 | -------------------------------------------------------------------------------- /src/components/dictionaries/youdaotrans/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/youdaotrans/favicon.png -------------------------------------------------------------------------------- /src/components/dictionaries/zdic/View.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { ZdicResult } from './engine' 3 | import { ViewPorps } from '@/components/dictionaries/helpers' 4 | import EntryBox from '@/components/EntryBox' 5 | import { StrElm } from '@/components/StrElm' 6 | 7 | export const DictZdic: FC> = ({ result }) => ( 8 |
9 | {result.map(entry => ( 10 | 11 | 12 | 13 | ))} 14 |
15 | ) 16 | 17 | export default DictZdic 18 | -------------------------------------------------------------------------------- /src/components/dictionaries/zdic/_locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "en": "汉典", 4 | "zh-CN": "汉典", 5 | "zh-TW": "漢典" 6 | }, 7 | "options": { 8 | "audio": { 9 | "en": "Enable audio", 10 | "zh-CN": "开启发音", 11 | "zh-TW": "啟用發音" 12 | } 13 | }, 14 | "helps": { 15 | "audio": { 16 | "en": "Referer modification is required, which may slightly impact performance.", 17 | "zh-CN": "突破外链限制需要改写 Referer,可能会轻微影响性能。", 18 | "zh-TW": "突破外鏈限制需要改寫 Referer,可能會輕微影響效能。" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/dictionaries/zdic/config.ts: -------------------------------------------------------------------------------- 1 | import { DictItem } from '@/app-config/dicts' 2 | 3 | export type ZdicConfig = DictItem<{ 4 | audio: boolean 5 | }> 6 | 7 | export default (): ZdicConfig => ({ 8 | lang: '01000000', 9 | selectionLang: { 10 | english: false, 11 | chinese: true, 12 | japanese: false, 13 | korean: false, 14 | french: false, 15 | spanish: false, 16 | deutsch: false, 17 | others: false, 18 | matchAll: false 19 | }, 20 | defaultUnfold: { 21 | english: true, 22 | chinese: true, 23 | japanese: true, 24 | korean: true, 25 | french: true, 26 | spanish: true, 27 | deutsch: true, 28 | others: true, 29 | matchAll: false 30 | }, 31 | preferredHeight: 400, 32 | selectionWC: { 33 | min: 1, 34 | max: 5 35 | }, 36 | options: { 37 | audio: false 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/dictionaries/zdic/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crimx/ext-saladict/ffb478cd2e3277a40edeb9470ad1c6614b010028/src/components/dictionaries/zdic/favicon.png -------------------------------------------------------------------------------- /src/content/__fake__/env-instant-capture.ts: -------------------------------------------------------------------------------- 1 | import { createIntantCaptureStream } from '@/selection/instant-capture' 2 | import getDefaultConfig, { AppConfigMutable } from '@/app-config' 3 | 4 | const config = getDefaultConfig() as AppConfigMutable 5 | config.mode.instant.enable = true 6 | config.mode.instant.key = 'ctrl' 7 | 8 | createIntantCaptureStream(config).subscribe(console.log) 9 | -------------------------------------------------------------------------------- /src/content/__fake__/env-select-text.ts: -------------------------------------------------------------------------------- 1 | import { createSelectTextStream } from '@/selection/select-text' 2 | import getDefaultConfig from '@/app-config' 3 | 4 | const config = getDefaultConfig() 5 | 6 | createSelectTextStream(config).subscribe(console.log) 7 | -------------------------------------------------------------------------------- /src/content/__fake__/env.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker' 2 | import '@/selection' 3 | import { initConfig, updateConfig } from '@/_helpers/config-manager' 4 | import { initProfiles, updateProfile } from '@/_helpers/profile-manager' 5 | import { ProfileMutable } from '@/app-config/profiles' 6 | import { AppConfigMutable } from '@/app-config' 7 | 8 | browser.runtime.sendMessage['_sender'].callsFake(() => ({ 9 | tab: { 10 | id: 'saladict-page' 11 | } 12 | })) 13 | 14 | initConfig().then(_config => { 15 | const config = _config as AppConfigMutable 16 | config.mode.instant.enable = true 17 | config.panelMode.direct = true 18 | updateConfig(config) 19 | }) 20 | initProfiles().then(profile => { 21 | ;(profile as ProfileMutable).dicts.selected = ['bing'] 22 | updateProfile(profile) 23 | }) 24 | 25 | for (let i = 0; i < 10; i++) { 26 | const $p = document.createElement('p') 27 | $p.innerHTML = 'love ' + faker.lorem.paragraph() 28 | document.body.appendChild($p) 29 | } 30 | -------------------------------------------------------------------------------- /src/content/_style.scss: -------------------------------------------------------------------------------- 1 | .saladict-div { 2 | @extend %reset-important; 3 | } 4 | -------------------------------------------------------------------------------- /src/content/components/DictList/DictList.scss: -------------------------------------------------------------------------------- 1 | @import '../DictItem/DictItem.scss'; 2 | 3 | .dictList > .dictItem:first-child > .dictItemHead { 4 | border-top-color: transparent; 5 | } 6 | -------------------------------------------------------------------------------- /src/content/components/DictPanel/DictPanel.scss: -------------------------------------------------------------------------------- 1 | @import '@/_sass_shared/_theme.scss'; 2 | 3 | .dictPanel-FloatBox-Container { 4 | position: relative; 5 | } 6 | 7 | .dictPanel-Root { 8 | display: flex; 9 | flex-direction: column; 10 | box-sizing: border-box; 11 | position: fixed; 12 | z-index: $global-zindex-dictpanel; 13 | top: 0; 14 | left: 0; 15 | overflow: hidden; 16 | text-align: initial; 17 | border-radius: 6px; 18 | background-color: inherit; 19 | box-shadow: rgba(0, 0, 0, 0.8) 0px 4px 23px -6px; 20 | } 21 | 22 | .dictPanel-Head { 23 | flex-shrink: 0; 24 | } 25 | 26 | .dictPanel-Body { 27 | flex: 1; 28 | overflow-x: hidden; 29 | overflow-y: scroll; 30 | // font-size: 0; // https://bugzilla.mozilla.org/show_bug.cgi?id=1573030 31 | -webkit-overflow-scrolling: touch; 32 | } 33 | 34 | @import '@/_sass_shared/_fancy-scrollbar.scss'; 35 | 36 | @import '../MenuBar/MenuBar.scss'; 37 | @import '../MtaBox/MtaBox.scss'; 38 | @import '../DictList/DictList.scss'; 39 | @import '../WaveformBox/WaveformBox.scss'; 40 | -------------------------------------------------------------------------------- /src/content/components/DictPanel/DictPanel.shadow.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/ShadowPortal/ShadowPortal.scss'; 2 | @import './DictPanel.scss'; 3 | 4 | .dictPanel-DragMask { 5 | position: fixed; 6 | z-index: $global-zindex-dictpanel; 7 | top: 0; 8 | left: 0; 9 | bottom: 0; 10 | right: 0; 11 | margin: auto; 12 | background: rgba(225, 225, 225, 0.01); 13 | cursor: grabbing; 14 | cursor: -moz-grabbing; 15 | cursor: -webkit-grabbing; 16 | } 17 | 18 | .dictPanel-Root { 19 | @include isAnimate { 20 | transition: width 0.4s, height 0.4s, opacity 0.4s, 21 | top 0.4s cubic-bezier(0.55, 0.82, 0.63, 0.95), 22 | left 0.4s cubic-bezier(0.4, 0.9, 0.71, 1.02); 23 | } 24 | 25 | &.isDragging { 26 | @include isAnimate { 27 | transition: width 0.4s, height 0.4s, opacity 0.4s; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/content/components/DictPanel/DictPanelStandalone.scss: -------------------------------------------------------------------------------- 1 | @import './DictPanel.scss'; 2 | 3 | .dictPanel-FloatBox-Container { 4 | position: absolute; 5 | top: 0; 6 | left: 0; 7 | } 8 | 9 | .dictPanel-Root { 10 | position: relative !important; 11 | top: 0 !important; 12 | left: 0 !important; 13 | width: 450px; 14 | height: 500px; 15 | --panel-width: 450px; 16 | --panel-max-height: 500px; 17 | border-radius: 0; 18 | box-shadow: rgba(0, 0, 0, 0.8) 0px 5px 20px -12px; 19 | 20 | @include isAnimate { 21 | transition: height 0.4s; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/content/components/MenuBar/MenuBar.scss: -------------------------------------------------------------------------------- 1 | @import './MenubarBtns.scss'; 2 | @import './SearchBox.scss'; 3 | @import './Profiles.scss'; 4 | 5 | .menuBar { 6 | display: flex; 7 | align-items: center; 8 | position: relative; 9 | height: 30px; 10 | padding: 0 3px; 11 | font-size: 14px; 12 | background-color: var(--color-brand); 13 | } 14 | 15 | .menuBar-DragArea { 16 | flex: 3; 17 | align-self: stretch; 18 | user-select: none; 19 | // prevent scrolling 20 | touch-action: none; 21 | cursor: move; 22 | } 23 | -------------------------------------------------------------------------------- /src/content/components/MenuBar/Profiles.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/HoverBox/HoverBox.scss'; 2 | @import './MenubarBtns.scss'; 3 | 4 | .menuBar-ProfileItem { 5 | position: relative; 6 | padding-left: 10px; 7 | color: var(--color-font); 8 | 9 | &.isActive::before { 10 | content: ''; 11 | position: absolute; 12 | top: 50%; 13 | transform: translateY(-55%); 14 | left: -5px; 15 | width: 0; 16 | height: 0; 17 | border-left: 10px solid currentColor; 18 | border-top: 5px solid transparent; 19 | border-bottom: 5px solid transparent; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/content/components/MenuBar/SearchBox.scss: -------------------------------------------------------------------------------- 1 | @import './Suggest.scss'; 2 | @import './MenubarBtns.scss'; 3 | 4 | .menuBar-SearchBox_Wrap { 5 | flex: 2; 6 | position: relative; 7 | 8 | &.isExpand { 9 | flex: 7; 10 | } 11 | 12 | @include isAnimate { 13 | transition: flex 0.6s; 14 | } 15 | } 16 | 17 | .menuBar-SearchBox { 18 | width: 100%; 19 | box-sizing: border-box; 20 | padding: 0 5px; 21 | border: 0 none; 22 | outline: 0 none; 23 | color: #fff; 24 | background-color: rgba(225, 225, 225, 0.1); 25 | } 26 | 27 | .menuBar-SearchBox_Suggests { 28 | position: absolute; 29 | left: 0; 30 | top: 30px; 31 | z-index: 1000; 32 | } 33 | 34 | .csst-menuBar-SearchBox_Suggests { 35 | @include isAnimate(-enter) { 36 | opacity: 0; 37 | transition: opacity 0.4s; 38 | } 39 | 40 | @include isAnimate(-enter-active, -exit) { 41 | opacity: 1; 42 | transition: opacity 0.4s; 43 | } 44 | 45 | @include isAnimate(-exit-active) { 46 | opacity: 0; 47 | transition: opacity 0.4s; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/content/components/MenuBar/Suggest.scss: -------------------------------------------------------------------------------- 1 | @import '@/components/FloatBox/FloatBox.scss'; 2 | 3 | .menuBar-SuggestsEntry { 4 | margin-right: 1.5em; 5 | color: #f9690e; 6 | } 7 | 8 | .menuBar-SuggestsExplain { 9 | color: var(--color-font); 10 | } 11 | -------------------------------------------------------------------------------- /src/content/components/WordEditor/WordEditor.scss: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------*\ 2 | Variables 3 | \*-----------------------------------------------*/ 4 | @import '@/_sass_shared/_theme.scss'; 5 | 6 | /*-----------------------------------------------*\ 7 | Libs 8 | \*-----------------------------------------------*/ 9 | @import '~normalize-scss'; 10 | 11 | /*-----------------------------------------------*\ 12 | Components 13 | \*-----------------------------------------------*/ 14 | @import './Notes.scss'; 15 | -------------------------------------------------------------------------------- /src/content/components/WordEditor/WordEditor.shadow.scss: -------------------------------------------------------------------------------- 1 | @import './WordEditor.scss'; 2 | @import '@/components/ShadowPortal/ShadowPortal.scss'; 3 | -------------------------------------------------------------------------------- /src/content/components/WordEditor/WordEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { Notes, NotesProps } from './Notes' 3 | import { SALADICT_EXTERNAL } from '@/_helpers/saladict' 4 | 5 | export interface WordEditorProps extends NotesProps {} 6 | 7 | export const WordEditor: FC = props => { 8 | return ( 9 |
10 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/content/components/WordEditor/WordEditorStandalone.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { MapStateToProps } from 'react-retux' 3 | import { StoreState } from '@/content/redux/modules' 4 | import { WordEditor, WordEditorProps } from './WordEditor' 5 | 6 | const onClose = () => { 7 | window.close() 8 | } 9 | 10 | const mapStateToProps: MapStateToProps< 11 | StoreState, 12 | WordEditorProps 13 | > = state => ({ 14 | darkMode: state.config.darkMode, 15 | containerWidth: window.innerWidth, 16 | ctxTrans: state.config.ctxTrans, 17 | wordEditor: state.wordEditor, 18 | onClose 19 | }) 20 | 21 | export const WordEditorStandaloneContainer = connect(mapStateToProps)( 22 | WordEditor 23 | ) 24 | 25 | export default WordEditorStandaloneContainer 26 | -------------------------------------------------------------------------------- /src/content/redux/epics/utils.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs' 2 | import { Epic as RawEpic, ofType as rawOfType } from 'redux-observable' 3 | import { StoreAction, StoreActionType, StoreState } from '../modules' 4 | 5 | /** Tailored `Epic` for the store. */ 6 | export type Epic< 7 | TOutType extends StoreActionType = StoreActionType, 8 | TDeps = any 9 | > = RawEpic, StoreState, TDeps> 10 | 11 | /** 12 | * Tailored `ofType` for the store. 13 | * Now you can use `ofType` directly without the need to 14 | * manually offer types each time. 15 | */ 16 | export const ofType = rawOfType as < 17 | TInAction extends StoreAction, 18 | TTypes extends StoreActionType[] = StoreActionType[], 19 | TOutAction extends StoreAction = StoreAction 20 | >( 21 | ...types: TTypes 22 | ) => (source: Observable) => Observable 23 | -------------------------------------------------------------------------------- /src/history/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | -------------------------------------------------------------------------------- /src/history/index.tsx: -------------------------------------------------------------------------------- 1 | import './env' 2 | import '@/selection' 3 | 4 | import React from 'react' 5 | import { WordPage } from '@/components/WordPage' 6 | import { initAntdRoot } from '@/components/AntdRoot' 7 | 8 | document.title = 'Saladict History' 9 | 10 | initAntdRoot(() => , '/wordpage/history') 11 | -------------------------------------------------------------------------------- /src/manifest/chrome.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "persistent": true 4 | }, 5 | "options_ui": { 6 | "chrome_style": false 7 | }, 8 | "optional_permissions": [ 9 | "background" 10 | ], 11 | "content_security_policy": "script-src 'self' chrome-extension://hfjbmagddngcpeloejdejnfgbamkjaeg/ chrome-extension://aibcglbfblnogfjhbcmmpobjhnomhcdo/; object-src 'self'", 12 | "incognito": "split", 13 | "update_url": "https://clients2.google.com/service/update2/crx", 14 | "minimum_chrome_version": "63" 15 | } 16 | -------------------------------------------------------------------------------- /src/manifest/edge.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "persistent": true 4 | }, 5 | "options_ui": { 6 | "chrome_style": false 7 | }, 8 | "optional_permissions": [ 9 | "background" 10 | ], 11 | "content_security_policy": "script-src 'self' chrome-extension://hfjbmagddngcpeloejdejnfgbamkjaeg/ chrome-extension://aibcglbfblnogfjhbcmmpobjhnomhcdo/; object-src 'self'", 12 | "incognito": "split", 13 | "update_url": "https://edge.microsoft.com/extensionwebstorebase/v1/crx" 14 | } 15 | -------------------------------------------------------------------------------- /src/manifest/firefox.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "options_ui": { 3 | "browser_style": false 4 | }, 5 | "applications": { 6 | "gecko": { 7 | "id": "saladict@crimx.com", 8 | "strict_min_version": "67.0" 9 | } 10 | }, 11 | "content_security_policy": "script-src 'self'; object-src 'self'" 12 | } 13 | -------------------------------------------------------------------------------- /src/manifest/safari.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "persistent": true 4 | }, 5 | "options_ui": { 6 | "chrome_style": false 7 | }, 8 | "optional_permissions": [ 9 | "background" 10 | ], 11 | "incognito": "split" 12 | } 13 | -------------------------------------------------------------------------------- /src/notebook/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | -------------------------------------------------------------------------------- /src/notebook/index.tsx: -------------------------------------------------------------------------------- 1 | import './env' 2 | import '@/selection' 3 | 4 | import React from 'react' 5 | import { WordPage } from '@/components/WordPage' 6 | import { initAntdRoot } from '@/components/AntdRoot' 7 | 8 | document.title = 'Saladict Notebook' 9 | 10 | initAntdRoot(() => , '/wordpage/notebook') 11 | -------------------------------------------------------------------------------- /src/options/__fake__/env.ts: -------------------------------------------------------------------------------- 1 | import { initConfig } from '@/_helpers/config-manager' 2 | import { initProfiles } from '@/_helpers/profile-manager' 3 | import { browser } from '../../../test/helper' 4 | import packagejson from '../../../package.json' 5 | 6 | browser.runtime.getManifest.callsFake(() => ({ 7 | version: packagejson.version 8 | })) 9 | 10 | browser.permissions.contains.callsFake(() => Promise.resolve(true)) 11 | browser.permissions.request.callsFake(() => Promise.resolve(false)) 12 | 13 | initConfig() 14 | initProfiles() 15 | -------------------------------------------------------------------------------- /src/options/components/BtnPreview/_style.scss: -------------------------------------------------------------------------------- 1 | .btn-preview { 2 | position: fixed !important; 3 | right: 30px; 4 | bottom: 60px; 5 | border: none; 6 | color: #000 !important; 7 | background: #fff !important; 8 | box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 9 | 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); 10 | } 11 | 12 | .btn-preview-fade-enter { 13 | opacity: 0; 14 | transition: opacity 0.5s; 15 | } 16 | 17 | .btn-preview-fade-enter-active, 18 | .btn-preview-fade-exit { 19 | opacity: 1; 20 | transition: opacity 0.5s; 21 | } 22 | 23 | .btn-preview-fade-exit-active { 24 | opacity: 0; 25 | transition: opacity 0.5s; 26 | } 27 | -------------------------------------------------------------------------------- /src/options/components/Entries/Dictionaries/DictTitle/_style.scss: -------------------------------------------------------------------------------- 1 | .saladict-dict-title { 2 | word-break: break-all; 3 | 4 | & > * { 5 | word-break: keep-all; 6 | white-space: nowrap; 7 | } 8 | } 9 | 10 | .saladict-dict-title-icon { 11 | width: 1.3em; 12 | height: 1.3em; 13 | margin-right: 5px; 14 | vertical-align: text-bottom; 15 | } 16 | 17 | .saladict-dict-title-link { 18 | color: currentColor; 19 | } 20 | 21 | .saladict-dict-langs-char { 22 | margin-left: 5px; 23 | padding: 0 2px; 24 | font-size: 0.92em; 25 | color: #777; 26 | border: 1px solid #777; 27 | border-radius: 2px; 28 | } 29 | -------------------------------------------------------------------------------- /src/options/components/EntryError.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect } from 'react' 2 | import { FrownOutlined } from '@ant-design/icons' 3 | import { message } from '@/_helpers/browser-api' 4 | 5 | export const EntryError: FC = () => { 6 | useEffect(() => { 7 | message.self.send({ type: 'CLOSE_PANEL' }) 8 | }, []) 9 | 10 | return ( 11 |
20 | 23 |

Entry Not Found

24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/options/components/EntrySideBar/_style.scss: -------------------------------------------------------------------------------- 1 | @import '@/_sass_shared/_fancy-scrollbar.scss'; 2 | 3 | .entry-sidebar { 4 | height: calc(100vh - 64px); 5 | background: transparent; 6 | 7 | &.isAffixed { 8 | height: 100vh; 9 | } 10 | 11 | @media (hover: hover) { 12 | overflow-y: hidden; 13 | 14 | &:hover { 15 | overflow-y: auto; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/options/components/Header/HeadInfo/AckList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { List } from 'antd' 3 | import { useTranslate } from '@/_helpers/i18n' 4 | import { acknowledgement } from '@/options/acknowledgement' 5 | 6 | export const AckList = React.memo(() => { 7 | const { t } = useTranslate('options') 8 | return ( 9 | ( 11 |
12 | 13 | {ack.name} 14 | {' '} 15 | {t(`headInfo.acknowledgement.${ack.locale}`)} 16 |
17 | ))} 18 | renderItem={item => {item}} 19 | /> 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /src/options/components/Header/_style.scss: -------------------------------------------------------------------------------- 1 | .options-header { 2 | max-width: 1440px; 3 | height: 100%; 4 | margin: 0 auto; 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | line-height: 1.2; 9 | 10 | @media screen and (max-width: 800px) { 11 | margin: 0 -20px !important; 12 | } 13 | 14 | @media screen and (max-width: 600px) { 15 | margin: 0 -40px !important; 16 | } 17 | 18 | > * { 19 | color: #fff; 20 | } 21 | } 22 | 23 | .options-header-title { 24 | text-align: right; 25 | 26 | h1 { 27 | font-size: 1.5em; 28 | color: #fff; 29 | margin: 0; 30 | } 31 | 32 | span { 33 | opacity: 0.65; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/options/components/InputNumberGroup/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { InputNumber } from 'antd' 3 | import { InputNumberProps } from 'antd/lib/input-number' 4 | 5 | import './_style.scss' 6 | 7 | export interface InputNumberGroupProps extends InputNumberProps { 8 | suffix?: React.ReactNode 9 | } 10 | 11 | export const InputNumberGroup: FC = props => { 12 | const { suffix, ...restProps } = props 13 | return ( 14 | 15 | 16 | 17 | {suffix && {suffix}} 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/options/components/SaladictForm/SaveBtn.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { Button } from 'antd' 3 | import { useObservableState } from 'observable-hooks' 4 | import { useTranslate } from '@/_helpers/i18n' 5 | import { uploadStatus$ } from '@/options/helpers/upload' 6 | 7 | /** 8 | * Move the button out as independent component to reduce 9 | * re-rendering of the whole component. 10 | */ 11 | export const SaveBtn: FC = () => { 12 | const { t } = useTranslate('common') 13 | const uploadStatus = useObservableState(uploadStatus$, 'idle') 14 | 15 | return ( 16 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/options/components/SaladictForm/_style.scss: -------------------------------------------------------------------------------- 1 | .saladict-form-btns { 2 | margin-top: 50px; 3 | 4 | button { 5 | margin-right: 8px; 6 | margin-bottom: 12px; 7 | } 8 | } 9 | 10 | .saladict-hide { 11 | display: none !important; 12 | } 13 | 14 | .saladict-form-danger-extra .ant-form-item-extra { 15 | color: #c0392b; 16 | } 17 | -------------------------------------------------------------------------------- /src/options/components/SortableList/_style.scss: -------------------------------------------------------------------------------- 1 | .sortable-list-item { 2 | button, 3 | .ant-radio-inner, 4 | .ant-radio-inner::after { 5 | transition: none !important; 6 | } 7 | 8 | .ant-btn-icon-only.ant-btn-sm { 9 | > * { 10 | font-size: 16px; 11 | } 12 | 13 | .anticon svg { 14 | display: block; 15 | } 16 | } 17 | } 18 | 19 | .sortable-list-item-btns { 20 | min-width: fit-content; 21 | } 22 | -------------------------------------------------------------------------------- /src/options/components/SortableList/reorder.ts: -------------------------------------------------------------------------------- 1 | export function reorder( 2 | list: T, 3 | startIndex: number, 4 | endIndex: number 5 | ) { 6 | const result = Array.from(list) 7 | const [removed] = result.splice(startIndex, 1) 8 | result.splice(endIndex, 0, removed) 9 | return result 10 | } 11 | -------------------------------------------------------------------------------- /src/options/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | window.__SALADICT_OPTIONS_PAGE__ = true 5 | window.__SALADICT_LAST_SEARCH__ = '' 6 | -------------------------------------------------------------------------------- /src/options/helpers/change-entry.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const ChangeEntryContext = React.createContext<(entry: string) => void>( 4 | null as any 5 | ) 6 | -------------------------------------------------------------------------------- /src/options/helpers/panel-store.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | import { StoreState } from '@/content/redux/modules' 3 | 4 | const pickIsShowDictPanel = (state: StoreState): boolean => 5 | state.isShowDictPanel 6 | 7 | export const useIsShowDictPanel = () => useSelector(pickIsShowDictPanel) 8 | -------------------------------------------------------------------------------- /src/options/helpers/use-form-dirty.ts: -------------------------------------------------------------------------------- 1 | const formDirty = { 2 | value: false 3 | } 4 | 5 | export const setFormDirty = (value: boolean) => { 6 | formDirty.value = value 7 | } 8 | 9 | export const useFormDirty = (): Readonly => formDirty 10 | -------------------------------------------------------------------------------- /src/options/index.tsx: -------------------------------------------------------------------------------- 1 | import './env' 2 | import '@/selection' 3 | 4 | import React from 'react' 5 | 6 | import { initAntdRoot } from '@/components/AntdRoot' 7 | import { MainEntry } from './components/MainEntry' 8 | 9 | import './_style.scss' 10 | 11 | document.title = 'Saladict Options' 12 | 13 | initAntdRoot(() => ) 14 | -------------------------------------------------------------------------------- /src/popup/__fake__/_style.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ddd; 3 | } 4 | -------------------------------------------------------------------------------- /src/popup/__fake__/env.ts: -------------------------------------------------------------------------------- 1 | import '../index' 2 | import './_style.scss' 3 | import { initConfig, updateConfig } from '@/_helpers/config-manager' 4 | 5 | async function main() { 6 | const config = await initConfig() 7 | await updateConfig({ 8 | ...config, 9 | darkMode: true 10 | }) 11 | } 12 | 13 | main() 14 | -------------------------------------------------------------------------------- /src/popup/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | window.__SALADICT_POPUP_PAGE__ = true 5 | -------------------------------------------------------------------------------- /src/quick-search/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | window.__SALADICT_QUICK_SEARCH_PAGE__ = true 5 | -------------------------------------------------------------------------------- /src/quick-search/quick-search.scss: -------------------------------------------------------------------------------- 1 | @import '@/content/components/DictPanel/DictPanelStandalone.scss'; 2 | 3 | html, 4 | body, 5 | #root { 6 | position: static; 7 | height: 100%; 8 | margin: 0; 9 | padding: 0; 10 | // hide white spaces 11 | font-size: 0; 12 | } 13 | 14 | #root { 15 | overflow: hidden; 16 | } 17 | 18 | .popup-root { 19 | overflow: hidden; 20 | display: flex; 21 | flex-direction: column-reverse; 22 | width: 450px; 23 | height: 550px; 24 | font-size: 14px; 25 | } 26 | -------------------------------------------------------------------------------- /src/selection/quick-search.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, merge } from 'rxjs' 2 | import { share, buffer, debounceTime, filter } from 'rxjs/operators' 3 | import { AppConfig } from '@/app-config' 4 | import { isStandalonePage, isOptionsPage } from '@/_helpers/saladict' 5 | import { whenKeyPressed, isQSKey } from './helper' 6 | 7 | /** 8 | * Listen to triple-ctrl shortcut which opens quick search panel. 9 | * Pressing ctrl/command key more than three times within 500ms 10 | * trigers triple-ctrl. 11 | */ 12 | export function createQuickSearchStream(config: AppConfig | null) { 13 | if (!config || !config.tripleCtrl || isStandalonePage() || isOptionsPage()) { 14 | return EMPTY 15 | } 16 | 17 | const qsKeyPressed$$ = share()(whenKeyPressed(isQSKey)) 18 | 19 | return qsKeyPressed$$.pipe( 20 | buffer( 21 | merge( 22 | debounceTime(500)(qsKeyPressed$$), // collect after 0.5s 23 | whenKeyPressed(e => !isQSKey(e)) // other key pressed 24 | ) 25 | ), 26 | filter(group => group.length >= 3) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/typings/css.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import * as CSS from 'csstype' 3 | 4 | declare module 'csstype' { 5 | interface Properties { 6 | '--panel-width'?: string 7 | '--panel-max-height'?: string 8 | '--panel-font-size'?: string 9 | '--color-brand'?: string 10 | '--color-font'?: string 11 | '--color-background'?: string 12 | '--color-rgb-background'?: string 13 | '--color-divider'?: string 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | browser: typeof browser 3 | 4 | __SALADICT_PANEL_LOADED__?: boolean 5 | __SALADICT_SELECTION_LOADED__?: boolean 6 | 7 | // For self page messaging 8 | pageId?: number | string 9 | faviconURL?: string 10 | pageTitle?: string 11 | pageURL?: string 12 | 13 | __SALADICT_BACKGROUND_PAGE__?: boolean 14 | __SALADICT_INTERNAL_PAGE__?: boolean 15 | __SALADICT_OPTIONS_PAGE__?: boolean 16 | __SALADICT_POPUP_PAGE__?: boolean 17 | __SALADICT_QUICK_SEARCH_PAGE__?: boolean 18 | __SALADICT_PDF_PAGE__?: boolean 19 | 20 | // Options page 21 | __SALADICT_LAST_SEARCH__?: string 22 | 23 | // eslint-disable-next-line 24 | __webpack_public_path__?: string 25 | } 26 | -------------------------------------------------------------------------------- /src/typings/helpers.ts: -------------------------------------------------------------------------------- 1 | interface DeepReadonlyArray extends ReadonlyArray> {} 2 | 3 | type DeepReadonlyObject = { 4 | readonly [P in keyof T]: DeepReadonly 5 | } 6 | 7 | export type DeepReadonly = T extends (infer R)[] 8 | ? DeepReadonlyArray 9 | : T extends Function 10 | ? T 11 | : T extends object 12 | ? DeepReadonlyObject 13 | : T 14 | 15 | export type Diff = Exclude 16 | 17 | export type Omit = Pick> 18 | 19 | export type Mutable = { -readonly [P in keyof T]: T[P] } 20 | 21 | export type UnionKeys = T extends any ? keyof T : never 22 | export type UnionPick> = T extends any 23 | ? Pick> 24 | : never 25 | 26 | export type Subunion = U 27 | 28 | export const objectKeys = Object.keys as (o: T) => Extract[] 29 | -------------------------------------------------------------------------------- /src/word-editor/env.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | window.__SALADICT_INTERNAL_PAGE__ = true 4 | -------------------------------------------------------------------------------- /src/word-editor/word-editor.scss: -------------------------------------------------------------------------------- 1 | @import '@/content/components/WordEditor/WordEditor.scss'; 2 | 3 | html, 4 | body, 5 | #root { 6 | position: static; 7 | height: 100%; 8 | margin: 0; 9 | padding: 0; 10 | // hide white spaces 11 | font-size: 0; 12 | } 13 | 14 | #root { 15 | overflow: hidden; 16 | } 17 | 18 | .wordEditorPanel-Container { 19 | width: 100% !important; 20 | } 21 | 22 | .wordEditorPanel { 23 | width: 100% !important; 24 | height: 100vh !important; 25 | max-width: unset; 26 | max-height: unset; 27 | border-radius: 0; 28 | box-shadow: none; 29 | } 30 | -------------------------------------------------------------------------------- /test/helper.ts: -------------------------------------------------------------------------------- 1 | import * as SinonChrome from 'sinon-chrome' 2 | 3 | export const browser = (window.browser as unknown) as typeof SinonChrome 4 | -------------------------------------------------------------------------------- /test/specs/_helpers/chs-to-chz.spec.ts: -------------------------------------------------------------------------------- 1 | import chsToChz from '@/_helpers/chs-to-chz' 2 | 3 | describe('Chs to Chz', () => { 4 | it('should convert chs to chz', () => { 5 | expect(chsToChz('龙龟')).toBe('龍龜') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/ahdict/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['comment.html', 'https://ahdictionary.com/word/search.html?q=comment'], 4 | ['love.html', 'https://ahdictionary.com/word/search.html?q=love'], 5 | ['salad.html', 'https://ahdictionary.com/word/search.html?q=salad'] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/ahdict/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['comment', 'love', 'salad'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/ahdictionary.+comment$/) 8 | .reply(200, require('!raw-loader!./response/comment.html').default) 9 | 10 | mock 11 | .onGet(/ahdictionary.+love$/) 12 | .reply(200, require('!raw-loader!./response/love.html').default) 13 | 14 | mock 15 | .onGet(/ahdictionary.+salad$/) 16 | .reply(200, require('!raw-loader!./response/salad.html').default) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/bing/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'lex.html', 5 | 'https://cn.bing.com/dict/clientsearch?mkt=zh-CN&setLang=zh&form=BDVEHC&ClientVer=BDDTV3.5.1.4320&q=love' 6 | ], 7 | [ 8 | 'machine.html', 9 | `https://cn.bing.com/dict/clientsearch?mkt=zh-CN&setLang=zh&form=BDVEHC&ClientVer=BDDTV3.5.1.4320&q=${encodeURIComponent( 10 | 'lose yourself in the dark' 11 | )}` 12 | ], 13 | [ 14 | 'related.html', 15 | 'https://cn.bing.com/dict/clientsearch?mkt=zh-CN&setLang=zh&form=BDVEHC&ClientVer=BDDTV3.5.1.4320&q=lovxx' 16 | ] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/bing/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love', 'machine', 'related'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/bing\.com.+love$/) 8 | .reply(200, require('!raw-loader!./response/lex.html').default) 9 | 10 | mock 11 | .onGet(/bing\.com.+machine$/) 12 | .reply(200, require('!raw-loader!./response/machine.html').default) 13 | 14 | mock 15 | .onGet(/bing\.com.+related$/) 16 | .reply(200, require('!raw-loader!./response/related.html').default) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cambridge/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'catch-zht.html', 5 | 'https://dictionary.cambridge.org/zht/%E6%90%9C%E7%B4%A2/direct/?datasetsearch=english-chinese-traditional&q=catch' 6 | ], 7 | [ 8 | 'house-zhs.html', 9 | 'https://dictionary.cambridge.org/zhs/%E6%90%9C%E7%B4%A2/direct/?datasetsearch=english-chinese-simplified&q=house' 10 | ], 11 | [ 12 | 'love.html', 13 | 'https://dictionary.cambridge.org/search/direct/?datasetsearch=english&q=love' 14 | ], 15 | [ 16 | 'jumblish.html', 17 | 'https://dictionary.cambridge.org/search/direct/?datasetsearch=english&q=jumblish' 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cambridge/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['jumblish', 'catch-zht', 'house-zhs', 'love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/cambridge/).reply(info => { 7 | return [ 8 | 200, 9 | require('!raw-loader!./response/' + 10 | new URL(info.url!).searchParams.get('q') + 11 | '.html').default 12 | ] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cnki/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/cnki/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/CNKI/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('love', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(({ result, audio }) => { 12 | expect(audio).toBeUndefined() 13 | expect(result.dict.length).toBeGreaterThan(0) 14 | expect(result.senbi.length).toBeGreaterThan(0) 15 | expect(result.seneng.length).toBeGreaterThan(0) 16 | }) 17 | ) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cnki/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'http://dict.cnki.net/old/dict_result.aspx?scw=love'] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cnki/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/cnki/).reply(info => { 7 | return [ 8 | 200, 9 | require('!raw-loader!./response/' + 10 | new URL(info.url!).searchParams.get('searchword') + 11 | '.html').default 12 | ] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cobuild/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/cobuild/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile, ProfileMutable } from '@/app-config/profiles' 5 | 6 | describe('Dict/COBUILD/engine', () => { 7 | it('should parse result correctly', () => { 8 | const profile = getDefaultProfile() as ProfileMutable 9 | return retry(() => 10 | search('love', getDefaultConfig(), profile, { isPDF: false }).then( 11 | searchResult => { 12 | expect(searchResult.result).toBeTruthy() 13 | } 14 | ) 15 | ) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cobuild/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['how.html', 'https://www.collinsdictionary.com/dictionary/english/how'], 4 | [ 5 | 'love.html', 6 | 'https://www.collinsdictionary.com/zh/dictionary/english/love' 7 | ] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/cobuild/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['test'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/collinsdictionary\.com\/zh/) 8 | .reply(200, require('!raw-loader!./response/love.html').default) 9 | 10 | mock 11 | .onGet(/collinsdictionary/) 12 | .reply(200, require('!raw-loader!./response/how.html').default) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/etymonline/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love-word.html', 'https://www.etymonline.com/word/love'], 4 | ['love.html', 'http://www.etymonline.com/search?q=love'] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/etymonline/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love-word', 'love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/etymonline.+\/word\//).reply(info => { 7 | return /love-word/.test(info.url || '') 8 | ? [200, require('!raw-loader!./response/love-word.html').default] 9 | : [404] 10 | }) 11 | 12 | mock.onGet(/etymonline.+\/search\?/).reply(info => { 13 | return /love-word/.test(info.url || '') 14 | ? [404] 15 | : [200, require('!raw-loader!./response/love-word.html').default] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/eudic/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/eudic/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Eudic/engine', () => { 7 | it('should parse result correctly', async () => { 8 | return retry(() => 9 | search('love', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(searchResult => { 12 | expect(searchResult.audio && typeof searchResult.audio.us).toBe( 13 | 'string' 14 | ) 15 | expect(searchResult.result).toHaveLength(10) 16 | const item = searchResult.result[0] 17 | expect(typeof item.chs).toBe('string') 18 | expect(typeof item.eng).toBe('string') 19 | expect(typeof item.mp3).toBe('string') 20 | expect(typeof item.channel).toBe('string') 21 | }) 22 | ) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/eudic/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'https://dict.eudic.net/dicts/en/love'], 4 | [ 5 | 'sentences.html', 6 | ([page]) => { 7 | const statusMatch = /id="page-status" value="([^"]+)"/.exec(page) 8 | if (statusMatch) { 9 | return { 10 | url: 'https://dict.eudic.net/Dicts/en/tab-detail/-12', 11 | method: 'post', 12 | transformResponse: [data => data], 13 | headers: { 14 | 'Content-Type': 'application/x-www-form-urlencoded' 15 | }, 16 | responseType: 'text/plain', 17 | data: `status=${statusMatch[1]}` 18 | } 19 | } 20 | } 21 | ] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/eudic/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onAny(/eudic/).reply(info => { 7 | const file = /tab-detail/.test(info.url || '') ? 'sentences' : 'love' 8 | return [200, require(`raw-loader!./response/${file}.html`).default] 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/googledict/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'chs-mouse.html', 5 | 'https://www.google.com/search?hl=en&safe=off&q=meaning:mouse' 6 | ], 7 | [ 8 | 'chs-爱.html', 9 | 'https://www.google.com/search?hl=en&safe=off&q=meaning:' + 10 | encodeURIComponent('爱') 11 | ], 12 | [ 13 | 'en-love.html', 14 | 'https://www.google.com/search?hl=en&safe=off&q=meaning:love' 15 | ], 16 | [ 17 | 'en-salad.html', 18 | 'https://www.google.com/search?hl=en&safe=off&q=meaning:salad' 19 | ] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/googledict/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['salad', 'mouse', '爱', 'love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/google.+(define|meaning).+mouse/) 8 | .reply(200, require('!raw-loader!./response/chs-mouse.html').default) 9 | 10 | mock 11 | .onGet(new RegExp(encodeURIComponent('爱'))) 12 | .reply(200, require('!raw-loader!./response/chs-爱.html').default) 13 | 14 | mock 15 | .onGet(/google.+(define|meaning).+love/) 16 | .reply(200, require('!raw-loader!./response/en-love.html').default) 17 | 18 | mock 19 | .onGet(/google.+(define|meaning).+salad/) 20 | .reply(200, require('!raw-loader!./response/en-salad.html').default) 21 | } 22 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/guoyu/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/guoyu/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/GuoYu/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('愛', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(searchResult => { 12 | expect(searchResult.audio && typeof searchResult.audio.py).toBe( 13 | 'string' 14 | ) 15 | expect(typeof searchResult.result.t).toBe('string') 16 | expect(Array.isArray(searchResult.result.h)).toBeTruthy() 17 | expect(searchResult.result.translation).toBeTruthy() 18 | }) 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/guoyu/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['愛.json', `https://www.moedict.tw/a/${encodeURIComponent('愛')}.json`] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/guoyu/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['愛'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/moedict/).reply(200, require('./response/愛.json')) 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/helpers.ts: -------------------------------------------------------------------------------- 1 | import { timer } from '@/_helpers/promise-more' 2 | 3 | export async function retry(executor: () => Promise, retryTimes = 1) { 4 | let times = retryTimes + 1 5 | while (times--) { 6 | try { 7 | return await executor() 8 | } catch (e) { 9 | await timer(1000) 10 | } 11 | } 12 | console.error('>>>>> timeout') 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/hjdict/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['henr.html', 'https://www.hjdict.com/w/henr'], 4 | ['love.html', 'https://www.hjdict.com/w/love'], 5 | ['爱.html', 'https://www.hjdict.com/jp/jc/%E7%88%B1'] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/hjdict/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['爱', 'love', 'henr'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/hjdict/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jikipedia/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/jikipedia/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Jikipedia/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('xswl', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(searchResult => { 12 | expect(typeof searchResult.result.length).toBeGreaterThan(0) 13 | expect(searchResult.result[0].title).toBe('string') 14 | expect(searchResult.result[0].content).toBe('string') 15 | }) 16 | ) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jikipedia/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [['xswl.html', 'https://jikipedia.com/search?phrase=xswl']] 3 | } 4 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jikipedia/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['xswl'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/jikipedia/) 8 | .reply(200, require(`raw-loader!./response/xswl.html`).default) 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jukuu/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/jukuu/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Jukuu/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('love', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(searchResult => { 12 | expect(typeof searchResult.result.lang).toBe('string') 13 | expect(searchResult.result.sens.length).toBeGreaterThan(0) 14 | expect(typeof searchResult.result.sens[0].trans).toBe('string') 15 | expect(searchResult.result.sens[0].trans.length).toBeGreaterThan(0) 16 | }) 17 | ) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jukuu/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [['love.html', 'http://www.jukuu.com/search.php?q=love']] 3 | } 4 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/jukuu/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/jukuu/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/lexico/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['jumblish.html', 'https://www.lexico.com/definition/jumblish'], 4 | ['love.html', 'https://www.lexico.com/definition/love'], 5 | ['how.html', 'https://www.lexico.com/definition/how'] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/lexico/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['how', 'love', 'jumblish'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/lexico/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/liangan/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['愛.json', `https://www.moedict.tw/c/${encodeURIComponent('愛')}.json`] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/liangan/requests.mock.ts: -------------------------------------------------------------------------------- 1 | export { mockSearchTexts, mockRequest } from '../guoyu/requests.mock' 2 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/longman/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['jumblish.html', 'http://www.ldoceonline.com/dictionary/jumblish'], 4 | ['love.html', 'http://www.ldoceonline.com/dictionary/love'], 5 | ['profit.html', 'http://www.ldoceonline.com/dictionary/profit'] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/longman/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love', 'profit', 'jumblish'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/ldoceonline/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/macmillan/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'jumblish.html', 5 | 'http://www.macmillandictionary.com/dictionary/british/jumblish' 6 | ], 7 | ['love.html', 'http://www.macmillandictionary.com/dictionary/british/love'], 8 | [ 9 | 'love_2.html', 10 | 'http://www.macmillandictionary.com/dictionary/british/love_2' 11 | ], 12 | [ 13 | 'viral.html', 14 | 'http://www.macmillandictionary.com/dictionary/british/viral' 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/macmillan/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love', 'viral', 'jumblish'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/macmillan/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/merriamwebster/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'https://www.merriam-webster.com/dictionary/love'], 4 | ['text.html', 'https://www.merriam-webster.com/dictionary/text'], 5 | ['salad.html', 'https://www.merriam-webster.com/dictionary/salad'], 6 | ['add.html', 'https://www.merriam-webster.com/dictionary/add'], 7 | ['transitive.html', 'https://www.merriam-webster.com/dictionary/transitive'] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/merriamwebster/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love', 'text', 'salad'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/merriam-webster.+love$/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | 10 | mock 11 | .onGet(/merriam-webster.+text$/) 12 | .reply(200, require(`raw-loader!./response/text.html`).default) 13 | 14 | mock 15 | .onGet(/merriam-webster.+salad$/) 16 | .reply(200, require(`raw-loader!./response/salad.html`).default) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/mojidict/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['心'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onPost(/mojidict.*fetchWord/) 8 | .reply(200, require(`./response/心/fetchWord.json`)) 9 | .onPost(/mojidict.*search/) 10 | .reply(200, require(`./response/心/search.json`)) 11 | .onPost(/mojidict.*fetchTts/) 12 | .reply(200, require(`./response/心/fetchTts.json`)) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/naver/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | '愛.json', 5 | 'https://ja.dict.naver.com/api3/jako/search?query=' + 6 | encodeURIComponent('愛') 7 | ], 8 | [ 9 | '爱.json', 10 | `https://zh.dict.naver.com/api3/zhko/search?query=${encodeURIComponent( 11 | '爱' 12 | )}&lang=zh_CN` 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/naver/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['爱', '愛'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(new RegExp('naver.+' + encodeURIComponent('爱'))) 8 | .reply(200, require(`./response/爱.json`)) 9 | 10 | mock 11 | .onGet(new RegExp('naver.+' + encodeURIComponent('愛'))) 12 | .reply(200, require(`./response/愛.json`)) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/oaldict/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'comment.html', 5 | 'https://www.oxfordlearnersdictionaries.com/search/english/direct/?q=comment' 6 | ], 7 | [ 8 | 'love.html', 9 | 'https://www.oxfordlearnersdictionaries.com/search/english/direct/?q=love' 10 | ], 11 | [ 12 | 'salad.html', 13 | 'https://www.oxfordlearnersdictionaries.com/search/english/direct/?q=salad' 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/oaldict/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['comment', 'love', 'salad'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/oxfordlearnersdictionaries.+comment$/) 8 | .reply(200, require('!raw-loader!./response/comment.html').default) 9 | 10 | mock 11 | .onGet(/oxfordlearnersdictionaries.+love$/) 12 | .reply(200, require('!raw-loader!./response/love.html').default) 13 | 14 | mock 15 | .onGet(/oxfordlearnersdictionaries.+salad$/) 16 | .reply(200, require('!raw-loader!./response/salad.html').default) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/renren/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/renren/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Renren/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('love', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(searchResult => {}) 12 | ) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/renren/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'https://www.91dict.com/words?w=love'], 4 | [ 5 | 'detail.html', 6 | ([page]) => { 7 | const detailMatch = /\/r_subs\?sub_id=[^"']+/.exec(page) 8 | if (detailMatch) { 9 | return { 10 | url: 'https://www.91dict.com' + detailMatch 11 | } 12 | } 13 | } 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/renren/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/91dict.+r_subs/) 8 | .reply(200, require(`raw-loader!./response/detail.html`).default) 9 | 10 | mock 11 | .onGet(/91dict/) 12 | .reply(200, require(`raw-loader!./response/love.html`).default) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/shanbay/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'https://www.shanbay.com/bdc/mobile/preview/word?word=love'], 4 | [ 5 | 'love.json', 6 | ([page]) => { 7 | const wordIdMatch = /"word-spell" data-id="([^"]+)"/.exec(page) 8 | if (wordIdMatch) { 9 | return { 10 | url: `https://www.shanbay.com/api/v1/bdc/example/?vocabulary_id=${ 11 | wordIdMatch[1] 12 | }&type=sys` 13 | } 14 | } 15 | } 16 | ] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/shanbay/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/shanbay/).reply(info => { 7 | return /mobile/.test(info.url || '') 8 | ? [200, require(`raw-loader!./response/love.html`).default] 9 | : [200, require(`./response/love.json`)] 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/urban/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [['love.html', 'http://www.urbandictionary.com/define.php?term=love']] 3 | } 4 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/urban/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/urbandictionary/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/vocabulary/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/vocabulary/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Vocabulary/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('love', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(({ result, audio }) => { 12 | expect(audio).toBeUndefined() 13 | expect(typeof result.long).toBe('string') 14 | expect(typeof result.short).toBe('string') 15 | }) 16 | ) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/vocabulary/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [['love.html', 'https://www.vocabulary.com/dictionary/love']] 3 | } 4 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/vocabulary/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/vocabulary/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblio/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/weblio/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Weblio/engine', () => { 7 | ;['love', '吐く', '当たる'].forEach(text => { 8 | it(`should parse result ${text} correctly`, () => { 9 | return retry(() => 10 | search(text, getDefaultConfig(), getDefaultProfile(), { 11 | isPDF: false 12 | }).then(({ result }) => { 13 | expect(result.length).toBeGreaterThanOrEqual(1) 14 | expect(typeof result[0].title).toBe('string') 15 | expect(typeof result[0].def).toBe('string') 16 | }) 17 | ) 18 | }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblio/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | '主催.html', 5 | 'https://www.weblio.jp/content/' + encodeURIComponent('主催') 6 | ], 7 | ['love.html', 'https://www.weblio.jp/content/love'], 8 | [ 9 | '吐く.html', 10 | 'https://www.weblio.jp/content/' + encodeURIComponent('吐く') 11 | ], 12 | [ 13 | '当たる.html', 14 | 'https://www.weblio.jp/content/' + encodeURIComponent('当たる') 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblio/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['主催', 'love', '吐く', '当たる'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/www\.weblio\.jp.+love/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | 10 | mock 11 | .onGet(new RegExp('www\\.weblio\\.jp.+' + encodeURIComponent('吐く'))) 12 | .reply(200, require(`raw-loader!./response/吐く.html`).default) 13 | 14 | mock 15 | .onGet(new RegExp('www\\.weblio\\.jp.+' + encodeURIComponent('当たる'))) 16 | .reply(200, require(`raw-loader!./response/当たる.html`).default) 17 | 18 | mock 19 | .onGet(new RegExp('www\\.weblio\\.jp.+' + encodeURIComponent('主催'))) 20 | .reply(200, require(`raw-loader!./response/主催.html`).default) 21 | } 22 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblioejje/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/weblioejje/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Weblioejje/engine', () => { 7 | ;['love', '愛'].forEach(text => { 8 | it(`should parse result ${text} correctly`, () => { 9 | return retry(() => 10 | search(text, getDefaultConfig(), getDefaultProfile(), { 11 | isPDF: false 12 | }).then(({ result }) => { 13 | expect(result.length).toBeGreaterThanOrEqual(1) 14 | for (const { content } of result) { 15 | expect(typeof content).toBe('string') 16 | expect(content.length).toBeGreaterThan(1) 17 | } 18 | }) 19 | ) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblioejje/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['love.html', 'https://ejje.weblio.jp/content/love'], 4 | ['愛.html', 'https://ejje.weblio.jp/content/' + encodeURIComponent('愛')] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/weblioejje/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['love', '愛'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/ejje\.weblio\.jp.+love/) 8 | .reply(200, require(`raw-loader!./response/love.html`).default) 9 | 10 | mock 11 | .onGet(new RegExp('ejje\\.weblio\\.jp.+' + encodeURIComponent('愛'))) 12 | .reply(200, require(`raw-loader!./response/愛.html`).default) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/websterlearner/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['door.html', 'http://www.learnersdictionary.com/definition/door'], 4 | ['house.html', 'http://www.learnersdictionary.com/definition/house'], 5 | ['jumblish.html', 'http://www.learnersdictionary.com/definition/jumblish'] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/websterlearner/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['door', 'house', 'jumblish'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/learnersdictionary/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/wikipedia/engine.spec.ts: -------------------------------------------------------------------------------- 1 | import { retry } from '../helpers' 2 | import { search } from '@/components/dictionaries/wikipedia/engine' 3 | import { getDefaultConfig } from '@/app-config' 4 | import { getDefaultProfile } from '@/app-config/profiles' 5 | 6 | describe('Dict/Wikipedia/engine', () => { 7 | it('should parse result correctly', () => { 8 | return retry(() => 9 | search('数字', getDefaultConfig(), getDefaultProfile(), { 10 | isPDF: false 11 | }).then(({ result }) => { 12 | expect(typeof result.title).toBe('string') 13 | expect(typeof result.content).toBe('string') 14 | }) 15 | ) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/wikipedia/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | [ 4 | 'langlist.html', 5 | 'https://zh.m.wikipedia.org/wiki/Special:%E7%A7%BB%E5%8A%A8%E7%89%88%E8%AF%AD%E8%A8%80/%E6%95%B8%E5%AD%97' 6 | ], 7 | ['数字.html', 'https://zh.m.wikipedia.org/wiki/%E6%95%B0%E5%AD%97'] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/wikipedia/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['数字'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock 7 | .onGet(/wikipedia.+Special/) 8 | .reply(200, require(`raw-loader!./response/langlist.html`).default) 9 | 10 | mock 11 | .onGet(/m\.wikipedia/) 12 | .reply(200, require(`raw-loader!./response/数字.html`).default) 13 | } 14 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/youdao/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['jumblish.html', 'https://dict.youdao.com/w/jumblish'], 4 | ['love.html', 'https://dict.youdao.com/w/love'], 5 | ['make.html', 'https://dict.youdao.com/w/make'], // collins 6 | [ 7 | 'translation.html', 8 | 'https://dict.youdao.com/w/' + 9 | encodeURIComponent( 10 | `She walks in beauty, like the night Of cloudless climes and starry skies.` 11 | ) 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/youdao/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['make', 'love', 'translation', 'jumblish'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/youdao/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/zdic/fixtures.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | ['沙拉.html', 'https://www.zdic.net/hans/' + encodeURIComponent('沙拉')], 4 | ['爱.html', 'https://www.zdic.net/hans/' + encodeURIComponent('爱')] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/specs/components/dictionaries/zdic/requests.mock.ts: -------------------------------------------------------------------------------- 1 | import { MockRequest } from '@/components/dictionaries/helpers' 2 | 3 | export const mockSearchTexts = ['沙拉', '爱'] 4 | 5 | export const mockRequest: MockRequest = mock => { 6 | mock.onGet(/zdic/).reply(info => { 7 | const wordMatch = /[^/]+$/.exec(info.url || '') 8 | return wordMatch 9 | ? [ 10 | 200, 11 | require(`raw-loader!./response/${decodeURIComponent( 12 | wordMatch[0] 13 | )}.html`).default 14 | ] 15 | : [404] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Target latest version of ECMAScript. 4 | "target": "esnext", 5 | // Search under node_modules for non-relative imports. 6 | "moduleResolution": "node", 7 | // Process & infer types from .js files. 8 | "allowJs": true, 9 | // Don't emit; allow Babel to transform files. 10 | "noEmit": true, 11 | // Enable strictest settings like strictNullChecks & noImplicitAny. 12 | "strict": true, 13 | // Disallow features that require cross-file information for emit. 14 | "isolatedModules": true, 15 | // Import non-ES modules as default imports. 16 | "esModuleInterop": true, 17 | "module": "esnext", 18 | "resolveJsonModule": true, 19 | "noImplicitAny": false, 20 | "jsx": "react", 21 | "baseUrl": "./", 22 | "paths": { 23 | "@/*": ["src/*"] 24 | }, 25 | "typeRoots": ["node_modules/@types", "src/typings"], 26 | "lib": ["esnext", "dom", "dom.iterable"] 27 | }, 28 | "include": ["src", "test", ".storybook"] 29 | } 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('neutrino')().webpack() 2 | --------------------------------------------------------------------------------