aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.eslintrc12
-rw-r--r--gulpfile.babel.js2
-rw-r--r--src/api/server/ServerApi.js70
-rw-r--r--src/containers/auth/SetupAssistantScreen.js30
-rw-r--r--src/electron/macOSPermissions.js29
-rw-r--r--src/i18n/locales/defaultMessages.json256
-rw-r--r--src/i18n/messages/src/components/auth/Signup.json52
-rw-r--r--src/i18n/messages/src/lib/Menu.json204
-rw-r--r--src/index.js156
-rw-r--r--src/internal-server/app/Controllers/Http/ServiceController.js150
-rw-r--r--src/internal-server/app/Exceptions/Handler.js10
-rw-r--r--src/internal-server/public/js/transfer.js3
-rw-r--r--src/internal-server/start.js17
-rw-r--r--src/stores/ServicesStore.js378
-rw-r--r--src/webview/recipe.js164
15 files changed, 887 insertions, 646 deletions
diff --git a/.eslintrc b/.eslintrc
index 640e29aa6..c74b43cf5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -89,7 +89,8 @@
89 "localStorage": true, 89 "localStorage": true,
90 "navigator": true, 90 "navigator": true,
91 "Element": true, 91 "Element": true,
92 "use": true 92 "use": true,
93 "FileReader": true
93 }, 94 },
94 "env": { 95 "env": {
95 "jest/globals": true 96 "jest/globals": true
@@ -97,18 +98,23 @@
97 "rules": { 98 "rules": {
98 // eslint 99 // eslint
99 "arrow-parens": 0, 100 "arrow-parens": 0,
100 "class-methods-use-this": 1, 101 "class-methods-use-this": 0,
101 "consistent-return": 1, 102 "consistent-return": 1,
103 "implicit-arrow-linebreak": 0,
104 "function-paren-newline": 0,
102 "max-len": 0, 105 "max-len": 0,
106 "no-await-in-loop": 1,
107 "no-console": [1, { "allow": ["warn", "error"] }],
103 "no-param-reassign": 1, 108 "no-param-reassign": 1,
104 "no-restricted-syntax": 0, 109 "no-restricted-syntax": 0,
105 "no-underscore-dangle": 0, 110 "no-underscore-dangle": 0,
106 "operator-linebreak": 0, 111 "operator-linebreak": 0,
107 "prefer-destructuring": 1, 112 "prefer-destructuring": 1,
113 "object-curly-newline": 0,
108 // eslint-plugin-import 114 // eslint-plugin-import
109 "import/extensions": 1, 115 "import/extensions": 1,
110 "import/prefer-default-export": 0, 116 "import/prefer-default-export": 0,
111 "import/no-extraneous-dependencies": 1, 117 "import/no-extraneous-dependencies": 0, // various false positives, re-enable at some point
112 "import/no-unresolved": 1, 118 "import/no-unresolved": 1,
113 // eslint-plugin-react 119 // eslint-plugin-react
114 "react/forbid-prop-types": 1, 120 "react/forbid-prop-types": 1,
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index ed5245ee1..08cc34b63 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -18,7 +18,7 @@ import hexRgb from 'hex-rgb';
18import * as buildInfo from 'preval-build-info'; 18import * as buildInfo from 'preval-build-info';
19import config from './package.json'; 19import config from './package.json';
20 20
21import * as rawStyleConfig from './src/theme/default/legacy.js'; 21import * as rawStyleConfig from './src/theme/default/legacy';
22 22
23dotenv.config(); 23dotenv.config();
24 24
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index c60d64197..f91aeb23a 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -167,10 +167,10 @@ export default class ServerApi {
167 } 167 }
168 const data = await request.json(); 168 const data = await request.json();
169 169
170 let services = await this._mapServiceModels(data); 170 const services = await this._mapServiceModels(data);
171 services = services.filter((service) => service !== null); 171 const filteredServices = services.filter(service => !!service);
172 debug('ServerApi::getServices resolves', services); 172 debug('ServerApi::getServices resolves', filteredServices);
173 return services; 173 return filteredServices;
174 } 174 }
175 175
176 async createService(recipeId, data) { 176 async createService(recipeId, data) {
@@ -314,18 +314,19 @@ export default class ServerApi {
314 const paths = fs 314 const paths = fs
315 .readdirSync(recipesDirectory) 315 .readdirSync(recipesDirectory)
316 .filter( 316 .filter(
317 (file) => fs.statSync(path.join(recipesDirectory, file)).isDirectory() 317 file =>
318 && file !== 'temp' 318 fs.statSync(path.join(recipesDirectory, file)).isDirectory() &&
319 && file !== 'dev', 319 file !== 'temp' &&
320 file !== 'dev',
320 ); 321 );
321 322
322 this.recipes = paths 323 this.recipes = paths
323 .map((id) => { 324 .map(id => {
324 // eslint-disable-next-line 325 // eslint-disable-next-line
325 const Recipe = require(id)(RecipeModel); 326 const Recipe = require(id)(RecipeModel);
326 return new Recipe(loadRecipeConfig(id)); 327 return new Recipe(loadRecipeConfig(id));
327 }) 328 })
328 .filter((recipe) => recipe.id); 329 .filter(recipe => recipe.id);
329 330
330 this.recipes = this.recipes.concat(this._getDevRecipes()); 331 this.recipes = this.recipes.concat(this._getDevRecipes());
331 332
@@ -390,13 +391,11 @@ export default class ServerApi {
390 391
391 let archivePath; 392 let archivePath;
392 393
393 if (await fs.exists(internalRecipeFile)) { 394 if (fs.existsSync(internalRecipeFile)) {
394 console.log('[ServerApi::getRecipePackage] Using internal recipe file'); 395 debug('[ServerApi::getRecipePackage] Using internal recipe file');
395 archivePath = internalRecipeFile; 396 archivePath = internalRecipeFile;
396 } else { 397 } else {
397 console.log( 398 debug('[ServerApi::getRecipePackage] Downloading recipe from server');
398 '[ServerApi::getRecipePackage] Downloading recipe from server',
399 );
400 archivePath = tempArchivePath; 399 archivePath = tempArchivePath;
401 400
402 const packageUrl = `${apiBase()}/recipes/download/${recipeId}`; 401 const packageUrl = `${apiBase()}/recipes/download/${recipeId}`;
@@ -406,7 +405,7 @@ export default class ServerApi {
406 const buffer = await res.buffer(); 405 const buffer = await res.buffer();
407 fs.writeFileSync(archivePath, buffer); 406 fs.writeFileSync(archivePath, buffer);
408 } 407 }
409 console.log(archivePath); 408 debug(archivePath);
410 409
411 await sleep(10); 410 await sleep(10);
412 411
@@ -416,7 +415,7 @@ export default class ServerApi {
416 preservePaths: true, 415 preservePaths: true,
417 unlink: true, 416 unlink: true,
418 preserveOwner: false, 417 preserveOwner: false,
419 onwarn: (x) => console.log('warn', recipeId, x), 418 onwarn: x => debug('warn', recipeId, x),
420 }); 419 });
421 420
422 await sleep(10); 421 await sleep(10);
@@ -487,7 +486,7 @@ export default class ServerApi {
487 486
488 if (Object.prototype.hasOwnProperty.call(config, 'services')) { 487 if (Object.prototype.hasOwnProperty.call(config, 'services')) {
489 const services = await Promise.all( 488 const services = await Promise.all(
490 config.services.map(async (s) => { 489 config.services.map(async s => {
491 const service = s; 490 const service = s;
492 const request = await sendAuthRequest( 491 const request = await sendAuthRequest(
493 `${apiBase()}/recipes/${s.service}`, 492 `${apiBase()}/recipes/${s.service}`,
@@ -514,11 +513,11 @@ export default class ServerApi {
514 513
515 // Helper 514 // Helper
516 async _mapServiceModels(services) { 515 async _mapServiceModels(services) {
517 const recipes = services.map((s) => s.recipeId); 516 const recipes = services.map(s => s.recipeId);
518 await this._bulkRecipeCheck(recipes); 517 await this._bulkRecipeCheck(recipes);
519 /* eslint-disable no-return-await */ 518 /* eslint-disable no-return-await */
520 return Promise.all( 519 return Promise.all(
521 services.map(async (service) => await this._prepareServiceModel(service)), 520 services.map(async service => await this._prepareServiceModel(service)),
522 ); 521 );
523 /* eslint-enable no-return-await */ 522 /* eslint-enable no-return-await */
524 } 523 }
@@ -526,7 +525,7 @@ export default class ServerApi {
526 async _prepareServiceModel(service) { 525 async _prepareServiceModel(service) {
527 let recipe; 526 let recipe;
528 try { 527 try {
529 recipe = this.recipes.find((r) => r.id === service.recipeId); 528 recipe = this.recipes.find(r => r.id === service.recipeId);
530 529
531 if (!recipe) { 530 if (!recipe) {
532 console.warn(`Recipe ${service.recipeId} not loaded`); 531 console.warn(`Recipe ${service.recipeId} not loaded`);
@@ -547,8 +546,8 @@ export default class ServerApi {
547 ); 546 );
548 547
549 return Promise.all( 548 return Promise.all(
550 recipes.map(async (recipeId) => { 549 recipes.map(async recipeId => {
551 let recipe = this.recipes.find((r) => r.id === recipeId); 550 let recipe = this.recipes.find(r => r.id === recipeId);
552 551
553 if (!recipe) { 552 if (!recipe) {
554 console.warn( 553 console.warn(
@@ -560,7 +559,7 @@ export default class ServerApi {
560 debug('Rerun ServerAPI::getInstalledRecipes'); 559 debug('Rerun ServerAPI::getInstalledRecipes');
561 await this.getInstalledRecipes(); 560 await this.getInstalledRecipes();
562 561
563 recipe = this.recipes.find((r) => r.id === recipeId); 562 recipe = this.recipes.find(r => r.id === recipeId);
564 563
565 if (!recipe) { 564 if (!recipe) {
566 console.warn(`Could not load recipe ${recipeId}`); 565 console.warn(`Could not load recipe ${recipeId}`);
@@ -570,12 +569,12 @@ export default class ServerApi {
570 569
571 return recipe; 570 return recipe;
572 }), 571 }),
573 ).catch((err) => console.error("Can't load recipe", err)); 572 ).catch(err => console.error("Can't load recipe", err));
574 } 573 }
575 574
576 _mapRecipePreviewModel(recipes) { 575 _mapRecipePreviewModel(recipes) {
577 return recipes 576 return recipes
578 .map((recipe) => { 577 .map(recipe => {
579 try { 578 try {
580 return new RecipePreviewModel(recipe); 579 return new RecipePreviewModel(recipe);
581 } catch (e) { 580 } catch (e) {
@@ -583,12 +582,12 @@ export default class ServerApi {
583 return null; 582 return null;
584 } 583 }
585 }) 584 })
586 .filter((recipe) => recipe !== null); 585 .filter(recipe => recipe !== null);
587 } 586 }
588 587
589 _mapNewsModels(news) { 588 _mapNewsModels(news) {
590 return news 589 return news
591 .map((newsItem) => { 590 .map(newsItem => {
592 try { 591 try {
593 return new NewsModel(newsItem); 592 return new NewsModel(newsItem);
594 } catch (e) { 593 } catch (e) {
@@ -596,12 +595,12 @@ export default class ServerApi {
596 return null; 595 return null;
597 } 596 }
598 }) 597 })
599 .filter((newsItem) => newsItem !== null); 598 .filter(newsItem => newsItem !== null);
600 } 599 }
601 600
602 _mapOrderModels(orders) { 601 _mapOrderModels(orders) {
603 return orders 602 return orders
604 .map((orderItem) => { 603 .map(orderItem => {
605 try { 604 try {
606 return new OrderModel(orderItem); 605 return new OrderModel(orderItem);
607 } catch (e) { 606 } catch (e) {
@@ -609,7 +608,7 @@ export default class ServerApi {
609 return null; 608 return null;
610 } 609 }
611 }) 610 })
612 .filter((orderItem) => orderItem !== null); 611 .filter(orderItem => orderItem !== null);
613 } 612 }
614 613
615 _getDevRecipes() { 614 _getDevRecipes() {
@@ -618,12 +617,13 @@ export default class ServerApi {
618 const paths = fs 617 const paths = fs
619 .readdirSync(recipesDirectory) 618 .readdirSync(recipesDirectory)
620 .filter( 619 .filter(
621 (file) => fs.statSync(path.join(recipesDirectory, file)).isDirectory() 620 file =>
622 && file !== 'temp', 621 fs.statSync(path.join(recipesDirectory, file)).isDirectory() &&
622 file !== 'temp',
623 ); 623 );
624 624
625 const recipes = paths 625 const recipes = paths
626 .map((id) => { 626 .map(id => {
627 let Recipe; 627 let Recipe;
628 try { 628 try {
629 // eslint-disable-next-line 629 // eslint-disable-next-line
@@ -635,8 +635,8 @@ export default class ServerApi {
635 635
636 return false; 636 return false;
637 }) 637 })
638 .filter((recipe) => recipe.id) 638 .filter(recipe => recipe.id)
639 .map((data) => { 639 .map(data => {
640 const recipe = data; 640 const recipe = data;
641 641
642 recipe.icons = { 642 recipe.icons = {
diff --git a/src/containers/auth/SetupAssistantScreen.js b/src/containers/auth/SetupAssistantScreen.js
index d14085f63..d7036969a 100644
--- a/src/containers/auth/SetupAssistantScreen.js
+++ b/src/containers/auth/SetupAssistantScreen.js
@@ -4,14 +4,27 @@ import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5 5
6import { RouterStore } from 'mobx-react-router'; 6import { RouterStore } from 'mobx-react-router';
7import { DEFAULT_TODO_RECIPE_ID, DEFAULT_TODO_SERVICE_NAME } from '../../config'; 7import {
8 DEFAULT_TODO_RECIPE_ID,
9 DEFAULT_TODO_SERVICE_NAME,
10} from '../../config';
8import { sleep } from '../../helpers/async-helpers'; 11import { sleep } from '../../helpers/async-helpers';
9import SetupAssistant from '../../components/auth/SetupAssistant'; 12import SetupAssistant from '../../components/auth/SetupAssistant';
10import ServicesStore from '../../stores/ServicesStore'; 13import ServicesStore from '../../stores/ServicesStore';
11import RecipesStore from '../../stores/RecipesStore'; 14import RecipesStore from '../../stores/RecipesStore';
12import UserStore from '../../stores/UserStore'; 15import UserStore from '../../stores/UserStore';
13 16
14export default @inject('stores', 'actions') @observer class SetupAssistantScreen extends Component { 17export default
18@inject('stores', 'actions')
19@observer
20class SetupAssistantScreen extends Component {
21 constructor(props) {
22 super(props);
23 this.state = {
24 isSettingUpServices: false,
25 };
26 }
27
15 // TODO: Why are these hardcoded here? Do they need to conform to specific services in the packaged recipes? If so, its more important to fix this 28 // TODO: Why are these hardcoded here? Do they need to conform to specific services in the packaged recipes? If so, its more important to fix this
16 services = { 29 services = {
17 whatsapp: { 30 whatsapp: {
@@ -50,15 +63,12 @@ export default @inject('stores', 'actions') @observer class SetupAssistantScreen
50 name: 'LinkedIn', 63 name: 'LinkedIn',
51 hasTeamId: false, 64 hasTeamId: false,
52 }, 65 },
53 } 66 };
54
55 state = {
56 isSettingUpServices: false,
57 }
58 67
59 async setupServices(serviceConfig) { 68 async setupServices(serviceConfig) {
60 const { stores: { services } } = this.props; 69 const {
61 console.log(serviceConfig); 70 stores: { services },
71 } = this.props;
62 72
63 this.setState({ 73 this.setState({
64 isSettingUpServices: true, 74 isSettingUpServices: true,
@@ -102,7 +112,7 @@ export default @inject('stores', 'actions') @observer class SetupAssistantScreen
102 render() { 112 render() {
103 return ( 113 return (
104 <SetupAssistant 114 <SetupAssistant
105 onSubmit={(config) => this.setupServices(config)} 115 onSubmit={config => this.setupServices(config)}
106 services={this.services} 116 services={this.services}
107 embed={false} 117 embed={false}
108 isSettingUpServices={this.state.isSettingUpServices} 118 isSettingUpServices={this.state.isSettingUpServices}
diff --git a/src/electron/macOSPermissions.js b/src/electron/macOSPermissions.js
index 940b16c6e..c114f4843 100644
--- a/src/electron/macOSPermissions.js
+++ b/src/electron/macOSPermissions.js
@@ -2,18 +2,15 @@ import { app, systemPreferences, dialog } from 'electron';
2import fs from 'fs'; 2import fs from 'fs';
3import macosVersion from 'macos-version'; 3import macosVersion from 'macos-version';
4import path from 'path'; 4import path from 'path';
5import { isMac } from '../environment'; 5import { askForScreenCaptureAccess } from 'node-mac-permissions';
6
7let askForScreenCaptureAccess;
8if (isMac) {
9 // eslint-disable-next-line global-require
10 askForScreenCaptureAccess = require('node-mac-permissions').askForScreenCaptureAccess;
11}
12 6
13const debug = require('debug')('Ferdi:macOSPermissions'); 7const debug = require('debug')('Ferdi:macOSPermissions');
14 8
15const permissionExists = macosVersion.isGreaterThanOrEqualTo('10.15'); 9const permissionExists = macosVersion.isGreaterThanOrEqualTo('10.15');
16const filePath = path.join(app.getPath('userData'), '.has-app-requested-screen-capture-permissions'); 10const filePath = path.join(
11 app.getPath('userData'),
12 '.has-app-requested-screen-capture-permissions',
13);
17 14
18function hasPromptedForPermission() { 15function hasPromptedForPermission() {
19 if (!permissionExists) { 16 if (!permissionExists) {
@@ -49,7 +46,7 @@ function createStatusFile() {
49 } 46 }
50} 47}
51 48
52export default async function (mainWindow) { 49export const askFormacOSPermissions = async mainWindow => {
53 debug('Checking camera & microphone permissions'); 50 debug('Checking camera & microphone permissions');
54 systemPreferences.askForMediaAccess('camera'); 51 systemPreferences.askForMediaAccess('camera');
55 systemPreferences.askForMediaAccess('microphone'); 52 systemPreferences.askForMediaAccess('microphone');
@@ -60,24 +57,20 @@ export default async function (mainWindow) {
60 const { response } = await dialog.showMessageBox(mainWindow, { 57 const { response } = await dialog.showMessageBox(mainWindow, {
61 type: 'info', 58 type: 'info',
62 message: 'Enable Screen Sharing', 59 message: 'Enable Screen Sharing',
63 detail: 'To enable screen sharing for some services, Ferdi needs the permission to record your screen.', 60 detail:
64 buttons: [ 61 'To enable screen sharing for some services, Ferdi needs the permission to record your screen.',
65 'Allow screen sharing', 62 buttons: ['Allow screen sharing', 'No', 'Ask me later'],
66 'No',
67 'Ask me later',
68 ],
69 defaultId: 0, 63 defaultId: 0,
70 cancelId: 2, 64 cancelId: 2,
71 }); 65 });
72 66
73 console.log('result', response);
74 if (response === 0) { 67 if (response === 0) {
75 debug('Asking for access'); 68 debug('Asking for access');
76 askForScreenCaptureAccess(); 69 askForScreenCaptureAccess();
77 createStatusFile(); 70 createStatusFile();
78 } else if (response === 1) { 71 } else if (response === 1) {
79 debug('Don\'t ask again'); 72 debug("Don't ask again");
80 createStatusFile(); 73 createStatusFile();
81 } 74 }
82 } 75 }
83} 76};
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index d8050906b..f18a09940 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -817,169 +817,169 @@
817 "defaultMessage": "!!!Sign up", 817 "defaultMessage": "!!!Sign up",
818 "end": { 818 "end": {
819 "column": 3, 819 "column": 3,
820 "line": 22 820 "line": 23
821 }, 821 },
822 "file": "src/components/auth/Signup.js", 822 "file": "src/components/auth/Signup.js",
823 "id": "signup.headline", 823 "id": "signup.headline",
824 "start": { 824 "start": {
825 "column": 12, 825 "column": 12,
826 "line": 19 826 "line": 20
827 } 827 }
828 }, 828 },
829 { 829 {
830 "defaultMessage": "!!!Firstname", 830 "defaultMessage": "!!!Firstname",
831 "end": { 831 "end": {
832 "column": 3, 832 "column": 3,
833 "line": 26 833 "line": 27
834 }, 834 },
835 "file": "src/components/auth/Signup.js", 835 "file": "src/components/auth/Signup.js",
836 "id": "signup.firstname.label", 836 "id": "signup.firstname.label",
837 "start": { 837 "start": {
838 "column": 18, 838 "column": 18,
839 "line": 23 839 "line": 24
840 } 840 }
841 }, 841 },
842 { 842 {
843 "defaultMessage": "!!!Lastname", 843 "defaultMessage": "!!!Lastname",
844 "end": { 844 "end": {
845 "column": 3, 845 "column": 3,
846 "line": 30 846 "line": 31
847 }, 847 },
848 "file": "src/components/auth/Signup.js", 848 "file": "src/components/auth/Signup.js",
849 "id": "signup.lastname.label", 849 "id": "signup.lastname.label",
850 "start": { 850 "start": {
851 "column": 17, 851 "column": 17,
852 "line": 27 852 "line": 28
853 } 853 }
854 }, 854 },
855 { 855 {
856 "defaultMessage": "!!!Email address", 856 "defaultMessage": "!!!Email address",
857 "end": { 857 "end": {
858 "column": 3, 858 "column": 3,
859 "line": 34 859 "line": 35
860 }, 860 },
861 "file": "src/components/auth/Signup.js", 861 "file": "src/components/auth/Signup.js",
862 "id": "signup.email.label", 862 "id": "signup.email.label",
863 "start": { 863 "start": {
864 "column": 14, 864 "column": 14,
865 "line": 31 865 "line": 32
866 } 866 }
867 }, 867 },
868 { 868 {
869 "defaultMessage": "!!!Password", 869 "defaultMessage": "!!!Password",
870 "end": { 870 "end": {
871 "column": 3, 871 "column": 3,
872 "line": 42 872 "line": 43
873 }, 873 },
874 "file": "src/components/auth/Signup.js", 874 "file": "src/components/auth/Signup.js",
875 "id": "signup.password.label", 875 "id": "signup.password.label",
876 "start": { 876 "start": {
877 "column": 17, 877 "column": 17,
878 "line": 39 878 "line": 40
879 } 879 }
880 }, 880 },
881 { 881 {
882 "defaultMessage": "!!!By creating a Ferdi account you accept the", 882 "defaultMessage": "!!!By creating a Ferdi account you accept the",
883 "end": { 883 "end": {
884 "column": 3, 884 "column": 3,
885 "line": 46 885 "line": 47
886 }, 886 },
887 "file": "src/components/auth/Signup.js", 887 "file": "src/components/auth/Signup.js",
888 "id": "signup.legal.info", 888 "id": "signup.legal.info",
889 "start": { 889 "start": {
890 "column": 13, 890 "column": 13,
891 "line": 43 891 "line": 44
892 } 892 }
893 }, 893 },
894 { 894 {
895 "defaultMessage": "!!!Terms of service", 895 "defaultMessage": "!!!Terms of service",
896 "end": { 896 "end": {
897 "column": 3, 897 "column": 3,
898 "line": 50 898 "line": 51
899 }, 899 },
900 "file": "src/components/auth/Signup.js", 900 "file": "src/components/auth/Signup.js",
901 "id": "signup.legal.terms", 901 "id": "signup.legal.terms",
902 "start": { 902 "start": {
903 "column": 9, 903 "column": 9,
904 "line": 47 904 "line": 48
905 } 905 }
906 }, 906 },
907 { 907 {
908 "defaultMessage": "!!!Privacy Statement", 908 "defaultMessage": "!!!Privacy Statement",
909 "end": { 909 "end": {
910 "column": 3, 910 "column": 3,
911 "line": 54 911 "line": 55
912 }, 912 },
913 "file": "src/components/auth/Signup.js", 913 "file": "src/components/auth/Signup.js",
914 "id": "signup.legal.privacy", 914 "id": "signup.legal.privacy",
915 "start": { 915 "start": {
916 "column": 11, 916 "column": 11,
917 "line": 51 917 "line": 52
918 } 918 }
919 }, 919 },
920 { 920 {
921 "defaultMessage": "!!!Create account", 921 "defaultMessage": "!!!Create account",
922 "end": { 922 "end": {
923 "column": 3, 923 "column": 3,
924 "line": 58 924 "line": 59
925 }, 925 },
926 "file": "src/components/auth/Signup.js", 926 "file": "src/components/auth/Signup.js",
927 "id": "signup.submit.label", 927 "id": "signup.submit.label",
928 "start": { 928 "start": {
929 "column": 21, 929 "column": 21,
930 "line": 55 930 "line": 56
931 } 931 }
932 }, 932 },
933 { 933 {
934 "defaultMessage": "!!!Already have an account, sign in?", 934 "defaultMessage": "!!!Already have an account, sign in?",
935 "end": { 935 "end": {
936 "column": 3, 936 "column": 3,
937 "line": 62 937 "line": 63
938 }, 938 },
939 "file": "src/components/auth/Signup.js", 939 "file": "src/components/auth/Signup.js",
940 "id": "signup.link.login", 940 "id": "signup.link.login",
941 "start": { 941 "start": {
942 "column": 13, 942 "column": 13,
943 "line": 59 943 "line": 60
944 } 944 }
945 }, 945 },
946 { 946 {
947 "defaultMessage": "!!!Change server", 947 "defaultMessage": "!!!Change server",
948 "end": { 948 "end": {
949 "column": 3, 949 "column": 3,
950 "line": 66 950 "line": 67
951 }, 951 },
952 "file": "src/components/auth/Signup.js", 952 "file": "src/components/auth/Signup.js",
953 "id": "login.changeServer", 953 "id": "login.changeServer",
954 "start": { 954 "start": {
955 "column": 16, 955 "column": 16,
956 "line": 63 956 "line": 64
957 } 957 }
958 }, 958 },
959 { 959 {
960 "defaultMessage": "!!!Use Ferdi without an Account", 960 "defaultMessage": "!!!Use Ferdi without an Account",
961 "end": { 961 "end": {
962 "column": 3, 962 "column": 3,
963 "line": 70 963 "line": 71
964 }, 964 },
965 "file": "src/components/auth/Signup.js", 965 "file": "src/components/auth/Signup.js",
966 "id": "services.serverless", 966 "id": "services.serverless",
967 "start": { 967 "start": {
968 "column": 14, 968 "column": 14,
969 "line": 67 969 "line": 68
970 } 970 }
971 }, 971 },
972 { 972 {
973 "defaultMessage": "!!!A user with that email address already exists", 973 "defaultMessage": "!!!A user with that email address already exists",
974 "end": { 974 "end": {
975 "column": 3, 975 "column": 3,
976 "line": 74 976 "line": 75
977 }, 977 },
978 "file": "src/components/auth/Signup.js", 978 "file": "src/components/auth/Signup.js",
979 "id": "signup.emailDuplicate", 979 "id": "signup.emailDuplicate",
980 "start": { 980 "start": {
981 "column": 18, 981 "column": 18,
982 "line": 71 982 "line": 72
983 } 983 }
984 } 984 }
985 ], 985 ],
@@ -5929,663 +5929,663 @@
5929 "defaultMessage": "!!!Edit", 5929 "defaultMessage": "!!!Edit",
5930 "end": { 5930 "end": {
5931 "column": 3, 5931 "column": 3,
5932 "line": 22 5932 "line": 23
5933 }, 5933 },
5934 "file": "src/lib/Menu.js", 5934 "file": "src/lib/Menu.js",
5935 "id": "menu.edit", 5935 "id": "menu.edit",
5936 "start": { 5936 "start": {
5937 "column": 8, 5937 "column": 8,
5938 "line": 19 5938 "line": 20
5939 } 5939 }
5940 }, 5940 },
5941 { 5941 {
5942 "defaultMessage": "!!!View", 5942 "defaultMessage": "!!!View",
5943 "end": { 5943 "end": {
5944 "column": 3, 5944 "column": 3,
5945 "line": 26 5945 "line": 27
5946 }, 5946 },
5947 "file": "src/lib/Menu.js", 5947 "file": "src/lib/Menu.js",
5948 "id": "menu.view", 5948 "id": "menu.view",
5949 "start": { 5949 "start": {
5950 "column": 8, 5950 "column": 8,
5951 "line": 23 5951 "line": 24
5952 } 5952 }
5953 }, 5953 },
5954 { 5954 {
5955 "defaultMessage": "!!!Find in Page", 5955 "defaultMessage": "!!!Find in Page",
5956 "end": { 5956 "end": {
5957 "column": 3, 5957 "column": 3,
5958 "line": 30 5958 "line": 31
5959 }, 5959 },
5960 "file": "src/lib/Menu.js", 5960 "file": "src/lib/Menu.js",
5961 "id": "menu.edit.findInPage", 5961 "id": "menu.edit.findInPage",
5962 "start": { 5962 "start": {
5963 "column": 14, 5963 "column": 14,
5964 "line": 27 5964 "line": 28
5965 } 5965 }
5966 }, 5966 },
5967 { 5967 {
5968 "defaultMessage": "!!!Speech", 5968 "defaultMessage": "!!!Speech",
5969 "end": { 5969 "end": {
5970 "column": 3, 5970 "column": 3,
5971 "line": 34 5971 "line": 35
5972 }, 5972 },
5973 "file": "src/lib/Menu.js", 5973 "file": "src/lib/Menu.js",
5974 "id": "menu.edit.speech", 5974 "id": "menu.edit.speech",
5975 "start": { 5975 "start": {
5976 "column": 10, 5976 "column": 10,
5977 "line": 31 5977 "line": 32
5978 } 5978 }
5979 }, 5979 },
5980 { 5980 {
5981 "defaultMessage": "!!!Start Speaking", 5981 "defaultMessage": "!!!Start Speaking",
5982 "end": { 5982 "end": {
5983 "column": 3, 5983 "column": 3,
5984 "line": 38 5984 "line": 39
5985 }, 5985 },
5986 "file": "src/lib/Menu.js", 5986 "file": "src/lib/Menu.js",
5987 "id": "menu.edit.startSpeaking", 5987 "id": "menu.edit.startSpeaking",
5988 "start": { 5988 "start": {
5989 "column": 17, 5989 "column": 17,
5990 "line": 35 5990 "line": 36
5991 } 5991 }
5992 }, 5992 },
5993 { 5993 {
5994 "defaultMessage": "!!!Stop Speaking", 5994 "defaultMessage": "!!!Stop Speaking",
5995 "end": { 5995 "end": {
5996 "column": 3, 5996 "column": 3,
5997 "line": 42 5997 "line": 43
5998 }, 5998 },
5999 "file": "src/lib/Menu.js", 5999 "file": "src/lib/Menu.js",
6000 "id": "menu.edit.stopSpeaking", 6000 "id": "menu.edit.stopSpeaking",
6001 "start": { 6001 "start": {
6002 "column": 16, 6002 "column": 16,
6003 "line": 39 6003 "line": 40
6004 } 6004 }
6005 }, 6005 },
6006 { 6006 {
6007 "defaultMessage": "!!!Start Dictation", 6007 "defaultMessage": "!!!Start Dictation",
6008 "end": { 6008 "end": {
6009 "column": 3, 6009 "column": 3,
6010 "line": 46 6010 "line": 47
6011 }, 6011 },
6012 "file": "src/lib/Menu.js", 6012 "file": "src/lib/Menu.js",
6013 "id": "menu.edit.startDictation", 6013 "id": "menu.edit.startDictation",
6014 "start": { 6014 "start": {
6015 "column": 18, 6015 "column": 18,
6016 "line": 43 6016 "line": 44
6017 } 6017 }
6018 }, 6018 },
6019 { 6019 {
6020 "defaultMessage": "!!!Emoji & Symbols", 6020 "defaultMessage": "!!!Emoji & Symbols",
6021 "end": { 6021 "end": {
6022 "column": 3, 6022 "column": 3,
6023 "line": 50 6023 "line": 51
6024 }, 6024 },
6025 "file": "src/lib/Menu.js", 6025 "file": "src/lib/Menu.js",
6026 "id": "menu.edit.emojiSymbols", 6026 "id": "menu.edit.emojiSymbols",
6027 "start": { 6027 "start": {
6028 "column": 16, 6028 "column": 16,
6029 "line": 47 6029 "line": 48
6030 } 6030 }
6031 }, 6031 },
6032 { 6032 {
6033 "defaultMessage": "!!!Open Quick Switch", 6033 "defaultMessage": "!!!Open Quick Switch",
6034 "end": { 6034 "end": {
6035 "column": 3, 6035 "column": 3,
6036 "line": 54 6036 "line": 55
6037 }, 6037 },
6038 "file": "src/lib/Menu.js", 6038 "file": "src/lib/Menu.js",
6039 "id": "menu.view.openQuickSwitch", 6039 "id": "menu.view.openQuickSwitch",
6040 "start": { 6040 "start": {
6041 "column": 19, 6041 "column": 19,
6042 "line": 51 6042 "line": 52
6043 } 6043 }
6044 }, 6044 },
6045 { 6045 {
6046 "defaultMessage": "!!!Back", 6046 "defaultMessage": "!!!Back",
6047 "end": { 6047 "end": {
6048 "column": 3, 6048 "column": 3,
6049 "line": 58 6049 "line": 59
6050 }, 6050 },
6051 "file": "src/lib/Menu.js", 6051 "file": "src/lib/Menu.js",
6052 "id": "menu.view.back", 6052 "id": "menu.view.back",
6053 "start": { 6053 "start": {
6054 "column": 8, 6054 "column": 8,
6055 "line": 55 6055 "line": 56
6056 } 6056 }
6057 }, 6057 },
6058 { 6058 {
6059 "defaultMessage": "!!!Forward", 6059 "defaultMessage": "!!!Forward",
6060 "end": { 6060 "end": {
6061 "column": 3, 6061 "column": 3,
6062 "line": 62 6062 "line": 63
6063 }, 6063 },
6064 "file": "src/lib/Menu.js", 6064 "file": "src/lib/Menu.js",
6065 "id": "menu.view.forward", 6065 "id": "menu.view.forward",
6066 "start": { 6066 "start": {
6067 "column": 11, 6067 "column": 11,
6068 "line": 59 6068 "line": 60
6069 } 6069 }
6070 }, 6070 },
6071 { 6071 {
6072 "defaultMessage": "!!!Toggle Dark Mode", 6072 "defaultMessage": "!!!Toggle Dark Mode",
6073 "end": { 6073 "end": {
6074 "column": 3, 6074 "column": 3,
6075 "line": 66 6075 "line": 67
6076 }, 6076 },
6077 "file": "src/lib/Menu.js", 6077 "file": "src/lib/Menu.js",
6078 "id": "menu.view.toggleDarkMode", 6078 "id": "menu.view.toggleDarkMode",
6079 "start": { 6079 "start": {
6080 "column": 18, 6080 "column": 18,
6081 "line": 63 6081 "line": 64
6082 } 6082 }
6083 }, 6083 },
6084 { 6084 {
6085 "defaultMessage": "!!!Toggle Developer Tools", 6085 "defaultMessage": "!!!Toggle Developer Tools",
6086 "end": { 6086 "end": {
6087 "column": 3, 6087 "column": 3,
6088 "line": 70 6088 "line": 71
6089 }, 6089 },
6090 "file": "src/lib/Menu.js", 6090 "file": "src/lib/Menu.js",
6091 "id": "menu.view.toggleDevTools", 6091 "id": "menu.view.toggleDevTools",
6092 "start": { 6092 "start": {
6093 "column": 18, 6093 "column": 18,
6094 "line": 67 6094 "line": 68
6095 } 6095 }
6096 }, 6096 },
6097 { 6097 {
6098 "defaultMessage": "!!!Toggle Todos Developer Tools", 6098 "defaultMessage": "!!!Toggle Todos Developer Tools",
6099 "end": { 6099 "end": {
6100 "column": 3, 6100 "column": 3,
6101 "line": 74 6101 "line": 75
6102 }, 6102 },
6103 "file": "src/lib/Menu.js", 6103 "file": "src/lib/Menu.js",
6104 "id": "menu.view.toggleTodosDevTools", 6104 "id": "menu.view.toggleTodosDevTools",
6105 "start": { 6105 "start": {
6106 "column": 23, 6106 "column": 23,
6107 "line": 71 6107 "line": 72
6108 } 6108 }
6109 }, 6109 },
6110 { 6110 {
6111 "defaultMessage": "!!!Toggle Service Developer Tools", 6111 "defaultMessage": "!!!Toggle Service Developer Tools",
6112 "end": { 6112 "end": {
6113 "column": 3, 6113 "column": 3,
6114 "line": 78 6114 "line": 79
6115 }, 6115 },
6116 "file": "src/lib/Menu.js", 6116 "file": "src/lib/Menu.js",
6117 "id": "menu.view.toggleServiceDevTools", 6117 "id": "menu.view.toggleServiceDevTools",
6118 "start": { 6118 "start": {
6119 "column": 25, 6119 "column": 25,
6120 "line": 75 6120 "line": 76
6121 } 6121 }
6122 }, 6122 },
6123 { 6123 {
6124 "defaultMessage": "!!!Reload Service", 6124 "defaultMessage": "!!!Reload Service",
6125 "end": { 6125 "end": {
6126 "column": 3, 6126 "column": 3,
6127 "line": 82 6127 "line": 83
6128 }, 6128 },
6129 "file": "src/lib/Menu.js", 6129 "file": "src/lib/Menu.js",
6130 "id": "menu.view.reloadService", 6130 "id": "menu.view.reloadService",
6131 "start": { 6131 "start": {
6132 "column": 17, 6132 "column": 17,
6133 "line": 79 6133 "line": 80
6134 } 6134 }
6135 }, 6135 },
6136 { 6136 {
6137 "defaultMessage": "!!!Reload Ferdi", 6137 "defaultMessage": "!!!Reload Ferdi",
6138 "end": { 6138 "end": {
6139 "column": 3, 6139 "column": 3,
6140 "line": 86 6140 "line": 87
6141 }, 6141 },
6142 "file": "src/lib/Menu.js", 6142 "file": "src/lib/Menu.js",
6143 "id": "menu.view.reloadFerdi", 6143 "id": "menu.view.reloadFerdi",
6144 "start": { 6144 "start": {
6145 "column": 15, 6145 "column": 15,
6146 "line": 83 6146 "line": 84
6147 } 6147 }
6148 }, 6148 },
6149 { 6149 {
6150 "defaultMessage": "!!!Lock Ferdi", 6150 "defaultMessage": "!!!Lock Ferdi",
6151 "end": { 6151 "end": {
6152 "column": 3, 6152 "column": 3,
6153 "line": 90 6153 "line": 91
6154 }, 6154 },
6155 "file": "src/lib/Menu.js", 6155 "file": "src/lib/Menu.js",
6156 "id": "menu.view.lockFerdi", 6156 "id": "menu.view.lockFerdi",
6157 "start": { 6157 "start": {
6158 "column": 13, 6158 "column": 13,
6159 "line": 87 6159 "line": 88
6160 } 6160 }
6161 }, 6161 },
6162 { 6162 {
6163 "defaultMessage": "!!!Reload ToDos", 6163 "defaultMessage": "!!!Reload ToDos",
6164 "end": { 6164 "end": {
6165 "column": 3, 6165 "column": 3,
6166 "line": 94 6166 "line": 95
6167 }, 6167 },
6168 "file": "src/lib/Menu.js", 6168 "file": "src/lib/Menu.js",
6169 "id": "menu.view.reloadTodos", 6169 "id": "menu.view.reloadTodos",
6170 "start": { 6170 "start": {
6171 "column": 15, 6171 "column": 15,
6172 "line": 91 6172 "line": 92
6173 } 6173 }
6174 }, 6174 },
6175 { 6175 {
6176 "defaultMessage": "!!!Learn More", 6176 "defaultMessage": "!!!Learn More",
6177 "end": { 6177 "end": {
6178 "column": 3, 6178 "column": 3,
6179 "line": 98 6179 "line": 99
6180 }, 6180 },
6181 "file": "src/lib/Menu.js", 6181 "file": "src/lib/Menu.js",
6182 "id": "menu.help.learnMore", 6182 "id": "menu.help.learnMore",
6183 "start": { 6183 "start": {
6184 "column": 13, 6184 "column": 13,
6185 "line": 95 6185 "line": 96
6186 } 6186 }
6187 }, 6187 },
6188 { 6188 {
6189 "defaultMessage": "!!!Changelog", 6189 "defaultMessage": "!!!Changelog",
6190 "end": { 6190 "end": {
6191 "column": 3, 6191 "column": 3,
6192 "line": 102 6192 "line": 103
6193 }, 6193 },
6194 "file": "src/lib/Menu.js", 6194 "file": "src/lib/Menu.js",
6195 "id": "menu.help.changelog", 6195 "id": "menu.help.changelog",
6196 "start": { 6196 "start": {
6197 "column": 13, 6197 "column": 13,
6198 "line": 99 6198 "line": 100
6199 } 6199 }
6200 }, 6200 },
6201 { 6201 {
6202 "defaultMessage": "!!!Support", 6202 "defaultMessage": "!!!Support",
6203 "end": { 6203 "end": {
6204 "column": 3, 6204 "column": 3,
6205 "line": 106 6205 "line": 107
6206 }, 6206 },
6207 "file": "src/lib/Menu.js", 6207 "file": "src/lib/Menu.js",
6208 "id": "menu.help.support", 6208 "id": "menu.help.support",
6209 "start": { 6209 "start": {
6210 "column": 11, 6210 "column": 11,
6211 "line": 103 6211 "line": 104
6212 } 6212 }
6213 }, 6213 },
6214 { 6214 {
6215 "defaultMessage": "!!!Copy Debug Information", 6215 "defaultMessage": "!!!Copy Debug Information",
6216 "end": { 6216 "end": {
6217 "column": 3, 6217 "column": 3,
6218 "line": 110 6218 "line": 111
6219 }, 6219 },
6220 "file": "src/lib/Menu.js", 6220 "file": "src/lib/Menu.js",
6221 "id": "menu.help.debugInfo", 6221 "id": "menu.help.debugInfo",
6222 "start": { 6222 "start": {
6223 "column": 13, 6223 "column": 13,
6224 "line": 107 6224 "line": 108
6225 } 6225 }
6226 }, 6226 },
6227 { 6227 {
6228 "defaultMessage": "!!!Publish Debug Information", 6228 "defaultMessage": "!!!Publish Debug Information",
6229 "end": { 6229 "end": {
6230 "column": 3, 6230 "column": 3,
6231 "line": 114 6231 "line": 115
6232 }, 6232 },
6233 "file": "src/lib/Menu.js", 6233 "file": "src/lib/Menu.js",
6234 "id": "menu.help.publishDebugInfo", 6234 "id": "menu.help.publishDebugInfo",
6235 "start": { 6235 "start": {
6236 "column": 20, 6236 "column": 20,
6237 "line": 111 6237 "line": 112
6238 } 6238 }
6239 }, 6239 },
6240 { 6240 {
6241 "defaultMessage": "!!!Ferdi Debug Information", 6241 "defaultMessage": "!!!Ferdi Debug Information",
6242 "end": { 6242 "end": {
6243 "column": 3, 6243 "column": 3,
6244 "line": 118 6244 "line": 119
6245 }, 6245 },
6246 "file": "src/lib/Menu.js", 6246 "file": "src/lib/Menu.js",
6247 "id": "menu.help.debugInfoCopiedHeadline", 6247 "id": "menu.help.debugInfoCopiedHeadline",
6248 "start": { 6248 "start": {
6249 "column": 27, 6249 "column": 27,
6250 "line": 115 6250 "line": 116
6251 } 6251 }
6252 }, 6252 },
6253 { 6253 {
6254 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.", 6254 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.",
6255 "end": { 6255 "end": {
6256 "column": 3, 6256 "column": 3,
6257 "line": 122 6257 "line": 123
6258 }, 6258 },
6259 "file": "src/lib/Menu.js", 6259 "file": "src/lib/Menu.js",
6260 "id": "menu.help.debugInfoCopiedBody", 6260 "id": "menu.help.debugInfoCopiedBody",
6261 "start": { 6261 "start": {
6262 "column": 23, 6262 "column": 23,
6263 "line": 119 6263 "line": 120
6264 } 6264 }
6265 }, 6265 },
6266 { 6266 {
6267 "defaultMessage": "!!!Unlock with Touch ID", 6267 "defaultMessage": "!!!Unlock with Touch ID",
6268 "end": { 6268 "end": {
6269 "column": 3, 6269 "column": 3,
6270 "line": 126 6270 "line": 127
6271 }, 6271 },
6272 "file": "src/lib/Menu.js", 6272 "file": "src/lib/Menu.js",
6273 "id": "locked.touchId", 6273 "id": "locked.touchId",
6274 "start": { 6274 "start": {
6275 "column": 11, 6275 "column": 11,
6276 "line": 123 6276 "line": 124
6277 } 6277 }
6278 }, 6278 },
6279 { 6279 {
6280 "defaultMessage": "!!!unlock via Touch ID", 6280 "defaultMessage": "!!!unlock via Touch ID",
6281 "end": { 6281 "end": {
6282 "column": 3, 6282 "column": 3,
6283 "line": 130 6283 "line": 131
6284 }, 6284 },
6285 "file": "src/lib/Menu.js", 6285 "file": "src/lib/Menu.js",
6286 "id": "locked.touchIdPrompt", 6286 "id": "locked.touchIdPrompt",
6287 "start": { 6287 "start": {
6288 "column": 17, 6288 "column": 17,
6289 "line": 127 6289 "line": 128
6290 } 6290 }
6291 }, 6291 },
6292 { 6292 {
6293 "defaultMessage": "!!!Terms of Service", 6293 "defaultMessage": "!!!Terms of Service",
6294 "end": { 6294 "end": {
6295 "column": 3, 6295 "column": 3,
6296 "line": 134 6296 "line": 135
6297 }, 6297 },
6298 "file": "src/lib/Menu.js", 6298 "file": "src/lib/Menu.js",
6299 "id": "menu.help.tos", 6299 "id": "menu.help.tos",
6300 "start": { 6300 "start": {
6301 "column": 7, 6301 "column": 7,
6302 "line": 131 6302 "line": 132
6303 } 6303 }
6304 }, 6304 },
6305 { 6305 {
6306 "defaultMessage": "!!!Privacy Statement", 6306 "defaultMessage": "!!!Privacy Statement",
6307 "end": { 6307 "end": {
6308 "column": 3, 6308 "column": 3,
6309 "line": 138 6309 "line": 139
6310 }, 6310 },
6311 "file": "src/lib/Menu.js", 6311 "file": "src/lib/Menu.js",
6312 "id": "menu.help.privacy", 6312 "id": "menu.help.privacy",
6313 "start": { 6313 "start": {
6314 "column": 11, 6314 "column": 11,
6315 "line": 135 6315 "line": 136
6316 } 6316 }
6317 }, 6317 },
6318 { 6318 {
6319 "defaultMessage": "!!!File", 6319 "defaultMessage": "!!!File",
6320 "end": { 6320 "end": {
6321 "column": 3, 6321 "column": 3,
6322 "line": 142 6322 "line": 143
6323 }, 6323 },
6324 "file": "src/lib/Menu.js", 6324 "file": "src/lib/Menu.js",
6325 "id": "menu.file", 6325 "id": "menu.file",
6326 "start": { 6326 "start": {
6327 "column": 8, 6327 "column": 8,
6328 "line": 139 6328 "line": 140
6329 } 6329 }
6330 }, 6330 },
6331 { 6331 {
6332 "defaultMessage": "!!!Services", 6332 "defaultMessage": "!!!Services",
6333 "end": { 6333 "end": {
6334 "column": 3, 6334 "column": 3,
6335 "line": 146 6335 "line": 147
6336 }, 6336 },
6337 "file": "src/lib/Menu.js", 6337 "file": "src/lib/Menu.js",
6338 "id": "menu.services", 6338 "id": "menu.services",
6339 "start": { 6339 "start": {
6340 "column": 12, 6340 "column": 12,
6341 "line": 143 6341 "line": 144
6342 } 6342 }
6343 }, 6343 },
6344 { 6344 {
6345 "defaultMessage": "!!!What's new?", 6345 "defaultMessage": "!!!What's new?",
6346 "end": { 6346 "end": {
6347 "column": 3, 6347 "column": 3,
6348 "line": 150 6348 "line": 151
6349 }, 6349 },
6350 "file": "src/lib/Menu.js", 6350 "file": "src/lib/Menu.js",
6351 "id": "menu.app.announcement", 6351 "id": "menu.app.announcement",
6352 "start": { 6352 "start": {
6353 "column": 16, 6353 "column": 16,
6354 "line": 147 6354 "line": 148
6355 } 6355 }
6356 }, 6356 },
6357 { 6357 {
6358 "defaultMessage": "!!!Settings", 6358 "defaultMessage": "!!!Settings",
6359 "end": { 6359 "end": {
6360 "column": 3, 6360 "column": 3,
6361 "line": 154 6361 "line": 155
6362 }, 6362 },
6363 "file": "src/lib/Menu.js", 6363 "file": "src/lib/Menu.js",
6364 "id": "menu.app.settings", 6364 "id": "menu.app.settings",
6365 "start": { 6365 "start": {
6366 "column": 12, 6366 "column": 12,
6367 "line": 151 6367 "line": 152
6368 } 6368 }
6369 }, 6369 },
6370 { 6370 {
6371 "defaultMessage": "!!!Check for updates", 6371 "defaultMessage": "!!!Check for updates",
6372 "end": { 6372 "end": {
6373 "column": 3, 6373 "column": 3,
6374 "line": 158 6374 "line": 159
6375 }, 6375 },
6376 "file": "src/lib/Menu.js", 6376 "file": "src/lib/Menu.js",
6377 "id": "menu.app.checkForUpdates", 6377 "id": "menu.app.checkForUpdates",
6378 "start": { 6378 "start": {
6379 "column": 19, 6379 "column": 19,
6380 "line": 155 6380 "line": 156
6381 } 6381 }
6382 }, 6382 },
6383 { 6383 {
6384 "defaultMessage": "!!!Auto-hide menu bar", 6384 "defaultMessage": "!!!Auto-hide menu bar",
6385 "end": { 6385 "end": {
6386 "column": 3, 6386 "column": 3,
6387 "line": 162 6387 "line": 163
6388 }, 6388 },
6389 "file": "src/lib/Menu.js", 6389 "file": "src/lib/Menu.js",
6390 "id": "menu.app.autohideMenuBar", 6390 "id": "menu.app.autohideMenuBar",
6391 "start": { 6391 "start": {
6392 "column": 19, 6392 "column": 19,
6393 "line": 159 6393 "line": 160
6394 } 6394 }
6395 }, 6395 },
6396 { 6396 {
6397 "defaultMessage": "!!!Add New Service...", 6397 "defaultMessage": "!!!Add New Service...",
6398 "end": { 6398 "end": {
6399 "column": 3, 6399 "column": 3,
6400 "line": 166 6400 "line": 167
6401 }, 6401 },
6402 "file": "src/lib/Menu.js", 6402 "file": "src/lib/Menu.js",
6403 "id": "menu.services.addNewService", 6403 "id": "menu.services.addNewService",
6404 "start": { 6404 "start": {
6405 "column": 17, 6405 "column": 17,
6406 "line": 163 6406 "line": 164
6407 } 6407 }
6408 }, 6408 },
6409 { 6409 {
6410 "defaultMessage": "!!!Add New Workspace...", 6410 "defaultMessage": "!!!Add New Workspace...",
6411 "end": { 6411 "end": {
6412 "column": 3, 6412 "column": 3,
6413 "line": 170 6413 "line": 171
6414 }, 6414 },
6415 "file": "src/lib/Menu.js", 6415 "file": "src/lib/Menu.js",
6416 "id": "menu.workspaces.addNewWorkspace", 6416 "id": "menu.workspaces.addNewWorkspace",
6417 "start": { 6417 "start": {
6418 "column": 19, 6418 "column": 19,
6419 "line": 167 6419 "line": 168
6420 } 6420 }
6421 }, 6421 },
6422 { 6422 {
6423 "defaultMessage": "!!!Open workspace drawer", 6423 "defaultMessage": "!!!Open workspace drawer",
6424 "end": { 6424 "end": {
6425 "column": 3, 6425 "column": 3,
6426 "line": 174 6426 "line": 175
6427 }, 6427 },
6428 "file": "src/lib/Menu.js", 6428 "file": "src/lib/Menu.js",
6429 "id": "menu.workspaces.openWorkspaceDrawer", 6429 "id": "menu.workspaces.openWorkspaceDrawer",
6430 "start": { 6430 "start": {
6431 "column": 23, 6431 "column": 23,
6432 "line": 171 6432 "line": 172
6433 } 6433 }
6434 }, 6434 },
6435 { 6435 {
6436 "defaultMessage": "!!!Close workspace drawer", 6436 "defaultMessage": "!!!Close workspace drawer",
6437 "end": { 6437 "end": {
6438 "column": 3, 6438 "column": 3,
6439 "line": 178 6439 "line": 179
6440 }, 6440 },
6441 "file": "src/lib/Menu.js", 6441 "file": "src/lib/Menu.js",
6442 "id": "menu.workspaces.closeWorkspaceDrawer", 6442 "id": "menu.workspaces.closeWorkspaceDrawer",
6443 "start": { 6443 "start": {
6444 "column": 24, 6444 "column": 24,
6445 "line": 175 6445 "line": 176
6446 } 6446 }
6447 }, 6447 },
6448 { 6448 {
6449 "defaultMessage": "!!!Activate next service...", 6449 "defaultMessage": "!!!Activate next service...",
6450 "end": { 6450 "end": {
6451 "column": 3, 6451 "column": 3,
6452 "line": 182 6452 "line": 183
6453 }, 6453 },
6454 "file": "src/lib/Menu.js", 6454 "file": "src/lib/Menu.js",
6455 "id": "menu.services.setNextServiceActive", 6455 "id": "menu.services.setNextServiceActive",
6456 "start": { 6456 "start": {
6457 "column": 23, 6457 "column": 23,
6458 "line": 179 6458 "line": 180
6459 } 6459 }
6460 }, 6460 },
6461 { 6461 {
6462 "defaultMessage": "!!!Activate previous service...", 6462 "defaultMessage": "!!!Activate previous service...",
6463 "end": { 6463 "end": {
6464 "column": 3, 6464 "column": 3,
6465 "line": 186 6465 "line": 187
6466 }, 6466 },
6467 "file": "src/lib/Menu.js", 6467 "file": "src/lib/Menu.js",
6468 "id": "menu.services.activatePreviousService", 6468 "id": "menu.services.activatePreviousService",
6469 "start": { 6469 "start": {
6470 "column": 27, 6470 "column": 27,
6471 "line": 183 6471 "line": 184
6472 } 6472 }
6473 }, 6473 },
6474 { 6474 {
6475 "defaultMessage": "!!!Disable notifications & audio", 6475 "defaultMessage": "!!!Disable notifications & audio",
6476 "end": { 6476 "end": {
6477 "column": 3, 6477 "column": 3,
6478 "line": 190 6478 "line": 191
6479 }, 6479 },
6480 "file": "src/lib/Menu.js", 6480 "file": "src/lib/Menu.js",
6481 "id": "sidebar.muteApp", 6481 "id": "sidebar.muteApp",
6482 "start": { 6482 "start": {
6483 "column": 11, 6483 "column": 11,
6484 "line": 187 6484 "line": 188
6485 } 6485 }
6486 }, 6486 },
6487 { 6487 {
6488 "defaultMessage": "!!!Enable notifications & audio", 6488 "defaultMessage": "!!!Enable notifications & audio",
6489 "end": { 6489 "end": {
6490 "column": 3, 6490 "column": 3,
6491 "line": 194 6491 "line": 195
6492 }, 6492 },
6493 "file": "src/lib/Menu.js", 6493 "file": "src/lib/Menu.js",
6494 "id": "sidebar.unmuteApp", 6494 "id": "sidebar.unmuteApp",
6495 "start": { 6495 "start": {
6496 "column": 13, 6496 "column": 13,
6497 "line": 191 6497 "line": 192
6498 } 6498 }
6499 }, 6499 },
6500 { 6500 {
6501 "defaultMessage": "!!!Workspaces", 6501 "defaultMessage": "!!!Workspaces",
6502 "end": { 6502 "end": {
6503 "column": 3, 6503 "column": 3,
6504 "line": 198 6504 "line": 199
6505 }, 6505 },
6506 "file": "src/lib/Menu.js", 6506 "file": "src/lib/Menu.js",
6507 "id": "menu.workspaces", 6507 "id": "menu.workspaces",
6508 "start": { 6508 "start": {
6509 "column": 14, 6509 "column": 14,
6510 "line": 195 6510 "line": 196
6511 } 6511 }
6512 }, 6512 },
6513 { 6513 {
6514 "defaultMessage": "!!!Default", 6514 "defaultMessage": "!!!Default",
6515 "end": { 6515 "end": {
6516 "column": 3, 6516 "column": 3,
6517 "line": 202 6517 "line": 203
6518 }, 6518 },
6519 "file": "src/lib/Menu.js", 6519 "file": "src/lib/Menu.js",
6520 "id": "menu.workspaces.defaultWorkspace", 6520 "id": "menu.workspaces.defaultWorkspace",
6521 "start": { 6521 "start": {
6522 "column": 20, 6522 "column": 20,
6523 "line": 199 6523 "line": 200
6524 } 6524 }
6525 }, 6525 },
6526 { 6526 {
6527 "defaultMessage": "!!!Todos", 6527 "defaultMessage": "!!!Todos",
6528 "end": { 6528 "end": {
6529 "column": 3, 6529 "column": 3,
6530 "line": 206 6530 "line": 207
6531 }, 6531 },
6532 "file": "src/lib/Menu.js", 6532 "file": "src/lib/Menu.js",
6533 "id": "menu.todos", 6533 "id": "menu.todos",
6534 "start": { 6534 "start": {
6535 "column": 9, 6535 "column": 9,
6536 "line": 203 6536 "line": 204
6537 } 6537 }
6538 }, 6538 },
6539 { 6539 {
6540 "defaultMessage": "!!!Open Todos drawer", 6540 "defaultMessage": "!!!Open Todos drawer",
6541 "end": { 6541 "end": {
6542 "column": 3, 6542 "column": 3,
6543 "line": 210 6543 "line": 211
6544 }, 6544 },
6545 "file": "src/lib/Menu.js", 6545 "file": "src/lib/Menu.js",
6546 "id": "menu.Todoss.openTodosDrawer", 6546 "id": "menu.Todoss.openTodosDrawer",
6547 "start": { 6547 "start": {
6548 "column": 19, 6548 "column": 19,
6549 "line": 207 6549 "line": 208
6550 } 6550 }
6551 }, 6551 },
6552 { 6552 {
6553 "defaultMessage": "!!!Close Todos drawer", 6553 "defaultMessage": "!!!Close Todos drawer",
6554 "end": { 6554 "end": {
6555 "column": 3, 6555 "column": 3,
6556 "line": 214 6556 "line": 215
6557 }, 6557 },
6558 "file": "src/lib/Menu.js", 6558 "file": "src/lib/Menu.js",
6559 "id": "menu.Todoss.closeTodosDrawer", 6559 "id": "menu.Todoss.closeTodosDrawer",
6560 "start": { 6560 "start": {
6561 "column": 20, 6561 "column": 20,
6562 "line": 211 6562 "line": 212
6563 } 6563 }
6564 }, 6564 },
6565 { 6565 {
6566 "defaultMessage": "!!!Enable Todos", 6566 "defaultMessage": "!!!Enable Todos",
6567 "end": { 6567 "end": {
6568 "column": 3, 6568 "column": 3,
6569 "line": 218 6569 "line": 219
6570 }, 6570 },
6571 "file": "src/lib/Menu.js", 6571 "file": "src/lib/Menu.js",
6572 "id": "menu.todos.enableTodos", 6572 "id": "menu.todos.enableTodos",
6573 "start": { 6573 "start": {
6574 "column": 15, 6574 "column": 15,
6575 "line": 215 6575 "line": 216
6576 } 6576 }
6577 }, 6577 },
6578 { 6578 {
6579 "defaultMessage": "!!!Home", 6579 "defaultMessage": "!!!Home",
6580 "end": { 6580 "end": {
6581 "column": 3, 6581 "column": 3,
6582 "line": 222 6582 "line": 223
6583 }, 6583 },
6584 "file": "src/lib/Menu.js", 6584 "file": "src/lib/Menu.js",
6585 "id": "menu.services.goHome", 6585 "id": "menu.services.goHome",
6586 "start": { 6586 "start": {
6587 "column": 17, 6587 "column": 17,
6588 "line": 219 6588 "line": 220
6589 } 6589 }
6590 } 6590 }
6591 ], 6591 ],
diff --git a/src/i18n/messages/src/components/auth/Signup.json b/src/i18n/messages/src/components/auth/Signup.json
index 2628c9aa3..4a32628ef 100644
--- a/src/i18n/messages/src/components/auth/Signup.json
+++ b/src/i18n/messages/src/components/auth/Signup.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Sign up", 4 "defaultMessage": "!!!Sign up",
5 "file": "src/components/auth/Signup.js", 5 "file": "src/components/auth/Signup.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": "!!!Firstname", 17 "defaultMessage": "!!!Firstname",
18 "file": "src/components/auth/Signup.js", 18 "file": "src/components/auth/Signup.js",
19 "start": { 19 "start": {
20 "line": 23, 20 "line": 24,
21 "column": 18 21 "column": 18
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": "!!!Lastname", 30 "defaultMessage": "!!!Lastname",
31 "file": "src/components/auth/Signup.js", 31 "file": "src/components/auth/Signup.js",
32 "start": { 32 "start": {
33 "line": 27, 33 "line": 28,
34 "column": 17 34 "column": 17
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": "!!!Email address", 43 "defaultMessage": "!!!Email address",
44 "file": "src/components/auth/Signup.js", 44 "file": "src/components/auth/Signup.js",
45 "start": { 45 "start": {
46 "line": 31, 46 "line": 32,
47 "column": 14 47 "column": 14
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/Signup.js", 57 "file": "src/components/auth/Signup.js",
58 "start": { 58 "start": {
59 "line": 39, 59 "line": 40,
60 "column": 17 60 "column": 17
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 42, 63 "line": 43,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!By creating a Ferdi account you accept the", 69 "defaultMessage": "!!!By creating a Ferdi account you accept the",
70 "file": "src/components/auth/Signup.js", 70 "file": "src/components/auth/Signup.js",
71 "start": { 71 "start": {
72 "line": 43, 72 "line": 44,
73 "column": 13 73 "column": 13
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 46, 76 "line": 47,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Terms of service", 82 "defaultMessage": "!!!Terms of service",
83 "file": "src/components/auth/Signup.js", 83 "file": "src/components/auth/Signup.js",
84 "start": { 84 "start": {
85 "line": 47, 85 "line": 48,
86 "column": 9 86 "column": 9
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 50, 89 "line": 51,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Privacy Statement", 95 "defaultMessage": "!!!Privacy Statement",
96 "file": "src/components/auth/Signup.js", 96 "file": "src/components/auth/Signup.js",
97 "start": { 97 "start": {
98 "line": 51, 98 "line": 52,
99 "column": 11 99 "column": 11
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 54, 102 "line": 55,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Create account", 108 "defaultMessage": "!!!Create account",
109 "file": "src/components/auth/Signup.js", 109 "file": "src/components/auth/Signup.js",
110 "start": { 110 "start": {
111 "line": 55, 111 "line": 56,
112 "column": 21 112 "column": 21
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 58, 115 "line": 59,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Already have an account, sign in?", 121 "defaultMessage": "!!!Already have an account, sign in?",
122 "file": "src/components/auth/Signup.js", 122 "file": "src/components/auth/Signup.js",
123 "start": { 123 "start": {
124 "line": 59, 124 "line": 60,
125 "column": 13 125 "column": 13
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 62, 128 "line": 63,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Change server", 134 "defaultMessage": "!!!Change server",
135 "file": "src/components/auth/Signup.js", 135 "file": "src/components/auth/Signup.js",
136 "start": { 136 "start": {
137 "line": 63, 137 "line": 64,
138 "column": 16 138 "column": 16
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 66, 141 "line": 67,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Use Ferdi without an Account", 147 "defaultMessage": "!!!Use Ferdi without an Account",
148 "file": "src/components/auth/Signup.js", 148 "file": "src/components/auth/Signup.js",
149 "start": { 149 "start": {
150 "line": 67, 150 "line": 68,
151 "column": 14 151 "column": 14
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 70, 154 "line": 71,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!A user with that email address already exists", 160 "defaultMessage": "!!!A user with that email address already exists",
161 "file": "src/components/auth/Signup.js", 161 "file": "src/components/auth/Signup.js",
162 "start": { 162 "start": {
163 "line": 71, 163 "line": 72,
164 "column": 18 164 "column": 18
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 74, 167 "line": 75,
168 "column": 3 168 "column": 3
169 } 169 }
170 } 170 }
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index 07d1b1b80..1ddd00c43 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Edit", 4 "defaultMessage": "!!!Edit",
5 "file": "src/lib/Menu.js", 5 "file": "src/lib/Menu.js",
6 "start": { 6 "start": {
7 "line": 19, 7 "line": 20,
8 "column": 8 8 "column": 8
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": "!!!View", 17 "defaultMessage": "!!!View",
18 "file": "src/lib/Menu.js", 18 "file": "src/lib/Menu.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": "!!!Find in Page", 30 "defaultMessage": "!!!Find in Page",
31 "file": "src/lib/Menu.js", 31 "file": "src/lib/Menu.js",
32 "start": { 32 "start": {
33 "line": 27, 33 "line": 28,
34 "column": 14 34 "column": 14
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": "!!!Speech", 43 "defaultMessage": "!!!Speech",
44 "file": "src/lib/Menu.js", 44 "file": "src/lib/Menu.js",
45 "start": { 45 "start": {
46 "line": 31, 46 "line": 32,
47 "column": 10 47 "column": 10
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": "!!!Start Speaking", 56 "defaultMessage": "!!!Start Speaking",
57 "file": "src/lib/Menu.js", 57 "file": "src/lib/Menu.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": "!!!Stop Speaking", 69 "defaultMessage": "!!!Stop Speaking",
70 "file": "src/lib/Menu.js", 70 "file": "src/lib/Menu.js",
71 "start": { 71 "start": {
72 "line": 39, 72 "line": 40,
73 "column": 16 73 "column": 16
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": "!!!Start Dictation", 82 "defaultMessage": "!!!Start Dictation",
83 "file": "src/lib/Menu.js", 83 "file": "src/lib/Menu.js",
84 "start": { 84 "start": {
85 "line": 43, 85 "line": 44,
86 "column": 18 86 "column": 18
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": "!!!Emoji & Symbols", 95 "defaultMessage": "!!!Emoji & Symbols",
96 "file": "src/lib/Menu.js", 96 "file": "src/lib/Menu.js",
97 "start": { 97 "start": {
98 "line": 47, 98 "line": 48,
99 "column": 16 99 "column": 16
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 50, 102 "line": 51,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Open Quick Switch", 108 "defaultMessage": "!!!Open Quick Switch",
109 "file": "src/lib/Menu.js", 109 "file": "src/lib/Menu.js",
110 "start": { 110 "start": {
111 "line": 51, 111 "line": 52,
112 "column": 19 112 "column": 19
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 54, 115 "line": 55,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Back", 121 "defaultMessage": "!!!Back",
122 "file": "src/lib/Menu.js", 122 "file": "src/lib/Menu.js",
123 "start": { 123 "start": {
124 "line": 55, 124 "line": 56,
125 "column": 8 125 "column": 8
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 58, 128 "line": 59,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Forward", 134 "defaultMessage": "!!!Forward",
135 "file": "src/lib/Menu.js", 135 "file": "src/lib/Menu.js",
136 "start": { 136 "start": {
137 "line": 59, 137 "line": 60,
138 "column": 11 138 "column": 11
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 62, 141 "line": 63,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Toggle Dark Mode", 147 "defaultMessage": "!!!Toggle Dark Mode",
148 "file": "src/lib/Menu.js", 148 "file": "src/lib/Menu.js",
149 "start": { 149 "start": {
150 "line": 63, 150 "line": 64,
151 "column": 18 151 "column": 18
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 66, 154 "line": 67,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Toggle Developer Tools", 160 "defaultMessage": "!!!Toggle Developer Tools",
161 "file": "src/lib/Menu.js", 161 "file": "src/lib/Menu.js",
162 "start": { 162 "start": {
163 "line": 67, 163 "line": 68,
164 "column": 18 164 "column": 18
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 70, 167 "line": 71,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!Toggle Todos Developer Tools", 173 "defaultMessage": "!!!Toggle Todos Developer Tools",
174 "file": "src/lib/Menu.js", 174 "file": "src/lib/Menu.js",
175 "start": { 175 "start": {
176 "line": 71, 176 "line": 72,
177 "column": 23 177 "column": 23
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 74, 180 "line": 75,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Toggle Service Developer Tools", 186 "defaultMessage": "!!!Toggle Service Developer Tools",
187 "file": "src/lib/Menu.js", 187 "file": "src/lib/Menu.js",
188 "start": { 188 "start": {
189 "line": 75, 189 "line": 76,
190 "column": 25 190 "column": 25
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 78, 193 "line": 79,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Reload Service", 199 "defaultMessage": "!!!Reload Service",
200 "file": "src/lib/Menu.js", 200 "file": "src/lib/Menu.js",
201 "start": { 201 "start": {
202 "line": 79, 202 "line": 80,
203 "column": 17 203 "column": 17
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 82, 206 "line": 83,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!Reload Ferdi", 212 "defaultMessage": "!!!Reload Ferdi",
213 "file": "src/lib/Menu.js", 213 "file": "src/lib/Menu.js",
214 "start": { 214 "start": {
215 "line": 83, 215 "line": 84,
216 "column": 15 216 "column": 15
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 86, 219 "line": 87,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Lock Ferdi", 225 "defaultMessage": "!!!Lock Ferdi",
226 "file": "src/lib/Menu.js", 226 "file": "src/lib/Menu.js",
227 "start": { 227 "start": {
228 "line": 87, 228 "line": 88,
229 "column": 13 229 "column": 13
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 90, 232 "line": 91,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Reload ToDos", 238 "defaultMessage": "!!!Reload ToDos",
239 "file": "src/lib/Menu.js", 239 "file": "src/lib/Menu.js",
240 "start": { 240 "start": {
241 "line": 91, 241 "line": 92,
242 "column": 15 242 "column": 15
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 94, 245 "line": 95,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!Learn More", 251 "defaultMessage": "!!!Learn More",
252 "file": "src/lib/Menu.js", 252 "file": "src/lib/Menu.js",
253 "start": { 253 "start": {
254 "line": 95, 254 "line": 96,
255 "column": 13 255 "column": 13
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 98, 258 "line": 99,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Changelog", 264 "defaultMessage": "!!!Changelog",
265 "file": "src/lib/Menu.js", 265 "file": "src/lib/Menu.js",
266 "start": { 266 "start": {
267 "line": 99, 267 "line": 100,
268 "column": 13 268 "column": 13
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 102, 271 "line": 103,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!Support", 277 "defaultMessage": "!!!Support",
278 "file": "src/lib/Menu.js", 278 "file": "src/lib/Menu.js",
279 "start": { 279 "start": {
280 "line": 103, 280 "line": 104,
281 "column": 11 281 "column": 11
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 106, 284 "line": 107,
285 "column": 3 285 "column": 3
286 } 286 }
287 }, 287 },
@@ -290,11 +290,11 @@
290 "defaultMessage": "!!!Copy Debug Information", 290 "defaultMessage": "!!!Copy Debug Information",
291 "file": "src/lib/Menu.js", 291 "file": "src/lib/Menu.js",
292 "start": { 292 "start": {
293 "line": 107, 293 "line": 108,
294 "column": 13 294 "column": 13
295 }, 295 },
296 "end": { 296 "end": {
297 "line": 110, 297 "line": 111,
298 "column": 3 298 "column": 3
299 } 299 }
300 }, 300 },
@@ -303,11 +303,11 @@
303 "defaultMessage": "!!!Publish Debug Information", 303 "defaultMessage": "!!!Publish Debug Information",
304 "file": "src/lib/Menu.js", 304 "file": "src/lib/Menu.js",
305 "start": { 305 "start": {
306 "line": 111, 306 "line": 112,
307 "column": 20 307 "column": 20
308 }, 308 },
309 "end": { 309 "end": {
310 "line": 114, 310 "line": 115,
311 "column": 3 311 "column": 3
312 } 312 }
313 }, 313 },
@@ -316,11 +316,11 @@
316 "defaultMessage": "!!!Ferdi Debug Information", 316 "defaultMessage": "!!!Ferdi Debug Information",
317 "file": "src/lib/Menu.js", 317 "file": "src/lib/Menu.js",
318 "start": { 318 "start": {
319 "line": 115, 319 "line": 116,
320 "column": 27 320 "column": 27
321 }, 321 },
322 "end": { 322 "end": {
323 "line": 118, 323 "line": 119,
324 "column": 3 324 "column": 3
325 } 325 }
326 }, 326 },
@@ -329,11 +329,11 @@
329 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.", 329 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.",
330 "file": "src/lib/Menu.js", 330 "file": "src/lib/Menu.js",
331 "start": { 331 "start": {
332 "line": 119, 332 "line": 120,
333 "column": 23 333 "column": 23
334 }, 334 },
335 "end": { 335 "end": {
336 "line": 122, 336 "line": 123,
337 "column": 3 337 "column": 3
338 } 338 }
339 }, 339 },
@@ -342,11 +342,11 @@
342 "defaultMessage": "!!!Unlock with Touch ID", 342 "defaultMessage": "!!!Unlock with Touch ID",
343 "file": "src/lib/Menu.js", 343 "file": "src/lib/Menu.js",
344 "start": { 344 "start": {
345 "line": 123, 345 "line": 124,
346 "column": 11 346 "column": 11
347 }, 347 },
348 "end": { 348 "end": {
349 "line": 126, 349 "line": 127,
350 "column": 3 350 "column": 3
351 } 351 }
352 }, 352 },
@@ -355,11 +355,11 @@
355 "defaultMessage": "!!!unlock via Touch ID", 355 "defaultMessage": "!!!unlock via Touch ID",
356 "file": "src/lib/Menu.js", 356 "file": "src/lib/Menu.js",
357 "start": { 357 "start": {
358 "line": 127, 358 "line": 128,
359 "column": 17 359 "column": 17
360 }, 360 },
361 "end": { 361 "end": {
362 "line": 130, 362 "line": 131,
363 "column": 3 363 "column": 3
364 } 364 }
365 }, 365 },
@@ -368,11 +368,11 @@
368 "defaultMessage": "!!!Terms of Service", 368 "defaultMessage": "!!!Terms of Service",
369 "file": "src/lib/Menu.js", 369 "file": "src/lib/Menu.js",
370 "start": { 370 "start": {
371 "line": 131, 371 "line": 132,
372 "column": 7 372 "column": 7
373 }, 373 },
374 "end": { 374 "end": {
375 "line": 134, 375 "line": 135,
376 "column": 3 376 "column": 3
377 } 377 }
378 }, 378 },
@@ -381,11 +381,11 @@
381 "defaultMessage": "!!!Privacy Statement", 381 "defaultMessage": "!!!Privacy Statement",
382 "file": "src/lib/Menu.js", 382 "file": "src/lib/Menu.js",
383 "start": { 383 "start": {
384 "line": 135, 384 "line": 136,
385 "column": 11 385 "column": 11
386 }, 386 },
387 "end": { 387 "end": {
388 "line": 138, 388 "line": 139,
389 "column": 3 389 "column": 3
390 } 390 }
391 }, 391 },
@@ -394,11 +394,11 @@
394 "defaultMessage": "!!!File", 394 "defaultMessage": "!!!File",
395 "file": "src/lib/Menu.js", 395 "file": "src/lib/Menu.js",
396 "start": { 396 "start": {
397 "line": 139, 397 "line": 140,
398 "column": 8 398 "column": 8
399 }, 399 },
400 "end": { 400 "end": {
401 "line": 142, 401 "line": 143,
402 "column": 3 402 "column": 3
403 } 403 }
404 }, 404 },
@@ -407,11 +407,11 @@
407 "defaultMessage": "!!!Services", 407 "defaultMessage": "!!!Services",
408 "file": "src/lib/Menu.js", 408 "file": "src/lib/Menu.js",
409 "start": { 409 "start": {
410 "line": 143, 410 "line": 144,
411 "column": 12 411 "column": 12
412 }, 412 },
413 "end": { 413 "end": {
414 "line": 146, 414 "line": 147,
415 "column": 3 415 "column": 3
416 } 416 }
417 }, 417 },
@@ -420,11 +420,11 @@
420 "defaultMessage": "!!!What's new?", 420 "defaultMessage": "!!!What's new?",
421 "file": "src/lib/Menu.js", 421 "file": "src/lib/Menu.js",
422 "start": { 422 "start": {
423 "line": 147, 423 "line": 148,
424 "column": 16 424 "column": 16
425 }, 425 },
426 "end": { 426 "end": {
427 "line": 150, 427 "line": 151,
428 "column": 3 428 "column": 3
429 } 429 }
430 }, 430 },
@@ -433,11 +433,11 @@
433 "defaultMessage": "!!!Settings", 433 "defaultMessage": "!!!Settings",
434 "file": "src/lib/Menu.js", 434 "file": "src/lib/Menu.js",
435 "start": { 435 "start": {
436 "line": 151, 436 "line": 152,
437 "column": 12 437 "column": 12
438 }, 438 },
439 "end": { 439 "end": {
440 "line": 154, 440 "line": 155,
441 "column": 3 441 "column": 3
442 } 442 }
443 }, 443 },
@@ -446,11 +446,11 @@
446 "defaultMessage": "!!!Check for updates", 446 "defaultMessage": "!!!Check for updates",
447 "file": "src/lib/Menu.js", 447 "file": "src/lib/Menu.js",
448 "start": { 448 "start": {
449 "line": 155, 449 "line": 156,
450 "column": 19 450 "column": 19
451 }, 451 },
452 "end": { 452 "end": {
453 "line": 158, 453 "line": 159,
454 "column": 3 454 "column": 3
455 } 455 }
456 }, 456 },
@@ -459,11 +459,11 @@
459 "defaultMessage": "!!!Auto-hide menu bar", 459 "defaultMessage": "!!!Auto-hide menu bar",
460 "file": "src/lib/Menu.js", 460 "file": "src/lib/Menu.js",
461 "start": { 461 "start": {
462 "line": 159, 462 "line": 160,
463 "column": 19 463 "column": 19
464 }, 464 },
465 "end": { 465 "end": {
466 "line": 162, 466 "line": 163,
467 "column": 3 467 "column": 3
468 } 468 }
469 }, 469 },
@@ -472,11 +472,11 @@
472 "defaultMessage": "!!!Add New Service...", 472 "defaultMessage": "!!!Add New Service...",
473 "file": "src/lib/Menu.js", 473 "file": "src/lib/Menu.js",
474 "start": { 474 "start": {
475 "line": 163, 475 "line": 164,
476 "column": 17 476 "column": 17
477 }, 477 },
478 "end": { 478 "end": {
479 "line": 166, 479 "line": 167,
480 "column": 3 480 "column": 3
481 } 481 }
482 }, 482 },
@@ -485,11 +485,11 @@
485 "defaultMessage": "!!!Add New Workspace...", 485 "defaultMessage": "!!!Add New Workspace...",
486 "file": "src/lib/Menu.js", 486 "file": "src/lib/Menu.js",
487 "start": { 487 "start": {
488 "line": 167, 488 "line": 168,
489 "column": 19 489 "column": 19
490 }, 490 },
491 "end": { 491 "end": {
492 "line": 170, 492 "line": 171,
493 "column": 3 493 "column": 3
494 } 494 }
495 }, 495 },
@@ -498,11 +498,11 @@
498 "defaultMessage": "!!!Open workspace drawer", 498 "defaultMessage": "!!!Open workspace drawer",
499 "file": "src/lib/Menu.js", 499 "file": "src/lib/Menu.js",
500 "start": { 500 "start": {
501 "line": 171, 501 "line": 172,
502 "column": 23 502 "column": 23
503 }, 503 },
504 "end": { 504 "end": {
505 "line": 174, 505 "line": 175,
506 "column": 3 506 "column": 3
507 } 507 }
508 }, 508 },
@@ -511,11 +511,11 @@
511 "defaultMessage": "!!!Close workspace drawer", 511 "defaultMessage": "!!!Close workspace drawer",
512 "file": "src/lib/Menu.js", 512 "file": "src/lib/Menu.js",
513 "start": { 513 "start": {
514 "line": 175, 514 "line": 176,
515 "column": 24 515 "column": 24
516 }, 516 },
517 "end": { 517 "end": {
518 "line": 178, 518 "line": 179,
519 "column": 3 519 "column": 3
520 } 520 }
521 }, 521 },
@@ -524,11 +524,11 @@
524 "defaultMessage": "!!!Activate next service...", 524 "defaultMessage": "!!!Activate next service...",
525 "file": "src/lib/Menu.js", 525 "file": "src/lib/Menu.js",
526 "start": { 526 "start": {
527 "line": 179, 527 "line": 180,
528 "column": 23 528 "column": 23
529 }, 529 },
530 "end": { 530 "end": {
531 "line": 182, 531 "line": 183,
532 "column": 3 532 "column": 3
533 } 533 }
534 }, 534 },
@@ -537,11 +537,11 @@
537 "defaultMessage": "!!!Activate previous service...", 537 "defaultMessage": "!!!Activate previous service...",
538 "file": "src/lib/Menu.js", 538 "file": "src/lib/Menu.js",
539 "start": { 539 "start": {
540 "line": 183, 540 "line": 184,
541 "column": 27 541 "column": 27
542 }, 542 },
543 "end": { 543 "end": {
544 "line": 186, 544 "line": 187,
545 "column": 3 545 "column": 3
546 } 546 }
547 }, 547 },
@@ -550,11 +550,11 @@
550 "defaultMessage": "!!!Disable notifications & audio", 550 "defaultMessage": "!!!Disable notifications & audio",
551 "file": "src/lib/Menu.js", 551 "file": "src/lib/Menu.js",
552 "start": { 552 "start": {
553 "line": 187, 553 "line": 188,
554 "column": 11 554 "column": 11
555 }, 555 },
556 "end": { 556 "end": {
557 "line": 190, 557 "line": 191,
558 "column": 3 558 "column": 3
559 } 559 }
560 }, 560 },
@@ -563,11 +563,11 @@
563 "defaultMessage": "!!!Enable notifications & audio", 563 "defaultMessage": "!!!Enable notifications & audio",
564 "file": "src/lib/Menu.js", 564 "file": "src/lib/Menu.js",
565 "start": { 565 "start": {
566 "line": 191, 566 "line": 192,
567 "column": 13 567 "column": 13
568 }, 568 },
569 "end": { 569 "end": {
570 "line": 194, 570 "line": 195,
571 "column": 3 571 "column": 3
572 } 572 }
573 }, 573 },
@@ -576,11 +576,11 @@
576 "defaultMessage": "!!!Workspaces", 576 "defaultMessage": "!!!Workspaces",
577 "file": "src/lib/Menu.js", 577 "file": "src/lib/Menu.js",
578 "start": { 578 "start": {
579 "line": 195, 579 "line": 196,
580 "column": 14 580 "column": 14
581 }, 581 },
582 "end": { 582 "end": {
583 "line": 198, 583 "line": 199,
584 "column": 3 584 "column": 3
585 } 585 }
586 }, 586 },
@@ -589,11 +589,11 @@
589 "defaultMessage": "!!!Default", 589 "defaultMessage": "!!!Default",
590 "file": "src/lib/Menu.js", 590 "file": "src/lib/Menu.js",
591 "start": { 591 "start": {
592 "line": 199, 592 "line": 200,
593 "column": 20 593 "column": 20
594 }, 594 },
595 "end": { 595 "end": {
596 "line": 202, 596 "line": 203,
597 "column": 3 597 "column": 3
598 } 598 }
599 }, 599 },
@@ -602,11 +602,11 @@
602 "defaultMessage": "!!!Todos", 602 "defaultMessage": "!!!Todos",
603 "file": "src/lib/Menu.js", 603 "file": "src/lib/Menu.js",
604 "start": { 604 "start": {
605 "line": 203, 605 "line": 204,
606 "column": 9 606 "column": 9
607 }, 607 },
608 "end": { 608 "end": {
609 "line": 206, 609 "line": 207,
610 "column": 3 610 "column": 3
611 } 611 }
612 }, 612 },
@@ -615,11 +615,11 @@
615 "defaultMessage": "!!!Open Todos drawer", 615 "defaultMessage": "!!!Open Todos drawer",
616 "file": "src/lib/Menu.js", 616 "file": "src/lib/Menu.js",
617 "start": { 617 "start": {
618 "line": 207, 618 "line": 208,
619 "column": 19 619 "column": 19
620 }, 620 },
621 "end": { 621 "end": {
622 "line": 210, 622 "line": 211,
623 "column": 3 623 "column": 3
624 } 624 }
625 }, 625 },
@@ -628,11 +628,11 @@
628 "defaultMessage": "!!!Close Todos drawer", 628 "defaultMessage": "!!!Close Todos drawer",
629 "file": "src/lib/Menu.js", 629 "file": "src/lib/Menu.js",
630 "start": { 630 "start": {
631 "line": 211, 631 "line": 212,
632 "column": 20 632 "column": 20
633 }, 633 },
634 "end": { 634 "end": {
635 "line": 214, 635 "line": 215,
636 "column": 3 636 "column": 3
637 } 637 }
638 }, 638 },
@@ -641,11 +641,11 @@
641 "defaultMessage": "!!!Enable Todos", 641 "defaultMessage": "!!!Enable Todos",
642 "file": "src/lib/Menu.js", 642 "file": "src/lib/Menu.js",
643 "start": { 643 "start": {
644 "line": 215, 644 "line": 216,
645 "column": 15 645 "column": 15
646 }, 646 },
647 "end": { 647 "end": {
648 "line": 218, 648 "line": 219,
649 "column": 3 649 "column": 3
650 } 650 }
651 }, 651 },
@@ -654,11 +654,11 @@
654 "defaultMessage": "!!!Home", 654 "defaultMessage": "!!!Home",
655 "file": "src/lib/Menu.js", 655 "file": "src/lib/Menu.js",
656 "start": { 656 "start": {
657 "line": 219, 657 "line": 220,
658 "column": 17 658 "column": 17
659 }, 659 },
660 "end": { 660 "end": {
661 "line": 222, 661 "line": 223,
662 "column": 3 662 "column": 3
663 } 663 }
664 } 664 }
diff --git a/src/index.js b/src/index.js
index 63e6e3d0f..7c52fa1b3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,12 +1,6 @@
1/* eslint-disable import/first */ 1/* eslint-disable import/first */
2 2
3import { 3import { app, BrowserWindow, shell, ipcMain, session } from 'electron';
4 app,
5 BrowserWindow,
6 shell,
7 ipcMain,
8 session,
9} from 'electron';
10 4
11import fs from 'fs-extra'; 5import fs from 'fs-extra';
12import path from 'path'; 6import path from 'path';
@@ -34,7 +28,7 @@ import DBus from './lib/DBus';
34import Settings from './electron/Settings'; 28import Settings from './electron/Settings';
35import handleDeepLink from './electron/deepLinking'; 29import handleDeepLink from './electron/deepLinking';
36import { isPositionValid } from './electron/windowUtils'; 30import { isPositionValid } from './electron/windowUtils';
37import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 31import { appId } from '../package.json';
38import './electron/exception'; 32import './electron/exception';
39 33
40import { asarPath } from './helpers/asar-helpers'; 34import { asarPath } from './helpers/asar-helpers';
@@ -90,7 +84,9 @@ if (settings.get('sentry')) {
90const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false; 84const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false;
91 85
92// Force single window 86// Force single window
93const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock(); 87const gotTheLock = liftSingleInstanceLock
88 ? true
89 : app.requestSingleInstanceLock();
94if (!gotTheLock) { 90if (!gotTheLock) {
95 app.quit(); 91 app.quit();
96} else { 92} else {
@@ -106,7 +102,7 @@ if (!gotTheLock) {
106 mainWindow.focus(); 102 mainWindow.focus();
107 103
108 if (isWindows) { 104 if (isWindows) {
109 onDidLoad((window) => { 105 onDidLoad(window => {
110 // Keep only command line / deep linked arguments 106 // Keep only command line / deep linked arguments
111 const url = argv.slice(1); 107 const url = argv.slice(1);
112 if (url) { 108 if (url) {
@@ -117,8 +113,14 @@ if (!gotTheLock) {
117 // Needs to be delayed to not interfere with mainWindow.restore(); 113 // Needs to be delayed to not interfere with mainWindow.restore();
118 setTimeout(() => { 114 setTimeout(() => {
119 debug('Resetting windows via Task'); 115 debug('Resetting windows via Task');
120 window.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100); 116 window.setPosition(
121 window.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height); 117 DEFAULT_WINDOW_OPTIONS.x + 100,
118 DEFAULT_WINDOW_OPTIONS.y + 100,
119 );
120 window.setSize(
121 DEFAULT_WINDOW_OPTIONS.width,
122 DEFAULT_WINDOW_OPTIONS.height,
123 );
122 }, 1); 124 }, 1);
123 } else if (argv.includes('--quit')) { 125 } else if (argv.includes('--quit')) {
124 // Needs to be delayed to not interfere with mainWindow.restore(); 126 // Needs to be delayed to not interfere with mainWindow.restore();
@@ -135,7 +137,10 @@ if (!gotTheLock) {
135 137
136// Fix Unity indicator issue 138// Fix Unity indicator issue
137// https://github.com/electron/electron/issues/9046 139// https://github.com/electron/electron/issues/9046
138if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) { 140if (
141 isLinux &&
142 ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1
143) {
139 process.env.XDG_CURRENT_DESKTOP = 'Unity'; 144 process.env.XDG_CURRENT_DESKTOP = 'Unity';
140} 145}
141 146
@@ -169,7 +174,9 @@ const createWindow = () => {
169 } 174 }
170 175
171 // Create the browser window. 176 // Create the browser window.
172 const backgroundColor = settings.get('darkMode') ? '#1E1E1E' : settings.get('accentColor'); 177 const backgroundColor = settings.get('darkMode')
178 ? '#1E1E1E'
179 : settings.get('accentColor');
173 180
174 mainWindow = new BrowserWindow({ 181 mainWindow = new BrowserWindow({
175 x: posX, 182 x: posX,
@@ -193,7 +200,7 @@ const createWindow = () => {
193 200
194 app.on('web-contents-created', (e, contents) => { 201 app.on('web-contents-created', (e, contents) => {
195 if (contents.getType() === 'webview') { 202 if (contents.getType() === 'webview') {
196 contents.on('new-window', (event) => { 203 contents.on('new-window', event => {
197 event.preventDefault(); 204 event.preventDefault();
198 }); 205 });
199 } 206 }
@@ -242,7 +249,7 @@ const createWindow = () => {
242 249
243 // Windows deep linking handling on app launch 250 // Windows deep linking handling on app launch
244 if (isWindows) { 251 if (isWindows) {
245 onDidLoad((window) => { 252 onDidLoad(window => {
246 const url = process.argv.slice(1); 253 const url = process.argv.slice(1);
247 if (url) { 254 if (url) {
248 handleDeepLink(window, url.toString()); 255 handleDeepLink(window, url.toString());
@@ -251,12 +258,16 @@ const createWindow = () => {
251 } 258 }
252 259
253 // Emitted when the window is closed. 260 // Emitted when the window is closed.
254 mainWindow.on('close', (e) => { 261 mainWindow.on('close', e => {
255 debug('Window: close window'); 262 debug('Window: close window');
256 // Dereference the window object, usually you would store windows 263 // Dereference the window object, usually you would store windows
257 // in an array if your app supports multi windows, this is the time 264 // in an array if your app supports multi windows, this is the time
258 // when you should delete the corresponding element. 265 // when you should delete the corresponding element.
259 if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) { 266 if (
267 !willQuitApp &&
268 (settings.get('runInBackground') === undefined ||
269 settings.get('runInBackground'))
270 ) {
260 e.preventDefault(); 271 e.preventDefault();
261 if (isWindows) { 272 if (isWindows) {
262 debug('Window: minimize'); 273 debug('Window: minimize');
@@ -315,7 +326,7 @@ const createWindow = () => {
315 326
316 if (isMac) { 327 if (isMac) {
317 // eslint-disable-next-line global-require 328 // eslint-disable-next-line global-require
318 const { default: askFormacOSPermissions } = require('./electron/macOSPermissions'); 329 const { askFormacOSPermissions } = require('./electron/macOSPermissions');
319 setTimeout(() => askFormacOSPermissions(mainWindow), ms('30s')); 330 setTimeout(() => askFormacOSPermissions(mainWindow), ms('30s'));
320 } 331 }
321 332
@@ -351,15 +362,24 @@ const createWindow = () => {
351const argv = require('minimist')(process.argv.slice(1)); 362const argv = require('minimist')(process.argv.slice(1));
352 363
353if (argv['auth-server-whitelist']) { 364if (argv['auth-server-whitelist']) {
354 app.commandLine.appendSwitch('auth-server-whitelist', argv['auth-server-whitelist']); 365 app.commandLine.appendSwitch(
366 'auth-server-whitelist',
367 argv['auth-server-whitelist'],
368 );
355} 369}
356if (argv['auth-negotiate-delegate-whitelist']) { 370if (argv['auth-negotiate-delegate-whitelist']) {
357 app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', argv['auth-negotiate-delegate-whitelist']); 371 app.commandLine.appendSwitch(
372 'auth-negotiate-delegate-whitelist',
373 argv['auth-negotiate-delegate-whitelist'],
374 );
358} 375}
359 376
360// Disable Chromium's poor MPRIS implementation 377// Disable Chromium's poor MPRIS implementation
361// and apply workaround for https://github.com/electron/electron/pull/26432 378// and apply workaround for https://github.com/electron/electron/pull/26432
362app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService,CrossOriginOpenerPolicy'); 379app.commandLine.appendSwitch(
380 'disable-features',
381 'HardwareMediaKeyHandling,MediaSessionService,CrossOriginOpenerPolicy',
382);
363 383
364// This method will be called when Electron has finished 384// This method will be called when Electron has finished
365// initialization and is ready to create browser windows. 385// initialization and is ready to create browser windows.
@@ -376,19 +396,29 @@ app.on('ready', () => {
376 } 396 }
377 397
378 if (isWindows) { 398 if (isWindows) {
379 app.setUserTasks([{ 399 app.setUserTasks([
380 program: process.execPath, 400 {
381 arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`, 401 program: process.execPath,
382 iconPath: asarPath(path.join(isDevMode ? `${__dirname}../src/` : __dirname, 'assets/images/taskbar/win32/display.ico')), 402 arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`,
383 iconIndex: 0, 403 iconPath: asarPath(
384 title: 'Move Ferdi to Current Display', 404 path.join(
385 description: 'Restore the position and size of Ferdi', 405 isDevMode ? `${__dirname}../src/` : __dirname,
386 }, { 406 'assets/images/taskbar/win32/display.ico',
387 program: process.execPath, 407 ),
388 arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`, 408 ),
389 iconIndex: 0, 409 iconIndex: 0,
390 title: 'Quit Ferdi', 410 title: 'Move Ferdi to Current Display',
391 }]); 411 description: 'Restore the position and size of Ferdi',
412 },
413 {
414 program: process.execPath,
415 arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`,
416 iconIndex: 0,
417 iconPath: null,
418 title: 'Quit Ferdi',
419 description: null,
420 },
421 ]);
392 } 422 }
393 423
394 createWindow(); 424 createWindow();
@@ -420,27 +450,35 @@ ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => {
420 450
421ipcMain.on('open-browser-window', (e, { url, serviceId }) => { 451ipcMain.on('open-browser-window', (e, { url, serviceId }) => {
422 const serviceSession = session.fromPartition(`persist:service-${serviceId}`); 452 const serviceSession = session.fromPartition(`persist:service-${serviceId}`);
423 const child = new BrowserWindow({ parent: mainWindow, webPreferences: { session: serviceSession } }); 453 const child = new BrowserWindow({
454 parent: mainWindow,
455 webPreferences: { session: serviceSession },
456 });
424 child.show(); 457 child.show();
425 child.loadURL(url); 458 child.loadURL(url);
426 debug('Received open-browser-window', url); 459 debug('Received open-browser-window', url);
427}); 460});
428 461
429ipcMain.on('modifyRequestHeaders', (e, { modifiedRequestHeaders, serviceId }) => { 462ipcMain.on(
430 debug('Received modifyRequestHeaders', modifiedRequestHeaders, serviceId); 463 'modifyRequestHeaders',
431 modifiedRequestHeaders.forEach((headerFilterSet) => { 464 (e, { modifiedRequestHeaders, serviceId }) => {
432 const { headers, requestFilters } = headerFilterSet; 465 debug('Received modifyRequestHeaders', modifiedRequestHeaders, serviceId);
433 session.fromPartition(`persist:service-${serviceId}`).webRequest.onBeforeSendHeaders(requestFilters, (details, callback) => { 466 modifiedRequestHeaders.forEach(headerFilterSet => {
434 for (const key in headers) { 467 const { headers, requestFilters } = headerFilterSet;
435 if (Object.prototype.hasOwnProperty.call(headers, key)) { 468 session
436 const value = headers[key]; 469 .fromPartition(`persist:service-${serviceId}`)
437 details.requestHeaders[key] = value; 470 .webRequest.onBeforeSendHeaders(requestFilters, (details, callback) => {
438 } 471 for (const key in headers) {
439 } 472 if (Object.prototype.hasOwnProperty.call(headers, key)) {
440 callback({ requestHeaders: details.requestHeaders }); 473 const value = headers[key];
474 details.requestHeaders[key] = value;
475 }
476 }
477 callback({ requestHeaders: details.requestHeaders });
478 });
441 }); 479 });
442 }); 480 },
443}); 481);
444 482
445ipcMain.on('feature-basic-auth-cancel', () => { 483ipcMain.on('feature-basic-auth-cancel', () => {
446 debug('Cancel basic auth'); 484 debug('Cancel basic auth');
@@ -453,7 +491,7 @@ ipcMain.on('feature-basic-auth-cancel', () => {
453 491
454ipcMain.on('find-in-page', (e, text, options) => { 492ipcMain.on('find-in-page', (e, text, options) => {
455 const { sender: webContents } = e; 493 const { sender: webContents } = e;
456 if (webContents !== mainWindow.webContents && typeof (text) === 'string') { 494 if (webContents !== mainWindow.webContents && typeof text === 'string') {
457 const sanitizedOptions = {}; 495 const sanitizedOptions = {};
458 for (const option of ['forward', 'findNext', 'matchCase']) { 496 for (const option of ['forward', 'findNext', 'matchCase']) {
459 if (option in options) { 497 if (option in options) {
@@ -471,7 +509,11 @@ ipcMain.on('find-in-page', (e, text, options) => {
471ipcMain.on('stop-find-in-page', (e, action) => { 509ipcMain.on('stop-find-in-page', (e, action) => {
472 const { sender: webContents } = e; 510 const { sender: webContents } = e;
473 if (webContents !== mainWindow.webContents) { 511 if (webContents !== mainWindow.webContents) {
474 const validActions = ['clearSelection', 'keepSelection', 'activateSelection']; 512 const validActions = [
513 'clearSelection',
514 'keepSelection',
515 'activateSelection',
516 ];
475 if (validActions.includes(action)) { 517 if (validActions.includes(action)) {
476 webContents.stopFindInPage(action); 518 webContents.stopFindInPage(action);
477 } 519 }
@@ -483,12 +525,14 @@ ipcMain.on('stop-find-in-page', (e, action) => {
483app.on('window-all-closed', () => { 525app.on('window-all-closed', () => {
484 // On OS X it is common for applications and their menu bar 526 // On OS X it is common for applications and their menu bar
485 // to stay active until the user quits explicitly with Cmd + Q 527 // to stay active until the user quits explicitly with Cmd + Q
486 if (settings.get('runInBackground') === undefined 528 if (
487 || settings.get('runInBackground')) { 529 settings.get('runInBackground') === undefined ||
530 settings.get('runInBackground')
531 ) {
488 debug('Window: all windows closed, quit app'); 532 debug('Window: all windows closed, quit app');
489 app.quit(); 533 app.quit();
490 } else { 534 } else {
491 debug('Window: don\'t quit app'); 535 debug("Window: don't quit app");
492 } 536 }
493}); 537});
494 538
@@ -517,7 +561,7 @@ app.on('will-finish-launching', () => {
517 app.on('open-url', (event, url) => { 561 app.on('open-url', (event, url) => {
518 event.preventDefault(); 562 event.preventDefault();
519 563
520 onDidLoad((window) => { 564 onDidLoad(window => {
521 debug('open-url event', url); 565 debug('open-url event', url);
522 handleDeepLink(window, url); 566 handleDeepLink(window, url);
523 }); 567 });
diff --git a/src/internal-server/app/Controllers/Http/ServiceController.js b/src/internal-server/app/Controllers/Http/ServiceController.js
index 36d20c70c..c30e9e040 100644
--- a/src/internal-server/app/Controllers/Http/ServiceController.js
+++ b/src/internal-server/app/Controllers/Http/ServiceController.js
@@ -1,7 +1,5 @@
1const Service = use('App/Models/Service'); 1const Service = use('App/Models/Service');
2const { 2const { validateAll } = use('Validator');
3 validateAll,
4} = use('Validator');
5const Env = use('Env'); 3const Env = use('Env');
6 4
7const uuid = require('uuid/v4'); 5const uuid = require('uuid/v4');
@@ -10,10 +8,7 @@ const fs = require('fs-extra');
10 8
11class ServiceController { 9class ServiceController {
12 // Create a new service for user 10 // Create a new service for user
13 async create({ 11 async create({ request, response }) {
14 request,
15 response,
16 }) {
17 // Validate user input 12 // Validate user input
18 const validation = await validateAll(request.all(), { 13 const validation = await validateAll(request.all(), {
19 name: 'required|string', 14 name: 'required|string',
@@ -33,7 +28,10 @@ class ServiceController {
33 let serviceId; 28 let serviceId;
34 do { 29 do {
35 serviceId = uuid(); 30 serviceId = uuid();
36 } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop 31 } while (
32 (await Service.query().where('serviceId', serviceId).fetch()).rows
33 .length > 0
34 ); // eslint-disable-line no-await-in-loop
37 35
38 await Service.create({ 36 await Service.create({
39 serviceId, 37 serviceId,
@@ -64,13 +62,14 @@ class ServiceController {
64 } 62 }
65 63
66 // List all services a user has created 64 // List all services a user has created
67 async list({ 65 async list({ response }) {
68 response,
69 }) {
70 const services = (await Service.all()).rows; 66 const services = (await Service.all()).rows;
71 // Convert to array with all data Franz wants 67 // Convert to array with all data Franz wants
72 const servicesArray = services.map((service) => { 68 const servicesArray = services.map(service => {
73 const settings = typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings; 69 const settings =
70 typeof service.settings === 'string'
71 ? JSON.parse(service.settings)
72 : service.settings;
74 73
75 return { 74 return {
76 customRecipe: false, 75 customRecipe: false,
@@ -84,7 +83,9 @@ class ServiceController {
84 spellcheckerLanguage: '', 83 spellcheckerLanguage: '',
85 workspaces: [], 84 workspaces: [],
86 ...JSON.parse(service.settings), 85 ...JSON.parse(service.settings),
87 iconUrl: settings.iconId ? `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${settings.iconId}` : null, 86 iconUrl: settings.iconId
87 ? `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${settings.iconId}`
88 : null,
88 id: service.serviceId, 89 id: service.serviceId,
89 name: service.name, 90 name: service.name,
90 recipeId: service.recipeId, 91 recipeId: service.recipeId,
@@ -95,11 +96,7 @@ class ServiceController {
95 return response.send(servicesArray); 96 return response.send(servicesArray);
96 } 97 }
97 98
98 async edit({ 99 async edit({ request, response, params }) {
99 request,
100 response,
101 params,
102 }) {
103 if (request.file('icon')) { 100 if (request.file('icon')) {
104 // Upload custom service icon 101 // Upload custom service icon
105 await fs.ensureDir(path.join(Env.get('USER_PATH'), 'icons')); 102 await fs.ensureDir(path.join(Env.get('USER_PATH'), 'icons'));
@@ -108,19 +105,19 @@ class ServiceController {
108 types: ['image'], 105 types: ['image'],
109 size: '2mb', 106 size: '2mb',
110 }); 107 });
111 const { 108 const { id } = params;
112 id, 109 const service = (await Service.query().where('serviceId', id).fetch())
113 } = params; 110 .rows[0];
114 const service = (await Service.query() 111 const settings =
115 .where('serviceId', id).fetch()).rows[0]; 112 typeof service.settings === 'string'
116 const settings = typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings; 113 ? JSON.parse(service.settings)
114 : service.settings;
117 115
118 // Generate new icon ID 116 // Generate new icon ID
119 let iconId; 117 let iconId;
120 do { 118 do {
121 iconId = uuid() + uuid(); 119 iconId = uuid() + uuid();
122 // eslint-disable-next-line no-await-in-loop 120 } while (fs.existsSync(path.join(Env.get('USER_PATH'), 'icons', iconId)));
123 } while (await fs.exists(path.join(Env.get('USER_PATH'), 'icons', iconId)));
124 121
125 await icon.move(path.join(Env.get('USER_PATH'), 'icons'), { 122 await icon.move(path.join(Env.get('USER_PATH'), 'icons'), {
126 name: iconId, 123 name: iconId,
@@ -135,23 +132,29 @@ class ServiceController {
135 ...settings, 132 ...settings,
136 ...{ 133 ...{
137 iconId, 134 iconId,
138 customIconVersion: settings && settings.customIconVersion ? settings.customIconVersion + 1 : 1, 135 customIconVersion:
136 settings && settings.customIconVersion
137 ? settings.customIconVersion + 1
138 : 1,
139 }, 139 },
140 }; 140 };
141 141
142 // Update data in database 142 // Update data in database
143 await (Service.query() 143 await Service.query()
144 .where('serviceId', id)).update({ 144 .where('serviceId', id)
145 name: service.name, 145 .update({
146 settings: JSON.stringify(newSettings), 146 name: service.name,
147 }); 147 settings: JSON.stringify(newSettings),
148 });
148 149
149 return response.send({ 150 return response.send({
150 data: { 151 data: {
151 id, 152 id,
152 name: service.name, 153 name: service.name,
153 ...newSettings, 154 ...newSettings,
154 iconUrl: `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${newSettings.iconId}`, 155 iconUrl: `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${
156 newSettings.iconId
157 }`,
155 userId: 1, 158 userId: 1,
156 }, 159 },
157 status: ['updated'], 160 status: ['updated'],
@@ -159,29 +162,30 @@ class ServiceController {
159 } 162 }
160 // Update service info 163 // Update service info
161 const data = request.all(); 164 const data = request.all();
162 const { 165 const { id } = params;
163 id,
164 } = params;
165 166
166 // Get current settings from db 167 // Get current settings from db
167 const serviceData = (await Service.query() 168 const serviceData = (await Service.query().where('serviceId', id).fetch())
168 .where('serviceId', id).fetch()).rows[0]; 169 .rows[0];
169 170
170 const settings = { 171 const settings = {
171 ...typeof serviceData.settings === 'string' ? JSON.parse(serviceData.settings) : serviceData.settings, 172 ...(typeof serviceData.settings === 'string'
173 ? JSON.parse(serviceData.settings)
174 : serviceData.settings),
172 ...data, 175 ...data,
173 }; 176 };
174 177
175 // Update data in database 178 // Update data in database
176 await (Service.query() 179 await Service.query()
177 .where('serviceId', id)).update({ 180 .where('serviceId', id)
178 name: data.name, 181 .update({
179 settings: JSON.stringify(settings), 182 name: data.name,
180 }); 183 settings: JSON.stringify(settings),
184 });
181 185
182 // Get updated row 186 // Get updated row
183 const service = (await Service.query() 187 const service = (await Service.query().where('serviceId', id).fetch())
184 .where('serviceId', id).fetch()).rows[0]; 188 .rows[0];
185 189
186 return response.send({ 190 return response.send({
187 data: { 191 data: {
@@ -195,34 +199,29 @@ class ServiceController {
195 }); 199 });
196 } 200 }
197 201
198 async icon({ 202 async icon({ params, response }) {
199 params, 203 const { id } = params;
200 response,
201 }) {
202 const {
203 id,
204 } = params;
205 204
206 const iconPath = path.join(Env.get('USER_PATH'), 'icons', id); 205 const iconPath = path.join(Env.get('USER_PATH'), 'icons', id);
207 if (!await fs.exists(iconPath)) { 206 if (!fs.existsSync(iconPath)) {
208 return response.status(404).send({ 207 return response.status(404).send({
209 status: 'Icon doesn\'t exist', 208 status: "Icon doesn't exist",
210 }); 209 });
211 } 210 }
212 211
213 return response.download(iconPath); 212 return response.download(iconPath);
214 } 213 }
215 214
216 async reorder({ 215 async reorder({ request, response }) {
217 request,
218 response,
219 }) {
220 const data = request.all(); 216 const data = request.all();
221 217
222 for (const service of Object.keys(data)) { 218 for (const service of Object.keys(data)) {
223 // Get current settings from db 219 // Get current settings from db
224 const serviceData = (await Service.query() // eslint-disable-line no-await-in-loop 220 const serviceData = (
225 .where('serviceId', service).fetch()).rows[0]; 221 await Service.query() // eslint-disable-line no-await-in-loop
222 .where('serviceId', service)
223 .fetch()
224 ).rows[0];
226 225
227 const settings = { 226 const settings = {
228 ...JSON.parse(serviceData.settings), 227 ...JSON.parse(serviceData.settings),
@@ -230,8 +229,8 @@ class ServiceController {
230 }; 229 };
231 230
232 // Update data in database 231 // Update data in database
233 await (Service.query() // eslint-disable-line no-await-in-loop 232 await Service.query() // eslint-disable-line no-await-in-loop
234 .where('serviceId', service)) 233 .where('serviceId', service)
235 .update({ 234 .update({
236 settings: JSON.stringify(settings), 235 settings: JSON.stringify(settings),
237 }); 236 });
@@ -240,8 +239,11 @@ class ServiceController {
240 // Get new services 239 // Get new services
241 const services = (await Service.all()).rows; 240 const services = (await Service.all()).rows;
242 // Convert to array with all data Franz wants 241 // Convert to array with all data Franz wants
243 const servicesArray = services.map((service) => { 242 const servicesArray = services.map(service => {
244 const settings = typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings; 243 const settings =
244 typeof service.settings === 'string'
245 ? JSON.parse(service.settings)
246 : service.settings;
245 247
246 return { 248 return {
247 customRecipe: false, 249 customRecipe: false,
@@ -255,7 +257,9 @@ class ServiceController {
255 spellcheckerLanguage: '', 257 spellcheckerLanguage: '',
256 workspaces: [], 258 workspaces: [],
257 ...JSON.parse(service.settings), 259 ...JSON.parse(service.settings),
258 iconUrl: settings.iconId ? `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${settings.iconId}` : null, 260 iconUrl: settings.iconId
261 ? `http://127.0.0.1:${Env.get('PORT')}/v1/icon/${settings.iconId}`
262 : null,
259 id: service.serviceId, 263 id: service.serviceId,
260 name: service.name, 264 name: service.name,
261 recipeId: service.recipeId, 265 recipeId: service.recipeId,
@@ -266,19 +270,13 @@ class ServiceController {
266 return response.send(servicesArray); 270 return response.send(servicesArray);
267 } 271 }
268 272
269 update({ 273 update({ response }) {
270 response,
271 }) {
272 return response.send([]); 274 return response.send([]);
273 } 275 }
274 276
275 async delete({ 277 async delete({ params, response }) {
276 params,
277 response,
278 }) {
279 // Update data in database 278 // Update data in database
280 await (Service.query() 279 await Service.query().where('serviceId', params.id).delete();
281 .where('serviceId', params.id)).delete();
282 280
283 return response.send({ 281 return response.send({
284 message: 'Sucessfully deleted service', 282 message: 'Sucessfully deleted service',
diff --git a/src/internal-server/app/Exceptions/Handler.js b/src/internal-server/app/Exceptions/Handler.js
index 111ef4e0e..ab323fd38 100644
--- a/src/internal-server/app/Exceptions/Handler.js
+++ b/src/internal-server/app/Exceptions/Handler.js
@@ -13,10 +13,9 @@ class ExceptionHandler extends BaseExceptionHandler {
13 * @method handle 13 * @method handle
14 * 14 *
15 * @param {Object} error 15 * @param {Object} error
16 * @param {Object} options.request 16 * @param {object} options.response
17 * @param {Object} options.response
18 * 17 *
19 * @return {void} 18 * @return {Promise<void>}
20 */ 19 */
21 async handle(error, { response }) { 20 async handle(error, { response }) {
22 if (error.name === 'ValidationException') { 21 if (error.name === 'ValidationException') {
@@ -31,10 +30,7 @@ class ExceptionHandler extends BaseExceptionHandler {
31 * 30 *
32 * @method report 31 * @method report
33 * 32 *
34 * @param {Object} error 33 * @return {Promise<boolean>}
35 * @param {Object} options.request
36 *
37 * @return {void}
38 */ 34 */
39 async report() { 35 async report() {
40 return true; 36 return true;
diff --git a/src/internal-server/public/js/transfer.js b/src/internal-server/public/js/transfer.js
index c04a6d3b1..8382bba02 100644
--- a/src/internal-server/public/js/transfer.js
+++ b/src/internal-server/public/js/transfer.js
@@ -1,11 +1,10 @@
1/* eslint-env browser */
2const submitBtn = document.getElementById('submit'); 1const submitBtn = document.getElementById('submit');
3const fileInput = document.getElementById('file'); 2const fileInput = document.getElementById('file');
4const fileOutput = document.getElementById('fileoutput'); 3const fileOutput = document.getElementById('fileoutput');
5 4
6fileInput.addEventListener('change', () => { 5fileInput.addEventListener('change', () => {
7 const reader = new FileReader(); 6 const reader = new FileReader();
8 reader.onload = function () { 7 reader.onload = () => {
9 const text = reader.result; 8 const text = reader.result;
10 fileOutput.value = text; 9 fileOutput.value = text;
11 submitBtn.disabled = false; 10 submitBtn.disabled = false;
diff --git a/src/internal-server/start.js b/src/internal-server/start.js
index adcac0bec..683f24651 100644
--- a/src/internal-server/start.js
+++ b/src/internal-server/start.js
@@ -14,22 +14,22 @@
14| Also you can preload files by calling `preLoad('path/to/file')` method. 14| Also you can preload files by calling `preLoad('path/to/file')` method.
15| Make sure to pass a relative path from the project root. 15| Make sure to pass a relative path from the project root.
16*/ 16*/
17process.env.FERDI_VERSION = '5.4.0-beta.5';
18 17
19const path = require('path'); 18const fold = require('@adonisjs/fold');
19const { Ignitor } = require('@adonisjs/ignitor');
20const fs = require('fs-extra'); 20const fs = require('fs-extra');
21const os = require('os'); 21const os = require('os');
22const path = require('path');
23const packageJSON = require('../../package.json');
22 24
25process.env.FERDI_VERSION = packageJSON.version;
23process.env.ENV_PATH = path.join(__dirname, 'env.ini'); 26process.env.ENV_PATH = path.join(__dirname, 'env.ini');
24 27
25const { Ignitor } = require('@adonisjs/ignitor');
26const fold = require('@adonisjs/fold');
27
28module.exports = async (userPath, port) => { 28module.exports = async (userPath, port) => {
29 const dbPath = path.join(userPath, 'server.sqlite'); 29 const dbPath = path.join(userPath, 'server.sqlite');
30 const dbTemplatePath = path.join(__dirname, 'database', 'template.sqlite'); 30 const dbTemplatePath = path.join(__dirname, 'database', 'template.sqlite');
31 31
32 if (!await fs.exists(dbPath)) { 32 if (!fs.existsSync(dbPath)) {
33 // Manually copy file 33 // Manually copy file
34 // We can't use copyFile here as it will cause the file to be readonly on Windows 34 // We can't use copyFile here as it will cause the file to be readonly on Windows
35 const dbTemplate = await fs.readFile(dbTemplatePath); 35 const dbTemplate = await fs.readFile(dbTemplatePath);
@@ -46,8 +46,5 @@ module.exports = async (userPath, port) => {
46 process.env.USER_PATH = userPath; 46 process.env.USER_PATH = userPath;
47 process.env.PORT = port; 47 process.env.PORT = port;
48 48
49 new Ignitor(fold) 49 new Ignitor(fold).appRoot(__dirname).fireHttpServer().catch(console.error); // eslint-disable-line no-console
50 .appRoot(__dirname)
51 .fireHttpServer()
52 .catch(console.error); // eslint-disable-line no-console
53}; 50};
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index fefcb5080..829797930 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,10 +1,5 @@
1import { shell } from 'electron'; 1import { shell } from 'electron';
2import { 2import { action, reaction, computed, observable } from 'mobx';
3 action,
4 reaction,
5 computed,
6 observable,
7} from 'mobx';
8import { debounce, remove } from 'lodash'; 3import { debounce, remove } from 'lodash';
9import ms from 'ms'; 4import ms from 'ms';
10import { app } from '@electron/remote'; 5import { app } from '@electron/remote';
@@ -16,7 +11,10 @@ import Request from './lib/Request';
16import CachedRequest from './lib/CachedRequest'; 11import CachedRequest from './lib/CachedRequest';
17import { matchRoute } from '../helpers/routing-helpers'; 12import { matchRoute } from '../helpers/routing-helpers';
18import { isInTimeframe } from '../helpers/schedule-helpers'; 13import { isInTimeframe } from '../helpers/schedule-helpers';
19import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; 14import {
15 getRecipeDirectory,
16 getDevRecipeDirectory,
17} from '../helpers/recipe-helpers';
20import { workspaceStore } from '../features/workspaces'; 18import { workspaceStore } from '../features/workspaces';
21import { KEEP_WS_LOADED_USID } from '../config'; 19import { KEEP_WS_LOADED_USID } from '../config';
22import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 20import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -30,7 +28,10 @@ export default class ServicesStore extends Store {
30 28
31 @observable updateServiceRequest = new Request(this.api.services, 'update'); 29 @observable updateServiceRequest = new Request(this.api.services, 'update');
32 30
33 @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); 31 @observable reorderServicesRequest = new Request(
32 this.api.services,
33 'reorder',
34 );
34 35
35 @observable deleteServiceRequest = new Request(this.api.services, 'delete'); 36 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
36 37
@@ -51,22 +52,36 @@ export default class ServicesStore extends Store {
51 this.actions.service.blurActive.listen(this._blurActive.bind(this)); 52 this.actions.service.blurActive.listen(this._blurActive.bind(this));
52 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); 53 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this));
53 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); 54 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this));
54 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); 55 this.actions.service.showAddServiceInterface.listen(
56 this._showAddServiceInterface.bind(this),
57 );
55 this.actions.service.createService.listen(this._createService.bind(this)); 58 this.actions.service.createService.listen(this._createService.bind(this));
56 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); 59 this.actions.service.createFromLegacyService.listen(
60 this._createFromLegacyService.bind(this),
61 );
57 this.actions.service.updateService.listen(this._updateService.bind(this)); 62 this.actions.service.updateService.listen(this._updateService.bind(this));
58 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 63 this.actions.service.deleteService.listen(this._deleteService.bind(this));
59 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this)); 64 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this));
60 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 65 this.actions.service.clearCache.listen(this._clearCache.bind(this));
61 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 66 this.actions.service.setWebviewReference.listen(
67 this._setWebviewReference.bind(this),
68 );
62 this.actions.service.detachService.listen(this._detachService.bind(this)); 69 this.actions.service.detachService.listen(this._detachService.bind(this));
63 this.actions.service.focusService.listen(this._focusService.bind(this)); 70 this.actions.service.focusService.listen(this._focusService.bind(this));
64 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 71 this.actions.service.focusActiveService.listen(
72 this._focusActiveService.bind(this),
73 );
65 this.actions.service.toggleService.listen(this._toggleService.bind(this)); 74 this.actions.service.toggleService.listen(this._toggleService.bind(this));
66 this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this)); 75 this.actions.service.handleIPCMessage.listen(
76 this._handleIPCMessage.bind(this),
77 );
67 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this)); 78 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this));
68 this.actions.service.sendIPCMessageToAllServices.listen(this._sendIPCMessageToAllServices.bind(this)); 79 this.actions.service.sendIPCMessageToAllServices.listen(
69 this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this)); 80 this._sendIPCMessageToAllServices.bind(this),
81 );
82 this.actions.service.setUnreadMessageCount.listen(
83 this._setUnreadMessageCount.bind(this),
84 );
70 this.actions.service.openWindow.listen(this._openWindow.bind(this)); 85 this.actions.service.openWindow.listen(this._openWindow.bind(this));
71 this.actions.service.filter.listen(this._filter.bind(this)); 86 this.actions.service.filter.listen(this._filter.bind(this));
72 this.actions.service.resetFilter.listen(this._resetFilter.bind(this)); 87 this.actions.service.resetFilter.listen(this._resetFilter.bind(this));
@@ -74,16 +89,26 @@ export default class ServicesStore extends Store {
74 this.actions.service.reload.listen(this._reload.bind(this)); 89 this.actions.service.reload.listen(this._reload.bind(this));
75 this.actions.service.reloadActive.listen(this._reloadActive.bind(this)); 90 this.actions.service.reloadActive.listen(this._reloadActive.bind(this));
76 this.actions.service.reloadAll.listen(this._reloadAll.bind(this)); 91 this.actions.service.reloadAll.listen(this._reloadAll.bind(this));
77 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); 92 this.actions.service.reloadUpdatedServices.listen(
93 this._reloadUpdatedServices.bind(this),
94 );
78 this.actions.service.reorder.listen(this._reorder.bind(this)); 95 this.actions.service.reorder.listen(this._reorder.bind(this));
79 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); 96 this.actions.service.toggleNotifications.listen(
97 this._toggleNotifications.bind(this),
98 );
80 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); 99 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
81 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 100 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
82 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 101 this.actions.service.openDevToolsForActiveService.listen(
102 this._openDevToolsForActiveService.bind(this),
103 );
83 this.actions.service.hibernate.listen(this._hibernate.bind(this)); 104 this.actions.service.hibernate.listen(this._hibernate.bind(this));
84 this.actions.service.awake.listen(this._awake.bind(this)); 105 this.actions.service.awake.listen(this._awake.bind(this));
85 this.actions.service.resetLastPollTimer.listen(this._resetLastPollTimer.bind(this)); 106 this.actions.service.resetLastPollTimer.listen(
86 this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); 107 this._resetLastPollTimer.bind(this),
108 );
109 this.actions.service.shareSettingsWithServiceProcess.listen(
110 this._shareSettingsWithServiceProcess.bind(this),
111 );
87 112
88 this.registerReactions([ 113 this.registerReactions([
89 this._focusServiceReaction.bind(this), 114 this._focusServiceReaction.bind(this),
@@ -164,27 +189,42 @@ export default class ServicesStore extends Store {
164 * Run various maintenance tasks on services 189 * Run various maintenance tasks on services
165 */ 190 */
166 _serviceMaintenance() { 191 _serviceMaintenance() {
167 this.all.forEach((service) => { 192 this.all.forEach(service => {
168 // Defines which services should be hibernated or woken up 193 // Defines which services should be hibernated or woken up
169 if (!service.isActive) { 194 if (!service.isActive) {
170 if (!service.lastHibernated && (Date.now() - service.lastUsed > ms(`${this.stores.settings.all.app.hibernationStrategy}s`))) { 195 if (
196 !service.lastHibernated &&
197 Date.now() - service.lastUsed >
198 ms(`${this.stores.settings.all.app.hibernationStrategy}s`)
199 ) {
171 // If service is stale, hibernate it. 200 // If service is stale, hibernate it.
172 this._hibernate({ serviceId: service.id }); 201 this._hibernate({ serviceId: service.id });
173 } 202 }
174 203
175 if (service.lastHibernated && Number(this.stores.settings.all.app.wakeUpStrategy) > 0) { 204 if (
205 service.lastHibernated &&
206 Number(this.stores.settings.all.app.wakeUpStrategy) > 0
207 ) {
176 // If service is in hibernation and the wakeup time has elapsed, wake it. 208 // If service is in hibernation and the wakeup time has elapsed, wake it.
177 if ((Date.now() - service.lastHibernated > ms(`${this.stores.settings.all.app.wakeUpStrategy}s`))) { 209 if (
210 Date.now() - service.lastHibernated >
211 ms(`${this.stores.settings.all.app.wakeUpStrategy}s`)
212 ) {
178 this._awake({ serviceId: service.id }); 213 this._awake({ serviceId: service.id });
179 } 214 }
180 } 215 }
181 } 216 }
182 217
183 if (service.lastPoll && (service.lastPoll - service.lastPollAnswer > ms('1m'))) { 218 if (
219 service.lastPoll &&
220 service.lastPoll - service.lastPollAnswer > ms('1m')
221 ) {
184 // If service did not reply for more than 1m try to reload. 222 // If service did not reply for more than 1m try to reload.
185 if (!service.isActive) { 223 if (!service.isActive) {
186 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { 224 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
187 debug(`Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`); 225 debug(
226 `Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`,
227 );
188 // service.webview.reload(); 228 // service.webview.reload();
189 service.lostRecipeReloadAttempt += 1; 229 service.lostRecipeReloadAttempt += 1;
190 230
@@ -206,21 +246,29 @@ export default class ServicesStore extends Store {
206 if (this.stores.user.isLoggedIn) { 246 if (this.stores.user.isLoggedIn) {
207 const services = this.allServicesRequest.execute().result; 247 const services = this.allServicesRequest.execute().result;
208 if (services) { 248 if (services) {
209 return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => { 249 return observable(
210 s.index = index; 250 services
211 return s; 251 .slice()
212 })); 252 .slice()
253 .sort((a, b) => a.order - b.order)
254 .map((s, index) => {
255 s.index = index;
256 return s;
257 }),
258 );
213 } 259 }
214 } 260 }
215 return []; 261 return [];
216 } 262 }
217 263
218 @computed get enabled() { 264 @computed get enabled() {
219 return this.all.filter((service) => service.isEnabled); 265 return this.all.filter(service => service.isEnabled);
220 } 266 }
221 267
222 @computed get allDisplayed() { 268 @computed get allDisplayed() {
223 const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; 269 const services = this.stores.settings.all.app.showDisabledServices
270 ? this.all
271 : this.enabled;
224 return workspaceStore.filterServicesByActiveWorkspace(services); 272 return workspaceStore.filterServicesByActiveWorkspace(services);
225 } 273 }
226 274
@@ -229,7 +277,9 @@ export default class ServicesStore extends Store {
229 const { showDisabledServices } = this.stores.settings.all.app; 277 const { showDisabledServices } = this.stores.settings.all.app;
230 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings; 278 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings;
231 const services = this.allServicesRequest.execute().result || []; 279 const services = this.allServicesRequest.execute().result || [];
232 const filteredServices = showDisabledServices ? services : services.filter((service) => service.isEnabled); 280 const filteredServices = showDisabledServices
281 ? services
282 : services.filter(service => service.isEnabled);
233 283
234 let displayedServices; 284 let displayedServices;
235 if (keepAllWorkspacesLoaded) { 285 if (keepAllWorkspacesLoaded) {
@@ -237,40 +287,49 @@ export default class ServicesStore extends Store {
237 displayedServices = filteredServices; 287 displayedServices = filteredServices;
238 } else { 288 } else {
239 // Keep all services in current workspace loaded 289 // Keep all services in current workspace loaded
240 displayedServices = workspaceStore.filterServicesByActiveWorkspace(filteredServices); 290 displayedServices =
291 workspaceStore.filterServicesByActiveWorkspace(filteredServices);
241 292
242 // Keep all services active in workspaces that should be kept loaded 293 // Keep all services active in workspaces that should be kept loaded
243 for (const workspace of this.stores.workspaces.workspaces) { 294 for (const workspace of this.stores.workspaces.workspaces) {
244 // Check if workspace needs to be kept loaded 295 // Check if workspace needs to be kept loaded
245 if (workspace.services.includes(KEEP_WS_LOADED_USID)) { 296 if (workspace.services.includes(KEEP_WS_LOADED_USID)) {
246 // Get services for workspace 297 // Get services for workspace
247 const serviceIDs = workspace.services.filter((i) => i !== KEEP_WS_LOADED_USID); 298 const serviceIDs = workspace.services.filter(
248 const wsServices = filteredServices.filter((service) => serviceIDs.includes(service.id)); 299 i => i !== KEEP_WS_LOADED_USID,
249 300 );
250 displayedServices = [ 301 const wsServices = filteredServices.filter(service =>
251 ...displayedServices, 302 serviceIDs.includes(service.id),
252 ...wsServices, 303 );
253 ]; 304
305 displayedServices = [...displayedServices, ...wsServices];
254 } 306 }
255 } 307 }
256 308
257 // Make sure every service is in the list only once 309 // Make sure every service is in the list only once
258 displayedServices = displayedServices.filter((v, i, a) => a.indexOf(v) === i); 310 displayedServices = displayedServices.filter(
311 (v, i, a) => a.indexOf(v) === i,
312 );
259 } 313 }
260 314
261 return displayedServices; 315 return displayedServices;
262 } 316 }
263 317
264 @computed get filtered() { 318 @computed get filtered() {
265 return this.all.filter((service) => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase())); 319 return this.all.filter(service =>
320 service.name.toLowerCase().includes(this.filterNeedle.toLowerCase()),
321 );
266 } 322 }
267 323
268 @computed get active() { 324 @computed get active() {
269 return this.all.find((service) => service.isActive); 325 return this.all.find(service => service.isActive);
270 } 326 }
271 327
272 @computed get activeSettings() { 328 @computed get activeSettings() {
273 const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname); 329 const match = matchRoute(
330 '/settings/services/edit/:id',
331 this.stores.router.location.pathname,
332 );
274 if (match) { 333 if (match) {
275 const activeService = this.one(match.id); 334 const activeService = this.one(match.id);
276 if (activeService) { 335 if (activeService) {
@@ -284,7 +343,11 @@ export default class ServicesStore extends Store {
284 } 343 }
285 344
286 @computed get isTodosServiceAdded() { 345 @computed get isTodosServiceAdded() {
287 return this.allDisplayed.find((service) => service.isTodosService && service.isEnabled) || false; 346 return (
347 this.allDisplayed.find(
348 service => service.isTodosService && service.isEnabled,
349 ) || false
350 );
288 } 351 }
289 352
290 @computed get isTodosServiceActive() { 353 @computed get isTodosServiceActive() {
@@ -292,7 +355,7 @@ export default class ServicesStore extends Store {
292 } 355 }
293 356
294 one(id) { 357 one(id) {
295 return this.all.find((service) => service.id === id); 358 return this.all.find(service => service.id === id);
296 } 359 }
297 360
298 async _showAddServiceInterface({ recipeId }) { 361 async _showAddServiceInterface({ recipeId }) {
@@ -301,7 +364,10 @@ export default class ServicesStore extends Store {
301 364
302 // Actions 365 // Actions
303 async _createService({ 366 async _createService({
304 recipeId, serviceData, redirect = true, skipCleanup = false, 367 recipeId,
368 serviceData,
369 redirect = true,
370 skipCleanup = false,
305 }) { 371 }) {
306 if (!this.stores.recipes.isInstalled(recipeId)) { 372 if (!this.stores.recipes.isInstalled(recipeId)) {
307 debug(`Recipe "${recipeId}" is not installed, installing recipe`); 373 debug(`Recipe "${recipeId}" is not installed, installing recipe`);
@@ -311,17 +377,21 @@ export default class ServicesStore extends Store {
311 377
312 // set default values for serviceData 378 // set default values for serviceData
313 // eslint-disable-next-line prefer-object-spread 379 // eslint-disable-next-line prefer-object-spread
314 Object.assign({ 380 Object.assign(
315 isEnabled: true, 381 {
316 isHibernationEnabled: false, 382 isEnabled: true,
317 isNotificationEnabled: true, 383 isHibernationEnabled: false,
318 isBadgeEnabled: true, 384 isNotificationEnabled: true,
319 isMuted: false, 385 isBadgeEnabled: true,
320 customIcon: false, 386 isMuted: false,
321 isDarkModeEnabled: false, 387 customIcon: false,
322 spellcheckerLanguage: SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], 388 isDarkModeEnabled: false,
323 userAgentPref: '', 389 spellcheckerLanguage:
324 }, serviceData); 390 SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage],
391 userAgentPref: '',
392 },
393 serviceData,
394 );
325 395
326 let data = serviceData; 396 let data = serviceData;
327 397
@@ -329,9 +399,10 @@ export default class ServicesStore extends Store {
329 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 399 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
330 } 400 }
331 401
332 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 402 const response = await this.createServiceRequest.execute(recipeId, data)
403 ._promise;
333 404
334 this.allServicesRequest.patch((result) => { 405 this.allServicesRequest.patch(result => {
335 if (!result) return; 406 if (!result) return;
336 result.push(response.data); 407 result.push(response.data);
337 }); 408 });
@@ -375,7 +446,10 @@ export default class ServicesStore extends Store {
375 446
376 @action async _updateService({ serviceId, serviceData, redirect = true }) { 447 @action async _updateService({ serviceId, serviceData, redirect = true }) {
377 const service = this.one(serviceId); 448 const service = this.one(serviceId);
378 const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); 449 const data = this._cleanUpTeamIdAndCustomUrl(
450 service.recipe.id,
451 serviceData,
452 );
379 const request = this.updateServiceRequest.execute(serviceId, data); 453 const request = this.updateServiceRequest.execute(serviceId, data);
380 454
381 const newData = serviceData; 455 const newData = serviceData;
@@ -386,7 +460,7 @@ export default class ServicesStore extends Store {
386 newData.hasCustomUploadedIcon = true; 460 newData.hasCustomUploadedIcon = true;
387 } 461 }
388 462
389 this.allServicesRequest.patch((result) => { 463 this.allServicesRequest.patch(result => {
390 if (!result) return; 464 if (!result) return;
391 465
392 // patch custom icon deletion 466 // patch custom icon deletion
@@ -400,7 +474,10 @@ export default class ServicesStore extends Store {
400 newData.iconUrl = data.customIconUrl; 474 newData.iconUrl = data.customIconUrl;
401 } 475 }
402 476
403 Object.assign(result.find((c) => c.id === serviceId), newData); 477 Object.assign(
478 result.find(c => c.id === serviceId),
479 newData,
480 );
404 }); 481 });
405 482
406 await request._promise; 483 await request._promise;
@@ -433,8 +510,8 @@ export default class ServicesStore extends Store {
433 this.stores.router.push(redirect); 510 this.stores.router.push(redirect);
434 } 511 }
435 512
436 this.allServicesRequest.patch((result) => { 513 this.allServicesRequest.patch(result => {
437 remove(result, (c) => c.id === serviceId); 514 remove(result, c => c.id === serviceId);
438 }); 515 });
439 516
440 await request._promise; 517 await request._promise;
@@ -459,12 +536,15 @@ export default class ServicesStore extends Store {
459 // Create and open file 536 // Create and open file
460 const filePath = path.join(directory, file); 537 const filePath = path.join(directory, file);
461 if (file === 'user.js') { 538 if (file === 'user.js') {
462 if (!await fs.exists(filePath)) { 539 if (!fs.existsSync(filePath)) {
463 await fs.writeFile(filePath, `module.exports = (config, Ferdi) => { 540 await fs.writeFile(
541 filePath,
542 `module.exports = (config, Ferdi) => {
464 // Write your scripts here 543 // Write your scripts here
465 console.log("Hello, World!", config); 544 console.log("Hello, World!", config);
466} 545}
467`); 546`,
547 );
468 } 548 }
469 } else { 549 } else {
470 await fs.ensureFile(filePath); 550 await fs.ensureFile(filePath);
@@ -478,22 +558,27 @@ export default class ServicesStore extends Store {
478 await request._promise; 558 await request._promise;
479 } 559 }
480 560
481 @action _setActive({ serviceId, keepActiveRoute }) { 561 @action _setActive({ serviceId, keepActiveRoute = null }) {
482 if (!keepActiveRoute) this.stores.router.push('/'); 562 if (!keepActiveRoute) this.stores.router.push('/');
483 const service = this.one(serviceId); 563 const service = this.one(serviceId);
484 564
485 this.all.forEach((s) => { 565 this.all.forEach(s => {
486 s.isActive = false; 566 s.isActive = false;
487 }); 567 });
488 service.isActive = true; 568 service.isActive = true;
489 this._awake({ serviceId: service.id }); 569 this._awake({ serviceId: service.id });
490 570
491 if (this.isTodosServiceActive && !this.stores.todos.settings.isFeatureEnabledByUser) { 571 if (
572 this.isTodosServiceActive &&
573 !this.stores.todos.settings.isFeatureEnabledByUser
574 ) {
492 this.actions.todos.toggleTodosFeatureVisibility(); 575 this.actions.todos.toggleTodosFeatureVisibility();
493 } 576 }
494 577
495 // Update list of last used services 578 // Update list of last used services
496 this.lastUsedServices = this.lastUsedServices.filter((id) => id !== serviceId); 579 this.lastUsedServices = this.lastUsedServices.filter(
580 id => id !== serviceId,
581 );
497 this.lastUsedServices.unshift(serviceId); 582 this.lastUsedServices.unshift(serviceId);
498 583
499 this._focusActiveService(); 584 this._focusActiveService();
@@ -505,7 +590,11 @@ export default class ServicesStore extends Store {
505 } 590 }
506 591
507 @action _setActiveNext() { 592 @action _setActiveNext() {
508 const nextIndex = this._wrapIndex(this.allDisplayed.findIndex((service) => service.isActive), 1, this.allDisplayed.length); 593 const nextIndex = this._wrapIndex(
594 this.allDisplayed.findIndex(service => service.isActive),
595 1,
596 this.allDisplayed.length,
597 );
509 598
510 // TODO: simplify this; 599 // TODO: simplify this;
511 this.all.forEach((s, index) => { 600 this.all.forEach((s, index) => {
@@ -515,7 +604,11 @@ export default class ServicesStore extends Store {
515 } 604 }
516 605
517 @action _setActivePrev() { 606 @action _setActivePrev() {
518 const prevIndex = this._wrapIndex(this.allDisplayed.findIndex((service) => service.isActive), -1, this.allDisplayed.length); 607 const prevIndex = this._wrapIndex(
608 this.allDisplayed.findIndex(service => service.isActive),
609 -1,
610 this.allDisplayed.length,
611 );
519 612
520 // TODO: simplify this; 613 // TODO: simplify this;
521 this.all.forEach((s, index) => { 614 this.all.forEach((s, index) => {
@@ -606,17 +699,21 @@ export default class ServicesStore extends Store {
606 const { options } = args[0]; 699 const { options } = args[0];
607 700
608 // Check if we are in scheduled Do-not-Disturb time 701 // Check if we are in scheduled Do-not-Disturb time
609 const { 702 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } =
610 scheduledDNDEnabled, 703 this.stores.settings.all.app;
611 scheduledDNDStart,
612 scheduledDNDEnd,
613 } = this.stores.settings.all.app;
614 704
615 if (scheduledDNDEnabled && isInTimeframe(scheduledDNDStart, scheduledDNDEnd)) { 705 if (
706 scheduledDNDEnabled &&
707 isInTimeframe(scheduledDNDStart, scheduledDNDEnd)
708 ) {
616 return; 709 return;
617 } 710 }
618 711
619 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) { 712 if (
713 service.recipe.hasNotificationSound ||
714 service.isMuted ||
715 this.stores.settings.all.app.isAppMuted
716 ) {
620 Object.assign(options, { 717 Object.assign(options, {
621 silent: true, 718 silent: true,
622 }); 719 });
@@ -626,7 +723,8 @@ export default class ServicesStore extends Store {
626 let title = `Notification from ${service.name}`; 723 let title = `Notification from ${service.name}`;
627 if (!this.stores.settings.all.app.privateNotifications) { 724 if (!this.stores.settings.all.app.privateNotifications) {
628 options.body = typeof options.body === 'string' ? options.body : ''; 725 options.body = typeof options.body === 'string' ? options.body : '';
629 title = typeof args[0].title === 'string' ? args[0].title : service.name; 726 title =
727 typeof args[0].title === 'string' ? args[0].title : service.name;
630 } else { 728 } else {
631 // Remove message data from notification in private mode 729 // Remove message data from notification in private mode
632 options.body = ''; 730 options.body = '';
@@ -689,11 +787,13 @@ export default class ServicesStore extends Store {
689 } 787 }
690 788
691 @action _sendIPCMessageToAllServices({ channel, args }) { 789 @action _sendIPCMessageToAllServices({ channel, args }) {
692 this.all.forEach((s) => this.actions.service.sendIPCMessage({ 790 this.all.forEach(s =>
693 serviceId: s.id, 791 this.actions.service.sendIPCMessage({
694 channel, 792 serviceId: s.id,
695 args, 793 channel,
696 })); 794 args,
795 }),
796 );
697 } 797 }
698 798
699 @action _openWindow({ event }) { 799 @action _openWindow({ event }) {
@@ -740,9 +840,11 @@ export default class ServicesStore extends Store {
740 } 840 }
741 841
742 @action _reloadAll() { 842 @action _reloadAll() {
743 this.enabled.forEach((s) => this._reload({ 843 this.enabled.forEach(s =>
744 serviceId: s.id, 844 this._reload({
745 })); 845 serviceId: s.id,
846 }),
847 );
746 } 848 }
747 849
748 @action _reloadUpdatedServices() { 850 @action _reloadUpdatedServices() {
@@ -761,10 +863,18 @@ export default class ServicesStore extends Store {
761 863
762 @action _reorderService({ oldIndex, newIndex }) { 864 @action _reorderService({ oldIndex, newIndex }) {
763 const { showDisabledServices } = this.stores.settings.all.app; 865 const { showDisabledServices } = this.stores.settings.all.app;
764 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 866 const oldEnabledSortIndex = showDisabledServices
765 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 867 ? oldIndex
766 868 : this.all.indexOf(this.enabled[oldIndex]);
767 this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]); 869 const newEnabledSortIndex = showDisabledServices
870 ? newIndex
871 : this.all.indexOf(this.enabled[newIndex]);
872
873 this.all.splice(
874 newEnabledSortIndex,
875 0,
876 this.all.splice(oldEnabledSortIndex, 1)[0],
877 );
768 878
769 const services = {}; 879 const services = {};
770 this.all.forEach((s, index) => { 880 this.all.forEach((s, index) => {
@@ -772,8 +882,8 @@ export default class ServicesStore extends Store {
772 }); 882 });
773 883
774 this.reorderServicesRequest.execute(services); 884 this.reorderServicesRequest.execute(services);
775 this.allServicesRequest.patch((data) => { 885 this.allServicesRequest.patch(data => {
776 data.forEach((s) => { 886 data.forEach(s => {
777 const service = s; 887 const service = s;
778 888
779 service.order = services[s.id]; 889 service.order = services[s.id];
@@ -851,15 +961,19 @@ export default class ServicesStore extends Store {
851 } 961 }
852 962
853 @action _resetLastPollTimer({ serviceId = null }) { 963 @action _resetLastPollTimer({ serviceId = null }) {
854 debug(`Reset last poll timer for ${serviceId ? `service: "${serviceId}"` : 'all services'}`); 964 debug(
965 `Reset last poll timer for ${
966 serviceId ? `service: "${serviceId}"` : 'all services'
967 }`,
968 );
855 969
856 const resetTimer = (service) => { 970 const resetTimer = service => {
857 service.lastPollAnswer = Date.now(); 971 service.lastPollAnswer = Date.now();
858 service.lastPoll = Date.now(); 972 service.lastPoll = Date.now();
859 }; 973 };
860 974
861 if (!serviceId) { 975 if (!serviceId) {
862 this.allDisplayed.forEach((service) => resetTimer(service)); 976 this.allDisplayed.forEach(service => resetTimer(service));
863 } else { 977 } else {
864 const service = this.one(serviceId); 978 const service = this.one(serviceId);
865 if (service) { 979 if (service) {
@@ -893,9 +1007,13 @@ export default class ServicesStore extends Store {
893 _mapActiveServiceToServiceModelReaction() { 1007 _mapActiveServiceToServiceModelReaction() {
894 const { activeService } = this.stores.settings.all.service; 1008 const { activeService } = this.stores.settings.all.service;
895 if (this.allDisplayed.length) { 1009 if (this.allDisplayed.length) {
896 this.allDisplayed.map((service) => Object.assign(service, { 1010 this.allDisplayed.map(service =>
897 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id, 1011 Object.assign(service, {
898 })); 1012 isActive: activeService
1013 ? activeService === service.id
1014 : this.allDisplayed[0].id === service.id,
1015 }),
1016 );
899 } 1017 }
900 } 1018 }
901 1019
@@ -904,13 +1022,24 @@ export default class ServicesStore extends Store {
904 const { showMessageBadgesEvenWhenMuted } = this.stores.ui; 1022 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
905 1023
906 const unreadDirectMessageCount = this.allDisplayed 1024 const unreadDirectMessageCount = this.allDisplayed
907 .filter((s) => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) 1025 .filter(
908 .map((s) => s.unreadDirectMessageCount) 1026 s =>
1027 (showMessageBadgeWhenMuted || s.isNotificationEnabled) &&
1028 showMessageBadgesEvenWhenMuted &&
1029 s.isBadgeEnabled,
1030 )
1031 .map(s => s.unreadDirectMessageCount)
909 .reduce((a, b) => a + b, 0); 1032 .reduce((a, b) => a + b, 0);
910 1033
911 const unreadIndirectMessageCount = this.allDisplayed 1034 const unreadIndirectMessageCount = this.allDisplayed
912 .filter((s) => (showMessageBadgeWhenMuted && showMessageBadgesEvenWhenMuted) && (s.isBadgeEnabled && s.isIndirectMessageBadgeEnabled)) 1035 .filter(
913 .map((s) => s.unreadIndirectMessageCount) 1036 s =>
1037 showMessageBadgeWhenMuted &&
1038 showMessageBadgesEvenWhenMuted &&
1039 s.isBadgeEnabled &&
1040 s.isIndirectMessageBadgeEnabled,
1041 )
1042 .map(s => s.unreadIndirectMessageCount)
914 .reduce((a, b) => a + b, 0); 1043 .reduce((a, b) => a + b, 0);
915 1044
916 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases 1045 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases
@@ -936,7 +1065,7 @@ export default class ServicesStore extends Store {
936 const { enabled } = this; 1065 const { enabled } = this;
937 const { isAppMuted } = this.stores.settings.app; 1066 const { isAppMuted } = this.stores.settings.app;
938 1067
939 enabled.forEach((service) => { 1068 enabled.forEach(service => {
940 const { isAttached } = service; 1069 const { isAttached } = service;
941 const isMuted = isAppMuted || service.isMuted; 1070 const isMuted = isAppMuted || service.isMuted;
942 1071
@@ -963,7 +1092,12 @@ export default class ServicesStore extends Store {
963 1092
964 if (!recipe) return; 1093 if (!recipe) return;
965 1094
966 if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { 1095 if (
1096 recipe.hasTeamId &&
1097 recipe.hasCustomUrl &&
1098 data.team &&
1099 data.customUrl
1100 ) {
967 delete serviceData.team; 1101 delete serviceData.team;
968 } 1102 }
969 1103
@@ -971,11 +1105,17 @@ export default class ServicesStore extends Store {
971 } 1105 }
972 1106
973 _checkForActiveService() { 1107 _checkForActiveService() {
974 if (!this.stores.router.location || this.stores.router.location.pathname.includes('auth/signup')) { 1108 if (
1109 !this.stores.router.location ||
1110 this.stores.router.location.pathname.includes('auth/signup')
1111 ) {
975 return; 1112 return;
976 } 1113 }
977 1114
978 if (this.allDisplayed.findIndex((service) => service.isActive) === -1 && this.allDisplayed.length !== 0) { 1115 if (
1116 this.allDisplayed.findIndex(service => service.isActive) === -1 &&
1117 this.allDisplayed.length !== 0
1118 ) {
979 debug('No active service found, setting active service to index 0'); 1119 debug('No active service found, setting active service to index 0');
980 1120
981 this._setActive({ serviceId: this.allDisplayed[0].id }); 1121 this._setActive({ serviceId: this.allDisplayed[0].id });
@@ -988,13 +1128,19 @@ export default class ServicesStore extends Store {
988 1128
989 if (service.webview) { 1129 if (service.webview) {
990 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC 1130 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC
991 const shareWithWebview = JSON.parse(JSON.stringify(service.shareWithWebview)); 1131 const shareWithWebview = JSON.parse(
1132 JSON.stringify(service.shareWithWebview),
1133 );
992 1134
993 debug('Initialize recipe', service.recipe.id, service.name); 1135 debug('Initialize recipe', service.recipe.id, service.name);
994 service.webview.send('initialize-recipe', { 1136 service.webview.send(
995 ...shareWithWebview, 1137 'initialize-recipe',
996 franzVersion: app.getVersion(), 1138 {
997 }, service.recipe); 1139 ...shareWithWebview,
1140 franzVersion: app.getVersion(),
1141 },
1142 service.recipe,
1143 );
998 } 1144 }
999 } 1145 }
1000 1146
diff --git a/src/webview/recipe.js b/src/webview/recipe.js
index d143675dc..598c3eb9a 100644
--- a/src/webview/recipe.js
+++ b/src/webview/recipe.js
@@ -23,11 +23,25 @@ import Userscript from './lib/Userscript';
23 23
24import { BadgeHandler } from './badge'; 24import { BadgeHandler } from './badge';
25import contextMenu from './contextMenu'; 25import contextMenu from './contextMenu';
26import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; 26import {
27 injectDarkModeStyle,
28 isDarkModeStyleInjected,
29 removeDarkModeStyle,
30} from './darkmode';
27import FindInPage from './find'; 31import FindInPage from './find';
28import { NotificationsHandler, notificationsClassDefinition } from './notifications'; 32import {
29import { getDisplayMediaSelector, screenShareCss, screenShareJs } from './screenshare'; 33 NotificationsHandler,
30import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; 34 notificationsClassDefinition,
35} from './notifications';
36import {
37 getDisplayMediaSelector,
38 screenShareCss,
39 screenShareJs,
40} from './screenshare';
41import {
42 switchDict,
43 getSpellcheckerLocaleByFuzzyIdentifier,
44} from './spellchecker';
31 45
32import { DEFAULT_APP_SETTINGS } from '../environment'; 46import { DEFAULT_APP_SETTINGS } from '../environment';
33 47
@@ -86,15 +100,19 @@ window.open = (url, frameName, features) => {
86// then overwrite the corresponding field of the window object by injected JS. 100// then overwrite the corresponding field of the window object by injected JS.
87contextBridge.exposeInMainWorld('ferdi', { 101contextBridge.exposeInMainWorld('ferdi', {
88 open: window.open, 102 open: window.open,
89 setBadge: (direct, indirect) => badgeHandler.setBadge(direct || 0, indirect || 0), 103 setBadge: (direct, indirect) =>
90 displayNotification: (title, options) => notificationsHandler.displayNotification(title, options), 104 badgeHandler.setBadge(direct || 0, indirect || 0),
105 displayNotification: (title, options) =>
106 notificationsHandler.displayNotification(title, options),
91 getDisplayMediaSelector, 107 getDisplayMediaSelector,
92}); 108});
93 109
94ipcRenderer.sendToHost('inject-js-unsafe', 110ipcRenderer.sendToHost(
111 'inject-js-unsafe',
95 'window.open = window.ferdi.open;', 112 'window.open = window.ferdi.open;',
96 notificationsClassDefinition, 113 notificationsClassDefinition,
97 screenShareJs); 114 screenShareJs,
115);
98 116
99class RecipeController { 117class RecipeController {
100 @observable settings = { 118 @observable settings = {
@@ -129,7 +147,9 @@ class RecipeController {
129 } 147 }
130 148
131 @computed get spellcheckerLanguage() { 149 @computed get spellcheckerLanguage() {
132 const selected = this.settings.service.spellcheckerLanguage || this.settings.app.spellcheckerLanguage; 150 const selected =
151 this.settings.service.spellcheckerLanguage ||
152 this.settings.app.spellcheckerLanguage;
133 return selected; 153 return selected;
134 } 154 }
135 155
@@ -138,7 +158,7 @@ class RecipeController {
138 findInPage = null; 158 findInPage = null;
139 159
140 async initialize() { 160 async initialize() {
141 Object.keys(this.ipcEvents).forEach((channel) => { 161 Object.keys(this.ipcEvents).forEach(channel => {
142 ipcRenderer.on(channel, (...args) => { 162 ipcRenderer.on(channel, (...args) => {
143 debug('Received IPC event for channel', channel, 'with', ...args); 163 debug('Received IPC event for channel', channel, 'with', ...args);
144 this[this.ipcEvents[channel]](...args); 164 this[this.ipcEvents[channel]](...args);
@@ -176,7 +196,7 @@ class RecipeController {
176 try { 196 try {
177 this.recipe = new RecipeWebview(badgeHandler, notificationsHandler); 197 this.recipe = new RecipeWebview(badgeHandler, notificationsHandler);
178 // eslint-disable-next-line 198 // eslint-disable-next-line
179 require(modulePath)(this.recipe, {...config, recipe,}); 199 require(modulePath)(this.recipe, { ...config, recipe });
180 debug('Initialize Recipe', config, recipe); 200 debug('Initialize Recipe', config, recipe);
181 201
182 this.settings.service = Object.assign(config, { recipe }); 202 this.settings.service = Object.assign(config, { recipe });
@@ -195,14 +215,14 @@ class RecipeController {
195 styles.innerHTML = screenShareCss; 215 styles.innerHTML = screenShareCss;
196 216
197 const userCss = path.join(recipe.path, 'user.css'); 217 const userCss = path.join(recipe.path, 'user.css');
198 if (await fs.exists(userCss)) { 218 if (fs.existsSync(userCss)) {
199 const data = await fs.readFile(userCss); 219 const data = await fs.readFile(userCss);
200 styles.innerHTML += data.toString(); 220 styles.innerHTML += data.toString();
201 } 221 }
202 document.querySelector('head').appendChild(styles); 222 document.querySelector('head').appendChild(styles);
203 223
204 const userJs = path.join(recipe.path, 'user.js'); 224 const userJs = path.join(recipe.path, 'user.js');
205 if (await fs.exists(userJs)) { 225 if (fs.existsSync(userJs)) {
206 const loadUserJs = () => { 226 const loadUserJs = () => {
207 // eslint-disable-next-line 227 // eslint-disable-next-line
208 const userJsModule = require(userJs); 228 const userJsModule = require(userJs);
@@ -230,8 +250,14 @@ class RecipeController {
230 update() { 250 update() {
231 debug('enableSpellchecking', this.settings.app.enableSpellchecking); 251 debug('enableSpellchecking', this.settings.app.enableSpellchecking);
232 debug('isDarkModeEnabled', this.settings.service.isDarkModeEnabled); 252 debug('isDarkModeEnabled', this.settings.service.isDarkModeEnabled);
233 debug('System spellcheckerLanguage', this.settings.app.spellcheckerLanguage); 253 debug(
234 debug('Service spellcheckerLanguage', this.settings.service.spellcheckerLanguage); 254 'System spellcheckerLanguage',
255 this.settings.app.spellcheckerLanguage,
256 );
257 debug(
258 'Service spellcheckerLanguage',
259 this.settings.service.spellcheckerLanguage,
260 );
235 debug('darkReaderSettigs', this.settings.service.darkReaderSettings); 261 debug('darkReaderSettigs', this.settings.service.darkReaderSettings);
236 debug('searchEngine', this.settings.app.searchEngine); 262 debug('searchEngine', this.settings.app.searchEngine);
237 263
@@ -244,7 +270,10 @@ class RecipeController {
244 let { spellcheckerLanguage } = this; 270 let { spellcheckerLanguage } = this;
245 if (spellcheckerLanguage.includes('automatic')) { 271 if (spellcheckerLanguage.includes('automatic')) {
246 this.automaticLanguageDetection(); 272 this.automaticLanguageDetection();
247 debug('Found `automatic` locale, falling back to user locale until detected', this.settings.app.locale); 273 debug(
274 'Found `automatic` locale, falling back to user locale until detected',
275 this.settings.app.locale,
276 );
248 spellcheckerLanguage = this.settings.app.locale; 277 spellcheckerLanguage = this.settings.app.locale;
249 } 278 }
250 switchDict(spellcheckerLanguage); 279 switchDict(spellcheckerLanguage);
@@ -256,7 +285,7 @@ class RecipeController {
256 this.hasUpdatedBeforeRecipeLoaded = true; 285 this.hasUpdatedBeforeRecipeLoaded = true;
257 } 286 }
258 287
259 console.log( 288 debug(
260 'Darkmode enabled?', 289 'Darkmode enabled?',
261 this.settings.service.isDarkModeEnabled, 290 this.settings.service.isDarkModeEnabled,
262 'Dark theme active?', 291 'Dark theme active?',
@@ -267,22 +296,29 @@ class RecipeController {
267 removeDarkModeStyle, 296 removeDarkModeStyle,
268 disableDarkMode, 297 disableDarkMode,
269 enableDarkMode, 298 enableDarkMode,
270 injectDarkModeStyle: () => injectDarkModeStyle(this.settings.service.recipe.path), 299 injectDarkModeStyle: () =>
300 injectDarkModeStyle(this.settings.service.recipe.path),
271 isDarkModeStyleInjected, 301 isDarkModeStyleInjected,
272 }; 302 };
273 303
274 if (this.settings.service.isDarkModeEnabled && this.settings.app.isDarkThemeActive !== false) { 304 if (
305 this.settings.service.isDarkModeEnabled &&
306 this.settings.app.isDarkThemeActive !== false
307 ) {
275 debug('Enable dark mode'); 308 debug('Enable dark mode');
276 309
277 // Check if recipe has a darkmode.css 310 // Check if recipe has a darkmode.css
278 const darkModeStyle = path.join(this.settings.service.recipe.path, 'darkmode.css'); 311 const darkModeStyle = path.join(
312 this.settings.service.recipe.path,
313 'darkmode.css',
314 );
279 const darkModeExists = fs.pathExistsSync(darkModeStyle); 315 const darkModeExists = fs.pathExistsSync(darkModeStyle);
280 316
281 console.log('darkmode.css exists? ', darkModeExists ? 'Yes' : 'No'); 317 debug('darkmode.css exists? ', darkModeExists ? 'Yes' : 'No');
282 318
283 // Check if recipe has a custom dark mode handler 319 // Check if recipe has a custom dark mode handler
284 if (this.recipe && this.recipe.darkModeHandler) { 320 if (this.recipe && this.recipe.darkModeHandler) {
285 console.log('Using custom dark mode handler'); 321 debug('Using custom dark mode handler');
286 322
287 // Remove other dark mode styles if they were already loaded 323 // Remove other dark mode styles if they were already loaded
288 if (this.hasUpdatedBeforeRecipeLoaded) { 324 if (this.hasUpdatedBeforeRecipeLoaded) {
@@ -293,25 +329,32 @@ class RecipeController {
293 329
294 this.recipe.darkModeHandler(true, handlerConfig); 330 this.recipe.darkModeHandler(true, handlerConfig);
295 } else if (darkModeExists) { 331 } else if (darkModeExists) {
296 console.log('Injecting darkmode.css'); 332 debug('Injecting darkmode.css');
297 injectDarkModeStyle(this.settings.service.recipe.path); 333 injectDarkModeStyle(this.settings.service.recipe.path);
298 334
299 // Make sure universal dark mode is disabled 335 // Make sure universal dark mode is disabled
300 disableDarkMode(); 336 disableDarkMode();
301 this.universalDarkModeInjected = false; 337 this.universalDarkModeInjected = false;
302 } else if (this.settings.app.universalDarkMode && !ignoreList.includes(window.location.host)) { 338 } else if (
303 console.log('Injecting Dark Reader'); 339 this.settings.app.universalDarkMode &&
340 !ignoreList.includes(window.location.host)
341 ) {
342 debug('Injecting Dark Reader');
304 343
305 // Use Dark Reader instead 344 // Use Dark Reader instead
306 const { brightness, contrast, sepia } = this.settings.service.darkReaderSettings; 345 const { brightness, contrast, sepia } =
307 enableDarkMode({ brightness, contrast, sepia }, { 346 this.settings.service.darkReaderSettings;
308 css: customDarkModeCss[window.location.host] || '', 347 enableDarkMode(
309 }); 348 { brightness, contrast, sepia },
349 {
350 css: customDarkModeCss[window.location.host] || '',
351 },
352 );
310 this.universalDarkModeInjected = true; 353 this.universalDarkModeInjected = true;
311 } 354 }
312 } else { 355 } else {
313 debug('Remove dark mode'); 356 debug('Remove dark mode');
314 console.log('DarkMode disabled - removing remaining styles'); 357 debug('DarkMode disabled - removing remaining styles');
315 358
316 if (this.recipe && this.recipe.darkModeHandler) { 359 if (this.recipe && this.recipe.darkModeHandler) {
317 // Remove other dark mode styles if they were already loaded 360 // Remove other dark mode styles if they were already loaded
@@ -323,10 +366,10 @@ class RecipeController {
323 366
324 this.recipe.darkModeHandler(false, handlerConfig); 367 this.recipe.darkModeHandler(false, handlerConfig);
325 } else if (isDarkModeStyleInjected()) { 368 } else if (isDarkModeStyleInjected()) {
326 console.log('Removing injected darkmode.css'); 369 debug('Removing injected darkmode.css');
327 removeDarkModeStyle(); 370 removeDarkModeStyle();
328 } else { 371 } else {
329 console.log('Removing Dark Reader'); 372 debug('Removing Dark Reader');
330 373
331 disableDarkMode(); 374 disableDarkMode();
332 this.universalDarkModeInjected = false; 375 this.universalDarkModeInjected = false;
@@ -336,9 +379,9 @@ class RecipeController {
336 // Remove dark reader if (universal) dark mode was just disabled 379 // Remove dark reader if (universal) dark mode was just disabled
337 if (this.universalDarkModeInjected) { 380 if (this.universalDarkModeInjected) {
338 if ( 381 if (
339 !this.settings.app.darkMode 382 !this.settings.app.darkMode ||
340 || !this.settings.service.isDarkModeEnabled 383 !this.settings.service.isDarkModeEnabled ||
341 || !this.settings.app.universalDarkMode 384 !this.settings.app.universalDarkMode
342 ) { 385 ) {
343 disableDarkMode(); 386 disableDarkMode();
344 this.universalDarkModeInjected = false; 387 this.universalDarkModeInjected = false;
@@ -360,30 +403,39 @@ class RecipeController {
360 } 403 }
361 404
362 async automaticLanguageDetection() { 405 async automaticLanguageDetection() {
363 window.addEventListener('keyup', debounce(async (e) => { 406 window.addEventListener(
364 const element = e.target; 407 'keyup',
365 408 debounce(async e => {
366 if (!element) return; 409 const element = e.target;
367 410
368 let value = ''; 411 if (!element) return;
369 if (element.isContentEditable) { 412
370 value = element.textContent; 413 let value = '';
371 } else if (element.value) { 414 if (element.isContentEditable) {
372 value = element.value; 415 value = element.textContent;
373 } 416 } else if (element.value) {
417 value = element.value;
418 }
374 419
375 // Force a minimum length to get better detection results 420 // Force a minimum length to get better detection results
376 if (value.length < 25) return; 421 if (value.length < 25) return;
377 422
378 debug('Detecting language for', value); 423 debug('Detecting language for', value);
379 const locale = await ipcRenderer.invoke('detect-language', { sample: value }); 424 const locale = await ipcRenderer.invoke('detect-language', {
425 sample: value,
426 });
380 427
381 const spellcheckerLocale = getSpellcheckerLocaleByFuzzyIdentifier(locale); 428 const spellcheckerLocale =
382 debug('Language detected reliably, setting spellchecker language to', spellcheckerLocale); 429 getSpellcheckerLocaleByFuzzyIdentifier(locale);
383 if (spellcheckerLocale) { 430 debug(
384 switchDict(spellcheckerLocale); 431 'Language detected reliably, setting spellchecker language to',
385 } 432 spellcheckerLocale,
386 }, 225)); 433 );
434 if (spellcheckerLocale) {
435 switchDict(spellcheckerLocale);
436 }
437 }, 225),
438 );
387 } 439 }
388} 440}
389 441