diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-02-12 20:42:21 +0100 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-02-12 20:42:21 +0100 |
commit | 2d5a303b1cc559339c3ecc7a176e9d8bd66755ef (patch) | |
tree | 2a97c1db68a72b8e208bd177af7faad5df2c9f48 /src/webview | |
parent | feat(Windows): Add option to quit Franz from Taskbar icon (diff) | |
parent | feat(Spell checking): Add option to automatically detect language (diff) | |
download | ferdium-app-2d5a303b1cc559339c3ecc7a176e9d8bd66755ef.tar.gz ferdium-app-2d5a303b1cc559339c3ecc7a176e9d8bd66755ef.tar.zst ferdium-app-2d5a303b1cc559339c3ecc7a176e9d8bd66755ef.zip |
Merge branch 'feature/spellcheck-autodetect' into develop
Diffstat (limited to 'src/webview')
-rw-r--r-- | src/webview/contextMenu.js | 12 | ||||
-rw-r--r-- | src/webview/recipe.js | 56 | ||||
-rw-r--r-- | src/webview/spellchecker.js | 11 |
3 files changed, 75 insertions, 4 deletions
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index a76c03e5a..afb1d8912 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -207,8 +207,6 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
207 | }); | 207 | }); |
208 | }); | 208 | }); |
209 | 209 | ||
210 | console.log('isSpellcheckEnabled', isSpellcheckEnabled); | ||
211 | |||
212 | menuTpl.push({ | 210 | menuTpl.push({ |
213 | type: 'separator', | 211 | type: 'separator', |
214 | }, { | 212 | }, { |
@@ -234,6 +232,16 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
234 | }, | 232 | }, |
235 | }, | 233 | }, |
236 | { | 234 | { |
235 | id: 'automaticDetection', | ||
236 | label: 'Automatic language detection', | ||
237 | type: 'radio', | ||
238 | checked: spellcheckerLanguage === 'automatic', | ||
239 | click() { | ||
240 | debug('Detect language automatically'); | ||
241 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'automatic'); | ||
242 | }, | ||
243 | }, | ||
244 | { | ||
237 | type: 'separator', | 245 | type: 'separator', |
238 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, | 246 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, |
239 | }, | 247 | }, |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index c718b348e..1e5d74b1f 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -1,10 +1,12 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import path from 'path'; | 2 | import path from 'path'; |
3 | import { autorun, computed, observable } from 'mobx'; | 3 | import { autorun, computed, observable } from 'mobx'; |
4 | import { loadModule } from 'cld3-asm'; | ||
5 | import { debounce } from 'lodash'; | ||
4 | 6 | ||
5 | import RecipeWebview from './lib/RecipeWebview'; | 7 | import RecipeWebview from './lib/RecipeWebview'; |
6 | 8 | ||
7 | import spellchecker, { switchDict, disable as disableSpellchecker } from './spellchecker'; | 9 | import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; |
8 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; | 10 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; |
9 | import contextMenu from './contextMenu'; | 11 | import contextMenu from './contextMenu'; |
10 | import './notifications'; | 12 | import './notifications'; |
@@ -40,6 +42,8 @@ class RecipeController { | |||
40 | return this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage; | 42 | return this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage; |
41 | } | 43 | } |
42 | 44 | ||
45 | cldIdentifier = null; | ||
46 | |||
43 | async initialize() { | 47 | async initialize() { |
44 | Object.keys(this.ipcEvents).forEach((channel) => { | 48 | Object.keys(this.ipcEvents).forEach((channel) => { |
45 | ipcRenderer.on(channel, (...args) => { | 49 | ipcRenderer.on(channel, (...args) => { |
@@ -87,10 +91,22 @@ class RecipeController { | |||
87 | 91 | ||
88 | if (this.settings.app.enableSpellchecking) { | 92 | if (this.settings.app.enableSpellchecking) { |
89 | debug('Setting spellchecker language to', this.spellcheckerLanguage); | 93 | debug('Setting spellchecker language to', this.spellcheckerLanguage); |
90 | switchDict(this.spellcheckerLanguage); | 94 | let { spellcheckerLanguage } = this; |
95 | if (spellcheckerLanguage === 'automatic') { | ||
96 | this.automaticLanguageDetection(); | ||
97 | debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale); | ||
98 | spellcheckerLanguage = this.settings.app.locale; | ||
99 | } else if (this.cldIdentifier) { | ||
100 | this.cldIdentifier.destroy(); | ||
101 | } | ||
102 | switchDict(spellcheckerLanguage); | ||
91 | } else { | 103 | } else { |
92 | debug('Disable spellchecker'); | 104 | debug('Disable spellchecker'); |
93 | disableSpellchecker(); | 105 | disableSpellchecker(); |
106 | |||
107 | if (this.cldIdentifier) { | ||
108 | this.cldIdentifier.destroy(); | ||
109 | } | ||
94 | } | 110 | } |
95 | 111 | ||
96 | if (this.settings.service.isDarkModeEnabled) { | 112 | if (this.settings.service.isDarkModeEnabled) { |
@@ -113,6 +129,42 @@ class RecipeController { | |||
113 | serviceIdEcho(event) { | 129 | serviceIdEcho(event) { |
114 | event.sender.send('service-id', this.settings.service.id); | 130 | event.sender.send('service-id', this.settings.service.id); |
115 | } | 131 | } |
132 | |||
133 | async automaticLanguageDetection() { | ||
134 | const cldFactory = await loadModule(); | ||
135 | this.cldIdentifier = cldFactory.create(0, 1000); | ||
136 | |||
137 | window.addEventListener('keyup', debounce((e) => { | ||
138 | const element = e.target; | ||
139 | |||
140 | console.log(element); | ||
141 | |||
142 | if (!element) return; | ||
143 | |||
144 | let value = ''; | ||
145 | if (element.isContentEditable) { | ||
146 | value = element.textContent; | ||
147 | } else if (element.value) { | ||
148 | value = element.value; | ||
149 | } | ||
150 | |||
151 | // Force a minimum length to get better detection results | ||
152 | if (value.length < 30) return; | ||
153 | |||
154 | debug('Detecting language for', value); | ||
155 | const findResult = this.cldIdentifier.findLanguage(value); | ||
156 | |||
157 | debug('Language detection result', findResult); | ||
158 | |||
159 | if (findResult.is_reliable) { | ||
160 | const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(findResult.language); | ||
161 | debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale); | ||
162 | if (spellcheckerLocale) { | ||
163 | switchDict(spellcheckerLocale); | ||
164 | } | ||
165 | } | ||
166 | }, 225)); | ||
167 | } | ||
116 | } | 168 | } |
117 | 169 | ||
118 | /* eslint-disable no-new */ | 170 | /* eslint-disable no-new */ |
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index becaed449..9158b3b94 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -3,6 +3,7 @@ import { SpellCheckerProvider } from 'electron-hunspell'; | |||
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | 4 | ||
5 | import { DICTIONARY_PATH } from '../config'; | 5 | import { DICTIONARY_PATH } from '../config'; |
6 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | ||
6 | 7 | ||
7 | const debug = require('debug')('Franz:spellchecker'); | 8 | const debug = require('debug')('Franz:spellchecker'); |
8 | 9 | ||
@@ -82,3 +83,13 @@ export function disable() { | |||
82 | currentDict = null; | 83 | currentDict = null; |
83 | } | 84 | } |
84 | } | 85 | } |
86 | |||
87 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { | ||
88 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
89 | |||
90 | if (locales.length >= 1) { | ||
91 | return locales[0]; | ||
92 | } | ||
93 | |||
94 | return null; | ||
95 | } | ||