diff --git a/app/browser/reducers/spellCheckerReducer.js b/app/browser/reducers/spellCheckerReducer.js
index e811500e3f2..5fdfd47938a 100644
--- a/app/browser/reducers/spellCheckerReducer.js
+++ b/app/browser/reducers/spellCheckerReducer.js
@@ -8,6 +8,9 @@ const appConstants = require('../../../js/constants/appConstants')
const {makeImmutable} = require('../../common/state/immutableUtil')
const {getWebContents} = require('../webContentsCache')
const spellChecker = require('../../spellChecker')
+const settings = require('../../../js/constants/settings')
+const {setUserPref} = require('../../../js/state/userPrefs')
+const getSetting = require('../../../js/settings').getSetting
const migrate = (state) => {
if (state.get('dictionary')) {
@@ -28,12 +31,32 @@ const migrate = (state) => {
return state
}
+const setSpellCheckerSettings = () => {
+ const enabled = getSetting(settings.SPELLCHECK_ENABLED)
+ setUserPref('browser.enable_spellchecking', enabled)
+ if (enabled) {
+ setUserPref('spellcheck.dictionaries',
+ getSetting(settings.SPELLCHECK_LANGUAGES))
+ }
+}
+
const spellCheckerReducer = (state, action, immutableAction) => {
action = immutableAction || makeImmutable(action)
switch (action.get('actionType')) {
case appConstants.APP_SET_STATE:
state = migrate(state)
break
+ // TODO(darkdh): APP_SET_STATE is too early for startup setting so we use
+ // APP_WINDOW_CREATED here
+ case appConstants.APP_WINDOW_CREATED:
+ setSpellCheckerSettings()
+ break
+ case appConstants.APP_CHANGE_SETTING:
+ if ([settings.SPELLCHECK_ENABLED, settings.SPELLCHECK_LANGUAGES]
+ .includes(action.get('key'))) {
+ setSpellCheckerSettings()
+ }
+ break
case appConstants.APP_SPELLING_SUGGESTED:
if (typeof action.get('suggestion') === 'string') {
const webContents = getWebContents(action.get('tabId'))
diff --git a/app/extensions/brave/about-preferences.html b/app/extensions/brave/about-preferences.html
index 992e4841495..317f01b5e13 100644
--- a/app/extensions/brave/about-preferences.html
+++ b/app/extensions/brave/about-preferences.html
@@ -19,6 +19,7 @@
+
diff --git a/app/extensions/brave/index.html b/app/extensions/brave/index.html
index bc3b4dec0f8..eba8b9b80a8 100644
--- a/app/extensions/brave/index.html
+++ b/app/extensions/brave/index.html
@@ -19,6 +19,8 @@
+
+
diff --git a/app/extensions/brave/locales/en-US/locales.properties b/app/extensions/brave/locales/en-US/locales.properties
new file mode 100644
index 00000000000..183b3228927
--- /dev/null
+++ b/app/extensions/brave/locales/en-US/locales.properties
@@ -0,0 +1,26 @@
+bn-BD=Bengali (Bangladesh)
+bn-IN=Bengali (India)
+cs=Czech
+de-DE=German (Germany)
+en-GB=English (U.K.)
+en-US=English (U.S.)
+es=Spanish
+eu=Basque
+fr-FR=French (France)
+hi-IN=Hindi (India)
+id-ID=Indonesian (Indonesia)
+it-IT=Italian
+ja-JP=Japanese (Japan)
+ko-KR=Korean
+ms-MY=Malay (Malaysia)
+nl-NL=Dutch (Netherlands)
+pl-PL=Polish
+pt-BR=Portuguese (Brazil)
+ru=Russian
+sl=Slovenian
+sv-SE=Swedish (Sweden)
+ta=Tamil
+te=Telugu
+tr-TR=Turkish (Turkey)
+uk=Ukrainian
+zh-CN=Simplified Chinese (China)
diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties
index 4b9ba6e8743..03a1a7a6de6 100644
--- a/app/extensions/brave/locales/en-US/preferences.properties
+++ b/app/extensions/brave/locales/en-US/preferences.properties
@@ -54,9 +54,6 @@ bitcoinBalance=Please transfer:
bitwarden=bitwarden®
blockAttackSites=Block reported attack sites (not available yet)
blockedCountBadge=Display block count badge on shields button
-bn-BD=Bengali (Bangladesh)
-bn-BD=Bengali (Bangladesh)
-bn-IN=Bengali (India)
bookmarksBarFaviconOnly=Favicons only
bookmarksBarTextAndFavicon=Text and Favicons
bookmarksBarTextOnly=Text only
@@ -96,12 +93,10 @@ createWallet=create wallet
createWalletStatus=Click the Create Wallet button to get started.
creatingWallet=creating…
creatingWalletStatus=Creating wallet…
-cs=Czech
dashboardSettingsTitle=Dashboard
dashboardShowImages=Show images
dashlane=Dashlane® (requires application)
date=Date
-de-DE=German (Germany)
default=Default
default=Default
defaultBrowser=Brave is your default browser.
@@ -121,8 +116,7 @@ downloadsAskAlwaysSwitch=Always ask me where to save files
downloadsInput=~/downloads/
downloadsLabel=Save my downloads here:
emailAddress=Email address
-en-GB=English (U.K.)
-en-US=English (U.S.)
+enableSpellCheck=Enable Spell Check *
enableAutofill=Enable Autofill
enableFlash=Enable Adobe Flash support
enableFlashSubtext=Brave uses a special version of Pepper Flash which must be installed from
@@ -134,36 +128,28 @@ enableWidevine=Enable Google Widevine support
engineGoKey=Engine Go Key (Type First)
engineGoKey=Engine Go Key (type first)
enpass=Enpass® (requires application)
-es=Spanish
-eu=Basque
extensions=Extensions
firstKey=Key 1
firstRecoveryKey=Recovery Key 1
flash=Run Adobe Flash Player
flashAllowAlways=Allow until {{time}}
flashTroubleshooting=Flash not working? Try the troubleshooting tips on our
-fr-FR=French (France)
fullscreenContent=Full Screen Content
fullscreenPermission=Fullscreen access
general=General
generalSettings=General Settings
geolocationPermission=Location access
-hi-IN=Hindi (India)
hideExcluded=Show only included sites
hint0=The site shields panel allows you to turn HTTPS Everywhere on or off. HTTPS Everywhere automatically changes your HTTP traffic to HTTPS for supported sites to keep you more secure.
hint1=Brave will always auto-update for you, but you can check for an update on demand in the menu.
hint2=The File menu allows you to create a New Session Tab. Session tabs are like any other tab but they run in a different user profile. This allows you to login to the same sites multiple times with the same browser.
hintsTitle=Helpful hints
-id-ID=Indonesian (Indonesia)
importBrowserData=Import Browser Data
importButton=Import now…
importLabel=Browser data import:
importNow=Import now…
include=Include
insufficientFundsStatus=Your account balance is under your budget for this month. Please add funds.
-it-IT=Italian
-ja-JP=Japanese (Japan)
-ko-KR=Korean
lastContribution=Last contribution:
lastPass=LastPass®
ledgerBackupText1=Below, you will find the anonymized recovery key that is required if you ever lose access to this computer.
@@ -199,7 +185,6 @@ minimumVisitsLow=1 visit
minimumVisitsMedium=5 visits
minimumVisitsSetting=Minimum visits for publisher relevancy
monthlyBudget=monthly budget
-ms-MY=Malay (Malaysia)
multipleHomePages.title=Multiple home pages
myHomepage=My home page is
newTabBlank=blank
@@ -209,7 +194,6 @@ newTabHomePage=Home page
newTabMode=A new tab shows
newTabNewTabPage=Dashboard
nextContribution=Next contribution:
-nl-NL=Dutch (Netherlands)
nonVerifiedPublishers=Allow contributions to non-verified sites
noPaymentHistory=No previous contributions
normal=Normal
@@ -252,7 +236,6 @@ paymentsWelcomeTitle=Welcome to Brave Payments!
pendingFundsStatus=Pending funds: {{funds}}. Newly-added funds may take 30+ minutes to appear.
percentage=%
percentPaid=Percentage
-pl-PL=Polish
plugins=Plugins
pluginSettings=Plugin Settings
pointerLockPermission=Disable your mouse cursor
@@ -262,7 +245,6 @@ privacy=Privacy
privateData=Private Data
privateDataMessage=Clear the following data types when I close Brave
protocolRegistrationPermission=Protocol registration
-pt-BR=Portuguese (Brazil)
publisher=Site
publishers=Publishers
rank=Rank
@@ -273,7 +255,6 @@ recoveryKey=Recovery Key
recoverLedger=Recover your wallet
remove=Remove
requiresRestart=* Requires browser restart
-ru=Russian
savedSiteSettings=Saved site settings and permissions
saveRecoveryFile=Save recovery file…
scaleSizeLarger=Larger
@@ -309,7 +290,7 @@ showTopsiteSuggestions=Show top site suggestions
site=Site
sitePermissions=Saved Site Permissions
sitePermissionsExceptions=Saved Site Exceptions
-sl=Slovenian
+spellCheckLanguages=Spell Check Languages *
startsWith=Brave starts with
startsWithOptionHomePage=Home page
startsWithOptionLastTime=My windows / tabs from last time
@@ -320,7 +301,6 @@ statusNextReconcileOverdue=Overdue
statusNextReconcileToday=Today
statusOnError=Brave Wallet can't be reached.
submitFeedback=Submit Feedback…
-sv-SE=Swedish (Sweden)
swipeNavigationDistance=Swipe Navigation Distance
switchToNewTabs=Switch to new tabs immediately
sync=Sync
@@ -359,7 +339,6 @@ syncSiteSettings=Saved site settings
syncStart=I am new to Sync
syncTitle=Brave Sync
syncTitleMessage=Sync encrypted browser data between your devices securely and privately using Brave Sync.
-ta=Tamil
tabCloseAction=When closing an active tab:
tabCloseActionLastActive=Select the last viewed tab
tabCloseActionNext=Select the next tab
@@ -370,12 +349,9 @@ tabs=Tabs
tabSettings=Tab Settings
tabsPerTabPage=Number of tabs per tab set:
tabsSettings=Tabs Settings
-te=Telugu
timeSpent=Time Spent
toolbarUserInterfaceScale=Toolbar and UI elements scale
totalAmount=Total Amount
-tr-TR=Turkish (Turkey)
-uk=Ukrainian
update=Update
updateToPreviewReleases=Update to preview releases *
urlBarOptions=URL Bar Options
@@ -391,4 +367,3 @@ visits=Visits
wideURLbar=Use wide URL bar
widevine=Run Google Widevine
widevineSection=Google Widevine Support
-zh-CN=Chinese
diff --git a/app/locale.js b/app/locale.js
index 74a8232d2a2..18c36da7e58 100644
--- a/app/locale.js
+++ b/app/locale.js
@@ -268,8 +268,9 @@ var rendererIdentifiers = function () {
'autoplayMedia',
// Release channels
'channelDev',
- 'channelBeta'
- ].concat(countryCodes)
+ 'channelBeta',
+ 'spellCheckLanguages'
+ ].concat(countryCodes).concat(availableLanguages)
}
var ctx = null
@@ -302,32 +303,32 @@ exports.translation = function (token, replacements = {}) {
const DEFAULT_LANGUAGE = 'en-US'
const availableLanguages = [
- 'eu',
'bn-BD',
'bn-IN',
- 'zh-CN',
'cs',
- 'nl-NL',
- 'en-US',
+ 'de-DE',
'en-GB',
+ 'en-US',
+ 'es',
+ 'eu',
'fr-FR',
- 'de-DE',
'hi-IN',
'id-ID',
'it-IT',
'ja-JP',
'ko-KR',
'ms-MY',
+ 'nl-NL',
'pl-PL',
'pt-BR',
'ru',
'sl',
'sv-SE',
- 'es',
'ta',
'te',
'tr-TR',
- 'uk'
+ 'uk',
+ 'zh-CN'
]
// Currently configured languages
@@ -394,7 +395,9 @@ exports.init = function (language) {
path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'error.properties'),
path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'passwords.properties'),
path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'common.properties'),
- path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'countries.properties')
+ path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'countries.properties'),
+ path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'locales.properties'),
+ path.join(__dirname, 'extensions', 'brave', 'locales', lang, 'preferences.properties')
)
}
diff --git a/js/about/preferences.js b/js/about/preferences.js
index 725cfac18cf..4aedc8900e6 100644
--- a/js/about/preferences.js
+++ b/js/about/preferences.js
@@ -6,9 +6,11 @@
const React = require('react')
const ImmutableComponent = require('../../app/renderer/components/immutableComponent')
const Immutable = require('immutable')
+const Select = require('react-select')
const {StyleSheet, css} = require('aphrodite/no-important')
const globalStyles = require('../../app/renderer/components/styles/global')
const commonStyles = require('../../app/renderer/components/styles/commonStyles')
+const locale = require('../../js/l10n')
// Components
const PreferenceNavigation = require('../../app/renderer/components/preferences/preferenceNavigation')
@@ -68,6 +70,7 @@ require('../../less/about/preferences.less')
require('../../less/forms.less')
require('../../less/button.less')
require('../../node_modules/font-awesome/css/font-awesome.css')
+require('../../less/react-select.less')
const permissionNames = {
'mediaPermission': ['boolean'],
@@ -100,6 +103,7 @@ class GeneralTab extends ImmutableComponent {
this.importBrowserDataNow = this.importBrowserDataNow.bind(this)
this.onChangeSetting = this.onChangeSetting.bind(this)
this.setAsDefaultBrowser = this.setAsDefaultBrowser.bind(this)
+ this.onSpellCheckLangsChange = this.onSpellCheckLangsChange.bind(this)
}
importBrowserDataNow () {
@@ -117,6 +121,10 @@ class GeneralTab extends ImmutableComponent {
this.props.onChangeSetting(key, value)
}
+ onSpellCheckLangsChange (value) {
+ this.props.onChangeSetting(settings.SPELLCHECK_LANGUAGES, value.split(','))
+ }
+
setAsDefaultBrowser () {
aboutActions.setAsDefaultBrowser()
}
@@ -136,6 +144,12 @@ class GeneralTab extends ImmutableComponent {
)
})
+ const spellCheckLangOptions = this.props.languageCodes.map(function (lc) {
+ return (
+ { value: lc, label: locale.translation(lc) }
+ )
+ })
+
const homepage = getSetting(settings.HOMEPAGE, this.props.settings)
const disableShowHomeButton = !homepage || !homepage.length
const defaultLanguage = this.props.languageCodes.find((lang) => lang.includes(navigator.language)) || 'en-US'
@@ -148,6 +162,18 @@ class GeneralTab extends ImmutableComponent {
onClick={this.setAsDefaultBrowser}
/>
+ const spellCheckLanguages = getSetting(settings.SPELLCHECK_ENABLED, this.props.settings)
+ ?
+
+
+ : null
const defaultZoomSetting = getSetting(settings.DEFAULT_ZOOM_LEVEL, this.props.settings)
return
@@ -218,6 +244,9 @@ class GeneralTab extends ImmutableComponent {
{languageOptions}
+
+ {spellCheckLanguages}
.Select-control {
+ background-color: #f9f9f9;
+}
+.Select.is-disabled > .Select-control:hover {
+ box-shadow: none;
+}
+.Select.is-disabled .Select-arrow-zone {
+ cursor: default;
+ pointer-events: none;
+ opacity: 0.35;
+}
+.Select-control {
+ background-color: #fff;
+ border-color: #d9d9d9 #ccc #b3b3b3;
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ color: #333;
+ cursor: default;
+ display: table !important;
+ border-spacing: 0;
+ border-collapse: separate;
+ height: 36px;
+ outline: none;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+
+ // fork
+ border-radius: 4px;
+ box-sizing: border-box;
+ color: rgb(68, 68, 68);
+ font-size: 14.5px;
+ box-shadow: 0 2px 8px -5px rgba(0, 0, 0, 1);
+ border: solid 1px rgba(0, 0, 0, 0.2);
+ outline: none;
+ padding: 3px 0;
+ width: 280px;
+ background-color: #fbfbfb;
+ background-size: 12px 12px;
+ -webkit-appearance: none;
+ max-width: 100%;
+}
+
+.Select .Select--multi {
+ max-width: 280px;
+}
+
+.Select-item {
+ display: inline-block;
+ vertical-align: middle;
+ background-color: #FFEDE0;
+ border-radius: 2px;
+ border: 1px solid rgb(255, 80, 0);
+ color: rgb(255, 80, 0);
+ display: inline-block;
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin: 3px 0 3px 6px;
+ vertical-align: top;
+}
+
+.Select-item .Select-item-icon {
+ margin: 0 !important;
+ padding: 0 6px !important;
+ vertical-align: middle !important;
+ display: inline-block !important;
+ font-size: 14px !important;
+ margin: -1px 0 0 0 !important;
+ color: rgb(255, 80, 0) !important;
+}
+
+.Select-item .Select-item-label {
+ border-left: 1px solid rgb(255, 80, 0);
+ padding: 0 4px !important;
+ margin: 0 !important;
+ vertical-align: middle !important;
+ display: inline-block !important;
+ line-height: 22px;
+ color: rgb(255, 80, 0) !important;
+}
+
+.Select-clear-zone, .Select-clear, .Select-arrow-zone, .Select-arrow {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.Select-control:hover {
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
+}
+.Select-control .Select-input:focus {
+ outline: none;
+}
+.is-searchable.is-open > .Select-control {
+ cursor: text;
+}
+.is-open > .Select-control {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ background: #fff;
+ border-color: #b3b3b3 #ccc #d9d9d9;
+}
+.is-open > .Select-control .Select-arrow {
+ top: -2px;
+ border-color: transparent transparent #888;
+ border-width: 0 7px 6px;
+}
+.is-searchable.is-focused:not(.is-open) > .Select-control {
+ cursor: text;
+}
+.is-focused:not(.is-open) > .Select-control {
+ border-color: rgb(255, 80, 0);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px #FFEDE0;
+}
+.Select-placeholder,
+.Select--single > .Select-control .Select-value {
+ bottom: 0;
+ color: #aaa;
+ left: 0;
+ line-height: 34px;
+ padding-left: 10px;
+ padding-right: 10px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.has-value.is-clearable.Select--single > .Select-control .Select-value {
+ padding-right: 42px;
+}
+.has-value.Select--single > .Select-control .Select-value .Select-value-label,
+.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value .Select-value-label {
+ color: #333;
+}
+.has-value.Select--single > .Select-control .Select-value a.Select-value-label,
+.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label {
+ cursor: pointer;
+ text-decoration: none;
+}
+.has-value.Select--single > .Select-control .Select-value a.Select-value-label:hover,
+.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:hover,
+.has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus,
+.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:focus {
+ color: rgb(255, 80, 0);
+ outline: none;
+ text-decoration: underline;
+}
+.Select-input {
+ margin-top: 7px;
+ // height: 34px;
+ padding-left: 10px;
+ padding-right: 10px;
+ vertical-align: middle;
+}
+.Select-input > input {
+ width: 100%;
+ background: none transparent;
+ border: 0 none;
+ box-shadow: none;
+ cursor: default;
+ display: inline-block !important;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ outline: none;
+ line-height: 14px;
+}
+.is-focused .Select-input > input {
+ cursor: text;
+}
+.has-value.is-pseudo-focused .Select-input {
+ opacity: 0;
+}
+.Select-control:not(.is-searchable) > .Select-input {
+ outline: none;
+}
+.Select-loading-zone {
+ cursor: pointer;
+ display: table-cell !important;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+}
+.Select-loading {
+ animation: Select-animation-spin 400ms infinite linear;
+ width: 16px;
+ height: 16px;
+ box-sizing: border-box;
+ border-radius: 50%;
+ border: 2px solid #ccc;
+ border-right-color: #333;
+ display: inline-block !important;
+ position: relative;
+ vertical-align: middle;
+}
+.Select-clear-zone {
+ animation: Select-animation-fadeIn 200ms;
+ color: #999;
+ cursor: pointer;
+ display: table-cell !important;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 17px;
+}
+.Select-clear-zone:hover {
+ color: #D0021B;
+}
+.Select-clear {
+ display: inline-block !important;
+ font-size: 18px;
+ line-height: 1;
+}
+.Select--multi .Select-clear-zone {
+ width: 17px;
+}
+.Select-arrow-zone {
+ cursor: pointer;
+ display: table-cell !important;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 25px;
+ padding-right: 5px;
+}
+.Select-arrow {
+ border-color: #888 transparent transparent;
+ border-style: solid;
+ border-width: 7px 6px 0;
+ display: inline-block !important;
+ height: 0;
+ width: 0;
+ position: relative;
+}
+.is-open .Select-arrow,
+.Select-arrow-zone:hover > .Select-arrow {
+ border-top-color: #666;
+}
+.Select--multi .Select-multi-value-wrapper {
+ display: inline-block !important;
+}
+.Select .Select-aria-only {
+ display: inline-block !important;
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ clip: rect(0, 0, 0, 0);
+ overflow: hidden;
+ float: left;
+}
+@-webkit-keyframes Select-animation-fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+@keyframes Select-animation-fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+.Select-menu-outer {
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ background-color: #fff;
+ // border: 1px solid #ccc;
+ border-top-color: #e6e6e6;
+ // box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
+ box-shadow: 0 2px 8px -5px rgba(0, 0, 0, 1);
+ border: solid 1px rgba(0, 0, 0, 0.2);
+ outline: none;
+ box-sizing: border-box;
+ margin-top: -1px;
+ max-height: 200px;
+ position: absolute;
+ top: 100%;
+ width: 100%;
+ max-width: 280px;
+ z-index: 1;
+ -webkit-overflow-scrolling: touch;
+ font-size: 0.9em;
+}
+.Select-menu {
+ max-height: 198px;
+ overflow-y: auto;
+}
+.Select-option {
+ box-sizing: border-box;
+ background-color: #fff;
+ color: #666666;
+ cursor: pointer;
+ display: block !important;
+ padding: 8px 10px;
+}
+.Select-option:last-child {
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+.Select-option.is-selected {
+ background-color: #FFEDE0;
+ color: #333;
+}
+.Select-option.is-focused {
+ background-color: #FFEDE0;
+ color: #333;
+}
+.Select-option.is-disabled {
+ color: #cccccc;
+ cursor: default;
+}
+.Select-noresults {
+ box-sizing: border-box;
+ color: #999999;
+ cursor: default;
+ display: block !important;
+ padding: 8px 10px;
+}
+.Select--multi .Select-input {
+ vertical-align: middle;
+ margin-left: 10px;
+ padding: 0;
+}
+.Select--multi.has-value .Select-input {
+ margin-left: 5px;
+}
+.Select--multi .Select-value {
+ background-color: #ebf5ff;
+ border-radius: 2px;
+ border: 1px solid #c2e0ff;
+ color: rgb(255, 80, 0);
+ display: inline-block !important;
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin-left: 5px;
+ margin-top: 5px;
+ vertical-align: top;
+}
+.Select--multi .Select-value-icon,
+.Select--multi .Select-value-label {
+ display: inline-block !important;
+ vertical-align: middle;
+}
+.Select--multi .Select-value-label {
+ border-bottom-right-radius: 2px;
+ border-top-right-radius: 2px;
+ cursor: default;
+ padding: 2px 5px;
+}
+.Select--multi a.Select-value-label {
+ color: rgb(255, 80, 0);
+ cursor: pointer;
+ text-decoration: none;
+}
+.Select--multi a.Select-value-label:hover {
+ text-decoration: underline;
+}
+.Select--multi .Select-value-icon {
+ cursor: pointer;
+ border-bottom-left-radius: 2px;
+ border-top-left-radius: 2px;
+ border-right: 1px solid #c2e0ff;
+ padding: 1px 5px 3px;
+}
+.Select--multi .Select-value-icon:hover,
+.Select--multi .Select-value-icon:focus {
+ background-color: rgba(0, 113, 230, 0.08);
+ color: #0071e6;
+}
+.Select--multi .Select-value-icon:active {
+ background-color: #c2e0ff;
+}
+.Select--multi.is-disabled .Select-value {
+ background-color: #fcfcfc;
+ border: 1px solid #e3e3e3;
+ color: #333;
+}
+.Select--multi.is-disabled .Select-value-icon {
+ cursor: not-allowed;
+ border-right: 1px solid #e3e3e3;
+}
+.Select--multi.is-disabled .Select-value-icon:hover,
+.Select--multi.is-disabled .Select-value-icon:focus,
+.Select--multi.is-disabled .Select-value-icon:active {
+ background-color: #fcfcfc;
+}
+@keyframes Select-animation-spin {
+ to {
+ transform: rotate(1turn);
+ }
+}
+@-webkit-keyframes Select-animation-spin {
+ to {
+ -webkit-transform: rotate(1turn);
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 78e130e4329..742735c453d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2889,6 +2889,11 @@
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
"integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A=="
},
+ "classnames": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
+ "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
+ },
"cli-boxes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
@@ -15706,6 +15711,20 @@
"prop-types": "15.6.0"
}
},
+ "react-input-autosize": {
+ "version": "0.6.13",
+ "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-0.6.13.tgz",
+ "integrity": "sha1-OG/3qdLD3AFsJlvy5Z05cFD2Wvc="
+ },
+ "react-select": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-0.9.1.tgz",
+ "integrity": "sha1-4yKi0KBjlqSCBrBVPfXsR9Fgg7o=",
+ "requires": {
+ "classnames": "2.2.5",
+ "react-input-autosize": "0.6.13"
+ }
+ },
"react-test-renderer": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-15.6.2.tgz",
diff --git a/package.json b/package.json
index d5bd2803dac..103d7ec6e3a 100644
--- a/package.json
+++ b/package.json
@@ -119,6 +119,7 @@
"react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.5.4",
"snazzy": "^7.0.0",
+ "react-select": "^0.9.1",
"string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0",
"tablesort": "5.0.1",
diff --git a/test/unit/about/preferencesTest.js b/test/unit/about/preferencesTest.js
index 81dc9f99068..de8904457d1 100644
--- a/test/unit/about/preferencesTest.js
+++ b/test/unit/about/preferencesTest.js
@@ -8,10 +8,13 @@ const {mount} = require('enzyme')
const sinon = require('sinon')
const assert = require('assert')
const fakeElectron = require('../lib/fakeElectron')
+const fakeComponent = require('../lib/fakeComponent')
let Preferences, appActions, SettingItemIcon
require('../braveUnit')
describe('Preferences component unittest', function () {
+ let translationSpy
+ let fakeLocale
before(function () {
mockery.enable({
warnOnReplace: false,
@@ -22,6 +25,7 @@ describe('Preferences component unittest', function () {
mockery.registerMock('../../less/about/preferences.less', {})
mockery.registerMock('../../less/forms.less', {})
mockery.registerMock('../../less/button.less', {})
+ mockery.registerMock('../../less/react-select.less', {})
mockery.registerMock('../../node_modules/font-awesome/css/font-awesome.css', {})
mockery.registerMock('../../../extensions/brave/img/caret_down_grey.svg')
mockery.registerMock('../../../extensions/brave/img/preferences/browser_prefs_general.svg', 'browser_prefs_general.svg')
@@ -62,6 +66,24 @@ describe('Preferences component unittest', function () {
// Mock image from addFundsDialogFooter
mockery.registerMock('../../../../../extensions/brave/img/ledger/uphold_logo_medium.png')
+ mockery.registerMock('electron', fakeElectron)
+
+ fakeLocale = {
+ translation: (msg, arg) => {
+ let retMsg = ''
+ switch (msg) {
+ case 'spellCheckLanguages':
+ retMsg += 'spellCheckLanguages'
+ break
+ }
+ return retMsg
+ }
+ }
+ translationSpy = sinon.spy(fakeLocale, 'translation')
+ mockery.registerMock('../../js/l10n', fakeLocale)
+
+ mockery.registerMock('react-select', fakeComponent)
+
window.chrome = fakeElectron
window.CustomEvent = {}
@@ -70,6 +92,7 @@ describe('Preferences component unittest', function () {
appActions = require('../../../js/actions/appActions')
})
after(function () {
+ translationSpy.restore()
mockery.disable()
})
diff --git a/test/unit/app/browser/reducers/spellCheckerReducerTest.js b/test/unit/app/browser/reducers/spellCheckerReducerTest.js
index 84174bae5ad..be8596bddc5 100644
--- a/test/unit/app/browser/reducers/spellCheckerReducerTest.js
+++ b/test/unit/app/browser/reducers/spellCheckerReducerTest.js
@@ -6,15 +6,21 @@ const assert = require('assert')
const fakeElectron = require('../../../lib/fakeElectron')
const appConstants = require('../../../../../js/constants/appConstants')
+const settings = require('../../../../../js/constants/settings')
require('../../../braveUnit')
describe('spellCheckerReducer unit tests', function () {
let spellCheckerReducer
- let fakeWebContentsCache, fakeWebContents, fakeSpellChecker
- let getWebContentsSpy, replaceMisspellingSpy, replaceSpy, addWordSpy, removeWordSpy
+ let fakeWebContentsCache, fakeWebContents, fakeSpellChecker, fakeSettings, fakeUserPrefs
+ let getWebContentsSpy, replaceMisspellingSpy, replaceSpy, addWordSpy, removeWordSpy,
+ setUserPrefSpy, getSettingSpy
+ let spellCheckEnabled
const dictionaryWord = 'braave'
const dictionarySuggestion = 'brave'
const tabId = 111
+ const enabledPref = 'browser.enable_spellchecking'
+ const dictsPref = 'spellcheck.dictionaries'
+ const dicts = ['en-US', 'fr-FR']
before(function () {
mockery.enable({
@@ -40,15 +46,34 @@ describe('spellCheckerReducer unit tests', function () {
}
}
+ fakeSettings = {
+ getSetting: (settingKey, settingsCollection) => {
+ switch (settingKey) {
+ case settings.SPELLCHECK_ENABLED:
+ return spellCheckEnabled
+ case settings.SPELLCHECK_LANGUAGES:
+ return dicts
+ }
+ }
+ }
+
+ fakeUserPrefs = {
+ setUserPref: (path, value, incognito = false) => {}
+ }
+
getWebContentsSpy = sinon.spy(fakeWebContentsCache, 'getWebContents')
replaceMisspellingSpy = sinon.spy(fakeWebContents, 'replaceMisspelling')
replaceSpy = sinon.spy(fakeWebContents, 'replace')
addWordSpy = sinon.spy(fakeSpellChecker, 'addWord')
removeWordSpy = sinon.spy(fakeSpellChecker, 'removeWord')
+ setUserPrefSpy = sinon.spy(fakeUserPrefs, 'setUserPref')
+ getSettingSpy = sinon.spy(fakeSettings, 'getSetting')
mockery.registerMock('electron', fakeElectron)
mockery.registerMock('../webContentsCache', fakeWebContentsCache)
mockery.registerMock('../../spellChecker', fakeSpellChecker)
+ mockery.registerMock('../../../js/state/userPrefs', fakeUserPrefs)
+ mockery.registerMock('../../../js/settings', fakeSettings)
spellCheckerReducer = require('../../../../../app/browser/reducers/spellCheckerReducer')
})
@@ -126,4 +151,127 @@ describe('spellCheckerReducer unit tests', function () {
assert(replaceSpy.withArgs(dictionaryWord).calledOnce)
})
})
+
+ describe('APP_WINDOW_CREATED', function () {
+ describe('APP_WINDOW_CREATED with enabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = true
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_WINDOW_CREATED
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, true).calledOnce)
+ })
+ it('calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).calledOnce)
+ })
+ })
+ describe('APP_WINDOW_CREATED with disabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = false
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_WINDOW_CREATED
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, false).calledOnce)
+ })
+ it('not calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).notCalled)
+ })
+ })
+ })
+
+ describe('APP_CHANGE_SETTING', function () {
+ describe('SPELLCHECK_ENABLED with enabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = true
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_CHANGE_SETTING,
+ key: settings.SPELLCHECK_ENABLED
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, true).calledOnce)
+ })
+ it('calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).calledOnce)
+ })
+ })
+ describe('SPELLCHECK_ENABLED with disabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = false
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_CHANGE_SETTING,
+ key: settings.SPELLCHECK_ENABLED
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, false).calledOnce)
+ })
+ it('not calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).notCalled)
+ })
+ })
+ describe('SPELLCHECK_LANGUAGES with enabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = true
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_CHANGE_SETTING,
+ key: settings.SPELLCHECK_LANGUAGES
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, true).calledOnce)
+ })
+ it('calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).calledOnce)
+ })
+ })
+ describe('SPELLCHECK_LANGUAGES with disabled', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = false
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_CHANGE_SETTING,
+ key: settings.SPELLCHECK_LANGUAGES
+ }))
+ })
+ it('calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, false).calledOnce)
+ })
+ it('not calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).notCalled)
+ })
+ })
+ describe('other settings', function () {
+ before(function () {
+ getSettingSpy.reset()
+ setUserPrefSpy.reset()
+ spellCheckEnabled = false
+ spellCheckerReducer(Immutable.Map(), Immutable.fromJS({
+ actionType: appConstants.APP_CHANGE_SETTING,
+ key: 'other-settings'
+ }))
+ })
+ it('not calls setUserPref to set enabledPref to true', function () {
+ assert(setUserPrefSpy.withArgs(enabledPref, false).notCalled)
+ })
+ it('not calls setUserPref to set dictionaries', function () {
+ assert(setUserPrefSpy.withArgs(dictsPref, dicts).notCalled)
+ })
+ })
+ })
})
diff --git a/test/unit/lib/fakeComponent.js b/test/unit/lib/fakeComponent.js
new file mode 100644
index 00000000000..4628b21a2f5
--- /dev/null
+++ b/test/unit/lib/fakeComponent.js
@@ -0,0 +1,10 @@
+const React = require('react')
+
+const fakeComponent =
+ class fakeComponent extends React.Component {
+ render () {
+ return null
+ }
+ }
+
+module.exports = fakeComponent