diff options
Diffstat (limited to 'src/webview')
-rw-r--r-- | src/webview/contextMenu.js | 12 | ||||
-rw-r--r-- | src/webview/contextMenuBuilder.js | 48 | ||||
-rw-r--r-- | src/webview/recipe.js | 61 | ||||
-rw-r--r-- | src/webview/spellchecker.js | 73 |
4 files changed, 86 insertions, 108 deletions
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index d04f90830..679bf5aaa 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -3,12 +3,18 @@ import ContextMenuBuilder from './contextMenuBuilder'; | |||
3 | 3 | ||
4 | const webContents = remote.getCurrentWebContents(); | 4 | const webContents = remote.getCurrentWebContents(); |
5 | 5 | ||
6 | export default async function setupContextMenu() { | 6 | export default async function setupContextMenu(isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { |
7 | const contextMenuBuilder = new ContextMenuBuilder( | 7 | const contextMenuBuilder = new ContextMenuBuilder( |
8 | webContents, | 8 | webContents, |
9 | ); | 9 | ); |
10 | 10 | ||
11 | webContents.on('context-menu', (event, params) => { | 11 | webContents.on('context-menu', (e, props) => { |
12 | contextMenuBuilder.showPopupMenu(params); | 12 | // TODO?: e.preventDefault(); |
13 | contextMenuBuilder.showPopupMenu( | ||
14 | props, | ||
15 | isSpellcheckEnabled(), | ||
16 | getDefaultSpellcheckerLanguage(), | ||
17 | getSpellcheckerLanguage(), | ||
18 | ); | ||
13 | }); | 19 | }); |
14 | } | 20 | } |
diff --git a/src/webview/contextMenuBuilder.js b/src/webview/contextMenuBuilder.js index 8adc3b432..d99dd6739 100644 --- a/src/webview/contextMenuBuilder.js +++ b/src/webview/contextMenuBuilder.js | |||
@@ -21,19 +21,19 @@ function matchesWord(string) { | |||
21 | } | 21 | } |
22 | 22 | ||
23 | const contextMenuStringTable = { | 23 | const contextMenuStringTable = { |
24 | copyMail: () => 'Copy Email Address', | ||
25 | copyLinkUrl: () => 'Copy Link', | ||
26 | openLinkUrl: () => 'Open Link', | ||
27 | copyImageUrl: () => 'Copy Image URL', | ||
28 | copyImage: () => 'Copy Image', | ||
29 | addToDictionary: () => 'Add to Dictionary', | ||
30 | lookUpDefinition: ({ word }) => `Look Up "${word}"`, | 24 | lookUpDefinition: ({ word }) => `Look Up "${word}"`, |
31 | searchGoogle: () => 'Search with Google', | ||
32 | cut: () => 'Cut', | 25 | cut: () => 'Cut', |
33 | copy: () => 'Copy', | 26 | copy: () => 'Copy', |
34 | paste: () => 'Paste', | 27 | paste: () => 'Paste', |
35 | inspectElement: () => 'Inspect Element', | 28 | searchGoogle: () => 'Search with Google', |
29 | openLinkUrl: () => 'Open Link', | ||
30 | copyLinkUrl: () => 'Copy Link', | ||
31 | copyImageUrl: () => 'Copy Image Address', | ||
32 | copyImage: () => 'Copy Image', | ||
33 | addToDictionary: () => 'Add to Dictionary', | ||
36 | goToHomePage: () => 'Go to Home Page', | 34 | goToHomePage: () => 'Go to Home Page', |
35 | copyMail: () => 'Copy Email Address', | ||
36 | inspectElement: () => 'Inspect Element', | ||
37 | }; | 37 | }; |
38 | 38 | ||
39 | /** | 39 | /** |
@@ -225,13 +225,13 @@ module.exports = class ContextMenuBuilder { | |||
225 | * if so, adds suggested spellings as individual menu items. | 225 | * if so, adds suggested spellings as individual menu items. |
226 | */ | 226 | */ |
227 | addSpellingItems(menu, menuInfo) { | 227 | addSpellingItems(menu, menuInfo) { |
228 | const target = this.getWebContents(); | 228 | const webContents = this.getWebContents(); |
229 | // Add each spelling suggestion | 229 | // Add each spelling suggestion |
230 | for (const suggestion of menuInfo.dictionarySuggestions) { | 230 | for (const suggestion of menuInfo.dictionarySuggestions) { |
231 | menu.append(new MenuItem({ | 231 | menu.append(new MenuItem({ |
232 | label: suggestion, | 232 | label: suggestion, |
233 | // eslint-disable-next-line no-loop-func | 233 | // eslint-disable-next-line no-loop-func |
234 | click: () => target.replaceMisspelling(suggestion), | 234 | click: () => webContents.replaceMisspelling(suggestion), |
235 | })); | 235 | })); |
236 | } | 236 | } |
237 | 237 | ||
@@ -240,7 +240,7 @@ module.exports = class ContextMenuBuilder { | |||
240 | menu.append( | 240 | menu.append( |
241 | new MenuItem({ | 241 | new MenuItem({ |
242 | label: 'Add to dictionary', | 242 | label: 'Add to dictionary', |
243 | click: () => target.session.addWordToSpellCheckerDictionary(menuInfo.misspelledWord), | 243 | click: () => webContents.session.addWordToSpellCheckerDictionary(menuInfo.misspelledWord), |
244 | }), | 244 | }), |
245 | ); | 245 | ); |
246 | } | 246 | } |
@@ -262,11 +262,11 @@ module.exports = class ContextMenuBuilder { | |||
262 | } | 262 | } |
263 | 263 | ||
264 | if (process.platform === 'darwin') { | 264 | if (process.platform === 'darwin') { |
265 | const target = this.getWebContents(); | 265 | const webContents = this.getWebContents(); |
266 | 266 | ||
267 | const lookUpDefinition = new MenuItem({ | 267 | const lookUpDefinition = new MenuItem({ |
268 | label: this.stringTable.lookUpDefinition({ word: menuInfo.selectionText.trim() }), | 268 | label: this.stringTable.lookUpDefinition({ word: menuInfo.selectionText.trim() }), |
269 | click: () => target.showDefinitionForSelection(), | 269 | click: () => webContents.showDefinitionForSelection(), |
270 | }); | 270 | }); |
271 | 271 | ||
272 | menu.append(lookUpDefinition); | 272 | menu.append(lookUpDefinition); |
@@ -316,12 +316,12 @@ module.exports = class ContextMenuBuilder { | |||
316 | * Adds the Cut menu item | 316 | * Adds the Cut menu item |
317 | */ | 317 | */ |
318 | addCut(menu, menuInfo) { | 318 | addCut(menu, menuInfo) { |
319 | const target = this.getWebContents(); | 319 | const webContents = this.getWebContents(); |
320 | menu.append(new MenuItem({ | 320 | menu.append(new MenuItem({ |
321 | label: this.stringTable.cut(), | 321 | label: this.stringTable.cut(), |
322 | accelerator: 'CommandOrControl+X', | 322 | accelerator: 'CommandOrControl+X', |
323 | enabled: menuInfo.editFlags.canCut, | 323 | enabled: menuInfo.editFlags.canCut, |
324 | click: () => target.cut(), | 324 | click: () => webContents.cut(), |
325 | })); | 325 | })); |
326 | 326 | ||
327 | return menu; | 327 | return menu; |
@@ -331,12 +331,12 @@ module.exports = class ContextMenuBuilder { | |||
331 | * Adds the Copy menu item. | 331 | * Adds the Copy menu item. |
332 | */ | 332 | */ |
333 | addCopy(menu, menuInfo) { | 333 | addCopy(menu, menuInfo) { |
334 | const target = this.getWebContents(); | 334 | const webContents = this.getWebContents(); |
335 | menu.append(new MenuItem({ | 335 | menu.append(new MenuItem({ |
336 | label: this.stringTable.copy(), | 336 | label: this.stringTable.copy(), |
337 | accelerator: 'CommandOrControl+C', | 337 | accelerator: 'CommandOrControl+C', |
338 | enabled: menuInfo.editFlags.canCopy, | 338 | enabled: menuInfo.editFlags.canCopy, |
339 | click: () => target.copy(), | 339 | click: () => webContents.copy(), |
340 | })); | 340 | })); |
341 | 341 | ||
342 | return menu; | 342 | return menu; |
@@ -346,12 +346,12 @@ module.exports = class ContextMenuBuilder { | |||
346 | * Adds the Paste menu item. | 346 | * Adds the Paste menu item. |
347 | */ | 347 | */ |
348 | addPaste(menu, menuInfo) { | 348 | addPaste(menu, menuInfo) { |
349 | const target = this.getWebContents(); | 349 | const webContents = this.getWebContents(); |
350 | menu.append(new MenuItem({ | 350 | menu.append(new MenuItem({ |
351 | label: this.stringTable.paste(), | 351 | label: this.stringTable.paste(), |
352 | accelerator: 'CommandOrControl+V', | 352 | accelerator: 'CommandOrControl+V', |
353 | enabled: menuInfo.editFlags.canPaste, | 353 | enabled: menuInfo.editFlags.canPaste, |
354 | click: () => target.paste(), | 354 | click: () => webContents.paste(), |
355 | })); | 355 | })); |
356 | 356 | ||
357 | return menu; | 357 | return menu; |
@@ -363,12 +363,12 @@ module.exports = class ContextMenuBuilder { | |||
363 | && !menuInfo.linkText | 363 | && !menuInfo.linkText |
364 | && !menuInfo.hasImageContents | 364 | && !menuInfo.hasImageContents |
365 | ) { | 365 | ) { |
366 | const target = this.getWebContents(); | 366 | const webContents = this.getWebContents(); |
367 | menu.append( | 367 | menu.append( |
368 | new MenuItem({ | 368 | new MenuItem({ |
369 | label: 'Paste as plain text', | 369 | label: 'Paste as plain text', |
370 | accelerator: 'CommandOrControl+Shift+V', | 370 | accelerator: 'CommandOrControl+Shift+V', |
371 | click: () => target.pasteAndMatchStyle(), | 371 | click: () => webContents.pasteAndMatchStyle(), |
372 | }), | 372 | }), |
373 | ); | 373 | ); |
374 | } | 374 | } |
@@ -386,13 +386,13 @@ module.exports = class ContextMenuBuilder { | |||
386 | * Adds the "Inspect Element" menu item. | 386 | * Adds the "Inspect Element" menu item. |
387 | */ | 387 | */ |
388 | addInspectElement(menu, menuInfo, needsSeparator = true) { | 388 | addInspectElement(menu, menuInfo, needsSeparator = true) { |
389 | const target = this.getWebContents(); | 389 | const webContents = this.getWebContents(); |
390 | if (!this.debugMode) return menu; | 390 | if (!this.debugMode) return menu; |
391 | if (needsSeparator) this.addSeparator(menu); | 391 | if (needsSeparator) this.addSeparator(menu); |
392 | 392 | ||
393 | const inspect = new MenuItem({ | 393 | const inspect = new MenuItem({ |
394 | label: this.stringTable.inspectElement(), | 394 | label: this.stringTable.inspectElement(), |
395 | click: () => target.inspectElement(menuInfo.x, menuInfo.y), | 395 | click: () => webContents.inspectElement(menuInfo.x, menuInfo.y), |
396 | }); | 396 | }); |
397 | 397 | ||
398 | menu.append(inspect); | 398 | menu.append(inspect); |
@@ -436,7 +436,7 @@ module.exports = class ContextMenuBuilder { | |||
436 | accelerator: 'CommandOrControl+Home', | 436 | accelerator: 'CommandOrControl+Home', |
437 | enabled: true, | 437 | enabled: true, |
438 | click: () => { | 438 | click: () => { |
439 | // target.loadURL(baseURL.origin); | 439 | // webContents.loadURL(baseURL.origin); |
440 | window.location.href = baseURL.origin; | 440 | window.location.href = baseURL.origin; |
441 | }, | 441 | }, |
442 | })); | 442 | })); |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 675f8e311..d29f3edbd 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -3,7 +3,6 @@ import { ipcRenderer, remote, desktopCapturer } from 'electron'; | |||
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | import { autorun, computed, observable } from 'mobx'; | 4 | import { autorun, computed, observable } from 'mobx'; |
5 | import fs from 'fs-extra'; | 5 | import fs from 'fs-extra'; |
6 | import { loadModule } from 'cld3-asm'; | ||
7 | import { debounce } from 'lodash'; | 6 | import { debounce } from 'lodash'; |
8 | import { FindInPage } from 'electron-find'; | 7 | import { FindInPage } from 'electron-find'; |
9 | 8 | ||
@@ -23,8 +22,9 @@ import customDarkModeCss from './darkmode/custom'; | |||
23 | import RecipeWebview from './lib/RecipeWebview'; | 22 | import RecipeWebview from './lib/RecipeWebview'; |
24 | import Userscript from './lib/Userscript'; | 23 | import Userscript from './lib/Userscript'; |
25 | 24 | ||
26 | import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; | 25 | import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; |
27 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; | 26 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; |
27 | import contextMenu from './contextMenu'; | ||
28 | import './notifications'; | 28 | import './notifications'; |
29 | 29 | ||
30 | import { DEFAULT_APP_SETTINGS } from '../config'; | 30 | import { DEFAULT_APP_SETTINGS } from '../config'; |
@@ -156,7 +156,14 @@ class RecipeController { | |||
156 | 156 | ||
157 | debug('Send "hello" to host'); | 157 | debug('Send "hello" to host'); |
158 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); | 158 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); |
159 | await spellchecker(); | 159 | |
160 | this.spellcheckingProvider = null; | ||
161 | contextMenu( | ||
162 | () => this.settings.app.enableSpellchecking, | ||
163 | () => this.settings.app.spellcheckerLanguage, | ||
164 | () => this.spellcheckerLanguage, | ||
165 | ); | ||
166 | |||
160 | autorun(() => this.update()); | 167 | autorun(() => this.update()); |
161 | 168 | ||
162 | document.addEventListener('DOMContentLoaded', () => { | 169 | document.addEventListener('DOMContentLoaded', () => { |
@@ -240,22 +247,15 @@ class RecipeController { | |||
240 | 247 | ||
241 | if (this.settings.app.enableSpellchecking) { | 248 | if (this.settings.app.enableSpellchecking) { |
242 | debug('Setting spellchecker language to', this.spellcheckerLanguage); | 249 | debug('Setting spellchecker language to', this.spellcheckerLanguage); |
243 | const { spellcheckerLanguage } = this; | 250 | let { spellcheckerLanguage } = this; |
244 | if (spellcheckerLanguage.includes('automatic')) { | 251 | if (spellcheckerLanguage.includes('automatic')) { |
245 | this.automaticLanguageDetection(); | 252 | this.automaticLanguageDetection(); |
246 | debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale); | 253 | debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale); |
247 | spellcheckerLanguage.push(this.settings.app.locale); | 254 | spellcheckerLanguage = this.settings.app.locale; |
248 | } else if (this.cldIdentifier) { | ||
249 | this.cldIdentifier.destroy(); | ||
250 | } | 255 | } |
251 | switchDict(spellcheckerLanguage); | 256 | switchDict(spellcheckerLanguage); |
252 | } else { | 257 | } else { |
253 | debug('Disable spellchecker'); | 258 | debug('Disable spellchecker'); |
254 | disableSpellchecker(); | ||
255 | |||
256 | if (this.cldIdentifier) { | ||
257 | this.cldIdentifier.destroy(); | ||
258 | } | ||
259 | } | 259 | } |
260 | 260 | ||
261 | if (!this.recipe) { | 261 | if (!this.recipe) { |
@@ -366,10 +366,7 @@ class RecipeController { | |||
366 | } | 366 | } |
367 | 367 | ||
368 | async automaticLanguageDetection() { | 368 | async automaticLanguageDetection() { |
369 | const cldFactory = await loadModule(); | 369 | window.addEventListener('keyup', debounce(async (e) => { |
370 | this.cldIdentifier = cldFactory.create(0, 1000); | ||
371 | |||
372 | window.addEventListener('keyup', debounce((e) => { | ||
373 | const element = e.target; | 370 | const element = e.target; |
374 | 371 | ||
375 | if (!element) return; | 372 | if (!element) return; |
@@ -382,22 +379,15 @@ class RecipeController { | |||
382 | } | 379 | } |
383 | 380 | ||
384 | // Force a minimum length to get better detection results | 381 | // Force a minimum length to get better detection results |
385 | if (value.length < 30) return; | 382 | if (value.length < 25) return; |
386 | 383 | ||
387 | debug('Detecting language for', value); | 384 | debug('Detecting language for', value); |
388 | const findResult = this.cldIdentifier.findLanguage(value); | 385 | const locale = await ipcRenderer.invoke('detect-language', { sample: value }); |
389 | 386 | ||
390 | debug('Language detection result', findResult); | 387 | const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(locale); |
391 | 388 | debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale); | |
392 | if (findResult.is_reliable) { | 389 | if (spellcheckerLocale) { |
393 | const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(findResult.language); | 390 | switchDict(spellcheckerLocale); |
394 | debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale); | ||
395 | if (spellcheckerLocale) { | ||
396 | switchDict([ | ||
397 | ...this.spellcheckerLanguage, | ||
398 | spellcheckerLocale, | ||
399 | ]); | ||
400 | } | ||
401 | } | 391 | } |
402 | }, 225)); | 392 | }, 225)); |
403 | } | 393 | } |
@@ -411,7 +401,8 @@ new RecipeController(); | |||
411 | const originalWindowOpen = window.open; | 401 | const originalWindowOpen = window.open; |
412 | 402 | ||
413 | window.open = (url, frameName, features) => { | 403 | window.open = (url, frameName, features) => { |
414 | if (!url && !frameName && !features) { | 404 | debug('window.open', url, frameName, features); |
405 | if (!url) { | ||
415 | // The service hasn't yet supplied a URL (as used in Skype). | 406 | // The service hasn't yet supplied a URL (as used in Skype). |
416 | // Return a new dummy window object and wait for the service to change the properties | 407 | // Return a new dummy window object and wait for the service to change the properties |
417 | const newWindow = { | 408 | const newWindow = { |
@@ -423,8 +414,12 @@ window.open = (url, frameName, features) => { | |||
423 | const checkInterval = setInterval(() => { | 414 | const checkInterval = setInterval(() => { |
424 | // Has the service changed the URL yet? | 415 | // Has the service changed the URL yet? |
425 | if (newWindow.location.href !== '') { | 416 | if (newWindow.location.href !== '') { |
426 | // Open the new URL | 417 | if (features) { |
427 | ipcRenderer.sendToHost('new-window', newWindow.location.href); | 418 | originalWindowOpen(newWindow.location.href, frameName, features); |
419 | } else { | ||
420 | // Open the new URL | ||
421 | ipcRenderer.sendToHost('new-window', newWindow.location.href); | ||
422 | } | ||
428 | clearInterval(checkInterval); | 423 | clearInterval(checkInterval); |
429 | } | 424 | } |
430 | }, 0); | 425 | }, 0); |
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 287c9cf11..e7ef102e9 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -1,64 +1,41 @@ | |||
1 | import { webFrame } from 'electron'; | 1 | import { |
2 | remote, | ||
3 | } from 'electron'; | ||
2 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 4 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
3 | import setupContextMenu from './contextMenu'; | 5 | import { isMac } from '../environment'; |
4 | 6 | ||
5 | const debug = require('debug')('Franz:spellchecker'); | 7 | const debug = require('debug')('Franz:spellchecker'); |
6 | 8 | ||
7 | let _isEnabled = false; | 9 | const webContents = remote.getCurrentWebContents(); |
10 | const [defaultLocale] = webContents.session.getSpellCheckerLanguages(); | ||
11 | debug('Spellchecker default locale is', defaultLocale); | ||
8 | 12 | ||
9 | export async function switchDict(locales) { | 13 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { |
10 | const { platform } = process; | 14 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key.toLocaleLowerCase() === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); |
11 | if (platform === 'darwin') { | ||
12 | // MacOS uses the build-in languages which cannot be changed | ||
13 | return; | ||
14 | } | ||
15 | |||
16 | try { | ||
17 | debug('Trying to load dictionary', locales); | ||
18 | |||
19 | webFrame.session.setSpellCheckerLanguages([...locales, 'en-US']); | ||
20 | |||
21 | debug('Switched dictionary to', locales); | ||
22 | |||
23 | _isEnabled = true; | ||
24 | } catch (err) { | ||
25 | console.error(err); | ||
26 | } | ||
27 | } | ||
28 | |||
29 | export default async function initialize(languages = ['en-us']) { | ||
30 | try { | ||
31 | debug('Init spellchecker'); | ||
32 | |||
33 | switchDict([ | ||
34 | navigator.language, | ||
35 | ...languages, | ||
36 | ]); | ||
37 | setupContextMenu(); | ||
38 | 15 | ||
39 | return true; | 16 | if (locales.length >= 1) { |
40 | } catch (err) { | 17 | return locales[0]; |
41 | console.error(err); | ||
42 | return false; | ||
43 | } | 18 | } |
44 | } | ||
45 | 19 | ||
46 | export function isEnabled() { | 20 | return null; |
47 | return _isEnabled; | ||
48 | } | 21 | } |
49 | 22 | ||
50 | export function disable() { | 23 | export function switchDict(locale) { |
51 | if (isEnabled()) { | 24 | if (isMac) { |
52 | // TODO: How to disable build-in spellchecker? | 25 | debug('Ignoring dictionary changes on macOS'); |
26 | return; | ||
53 | } | 27 | } |
54 | } | ||
55 | 28 | ||
56 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { | 29 | debug('Setting spellchecker locale to', locale); |
57 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
58 | 30 | ||
59 | if (locales.length >= 1) { | 31 | const locales = []; |
60 | return locales[0]; | 32 | const foundLocale = getSpellcheckerLocaleByFuzzyIdentifier(locale); |
33 | |||
34 | if (foundLocale) { | ||
35 | locales.push(foundLocale); | ||
61 | } | 36 | } |
62 | 37 | ||
63 | return null; | 38 | locales.push(defaultLocale, 'de'); |
39 | |||
40 | webContents.session.setSpellCheckerLanguages(locales); | ||
64 | } | 41 | } |