aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Bennett <hello@vantezzen.io>2020-06-21 09:19:59 +0200
committerLibravatar GitHub <noreply@github.com>2020-06-21 12:49:59 +0530
commit0d6d623d1e34cdbff2d46229165b49289a9a0619 (patch)
tree83f6a22a08e354da58adf3ed0393f4d39bca6124 /src
parentPrepare and Release/5.6.0 beta.1 (#820) (diff)
downloadferdium-app-0d6d623d1e34cdbff2d46229165b49289a9a0619.tar.gz
ferdium-app-0d6d623d1e34cdbff2d46229165b49289a9a0619.tar.zst
ferdium-app-0d6d623d1e34cdbff2d46229165b49289a9a0619.zip
Add FAB to service dashboard (#824)
* Implement #387 * Fix lint * Upgrade to Electron 9 * Remove dependency on electron-spellchecker * Allow multiple languages to be selected * Fix lint * Don't show spellchecker language chooser for macOS * Fix _requireAuthenticatedUser throwing error on startup * Add FAB
Diffstat (limited to 'src')
-rw-r--r--src/components/settings/services/EditServiceForm.js20
-rw-r--r--src/components/settings/services/ServicesDashboard.js7
-rw-r--r--src/components/settings/settings/EditSettingsForm.js11
-rw-r--r--src/components/ui/FAB.js72
-rw-r--r--src/components/ui/Select.js44
-rw-r--r--src/config.js2
-rw-r--r--src/containers/settings/EditSettingsScreen.js2
-rw-r--r--src/electron/ipc-api/settings.js4
-rw-r--r--src/electron/webview-ime-focus.js3
-rw-r--r--src/i18n/locales/defaultMessages.json205
-rw-r--r--src/i18n/locales/en-US.json1
-rw-r--r--src/i18n/messages/src/components/auth/Locked.json32
-rw-r--r--src/i18n/messages/src/components/settings/services/EditServiceForm.json112
-rw-r--r--src/i18n/messages/src/components/settings/services/ServicesDashboard.json36
-rw-r--r--src/i18n/messages/src/components/settings/settings/EditSettingsForm.json61
-rw-r--r--src/i18n/messages/src/features/todos/components/TodosWebview.json12
-rw-r--r--src/models/Service.js4
-rw-r--r--src/stores/ServicesStore.js13
-rw-r--r--src/stores/UserStore.js4
-rw-r--r--src/styles/button.scss29
-rw-r--r--src/webview/contextMenu.js48
-rw-r--r--src/webview/contextMenuBuilder.js419
-rw-r--r--src/webview/recipe.js24
-rw-r--r--src/webview/spellchecker.js50
24 files changed, 897 insertions, 318 deletions
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 3dba793b2..f1e70ce59 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -21,6 +21,8 @@ import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
21import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; 21import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
22import { serviceLimitStore } from '../../../features/serviceLimit'; 22import { serviceLimitStore } from '../../../features/serviceLimit';
23 23
24import { isMac } from '../../../environment';
25
24const messages = defineMessages({ 26const messages = defineMessages({
25 saveService: { 27 saveService: {
26 id: 'settings.service.form.saveButton', 28 id: 'settings.service.form.saveButton',
@@ -401,14 +403,16 @@ export default @observer class EditServiceForm extends Component {
401 </div> 403 </div>
402 </div> 404 </div>
403 405
404 <PremiumFeatureContainer 406 {!isMac && (
405 condition={!isSpellcheckerIncludedInCurrentPlan} 407 <PremiumFeatureContainer
406 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 408 condition={!isSpellcheckerIncludedInCurrentPlan}
407 > 409 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
408 <div className="settings__settings-group"> 410 >
409 <Select field={form.$('spellcheckerLanguage')} /> 411 <div className="settings__settings-group">
410 </div> 412 <Select field={form.$('spellcheckerLanguage')} multiple />
411 </PremiumFeatureContainer> 413 </div>
414 </PremiumFeatureContainer>
415 )}
412 416
413 {isProxyFeatureEnabled && ( 417 {isProxyFeatureEnabled && (
414 <PremiumFeatureContainer 418 <PremiumFeatureContainer
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index 78038e86a..826dbb176 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -7,6 +7,7 @@ import { defineMessages, intlShape } from 'react-intl';
7import SearchInput from '../../ui/SearchInput'; 7import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox'; 8import Infobox from '../../ui/Infobox';
9import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
10import FAB from '../../ui/FAB';
10import ServiceItem from './ServiceItem'; 11import ServiceItem from './ServiceItem';
11import Appear from '../../ui/effects/Appear'; 12import Appear from '../../ui/effects/Appear';
12import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; 13import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
@@ -175,6 +176,12 @@ export default @observer class ServicesDashboard extends Component {
175 </tbody> 176 </tbody>
176 </table> 177 </table>
177 )} 178 )}
179
180 <FAB>
181 <Link to="/settings/recipes">
182 +
183 </Link>
184 </FAB>
178 </div> 185 </div>
179 </div> 186 </div>
180 ); 187 );
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 50358c36f..ba7cb7317 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -91,6 +91,10 @@ const messages = defineMessages({
91 id: 'settings.app.translationHelp', 91 id: 'settings.app.translationHelp',
92 defaultMessage: '!!!Help us to translate Ferdi into your language.', 92 defaultMessage: '!!!Help us to translate Ferdi into your language.',
93 }, 93 },
94 spellCheckerLanguageInfo: {
95 id: 'settings.app.spellCheckerLanguageInfo',
96 defaultMessage: '!!!Ferdi uses your Mac\'s build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac\'s System Preferences.',
97 },
94 subheadlineCache: { 98 subheadlineCache: {
95 id: 'settings.app.subheadlineCache', 99 id: 'settings.app.subheadlineCache',
96 defaultMessage: '!!!Cache', 100 defaultMessage: '!!!Cache',
@@ -518,8 +522,11 @@ export default @observer class EditSettingsForm extends Component {
518 <Toggle 522 <Toggle
519 field={form.$('enableSpellchecking')} 523 field={form.$('enableSpellchecking')}
520 /> 524 />
521 {form.$('enableSpellchecking').value && ( 525 {form.$('enableSpellchecking').value && !isMac && (
522 <Select field={form.$('spellcheckerLanguage')} /> 526 <Select field={form.$('spellcheckerLanguage')} multiple />
527 )}
528 {form.$('enableSpellchecking').value && isMac && (
529 <p>{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p>
523 )} 530 )}
524 </Fragment> 531 </Fragment>
525 </PremiumFeatureContainer> 532 </PremiumFeatureContainer>
diff --git a/src/components/ui/FAB.js b/src/components/ui/FAB.js
new file mode 100644
index 000000000..9359a3c6c
--- /dev/null
+++ b/src/components/ui/FAB.js
@@ -0,0 +1,72 @@
1/**
2 * Floating Action Button (FAB)
3 */
4import React, { Component } from 'react';
5import PropTypes from 'prop-types';
6import { observer, inject } from 'mobx-react';
7import classnames from 'classnames';
8
9export default @inject('stores') @observer class Button extends Component {
10 static propTypes = {
11 className: PropTypes.string,
12 disabled: PropTypes.bool,
13 onClick: PropTypes.func,
14 type: PropTypes.string,
15 htmlForm: PropTypes.string,
16 stores: PropTypes.shape({
17 settings: PropTypes.shape({
18 app: PropTypes.shape({
19 accentColor: PropTypes.string.isRequired,
20 }).isRequired,
21 }).isRequired,
22 }).isRequired,
23 };
24
25 static defaultProps = {
26 className: null,
27 disabled: false,
28 onClick: () => { },
29 type: 'button',
30 htmlForm: '',
31 };
32
33 element = null;
34
35 render() {
36 const {
37 className,
38 disabled,
39 onClick,
40 type,
41 children,
42 htmlForm,
43 } = this.props;
44
45 const buttonProps = {
46 className: classnames({
47 ferdi__fab: true,
48 [`${className}`]: className,
49 }),
50 type,
51 };
52
53 if (disabled) {
54 buttonProps.disabled = true;
55 }
56
57 if (onClick) {
58 buttonProps.onClick = onClick;
59 }
60
61 if (htmlForm) {
62 buttonProps.form = htmlForm;
63 }
64
65 return (
66 // disabling rule as button has type defined in `buttonProps`
67 <button {...buttonProps} type="button">
68 {children}
69 </button>
70 );
71 }
72}
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js
index da52243ca..b4511433c 100644
--- a/src/components/ui/Select.js
+++ b/src/components/ui/Select.js
@@ -10,22 +10,60 @@ export default @observer class Select extends Component {
10 className: PropTypes.string, 10 className: PropTypes.string,
11 showLabel: PropTypes.bool, 11 showLabel: PropTypes.bool,
12 disabled: PropTypes.bool, 12 disabled: PropTypes.bool,
13 multiple: PropTypes.bool,
13 }; 14 };
14 15
15 static defaultProps = { 16 static defaultProps = {
16 className: null, 17 className: null,
17 showLabel: true, 18 showLabel: true,
18 disabled: false, 19 disabled: false,
20 multiple: false,
19 }; 21 };
20 22
23 constructor(props) {
24 super(props);
25
26 this.element = React.createRef();
27 }
28
29 multipleChange() {
30 const element = this.element.current;
31
32 const result = [];
33 const options = element && element.options;
34
35 for (const option of options) {
36 if (option.selected) {
37 result.push(option.value || option.text);
38 }
39 }
40
41 const { field } = this.props;
42 field.value = result;
43 }
44
21 render() { 45 render() {
22 const { 46 const {
23 field, 47 field,
24 className, 48 className,
25 showLabel, 49 showLabel,
26 disabled, 50 disabled,
51 multiple,
27 } = this.props; 52 } = this.props;
28 53
54 let selected = field.value;
55
56 if (multiple) {
57 if (typeof field.value === 'string' && field.value.substr(0, 1) === '[') {
58 // Value is JSON encoded
59 selected = JSON.parse(field.value);
60 } else if (typeof field.value === 'object') {
61 selected = field.value;
62 } else {
63 selected = [field.value];
64 }
65 }
66
29 return ( 67 return (
30 <div 68 <div
31 className={classnames({ 69 className={classnames({
@@ -44,11 +82,13 @@ export default @observer class Select extends Component {
44 </label> 82 </label>
45 )} 83 )}
46 <select 84 <select
47 onChange={field.onChange} 85 onChange={multiple ? e => this.multipleChange(e) : field.onChange}
48 id={field.id} 86 id={field.id}
49 defaultValue={field.value} 87 defaultValue={selected}
50 className="franz-form__select" 88 className="franz-form__select"
51 disabled={field.disabled || disabled} 89 disabled={field.disabled || disabled}
90 multiple={multiple}
91 ref={this.element}
52 > 92 >
53 {field.options.map(type => ( 93 {field.options.map(type => (
54 <option 94 <option
diff --git a/src/config.js b/src/config.js
index b8af16419..f9eecf375 100644
--- a/src/config.js
+++ b/src/config.js
@@ -98,7 +98,7 @@ export const DEFAULT_APP_SETTINGS = {
98 showMessageBadgeWhenMuted: true, 98 showMessageBadgeWhenMuted: true,
99 showDragArea: false, 99 showDragArea: false,
100 enableSpellchecking: true, 100 enableSpellchecking: true,
101 spellcheckerLanguage: 'en-us', 101 spellcheckerLanguage: '["en-US"]',
102 darkMode: process.platform === 'darwin' ? nativeTheme.shouldUseDarkColors : false, // We can't use refs from `./environment` at this time 102 darkMode: process.platform === 'darwin' ? nativeTheme.shouldUseDarkColors : false, // We can't use refs from `./environment` at this time
103 locale: '', 103 locale: '',
104 fallbackLocale: 'en-US', 104 fallbackLocale: 'en-US',
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 3dba3bc11..c6149fd9e 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -247,7 +247,7 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
247 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted, 247 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted,
248 showDragArea: settingsData.showDragArea, 248 showDragArea: settingsData.showDragArea,
249 enableSpellchecking: settingsData.enableSpellchecking, 249 enableSpellchecking: settingsData.enableSpellchecking,
250 spellcheckerLanguage: settingsData.spellcheckerLanguage, 250 spellcheckerLanguage: JSON.stringify(settingsData.spellcheckerLanguage),
251 beta: settingsData.beta, // we need this info in the main process as well 251 beta: settingsData.beta, // we need this info in the main process as well
252 automaticUpdates: settingsData.automaticUpdates, // we need this info in the main process as well 252 automaticUpdates: settingsData.automaticUpdates, // we need this info in the main process as well
253 locale: settingsData.locale, // we need this info in the main process as well 253 locale: settingsData.locale, // we need this info in the main process as well
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js
index b651db306..6d48c54e3 100644
--- a/src/electron/ipc-api/settings.js
+++ b/src/electron/ipc-api/settings.js
@@ -2,9 +2,11 @@ import { ipcMain } from 'electron';
2 2
3export default (params) => { 3export default (params) => {
4 ipcMain.on('getAppSettings', (event, type) => { 4 ipcMain.on('getAppSettings', (event, type) => {
5 const cleanData = JSON.parse(JSON.stringify(params.settings[type].all));
6
5 params.mainWindow.webContents.send('appSettings', { 7 params.mainWindow.webContents.send('appSettings', {
6 type, 8 type,
7 data: params.settings[type].all, 9 data: cleanData,
8 }); 10 });
9 }); 11 });
10 12
diff --git a/src/electron/webview-ime-focus.js b/src/electron/webview-ime-focus.js
index 1213b518e..b41232b63 100644
--- a/src/electron/webview-ime-focus.js
+++ b/src/electron/webview-ime-focus.js
@@ -1,3 +1,4 @@
1const { remote } = require('electron');
1const { releaseDocumentFocus } = require('./webview-ime-focus-helpers'); 2const { releaseDocumentFocus } = require('./webview-ime-focus-helpers');
2 3
3function giveWebviewDocumentFocus(element) { 4function giveWebviewDocumentFocus(element) {
@@ -9,7 +10,7 @@ function giveWebviewDocumentFocus(element) {
9} 10}
10 11
11function elementIsUnfocusedWebview(element) { 12function elementIsUnfocusedWebview(element) {
12 return element.tagName === 'WEBVIEW' && !element.getWebContents().isFocused(); 13 return element.tagName === 'WEBVIEW' && !remote.webContents.fromId(element.getWebContentsId()).isFocused();
13} 14}
14 15
15function webviewDidAutofocus(element) { 16function webviewDidAutofocus(element) {
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 244ceb521..ce78f34a3 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -285,104 +285,104 @@
285 "defaultMessage": "!!!Locked", 285 "defaultMessage": "!!!Locked",
286 "end": { 286 "end": {
287 "column": 3, 287 "column": 3,
288 "line": 22 288 "line": 23
289 }, 289 },
290 "file": "src/components/auth/Locked.js", 290 "file": "src/components/auth/Locked.js",
291 "id": "locked.headline", 291 "id": "locked.headline",
292 "start": { 292 "start": {
293 "column": 12, 293 "column": 12,
294 "line": 19 294 "line": 20
295 } 295 }
296 }, 296 },
297 { 297 {
298 "defaultMessage": "!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.", 298 "defaultMessage": "!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.",
299 "end": { 299 "end": {
300 "column": 3, 300 "column": 3,
301 "line": 26 301 "line": 27
302 }, 302 },
303 "file": "src/components/auth/Locked.js", 303 "file": "src/components/auth/Locked.js",
304 "id": "locked.info", 304 "id": "locked.info",
305 "start": { 305 "start": {
306 "column": 8, 306 "column": 8,
307 "line": 23 307 "line": 24
308 } 308 }
309 }, 309 },
310 { 310 {
311 "defaultMessage": "!!!Unlock with Touch ID", 311 "defaultMessage": "!!!Unlock with Touch ID",
312 "end": { 312 "end": {
313 "column": 3, 313 "column": 3,
314 "line": 30 314 "line": 31
315 }, 315 },
316 "file": "src/components/auth/Locked.js", 316 "file": "src/components/auth/Locked.js",
317 "id": "locked.touchId", 317 "id": "locked.touchId",
318 "start": { 318 "start": {
319 "column": 11, 319 "column": 11,
320 "line": 27 320 "line": 28
321 } 321 }
322 }, 322 },
323 { 323 {
324 "defaultMessage": "!!!unlock via Touch ID", 324 "defaultMessage": "!!!unlock via Touch ID",
325 "end": { 325 "end": {
326 "column": 3, 326 "column": 3,
327 "line": 34 327 "line": 35
328 }, 328 },
329 "file": "src/components/auth/Locked.js", 329 "file": "src/components/auth/Locked.js",
330 "id": "locked.touchIdPrompt", 330 "id": "locked.touchIdPrompt",
331 "start": { 331 "start": {
332 "column": 17, 332 "column": 17,
333 "line": 31 333 "line": 32
334 } 334 }
335 }, 335 },
336 { 336 {
337 "defaultMessage": "!!!Password", 337 "defaultMessage": "!!!Password",
338 "end": { 338 "end": {
339 "column": 3, 339 "column": 3,
340 "line": 38 340 "line": 39
341 }, 341 },
342 "file": "src/components/auth/Locked.js", 342 "file": "src/components/auth/Locked.js",
343 "id": "locked.password.label", 343 "id": "locked.password.label",
344 "start": { 344 "start": {
345 "column": 17, 345 "column": 17,
346 "line": 35 346 "line": 36
347 } 347 }
348 }, 348 },
349 { 349 {
350 "defaultMessage": "!!!Unlock", 350 "defaultMessage": "!!!Unlock",
351 "end": { 351 "end": {
352 "column": 3, 352 "column": 3,
353 "line": 42 353 "line": 43
354 }, 354 },
355 "file": "src/components/auth/Locked.js", 355 "file": "src/components/auth/Locked.js",
356 "id": "locked.submit.label", 356 "id": "locked.submit.label",
357 "start": { 357 "start": {
358 "column": 21, 358 "column": 21,
359 "line": 39 359 "line": 40
360 } 360 }
361 }, 361 },
362 { 362 {
363 "defaultMessage": "!!!Unlock with Password", 363 "defaultMessage": "!!!Unlock with Password",
364 "end": { 364 "end": {
365 "column": 3, 365 "column": 3,
366 "line": 46 366 "line": 47
367 }, 367 },
368 "file": "src/components/auth/Locked.js", 368 "file": "src/components/auth/Locked.js",
369 "id": "locked.unlockWithPassword", 369 "id": "locked.unlockWithPassword",
370 "start": { 370 "start": {
371 "column": 22, 371 "column": 22,
372 "line": 43 372 "line": 44
373 } 373 }
374 }, 374 },
375 { 375 {
376 "defaultMessage": "!!!Password invalid", 376 "defaultMessage": "!!!Password invalid",
377 "end": { 377 "end": {
378 "column": 3, 378 "column": 3,
379 "line": 50 379 "line": 51
380 }, 380 },
381 "file": "src/components/auth/Locked.js", 381 "file": "src/components/auth/Locked.js",
382 "id": "locked.invalidCredentials", 382 "id": "locked.invalidCredentials",
383 "start": { 383 "start": {
384 "column": 22, 384 "column": 22,
385 "line": 47 385 "line": 48
386 } 386 }
387 } 387 }
388 ], 388 ],
@@ -2483,364 +2483,364 @@
2483 "defaultMessage": "!!!Save service", 2483 "defaultMessage": "!!!Save service",
2484 "end": { 2484 "end": {
2485 "column": 3, 2485 "column": 3,
2486 "line": 28 2486 "line": 30
2487 }, 2487 },
2488 "file": "src/components/settings/services/EditServiceForm.js", 2488 "file": "src/components/settings/services/EditServiceForm.js",
2489 "id": "settings.service.form.saveButton", 2489 "id": "settings.service.form.saveButton",
2490 "start": { 2490 "start": {
2491 "column": 15, 2491 "column": 15,
2492 "line": 25 2492 "line": 27
2493 } 2493 }
2494 }, 2494 },
2495 { 2495 {
2496 "defaultMessage": "!!!Delete Service", 2496 "defaultMessage": "!!!Delete Service",
2497 "end": { 2497 "end": {
2498 "column": 3, 2498 "column": 3,
2499 "line": 32 2499 "line": 34
2500 }, 2500 },
2501 "file": "src/components/settings/services/EditServiceForm.js", 2501 "file": "src/components/settings/services/EditServiceForm.js",
2502 "id": "settings.service.form.deleteButton", 2502 "id": "settings.service.form.deleteButton",
2503 "start": { 2503 "start": {
2504 "column": 17, 2504 "column": 17,
2505 "line": 29 2505 "line": 31
2506 } 2506 }
2507 }, 2507 },
2508 { 2508 {
2509 "defaultMessage": "!!!Open darkmode.css", 2509 "defaultMessage": "!!!Open darkmode.css",
2510 "end": { 2510 "end": {
2511 "column": 3, 2511 "column": 3,
2512 "line": 36 2512 "line": 38
2513 }, 2513 },
2514 "file": "src/components/settings/services/EditServiceForm.js", 2514 "file": "src/components/settings/services/EditServiceForm.js",
2515 "id": "settings.service.form.openDarkmodeCss", 2515 "id": "settings.service.form.openDarkmodeCss",
2516 "start": { 2516 "start": {
2517 "column": 19, 2517 "column": 19,
2518 "line": 33 2518 "line": 35
2519 } 2519 }
2520 }, 2520 },
2521 { 2521 {
2522 "defaultMessage": "!!!Open user.css", 2522 "defaultMessage": "!!!Open user.css",
2523 "end": { 2523 "end": {
2524 "column": 3, 2524 "column": 3,
2525 "line": 40 2525 "line": 42
2526 }, 2526 },
2527 "file": "src/components/settings/services/EditServiceForm.js", 2527 "file": "src/components/settings/services/EditServiceForm.js",
2528 "id": "settings.service.form.openUserCss", 2528 "id": "settings.service.form.openUserCss",
2529 "start": { 2529 "start": {
2530 "column": 15, 2530 "column": 15,
2531 "line": 37 2531 "line": 39
2532 } 2532 }
2533 }, 2533 },
2534 { 2534 {
2535 "defaultMessage": "!!!Open user.js", 2535 "defaultMessage": "!!!Open user.js",
2536 "end": { 2536 "end": {
2537 "column": 3, 2537 "column": 3,
2538 "line": 44 2538 "line": 46
2539 }, 2539 },
2540 "file": "src/components/settings/services/EditServiceForm.js", 2540 "file": "src/components/settings/services/EditServiceForm.js",
2541 "id": "settings.service.form.openUserJs", 2541 "id": "settings.service.form.openUserJs",
2542 "start": { 2542 "start": {
2543 "column": 14, 2543 "column": 14,
2544 "line": 41 2544 "line": 43
2545 } 2545 }
2546 }, 2546 },
2547 { 2547 {
2548 "defaultMessage": "!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.", 2548 "defaultMessage": "!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.",
2549 "end": { 2549 "end": {
2550 "column": 3, 2550 "column": 3,
2551 "line": 48 2551 "line": 50
2552 }, 2552 },
2553 "file": "src/components/settings/services/EditServiceForm.js", 2553 "file": "src/components/settings/services/EditServiceForm.js",
2554 "id": "settings.service.form.recipeFileInfo", 2554 "id": "settings.service.form.recipeFileInfo",
2555 "start": { 2555 "start": {
2556 "column": 18, 2556 "column": 18,
2557 "line": 45 2557 "line": 47
2558 } 2558 }
2559 }, 2559 },
2560 { 2560 {
2561 "defaultMessage": "!!!Available services", 2561 "defaultMessage": "!!!Available services",
2562 "end": { 2562 "end": {
2563 "column": 3, 2563 "column": 3,
2564 "line": 52 2564 "line": 54
2565 }, 2565 },
2566 "file": "src/components/settings/services/EditServiceForm.js", 2566 "file": "src/components/settings/services/EditServiceForm.js",
2567 "id": "settings.service.form.availableServices", 2567 "id": "settings.service.form.availableServices",
2568 "start": { 2568 "start": {
2569 "column": 21, 2569 "column": 21,
2570 "line": 49 2570 "line": 51
2571 } 2571 }
2572 }, 2572 },
2573 { 2573 {
2574 "defaultMessage": "!!!Your services", 2574 "defaultMessage": "!!!Your services",
2575 "end": { 2575 "end": {
2576 "column": 3, 2576 "column": 3,
2577 "line": 56 2577 "line": 58
2578 }, 2578 },
2579 "file": "src/components/settings/services/EditServiceForm.js", 2579 "file": "src/components/settings/services/EditServiceForm.js",
2580 "id": "settings.service.form.yourServices", 2580 "id": "settings.service.form.yourServices",
2581 "start": { 2581 "start": {
2582 "column": 16, 2582 "column": 16,
2583 "line": 53 2583 "line": 55
2584 } 2584 }
2585 }, 2585 },
2586 { 2586 {
2587 "defaultMessage": "!!!Add {name}", 2587 "defaultMessage": "!!!Add {name}",
2588 "end": { 2588 "end": {
2589 "column": 3, 2589 "column": 3,
2590 "line": 60 2590 "line": 62
2591 }, 2591 },
2592 "file": "src/components/settings/services/EditServiceForm.js", 2592 "file": "src/components/settings/services/EditServiceForm.js",
2593 "id": "settings.service.form.addServiceHeadline", 2593 "id": "settings.service.form.addServiceHeadline",
2594 "start": { 2594 "start": {
2595 "column": 22, 2595 "column": 22,
2596 "line": 57 2596 "line": 59
2597 } 2597 }
2598 }, 2598 },
2599 { 2599 {
2600 "defaultMessage": "!!!Edit {name}", 2600 "defaultMessage": "!!!Edit {name}",
2601 "end": { 2601 "end": {
2602 "column": 3, 2602 "column": 3,
2603 "line": 64 2603 "line": 66
2604 }, 2604 },
2605 "file": "src/components/settings/services/EditServiceForm.js", 2605 "file": "src/components/settings/services/EditServiceForm.js",
2606 "id": "settings.service.form.editServiceHeadline", 2606 "id": "settings.service.form.editServiceHeadline",
2607 "start": { 2607 "start": {
2608 "column": 23, 2608 "column": 23,
2609 "line": 61 2609 "line": 63
2610 } 2610 }
2611 }, 2611 },
2612 { 2612 {
2613 "defaultMessage": "!!!Hosted", 2613 "defaultMessage": "!!!Hosted",
2614 "end": { 2614 "end": {
2615 "column": 3, 2615 "column": 3,
2616 "line": 68 2616 "line": 70
2617 }, 2617 },
2618 "file": "src/components/settings/services/EditServiceForm.js", 2618 "file": "src/components/settings/services/EditServiceForm.js",
2619 "id": "settings.service.form.tabHosted", 2619 "id": "settings.service.form.tabHosted",
2620 "start": { 2620 "start": {
2621 "column": 13, 2621 "column": 13,
2622 "line": 65 2622 "line": 67
2623 } 2623 }
2624 }, 2624 },
2625 { 2625 {
2626 "defaultMessage": "!!!Self hosted ⭐️", 2626 "defaultMessage": "!!!Self hosted ⭐️",
2627 "end": { 2627 "end": {
2628 "column": 3, 2628 "column": 3,
2629 "line": 72 2629 "line": 74
2630 }, 2630 },
2631 "file": "src/components/settings/services/EditServiceForm.js", 2631 "file": "src/components/settings/services/EditServiceForm.js",
2632 "id": "settings.service.form.tabOnPremise", 2632 "id": "settings.service.form.tabOnPremise",
2633 "start": { 2633 "start": {
2634 "column": 16, 2634 "column": 16,
2635 "line": 69 2635 "line": 71
2636 } 2636 }
2637 }, 2637 },
2638 { 2638 {
2639 "defaultMessage": "!!!Use the hosted {name} service.", 2639 "defaultMessage": "!!!Use the hosted {name} service.",
2640 "end": { 2640 "end": {
2641 "column": 3, 2641 "column": 3,
2642 "line": 76 2642 "line": 78
2643 }, 2643 },
2644 "file": "src/components/settings/services/EditServiceForm.js", 2644 "file": "src/components/settings/services/EditServiceForm.js",
2645 "id": "settings.service.form.useHostedService", 2645 "id": "settings.service.form.useHostedService",
2646 "start": { 2646 "start": {
2647 "column": 20, 2647 "column": 20,
2648 "line": 73 2648 "line": 75
2649 } 2649 }
2650 }, 2650 },
2651 { 2651 {
2652 "defaultMessage": "!!!Could not validate custom {name} server.", 2652 "defaultMessage": "!!!Could not validate custom {name} server.",
2653 "end": { 2653 "end": {
2654 "column": 3, 2654 "column": 3,
2655 "line": 80 2655 "line": 82
2656 }, 2656 },
2657 "file": "src/components/settings/services/EditServiceForm.js", 2657 "file": "src/components/settings/services/EditServiceForm.js",
2658 "id": "settings.service.form.customUrlValidationError", 2658 "id": "settings.service.form.customUrlValidationError",
2659 "start": { 2659 "start": {
2660 "column": 28, 2660 "column": 28,
2661 "line": 77 2661 "line": 79
2662 } 2662 }
2663 }, 2663 },
2664 { 2664 {
2665 "defaultMessage": "!!!To add self hosted services, you need a Ferdi Premium Supporter Account.", 2665 "defaultMessage": "!!!To add self hosted services, you need a Ferdi Premium Supporter Account.",
2666 "end": { 2666 "end": {
2667 "column": 3, 2667 "column": 3,
2668 "line": 84 2668 "line": 86
2669 }, 2669 },
2670 "file": "src/components/settings/services/EditServiceForm.js", 2670 "file": "src/components/settings/services/EditServiceForm.js",
2671 "id": "settings.service.form.customUrlPremiumInfo", 2671 "id": "settings.service.form.customUrlPremiumInfo",
2672 "start": { 2672 "start": {
2673 "column": 24, 2673 "column": 24,
2674 "line": 81 2674 "line": 83
2675 } 2675 }
2676 }, 2676 },
2677 { 2677 {
2678 "defaultMessage": "!!!Upgrade your account", 2678 "defaultMessage": "!!!Upgrade your account",
2679 "end": { 2679 "end": {
2680 "column": 3, 2680 "column": 3,
2681 "line": 88 2681 "line": 90
2682 }, 2682 },
2683 "file": "src/components/settings/services/EditServiceForm.js", 2683 "file": "src/components/settings/services/EditServiceForm.js",
2684 "id": "settings.service.form.customUrlUpgradeAccount", 2684 "id": "settings.service.form.customUrlUpgradeAccount",
2685 "start": { 2685 "start": {
2686 "column": 27, 2686 "column": 27,
2687 "line": 85 2687 "line": 87
2688 } 2688 }
2689 }, 2689 },
2690 { 2690 {
2691 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 2691 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
2692 "end": { 2692 "end": {
2693 "column": 3, 2693 "column": 3,
2694 "line": 92 2694 "line": 94
2695 }, 2695 },
2696 "file": "src/components/settings/services/EditServiceForm.js", 2696 "file": "src/components/settings/services/EditServiceForm.js",
2697 "id": "settings.service.form.indirectMessageInfo", 2697 "id": "settings.service.form.indirectMessageInfo",
2698 "start": { 2698 "start": {
2699 "column": 23, 2699 "column": 23,
2700 "line": 89 2700 "line": 91
2701 } 2701 }
2702 }, 2702 },
2703 { 2703 {
2704 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 2704 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
2705 "end": { 2705 "end": {
2706 "column": 3, 2706 "column": 3,
2707 "line": 96 2707 "line": 98
2708 }, 2708 },
2709 "file": "src/components/settings/services/EditServiceForm.js", 2709 "file": "src/components/settings/services/EditServiceForm.js",
2710 "id": "settings.service.form.isMutedInfo", 2710 "id": "settings.service.form.isMutedInfo",
2711 "start": { 2711 "start": {
2712 "column": 15, 2712 "column": 15,
2713 "line": 93 2713 "line": 95
2714 } 2714 }
2715 }, 2715 },
2716 { 2716 {
2717 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.", 2717 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.",
2718 "end": { 2718 "end": {
2719 "column": 3, 2719 "column": 3,
2720 "line": 100 2720 "line": 102
2721 }, 2721 },
2722 "file": "src/components/settings/services/EditServiceForm.js", 2722 "file": "src/components/settings/services/EditServiceForm.js",
2723 "id": "settings.service.form.disableHibernationInfo", 2723 "id": "settings.service.form.disableHibernationInfo",
2724 "start": { 2724 "start": {
2725 "column": 26, 2725 "column": 26,
2726 "line": 97 2726 "line": 99
2727 } 2727 }
2728 }, 2728 },
2729 { 2729 {
2730 "defaultMessage": "!!!Notifications", 2730 "defaultMessage": "!!!Notifications",
2731 "end": { 2731 "end": {
2732 "column": 3, 2732 "column": 3,
2733 "line": 104 2733 "line": 106
2734 }, 2734 },
2735 "file": "src/components/settings/services/EditServiceForm.js", 2735 "file": "src/components/settings/services/EditServiceForm.js",
2736 "id": "settings.service.form.headlineNotifications", 2736 "id": "settings.service.form.headlineNotifications",
2737 "start": { 2737 "start": {
2738 "column": 25, 2738 "column": 25,
2739 "line": 101 2739 "line": 103
2740 } 2740 }
2741 }, 2741 },
2742 { 2742 {
2743 "defaultMessage": "!!!Unread message badges", 2743 "defaultMessage": "!!!Unread message badges",
2744 "end": { 2744 "end": {
2745 "column": 3, 2745 "column": 3,
2746 "line": 108 2746 "line": 110
2747 }, 2747 },
2748 "file": "src/components/settings/services/EditServiceForm.js", 2748 "file": "src/components/settings/services/EditServiceForm.js",
2749 "id": "settings.service.form.headlineBadges", 2749 "id": "settings.service.form.headlineBadges",
2750 "start": { 2750 "start": {
2751 "column": 18, 2751 "column": 18,
2752 "line": 105 2752 "line": 107
2753 } 2753 }
2754 }, 2754 },
2755 { 2755 {
2756 "defaultMessage": "!!!General", 2756 "defaultMessage": "!!!General",
2757 "end": { 2757 "end": {
2758 "column": 3, 2758 "column": 3,
2759 "line": 112 2759 "line": 114
2760 }, 2760 },
2761 "file": "src/components/settings/services/EditServiceForm.js", 2761 "file": "src/components/settings/services/EditServiceForm.js",
2762 "id": "settings.service.form.headlineGeneral", 2762 "id": "settings.service.form.headlineGeneral",
2763 "start": { 2763 "start": {
2764 "column": 19, 2764 "column": 19,
2765 "line": 109 2765 "line": 111
2766 } 2766 }
2767 }, 2767 },
2768 { 2768 {
2769 "defaultMessage": "!!!Dark Reader Settings", 2769 "defaultMessage": "!!!Dark Reader Settings",
2770 "end": { 2770 "end": {
2771 "column": 3, 2771 "column": 3,
2772 "line": 116 2772 "line": 118
2773 }, 2773 },
2774 "file": "src/components/settings/services/EditServiceForm.js", 2774 "file": "src/components/settings/services/EditServiceForm.js",
2775 "id": "settings.service.form.headlineDarkReaderSettings", 2775 "id": "settings.service.form.headlineDarkReaderSettings",
2776 "start": { 2776 "start": {
2777 "column": 30, 2777 "column": 30,
2778 "line": 113 2778 "line": 115
2779 } 2779 }
2780 }, 2780 },
2781 { 2781 {
2782 "defaultMessage": "!!!Delete", 2782 "defaultMessage": "!!!Delete",
2783 "end": { 2783 "end": {
2784 "column": 3, 2784 "column": 3,
2785 "line": 120 2785 "line": 122
2786 }, 2786 },
2787 "file": "src/components/settings/services/EditServiceForm.js", 2787 "file": "src/components/settings/services/EditServiceForm.js",
2788 "id": "settings.service.form.iconDelete", 2788 "id": "settings.service.form.iconDelete",
2789 "start": { 2789 "start": {
2790 "column": 14, 2790 "column": 14,
2791 "line": 117 2791 "line": 119
2792 } 2792 }
2793 }, 2793 },
2794 { 2794 {
2795 "defaultMessage": "!!!Drop your image, or click here", 2795 "defaultMessage": "!!!Drop your image, or click here",
2796 "end": { 2796 "end": {
2797 "column": 3, 2797 "column": 3,
2798 "line": 124 2798 "line": 126
2799 }, 2799 },
2800 "file": "src/components/settings/services/EditServiceForm.js", 2800 "file": "src/components/settings/services/EditServiceForm.js",
2801 "id": "settings.service.form.iconUpload", 2801 "id": "settings.service.form.iconUpload",
2802 "start": { 2802 "start": {
2803 "column": 14, 2803 "column": 14,
2804 "line": 121 2804 "line": 123
2805 } 2805 }
2806 }, 2806 },
2807 { 2807 {
2808 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 2808 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
2809 "end": { 2809 "end": {
2810 "column": 3, 2810 "column": 3,
2811 "line": 128 2811 "line": 130
2812 }, 2812 },
2813 "file": "src/components/settings/services/EditServiceForm.js", 2813 "file": "src/components/settings/services/EditServiceForm.js",
2814 "id": "settings.service.form.proxy.headline", 2814 "id": "settings.service.form.proxy.headline",
2815 "start": { 2815 "start": {
2816 "column": 17, 2816 "column": 17,
2817 "line": 125 2817 "line": 127
2818 } 2818 }
2819 }, 2819 },
2820 { 2820 {
2821 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.", 2821 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.",
2822 "end": { 2822 "end": {
2823 "column": 3, 2823 "column": 3,
2824 "line": 132 2824 "line": 134
2825 }, 2825 },
2826 "file": "src/components/settings/services/EditServiceForm.js", 2826 "file": "src/components/settings/services/EditServiceForm.js",
2827 "id": "settings.service.form.proxy.restartInfo", 2827 "id": "settings.service.form.proxy.restartInfo",
2828 "start": { 2828 "start": {
2829 "column": 20, 2829 "column": 20,
2830 "line": 129 2830 "line": 131
2831 } 2831 }
2832 }, 2832 },
2833 { 2833 {
2834 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.", 2834 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.",
2835 "end": { 2835 "end": {
2836 "column": 3, 2836 "column": 3,
2837 "line": 136 2837 "line": 138
2838 }, 2838 },
2839 "file": "src/components/settings/services/EditServiceForm.js", 2839 "file": "src/components/settings/services/EditServiceForm.js",
2840 "id": "settings.service.form.proxy.info", 2840 "id": "settings.service.form.proxy.info",
2841 "start": { 2841 "start": {
2842 "column": 13, 2842 "column": 13,
2843 "line": 133 2843 "line": 135
2844 } 2844 }
2845 } 2845 }
2846 ], 2846 ],
@@ -3306,159 +3306,172 @@
3306 } 3306 }
3307 }, 3307 },
3308 { 3308 {
3309 "defaultMessage": "!!!Cache", 3309 "defaultMessage": "!!!Ferdi uses your Mac's build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.",
3310 "end": { 3310 "end": {
3311 "column": 3, 3311 "column": 3,
3312 "line": 97 3312 "line": 97
3313 }, 3313 },
3314 "file": "src/components/settings/settings/EditSettingsForm.js", 3314 "file": "src/components/settings/settings/EditSettingsForm.js",
3315 "id": "settings.app.spellCheckerLanguageInfo",
3316 "start": {
3317 "column": 28,
3318 "line": 94
3319 }
3320 },
3321 {
3322 "defaultMessage": "!!!Cache",
3323 "end": {
3324 "column": 3,
3325 "line": 101
3326 },
3327 "file": "src/components/settings/settings/EditSettingsForm.js",
3315 "id": "settings.app.subheadlineCache", 3328 "id": "settings.app.subheadlineCache",
3316 "start": { 3329 "start": {
3317 "column": 20, 3330 "column": 20,
3318 "line": 94 3331 "line": 98
3319 } 3332 }
3320 }, 3333 },
3321 { 3334 {
3322 "defaultMessage": "!!!Ferdi cache is currently using {size} of disk space.", 3335 "defaultMessage": "!!!Ferdi cache is currently using {size} of disk space.",
3323 "end": { 3336 "end": {
3324 "column": 3, 3337 "column": 3,
3325 "line": 101 3338 "line": 105
3326 }, 3339 },
3327 "file": "src/components/settings/settings/EditSettingsForm.js", 3340 "file": "src/components/settings/settings/EditSettingsForm.js",
3328 "id": "settings.app.cacheInfo", 3341 "id": "settings.app.cacheInfo",
3329 "start": { 3342 "start": {
3330 "column": 13, 3343 "column": 13,
3331 "line": 98 3344 "line": 102
3332 } 3345 }
3333 }, 3346 },
3334 { 3347 {
3335 "defaultMessage": "!!!Couldn't clear all cache", 3348 "defaultMessage": "!!!Couldn't clear all cache",
3336 "end": { 3349 "end": {
3337 "column": 3, 3350 "column": 3,
3338 "line": 105 3351 "line": 109
3339 }, 3352 },
3340 "file": "src/components/settings/settings/EditSettingsForm.js", 3353 "file": "src/components/settings/settings/EditSettingsForm.js",
3341 "id": "settings.app.cacheNotCleared", 3354 "id": "settings.app.cacheNotCleared",
3342 "start": { 3355 "start": {
3343 "column": 19, 3356 "column": 19,
3344 "line": 102 3357 "line": 106
3345 } 3358 }
3346 }, 3359 },
3347 { 3360 {
3348 "defaultMessage": "!!!Clear cache", 3361 "defaultMessage": "!!!Clear cache",
3349 "end": { 3362 "end": {
3350 "column": 3, 3363 "column": 3,
3351 "line": 109 3364 "line": 113
3352 }, 3365 },
3353 "file": "src/components/settings/settings/EditSettingsForm.js", 3366 "file": "src/components/settings/settings/EditSettingsForm.js",
3354 "id": "settings.app.buttonClearAllCache", 3367 "id": "settings.app.buttonClearAllCache",
3355 "start": { 3368 "start": {
3356 "column": 23, 3369 "column": 23,
3357 "line": 106 3370 "line": 110
3358 } 3371 }
3359 }, 3372 },
3360 { 3373 {
3361 "defaultMessage": "!!!Check for updates", 3374 "defaultMessage": "!!!Check for updates",
3362 "end": { 3375 "end": {
3363 "column": 3, 3376 "column": 3,
3364 "line": 113 3377 "line": 117
3365 }, 3378 },
3366 "file": "src/components/settings/settings/EditSettingsForm.js", 3379 "file": "src/components/settings/settings/EditSettingsForm.js",
3367 "id": "settings.app.buttonSearchForUpdate", 3380 "id": "settings.app.buttonSearchForUpdate",
3368 "start": { 3381 "start": {
3369 "column": 25, 3382 "column": 25,
3370 "line": 110 3383 "line": 114
3371 } 3384 }
3372 }, 3385 },
3373 { 3386 {
3374 "defaultMessage": "!!!Restart & install update", 3387 "defaultMessage": "!!!Restart & install update",
3375 "end": { 3388 "end": {
3376 "column": 3, 3389 "column": 3,
3377 "line": 117 3390 "line": 121
3378 }, 3391 },
3379 "file": "src/components/settings/settings/EditSettingsForm.js", 3392 "file": "src/components/settings/settings/EditSettingsForm.js",
3380 "id": "settings.app.buttonInstallUpdate", 3393 "id": "settings.app.buttonInstallUpdate",
3381 "start": { 3394 "start": {
3382 "column": 23, 3395 "column": 23,
3383 "line": 114 3396 "line": 118
3384 } 3397 }
3385 }, 3398 },
3386 { 3399 {
3387 "defaultMessage": "!!!Is searching for update", 3400 "defaultMessage": "!!!Is searching for update",
3388 "end": { 3401 "end": {
3389 "column": 3, 3402 "column": 3,
3390 "line": 121 3403 "line": 125
3391 }, 3404 },
3392 "file": "src/components/settings/settings/EditSettingsForm.js", 3405 "file": "src/components/settings/settings/EditSettingsForm.js",
3393 "id": "settings.app.updateStatusSearching", 3406 "id": "settings.app.updateStatusSearching",
3394 "start": { 3407 "start": {
3395 "column": 25, 3408 "column": 25,
3396 "line": 118 3409 "line": 122
3397 } 3410 }
3398 }, 3411 },
3399 { 3412 {
3400 "defaultMessage": "!!!Update available, downloading...", 3413 "defaultMessage": "!!!Update available, downloading...",
3401 "end": { 3414 "end": {
3402 "column": 3, 3415 "column": 3,
3403 "line": 125 3416 "line": 129
3404 }, 3417 },
3405 "file": "src/components/settings/settings/EditSettingsForm.js", 3418 "file": "src/components/settings/settings/EditSettingsForm.js",
3406 "id": "settings.app.updateStatusAvailable", 3419 "id": "settings.app.updateStatusAvailable",
3407 "start": { 3420 "start": {
3408 "column": 25, 3421 "column": 25,
3409 "line": 122 3422 "line": 126
3410 } 3423 }
3411 }, 3424 },
3412 { 3425 {
3413 "defaultMessage": "!!!You are using the latest version of Ferdi", 3426 "defaultMessage": "!!!You are using the latest version of Ferdi",
3414 "end": { 3427 "end": {
3415 "column": 3, 3428 "column": 3,
3416 "line": 129 3429 "line": 133
3417 }, 3430 },
3418 "file": "src/components/settings/settings/EditSettingsForm.js", 3431 "file": "src/components/settings/settings/EditSettingsForm.js",
3419 "id": "settings.app.updateStatusUpToDate", 3432 "id": "settings.app.updateStatusUpToDate",
3420 "start": { 3433 "start": {
3421 "column": 24, 3434 "column": 24,
3422 "line": 126 3435 "line": 130
3423 } 3436 }
3424 }, 3437 },
3425 { 3438 {
3426 "defaultMessage": "!!!Current version:", 3439 "defaultMessage": "!!!Current version:",
3427 "end": { 3440 "end": {
3428 "column": 3, 3441 "column": 3,
3429 "line": 133 3442 "line": 137
3430 }, 3443 },
3431 "file": "src/components/settings/settings/EditSettingsForm.js", 3444 "file": "src/components/settings/settings/EditSettingsForm.js",
3432 "id": "settings.app.currentVersion", 3445 "id": "settings.app.currentVersion",
3433 "start": { 3446 "start": {
3434 "column": 18, 3447 "column": 18,
3435 "line": 130 3448 "line": 134
3436 } 3449 }
3437 }, 3450 },
3438 { 3451 {
3439 "defaultMessage": "!!!Changes require restart", 3452 "defaultMessage": "!!!Changes require restart",
3440 "end": { 3453 "end": {
3441 "column": 3, 3454 "column": 3,
3442 "line": 137 3455 "line": 141
3443 }, 3456 },
3444 "file": "src/components/settings/settings/EditSettingsForm.js", 3457 "file": "src/components/settings/settings/EditSettingsForm.js",
3445 "id": "settings.app.restartRequired", 3458 "id": "settings.app.restartRequired",
3446 "start": { 3459 "start": {
3447 "column": 29, 3460 "column": 29,
3448 "line": 134 3461 "line": 138
3449 } 3462 }
3450 }, 3463 },
3451 { 3464 {
3452 "defaultMessage": "!!!Official translations are English & German. All other languages are community based translations.", 3465 "defaultMessage": "!!!Official translations are English & German. All other languages are community based translations.",
3453 "end": { 3466 "end": {
3454 "column": 3, 3467 "column": 3,
3455 "line": 141 3468 "line": 145
3456 }, 3469 },
3457 "file": "src/components/settings/settings/EditSettingsForm.js", 3470 "file": "src/components/settings/settings/EditSettingsForm.js",
3458 "id": "settings.app.languageDisclaimer", 3471 "id": "settings.app.languageDisclaimer",
3459 "start": { 3472 "start": {
3460 "column": 22, 3473 "column": 22,
3461 "line": 138 3474 "line": 142
3462 } 3475 }
3463 } 3476 }
3464 ], 3477 ],
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index dcaa2466a..fd5c0755b 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -335,6 +335,7 @@
335 "settings.app.scheduledDNDInfo": "Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.", 335 "settings.app.scheduledDNDInfo": "Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.",
336 "settings.app.scheduledDNDTimeInfo": "Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.", 336 "settings.app.scheduledDNDTimeInfo": "Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.",
337 "settings.app.sentryInfo": "Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data! Changing this option requires you to restart Ferdi.", 337 "settings.app.sentryInfo": "Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data! Changing this option requires you to restart Ferdi.",
338 "settings.app.spellCheckerLanguageInfo": "Ferdi uses your Mac's build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.",
338 "settings.app.subheadlineCache": "Cache", 339 "settings.app.subheadlineCache": "Cache",
339 "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature.", 340 "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature.",
340 "settings.app.translationHelp": "Help us to translate Ferdi into your language.", 341 "settings.app.translationHelp": "Help us to translate Ferdi into your language.",
diff --git a/src/i18n/messages/src/components/auth/Locked.json b/src/i18n/messages/src/components/auth/Locked.json
index ac6091c35..ed3999114 100644
--- a/src/i18n/messages/src/components/auth/Locked.json
+++ b/src/i18n/messages/src/components/auth/Locked.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Locked", 4 "defaultMessage": "!!!Locked",
5 "file": "src/components/auth/Locked.js", 5 "file": "src/components/auth/Locked.js",
6 "start": { 6 "start": {
7 "line": 19, 7 "line": 20,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 22, 11 "line": 23,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.", 17 "defaultMessage": "!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.",
18 "file": "src/components/auth/Locked.js", 18 "file": "src/components/auth/Locked.js",
19 "start": { 19 "start": {
20 "line": 23, 20 "line": 24,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 26, 24 "line": 27,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Unlock with Touch ID", 30 "defaultMessage": "!!!Unlock with Touch ID",
31 "file": "src/components/auth/Locked.js", 31 "file": "src/components/auth/Locked.js",
32 "start": { 32 "start": {
33 "line": 27, 33 "line": 28,
34 "column": 11 34 "column": 11
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 30, 37 "line": 31,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!unlock via Touch ID", 43 "defaultMessage": "!!!unlock via Touch ID",
44 "file": "src/components/auth/Locked.js", 44 "file": "src/components/auth/Locked.js",
45 "start": { 45 "start": {
46 "line": 31, 46 "line": 32,
47 "column": 17 47 "column": 17
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 34, 50 "line": 35,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Password", 56 "defaultMessage": "!!!Password",
57 "file": "src/components/auth/Locked.js", 57 "file": "src/components/auth/Locked.js",
58 "start": { 58 "start": {
59 "line": 35, 59 "line": 36,
60 "column": 17 60 "column": 17
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 38, 63 "line": 39,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Unlock", 69 "defaultMessage": "!!!Unlock",
70 "file": "src/components/auth/Locked.js", 70 "file": "src/components/auth/Locked.js",
71 "start": { 71 "start": {
72 "line": 39, 72 "line": 40,
73 "column": 21 73 "column": 21
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 42, 76 "line": 43,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Unlock with Password", 82 "defaultMessage": "!!!Unlock with Password",
83 "file": "src/components/auth/Locked.js", 83 "file": "src/components/auth/Locked.js",
84 "start": { 84 "start": {
85 "line": 43, 85 "line": 44,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 46, 89 "line": 47,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Password invalid", 95 "defaultMessage": "!!!Password invalid",
96 "file": "src/components/auth/Locked.js", 96 "file": "src/components/auth/Locked.js",
97 "start": { 97 "start": {
98 "line": 47, 98 "line": 48,
99 "column": 22 99 "column": 22
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 50, 102 "line": 51,
103 "column": 3 103 "column": 3
104 } 104 }
105 } 105 }
diff --git a/src/i18n/messages/src/components/settings/services/EditServiceForm.json b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
index 987004199..4ee427e46 100644
--- a/src/i18n/messages/src/components/settings/services/EditServiceForm.json
+++ b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Save service", 4 "defaultMessage": "!!!Save service",
5 "file": "src/components/settings/services/EditServiceForm.js", 5 "file": "src/components/settings/services/EditServiceForm.js",
6 "start": { 6 "start": {
7 "line": 25, 7 "line": 27,
8 "column": 15 8 "column": 15
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 28, 11 "line": 30,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Delete Service", 17 "defaultMessage": "!!!Delete Service",
18 "file": "src/components/settings/services/EditServiceForm.js", 18 "file": "src/components/settings/services/EditServiceForm.js",
19 "start": { 19 "start": {
20 "line": 29, 20 "line": 31,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 32, 24 "line": 34,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Open darkmode.css", 30 "defaultMessage": "!!!Open darkmode.css",
31 "file": "src/components/settings/services/EditServiceForm.js", 31 "file": "src/components/settings/services/EditServiceForm.js",
32 "start": { 32 "start": {
33 "line": 33, 33 "line": 35,
34 "column": 19 34 "column": 19
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 36, 37 "line": 38,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Open user.css", 43 "defaultMessage": "!!!Open user.css",
44 "file": "src/components/settings/services/EditServiceForm.js", 44 "file": "src/components/settings/services/EditServiceForm.js",
45 "start": { 45 "start": {
46 "line": 37, 46 "line": 39,
47 "column": 15 47 "column": 15
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 40, 50 "line": 42,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Open user.js", 56 "defaultMessage": "!!!Open user.js",
57 "file": "src/components/settings/services/EditServiceForm.js", 57 "file": "src/components/settings/services/EditServiceForm.js",
58 "start": { 58 "start": {
59 "line": 41, 59 "line": 43,
60 "column": 14 60 "column": 14
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 44, 63 "line": 46,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.", 69 "defaultMessage": "!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.",
70 "file": "src/components/settings/services/EditServiceForm.js", 70 "file": "src/components/settings/services/EditServiceForm.js",
71 "start": { 71 "start": {
72 "line": 45, 72 "line": 47,
73 "column": 18 73 "column": 18
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 48, 76 "line": 50,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Available services", 82 "defaultMessage": "!!!Available services",
83 "file": "src/components/settings/services/EditServiceForm.js", 83 "file": "src/components/settings/services/EditServiceForm.js",
84 "start": { 84 "start": {
85 "line": 49, 85 "line": 51,
86 "column": 21 86 "column": 21
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 52, 89 "line": 54,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Your services", 95 "defaultMessage": "!!!Your services",
96 "file": "src/components/settings/services/EditServiceForm.js", 96 "file": "src/components/settings/services/EditServiceForm.js",
97 "start": { 97 "start": {
98 "line": 53, 98 "line": 55,
99 "column": 16 99 "column": 16
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 56, 102 "line": 58,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Add {name}", 108 "defaultMessage": "!!!Add {name}",
109 "file": "src/components/settings/services/EditServiceForm.js", 109 "file": "src/components/settings/services/EditServiceForm.js",
110 "start": { 110 "start": {
111 "line": 57, 111 "line": 59,
112 "column": 22 112 "column": 22
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 60, 115 "line": 62,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Edit {name}", 121 "defaultMessage": "!!!Edit {name}",
122 "file": "src/components/settings/services/EditServiceForm.js", 122 "file": "src/components/settings/services/EditServiceForm.js",
123 "start": { 123 "start": {
124 "line": 61, 124 "line": 63,
125 "column": 23 125 "column": 23
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 64, 128 "line": 66,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Hosted", 134 "defaultMessage": "!!!Hosted",
135 "file": "src/components/settings/services/EditServiceForm.js", 135 "file": "src/components/settings/services/EditServiceForm.js",
136 "start": { 136 "start": {
137 "line": 65, 137 "line": 67,
138 "column": 13 138 "column": 13
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 68, 141 "line": 70,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Self hosted ⭐️", 147 "defaultMessage": "!!!Self hosted ⭐️",
148 "file": "src/components/settings/services/EditServiceForm.js", 148 "file": "src/components/settings/services/EditServiceForm.js",
149 "start": { 149 "start": {
150 "line": 69, 150 "line": 71,
151 "column": 16 151 "column": 16
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 72, 154 "line": 74,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Use the hosted {name} service.", 160 "defaultMessage": "!!!Use the hosted {name} service.",
161 "file": "src/components/settings/services/EditServiceForm.js", 161 "file": "src/components/settings/services/EditServiceForm.js",
162 "start": { 162 "start": {
163 "line": 73, 163 "line": 75,
164 "column": 20 164 "column": 20
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 76, 167 "line": 78,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!Could not validate custom {name} server.", 173 "defaultMessage": "!!!Could not validate custom {name} server.",
174 "file": "src/components/settings/services/EditServiceForm.js", 174 "file": "src/components/settings/services/EditServiceForm.js",
175 "start": { 175 "start": {
176 "line": 77, 176 "line": 79,
177 "column": 28 177 "column": 28
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 80, 180 "line": 82,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!To add self hosted services, you need a Ferdi Premium Supporter Account.", 186 "defaultMessage": "!!!To add self hosted services, you need a Ferdi Premium Supporter Account.",
187 "file": "src/components/settings/services/EditServiceForm.js", 187 "file": "src/components/settings/services/EditServiceForm.js",
188 "start": { 188 "start": {
189 "line": 81, 189 "line": 83,
190 "column": 24 190 "column": 24
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 84, 193 "line": 86,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Upgrade your account", 199 "defaultMessage": "!!!Upgrade your account",
200 "file": "src/components/settings/services/EditServiceForm.js", 200 "file": "src/components/settings/services/EditServiceForm.js",
201 "start": { 201 "start": {
202 "line": 85, 202 "line": 87,
203 "column": 27 203 "column": 27
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 88, 206 "line": 90,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 212 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
213 "file": "src/components/settings/services/EditServiceForm.js", 213 "file": "src/components/settings/services/EditServiceForm.js",
214 "start": { 214 "start": {
215 "line": 89, 215 "line": 91,
216 "column": 23 216 "column": 23
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 92, 219 "line": 94,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 225 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
226 "file": "src/components/settings/services/EditServiceForm.js", 226 "file": "src/components/settings/services/EditServiceForm.js",
227 "start": { 227 "start": {
228 "line": 93, 228 "line": 95,
229 "column": 15 229 "column": 15
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 96, 232 "line": 98,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.", 238 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.",
239 "file": "src/components/settings/services/EditServiceForm.js", 239 "file": "src/components/settings/services/EditServiceForm.js",
240 "start": { 240 "start": {
241 "line": 97, 241 "line": 99,
242 "column": 26 242 "column": 26
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 100, 245 "line": 102,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!Notifications", 251 "defaultMessage": "!!!Notifications",
252 "file": "src/components/settings/services/EditServiceForm.js", 252 "file": "src/components/settings/services/EditServiceForm.js",
253 "start": { 253 "start": {
254 "line": 101, 254 "line": 103,
255 "column": 25 255 "column": 25
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 104, 258 "line": 106,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Unread message badges", 264 "defaultMessage": "!!!Unread message badges",
265 "file": "src/components/settings/services/EditServiceForm.js", 265 "file": "src/components/settings/services/EditServiceForm.js",
266 "start": { 266 "start": {
267 "line": 105, 267 "line": 107,
268 "column": 18 268 "column": 18
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 108, 271 "line": 110,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!General", 277 "defaultMessage": "!!!General",
278 "file": "src/components/settings/services/EditServiceForm.js", 278 "file": "src/components/settings/services/EditServiceForm.js",
279 "start": { 279 "start": {
280 "line": 109, 280 "line": 111,
281 "column": 19 281 "column": 19
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 112, 284 "line": 114,
285 "column": 3 285 "column": 3
286 } 286 }
287 }, 287 },
@@ -290,11 +290,11 @@
290 "defaultMessage": "!!!Dark Reader Settings", 290 "defaultMessage": "!!!Dark Reader Settings",
291 "file": "src/components/settings/services/EditServiceForm.js", 291 "file": "src/components/settings/services/EditServiceForm.js",
292 "start": { 292 "start": {
293 "line": 113, 293 "line": 115,
294 "column": 30 294 "column": 30
295 }, 295 },
296 "end": { 296 "end": {
297 "line": 116, 297 "line": 118,
298 "column": 3 298 "column": 3
299 } 299 }
300 }, 300 },
@@ -303,11 +303,11 @@
303 "defaultMessage": "!!!Delete", 303 "defaultMessage": "!!!Delete",
304 "file": "src/components/settings/services/EditServiceForm.js", 304 "file": "src/components/settings/services/EditServiceForm.js",
305 "start": { 305 "start": {
306 "line": 117, 306 "line": 119,
307 "column": 14 307 "column": 14
308 }, 308 },
309 "end": { 309 "end": {
310 "line": 120, 310 "line": 122,
311 "column": 3 311 "column": 3
312 } 312 }
313 }, 313 },
@@ -316,11 +316,11 @@
316 "defaultMessage": "!!!Drop your image, or click here", 316 "defaultMessage": "!!!Drop your image, or click here",
317 "file": "src/components/settings/services/EditServiceForm.js", 317 "file": "src/components/settings/services/EditServiceForm.js",
318 "start": { 318 "start": {
319 "line": 121, 319 "line": 123,
320 "column": 14 320 "column": 14
321 }, 321 },
322 "end": { 322 "end": {
323 "line": 124, 323 "line": 126,
324 "column": 3 324 "column": 3
325 } 325 }
326 }, 326 },
@@ -329,11 +329,11 @@
329 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 329 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
330 "file": "src/components/settings/services/EditServiceForm.js", 330 "file": "src/components/settings/services/EditServiceForm.js",
331 "start": { 331 "start": {
332 "line": 125, 332 "line": 127,
333 "column": 17 333 "column": 17
334 }, 334 },
335 "end": { 335 "end": {
336 "line": 128, 336 "line": 130,
337 "column": 3 337 "column": 3
338 } 338 }
339 }, 339 },
@@ -342,11 +342,11 @@
342 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.", 342 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.",
343 "file": "src/components/settings/services/EditServiceForm.js", 343 "file": "src/components/settings/services/EditServiceForm.js",
344 "start": { 344 "start": {
345 "line": 129, 345 "line": 131,
346 "column": 20 346 "column": 20
347 }, 347 },
348 "end": { 348 "end": {
349 "line": 132, 349 "line": 134,
350 "column": 3 350 "column": 3
351 } 351 }
352 }, 352 },
@@ -355,11 +355,11 @@
355 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.", 355 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.",
356 "file": "src/components/settings/services/EditServiceForm.js", 356 "file": "src/components/settings/services/EditServiceForm.js",
357 "start": { 357 "start": {
358 "line": 133, 358 "line": 135,
359 "column": 13 359 "column": 13
360 }, 360 },
361 "end": { 361 "end": {
362 "line": 136, 362 "line": 138,
363 "column": 3 363 "column": 3
364 } 364 }
365 } 365 }
diff --git a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
index fa661ea2f..140d17a8b 100644
--- a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
+++ b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services", 4 "defaultMessage": "!!!Your services",
5 "file": "src/components/settings/services/ServicesDashboard.js", 5 "file": "src/components/settings/services/ServicesDashboard.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 16,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 19,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Search service", 17 "defaultMessage": "!!!Search service",
18 "file": "src/components/settings/services/ServicesDashboard.js", 18 "file": "src/components/settings/services/ServicesDashboard.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 20,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 23,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!You haven't added any services yet.", 30 "defaultMessage": "!!!You haven't added any services yet.",
31 "file": "src/components/settings/services/ServicesDashboard.js", 31 "file": "src/components/settings/services/ServicesDashboard.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 24,
34 "column": 19 34 "column": 19
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 27,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Sorry, but no service matched your search term.", 43 "defaultMessage": "!!!Sorry, but no service matched your search term.",
44 "file": "src/components/settings/services/ServicesDashboard.js", 44 "file": "src/components/settings/services/ServicesDashboard.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 28,
47 "column": 18 47 "column": 18
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 31,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Discover services", 56 "defaultMessage": "!!!Discover services",
57 "file": "src/components/settings/services/ServicesDashboard.js", 57 "file": "src/components/settings/services/ServicesDashboard.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 32,
60 "column": 20 60 "column": 20
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 35,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load your services", 69 "defaultMessage": "!!!Could not load your services",
70 "file": "src/components/settings/services/ServicesDashboard.js", 70 "file": "src/components/settings/services/ServicesDashboard.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 36,
73 "column": 25 73 "column": 25
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 39,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Try again", 82 "defaultMessage": "!!!Try again",
83 "file": "src/components/settings/services/ServicesDashboard.js", 83 "file": "src/components/settings/services/ServicesDashboard.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 40,
86 "column": 21 86 "column": 21
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 43,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Your changes have been saved", 95 "defaultMessage": "!!!Your changes have been saved",
96 "file": "src/components/settings/services/ServicesDashboard.js", 96 "file": "src/components/settings/services/ServicesDashboard.js",
97 "start": { 97 "start": {
98 "line": 43, 98 "line": 44,
99 "column": 15 99 "column": 15
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 46, 102 "line": 47,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Service has been deleted", 108 "defaultMessage": "!!!Service has been deleted",
109 "file": "src/components/settings/services/ServicesDashboard.js", 109 "file": "src/components/settings/services/ServicesDashboard.js",
110 "start": { 110 "start": {
111 "line": 47, 111 "line": 48,
112 "column": 15 112 "column": 15
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 50, 115 "line": 51,
116 "column": 3 116 "column": 3
117 } 117 }
118 } 118 }
diff --git a/src/i18n/messages/src/components/settings/settings/EditSettingsForm.json b/src/i18n/messages/src/components/settings/settings/EditSettingsForm.json
index 97360b78e..373cd78f9 100644
--- a/src/i18n/messages/src/components/settings/settings/EditSettingsForm.json
+++ b/src/i18n/messages/src/components/settings/settings/EditSettingsForm.json
@@ -234,15 +234,28 @@
234 } 234 }
235 }, 235 },
236 { 236 {
237 "id": "settings.app.spellCheckerLanguageInfo",
238 "defaultMessage": "!!!Ferdi uses your Mac's build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.",
239 "file": "src/components/settings/settings/EditSettingsForm.js",
240 "start": {
241 "line": 94,
242 "column": 28
243 },
244 "end": {
245 "line": 97,
246 "column": 3
247 }
248 },
249 {
237 "id": "settings.app.subheadlineCache", 250 "id": "settings.app.subheadlineCache",
238 "defaultMessage": "!!!Cache", 251 "defaultMessage": "!!!Cache",
239 "file": "src/components/settings/settings/EditSettingsForm.js", 252 "file": "src/components/settings/settings/EditSettingsForm.js",
240 "start": { 253 "start": {
241 "line": 94, 254 "line": 98,
242 "column": 20 255 "column": 20
243 }, 256 },
244 "end": { 257 "end": {
245 "line": 97, 258 "line": 101,
246 "column": 3 259 "column": 3
247 } 260 }
248 }, 261 },
@@ -251,11 +264,11 @@
251 "defaultMessage": "!!!Ferdi cache is currently using {size} of disk space.", 264 "defaultMessage": "!!!Ferdi cache is currently using {size} of disk space.",
252 "file": "src/components/settings/settings/EditSettingsForm.js", 265 "file": "src/components/settings/settings/EditSettingsForm.js",
253 "start": { 266 "start": {
254 "line": 98, 267 "line": 102,
255 "column": 13 268 "column": 13
256 }, 269 },
257 "end": { 270 "end": {
258 "line": 101, 271 "line": 105,
259 "column": 3 272 "column": 3
260 } 273 }
261 }, 274 },
@@ -264,11 +277,11 @@
264 "defaultMessage": "!!!Couldn't clear all cache", 277 "defaultMessage": "!!!Couldn't clear all cache",
265 "file": "src/components/settings/settings/EditSettingsForm.js", 278 "file": "src/components/settings/settings/EditSettingsForm.js",
266 "start": { 279 "start": {
267 "line": 102, 280 "line": 106,
268 "column": 19 281 "column": 19
269 }, 282 },
270 "end": { 283 "end": {
271 "line": 105, 284 "line": 109,
272 "column": 3 285 "column": 3
273 } 286 }
274 }, 287 },
@@ -277,11 +290,11 @@
277 "defaultMessage": "!!!Clear cache", 290 "defaultMessage": "!!!Clear cache",
278 "file": "src/components/settings/settings/EditSettingsForm.js", 291 "file": "src/components/settings/settings/EditSettingsForm.js",
279 "start": { 292 "start": {
280 "line": 106, 293 "line": 110,
281 "column": 23 294 "column": 23
282 }, 295 },
283 "end": { 296 "end": {
284 "line": 109, 297 "line": 113,
285 "column": 3 298 "column": 3
286 } 299 }
287 }, 300 },
@@ -290,11 +303,11 @@
290 "defaultMessage": "!!!Check for updates", 303 "defaultMessage": "!!!Check for updates",
291 "file": "src/components/settings/settings/EditSettingsForm.js", 304 "file": "src/components/settings/settings/EditSettingsForm.js",
292 "start": { 305 "start": {
293 "line": 110, 306 "line": 114,
294 "column": 25 307 "column": 25
295 }, 308 },
296 "end": { 309 "end": {
297 "line": 113, 310 "line": 117,
298 "column": 3 311 "column": 3
299 } 312 }
300 }, 313 },
@@ -303,11 +316,11 @@
303 "defaultMessage": "!!!Restart & install update", 316 "defaultMessage": "!!!Restart & install update",
304 "file": "src/components/settings/settings/EditSettingsForm.js", 317 "file": "src/components/settings/settings/EditSettingsForm.js",
305 "start": { 318 "start": {
306 "line": 114, 319 "line": 118,
307 "column": 23 320 "column": 23
308 }, 321 },
309 "end": { 322 "end": {
310 "line": 117, 323 "line": 121,
311 "column": 3 324 "column": 3
312 } 325 }
313 }, 326 },
@@ -316,11 +329,11 @@
316 "defaultMessage": "!!!Is searching for update", 329 "defaultMessage": "!!!Is searching for update",
317 "file": "src/components/settings/settings/EditSettingsForm.js", 330 "file": "src/components/settings/settings/EditSettingsForm.js",
318 "start": { 331 "start": {
319 "line": 118, 332 "line": 122,
320 "column": 25 333 "column": 25
321 }, 334 },
322 "end": { 335 "end": {
323 "line": 121, 336 "line": 125,
324 "column": 3 337 "column": 3
325 } 338 }
326 }, 339 },
@@ -329,11 +342,11 @@
329 "defaultMessage": "!!!Update available, downloading...", 342 "defaultMessage": "!!!Update available, downloading...",
330 "file": "src/components/settings/settings/EditSettingsForm.js", 343 "file": "src/components/settings/settings/EditSettingsForm.js",
331 "start": { 344 "start": {
332 "line": 122, 345 "line": 126,
333 "column": 25 346 "column": 25
334 }, 347 },
335 "end": { 348 "end": {
336 "line": 125, 349 "line": 129,
337 "column": 3 350 "column": 3
338 } 351 }
339 }, 352 },
@@ -342,11 +355,11 @@
342 "defaultMessage": "!!!You are using the latest version of Ferdi", 355 "defaultMessage": "!!!You are using the latest version of Ferdi",
343 "file": "src/components/settings/settings/EditSettingsForm.js", 356 "file": "src/components/settings/settings/EditSettingsForm.js",
344 "start": { 357 "start": {
345 "line": 126, 358 "line": 130,
346 "column": 24 359 "column": 24
347 }, 360 },
348 "end": { 361 "end": {
349 "line": 129, 362 "line": 133,
350 "column": 3 363 "column": 3
351 } 364 }
352 }, 365 },
@@ -355,11 +368,11 @@
355 "defaultMessage": "!!!Current version:", 368 "defaultMessage": "!!!Current version:",
356 "file": "src/components/settings/settings/EditSettingsForm.js", 369 "file": "src/components/settings/settings/EditSettingsForm.js",
357 "start": { 370 "start": {
358 "line": 130, 371 "line": 134,
359 "column": 18 372 "column": 18
360 }, 373 },
361 "end": { 374 "end": {
362 "line": 133, 375 "line": 137,
363 "column": 3 376 "column": 3
364 } 377 }
365 }, 378 },
@@ -368,11 +381,11 @@
368 "defaultMessage": "!!!Changes require restart", 381 "defaultMessage": "!!!Changes require restart",
369 "file": "src/components/settings/settings/EditSettingsForm.js", 382 "file": "src/components/settings/settings/EditSettingsForm.js",
370 "start": { 383 "start": {
371 "line": 134, 384 "line": 138,
372 "column": 29 385 "column": 29
373 }, 386 },
374 "end": { 387 "end": {
375 "line": 137, 388 "line": 141,
376 "column": 3 389 "column": 3
377 } 390 }
378 }, 391 },
@@ -381,11 +394,11 @@
381 "defaultMessage": "!!!Official translations are English & German. All other languages are community based translations.", 394 "defaultMessage": "!!!Official translations are English & German. All other languages are community based translations.",
382 "file": "src/components/settings/settings/EditSettingsForm.js", 395 "file": "src/components/settings/settings/EditSettingsForm.js",
383 "start": { 396 "start": {
384 "line": 138, 397 "line": 142,
385 "column": 22 398 "column": 22
386 }, 399 },
387 "end": { 400 "end": {
388 "line": 141, 401 "line": 145,
389 "column": 3 402 "column": 3
390 } 403 }
391 } 404 }
diff --git a/src/i18n/messages/src/features/todos/components/TodosWebview.json b/src/i18n/messages/src/features/todos/components/TodosWebview.json
index 173389570..ff6e037fc 100644
--- a/src/i18n/messages/src/features/todos/components/TodosWebview.json
+++ b/src/i18n/messages/src/features/todos/components/TodosWebview.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Franz Todos are available to premium users now!", 4 "defaultMessage": "!!!Franz Todos are available to premium users now!",
5 "file": "src/features/todos/components/TodosWebview.js", 5 "file": "src/features/todos/components/TodosWebview.js",
6 "start": { 6 "start": {
7 "line": 27, 7 "line": 31,
8 "column": 15 8 "column": 15
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 30, 11 "line": 34,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Upgrade Account", 17 "defaultMessage": "!!!Upgrade Account",
18 "file": "src/features/todos/components/TodosWebview.js", 18 "file": "src/features/todos/components/TodosWebview.js",
19 "start": { 19 "start": {
20 "line": 31, 20 "line": 35,
21 "column": 14 21 "column": 14
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 34, 24 "line": 38,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Everyone else will have to wait a little longer.", 30 "defaultMessage": "!!!Everyone else will have to wait a little longer.",
31 "file": "src/features/todos/components/TodosWebview.js", 31 "file": "src/features/todos/components/TodosWebview.js",
32 "start": { 32 "start": {
33 "line": 35, 33 "line": 39,
34 "column": 15 34 "column": 15
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 38, 37 "line": 42,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/models/Service.js b/src/models/Service.js
index 45dc55fce..4b5e8592d 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -1,5 +1,5 @@
1import { autorun, computed, observable } from 'mobx'; 1import { autorun, computed, observable } from 'mobx';
2import { ipcRenderer } from 'electron'; 2import { ipcRenderer, remote } from 'electron';
3import normalizeUrl from 'normalize-url'; 3import normalizeUrl from 'normalize-url';
4import path from 'path'; 4import path from 'path';
5 5
@@ -227,7 +227,7 @@ export default class Service {
227 227
228 228
229 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { 229 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) {
230 const webContents = this.webview.getWebContents(); 230 const webContents = remote.webContents.fromId(this.webview.getWebContentsId());
231 231
232 // If the recipe has implemented modifyRequestHeaders, 232 // If the recipe has implemented modifyRequestHeaders,
233 // Send those headers to ipcMain so that it can be set in session 233 // Send those headers to ipcMain so that it can be set in session
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index a8d9cc1fb..448260638 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -618,7 +618,10 @@ export default class ServicesStore extends Store {
618 const service = this.one(serviceId); 618 const service = this.one(serviceId);
619 619
620 if (service.webview) { 620 if (service.webview) {
621 service.webview.send(channel, args); 621 // Make sure the args are clean, otherwise ElectronJS can't transmit them
622 const cleanArgs = JSON.parse(JSON.stringify(args));
623
624 service.webview.send(channel, cleanArgs);
622 } 625 }
623 } 626 }
624 627
@@ -895,10 +898,14 @@ export default class ServicesStore extends Store {
895 const service = this.one(serviceId); 898 const service = this.one(serviceId);
896 899
897 if (service.webview) { 900 if (service.webview) {
901 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC
902 const shareWithWebview = JSON.parse(JSON.stringify(service.shareWithWebview));
903
898 debug('Initialize recipe', service.recipe.id, service.name); 904 debug('Initialize recipe', service.recipe.id, service.name);
899 service.webview.send('initialize-recipe', Object.assign({ 905 service.webview.send('initialize-recipe', {
906 ...shareWithWebview,
900 franzVersion: app.getVersion(), 907 franzVersion: app.getVersion(),
901 }, service.shareWithWebview), service.recipe); 908 }, service.recipe);
902 } 909 }
903 } 910 }
904 911
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 3a53d150d..c1ed2944a 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -99,7 +99,7 @@ export default class UserStore extends Store {
99 99
100 // Reactions 100 // Reactions
101 this.registerReactions([ 101 this.registerReactions([
102 this._requireAuthenticatedUser, 102 this._requireAuthenticatedUser.bind(this),
103 this._getUserData.bind(this), 103 this._getUserData.bind(this),
104 this._resetTrialActivationState.bind(this), 104 this._resetTrialActivationState.bind(this),
105 ]); 105 ]);
@@ -321,7 +321,7 @@ export default class UserStore extends Store {
321 } 321 }
322 322
323 const { router } = this.stores; 323 const { router } = this.stores;
324 const currentRoute = router.location.pathname; 324 const currentRoute = window.location.hash;
325 if (!this.isLoggedIn 325 if (!this.isLoggedIn
326 && currentRoute.includes('token=')) { 326 && currentRoute.includes('token=')) {
327 router.push(this.WELCOME_ROUTE); 327 router.push(this.WELCOME_ROUTE);
diff --git a/src/styles/button.scss b/src/styles/button.scss
index 38f8fd26d..cf2568fc4 100644
--- a/src/styles/button.scss
+++ b/src/styles/button.scss
@@ -110,3 +110,32 @@
110 z-index: 9999; 110 z-index: 9999;
111 } 111 }
112} 112}
113
114.ferdi__fab {
115 box-sizing: border-box;
116
117 width: 50px;
118 height: 50px;
119
120 border-radius: 25px;
121 white-space: nowrap;
122 z-index: 9998;
123 list-style: none;
124 background: $theme-brand-primary;
125
126 position: absolute;
127 bottom: 20px;
128 right: 20px;
129
130 cursor: pointer;
131
132 display: flex;
133 justify-content: center;
134 align-items: center;
135
136 a {
137 font-size: 30px;
138 color: #FFFFFF;
139 cursor: pointer;
140 }
141} \ No newline at end of file
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index eeb825ece..d04f90830 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -1,50 +1,14 @@
1import { remote } from 'electron'; 1import { remote } from 'electron';
2import { ContextMenuBuilder, ContextMenuListener } from 'electron-spellchecker'; 2import ContextMenuBuilder from './contextMenuBuilder';
3 3
4const webContents = remote.getCurrentWebContents(); 4const webContents = remote.getCurrentWebContents();
5 5
6export default async function setupContextMenu(handler) { 6export default async function setupContextMenu() {
7 const addCustomMenuItems = (menu, menuInfo) => {
8 // Add "Paste as plain text" item when right-clicking editable content
9 if (
10 menuInfo.editFlags.canPaste
11 && !menuInfo.linkText
12 && !menuInfo.hasImageContents
13 ) {
14 menu.insert(
15 3,
16 new remote.MenuItem({
17 label: 'Paste as plain text',
18 accelerator: 'CommandOrControl+Shift+V',
19 click: () => webContents.pasteAndMatchStyle(),
20 }),
21 );
22 }
23
24 // Add "Open Link in Ferdi" item for links
25 if (menuInfo.linkURL) {
26 menu.insert(
27 2,
28 new remote.MenuItem({
29 label: 'Open Link in Ferdi',
30 click: () => {
31 window.location.href = menuInfo.linkURL;
32 },
33 }),
34 );
35 }
36
37 return menu;
38 };
39
40 const contextMenuBuilder = new ContextMenuBuilder( 7 const contextMenuBuilder = new ContextMenuBuilder(
41 handler, 8 webContents,
42 null,
43 true,
44 addCustomMenuItems,
45 ); 9 );
46 // eslint-disable-next-line no-new 10
47 new ContextMenuListener((info) => { 11 webContents.on('context-menu', (event, params) => {
48 contextMenuBuilder.showPopupMenu(info); 12 contextMenuBuilder.showPopupMenu(params);
49 }); 13 });
50} 14}
diff --git a/src/webview/contextMenuBuilder.js b/src/webview/contextMenuBuilder.js
new file mode 100644
index 000000000..ee27877b9
--- /dev/null
+++ b/src/webview/contextMenuBuilder.js
@@ -0,0 +1,419 @@
1/**
2 * Context Menu builder.
3 *
4 * Based on "electron-spellchecker"'s ContextMenuBuilder but customized for Ferdi
5 * and for usage with Electron's build-in spellchecker
6 *
7 * Source: https://github.com/electron-userland/electron-spellchecker/blob/master/src/context-menu-builder.js
8 */
9const {
10 clipboard, nativeImage, remote, shell,
11} = require('electron');
12
13const { Menu, MenuItem } = remote;
14
15function matchesWord(string) {
16 const regex = /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+/g;
17
18 return string.match(regex);
19}
20
21const contextMenuStringTable = {
22 copyMail: () => 'Copy Email Address',
23 copyLinkUrl: () => 'Copy Link',
24 openLinkUrl: () => 'Open Link',
25 copyImageUrl: () => 'Copy Image URL',
26 copyImage: () => 'Copy Image',
27 addToDictionary: () => 'Add to Dictionary',
28 lookUpDefinition: ({ word }) => `Look Up "${word}"`,
29 searchGoogle: () => 'Search with Google',
30 cut: () => 'Cut',
31 copy: () => 'Copy',
32 paste: () => 'Paste',
33 inspectElement: () => 'Inspect Element',
34};
35
36/**
37 * ContextMenuBuilder creates context menus based on the content clicked - this
38 * information is derived from
39 * https://github.com/electron/electron/blob/master/docs/api/web-contents.md#event-context-menu,
40 * which we use to generate the menu. We also use the spell-check information to
41 * generate suggestions.
42 */
43module.exports = class ContextMenuBuilder {
44 /**
45 * Creates an instance of ContextMenuBuilder
46 *
47 * @param {webContents} webContents Current webContents
48 * @param {Boolean} debugMode If true, display the "Inspect Element" menu item.
49 * @param {function} processMenu If passed, this method will be passed the menu to change
50 * it prior to display. Signature: (menu, info) => menu
51 */
52 constructor(webContents, debugMode = false, processMenu = m => m) {
53 this.debugMode = debugMode;
54 this.processMenu = processMenu;
55 this.menu = null;
56 this.stringTable = Object.assign({}, contextMenuStringTable);
57 this.getWebContents = () => webContents;
58 }
59
60 /**
61 * Specify alternate string formatter for each context menu.
62 * String table consist of string formatter as function instead per each context menu item,
63 * allows to change string in runtime. All formatters are simply typeof () => string, except
64 * lookUpDefinition provides word, ({word}) => string.
65 *
66 * @param {Object} stringTable The object contains string foramtter function for context menu.
67 * It is allowed to specify only certain menu string as necessary, which will makes other string
68 * fall backs to default.
69 *
70 */
71 setAlternateStringFormatter(stringTable) {
72 this.stringTable = Object.assign(this.stringTable, stringTable);
73 }
74
75 /**
76 * Shows a popup menu given the information returned from the context-menu
77 * event. This is probably the only method you need to call in this class.
78 *
79 * @param {Object} contextInfo The object returned from the 'context-menu'
80 * Electron event.
81 *
82 * @return {Promise} Completion
83 */
84 async showPopupMenu(contextInfo) {
85 const menu = await this.buildMenuForElement(contextInfo);
86 if (!menu) return;
87 menu.popup({});
88 }
89
90 /**
91 * Builds a context menu specific to the given info that _would_ be shown
92 * immediately by {{showPopupMenu}}. Use this to add your own menu items to
93 * the list but use most of the default behavior.
94 *
95 * @return {Promise<Menu>} The newly created `Menu`
96 */
97 async buildMenuForElement(info) {
98 if (info.linkURL && info.linkURL.length > 0) {
99 return this.buildMenuForLink(info);
100 }
101
102 if (info.hasImageContents && info.srcURL && info.srcURL.length > 1) {
103 return this.buildMenuForImage(info);
104 }
105
106 if (info.isEditable || (info.inputFieldType && info.inputFieldType !== 'none')) {
107 return this.buildMenuForTextInput(info);
108 }
109
110 return this.buildMenuForText(info);
111 }
112
113 /**
114 * Builds a menu applicable to a text input field.
115 *
116 * @return {Menu} The `Menu`
117 */
118 buildMenuForTextInput(menuInfo) {
119 const menu = new Menu();
120
121 this.addSpellingItems(menu, menuInfo);
122 this.addSearchItems(menu, menuInfo);
123
124 this.addCut(menu, menuInfo);
125 this.addCopy(menu, menuInfo);
126 this.addPaste(menu, menuInfo);
127 this.addPastePlain(menu, menuInfo);
128 this.addInspectElement(menu, menuInfo);
129 this.processMenu(menu, menuInfo);
130
131 return menu;
132 }
133
134 /**
135 * Builds a menu applicable to a link element.
136 *
137 * @return {Menu} The `Menu`
138 */
139 buildMenuForLink(menuInfo) {
140 const menu = new Menu();
141 const isEmailAddress = menuInfo.linkURL.startsWith('mailto:');
142
143 const copyLink = new MenuItem({
144 label: isEmailAddress ? this.stringTable.copyMail() : this.stringTable.copyLinkUrl(),
145 click: () => {
146 // Omit the mailto: portion of the link; we just want the address
147 clipboard.writeText(isEmailAddress
148 ? menuInfo.linkText : menuInfo.linkURL);
149 },
150 });
151
152 const openLink = new MenuItem({
153 label: this.stringTable.openLinkUrl(),
154 click: () => {
155 shell.openExternal(menuInfo.linkURL);
156 },
157 });
158
159 const openInFerdiLink = new MenuItem({
160 label: 'Open Link in Ferdi',
161 click: () => {
162 window.location.href = menuInfo.linkURL;
163 },
164 });
165
166 menu.append(copyLink);
167 menu.append(openLink);
168 menu.append(openInFerdiLink);
169
170 if (this.isSrcUrlValid(menuInfo)) {
171 this.addSeparator(menu);
172 this.addImageItems(menu, menuInfo);
173 }
174
175 this.addInspectElement(menu, menuInfo);
176 this.processMenu(menu, menuInfo);
177
178 return menu;
179 }
180
181 /**
182 * Builds a menu applicable to a text field.
183 *
184 * @return {Menu} The `Menu`
185 */
186 buildMenuForText(menuInfo) {
187 const menu = new Menu();
188
189 this.addSearchItems(menu, menuInfo);
190 this.addCopy(menu, menuInfo);
191 this.addInspectElement(menu, menuInfo);
192 this.processMenu(menu, menuInfo);
193
194 return menu;
195 }
196
197 /**
198 * Builds a menu applicable to an image.
199 *
200 * @return {Menu} The `Menu`
201 */
202 buildMenuForImage(menuInfo) {
203 const menu = new Menu();
204
205 if (this.isSrcUrlValid(menuInfo)) {
206 this.addImageItems(menu, menuInfo);
207 }
208 this.addInspectElement(menu, menuInfo);
209 this.processMenu(menu, menuInfo);
210
211 return menu;
212 }
213
214 /**
215 * Checks if the current text selection contains a single misspelled word and
216 * if so, adds suggested spellings as individual menu items.
217 */
218 addSpellingItems(menu, menuInfo) {
219 const target = this.getWebContents();
220 // Add each spelling suggestion
221 for (const suggestion of menuInfo.dictionarySuggestions) {
222 menu.append(new MenuItem({
223 label: suggestion,
224 // eslint-disable-next-line no-loop-func
225 click: () => target.replaceMisspelling(suggestion),
226 }));
227 }
228
229 // Allow users to add the misspelled word to the dictionary
230 if (menuInfo.misspelledWord) {
231 menu.append(
232 new MenuItem({
233 label: 'Add to dictionary',
234 click: () => target.session.addWordToSpellCheckerDictionary(menuInfo.misspelledWord),
235 }),
236 );
237 }
238
239 return menu;
240 }
241
242 /**
243 * Adds search-related menu items.
244 */
245 addSearchItems(menu, menuInfo) {
246 if (!menuInfo.selectionText || menuInfo.selectionText.length < 1) {
247 return menu;
248 }
249
250 const match = matchesWord(menuInfo.selectionText);
251 if (!match || match.length === 0) {
252 return menu;
253 }
254
255 if (process.platform === 'darwin') {
256 const target = this.getWebContents();
257
258 const lookUpDefinition = new MenuItem({
259 label: this.stringTable.lookUpDefinition({ word: menuInfo.selectionText.trim() }),
260 click: () => target.showDefinitionForSelection(),
261 });
262
263 menu.append(lookUpDefinition);
264 }
265
266 const search = new MenuItem({
267 label: this.stringTable.searchGoogle(),
268 click: () => {
269 const url = `https://www.google.com/#q=${encodeURIComponent(menuInfo.selectionText)}`;
270
271 shell.openExternal(url);
272 },
273 });
274
275 menu.append(search);
276 this.addSeparator(menu);
277
278 return menu;
279 }
280
281 isSrcUrlValid(menuInfo) {
282 return menuInfo.srcURL && menuInfo.srcURL.length > 0;
283 }
284
285 /**
286 * Adds "Copy Image" and "Copy Image URL" items when `src` is valid.
287 */
288 addImageItems(menu, menuInfo) {
289 const copyImage = new MenuItem({
290 label: this.stringTable.copyImage(),
291 click: () => this.convertImageToBase64(menuInfo.srcURL,
292 dataURL => clipboard.writeImage(nativeImage.createFromDataURL(dataURL))),
293 });
294
295 menu.append(copyImage);
296
297 const copyImageUrl = new MenuItem({
298 label: this.stringTable.copyImageUrl(),
299 click: () => clipboard.writeText(menuInfo.srcURL),
300 });
301
302 menu.append(copyImageUrl);
303 return menu;
304 }
305
306 /**
307 * Adds the Cut menu item
308 */
309 addCut(menu, menuInfo) {
310 const target = this.getWebContents();
311 menu.append(new MenuItem({
312 label: this.stringTable.cut(),
313 accelerator: 'CommandOrControl+X',
314 enabled: menuInfo.editFlags.canCut,
315 click: () => target.cut(),
316 }));
317
318 return menu;
319 }
320
321 /**
322 * Adds the Copy menu item.
323 */
324 addCopy(menu, menuInfo) {
325 const target = this.getWebContents();
326 menu.append(new MenuItem({
327 label: this.stringTable.copy(),
328 accelerator: 'CommandOrControl+C',
329 enabled: menuInfo.editFlags.canCopy,
330 click: () => target.copy(),
331 }));
332
333 return menu;
334 }
335
336 /**
337 * Adds the Paste menu item.
338 */
339 addPaste(menu, menuInfo) {
340 const target = this.getWebContents();
341 menu.append(new MenuItem({
342 label: this.stringTable.paste(),
343 accelerator: 'CommandOrControl+V',
344 enabled: menuInfo.editFlags.canPaste,
345 click: () => target.paste(),
346 }));
347
348 return menu;
349 }
350
351 addPastePlain(menu, menuInfo) {
352 if (
353 menuInfo.editFlags.canPaste
354 && !menuInfo.linkText
355 && !menuInfo.hasImageContents
356 ) {
357 const target = this.getWebContents();
358 menu.append(
359 new MenuItem({
360 label: 'Paste as plain text',
361 accelerator: 'CommandOrControl+Shift+V',
362 click: () => target.pasteAndMatchStyle(),
363 }),
364 );
365 }
366 }
367
368 /**
369 * Adds a separator item.
370 */
371 addSeparator(menu) {
372 menu.append(new MenuItem({ type: 'separator' }));
373 return menu;
374 }
375
376 /**
377 * Adds the "Inspect Element" menu item.
378 */
379 addInspectElement(menu, menuInfo, needsSeparator = true) {
380 const target = this.getWebContents();
381 if (!this.debugMode) return menu;
382 if (needsSeparator) this.addSeparator(menu);
383
384 const inspect = new MenuItem({
385 label: this.stringTable.inspectElement(),
386 click: () => target.inspectElement(menuInfo.x, menuInfo.y),
387 });
388
389 menu.append(inspect);
390 return menu;
391 }
392
393 /**
394 * Converts an image to a base-64 encoded string.
395 *
396 * @param {String} url The image URL
397 * @param {Function} callback A callback that will be invoked with the result
398 * @param {String} outputFormat The image format to use, defaults to 'image/png'
399 */
400 convertImageToBase64(url, callback, outputFormat = 'image/png') {
401 let canvas = document.createElement('CANVAS');
402 const ctx = canvas.getContext('2d');
403 // eslint-disable-next-line no-undef
404 const img = new Image();
405 img.crossOrigin = 'Anonymous';
406
407 img.onload = () => {
408 canvas.height = img.height;
409 canvas.width = img.width;
410 ctx.drawImage(img, 0, 0);
411
412 const dataURL = canvas.toDataURL(outputFormat);
413 canvas = null;
414 callback(dataURL);
415 };
416
417 img.src = url;
418 }
419};
diff --git a/src/webview/recipe.js b/src/webview/recipe.js
index b0aefd9c1..c6724e35a 100644
--- a/src/webview/recipe.js
+++ b/src/webview/recipe.js
@@ -65,7 +65,18 @@ class RecipeController {
65 } 65 }
66 66
67 @computed get spellcheckerLanguage() { 67 @computed get spellcheckerLanguage() {
68 return this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage; 68 let selected;
69 const langs = this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage;
70 if (typeof langs === 'string' && langs.substr(0, 1) === '[') {
71 // Value is JSON encoded
72 selected = JSON.parse(langs);
73 } else if (typeof langs === 'object') {
74 selected = langs;
75 } else {
76 selected = [langs];
77 }
78
79 return selected;
69 } 80 }
70 81
71 cldIdentifier = null; 82 cldIdentifier = null;
@@ -165,11 +176,11 @@ class RecipeController {
165 176
166 if (this.settings.app.enableSpellchecking) { 177 if (this.settings.app.enableSpellchecking) {
167 debug('Setting spellchecker language to', this.spellcheckerLanguage); 178 debug('Setting spellchecker language to', this.spellcheckerLanguage);
168 let { spellcheckerLanguage } = this; 179 const { spellcheckerLanguage } = this;
169 if (spellcheckerLanguage === 'automatic') { 180 if (spellcheckerLanguage.includes('automatic')) {
170 this.automaticLanguageDetection(); 181 this.automaticLanguageDetection();
171 debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale); 182 debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale);
172 spellcheckerLanguage = this.settings.app.locale; 183 spellcheckerLanguage.push(this.settings.app.locale);
173 } else if (this.cldIdentifier) { 184 } else if (this.cldIdentifier) {
174 this.cldIdentifier.destroy(); 185 this.cldIdentifier.destroy();
175 } 186 }
@@ -318,7 +329,10 @@ class RecipeController {
318 const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(findResult.language); 329 const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(findResult.language);
319 debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale); 330 debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale);
320 if (spellcheckerLocale) { 331 if (spellcheckerLocale) {
321 switchDict(spellcheckerLocale); 332 switchDict([
333 ...this.spellcheckerLanguage,
334 spellcheckerLocale,
335 ]);
322 } 336 }
323 } 337 }
324 }, 225)); 338 }, 225));
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
index a33a506b2..287c9cf11 100644
--- a/src/webview/spellchecker.js
+++ b/src/webview/spellchecker.js
@@ -1,53 +1,42 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import { SpellCheckHandler } from 'electron-spellchecker';
3import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 2import { SPELLCHECKER_LOCALES } from '../i18n/languages';
4import setupContextMenu from './contextMenu'; 3import setupContextMenu from './contextMenu';
5 4
6const debug = require('debug')('Franz:spellchecker'); 5const debug = require('debug')('Franz:spellchecker');
7 6
8let handler;
9let currentDict;
10let _isEnabled = false; 7let _isEnabled = false;
11 8
12export async function switchDict(locale) { 9export async function switchDict(locales) {
13 try { 10 const { platform } = process;
14 debug('Trying to load dictionary', locale); 11 if (platform === 'darwin') {
15 12 // MacOS uses the build-in languages which cannot be changed
16 if (!handler) { 13 return;
17 console.warn('SpellcheckHandler not initialized'); 14 }
18
19 return;
20 }
21
22 if (locale === currentDict) {
23 console.warn('Dictionary is already used', currentDict);
24 15
25 return; 16 try {
26 } 17 debug('Trying to load dictionary', locales);
27 18
28 handler.switchLanguage(locale); 19 webFrame.session.setSpellCheckerLanguages([...locales, 'en-US']);
29 20
30 debug('Switched dictionary to', locale); 21 debug('Switched dictionary to', locales);
31 22
32 currentDict = locale;
33 _isEnabled = true; 23 _isEnabled = true;
34 } catch (err) { 24 } catch (err) {
35 console.error(err); 25 console.error(err);
36 } 26 }
37} 27}
38 28
39export default async function initialize(languageCode = 'en-us') { 29export default async function initialize(languages = ['en-us']) {
40 try { 30 try {
41 handler = new SpellCheckHandler();
42 setTimeout(() => handler.attachToInput(), 1000);
43 const locale = languageCode.toLowerCase();
44
45 debug('Init spellchecker'); 31 debug('Init spellchecker');
46 32
47 switchDict(locale); 33 switchDict([
48 setupContextMenu(handler); 34 navigator.language,
35 ...languages,
36 ]);
37 setupContextMenu();
49 38
50 return handler; 39 return true;
51 } catch (err) { 40 } catch (err) {
52 console.error(err); 41 console.error(err);
53 return false; 42 return false;
@@ -60,10 +49,7 @@ export function isEnabled() {
60 49
61export function disable() { 50export function disable() {
62 if (isEnabled()) { 51 if (isEnabled()) {
63 handler.unsubscribe(); 52 // TODO: How to disable build-in spellchecker?
64 webFrame.setSpellCheckProvider(currentDict, { spellCheck: () => true });
65 _isEnabled = false;
66 currentDict = null;
67 } 53 }
68} 54}
69 55