aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview
diff options
context:
space:
mode:
authorLibravatar Amine El Mouafik <412895+kytwb@users.noreply.github.com>2021-02-08 10:34:45 +0100
committerLibravatar GitHub <noreply@github.com>2021-02-08 10:34:45 +0100
commit035002ceedf78d5ec73eabc0df7f06139939b967 (patch)
tree1c0d1e9531bae05fb65d70b9ea25baf404b74fe1 /src/webview
parentdocs: add k0staa as a contributor (#1193) (diff)
downloadferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.gz
ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.zst
ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.zip
Synchronize with Franz 5.6.0 (#1033)
Co-authored-by: FranzBot <i18n@meetfranz.com> Co-authored-by: vantezzen <hello@vantezzen.io> Co-authored-by: Makazzz <makazzzpro@live.ca> Co-authored-by: Stefan Malzner <stefan@adlk.io> Co-authored-by: Amine Mouafik <amine@mouafik.fr>
Diffstat (limited to 'src/webview')
-rw-r--r--src/webview/contextMenu.js12
-rw-r--r--src/webview/contextMenuBuilder.js48
-rw-r--r--src/webview/recipe.js61
-rw-r--r--src/webview/spellchecker.js73
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
4const webContents = remote.getCurrentWebContents(); 4const webContents = remote.getCurrentWebContents();
5 5
6export default async function setupContextMenu() { 6export 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
23const contextMenuStringTable = { 23const 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';
3import path from 'path'; 3import path from 'path';
4import { autorun, computed, observable } from 'mobx'; 4import { autorun, computed, observable } from 'mobx';
5import fs from 'fs-extra'; 5import fs from 'fs-extra';
6import { loadModule } from 'cld3-asm';
7import { debounce } from 'lodash'; 6import { debounce } from 'lodash';
8import { FindInPage } from 'electron-find'; 7import { FindInPage } from 'electron-find';
9 8
@@ -23,8 +22,9 @@ import customDarkModeCss from './darkmode/custom';
23import RecipeWebview from './lib/RecipeWebview'; 22import RecipeWebview from './lib/RecipeWebview';
24import Userscript from './lib/Userscript'; 23import Userscript from './lib/Userscript';
25 24
26import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; 25import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker';
27import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; 26import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode';
27import contextMenu from './contextMenu';
28import './notifications'; 28import './notifications';
29 29
30import { DEFAULT_APP_SETTINGS } from '../config'; 30import { 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();
411const originalWindowOpen = window.open; 401const originalWindowOpen = window.open;
412 402
413window.open = (url, frameName, features) => { 403window.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 @@
1import { webFrame } from 'electron'; 1import {
2 remote,
3} from 'electron';
2import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 4import { SPELLCHECKER_LOCALES } from '../i18n/languages';
3import setupContextMenu from './contextMenu'; 5import { isMac } from '../environment';
4 6
5const debug = require('debug')('Franz:spellchecker'); 7const debug = require('debug')('Franz:spellchecker');
6 8
7let _isEnabled = false; 9const webContents = remote.getCurrentWebContents();
10const [defaultLocale] = webContents.session.getSpellCheckerLanguages();
11debug('Spellchecker default locale is', defaultLocale);
8 12
9export async function switchDict(locales) { 13export 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
29export 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
46export function isEnabled() { 20 return null;
47 return _isEnabled;
48} 21}
49 22
50export function disable() { 23export 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
56export 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}