diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 997883fd6..881af2985 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -969,6 +969,14 @@ "message": "Allow access to camera and microphone", "description": "Description of the media permission description" }, + "spellCheck": { + "message": "Spell Check", + "description": "Description of the media permission description" + }, + "spellCheckDescription": { + "message": "Enable spell check of text entered in message composition box", + "description": "Description of the media permission description" + }, "clearDataHeader": { "message": "Clear Data", "description": diff --git a/js/background.js b/js/background.js index 8f3b31546..4cda02f50 100644 --- a/js/background.js +++ b/js/background.js @@ -160,6 +160,12 @@ getAudioNotification: () => storage.get('audio-notification'), setAudioNotification: value => storage.put('audio-notification', value), + getSpellCheck: () => storage.get('spell-check', true), + setSpellCheck: value => { + storage.put('spell-check', value); + startSpellCheck(); + }, + // eslint-disable-next-line eqeqeq isPrimary: () => textsecure.storage.user.getDeviceId() == '1', getSyncRequest: () => @@ -185,6 +191,15 @@ }, }; + const startSpellCheck = () => { + if (window.Events.getSpellCheck()) { + window.enableSpellCheck(); + } else { + window.disableSpellCheck(); + } + }; + startSpellCheck(); + const themeSetting = window.Events.getThemeSetting(); const newThemeSetting = mapOldThemeToNew(themeSetting); window.Events.setThemeSetting(newThemeSetting); diff --git a/js/settings_start.js b/js/settings_start.js index 0275e2e4a..c81438577 100644 --- a/js/settings_start.js +++ b/js/settings_start.js @@ -21,6 +21,8 @@ const getInitialData = async () => ({ notificationSetting: await window.getNotificationSetting(), audioNotification: await window.getAudioNotification(), + spellCheck: await window.getSpellCheck(), + mediaPermissions: await window.getMediaPermissions(), isPrimary: await window.isPrimary(), diff --git a/js/spell_check.js b/js/spell_check.js index b2b4814e6..e36e82328 100644 --- a/js/spell_check.js +++ b/js/spell_check.js @@ -118,17 +118,39 @@ const simpleChecker = { }, }; +const dummyChecker = { + spellCheck() { + return true; + }, + isMisspelled() { + return false; + }, + getSuggestions() { + return []; + }, + add() { + // nothing + }, +}; + window.spellChecker = simpleChecker; +window.disableSpellCheck = () => { + window.removeEventListener('contextmenu', spellCheckHandler); + webFrame.setSpellCheckProvider('en-US', false, dummyChecker); +}; -webFrame.setSpellCheckProvider( - 'en-US', - // Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371 - // The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too. - true, - simpleChecker -); +window.enableSpellCheck = () => { + webFrame.setSpellCheckProvider( + 'en-US', + // Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371 + // The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too. + true, + simpleChecker + ); + window.addEventListener('contextmenu', spellCheckHandler); +}; -window.addEventListener('contextmenu', e => { +const spellCheckHandler = e => { // Only show the context menu in text editors. if (!e.target.closest('textarea, input, [contenteditable="true"]')) { return; @@ -150,4 +172,4 @@ window.addEventListener('contextmenu', e => { setTimeout(() => { menu.popup(remote.getCurrentWindow()); }, 30); -}); +}; diff --git a/js/views/settings_view.js b/js/views/settings_view.js index 6b15ae969..a9e7c0f3c 100644 --- a/js/views/settings_view.js +++ b/js/views/settings_view.js @@ -98,6 +98,11 @@ setFn: window.setAudioNotification, }); } + new CheckboxView({ + el: this.$('.spell-check-setting'), + value: window.initialData.spellCheck, + setFn: window.setSpellCheck, + }); new CheckboxView({ el: this.$('.menu-bar-setting'), value: window.initialData.hideMenuBar, @@ -139,6 +144,8 @@ clearDataExplanation: i18n('clearDataExplanation'), permissions: i18n('permissions'), mediaPermissionsDescription: i18n('mediaPermissionsDescription'), + spellCheckHeader: i18n('spellCheck'), + spellCheckDescription: i18n('spellCheckDescription'), }; }, onClose() { diff --git a/main.js b/main.js index 589c56703..a4b72be4e 100644 --- a/main.js +++ b/main.js @@ -808,6 +808,9 @@ installSettingsSetter('notification-setting'); installSettingsGetter('audio-notification'); installSettingsSetter('audio-notification'); +installSettingsGetter('spell-check'); +installSettingsSetter('spell-check'); + // This one is different because its single source of truth is userConfig, not IndexedDB ipc.on('get-media-permissions', event => { event.sender.send( diff --git a/preload.js b/preload.js index 4325a97f0..a61052b6b 100644 --- a/preload.js +++ b/preload.js @@ -112,6 +112,9 @@ installSetter('notification-setting', 'setNotificationSetting'); installGetter('audio-notification', 'getAudioNotification'); installSetter('audio-notification', 'setAudioNotification'); +installGetter('spell-check', 'getSpellCheck'); +installSetter('spell-check', 'setSpellCheck'); + window.getMediaPermissions = () => new Promise((resolve, reject) => { ipc.once('get-success-media-permissions', (_event, error, value) => { diff --git a/settings.html b/settings.html index 39cc6f9fd..aeb88c13a 100644 --- a/settings.html +++ b/settings.html @@ -87,6 +87,12 @@ {{ /isAudioNotificationSupported }}