diff options
99 files changed, 1439 insertions, 1137 deletions
diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 26159ed87..000000000 --- a/.eslintrc +++ /dev/null | |||
@@ -1,165 +0,0 @@ | |||
1 | { | ||
2 | "root": true, | ||
3 | "parser": "@babel/eslint-parser", | ||
4 | "extends": "eslint-config-airbnb", | ||
5 | "plugins": [ | ||
6 | "jest" | ||
7 | ], | ||
8 | "overrides": [ | ||
9 | { | ||
10 | "files": [ | ||
11 | "**/*.ts", | ||
12 | "**/*.tsx" | ||
13 | ], | ||
14 | "env": { | ||
15 | "browser": true, | ||
16 | "es6": true, | ||
17 | "node": true | ||
18 | }, | ||
19 | "extends": [ | ||
20 | "airbnb-typescript" | ||
21 | ], | ||
22 | "parser": "@typescript-eslint/parser", | ||
23 | "parserOptions": { | ||
24 | "ecmaFeatures": { | ||
25 | "jsx": true | ||
26 | }, | ||
27 | "ecmaVersion": 2018, | ||
28 | "sourceType": "module", | ||
29 | "project": "./tsconfig.json" | ||
30 | }, | ||
31 | "plugins": [ | ||
32 | "@typescript-eslint" | ||
33 | ], | ||
34 | "rules": { | ||
35 | // eslint | ||
36 | "arrow-parens": 0, | ||
37 | "array-callback-return": 1, | ||
38 | "class-methods-use-this": 0, | ||
39 | "consistent-return": 0, | ||
40 | "function-paren-newline": 0, | ||
41 | "implicit-arrow-linebreak": 0, | ||
42 | "linebreak-style": 0, | ||
43 | "max-len": 0, | ||
44 | "no-confusing-arrow": 0, | ||
45 | "no-console": 0, | ||
46 | "no-param-reassign": 0, | ||
47 | "no-return-assign": 1, | ||
48 | "no-underscore-dangle": 0, | ||
49 | "no-use-before-define": 0, | ||
50 | "prefer-destructuring": 1, | ||
51 | "object-curly-newline": 0, | ||
52 | "operator-linebreak": 0, | ||
53 | // @typescript-eslint | ||
54 | "@typescript-eslint/indent": 0, | ||
55 | "@typescript-eslint/no-shadow": 0, | ||
56 | "@typescript-eslint/no-unused-expressions": 0, | ||
57 | // eslint-plugin-import | ||
58 | "import/extensions": 0, | ||
59 | "import/no-cycle": 1, | ||
60 | "import/no-extraneous-dependencies": 0, | ||
61 | "import/no-unresolved": 0, | ||
62 | "import/prefer-default-export": 0, | ||
63 | // eslint-plugin-react | ||
64 | "react/destructuring-assignment": 0, | ||
65 | "react/button-has-type": 0, | ||
66 | "react/forbid-prop-types": 0, | ||
67 | "react/jsx-curly-newline": 0, | ||
68 | "react/jsx-no-bind": 0, | ||
69 | "react/jsx-no-target-blank": 0, | ||
70 | "react/jsx-props-no-spreading": 0, | ||
71 | "react/no-deprecated": 1, | ||
72 | "react/no-array-index-key": 0, | ||
73 | "react/prefer-stateless-function": 0, | ||
74 | "react/sort-comp": 0, | ||
75 | "react/state-in-constructor": 0, | ||
76 | "react/static-property-placement": 0, | ||
77 | // eslint-plugin-jsx-a11y | ||
78 | "jsx-a11y/click-events-have-key-events": 1, | ||
79 | "jsx-a11y/mouse-events-have-key-events": 1, | ||
80 | "jsx-a11y/label-has-for": [ | ||
81 | 2, | ||
82 | { | ||
83 | "components": [ | ||
84 | "Label" | ||
85 | ], | ||
86 | "required": { | ||
87 | "every": [ | ||
88 | "id" | ||
89 | ] | ||
90 | }, | ||
91 | "allowChildren": false | ||
92 | } | ||
93 | ], | ||
94 | "jsx-a11y/no-static-element-interactions": 0, | ||
95 | "jsx-a11y/no-noninteractive-element-interactions": 1 | ||
96 | } | ||
97 | } | ||
98 | ], | ||
99 | "settings": { | ||
100 | "react": { | ||
101 | "pragma": "React", // Pragma to use, default to "React" | ||
102 | "version": "detect" // React version. "detect" automatically picks the version you have installed. | ||
103 | } | ||
104 | }, | ||
105 | "globals": { | ||
106 | "window": true, | ||
107 | "document": true, | ||
108 | "FormData": true, | ||
109 | "localStorage": true, | ||
110 | "navigator": true, | ||
111 | "Element": true, | ||
112 | "use": true, | ||
113 | "FileReader": true | ||
114 | }, | ||
115 | "env": { | ||
116 | "jest/globals": true | ||
117 | }, | ||
118 | "rules": { | ||
119 | // eslint | ||
120 | "arrow-parens": 0, | ||
121 | "class-methods-use-this": 0, | ||
122 | "consistent-return": 1, | ||
123 | "implicit-arrow-linebreak": 0, | ||
124 | "function-paren-newline": 0, | ||
125 | "max-len": 0, | ||
126 | "no-await-in-loop": 1, | ||
127 | "no-console": [ | ||
128 | 1, | ||
129 | { | ||
130 | "allow": [ | ||
131 | "warn", | ||
132 | "error" | ||
133 | ] | ||
134 | } | ||
135 | ], | ||
136 | "no-param-reassign": 1, | ||
137 | "no-restricted-syntax": 0, | ||
138 | "no-underscore-dangle": 0, | ||
139 | "operator-linebreak": 0, | ||
140 | "prefer-destructuring": 1, | ||
141 | "object-curly-newline": 0, | ||
142 | // eslint-plugin-import | ||
143 | "import/extensions": 1, | ||
144 | "import/prefer-default-export": 0, | ||
145 | "import/no-extraneous-dependencies": 0, // various false positives, re-enable at some point | ||
146 | "import/no-unresolved": 1, | ||
147 | // eslint-plugin-react | ||
148 | "react/forbid-prop-types": 1, | ||
149 | "react/destructuring-assignment": 0, | ||
150 | "react/jsx-curly-newline": 0, | ||
151 | "react/jsx-filename-extension": 1, | ||
152 | "react/jsx-one-expression-per-line": 0, | ||
153 | "react/jsx-no-bind": 1, | ||
154 | "react/jsx-props-no-spreading": 0, | ||
155 | "react/prefer-stateless-function": 1, | ||
156 | "react/prop-types": 0, | ||
157 | "react/static-property-placement": 0, | ||
158 | "react/state-in-constructor": 1, | ||
159 | "react/sort-comp": 0, | ||
160 | // eslint-plugin-jsx-a11y | ||
161 | "jsx-a11y/click-events-have-key-events": 1, | ||
162 | "jsx-a11y/no-static-element-interactions": 1, | ||
163 | "jsx-a11y/no-noninteractive-element-interactions": 1 | ||
164 | } | ||
165 | } | ||
diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..057631708 --- /dev/null +++ b/.eslintrc.js | |||
@@ -0,0 +1,186 @@ | |||
1 | module.exports = { | ||
2 | root: true, | ||
3 | parser: '@babel/eslint-parser', | ||
4 | parserOptions: { | ||
5 | ecmaFeatures: { | ||
6 | jsx: true, | ||
7 | }, | ||
8 | ecmaVersion: 2018, | ||
9 | sourceType: 'module', | ||
10 | project: './tsconfig.json', | ||
11 | }, | ||
12 | extends: ['eslint-config-airbnb', 'plugin:unicorn/recommended'], | ||
13 | plugins: ['jest'], | ||
14 | settings: { | ||
15 | react: { | ||
16 | pragma: 'React', // Pragma to use, default to "React" | ||
17 | version: 'detect', // React version. "detect" automatically picks the version you have installed. | ||
18 | }, | ||
19 | }, | ||
20 | globals: { | ||
21 | window: true, | ||
22 | document: true, | ||
23 | FormData: true, | ||
24 | localStorage: true, | ||
25 | navigator: true, | ||
26 | Element: true, | ||
27 | use: true, | ||
28 | FileReader: true, | ||
29 | }, | ||
30 | env: { | ||
31 | browser: true, | ||
32 | es6: true, | ||
33 | node: true, | ||
34 | jest: true, | ||
35 | }, | ||
36 | overrides: [ | ||
37 | { | ||
38 | files: ['**/*.ts', '**/*.tsx'], | ||
39 | extends: ['airbnb-typescript', 'plugin:unicorn/recommended'], | ||
40 | parser: '@typescript-eslint/parser', | ||
41 | plugins: ['@typescript-eslint'], | ||
42 | rules: { | ||
43 | // eslint | ||
44 | 'arrow-parens': 0, | ||
45 | 'array-callback-return': 1, | ||
46 | 'class-methods-use-this': 0, | ||
47 | 'consistent-return': 0, | ||
48 | 'function-paren-newline': 0, | ||
49 | 'implicit-arrow-linebreak': 0, | ||
50 | 'linebreak-style': 0, | ||
51 | 'max-len': 0, | ||
52 | 'no-confusing-arrow': 0, | ||
53 | 'no-console': 0, | ||
54 | 'no-param-reassign': 0, | ||
55 | 'no-restricted-syntax': 0, | ||
56 | 'no-return-assign': 1, | ||
57 | 'no-underscore-dangle': 0, | ||
58 | 'no-use-before-define': 0, | ||
59 | 'prefer-destructuring': 1, | ||
60 | 'object-curly-newline': 0, | ||
61 | 'operator-linebreak': 0, | ||
62 | // @typescript-eslint | ||
63 | '@typescript-eslint/indent': 0, | ||
64 | '@typescript-eslint/no-shadow': 0, | ||
65 | '@typescript-eslint/no-unused-expressions': 0, | ||
66 | // eslint-plugin-import | ||
67 | 'import/extensions': 0, | ||
68 | 'import/no-cycle': 1, | ||
69 | 'import/no-extraneous-dependencies': 0, | ||
70 | 'import/no-unresolved': 0, | ||
71 | 'import/prefer-default-export': 0, | ||
72 | // eslint-plugin-react | ||
73 | 'react/destructuring-assignment': 0, | ||
74 | 'react/button-has-type': 0, | ||
75 | 'react/forbid-prop-types': 0, | ||
76 | 'react/jsx-curly-newline': 0, | ||
77 | 'react/jsx-no-bind': 0, | ||
78 | 'react/jsx-no-target-blank': 0, | ||
79 | 'react/jsx-props-no-spreading': 0, | ||
80 | 'react/no-deprecated': 1, | ||
81 | 'react/no-array-index-key': 0, | ||
82 | 'react/prefer-stateless-function': 0, | ||
83 | 'react/sort-comp': 0, | ||
84 | 'react/state-in-constructor': 0, | ||
85 | 'react/static-property-placement': 0, | ||
86 | // eslint-plugin-jsx-a11y | ||
87 | 'jsx-a11y/click-events-have-key-events': 1, | ||
88 | 'jsx-a11y/mouse-events-have-key-events': 1, | ||
89 | 'jsx-a11y/label-has-for': [ | ||
90 | 2, | ||
91 | { | ||
92 | components: ['Label'], | ||
93 | required: { | ||
94 | every: ['id'], | ||
95 | }, | ||
96 | allowChildren: false, | ||
97 | }, | ||
98 | ], | ||
99 | 'jsx-a11y/no-static-element-interactions': 0, | ||
100 | 'jsx-a11y/no-noninteractive-element-interactions': 1, | ||
101 | // eslint-plugin-unicorn | ||
102 | 'unicorn/filename-case': 0, | ||
103 | 'unicorn/no-null': 0, | ||
104 | 'unicorn/no-useless-undefined': 0, | ||
105 | 'unicorn/prefer-module': 0, | ||
106 | 'unicorn/prevent-abbreviations': 0, | ||
107 | 'unicorn/prefer-node-protocol': 0, | ||
108 | 'unicorn/import-style': [ | ||
109 | 2, | ||
110 | { | ||
111 | styles: { | ||
112 | path: { | ||
113 | named: true, | ||
114 | }, | ||
115 | }, | ||
116 | }, | ||
117 | ], | ||
118 | 'unicorn/consistent-destructuring': 0, | ||
119 | }, | ||
120 | }, | ||
121 | ], | ||
122 | rules: { | ||
123 | // eslint | ||
124 | 'arrow-parens': 0, | ||
125 | 'class-methods-use-this': 0, | ||
126 | 'consistent-return': 1, | ||
127 | 'implicit-arrow-linebreak': 0, | ||
128 | indent: 0, | ||
129 | 'function-paren-newline': 0, | ||
130 | 'linebreak-style': 0, | ||
131 | 'max-len': 0, | ||
132 | 'no-await-in-loop': 1, | ||
133 | 'no-console': [ | ||
134 | 1, | ||
135 | { | ||
136 | allow: ['warn', 'error'], | ||
137 | }, | ||
138 | ], | ||
139 | 'no-param-reassign': 1, | ||
140 | 'no-restricted-syntax': 0, | ||
141 | 'no-underscore-dangle': 0, | ||
142 | 'operator-linebreak': 0, | ||
143 | 'prefer-destructuring': 1, | ||
144 | 'object-curly-newline': 0, | ||
145 | // eslint-plugin-import | ||
146 | 'import/extensions': 0, | ||
147 | 'import/prefer-default-export': 0, | ||
148 | 'import/no-extraneous-dependencies': 0, // various false positives, re-enable at some point | ||
149 | 'import/no-unresolved': 0, | ||
150 | // eslint-plugin-react | ||
151 | 'react/forbid-prop-types': 1, | ||
152 | 'react/destructuring-assignment': 0, | ||
153 | 'react/jsx-curly-newline': 0, | ||
154 | 'react/jsx-filename-extension': 1, | ||
155 | 'react/jsx-one-expression-per-line': 0, | ||
156 | 'react/jsx-no-bind': 1, | ||
157 | 'react/jsx-props-no-spreading': 0, | ||
158 | 'react/prefer-stateless-function': 1, | ||
159 | 'react/prop-types': 0, | ||
160 | 'react/static-property-placement': 0, | ||
161 | 'react/state-in-constructor': 1, | ||
162 | 'react/sort-comp': 0, | ||
163 | // eslint-plugin-jsx-a11y | ||
164 | 'jsx-a11y/click-events-have-key-events': 1, | ||
165 | 'jsx-a11y/no-static-element-interactions': 1, | ||
166 | 'jsx-a11y/no-noninteractive-element-interactions': 1, | ||
167 | // eslint-plugin-unicorn | ||
168 | 'unicorn/filename-case': 0, | ||
169 | 'unicorn/no-null': 0, | ||
170 | 'unicorn/no-useless-undefined': 0, | ||
171 | 'unicorn/prefer-module': 0, | ||
172 | 'unicorn/prevent-abbreviations': 0, | ||
173 | 'unicorn/prefer-node-protocol': 0, | ||
174 | 'unicorn/import-style': [ | ||
175 | 2, | ||
176 | { | ||
177 | styles: { | ||
178 | path: { | ||
179 | named: true, | ||
180 | }, | ||
181 | }, | ||
182 | }, | ||
183 | ], | ||
184 | 'unicorn/consistent-destructuring': 0, | ||
185 | }, | ||
186 | }; | ||
diff --git a/package-lock.json b/package-lock.json index a278c76f1..a3076c7d6 100644 --- a/package-lock.json +++ b/package-lock.json | |||
@@ -9851,6 +9851,12 @@ | |||
9851 | "sax": "^1.2.4" | 9851 | "sax": "^1.2.4" |
9852 | } | 9852 | } |
9853 | }, | 9853 | }, |
9854 | "builtin-modules": { | ||
9855 | "version": "3.2.0", | ||
9856 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", | ||
9857 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", | ||
9858 | "dev": true | ||
9859 | }, | ||
9854 | "builtin-status-codes": { | 9860 | "builtin-status-codes": { |
9855 | "version": "3.0.0", | 9861 | "version": "3.0.0", |
9856 | "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", | 9862 | "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", |
@@ -10394,6 +10400,15 @@ | |||
10394 | } | 10400 | } |
10395 | } | 10401 | } |
10396 | }, | 10402 | }, |
10403 | "clean-regexp": { | ||
10404 | "version": "1.0.0", | ||
10405 | "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", | ||
10406 | "integrity": "sha1-jffHquUf02h06PjQW5GAvBGj/tc=", | ||
10407 | "dev": true, | ||
10408 | "requires": { | ||
10409 | "escape-string-regexp": "^1.0.5" | ||
10410 | } | ||
10411 | }, | ||
10397 | "clean-stack": { | 10412 | "clean-stack": { |
10398 | "version": "2.2.0", | 10413 | "version": "2.2.0", |
10399 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", | 10414 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", |
@@ -14424,6 +14439,43 @@ | |||
14424 | "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", | 14439 | "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", |
14425 | "dev": true | 14440 | "dev": true |
14426 | }, | 14441 | }, |
14442 | "eslint-plugin-unicorn": { | ||
14443 | "version": "36.0.0", | ||
14444 | "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-36.0.0.tgz", | ||
14445 | "integrity": "sha512-xxN2vSctGWnDW6aLElm/LKIwcrmk6mdiEcW55Uv5krcrVcIFSWMmEgc/hwpemYfZacKZ5npFERGNz4aThsp1AA==", | ||
14446 | "dev": true, | ||
14447 | "requires": { | ||
14448 | "@babel/helper-validator-identifier": "^7.14.9", | ||
14449 | "ci-info": "^3.2.0", | ||
14450 | "clean-regexp": "^1.0.0", | ||
14451 | "eslint-template-visitor": "^2.3.2", | ||
14452 | "eslint-utils": "^3.0.0", | ||
14453 | "is-builtin-module": "^3.1.0", | ||
14454 | "lodash": "^4.17.21", | ||
14455 | "pluralize": "^8.0.0", | ||
14456 | "read-pkg-up": "^7.0.1", | ||
14457 | "regexp-tree": "^0.1.23", | ||
14458 | "safe-regex": "^2.1.1", | ||
14459 | "semver": "^7.3.5" | ||
14460 | }, | ||
14461 | "dependencies": { | ||
14462 | "pluralize": { | ||
14463 | "version": "8.0.0", | ||
14464 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", | ||
14465 | "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", | ||
14466 | "dev": true | ||
14467 | }, | ||
14468 | "safe-regex": { | ||
14469 | "version": "2.1.1", | ||
14470 | "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", | ||
14471 | "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", | ||
14472 | "dev": true, | ||
14473 | "requires": { | ||
14474 | "regexp-tree": "~0.1.1" | ||
14475 | } | ||
14476 | } | ||
14477 | } | ||
14478 | }, | ||
14427 | "eslint-scope": { | 14479 | "eslint-scope": { |
14428 | "version": "5.1.1", | 14480 | "version": "5.1.1", |
14429 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", | 14481 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", |
@@ -14434,6 +14486,19 @@ | |||
14434 | "estraverse": "^4.1.1" | 14486 | "estraverse": "^4.1.1" |
14435 | } | 14487 | } |
14436 | }, | 14488 | }, |
14489 | "eslint-template-visitor": { | ||
14490 | "version": "2.3.2", | ||
14491 | "resolved": "https://registry.npmjs.org/eslint-template-visitor/-/eslint-template-visitor-2.3.2.tgz", | ||
14492 | "integrity": "sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA==", | ||
14493 | "dev": true, | ||
14494 | "requires": { | ||
14495 | "@babel/core": "^7.12.16", | ||
14496 | "@babel/eslint-parser": "^7.12.16", | ||
14497 | "eslint-visitor-keys": "^2.0.0", | ||
14498 | "esquery": "^1.3.1", | ||
14499 | "multimap": "^1.1.0" | ||
14500 | } | ||
14501 | }, | ||
14437 | "eslint-utils": { | 14502 | "eslint-utils": { |
14438 | "version": "3.0.0", | 14503 | "version": "3.0.0", |
14439 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", | 14504 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", |
@@ -17902,6 +17967,15 @@ | |||
17902 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", | 17967 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", |
17903 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" | 17968 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" |
17904 | }, | 17969 | }, |
17970 | "is-builtin-module": { | ||
17971 | "version": "3.1.0", | ||
17972 | "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz", | ||
17973 | "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==", | ||
17974 | "dev": true, | ||
17975 | "requires": { | ||
17976 | "builtin-modules": "^3.0.0" | ||
17977 | } | ||
17978 | }, | ||
17905 | "is-callable": { | 17979 | "is-callable": { |
17906 | "version": "1.2.4", | 17980 | "version": "1.2.4", |
17907 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", | 17981 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", |
@@ -22324,6 +22398,12 @@ | |||
22324 | "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", | 22398 | "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", |
22325 | "dev": true | 22399 | "dev": true |
22326 | }, | 22400 | }, |
22401 | "multimap": { | ||
22402 | "version": "1.1.0", | ||
22403 | "resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz", | ||
22404 | "integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==", | ||
22405 | "dev": true | ||
22406 | }, | ||
22327 | "multimatch": { | 22407 | "multimatch": { |
22328 | "version": "5.0.0", | 22408 | "version": "5.0.0", |
22329 | "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", | 22409 | "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", |
@@ -25934,6 +26014,12 @@ | |||
25934 | } | 26014 | } |
25935 | } | 26015 | } |
25936 | }, | 26016 | }, |
26017 | "regexp-tree": { | ||
26018 | "version": "0.1.23", | ||
26019 | "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.23.tgz", | ||
26020 | "integrity": "sha512-+7HWfb4Bvu8Rs2eQTUIpX9I/PlQkYOuTNbRpKLJlQpSgwSkzFYh+pUj0gtvglnOZLKB6YgnIgRuJ2/IlpL48qw==", | ||
26021 | "dev": true | ||
26022 | }, | ||
25937 | "regexp.prototype.flags": { | 26023 | "regexp.prototype.flags": { |
25938 | "version": "1.3.1", | 26024 | "version": "1.3.1", |
25939 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", | 26025 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", |
diff --git a/package.json b/package.json index 9add26ec6..d0675ba6b 100644 --- a/package.json +++ b/package.json | |||
@@ -25,7 +25,8 @@ | |||
25 | "dev": "cross-env NODE_ENV=development gulp dev", | 25 | "dev": "cross-env NODE_ENV=development gulp dev", |
26 | "test": "jest", | 26 | "test": "jest", |
27 | "test:watch": "jest --watch", | 27 | "test:watch": "jest --watch", |
28 | "lint": "eslint \"{src,scripts,packages,uidev}/**/*.{js,jsx,ts,tsx}\" --quiet --fix", | 28 | "lint": "eslint \"{src,scripts,packages,uidev}/**/*.{js,jsx,ts,tsx}\" --quiet", |
29 | "lint:fix": "npm run lint -- --fix", | ||
29 | "extract": "formatjs extract 'src/**/*.{js,ts}' --out-file temp.json --flatten --id-interpolation-pattern '[sha512:contenthash:base64:6]' --preserve-whitespace", | 30 | "extract": "formatjs extract 'src/**/*.{js,ts}' --out-file temp.json --flatten --id-interpolation-pattern '[sha512:contenthash:base64:6]' --preserve-whitespace", |
30 | "compile": "formatjs compile 'temp.json' --out-file src/i18n/locales/en-US.json", | 31 | "compile": "formatjs compile 'temp.json' --out-file src/i18n/locales/en-US.json", |
31 | "manage-translations": "npm run extract && npm run compile && rimraf temp.json", | 32 | "manage-translations": "npm run extract && npm run compile && rimraf temp.json", |
@@ -187,6 +188,7 @@ | |||
187 | "eslint-plugin-prettier": "3.4.0", | 188 | "eslint-plugin-prettier": "3.4.0", |
188 | "eslint-plugin-react": "7.25.1", | 189 | "eslint-plugin-react": "7.25.1", |
189 | "eslint-plugin-react-hooks": "4.2.0", | 190 | "eslint-plugin-react-hooks": "4.2.0", |
191 | "eslint-plugin-unicorn": "36.0.0", | ||
190 | "expect.js": "0.3.1", | 192 | "expect.js": "0.3.1", |
191 | "gulp": "4.0.2", | 193 | "gulp": "4.0.2", |
192 | "gulp-babel": "8.0.0", | 194 | "gulp-babel": "8.0.0", |
diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx index 48fb61635..c9ae47d55 100644 --- a/packages/forms/src/button/index.tsx +++ b/packages/forms/src/button/index.tsx | |||
@@ -227,44 +227,38 @@ class ButtonComponent extends Component<IProps> { | |||
227 | </> | 227 | </> |
228 | ); | 228 | ); |
229 | 229 | ||
230 | let wrapperComponent: JSX.Element; | 230 | const wrapperComponent = !href ? ( |
231 | 231 | <button | |
232 | if (!href) { | 232 | id={id} |
233 | wrapperComponent = ( | 233 | type={type} |
234 | <button | 234 | onClick={onClick} |
235 | id={id} | 235 | className={classnames({ |
236 | type={type} | 236 | [`${classes.button}`]: true, |
237 | onClick={onClick} | 237 | [`${classes[buttonType as ButtonType]}`]: true, |
238 | className={classnames({ | 238 | [`${classes.disabled}`]: disabled, |
239 | [`${classes.button}`]: true, | 239 | [`${className}`]: className, |
240 | [`${classes[buttonType as ButtonType]}`]: true, | 240 | })} |
241 | [`${classes.disabled}`]: disabled, | 241 | disabled={disabled} |
242 | [`${className}`]: className, | 242 | data-type="franz-button" |
243 | })} | 243 | > |
244 | disabled={disabled} | 244 | {content} |
245 | data-type="franz-button" | 245 | </button> |
246 | > | 246 | ) : ( |
247 | {content} | 247 | <a |
248 | </button> | 248 | href={href} |
249 | ); | 249 | target={target} |
250 | } else { | 250 | onClick={onClick} |
251 | wrapperComponent = ( | 251 | className={classnames({ |
252 | <a | 252 | [`${classes.button}`]: true, |
253 | href={href} | 253 | [`${classes[buttonType as ButtonType]}`]: true, |
254 | target={target} | 254 | [`${className}`]: className, |
255 | onClick={onClick} | 255 | })} |
256 | className={classnames({ | 256 | rel={target === '_blank' ? 'noopener' : ''} |
257 | [`${classes.button}`]: true, | 257 | data-type="franz-button" |
258 | [`${classes[buttonType as ButtonType]}`]: true, | 258 | > |
259 | [`${className}`]: className, | 259 | {content} |
260 | })} | 260 | </a> |
261 | rel={target === '_blank' ? 'noopener' : ''} | 261 | ); |
262 | data-type="franz-button" | ||
263 | > | ||
264 | {content} | ||
265 | </a> | ||
266 | ); | ||
267 | } | ||
268 | 262 | ||
269 | return wrapperComponent; | 263 | return wrapperComponent; |
270 | } | 264 | } |
diff --git a/packages/forms/src/input/scorePassword.ts b/packages/forms/src/input/scorePassword.ts index bc30de4b8..59502e2b0 100644 --- a/packages/forms/src/input/scorePassword.ts +++ b/packages/forms/src/input/scorePassword.ts | |||
@@ -18,9 +18,9 @@ export function scorePasswordFunc(password: string): number { | |||
18 | 18 | ||
19 | // award every unique letter until 5 repetitions | 19 | // award every unique letter until 5 repetitions |
20 | const letters: ILetters = {}; | 20 | const letters: ILetters = {}; |
21 | for (let i = 0; i < password.length; i += 1) { | 21 | for (const element of password) { |
22 | letters[password[i]] = (letters[password[i]] || 0) + 1; | 22 | letters[element] = (letters[element] || 0) + 1; |
23 | score += 5.0 / letters[password[i]]; | 23 | score += 5 / letters[element]; |
24 | } | 24 | } |
25 | 25 | ||
26 | // bonus points for mixing it up | 26 | // bonus points for mixing it up |
@@ -32,9 +32,9 @@ export function scorePasswordFunc(password: string): number { | |||
32 | }; | 32 | }; |
33 | 33 | ||
34 | let variationCount = 0; | 34 | let variationCount = 0; |
35 | Object.keys(variations).forEach(key => { | 35 | for (const key of Object.keys(variations)) { |
36 | variationCount += variations[key] === true ? 1 : 0; | 36 | variationCount += variations[key] === true ? 1 : 0; |
37 | }); | 37 | } |
38 | 38 | ||
39 | score += (variationCount - 1) * 10; | 39 | score += (variationCount - 1) * 10; |
40 | 40 | ||
diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx index d7479f63e..7806baa2a 100644 --- a/packages/forms/src/select/index.tsx +++ b/packages/forms/src/select/index.tsx | |||
@@ -187,10 +187,8 @@ class SelectComponent extends Component<IProps> { | |||
187 | componentDidUpdate() { | 187 | componentDidUpdate() { |
188 | const { open } = this.state; | 188 | const { open } = this.state; |
189 | 189 | ||
190 | if (this.searchInputRef && this.searchInputRef.current) { | 190 | if (this.searchInputRef && this.searchInputRef.current && open) { |
191 | if (open) { | 191 | this.searchInputRef.current.focus(); |
192 | this.searchInputRef.current.focus(); | ||
193 | } | ||
194 | } | 192 | } |
195 | } | 193 | } |
196 | 194 | ||
@@ -228,6 +226,7 @@ class SelectComponent extends Component<IProps> { | |||
228 | } | 226 | } |
229 | 227 | ||
230 | componentWillUnmount() { | 228 | componentWillUnmount() { |
229 | // eslint-disable-next-line unicorn/no-invalid-remove-event-listener | ||
231 | window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); | 230 | window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); |
232 | } | 231 | } |
233 | 232 | ||
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index 87a68cb5d..aa132c743 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts | |||
@@ -9,7 +9,7 @@ export default (brandPrimary: string) => { | |||
9 | let brandPrimaryColor = color(legacyStyles.themeBrandPrimary); | 9 | let brandPrimaryColor = color(legacyStyles.themeBrandPrimary); |
10 | try { | 10 | try { |
11 | brandPrimaryColor = color(defaultStyles.brandPrimary); | 11 | brandPrimaryColor = color(defaultStyles.brandPrimary); |
12 | } catch (e) { | 12 | } catch { |
13 | // Ignore invalid color and fall back to default. | 13 | // Ignore invalid color and fall back to default. |
14 | } | 14 | } |
15 | 15 | ||
diff --git a/scripts/build-theme-info.js b/scripts/build-theme-info.js index 4058be942..8aee96ab7 100644 --- a/scripts/build-theme-info.js +++ b/scripts/build-theme-info.js | |||
@@ -23,7 +23,7 @@ async function getRulesFromCssFile(file) { | |||
23 | const cssSrc = (await fs.readFile(file)).toString(); | 23 | const cssSrc = (await fs.readFile(file)).toString(); |
24 | const cssTree = css.parse(cssSrc); | 24 | const cssTree = css.parse(cssSrc); |
25 | 25 | ||
26 | return cssTree.stylesheet.rules; | 26 | return cssTree.stylesheet?.rules; |
27 | } | 27 | } |
28 | 28 | ||
29 | /** | 29 | /** |
@@ -49,25 +49,30 @@ async function getRulesFromCssFile(file) { | |||
49 | function getSelectorsDeclaringValues(rules, values) { | 49 | function getSelectorsDeclaringValues(rules, values) { |
50 | const output = {}; | 50 | const output = {}; |
51 | 51 | ||
52 | rules.forEach((rule) => { | 52 | for (const rule of rules) { |
53 | if (rule.declarations) { | 53 | if (rule.declarations) { |
54 | rule.declarations.forEach((declaration) => { | 54 | for (const declaration of rule.declarations) { |
55 | if (declaration.type === 'declaration' | 55 | if ( |
56 | && values.includes(declaration.value.toLowerCase())) { | 56 | declaration.type === 'declaration' && |
57 | values.includes(declaration.value.toLowerCase()) | ||
58 | ) { | ||
57 | if (!output[declaration.property]) { | 59 | if (!output[declaration.property]) { |
58 | output[declaration.property] = []; | 60 | output[declaration.property] = []; |
59 | } | 61 | } |
60 | output[declaration.property] = output[declaration.property].concat(rule.selectors); | 62 | // eslint-disable-next-line unicorn/prefer-spread |
63 | output[declaration.property] = output[declaration.property].concat( | ||
64 | rule.selectors, | ||
65 | ); | ||
61 | } | 66 | } |
62 | }); | 67 | } |
63 | } | 68 | } |
64 | }); | 69 | } |
65 | 70 | ||
66 | return output; | 71 | return output; |
67 | } | 72 | } |
68 | 73 | ||
69 | async function generateThemeInfo() { | 74 | async function generateThemeInfo() { |
70 | if (!await fs.pathExists(cssFile)) { | 75 | if (!(await fs.pathExists(cssFile))) { |
71 | console.log('Please make sure to build the project first.'); | 76 | console.log('Please make sure to build the project first.'); |
72 | return; | 77 | return; |
73 | } | 78 | } |
@@ -75,23 +80,24 @@ async function generateThemeInfo() { | |||
75 | // Read and parse css bundle | 80 | // Read and parse css bundle |
76 | const rules = await getRulesFromCssFile(cssFile); | 81 | const rules = await getRulesFromCssFile(cssFile); |
77 | 82 | ||
78 | console.log(`Found ${rules.length} rules`); | 83 | console.log(`Found ${rules?.length} rules`); |
79 | 84 | ||
80 | // Get rules specifying the brand colors | 85 | // Get rules specifying the brand colors |
81 | const brandRules = getSelectorsDeclaringValues(rules, accentColors); | 86 | const brandRules = getSelectorsDeclaringValues(rules, accentColors); |
82 | 87 | ||
83 | console.log(`Found ${Object.keys(brandRules).join(', ')} properties that set color to brand color`); | 88 | console.log( |
89 | `Found ${Object.keys(brandRules).join( | ||
90 | ', ', | ||
91 | )} properties that set color to brand color`, | ||
92 | ); | ||
84 | 93 | ||
85 | // Join array of declarations into a single string | 94 | // Join array of declarations into a single string |
86 | Object.keys(brandRules).forEach((rule) => { | 95 | for (const rule of Object.keys(brandRules)) { |
87 | brandRules[rule] = brandRules[rule].join(', '); | 96 | brandRules[rule] = brandRules[rule].join(', '); |
88 | }); | 97 | } |
89 | 98 | ||
90 | // Write object with theme info to file | 99 | // Write object with theme info to file |
91 | fs.writeFile( | 100 | fs.writeFile(outputFile, JSON.stringify(brandRules)); |
92 | outputFile, | ||
93 | JSON.stringify(brandRules), | ||
94 | ); | ||
95 | } | 101 | } |
96 | 102 | ||
97 | generateThemeInfo(); | 103 | generateThemeInfo(); |
diff --git a/scripts/link-readme.js b/scripts/link-readme.js index 1e47cddf8..2ab38912c 100644 --- a/scripts/link-readme.js +++ b/scripts/link-readme.js | |||
@@ -22,7 +22,7 @@ let replacements = 0; | |||
22 | // Regex matches strings that don't begin with a "[", i.e. are not already linked | 22 | // Regex matches strings that don't begin with a "[", i.e. are not already linked |
23 | // followed by a "franz#" and digits to indicate | 23 | // followed by a "franz#" and digits to indicate |
24 | // a GitHub issue, and not ending with a "]" | 24 | // a GitHub issue, and not ending with a "]" |
25 | readme = readme.replace(/(?<!\[)franz#\d{1,}(?![\]\d])/gi, (match) => { | 25 | readme = readme.replace(/(?<!\[)franz#\d+(?![\d\]])/gi, match => { |
26 | const issueNr = match.replace('franz#', ''); | 26 | const issueNr = match.replace('franz#', ''); |
27 | replacements += 1; | 27 | replacements += 1; |
28 | return `[franz#${issueNr}](https://github.com/meetfranz/franz/issues/${issueNr})`; | 28 | return `[franz#${issueNr}](https://github.com/meetfranz/franz/issues/${issueNr})`; |
@@ -31,7 +31,7 @@ readme = readme.replace(/(?<!\[)franz#\d{1,}(?![\]\d])/gi, (match) => { | |||
31 | // Replace external issues | 31 | // Replace external issues |
32 | // Regex matches strings that don't begin with a "[", followed a repo name in the format "user/repo" | 32 | // Regex matches strings that don't begin with a "[", followed a repo name in the format "user/repo" |
33 | // followed by a "#" and digits to indicate a GitHub issue, and not ending with a "]" | 33 | // followed by a "#" and digits to indicate a GitHub issue, and not ending with a "]" |
34 | readme = readme.replace(/(?<!\[)\w+\/\w+#\d{1,}(?![\]\d])/gi, (match) => { | 34 | readme = readme.replace(/(?<!\[)\w+\/\w+#\d+(?![\d\]])/gi, match => { |
35 | const issueNr = match.replace(/\D/g, ''); | 35 | const issueNr = match.replace(/\D/g, ''); |
36 | const repo = match.replace(/#\d+/g, ''); | 36 | const repo = match.replace(/#\d+/g, ''); |
37 | replacements += 1; | 37 | replacements += 1; |
@@ -42,7 +42,7 @@ readme = readme.replace(/(?<!\[)\w+\/\w+#\d{1,}(?![\]\d])/gi, (match) => { | |||
42 | // Regex matches strings that don't begin with a "[", i.e. are not already linked and | 42 | // Regex matches strings that don't begin with a "[", i.e. are not already linked and |
43 | // don't begin with "franz", i.e. are not Franz issues, followed by a "#" and digits to indicate | 43 | // don't begin with "franz", i.e. are not Franz issues, followed by a "#" and digits to indicate |
44 | // a GitHub issue, and not ending with a "]" | 44 | // a GitHub issue, and not ending with a "]" |
45 | readme = readme.replace(/(?<!\[|franz)#\d{1,}(?![\]\d])/gi, (match) => { | 45 | readme = readme.replace(/(?<!\[|franz)#\d+(?![\d\]])/gi, match => { |
46 | const issueNr = match.replace('#', ''); | 46 | const issueNr = match.replace('#', ''); |
47 | replacements += 1; | 47 | replacements += 1; |
48 | return `[#${issueNr}](https://github.com/getferdi/ferdi/issues/${issueNr})`; | 48 | return `[#${issueNr}](https://github.com/getferdi/ferdi/issues/${issueNr})`; |
@@ -51,7 +51,7 @@ readme = readme.replace(/(?<!\[|franz)#\d{1,}(?![\]\d])/gi, (match) => { | |||
51 | // Link GitHub users | 51 | // Link GitHub users |
52 | // Regex matches strings that don't begin with a "[", i.e. are not already linked | 52 | // Regex matches strings that don't begin with a "[", i.e. are not already linked |
53 | // followed by a "@" and at least one word character and not ending with a "]" | 53 | // followed by a "@" and at least one word character and not ending with a "]" |
54 | readme = readme.replace(/(?<!\[)@\w+(?!\])/gi, (match) => { | 54 | readme = readme.replace(/(?<!\[)@\w+(?!])/gi, match => { |
55 | const username = match.replace('@', ''); | 55 | const username = match.replace('@', ''); |
56 | replacements += 1; | 56 | replacements += 1; |
57 | return `[@${username}](https://github.com/${username})`; | 57 | return `[@${username}](https://github.com/${username})`; |
diff --git a/src/actions/lib/actions.ts b/src/actions/lib/actions.ts index ed42eabc0..412a0d895 100644 --- a/src/actions/lib/actions.ts +++ b/src/actions/lib/actions.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | export const createActionsFromDefinitions = (actionDefinitions, validate) => { | 1 | export const createActionsFromDefinitions = (actionDefinitions, validate) => { |
2 | const actions = {}; | 2 | const actions = {}; |
3 | // eslint-disable-next-line unicorn/no-array-for-each | ||
3 | Object.keys(actionDefinitions).forEach(actionName => { | 4 | Object.keys(actionDefinitions).forEach(actionName => { |
4 | const action = (params = {}) => { | 5 | const action = (params = {}) => { |
5 | const schema = actionDefinitions[actionName]; | 6 | const schema = actionDefinitions[actionName]; |
@@ -14,6 +15,7 @@ export const createActionsFromDefinitions = (actionDefinitions, validate) => { | |||
14 | listeners.splice(listeners.indexOf(listener), 1); | 15 | listeners.splice(listeners.indexOf(listener), 1); |
15 | }; | 16 | }; |
16 | action.notify = params => | 17 | action.notify = params => |
18 | // eslint-disable-next-line unicorn/no-array-for-each | ||
17 | action.listeners.forEach(listener => listener(params)); | 19 | action.listeners.forEach(listener => listener(params)); |
18 | }); | 20 | }); |
19 | return actions; | 21 | return actions; |
@@ -21,6 +23,7 @@ export const createActionsFromDefinitions = (actionDefinitions, validate) => { | |||
21 | 23 | ||
22 | export default (definitions, validate) => { | 24 | export default (definitions, validate) => { |
23 | const newActions = {}; | 25 | const newActions = {}; |
26 | // eslint-disable-next-line unicorn/no-array-for-each | ||
24 | Object.keys(definitions).forEach(scopeName => { | 27 | Object.keys(definitions).forEach(scopeName => { |
25 | newActions[scopeName] = createActionsFromDefinitions( | 28 | newActions[scopeName] = createActionsFromDefinitions( |
26 | definitions[scopeName], | 29 | definitions[scopeName], |
diff --git a/src/api/apiBase.ts b/src/api/apiBase.ts index dc10fad91..510ccb619 100644 --- a/src/api/apiBase.ts +++ b/src/api/apiBase.ts | |||
@@ -12,8 +12,6 @@ import { | |||
12 | 12 | ||
13 | // Note: This cannot be used from the internal-server since we are not running within the context of a browser window | 13 | // Note: This cannot be used from the internal-server since we are not running within the context of a browser window |
14 | const apiBase = (withVersion = true) => { | 14 | const apiBase = (withVersion = true) => { |
15 | let url: string; | ||
16 | |||
17 | if ( | 15 | if ( |
18 | !(window as any).ferdi || | 16 | !(window as any).ferdi || |
19 | !(window as any).ferdi.stores.settings || | 17 | !(window as any).ferdi.stores.settings || |
@@ -23,15 +21,12 @@ const apiBase = (withVersion = true) => { | |||
23 | // Stores have not yet been loaded - return SERVER_NOT_LOADED to force a retry when stores are loaded | 21 | // Stores have not yet been loaded - return SERVER_NOT_LOADED to force a retry when stores are loaded |
24 | return SERVER_NOT_LOADED; | 22 | return SERVER_NOT_LOADED; |
25 | } | 23 | } |
26 | if ((window as any).ferdi.stores.settings.all.app.server === LOCAL_SERVER) { | 24 | const url = |
27 | // Use URL for local server | 25 | (window as any).ferdi.stores.settings.all.app.server === LOCAL_SERVER |
28 | url = `http://${LOCAL_HOSTNAME}:${ | 26 | ? `http://${LOCAL_HOSTNAME}:${ |
29 | (window as any).ferdi.stores.requests.localServerPort | 27 | (window as any).ferdi.stores.requests.localServerPort |
30 | }`; | 28 | }` |
31 | } else { | 29 | : (window as any).ferdi.stores.settings.all.app.server; |
32 | // Load URL from store | ||
33 | url = (window as any).ferdi.stores.settings.all.app.server; | ||
34 | } | ||
35 | 30 | ||
36 | return withVersion ? `${url}/${API_VERSION}` : url; | 31 | return withVersion ? `${url}/${API_VERSION}` : url; |
37 | }; | 32 | }; |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index b5042525a..fb0495b19 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -1,6 +1,16 @@ | |||
1 | /* eslint-disable global-require */ | ||
1 | import { join } from 'path'; | 2 | import { join } from 'path'; |
2 | import tar from 'tar'; | 3 | import tar from 'tar'; |
3 | import { readdirSync, statSync, writeFileSync, copySync, ensureDirSync, pathExistsSync, readJsonSync, removeSync } from 'fs-extra'; | 4 | import { |
5 | readdirSync, | ||
6 | statSync, | ||
7 | writeFileSync, | ||
8 | copySync, | ||
9 | ensureDirSync, | ||
10 | pathExistsSync, | ||
11 | readJsonSync, | ||
12 | removeSync, | ||
13 | } from 'fs-extra'; | ||
4 | import { require as remoteRequire } from '@electron/remote'; | 14 | import { require as remoteRequire } from '@electron/remote'; |
5 | 15 | ||
6 | import ServiceModel from '../../models/Service'; | 16 | import ServiceModel from '../../models/Service'; |
@@ -12,7 +22,14 @@ import UserModel from '../../models/User'; | |||
12 | import { sleep } from '../../helpers/async-helpers'; | 22 | import { sleep } from '../../helpers/async-helpers'; |
13 | 23 | ||
14 | import { SERVER_NOT_LOADED } from '../../config'; | 24 | import { SERVER_NOT_LOADED } from '../../config'; |
15 | import { osArch, osPlatform, asarRecipesPath, userDataRecipesPath, userDataPath, ferdiVersion } from '../../environment'; | 25 | import { |
26 | osArch, | ||
27 | osPlatform, | ||
28 | asarRecipesPath, | ||
29 | userDataRecipesPath, | ||
30 | userDataPath, | ||
31 | ferdiVersion, | ||
32 | } from '../../environment'; | ||
16 | import apiBase from '../apiBase'; | 33 | import apiBase from '../apiBase'; |
17 | import { prepareAuthRequest, sendAuthRequest } from '../utils/auth'; | 34 | import { prepareAuthRequest, sendAuthRequest } from '../utils/auth'; |
18 | 35 | ||
@@ -310,22 +327,22 @@ export default class ServerApi { | |||
310 | // Recipes | 327 | // Recipes |
311 | async getInstalledRecipes() { | 328 | async getInstalledRecipes() { |
312 | const recipesDirectory = getRecipeDirectory(); | 329 | const recipesDirectory = getRecipeDirectory(); |
313 | const paths = readdirSync(recipesDirectory) | 330 | const paths = readdirSync(recipesDirectory).filter( |
314 | .filter( | 331 | file => |
315 | file => | 332 | statSync(join(recipesDirectory, file)).isDirectory() && |
316 | statSync(join(recipesDirectory, file)).isDirectory() && | 333 | file !== 'temp' && |
317 | file !== 'temp' && | 334 | file !== 'dev', |
318 | file !== 'dev', | 335 | ); |
319 | ); | ||
320 | 336 | ||
321 | this.recipes = paths | 337 | this.recipes = paths |
322 | .map(id => { | 338 | .map(id => { |
323 | // eslint-disable-next-line | 339 | // eslint-disable-next-line import/no-dynamic-require |
324 | const Recipe = require(id)(RecipeModel); | 340 | const Recipe = require(id)(RecipeModel); |
325 | return new Recipe(loadRecipeConfig(id)); | 341 | return new Recipe(loadRecipeConfig(id)); |
326 | }) | 342 | }) |
327 | .filter(recipe => recipe.id); | 343 | .filter(recipe => recipe.id); |
328 | 344 | ||
345 | // eslint-disable-next-line unicorn/prefer-spread | ||
329 | this.recipes = this.recipes.concat(this._getDevRecipes()); | 346 | this.recipes = this.recipes.concat(this._getDevRecipes()); |
330 | 347 | ||
331 | debug('StubServerApi::getInstalledRecipes resolves', this.recipes); | 348 | debug('StubServerApi::getInstalledRecipes resolves', this.recipes); |
@@ -425,8 +442,8 @@ export default class ServerApi { | |||
425 | removeSync(join(recipesDirectory, recipeId, 'recipe.tar.gz')); | 442 | removeSync(join(recipesDirectory, recipeId, 'recipe.tar.gz')); |
426 | 443 | ||
427 | return id; | 444 | return id; |
428 | } catch (err) { | 445 | } catch (error) { |
429 | console.error(err); | 446 | console.error(error); |
430 | 447 | ||
431 | return false; | 448 | return false; |
432 | } | 449 | } |
@@ -434,7 +451,9 @@ export default class ServerApi { | |||
434 | 451 | ||
435 | // News | 452 | // News |
436 | async getLatestNews() { | 453 | async getLatestNews() { |
437 | const url = `${apiBase(true)}/news?platform=${osPlatform}&arch=${osArch}&version=${ferdiVersion}`; | 454 | const url = `${apiBase( |
455 | true, | ||
456 | )}/news?platform=${osPlatform}&arch=${osArch}&version=${ferdiVersion}`; | ||
438 | const request = await sendAuthRequest(url); | 457 | const request = await sendAuthRequest(url); |
439 | if (!request.ok) throw request; | 458 | if (!request.ok) throw request; |
440 | const data = await request.json(); | 459 | const data = await request.json(); |
@@ -494,7 +513,7 @@ export default class ServerApi { | |||
494 | debug('ServerApi::getLegacyServices resolves', services); | 513 | debug('ServerApi::getLegacyServices resolves', services); |
495 | return services; | 514 | return services; |
496 | } | 515 | } |
497 | } catch (err) { | 516 | } catch { |
498 | console.error('ServerApi::getLegacyServices no config found'); | 517 | console.error('ServerApi::getLegacyServices no config found'); |
499 | } | 518 | } |
500 | 519 | ||
@@ -523,8 +542,8 @@ export default class ServerApi { | |||
523 | } | 542 | } |
524 | 543 | ||
525 | return new ServiceModel(service, recipe); | 544 | return new ServiceModel(service, recipe); |
526 | } catch (e) { | 545 | } catch (error) { |
527 | debug(e); | 546 | debug(error); |
528 | return null; | 547 | return null; |
529 | } | 548 | } |
530 | } | 549 | } |
@@ -559,7 +578,7 @@ export default class ServerApi { | |||
559 | 578 | ||
560 | return recipe; | 579 | return recipe; |
561 | }), | 580 | }), |
562 | ).catch(err => console.error("Can't load recipe", err)); | 581 | ).catch(error => console.error("Can't load recipe", error)); |
563 | } | 582 | } |
564 | 583 | ||
565 | _mapRecipePreviewModel(recipes) { | 584 | _mapRecipePreviewModel(recipes) { |
@@ -567,8 +586,8 @@ export default class ServerApi { | |||
567 | .map(recipe => { | 586 | .map(recipe => { |
568 | try { | 587 | try { |
569 | return new RecipePreviewModel(recipe); | 588 | return new RecipePreviewModel(recipe); |
570 | } catch (e) { | 589 | } catch (error) { |
571 | console.error(e); | 590 | console.error(error); |
572 | return null; | 591 | return null; |
573 | } | 592 | } |
574 | }) | 593 | }) |
@@ -580,8 +599,8 @@ export default class ServerApi { | |||
580 | .map(newsItem => { | 599 | .map(newsItem => { |
581 | try { | 600 | try { |
582 | return new NewsModel(newsItem); | 601 | return new NewsModel(newsItem); |
583 | } catch (e) { | 602 | } catch (error) { |
584 | console.error(e); | 603 | console.error(error); |
585 | return null; | 604 | return null; |
586 | } | 605 | } |
587 | }) | 606 | }) |
@@ -591,22 +610,21 @@ export default class ServerApi { | |||
591 | _getDevRecipes() { | 610 | _getDevRecipes() { |
592 | const recipesDirectory = getDevRecipeDirectory(); | 611 | const recipesDirectory = getDevRecipeDirectory(); |
593 | try { | 612 | try { |
594 | const paths = readdirSync(recipesDirectory) | 613 | const paths = readdirSync(recipesDirectory).filter( |
595 | .filter( | 614 | file => |
596 | file => | 615 | statSync(join(recipesDirectory, file)).isDirectory() && |
597 | statSync(join(recipesDirectory, file)).isDirectory() && | 616 | file !== 'temp', |
598 | file !== 'temp', | 617 | ); |
599 | ); | ||
600 | 618 | ||
601 | const recipes = paths | 619 | const recipes = paths |
602 | .map(id => { | 620 | .map(id => { |
603 | let Recipe; | 621 | let Recipe; |
604 | try { | 622 | try { |
605 | // eslint-disable-next-line | 623 | // eslint-disable-next-line import/no-dynamic-require |
606 | Recipe = require(id)(RecipeModel); | 624 | Recipe = require(id)(RecipeModel); |
607 | return new Recipe(loadRecipeConfig(id)); | 625 | return new Recipe(loadRecipeConfig(id)); |
608 | } catch (err) { | 626 | } catch (error) { |
609 | console.error(err); | 627 | console.error(error); |
610 | } | 628 | } |
611 | 629 | ||
612 | return false; | 630 | return false; |
@@ -624,7 +642,7 @@ export default class ServerApi { | |||
624 | }); | 642 | }); |
625 | 643 | ||
626 | return recipes; | 644 | return recipes; |
627 | } catch (err) { | 645 | } catch { |
628 | debug('Could not load dev recipes'); | 646 | debug('Could not load dev recipes'); |
629 | return false; | 647 | return false; |
630 | } | 648 | } |
diff --git a/src/api/utils/auth.js b/src/api/utils/auth.js index e493b2962..527c68840 100644 --- a/src/api/utils/auth.js +++ b/src/api/utils/auth.js | |||
@@ -1,7 +1,11 @@ | |||
1 | import localStorage from 'mobx-localstorage'; | 1 | import localStorage from 'mobx-localstorage'; |
2 | import { ferdiLocale, ferdiVersion } from '../../environment'; | 2 | import { ferdiLocale, ferdiVersion } from '../../environment'; |
3 | 3 | ||
4 | export const prepareAuthRequest = (options = { method: 'GET' }, auth = true) => { | 4 | export const prepareAuthRequest = ( |
5 | // eslint-disable-next-line unicorn/no-object-as-default-parameter | ||
6 | options = { method: 'GET' }, | ||
7 | auth = true, | ||
8 | ) => { | ||
5 | const request = Object.assign(options, { | 9 | const request = Object.assign(options, { |
6 | mode: 'cors', | 10 | mode: 'cors', |
7 | headers: { | 11 | headers: { |
@@ -16,12 +20,13 @@ export const prepareAuthRequest = (options = { method: 'GET' }, auth = true) => | |||
16 | }); | 20 | }); |
17 | 21 | ||
18 | if (auth) { | 22 | if (auth) { |
19 | request.headers.Authorization = `Bearer ${localStorage.getItem('authToken')}`; | 23 | request.headers.Authorization = `Bearer ${localStorage.getItem( |
24 | 'authToken', | ||
25 | )}`; | ||
20 | } | 26 | } |
21 | 27 | ||
22 | return request; | 28 | return request; |
23 | }; | 29 | }; |
24 | 30 | ||
25 | export const sendAuthRequest = (url, options, auth) => ( | 31 | export const sendAuthRequest = (url, options, auth) => |
26 | window.fetch(url, prepareAuthRequest(options, auth)) | 32 | window.fetch(url, prepareAuthRequest(options, auth)); |
27 | ); | ||
diff --git a/src/app.js b/src/app.js index e0d2dbc5a..8a1f99320 100644 --- a/src/app.js +++ b/src/app.js | |||
@@ -44,7 +44,7 @@ window.addEventListener('load', () => { | |||
44 | </I18N> | 44 | </I18N> |
45 | </Provider> | 45 | </Provider> |
46 | ); | 46 | ); |
47 | render(preparedApp, document.getElementById('root')); | 47 | render(preparedApp, document.querySelector('#root')); |
48 | }, | 48 | }, |
49 | }; | 49 | }; |
50 | window.ferdi.render(); | 50 | window.ferdi.render(); |
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js index 519691ede..df8980314 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.js | |||
@@ -65,7 +65,7 @@ class Invite extends Component { | |||
65 | { | 65 | { |
66 | fields: { | 66 | fields: { |
67 | invite: [ | 67 | invite: [ |
68 | ...Array(3).fill({ | 68 | ...Array.from({ length: 3 }).fill({ |
69 | fields: { | 69 | fields: { |
70 | name: { | 70 | name: { |
71 | label: this.props.intl.formatMessage(messages.nameLabel), | 71 | label: this.props.intl.formatMessage(messages.nameLabel), |
@@ -95,19 +95,19 @@ class Invite extends Component { | |||
95 | this.props.intl, | 95 | this.props.intl, |
96 | ); | 96 | ); |
97 | 97 | ||
98 | document.querySelector('input:first-child').focus(); | 98 | document.querySelector('input:first-child')?.focus(); |
99 | } | 99 | } |
100 | 100 | ||
101 | submit(e) { | 101 | submit(e) { |
102 | e.preventDefault(); | 102 | e.preventDefault(); |
103 | 103 | ||
104 | this.form.submit({ | 104 | this.form?.submit({ |
105 | onSuccess: form => { | 105 | onSuccess: form => { |
106 | this.props.onSubmit({ invites: form.values().invite }); | 106 | this.props.onSubmit({ invites: form.values().invite }); |
107 | 107 | ||
108 | this.form.clear(); | 108 | this.form?.clear(); |
109 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( | 109 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( |
110 | document.querySelector('input:first-child').focus(); | 110 | document.querySelector('input:first-child')?.focus(); |
111 | this.setState({ showSuccessInfo: true }); | 111 | this.setState({ showSuccessInfo: true }); |
112 | }, | 112 | }, |
113 | onError: () => {}, | 113 | onError: () => {}, |
diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js index 0574b3765..71250bd4d 100644 --- a/src/components/settings/SettingsLayout.js +++ b/src/components/settings/SettingsLayout.js | |||
@@ -29,6 +29,7 @@ class SettingsLayout extends Component { | |||
29 | componentWillUnmount() { | 29 | componentWillUnmount() { |
30 | document.removeEventListener( | 30 | document.removeEventListener( |
31 | 'keydown', | 31 | 'keydown', |
32 | // eslint-disable-next-line unicorn/no-invalid-remove-event-listener | ||
32 | this.handleKeyDown.bind(this), | 33 | this.handleKeyDown.bind(this), |
33 | false, | 34 | false, |
34 | ); | 35 | ); |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 9a9abeab4..7df6d5c78 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -183,8 +183,8 @@ class EditServiceForm extends Component { | |||
183 | removeTrailingSlash: false, | 183 | removeTrailingSlash: false, |
184 | }); | 184 | }); |
185 | isValid = await recipe.validateUrl(values.customUrl); | 185 | isValid = await recipe.validateUrl(values.customUrl); |
186 | } catch (err) { | 186 | } catch (error) { |
187 | console.warn('ValidateURL', err); | 187 | console.warn('ValidateURL', error); |
188 | isValid = false; | 188 | isValid = false; |
189 | } | 189 | } |
190 | } | 190 | } |
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js index 847f2ea06..9272b05c9 100644 --- a/src/components/settings/services/ServicesDashboard.js +++ b/src/components/settings/services/ServicesDashboard.js | |||
@@ -91,7 +91,7 @@ class ServicesDashboard extends Component { | |||
91 | <h1>{intl.formatMessage(messages.headline)}</h1> | 91 | <h1>{intl.formatMessage(messages.headline)}</h1> |
92 | </div> | 92 | </div> |
93 | <div className="settings__body"> | 93 | <div className="settings__body"> |
94 | {(services.length !== 0 || searchNeedle) && !isLoading && ( | 94 | {(services.length > 0 || searchNeedle) && !isLoading && ( |
95 | <SearchInput | 95 | <SearchInput |
96 | placeholder={intl.formatMessage(messages.searchService)} | 96 | placeholder={intl.formatMessage(messages.searchService)} |
97 | onChange={needle => filterServices({ needle })} | 97 | onChange={needle => filterServices({ needle })} |
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js index 8ea31ca40..49aff389b 100644 --- a/src/components/ui/ImageUpload.js +++ b/src/components/ui/ImageUpload.js | |||
@@ -30,14 +30,14 @@ class ImageUpload extends Component { | |||
30 | onDrop(acceptedFiles) { | 30 | onDrop(acceptedFiles) { |
31 | const { field } = this.props; | 31 | const { field } = this.props; |
32 | 32 | ||
33 | acceptedFiles.forEach(file => { | 33 | for (const file of acceptedFiles) { |
34 | const imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path; | 34 | const imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path; |
35 | this.setState({ | 35 | this.setState({ |
36 | path: imgPath, | 36 | path: imgPath, |
37 | }); | 37 | }); |
38 | 38 | ||
39 | this.props.field.onDrop(file); | 39 | this.props.field.onDrop(file); |
40 | }); | 40 | } |
41 | 41 | ||
42 | field.set(''); | 42 | field.set(''); |
43 | } | 43 | } |
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js index f5cbad48b..dc6be10da 100644 --- a/src/components/ui/InfoBar.js +++ b/src/components/ui/InfoBar.js | |||
@@ -5,7 +5,6 @@ import classnames from 'classnames'; | |||
5 | import Loader from 'react-loader'; | 5 | import Loader from 'react-loader'; |
6 | import { defineMessages, injectIntl } from 'react-intl'; | 6 | import { defineMessages, injectIntl } from 'react-intl'; |
7 | 7 | ||
8 | // import { oneOrManyChildElements } from '../../prop-types'; | ||
9 | import Appear from './effects/Appear'; | 8 | import Appear from './effects/Appear'; |
10 | 9 | ||
11 | const messages = defineMessages({ | 10 | const messages = defineMessages({ |
@@ -18,7 +17,7 @@ const messages = defineMessages({ | |||
18 | @observer | 17 | @observer |
19 | class InfoBar extends Component { | 18 | class InfoBar extends Component { |
20 | static propTypes = { | 19 | static propTypes = { |
21 | // eslint-disable-next-line | 20 | // eslint-disable-next-line react/forbid-prop-types |
22 | children: PropTypes.any.isRequired, | 21 | children: PropTypes.any.isRequired, |
23 | onClick: PropTypes.func, | 22 | onClick: PropTypes.func, |
24 | type: PropTypes.string, | 23 | type: PropTypes.string, |
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js index 13ae2303b..9e34bf110 100644 --- a/src/components/ui/Infobox.js +++ b/src/components/ui/Infobox.js | |||
@@ -15,7 +15,8 @@ const messages = defineMessages({ | |||
15 | @observer | 15 | @observer |
16 | class Infobox extends Component { | 16 | class Infobox extends Component { |
17 | static propTypes = { | 17 | static propTypes = { |
18 | children: PropTypes.any.isRequired, // eslint-disable-line | 18 | // eslint-disable-next-line react/forbid-prop-types |
19 | children: PropTypes.any.isRequired, | ||
19 | icon: PropTypes.string, | 20 | icon: PropTypes.string, |
20 | type: PropTypes.string, | 21 | type: PropTypes.string, |
21 | ctaOnClick: PropTypes.func, | 22 | ctaOnClick: PropTypes.func, |
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js index 9e6830b0c..3c7c66c59 100644 --- a/src/components/ui/Modal/index.js +++ b/src/components/ui/Modal/index.js | |||
@@ -54,7 +54,7 @@ class Modal extends Component { | |||
54 | portal={portal} | 54 | portal={portal} |
55 | onRequestClose={close} | 55 | onRequestClose={close} |
56 | shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} | 56 | shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} |
57 | appElement={document.getElementById('root')} | 57 | appElement={document.querySelector('#root')} |
58 | > | 58 | > |
59 | {showClose && close && ( | 59 | {showClose && close && ( |
60 | <button type="button" className={classes.close} onClick={close}> | 60 | <button type="button" className={classes.close} onClick={close}> |
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js index 15b4c28e7..5ac7ddd6d 100644 --- a/src/components/ui/Select.js +++ b/src/components/ui/Select.js | |||
@@ -49,7 +49,7 @@ class Select extends Component { | |||
49 | let selected = field.value; | 49 | let selected = field.value; |
50 | 50 | ||
51 | if (multiple) { | 51 | if (multiple) { |
52 | if (typeof field.value === 'string' && field.value.substr(0, 1) === '[') { | 52 | if (typeof field.value === 'string' && field.value.slice(0, 1) === '[') { |
53 | // Value is JSON encoded | 53 | // Value is JSON encoded |
54 | selected = JSON.parse(field.value); | 54 | selected = JSON.parse(field.value); |
55 | } else if (typeof field.value === 'object') { | 55 | } else if (typeof field.value === 'object') { |
diff --git a/src/components/ui/effects/Appear.js b/src/components/ui/effects/Appear.js index 1255fce2e..183181f8f 100644 --- a/src/components/ui/effects/Appear.js +++ b/src/components/ui/effects/Appear.js | |||
@@ -1,11 +1,11 @@ | |||
1 | /* eslint-disable react/no-did-mount-set-state */ | ||
2 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
3 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
4 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; | 3 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; |
5 | 4 | ||
6 | export default class Appear extends Component { | 5 | export default class Appear extends Component { |
7 | static propTypes = { | 6 | static propTypes = { |
8 | children: PropTypes.any.isRequired, // eslint-disable-line | 7 | // eslint-disable-next-line react/forbid-prop-types |
8 | children: PropTypes.any.isRequired, | ||
9 | transitionName: PropTypes.string, | 9 | transitionName: PropTypes.string, |
10 | className: PropTypes.string, | 10 | className: PropTypes.string, |
11 | }; | 11 | }; |
@@ -24,11 +24,7 @@ export default class Appear extends Component { | |||
24 | } | 24 | } |
25 | 25 | ||
26 | render() { | 26 | render() { |
27 | const { | 27 | const { children, transitionName, className } = this.props; |
28 | children, | ||
29 | transitionName, | ||
30 | className, | ||
31 | } = this.props; | ||
32 | 28 | ||
33 | if (!this.state.mounted) { | 29 | if (!this.state.mounted) { |
34 | return null; | 30 | return null; |
diff --git a/src/config.ts b/src/config.ts index 7bb2525a5..6ad58c7a5 100644 --- a/src/config.ts +++ b/src/config.ts | |||
@@ -5,7 +5,7 @@ import ms from 'ms'; | |||
5 | export const CHECK_INTERVAL = ms('1h'); // How often should we perform checks | 5 | export const CHECK_INTERVAL = ms('1h'); // How often should we perform checks |
6 | 6 | ||
7 | export const LOCAL_HOSTNAME = 'localhost'; | 7 | export const LOCAL_HOSTNAME = 'localhost'; |
8 | export const LOCAL_PORT = 45569; | 8 | export const LOCAL_PORT = 45_569; |
9 | export const LOCAL_API = 'http://localhost:3000'; | 9 | export const LOCAL_API = 'http://localhost:3000'; |
10 | export const DEV_FRANZ_API = 'https://dev.franzinfra.com'; | 10 | export const DEV_FRANZ_API = 'https://dev.franzinfra.com'; |
11 | 11 | ||
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index cc3929656..0f9457fd0 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -32,14 +32,12 @@ class AccountScreen extends Component { | |||
32 | 32 | ||
33 | const api = stores.settings.all.app.server; | 33 | const api = stores.settings.all.app.server; |
34 | 34 | ||
35 | let url; | 35 | const url = |
36 | if (api === LIVE_FRANZ_API) { | 36 | api === LIVE_FRANZ_API |
37 | url = stores.user.getAuthURL( | 37 | ? stores.user.getAuthURL( |
38 | `${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`, | 38 | `${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`, |
39 | ); | 39 | ) |
40 | } else { | 40 | : `${api}${route}`; |
41 | url = `${api}${route}`; | ||
42 | } | ||
43 | 41 | ||
44 | actions.app.openExternalUrl({ url }); | 42 | actions.app.openExternalUrl({ url }); |
45 | } | 43 | } |
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js index 58e73f2f3..e03c4c1d2 100644 --- a/src/containers/settings/SettingsWindow.js +++ b/src/containers/settings/SettingsWindow.js | |||
@@ -19,11 +19,11 @@ class SettingsContainer extends Component { | |||
19 | el = document.createElement('div'); | 19 | el = document.createElement('div'); |
20 | 20 | ||
21 | componentDidMount() { | 21 | componentDidMount() { |
22 | this.portalRoot.appendChild(this.el); | 22 | this.portalRoot.append(this.el); |
23 | } | 23 | } |
24 | 24 | ||
25 | componentWillUnmount() { | 25 | componentWillUnmount() { |
26 | this.portalRoot.removeChild(this.el); | 26 | this.el.remove(); |
27 | } | 27 | } |
28 | 28 | ||
29 | render() { | 29 | render() { |
diff --git a/src/electron/ipc-api/appIndicator.ts b/src/electron/ipc-api/appIndicator.ts index 5b5f2bac7..a51ed8161 100644 --- a/src/electron/ipc-api/appIndicator.ts +++ b/src/electron/ipc-api/appIndicator.ts | |||
@@ -34,8 +34,7 @@ export default params => { | |||
34 | 34 | ||
35 | ipcMain.on('updateAppIndicator', (_event, args) => { | 35 | ipcMain.on('updateAppIndicator', (_event, args) => { |
36 | // Flash TaskBar for windows, bounce Dock on Mac | 36 | // Flash TaskBar for windows, bounce Dock on Mac |
37 | if (!(app as any).mainWindow.isFocused()) { | 37 | if (!(app as any).mainWindow.isFocused() && params.settings.app.get('notifyTaskBarOnMessage')) { |
38 | if (params.settings.app.get('notifyTaskBarOnMessage')) { | ||
39 | if (isWindows) { | 38 | if (isWindows) { |
40 | (app as any).mainWindow.flashFrame(true); | 39 | (app as any).mainWindow.flashFrame(true); |
41 | (app as any).mainWindow.once('focus', () => | 40 | (app as any).mainWindow.once('focus', () => |
@@ -45,7 +44,6 @@ export default params => { | |||
45 | app.dock.bounce('informational'); | 44 | app.dock.bounce('informational'); |
46 | } | 45 | } |
47 | } | 46 | } |
48 | } | ||
49 | 47 | ||
50 | // Update badge | 48 | // Update badge |
51 | if (isMac && typeof args.indicator === 'string') { | 49 | if (isMac && typeof args.indicator === 'string') { |
diff --git a/src/electron/ipc-api/autoUpdate.ts b/src/electron/ipc-api/autoUpdate.ts index 70890539d..31c614ab7 100644 --- a/src/electron/ipc-api/autoUpdate.ts +++ b/src/electron/ipc-api/autoUpdate.ts | |||
@@ -44,8 +44,8 @@ export default (params: { mainWindow: BrowserWindow; settings: any }) => { | |||
44 | app.quit(); | 44 | app.quit(); |
45 | }, 20); | 45 | }, 20); |
46 | } | 46 | } |
47 | } catch (e) { | 47 | } catch (error) { |
48 | console.error(e); | 48 | console.error(error); |
49 | event.sender.send('autoUpdate', { error: true }); | 49 | event.sender.send('autoUpdate', { error: true }); |
50 | } | 50 | } |
51 | } | 51 | } |
diff --git a/src/electron/ipc-api/cld.ts b/src/electron/ipc-api/cld.ts index b907f3730..4221f9b22 100644 --- a/src/electron/ipc-api/cld.ts +++ b/src/electron/ipc-api/cld.ts | |||
@@ -16,8 +16,8 @@ export default async () => { | |||
16 | 16 | ||
17 | return result.languages[0].code; | 17 | return result.languages[0].code; |
18 | } | 18 | } |
19 | } catch (e) { | 19 | } catch (error) { |
20 | console.error(e); | 20 | console.error(error); |
21 | } | 21 | } |
22 | }); | 22 | }); |
23 | }; | 23 | }; |
diff --git a/src/electron/ipc-api/dnd.ts b/src/electron/ipc-api/dnd.ts index 6fb8999a3..afaef9a66 100644 --- a/src/electron/ipc-api/dnd.ts +++ b/src/electron/ipc-api/dnd.ts | |||
@@ -15,8 +15,8 @@ export default async () => { | |||
15 | const isDND = getDoNotDisturb(); | 15 | const isDND = getDoNotDisturb(); |
16 | debug('Fetching DND state, set to', isDND); | 16 | debug('Fetching DND state, set to', isDND); |
17 | return isDND; | 17 | return isDND; |
18 | } catch (e) { | 18 | } catch (error) { |
19 | console.error(e); | 19 | console.error(error); |
20 | return false; | 20 | return false; |
21 | } | 21 | } |
22 | }); | 22 | }); |
diff --git a/src/electron/ipc-api/download.ts b/src/electron/ipc-api/download.ts index 822658f26..af15b157e 100644 --- a/src/electron/ipc-api/download.ts +++ b/src/electron/ipc-api/download.ts | |||
@@ -7,7 +7,7 @@ import { PathLike } from 'fs'; | |||
7 | const debug = require('debug')('Ferdi:ipcApi:download'); | 7 | const debug = require('debug')('Ferdi:ipcApi:download'); |
8 | 8 | ||
9 | function decodeBase64Image(dataString: string) { | 9 | function decodeBase64Image(dataString: string) { |
10 | const matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/); | 10 | const matches = dataString.match(/^data:([+/A-Za-z-]+);base64,(.+)$/); |
11 | 11 | ||
12 | if (matches?.length !== 3) { | 12 | if (matches?.length !== 3) { |
13 | return new Error('Invalid input string'); | 13 | return new Error('Invalid input string'); |
@@ -47,12 +47,12 @@ export default (params: { mainWindow: BrowserWindow }) => { | |||
47 | ); | 47 | ); |
48 | 48 | ||
49 | debug('File blob saved to', saveDialog.filePath); | 49 | debug('File blob saved to', saveDialog.filePath); |
50 | } catch (err) { | 50 | } catch (error) { |
51 | console.log(err); | 51 | console.log(error); |
52 | } | 52 | } |
53 | } | 53 | } |
54 | } catch (e) { | 54 | } catch (error) { |
55 | console.error(e); | 55 | console.error(error); |
56 | } | 56 | } |
57 | }, | 57 | }, |
58 | ); | 58 | ); |
diff --git a/src/features/appearance/index.js b/src/features/appearance/index.js index d1db68ac6..0c935be32 100644 --- a/src/features/appearance/index.js +++ b/src/features/appearance/index.js | |||
@@ -14,7 +14,7 @@ function createStyleElement() { | |||
14 | } | 14 | } |
15 | 15 | ||
16 | function setAppearance(style) { | 16 | function setAppearance(style) { |
17 | const styleElement = document.getElementById(STYLE_ELEMENT_ID); | 17 | const styleElement = document.querySelector(`#${STYLE_ELEMENT_ID}`); |
18 | 18 | ||
19 | if (styleElement) { | 19 | if (styleElement) { |
20 | styleElement.innerHTML = style; | 20 | styleElement.innerHTML = style; |
@@ -30,18 +30,18 @@ function darkenAbsolute(originalColor, absoluteChange) { | |||
30 | function generateAccentStyle(accentColorStr) { | 30 | function generateAccentStyle(accentColorStr) { |
31 | let style = ''; | 31 | let style = ''; |
32 | 32 | ||
33 | Object.keys(themeInfo).forEach(property => { | 33 | for (const property of Object.keys(themeInfo)) { |
34 | style += ` | 34 | style += ` |
35 | ${themeInfo[property]} { | 35 | ${themeInfo[property]} { |
36 | ${property}: ${accentColorStr}; | 36 | ${property}: ${accentColorStr}; |
37 | } | 37 | } |
38 | `; | 38 | `; |
39 | }); | 39 | } |
40 | 40 | ||
41 | let accentColor = color(DEFAULT_APP_SETTINGS.accentColor); | 41 | let accentColor = color(DEFAULT_APP_SETTINGS.accentColor); |
42 | try { | 42 | try { |
43 | accentColor = color(accentColorStr); | 43 | accentColor = color(accentColorStr); |
44 | } catch (e) { | 44 | } catch { |
45 | // Ignore invalid accent color. | 45 | // Ignore invalid accent color. |
46 | } | 46 | } |
47 | const darkerColorStr = darkenAbsolute(accentColor, 5).hex(); | 47 | const darkerColorStr = darkenAbsolute(accentColor, 5).hex(); |
@@ -133,14 +133,14 @@ function generateShowDragAreaStyle(accentColor) { | |||
133 | } | 133 | } |
134 | 134 | ||
135 | function generateVerticalStyle(widthStr, alwaysShowWorkspaces) { | 135 | function generateVerticalStyle(widthStr, alwaysShowWorkspaces) { |
136 | if (!document.getElementById('vertical-style')) { | 136 | if (!document.querySelector('#vertical-style')) { |
137 | const link = document.createElement('link'); | 137 | const link = document.createElement('link'); |
138 | link.id = 'vertical-style'; | 138 | link.id = 'vertical-style'; |
139 | link.rel = 'stylesheet'; | 139 | link.rel = 'stylesheet'; |
140 | link.type = 'text/css'; | 140 | link.type = 'text/css'; |
141 | link.href = './styles/vertical.css'; | 141 | link.href = './styles/vertical.css'; |
142 | 142 | ||
143 | document.head.appendChild(link); | 143 | document.head.append(link); |
144 | } | 144 | } |
145 | const width = Number(widthStr); | 145 | const width = Number(widthStr); |
146 | const sidebarWidth = width - 4; | 146 | const sidebarWidth = width - 4; |
@@ -150,12 +150,12 @@ function generateVerticalStyle(widthStr, alwaysShowWorkspaces) { | |||
150 | .sidebar { | 150 | .sidebar { |
151 | height: ${sidebarWidth + verticalStyleOffset + 1}px !important; | 151 | height: ${sidebarWidth + verticalStyleOffset + 1}px !important; |
152 | ${ | 152 | ${ |
153 | alwaysShowWorkspaces | 153 | alwaysShowWorkspaces |
154 | ? ` | 154 | ? ` |
155 | width: calc(100% - 300px) !important; | 155 | width: calc(100% - 300px) !important; |
156 | ` | 156 | ` |
157 | : '' | 157 | : '' |
158 | } | 158 | } |
159 | } | 159 | } |
160 | 160 | ||
161 | .sidebar .sidebar__button { | 161 | .sidebar .sidebar__button { |
@@ -220,10 +220,10 @@ function generateStyle(settings) { | |||
220 | } | 220 | } |
221 | if (useVerticalStyle) { | 221 | if (useVerticalStyle) { |
222 | style += generateVerticalStyle(serviceRibbonWidth, alwaysShowWorkspaces); | 222 | style += generateVerticalStyle(serviceRibbonWidth, alwaysShowWorkspaces); |
223 | } else if (document.getElementById('vertical-style')) { | 223 | } else if (document.querySelector('#vertical-style')) { |
224 | const link = document.getElementById('vertical-style'); | 224 | const link = document.querySelector('#vertical-style'); |
225 | if (link) { | 225 | if (link) { |
226 | document.head.removeChild(link); | 226 | link.remove(); |
227 | } | 227 | } |
228 | } | 228 | } |
229 | if (alwaysShowWorkspaces) { | 229 | if (alwaysShowWorkspaces) { |
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js index a3614dd11..05e18e2f7 100644 --- a/src/features/communityRecipes/store.js +++ b/src/features/communityRecipes/store.js | |||
@@ -18,11 +18,13 @@ export class CommunityRecipesStore extends FeatureStore { | |||
18 | @computed get communityRecipes() { | 18 | @computed get communityRecipes() { |
19 | if (!this.stores) return []; | 19 | if (!this.stores) return []; |
20 | 20 | ||
21 | return this.stores.recipePreviews.dev.map((r) => { | 21 | return this.stores.recipePreviews.dev.map(recipePreview => { |
22 | // TODO: Need to figure out if this is even necessary/used | 22 | // TODO: Need to figure out if this is even necessary/used |
23 | r.isDevRecipe = !!r.author.find((a) => a.email === this.stores.user.data.email); | 23 | recipePreview.isDevRecipe = !!recipePreview.author.some( |
24 | author => author.email === this.stores.user.data.email, | ||
25 | ); | ||
24 | 26 | ||
25 | return r; | 27 | return recipePreview; |
26 | }); | 28 | }); |
27 | } | 29 | } |
28 | } | 30 | } |
diff --git a/src/features/quickSwitch/Component.js b/src/features/quickSwitch/Component.js index df2bf968d..f21db0ebd 100644 --- a/src/features/quickSwitch/Component.js +++ b/src/features/quickSwitch/Component.js | |||
@@ -140,7 +140,7 @@ class QuickSwitchModal extends Component { | |||
140 | let services = []; | 140 | let services = []; |
141 | if ( | 141 | if ( |
142 | this.state.search && | 142 | this.state.search && |
143 | compact(invoke(this.state.search, 'match', /^[a-z0-9]/i)).length > 0 | 143 | compact(invoke(this.state.search, 'match', /^[\da-z]/i)).length > 0 |
144 | ) { | 144 | ) { |
145 | // Apply simple search algorythm to list of all services | 145 | // Apply simple search algorythm to list of all services |
146 | services = this.props.stores.services.allDisplayed; | 146 | services = this.props.stores.services.allDisplayed; |
@@ -261,7 +261,7 @@ class QuickSwitchModal extends Component { | |||
261 | // Wrapped inside timeout to let the modal render first | 261 | // Wrapped inside timeout to let the modal render first |
262 | setTimeout(() => { | 262 | setTimeout(() => { |
263 | if (this.inputRef.current) { | 263 | if (this.inputRef.current) { |
264 | this.inputRef.current.getElementsByTagName('input')[0].focus(); | 264 | this.inputRef.current.querySelectorAll('input')[0].focus(); |
265 | } | 265 | } |
266 | }, 10); | 266 | }, 10); |
267 | 267 | ||
@@ -273,7 +273,7 @@ class QuickSwitchModal extends Component { | |||
273 | // search query change when modal not visible | 273 | // search query change when modal not visible |
274 | setTimeout(() => { | 274 | setTimeout(() => { |
275 | if (this.inputRef.current) { | 275 | if (this.inputRef.current) { |
276 | this.inputRef.current.getElementsByTagName('input')[0].blur(); | 276 | this.inputRef.current.querySelectorAll('input')[0].blur(); |
277 | } | 277 | } |
278 | }, 100); | 278 | }, 100); |
279 | 279 | ||
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js index eb7116651..b9320cda9 100644 --- a/src/features/serviceProxy/index.js +++ b/src/features/serviceProxy/index.js | |||
@@ -18,7 +18,7 @@ export default function init(stores) { | |||
18 | 18 | ||
19 | debug('Service Proxy autorun'); | 19 | debug('Service Proxy autorun'); |
20 | 20 | ||
21 | services.forEach((service) => { | 21 | for (const service of services) { |
22 | const s = session.fromPartition(`persist:service-${service.id}`); | 22 | const s = session.fromPartition(`persist:service-${service.id}`); |
23 | 23 | ||
24 | if (config.isEnabled) { | 24 | if (config.isEnabled) { |
@@ -33,6 +33,6 @@ export default function init(stores) { | |||
33 | }); | 33 | }); |
34 | } | 34 | } |
35 | } | 35 | } |
36 | }); | 36 | } |
37 | }); | 37 | }); |
38 | } | 38 | } |
diff --git a/src/features/settingsWS/store.js b/src/features/settingsWS/store.js index 9100f33d1..e36ccee72 100755 --- a/src/features/settingsWS/store.js +++ b/src/features/settingsWS/store.js | |||
@@ -25,11 +25,13 @@ export class SettingsWSStore extends FeatureStore { | |||
25 | this.stores = stores; | 25 | this.stores = stores; |
26 | this.actions = actions; | 26 | this.actions = actions; |
27 | 27 | ||
28 | this._registerReactions(createReactions([ | 28 | this._registerReactions( |
29 | this._initialize.bind(this), | 29 | createReactions([ |
30 | this._reconnect.bind(this), | 30 | this._initialize.bind(this), |
31 | this._close.bind(this), | 31 | this._reconnect.bind(this), |
32 | ])); | 32 | this._close.bind(this), |
33 | ]), | ||
34 | ); | ||
33 | } | 35 | } |
34 | 36 | ||
35 | connect() { | 37 | connect() { |
@@ -51,12 +53,12 @@ export class SettingsWSStore extends FeatureStore { | |||
51 | this.heartbeat(); | 53 | this.heartbeat(); |
52 | }); | 54 | }); |
53 | 55 | ||
54 | this.ws.on('message', (data) => { | 56 | this.ws.on('message', data => { |
55 | const resp = JSON.parse(data); | 57 | const resp = JSON.parse(data); |
56 | debug('Received message', resp); | 58 | debug('Received message', resp); |
57 | 59 | ||
58 | if (resp.id) { | 60 | if (resp.id) { |
59 | this.stores.user.getUserInfoRequest.patch((result) => { | 61 | this.stores.user.getUserInfoRequest.patch(result => { |
60 | if (!result) return; | 62 | if (!result) return; |
61 | 63 | ||
62 | debug('Patching user object with new values'); | 64 | debug('Patching user object with new values'); |
@@ -66,8 +68,8 @@ export class SettingsWSStore extends FeatureStore { | |||
66 | }); | 68 | }); |
67 | 69 | ||
68 | this.ws.on('ping', this.heartbeat.bind(this)); | 70 | this.ws.on('ping', this.heartbeat.bind(this)); |
69 | } catch (err) { | 71 | } catch (error) { |
70 | console.err(err); | 72 | console.error(error); |
71 | } | 73 | } |
72 | } | 74 | } |
73 | 75 | ||
diff --git a/src/features/todos/preload.js b/src/features/todos/preload.js index 9bd76a704..3b86ddbc5 100644 --- a/src/features/todos/preload.js +++ b/src/features/todos/preload.js | |||
@@ -7,7 +7,9 @@ debug('Preloading Todos Webview'); | |||
7 | 7 | ||
8 | let hostMessageListener = ({ action }) => { | 8 | let hostMessageListener = ({ action }) => { |
9 | switch (action) { | 9 | switch (action) { |
10 | case 'todos:initialize-as-service': ipcRenderer.sendToHost('hello'); break; | 10 | case 'todos:initialize-as-service': |
11 | ipcRenderer.sendToHost('hello'); | ||
12 | break; | ||
11 | default: | 13 | default: |
12 | } | 14 | } |
13 | }; | 15 | }; |
@@ -15,7 +17,9 @@ let hostMessageListener = ({ action }) => { | |||
15 | window.ferdi = { | 17 | window.ferdi = { |
16 | onInitialize(ipcHostMessageListener) { | 18 | onInitialize(ipcHostMessageListener) { |
17 | hostMessageListener = ipcHostMessageListener; | 19 | hostMessageListener = ipcHostMessageListener; |
18 | ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, { action: 'todos:initialized' }); | 20 | ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, { |
21 | action: 'todos:initialized', | ||
22 | }); | ||
19 | }, | 23 | }, |
20 | sendToHost(message) { | 24 | sendToHost(message) { |
21 | ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, message); | 25 | ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, message); |
@@ -30,7 +34,7 @@ ipcRenderer.on(IPC.TODOS_HOST_CHANNEL, (event, message) => { | |||
30 | if (window.location.href === 'https://app.franztodos.com/login/') { | 34 | if (window.location.href === 'https://app.franztodos.com/login/') { |
31 | // Insert info element informing about Franz accounts | 35 | // Insert info element informing about Franz accounts |
32 | const infoElement = document.createElement('p'); | 36 | const infoElement = document.createElement('p'); |
33 | infoElement.innerText = `You are using Franz's official Todo Service. | 37 | infoElement.textContent = `You are using Franz's official Todo Service. |
34 | This service will only work with accounts registered with Franz - no Ferdi accounts will work here! | 38 | This service will only work with accounts registered with Franz - no Ferdi accounts will work here! |
35 | If you do not have a Franz account you can change the Todo service by going into Ferdi's settings and changing the "Todo server". | 39 | If you do not have a Franz account you can change the Todo service by going into Ferdi's settings and changing the "Todo server". |
36 | You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`; | 40 | You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`; |
@@ -42,7 +46,7 @@ You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`; | |||
42 | const textElement = document.querySelector('p'); | 46 | const textElement = document.querySelector('p'); |
43 | if (textElement) { | 47 | if (textElement) { |
44 | clearInterval(waitForReact); | 48 | clearInterval(waitForReact); |
45 | textElement.parentElement.insertBefore(infoElement, textElement); | 49 | textElement.parentElement?.insertBefore(infoElement, textElement); |
46 | } else { | 50 | } else { |
47 | numChecks += 1; | 51 | numChecks += 1; |
48 | 52 | ||
diff --git a/src/features/utils/FeatureStore.js b/src/features/utils/FeatureStore.js index 4d4e217a9..afe726294 100644 --- a/src/features/utils/FeatureStore.js +++ b/src/features/utils/FeatureStore.js | |||
@@ -16,11 +16,11 @@ export class FeatureStore { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | _startActions(actions = this._actions) { | 18 | _startActions(actions = this._actions) { |
19 | actions.forEach((a) => a.start()); | 19 | for (const a of actions) a.start(); |
20 | } | 20 | } |
21 | 21 | ||
22 | _stopActions(actions = this._actions) { | 22 | _stopActions(actions = this._actions) { |
23 | actions.forEach((a) => a.stop()); | 23 | for (const a of actions) a.stop(); |
24 | } | 24 | } |
25 | 25 | ||
26 | // REACTIONS | 26 | // REACTIONS |
@@ -31,10 +31,10 @@ export class FeatureStore { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | _startReactions(reactions = this._reactions) { | 33 | _startReactions(reactions = this._reactions) { |
34 | reactions.forEach((r) => r.start()); | 34 | for (const r of reactions) r.start(); |
35 | } | 35 | } |
36 | 36 | ||
37 | _stopReactions(reactions = this._reactions) { | 37 | _stopReactions(reactions = this._reactions) { |
38 | reactions.forEach((r) => r.stop()); | 38 | for (const r of reactions) r.stop(); |
39 | } | 39 | } |
40 | } | 40 | } |
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js index e1e1b9991..0273bb13e 100644 --- a/src/features/webControls/containers/WebControlsScreen.js +++ b/src/features/webControls/containers/WebControlsScreen.js | |||
@@ -16,7 +16,8 @@ const URL_EVENTS = [ | |||
16 | 'did-navigate-in-page', | 16 | 'did-navigate-in-page', |
17 | ]; | 17 | ]; |
18 | 18 | ||
19 | @inject('stores', 'actions') @observer | 19 | @inject('stores', 'actions') |
20 | @observer | ||
20 | class WebControlsScreen extends Component { | 21 | class WebControlsScreen extends Component { |
21 | @observable url = ''; | 22 | @observable url = ''; |
22 | 23 | ||
@@ -36,15 +37,15 @@ class WebControlsScreen extends Component { | |||
36 | this.webview = service.webview; | 37 | this.webview = service.webview; |
37 | this.url = this.webview.getURL(); | 38 | this.url = this.webview.getURL(); |
38 | 39 | ||
39 | URL_EVENTS.forEach((event) => { | 40 | for (const event of URL_EVENTS) { |
40 | this.webview.addEventListener(event, (e) => { | 41 | this.webview.addEventListener(event, e => { |
41 | if (!e.isMainFrame) return; | 42 | if (!e.isMainFrame) return; |
42 | 43 | ||
43 | this.url = e.url; | 44 | this.url = e.url; |
44 | this.canGoBack = this.webview.canGoBack(); | 45 | this.canGoBack = this.webview.canGoBack(); |
45 | this.canGoForward = this.webview.canGoForward(); | 46 | this.canGoForward = this.webview.canGoForward(); |
46 | }); | 47 | }); |
47 | }); | 48 | } |
48 | } | 49 | } |
49 | }); | 50 | }); |
50 | } | 51 | } |
@@ -83,13 +84,16 @@ class WebControlsScreen extends Component { | |||
83 | 84 | ||
84 | try { | 85 | try { |
85 | url = new URL(url).toString(); | 86 | url = new URL(url).toString(); |
86 | } catch (err) { | 87 | } catch { |
87 | // eslint-disable-next-line no-useless-escape | 88 | url = |
88 | if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) { | 89 | // eslint-disable-next-line no-useless-escape |
89 | url = `http://${url}`; | 90 | /^((?!-))(xn--)?[\da-z][\d_a-z-]{0,61}[\da-z]{0,1}\.(xn--)?([\da-z\-]{1,61}|[\da-z-]{1,30}\.[a-z]{2,})$/.test( |
90 | } else { | 91 | url, |
91 | url = SEARCH_ENGINE_URLS[this.settings.app.searchEngine]({ searchTerm: url }); | 92 | ) |
92 | } | 93 | ? `http://${url}` |
94 | : SEARCH_ENGINE_URLS[this.settings.app.searchEngine]({ | ||
95 | searchTerm: url, | ||
96 | }); | ||
93 | } | 97 | } |
94 | 98 | ||
95 | this.webview.loadURL(url); | 99 | this.webview.loadURL(url); |
@@ -114,7 +118,7 @@ class WebControlsScreen extends Component { | |||
114 | goBack={() => this.goBack()} | 118 | goBack={() => this.goBack()} |
115 | canGoForward={this.canGoForward} | 119 | canGoForward={this.canGoForward} |
116 | goForward={() => this.goForward()} | 120 | goForward={() => this.goForward()} |
117 | navigate={(url) => this.navigate(url)} | 121 | navigate={url => this.navigate(url)} |
118 | url={this.url} | 122 | url={this.url} |
119 | /> | 123 | /> |
120 | ); | 124 | ); |
diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js index cae95e9ed..f562733dd 100644 --- a/src/features/workspaces/components/EditWorkspaceForm.js +++ b/src/features/workspaces/components/EditWorkspaceForm.js | |||
@@ -108,7 +108,7 @@ class EditWorkspaceForm extends Component { | |||
108 | default: false, | 108 | default: false, |
109 | }, | 109 | }, |
110 | services: { | 110 | services: { |
111 | value: workspace.services.slice(), | 111 | value: [...workspace.services], |
112 | }, | 112 | }, |
113 | }, | 113 | }, |
114 | }); | 114 | }); |
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.js b/src/features/workspaces/components/WorkspaceDrawerItem.js index 82e1b81a4..7df2b60be 100644 --- a/src/features/workspaces/components/WorkspaceDrawerItem.js +++ b/src/features/workspaces/components/WorkspaceDrawerItem.js | |||
@@ -143,7 +143,7 @@ class WorkspaceDrawerItem extends Component { | |||
143 | isActive ? classes.activeServices : null, | 143 | isActive ? classes.activeServices : null, |
144 | ])} | 144 | ])} |
145 | > | 145 | > |
146 | {services.length | 146 | {services.length > 0 |
147 | ? services.join(', ') | 147 | ? services.join(', ') |
148 | : intl.formatMessage(messages.noServicesAddedYet)} | 148 | : intl.formatMessage(messages.noServicesAddedYet)} |
149 | </span> | 149 | </span> |
diff --git a/src/features/workspaces/models/Workspace.js b/src/features/workspaces/models/Workspace.js index d9488e991..14add9437 100644 --- a/src/features/workspaces/models/Workspace.js +++ b/src/features/workspaces/models/Workspace.js | |||
@@ -15,7 +15,7 @@ export default class Workspace { | |||
15 | 15 | ||
16 | constructor(data) { | 16 | constructor(data) { |
17 | if (!data.id) { | 17 | if (!data.id) { |
18 | throw Error('Workspace requires Id'); | 18 | throw new Error('Workspace requires Id'); |
19 | } | 19 | } |
20 | 20 | ||
21 | this.id = data.id; | 21 | this.id = data.id; |
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index ec9d7ee7f..73e882990 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js | |||
@@ -302,8 +302,8 @@ export default class WorkspacesStore extends FeatureStore { | |||
302 | const { allServicesRequest } = services; | 302 | const { allServicesRequest } = services; |
303 | const servicesHaveBeenLoaded = allServicesRequest.wasExecuted && !allServicesRequest.isError; | 303 | const servicesHaveBeenLoaded = allServicesRequest.wasExecuted && !allServicesRequest.isError; |
304 | // Loop through all workspaces and remove invalid service ids (locally) | 304 | // Loop through all workspaces and remove invalid service ids (locally) |
305 | this.workspaces.forEach((workspace) => { | 305 | for (const workspace of this.workspaces) { |
306 | workspace.services.forEach((serviceId) => { | 306 | for (const serviceId of workspace.services) { |
307 | if ( | 307 | if ( |
308 | servicesHaveBeenLoaded | 308 | servicesHaveBeenLoaded |
309 | && !services.one(serviceId) | 309 | && !services.one(serviceId) |
@@ -311,7 +311,7 @@ export default class WorkspacesStore extends FeatureStore { | |||
311 | ) { | 311 | ) { |
312 | workspace.services.remove(serviceId); | 312 | workspace.services.remove(serviceId); |
313 | } | 313 | } |
314 | }); | 314 | } |
315 | }); | 315 | } |
316 | }; | 316 | }; |
317 | } | 317 | } |
diff --git a/src/helpers/i18n-helpers.ts b/src/helpers/i18n-helpers.ts index c1f18f446..ec7dc8e98 100644 --- a/src/helpers/i18n-helpers.ts +++ b/src/helpers/i18n-helpers.ts | |||
@@ -4,13 +4,11 @@ export function getLocale({ | |||
4 | let localeStr = locale; | 4 | let localeStr = locale; |
5 | if (locales[locale] === undefined) { | 5 | if (locales[locale] === undefined) { |
6 | let localeFuzzy: string | undefined; | 6 | let localeFuzzy: string | undefined; |
7 | Object.keys(locales).forEach((localStr) => { | 7 | for (const localStr of Object.keys(locales)) { |
8 | if (locales && Object.hasOwnProperty.call(locales, localStr)) { | 8 | if (locales && Object.hasOwnProperty.call(locales, localStr) && locale.slice(0, 2) === localStr.slice(0, 2)) { |
9 | if (locale.substring(0, 2) === localStr.substring(0, 2)) { | ||
10 | localeFuzzy = localStr; | 9 | localeFuzzy = localStr; |
11 | } | 10 | } |
12 | } | 11 | } |
13 | }); | ||
14 | 12 | ||
15 | if (localeFuzzy !== undefined) { | 13 | if (localeFuzzy !== undefined) { |
16 | localeStr = localeFuzzy; | 14 | localeStr = localeFuzzy; |
@@ -61,12 +59,12 @@ export function getSelectOptions({ | |||
61 | if (sort) { | 59 | if (sort) { |
62 | keys = keys.sort(Intl.Collator().compare); | 60 | keys = keys.sort(Intl.Collator().compare); |
63 | } | 61 | } |
64 | keys.forEach((key) => { | 62 | for (const key of keys) { |
65 | options.push({ | 63 | options.push({ |
66 | value: key, | 64 | value: key, |
67 | label: locales[key], | 65 | label: locales[key], |
68 | }); | 66 | }); |
69 | }); | 67 | } |
70 | 68 | ||
71 | return options; | 69 | return options; |
72 | } | 70 | } |
diff --git a/src/helpers/password-helpers.ts b/src/helpers/password-helpers.ts index 89c75c752..e5d9a4a25 100644 --- a/src/helpers/password-helpers.ts +++ b/src/helpers/password-helpers.ts | |||
@@ -12,9 +12,9 @@ export function scorePassword(password: string) { | |||
12 | 12 | ||
13 | // award every unique letter until 5 repetitions | 13 | // award every unique letter until 5 repetitions |
14 | const letters = {}; | 14 | const letters = {}; |
15 | for (let i = 0; i < password.length; i += 1) { | 15 | for (const letter of password) { |
16 | letters[password[i]] = (letters[password[i]] || 0) + 1; | 16 | letters[letter] = (letters[letter] || 0) + 1; |
17 | score += 5.0 / letters[password[i]]; | 17 | score += 5 / letters[letter]; |
18 | } | 18 | } |
19 | 19 | ||
20 | // bonus points for mixing it up | 20 | // bonus points for mixing it up |
@@ -26,11 +26,11 @@ export function scorePassword(password: string) { | |||
26 | }; | 26 | }; |
27 | 27 | ||
28 | let variationCount = 0; | 28 | let variationCount = 0; |
29 | Object.keys(variations).forEach((key) => { | 29 | for (const key of Object.keys(variations)) { |
30 | variationCount += (variations[key] === true) ? 1 : 0; | 30 | variationCount += variations[key] === true ? 1 : 0; |
31 | }); | 31 | } |
32 | 32 | ||
33 | score += (variationCount - 1) * 10; | 33 | score += (variationCount - 1) * 10; |
34 | 34 | ||
35 | return parseInt(score.toString(), 10); | 35 | return Number.parseInt(score.toString(), 10); |
36 | } | 36 | } |
diff --git a/src/helpers/recipe-helpers.ts b/src/helpers/recipe-helpers.ts index 965429210..65ef04088 100644 --- a/src/helpers/recipe-helpers.ts +++ b/src/helpers/recipe-helpers.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | /* eslint-disable global-require */ | ||
1 | import { parse } from 'path'; | 2 | import { parse } from 'path'; |
2 | import { userDataRecipesPath } from '../environment'; | 3 | import { userDataRecipesPath } from '../environment'; |
3 | 4 | ||
@@ -15,20 +16,17 @@ export function loadRecipeConfig(recipeId: string) { | |||
15 | // Delete module from cache | 16 | // Delete module from cache |
16 | delete require.cache[require.resolve(configPath)]; | 17 | delete require.cache[require.resolve(configPath)]; |
17 | 18 | ||
18 | // eslint-disable-next-line | 19 | // eslint-disable-next-line import/no-dynamic-require |
19 | let config = require(configPath); | 20 | const config = require(configPath); |
20 | 21 | ||
21 | const moduleConfigPath = require.resolve(configPath); | 22 | const moduleConfigPath = require.resolve(configPath); |
22 | config.path = parse(moduleConfigPath).dir; | 23 | config.path = parse(moduleConfigPath).dir; |
23 | 24 | ||
24 | return config; | 25 | return config; |
25 | } catch (e) { | 26 | } catch (error) { |
26 | console.error(e); | 27 | console.error(error); |
27 | return null; | 28 | return null; |
28 | } | 29 | } |
29 | } | 30 | } |
30 | 31 | ||
31 | module.paths.unshift( | 32 | module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory()); |
32 | getDevRecipeDirectory(), | ||
33 | getRecipeDirectory(), | ||
34 | ); | ||
diff --git a/src/helpers/schedule-helpers.ts b/src/helpers/schedule-helpers.ts index 754fd5556..55b7c1e6f 100644 --- a/src/helpers/schedule-helpers.ts +++ b/src/helpers/schedule-helpers.ts | |||
@@ -5,15 +5,15 @@ export function isInTimeframe(start: string, end: string) { | |||
5 | startHourStr, | 5 | startHourStr, |
6 | startMinuteStr, | 6 | startMinuteStr, |
7 | ] = start.split(':'); | 7 | ] = start.split(':'); |
8 | const startHour = parseInt(startHourStr, 10); | 8 | const startHour = Number.parseInt(startHourStr, 10); |
9 | const startMinute = parseInt(startMinuteStr, 10); | 9 | const startMinute = Number.parseInt(startMinuteStr, 10); |
10 | 10 | ||
11 | const [ | 11 | const [ |
12 | endHourStr, | 12 | endHourStr, |
13 | endMinuteStr, | 13 | endMinuteStr, |
14 | ] = end.split(':'); | 14 | ] = end.split(':'); |
15 | const endHour = parseInt(endHourStr, 10); | 15 | const endHour = Number.parseInt(endHourStr, 10); |
16 | const endMinute = parseInt(endMinuteStr, 10); | 16 | const endMinute = Number.parseInt(endMinuteStr, 10); |
17 | 17 | ||
18 | const currentHour = new Date().getHours(); | 18 | const currentHour = new Date().getHours(); |
19 | const currentMinute = new Date().getMinutes(); | 19 | const currentMinute = new Date().getMinutes(); |
diff --git a/src/helpers/url-helpers.ts b/src/helpers/url-helpers.ts index 3657ae693..1e87ecabb 100644 --- a/src/helpers/url-helpers.ts +++ b/src/helpers/url-helpers.ts | |||
@@ -12,7 +12,7 @@ export function isValidExternalURL(url: string | URL) { | |||
12 | let parsedUrl: URL; | 12 | let parsedUrl: URL; |
13 | try { | 13 | try { |
14 | parsedUrl = new URL(url.toString()); | 14 | parsedUrl = new URL(url.toString()); |
15 | } catch (_) { | 15 | } catch { |
16 | return false; | 16 | return false; |
17 | } | 17 | } |
18 | 18 | ||
diff --git a/src/helpers/userAgent-helpers.ts b/src/helpers/userAgent-helpers.ts index 73c8bfd03..091a76400 100644 --- a/src/helpers/userAgent-helpers.ts +++ b/src/helpers/userAgent-helpers.ts | |||
@@ -8,7 +8,7 @@ import { | |||
8 | function macOS() { | 8 | function macOS() { |
9 | const version = macosVersion() || ''; | 9 | const version = macosVersion() || ''; |
10 | let cpuName = os.cpus()[0].model.split(' ')[0]; | 10 | let cpuName = os.cpus()[0].model.split(' ')[0]; |
11 | if (cpuName && cpuName.match(/\(/)) { | 11 | if (cpuName && /\(/.test(cpuName)) { |
12 | cpuName = cpuName.split('(')[0]; | 12 | cpuName = cpuName.split('(')[0]; |
13 | } | 13 | } |
14 | return `Macintosh; ${cpuName} Mac OS X ${version.replace(/\./g, '_')}`; | 14 | return `Macintosh; ${cpuName} Mac OS X ${version.replace(/\./g, '_')}`; |
diff --git a/src/helpers/validation-helpers.ts b/src/helpers/validation-helpers.ts index 3a9622309..23c297443 100644 --- a/src/helpers/validation-helpers.ts +++ b/src/helpers/validation-helpers.ts | |||
@@ -49,16 +49,15 @@ export function url({ field }) { | |||
49 | const value = field.value.trim(); | 49 | const value = field.value.trim(); |
50 | let isValid = false; | 50 | let isValid = false; |
51 | 51 | ||
52 | if (value !== '') { | 52 | isValid = |
53 | // eslint-disable-next-line | 53 | value !== '' |
54 | isValid = Boolean( | 54 | ? Boolean( |
55 | value.match( | 55 | // eslint-disable-next-line unicorn/better-regex |
56 | /(^|[\s.:;?\-\]<(])(https?:\/\/[-\w;/?:@&=+$|_.!~*|'()[\]%#,☺]+[\w/#](\(\))?)(?=$|[\s',|().:;?\-[\]>)])/i, | 56 | /(^|[\s.:;?\-\]<(])(https?:\/\/[-\w;/?:@&=+$|_.!~*|'()[\]%#,☺]+[\w/#](\(\))?)(?=$|[\s',|().:;?\-[\]>)])/i.test( |
57 | ), | 57 | value, |
58 | ); | 58 | ), |
59 | } else { | 59 | ) |
60 | isValid = true; | 60 | : true; |
61 | } | ||
62 | 61 | ||
63 | return [ | 62 | return [ |
64 | isValid, | 63 | isValid, |
diff --git a/src/i18n/apply-branding.js b/src/i18n/apply-branding.js index 7aeabc4af..8ec573919 100644 --- a/src/i18n/apply-branding.js +++ b/src/i18n/apply-branding.js | |||
@@ -7,7 +7,7 @@ const path = require('path'); | |||
7 | console.log('Applying Ferdi branding to translations...'); | 7 | console.log('Applying Ferdi branding to translations...'); |
8 | 8 | ||
9 | // Keys to ignore when applying branding | 9 | // Keys to ignore when applying branding |
10 | const ignore = [ | 10 | const ignore = new Set([ |
11 | 'login.customServerSuggestion', | 11 | 'login.customServerSuggestion', |
12 | 'login.customServerQuestion', | 12 | 'login.customServerQuestion', |
13 | 'settings.app.todoServerInfo', | 13 | 'settings.app.todoServerInfo', |
@@ -18,10 +18,10 @@ const ignore = [ | |||
18 | 'settings.team.copy', | 18 | 'settings.team.copy', |
19 | 'settings.team.manageAction', | 19 | 'settings.team.manageAction', |
20 | 'settings.app.serverMoneyInfo', | 20 | 'settings.app.serverMoneyInfo', |
21 | ]; | 21 | ]); |
22 | 22 | ||
23 | // Files to ignore when applying branding | 23 | // Files to ignore when applying branding |
24 | const ignoreFiles = ['.DS_Store', '.', '..']; | 24 | const ignoreFiles = new Set(['.DS_Store', '.', '..']); |
25 | 25 | ||
26 | // What to replace | 26 | // What to replace |
27 | const replace = { | 27 | const replace = { |
@@ -38,14 +38,15 @@ const replaceFind = Object.keys(replace); | |||
38 | const replaceReplaceWith = Object.values(replace); | 38 | const replaceReplaceWith = Object.values(replace); |
39 | 39 | ||
40 | const replaceStr = (str, find, replaceWith) => { | 40 | const replaceStr = (str, find, replaceWith) => { |
41 | for (let i = 0; i < find.length; i += 1) { | 41 | for (const [i, element] of find.entries()) { |
42 | str = str.replace(new RegExp(find[i], 'gi'), replaceWith[i]); | 42 | str = str.replace(new RegExp(element, 'gi'), replaceWith[i]); |
43 | } | 43 | } |
44 | return str; | 44 | return str; |
45 | }; | 45 | }; |
46 | 46 | ||
47 | // eslint-disable-next-line unicorn/no-array-for-each | ||
47 | files.forEach(async file => { | 48 | files.forEach(async file => { |
48 | if (ignoreFiles.includes(file)) return; | 49 | if (ignoreFiles.has(file)) return; |
49 | 50 | ||
50 | // Read locale data | 51 | // Read locale data |
51 | const filePath = path.join(locales, file); | 52 | const filePath = path.join(locales, file); |
@@ -53,7 +54,7 @@ files.forEach(async file => { | |||
53 | 54 | ||
54 | // Replace branding | 55 | // Replace branding |
55 | for (const key in locale) { | 56 | for (const key in locale) { |
56 | if (!ignore.includes(key)) { | 57 | if (!ignore.has(key)) { |
57 | locale[key] = replaceStr(locale[key], replaceFind, replaceReplaceWith); | 58 | locale[key] = replaceStr(locale[key], replaceFind, replaceReplaceWith); |
58 | } | 59 | } |
59 | } | 60 | } |
diff --git a/src/i18n/translations.js b/src/i18n/translations.js index 161a172ba..9a7dc7453 100644 --- a/src/i18n/translations.js +++ b/src/i18n/translations.js | |||
@@ -1,13 +1,15 @@ | |||
1 | /* eslint-disable global-require */ | ||
1 | import { APP_LOCALES } from './languages'; | 2 | import { APP_LOCALES } from './languages'; |
2 | 3 | ||
3 | const translations = []; | 4 | const translations = []; |
4 | Object.keys(APP_LOCALES).forEach((key) => { | 5 | for (const key of Object.keys(APP_LOCALES)) { |
5 | try { | 6 | try { |
6 | const translation = require(`./locales/${key}.json`); // eslint-disable-line | 7 | // eslint-disable-next-line import/no-dynamic-require |
8 | const translation = require(`./locales/${key}.json`); | ||
7 | translations[key] = translation; | 9 | translations[key] = translation; |
8 | } catch (err) { | 10 | } catch { |
9 | console.warn(`Can't find translations for ${key}`); | 11 | console.warn(`Can't find translations for ${key}`); |
10 | } | 12 | } |
11 | }); | 13 | } |
12 | 14 | ||
13 | module.exports = translations; | 15 | module.exports = translations; |
diff --git a/src/index.js b/src/index.js index 08d9a3a45..758b11dc9 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -147,7 +147,7 @@ if (!gotTheLock) { | |||
147 | // https://github.com/electron/electron/issues/9046 | 147 | // https://github.com/electron/electron/issues/9046 |
148 | if ( | 148 | if ( |
149 | isLinux && | 149 | isLinux && |
150 | ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1 | 150 | ['Pantheon', 'Unity:Unity7'].includes(process.env.XDG_CURRENT_DESKTOP) |
151 | ) { | 151 | ) { |
152 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; | 152 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; |
153 | } | 153 | } |
@@ -475,7 +475,7 @@ ipcMain.on( | |||
475 | debug( | 475 | debug( |
476 | `Received modifyRequestHeaders ${modifiedRequestHeaders} for serviceId ${serviceId}`, | 476 | `Received modifyRequestHeaders ${modifiedRequestHeaders} for serviceId ${serviceId}`, |
477 | ); | 477 | ); |
478 | modifiedRequestHeaders.forEach(headerFilterSet => { | 478 | for (const headerFilterSet of modifiedRequestHeaders) { |
479 | const { headers, requestFilters } = headerFilterSet; | 479 | const { headers, requestFilters } = headerFilterSet; |
480 | session | 480 | session |
481 | .fromPartition(`persist:service-${serviceId}`) | 481 | .fromPartition(`persist:service-${serviceId}`) |
@@ -488,7 +488,7 @@ ipcMain.on( | |||
488 | } | 488 | } |
489 | callback({ requestHeaders: details.requestHeaders }); | 489 | callback({ requestHeaders: details.requestHeaders }); |
490 | }); | 490 | }); |
491 | }); | 491 | } |
492 | }, | 492 | }, |
493 | ); | 493 | ); |
494 | 494 | ||
diff --git a/src/internal-server/app/Controllers/Http/RecipeController.js b/src/internal-server/app/Controllers/Http/RecipeController.js index 1a7595a9d..2c7baf2a4 100644 --- a/src/internal-server/app/Controllers/Http/RecipeController.js +++ b/src/internal-server/app/Controllers/Http/RecipeController.js | |||
@@ -1,8 +1,6 @@ | |||
1 | const Recipe = use('App/Models/Recipe'); | 1 | const Recipe = use('App/Models/Recipe'); |
2 | const Drive = use('Drive'); | 2 | const Drive = use('Drive'); |
3 | const { | 3 | const { validateAll } = use('Validator'); |
4 | validateAll, | ||
5 | } = use('Validator'); | ||
6 | const Env = use('Env'); | 4 | const Env = use('Env'); |
7 | 5 | ||
8 | const fetch = require('node-fetch'); | 6 | const fetch = require('node-fetch'); |
@@ -14,9 +12,7 @@ const RECIPES_URL = `${LIVE_FERDI_API}/${API_VERSION}/recipes`; | |||
14 | 12 | ||
15 | class RecipeController { | 13 | class RecipeController { |
16 | // List official and custom recipes | 14 | // List official and custom recipes |
17 | async list({ | 15 | async list({ response }) { |
18 | response, | ||
19 | }) { | ||
20 | const officialRecipes = JSON.parse(await (await fetch(RECIPES_URL)).text()); | 16 | const officialRecipes = JSON.parse(await (await fetch(RECIPES_URL)).text()); |
21 | const customRecipesArray = (await Recipe.all()).rows; | 17 | const customRecipesArray = (await Recipe.all()).rows; |
22 | const customRecipes = customRecipesArray.map(recipe => ({ | 18 | const customRecipes = customRecipesArray.map(recipe => ({ |
@@ -25,19 +21,13 @@ class RecipeController { | |||
25 | ...JSON.parse(recipe.data), | 21 | ...JSON.parse(recipe.data), |
26 | })); | 22 | })); |
27 | 23 | ||
28 | const recipes = [ | 24 | const recipes = [...officialRecipes, ...customRecipes]; |
29 | ...officialRecipes, | ||
30 | ...customRecipes, | ||
31 | ]; | ||
32 | 25 | ||
33 | return response.send(recipes); | 26 | return response.send(recipes); |
34 | } | 27 | } |
35 | 28 | ||
36 | // Search official and custom recipes | 29 | // Search official and custom recipes |
37 | async search({ | 30 | async search({ request, response }) { |
38 | request, | ||
39 | response, | ||
40 | }) { | ||
41 | // Validate user input | 31 | // Validate user input |
42 | const validation = await validateAll(request.all(), { | 32 | const validation = await validateAll(request.all(), { |
43 | needle: 'required', | 33 | needle: 'required', |
@@ -64,13 +54,23 @@ class RecipeController { | |||
64 | })); | 54 | })); |
65 | } else { | 55 | } else { |
66 | let remoteResults = []; | 56 | let remoteResults = []; |
67 | if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq | 57 | // eslint-disable-next-line eqeqeq |
68 | remoteResults = JSON.parse(await (await fetch(`${RECIPES_URL}/search?needle=${encodeURIComponent(needle)}`)).text()); | 58 | if (Env.get('CONNECT_WITH_FRANZ') == 'true') { |
59 | // eslint-disable-line eqeqeq | ||
60 | remoteResults = JSON.parse( | ||
61 | await ( | ||
62 | await fetch( | ||
63 | `${RECIPES_URL}/search?needle=${encodeURIComponent(needle)}`, | ||
64 | ) | ||
65 | ).text(), | ||
66 | ); | ||
69 | } | 67 | } |
70 | 68 | ||
71 | debug('remoteResults:', remoteResults); | 69 | debug('remoteResults:', remoteResults); |
72 | 70 | ||
73 | const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON(); | 71 | const localResultsArray = ( |
72 | await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch() | ||
73 | ).toJSON(); | ||
74 | const localResults = localResultsArray.map(recipe => ({ | 74 | const localResults = localResultsArray.map(recipe => ({ |
75 | id: recipe.recipeId, | 75 | id: recipe.recipeId, |
76 | name: recipe.name, | 76 | name: recipe.name, |
@@ -79,20 +79,14 @@ class RecipeController { | |||
79 | 79 | ||
80 | debug('localResults:', localResults); | 80 | debug('localResults:', localResults); |
81 | 81 | ||
82 | results = [ | 82 | results = [...localResults, ...(remoteResults || [])]; |
83 | ...localResults, | ||
84 | ...remoteResults || [], | ||
85 | ]; | ||
86 | } | 83 | } |
87 | 84 | ||
88 | return response.send(results); | 85 | return response.send(results); |
89 | } | 86 | } |
90 | 87 | ||
91 | // Download a recipe | 88 | // Download a recipe |
92 | async download({ | 89 | async download({ response, params }) { |
93 | response, | ||
94 | params, | ||
95 | }) { | ||
96 | // Validate user input | 90 | // Validate user input |
97 | const validation = await validateAll(params, { | 91 | const validation = await validateAll(params, { |
98 | recipe: 'required|accepted', | 92 | recipe: 'required|accepted', |
@@ -108,14 +102,17 @@ class RecipeController { | |||
108 | const service = params.recipe; | 102 | const service = params.recipe; |
109 | 103 | ||
110 | // Check for invalid characters | 104 | // Check for invalid characters |
111 | if (/\.{1,}/.test(service) || /\/{1,}/.test(service)) { | 105 | if (/\.+/.test(service) || /\/+/.test(service)) { |
112 | return response.send('Invalid recipe name'); | 106 | return response.send('Invalid recipe name'); |
113 | } | 107 | } |
114 | 108 | ||
115 | // Check if recipe exists in recipes folder | 109 | // Check if recipe exists in recipes folder |
116 | if (await Drive.exists(`${service}.tar.gz`)) { | 110 | if (await Drive.exists(`${service}.tar.gz`)) { |
117 | return response.send(await Drive.get(`${service}.tar.gz`)); | 111 | return response.send(await Drive.get(`${service}.tar.gz`)); |
118 | } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq | 112 | } |
113 | // eslint-disable-next-line eqeqeq | ||
114 | if (Env.get('CONNECT_WITH_FRANZ') == 'true') { | ||
115 | // eslint-disable-line eqeqeq | ||
119 | return response.redirect(`${RECIPES_URL}/download/${service}`); | 116 | return response.redirect(`${RECIPES_URL}/download/${service}`); |
120 | } | 117 | } |
121 | return response.status(400).send({ | 118 | return response.status(400).send({ |
diff --git a/src/internal-server/app/Controllers/Http/ServiceController.js b/src/internal-server/app/Controllers/Http/ServiceController.js index f2af9d411..ae463617d 100644 --- a/src/internal-server/app/Controllers/Http/ServiceController.js +++ b/src/internal-server/app/Controllers/Http/ServiceController.js | |||
@@ -135,13 +135,11 @@ class ServiceController { | |||
135 | 135 | ||
136 | const newSettings = { | 136 | const newSettings = { |
137 | ...settings, | 137 | ...settings, |
138 | ...{ | 138 | iconId, |
139 | iconId, | 139 | customIconVersion: |
140 | customIconVersion: | 140 | settings && settings.customIconVersion |
141 | settings && settings.customIconVersion | 141 | ? settings.customIconVersion + 1 |
142 | ? settings.customIconVersion + 1 | 142 | : 1, |
143 | : 1, | ||
144 | }, | ||
145 | }; | 143 | }; |
146 | 144 | ||
147 | // Update data in database | 145 | // Update data in database |
@@ -157,9 +155,7 @@ class ServiceController { | |||
157 | id, | 155 | id, |
158 | name: service.name, | 156 | name: service.name, |
159 | ...newSettings, | 157 | ...newSettings, |
160 | iconUrl: `http://${hostname}:${port}/${API_VERSION}/icon/${ | 158 | iconUrl: `http://${hostname}:${port}/${API_VERSION}/icon/${newSettings.iconId}`, |
161 | newSettings.iconId | ||
162 | }`, | ||
163 | userId: 1, | 159 | userId: 1, |
164 | }, | 160 | }, |
165 | status: ['updated'], | 161 | status: ['updated'], |
diff --git a/src/internal-server/app/Controllers/Http/UserController.js b/src/internal-server/app/Controllers/Http/UserController.js index 994dcc0dc..7b71aac14 100644 --- a/src/internal-server/app/Controllers/Http/UserController.js +++ b/src/internal-server/app/Controllers/Http/UserController.js | |||
@@ -1,34 +1,37 @@ | |||
1 | const User = use('App/Models/User'); | 1 | const User = use('App/Models/User'); |
2 | const Service = use('App/Models/Service'); | 2 | const Service = use('App/Models/Service'); |
3 | const Workspace = use('App/Models/Workspace'); | 3 | const Workspace = use('App/Models/Workspace'); |
4 | const { | 4 | const { validateAll } = use('Validator'); |
5 | validateAll, | ||
6 | } = use('Validator'); | ||
7 | 5 | ||
8 | const btoa = require('btoa'); | 6 | const btoa = require('btoa'); |
9 | const fetch = require('node-fetch'); | 7 | const fetch = require('node-fetch'); |
10 | const uuid = require('uuid/v4'); | 8 | const uuid = require('uuid/v4'); |
11 | const crypto = require('crypto'); | 9 | const crypto = require('crypto'); |
12 | const { DEFAULT_APP_SETTINGS, API_VERSION } = require('../../../../environment'); | 10 | const { |
11 | DEFAULT_APP_SETTINGS, | ||
12 | API_VERSION, | ||
13 | } = require('../../../../environment'); | ||
13 | const { default: userAgent } = require('../../../../helpers/userAgent-helpers'); | 14 | const { default: userAgent } = require('../../../../helpers/userAgent-helpers'); |
14 | 15 | ||
15 | const apiRequest = (url, route, method, auth) => new Promise((resolve, reject) => { | 16 | const apiRequest = (url, route, method, auth) => |
16 | try { | 17 | new Promise((resolve, reject) => { |
17 | fetch(`${url}/${API_VERSION}/${route}`, { | 18 | try { |
18 | method, | 19 | fetch(`${url}/${API_VERSION}/${route}`, { |
19 | headers: { | 20 | method, |
20 | Authorization: `Bearer ${auth}`, | 21 | headers: { |
21 | 'User-Agent': userAgent(), | 22 | Authorization: `Bearer ${auth}`, |
22 | }, | 23 | 'User-Agent': userAgent(), |
23 | }) | 24 | }, |
24 | .then(data => data.json()) | 25 | }) |
25 | .then(json => resolve(json)); | 26 | .then(data => data.json()) |
26 | } catch (e) { | 27 | .then(json => resolve(json)); |
27 | reject(); | 28 | } catch { |
28 | } | 29 | reject(); |
29 | }); | 30 | } |
31 | }); | ||
30 | 32 | ||
31 | const LOGIN_SUCCESS_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M'; | 33 | const LOGIN_SUCCESS_TOKEN = |
34 | 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M'; | ||
32 | 35 | ||
33 | const DEFAULT_USER_DATA = { | 36 | const DEFAULT_USER_DATA = { |
34 | accountType: 'individual', | 37 | accountType: 'individual', |
@@ -45,10 +48,7 @@ const DEFAULT_USER_DATA = { | |||
45 | 48 | ||
46 | class UserController { | 49 | class UserController { |
47 | // Register a new user | 50 | // Register a new user |
48 | async signup({ | 51 | async signup({ request, response }) { |
49 | request, | ||
50 | response, | ||
51 | }) { | ||
52 | // Validate user input | 52 | // Validate user input |
53 | const validation = await validateAll(request.all(), { | 53 | const validation = await validateAll(request.all(), { |
54 | firstname: 'required', | 54 | firstname: 'required', |
@@ -70,10 +70,7 @@ class UserController { | |||
70 | } | 70 | } |
71 | 71 | ||
72 | // Login using an existing user | 72 | // Login using an existing user |
73 | async login({ | 73 | async login({ request, response }) { |
74 | request, | ||
75 | response, | ||
76 | }) { | ||
77 | if (!request.header('Authorization')) { | 74 | if (!request.header('Authorization')) { |
78 | return response.status(401).send({ | 75 | return response.status(401).send({ |
79 | message: 'Please provide authorization', | 76 | message: 'Please provide authorization', |
@@ -88,23 +85,21 @@ class UserController { | |||
88 | } | 85 | } |
89 | 86 | ||
90 | // Return information about the current user | 87 | // Return information about the current user |
91 | async me({ | 88 | async me({ response }) { |
92 | response, | ||
93 | }) { | ||
94 | const user = await User.find(1); | 89 | const user = await User.find(1); |
95 | 90 | ||
96 | const settings = typeof user.settings === 'string' ? JSON.parse(user.settings) : user.settings; | 91 | const settings = |
92 | typeof user.settings === 'string' | ||
93 | ? JSON.parse(user.settings) | ||
94 | : user.settings; | ||
97 | 95 | ||
98 | return response.send({ | 96 | return response.send({ |
99 | ...DEFAULT_USER_DATA, | 97 | ...DEFAULT_USER_DATA, |
100 | ...settings || {}, | 98 | ...settings, |
101 | }); | 99 | }); |
102 | } | 100 | } |
103 | 101 | ||
104 | async updateMe({ | 102 | async updateMe({ request, response }) { |
105 | request, | ||
106 | response, | ||
107 | }) { | ||
108 | const user = await User.find(1); | 103 | const user = await User.find(1); |
109 | 104 | ||
110 | let settings = user.settings || {}; | 105 | let settings = user.settings || {}; |
@@ -125,16 +120,11 @@ class UserController { | |||
125 | ...DEFAULT_USER_DATA, | 120 | ...DEFAULT_USER_DATA, |
126 | ...newSettings, | 121 | ...newSettings, |
127 | }, | 122 | }, |
128 | status: [ | 123 | status: ['data-updated'], |
129 | 'data-updated', | ||
130 | ], | ||
131 | }); | 124 | }); |
132 | } | 125 | } |
133 | 126 | ||
134 | async import({ | 127 | async import({ request, response }) { |
135 | request, | ||
136 | response, | ||
137 | }) { | ||
138 | // Validate user input | 128 | // Validate user input |
139 | const validation = await validateAll(request.all(), { | 129 | const validation = await validateAll(request.all(), { |
140 | email: 'required|email', | 130 | email: 'required|email', |
@@ -142,7 +132,8 @@ class UserController { | |||
142 | server: 'required', | 132 | server: 'required', |
143 | }); | 133 | }); |
144 | if (validation.fails()) { | 134 | if (validation.fails()) { |
145 | let errorMessage = 'There was an error while trying to import your account:\n'; | 135 | let errorMessage = |
136 | 'There was an error while trying to import your account:\n'; | ||
146 | for (const message of validation.messages()) { | 137 | for (const message of validation.messages()) { |
147 | if (message.validation === 'required') { | 138 | if (message.validation === 'required') { |
148 | errorMessage += `- Please make sure to supply your ${message.field}\n`; | 139 | errorMessage += `- Please make sure to supply your ${message.field}\n`; |
@@ -155,13 +146,12 @@ class UserController { | |||
155 | return response.status(401).send(errorMessage); | 146 | return response.status(401).send(errorMessage); |
156 | } | 147 | } |
157 | 148 | ||
158 | const { | 149 | const { email, password, server } = request.all(); |
159 | email, | ||
160 | password, | ||
161 | server, | ||
162 | } = request.all(); | ||
163 | 150 | ||
164 | const hashedPassword = crypto.createHash('sha256').update(password).digest('base64'); | 151 | const hashedPassword = crypto |
152 | .createHash('sha256') | ||
153 | .update(password) | ||
154 | .digest('base64'); | ||
165 | 155 | ||
166 | // Try to get an authentication token | 156 | // Try to get an authentication token |
167 | let token; | 157 | let token; |
@@ -178,16 +168,17 @@ class UserController { | |||
178 | const content = await rawResponse.json(); | 168 | const content = await rawResponse.json(); |
179 | 169 | ||
180 | if (!content.message || content.message !== 'Successfully logged in') { | 170 | if (!content.message || content.message !== 'Successfully logged in') { |
181 | const errorMessage = 'Could not login into Franz with your supplied credentials. Please check and try again'; | 171 | const errorMessage = |
172 | 'Could not login into Franz with your supplied credentials. Please check and try again'; | ||
182 | return response.status(401).send(errorMessage); | 173 | return response.status(401).send(errorMessage); |
183 | } | 174 | } |
184 | 175 | ||
185 | // eslint-disable-next-line prefer-destructuring | 176 | // eslint-disable-next-line prefer-destructuring |
186 | token = content.token; | 177 | token = content.token; |
187 | } catch (e) { | 178 | } catch (error) { |
188 | return response.status(401).send({ | 179 | return response.status(401).send({ |
189 | message: 'Cannot login to Franz', | 180 | message: 'Cannot login to Franz', |
190 | error: e, | 181 | error, |
191 | }); | 182 | }); |
192 | } | 183 | } |
193 | 184 | ||
@@ -195,12 +186,13 @@ class UserController { | |||
195 | let userInf = false; | 186 | let userInf = false; |
196 | try { | 187 | try { |
197 | userInf = await apiRequest(server, 'me', 'GET', token); | 188 | userInf = await apiRequest(server, 'me', 'GET', token); |
198 | } catch (e) { | 189 | } catch (error) { |
199 | const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${e}`; | 190 | const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${error}`; |
200 | return response.status(401).send(errorMessage); | 191 | return response.status(401).send(errorMessage); |
201 | } | 192 | } |
202 | if (!userInf) { | 193 | if (!userInf) { |
203 | const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later'; | 194 | const errorMessage = |
195 | 'Could not get your user info from Franz. Please check your credentials or try again later'; | ||
204 | return response.status(401).send(errorMessage); | 196 | return response.status(401).send(errorMessage); |
205 | } | 197 | } |
206 | 198 | ||
@@ -213,8 +205,8 @@ class UserController { | |||
213 | for (const service of services) { | 205 | for (const service of services) { |
214 | await this._createAndCacheService(service, serviceIdTranslation); // eslint-disable-line no-await-in-loop | 206 | await this._createAndCacheService(service, serviceIdTranslation); // eslint-disable-line no-await-in-loop |
215 | } | 207 | } |
216 | } catch (e) { | 208 | } catch (error) { |
217 | const errorMessage = `Could not import your services into our system.\nError: ${e}`; | 209 | const errorMessage = `Could not import your services into our system.\nError: ${error}`; |
218 | return response.status(401).send(errorMessage); | 210 | return response.status(401).send(errorMessage); |
219 | } | 211 | } |
220 | 212 | ||
@@ -225,12 +217,14 @@ class UserController { | |||
225 | for (const workspace of workspaces) { | 217 | for (const workspace of workspaces) { |
226 | await this._createWorkspace(workspace, serviceIdTranslation); // eslint-disable-line no-await-in-loop | 218 | await this._createWorkspace(workspace, serviceIdTranslation); // eslint-disable-line no-await-in-loop |
227 | } | 219 | } |
228 | } catch (e) { | 220 | } catch (error) { |
229 | const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`; | 221 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; |
230 | return response.status(401).send(errorMessage); | 222 | return response.status(401).send(errorMessage); |
231 | } | 223 | } |
232 | 224 | ||
233 | return response.send('Your account has been imported. You can now use your Franz account in Ferdi.'); | 225 | return response.send( |
226 | 'Your account has been imported. You can now use your Franz account in Ferdi.', | ||
227 | ); | ||
234 | } | 228 | } |
235 | 229 | ||
236 | // Account import/export | 230 | // Account import/export |
@@ -255,10 +249,7 @@ class UserController { | |||
255 | .send(exportData); | 249 | .send(exportData); |
256 | } | 250 | } |
257 | 251 | ||
258 | async importFerdi({ | 252 | async importFerdi({ request, response }) { |
259 | request, | ||
260 | response, | ||
261 | }) { | ||
262 | const validation = await validateAll(request.all(), { | 253 | const validation = await validateAll(request.all(), { |
263 | file: 'required', | 254 | file: 'required', |
264 | }); | 255 | }); |
@@ -269,8 +260,10 @@ class UserController { | |||
269 | let file; | 260 | let file; |
270 | try { | 261 | try { |
271 | file = JSON.parse(request.input('file')); | 262 | file = JSON.parse(request.input('file')); |
272 | } catch (e) { | 263 | } catch { |
273 | return response.send('Could not import: Invalid file, could not read file'); | 264 | return response.send( |
265 | 'Could not import: Invalid file, could not read file', | ||
266 | ); | ||
274 | } | 267 | } |
275 | 268 | ||
276 | if (!file || !file.services || !file.workspaces) { | 269 | if (!file || !file.services || !file.workspaces) { |
@@ -284,8 +277,8 @@ class UserController { | |||
284 | for (const service of file.services) { | 277 | for (const service of file.services) { |
285 | await this._createAndCacheService(service, serviceIdTranslation); // eslint-disable-line no-await-in-loop | 278 | await this._createAndCacheService(service, serviceIdTranslation); // eslint-disable-line no-await-in-loop |
286 | } | 279 | } |
287 | } catch (e) { | 280 | } catch (error) { |
288 | const errorMessage = `Could not import your services into our system.\nError: ${e}`; | 281 | const errorMessage = `Could not import your services into our system.\nError: ${error}`; |
289 | return response.send(errorMessage); | 282 | return response.send(errorMessage); |
290 | } | 283 | } |
291 | 284 | ||
@@ -294,8 +287,8 @@ class UserController { | |||
294 | for (const workspace of file.workspaces) { | 287 | for (const workspace of file.workspaces) { |
295 | await this._createWorkspace(workspace, serviceIdTranslation); // eslint-disable-line no-await-in-loop | 288 | await this._createWorkspace(workspace, serviceIdTranslation); // eslint-disable-line no-await-in-loop |
296 | } | 289 | } |
297 | } catch (e) { | 290 | } catch (error) { |
298 | const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`; | 291 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; |
299 | return response.status(401).send(errorMessage); | 292 | return response.status(401).send(errorMessage); |
300 | } | 293 | } |
301 | 294 | ||
@@ -306,15 +299,29 @@ class UserController { | |||
306 | let newWorkspaceId; | 299 | let newWorkspaceId; |
307 | do { | 300 | do { |
308 | newWorkspaceId = uuid(); | 301 | newWorkspaceId = uuid(); |
309 | } while ((await Workspace.query().where('workspaceId', newWorkspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | 302 | } while ( |
310 | 303 | (await Workspace.query().where('workspaceId', newWorkspaceId).fetch()) | |
311 | if (workspace.services && typeof (workspace.services) === 'string' && workspace.services.length > 0) { | 304 | .rows.length > 0 |
305 | ); // eslint-disable-line no-await-in-loop | ||
306 | |||
307 | if ( | ||
308 | workspace.services && | ||
309 | typeof workspace.services === 'string' && | ||
310 | workspace.services.length > 0 | ||
311 | ) { | ||
312 | workspace.services = JSON.parse(workspace.services); | 312 | workspace.services = JSON.parse(workspace.services); |
313 | } | 313 | } |
314 | const services = (workspace.services && typeof (workspace.services) === 'object') ? | 314 | const services = |
315 | workspace.services.map(oldServiceId => serviceIdTranslation[oldServiceId]) : | 315 | workspace.services && typeof workspace.services === 'object' |
316 | []; | 316 | ? workspace.services.map( |
317 | if (workspace.data && typeof (workspace.data) === 'string' && workspace.data.length > 0) { | 317 | oldServiceId => serviceIdTranslation[oldServiceId], |
318 | ) | ||
319 | : []; | ||
320 | if ( | ||
321 | workspace.data && | ||
322 | typeof workspace.data === 'string' && | ||
323 | workspace.data.length > 0 | ||
324 | ) { | ||
318 | workspace.data = JSON.parse(workspace.data); | 325 | workspace.data = JSON.parse(workspace.data); |
319 | } | 326 | } |
320 | 327 | ||
@@ -332,12 +339,19 @@ class UserController { | |||
332 | let newServiceId; | 339 | let newServiceId; |
333 | do { | 340 | do { |
334 | newServiceId = uuid(); | 341 | newServiceId = uuid(); |
335 | } while ((await Service.query().where('serviceId', newServiceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop | 342 | } while ( |
343 | (await Service.query().where('serviceId', newServiceId).fetch()).rows | ||
344 | .length > 0 | ||
345 | ); // eslint-disable-line no-await-in-loop | ||
336 | 346 | ||
337 | // store the old serviceId as the key for future lookup | 347 | // store the old serviceId as the key for future lookup |
338 | serviceIdTranslation[service.serviceId] = newServiceId; | 348 | serviceIdTranslation[service.serviceId] = newServiceId; |
339 | 349 | ||
340 | if (service.settings && typeof (service.settings) === 'string' && service.settings.length > 0) { | 350 | if ( |
351 | service.settings && | ||
352 | typeof service.settings === 'string' && | ||
353 | service.settings.length > 0 | ||
354 | ) { | ||
341 | service.settings = JSON.parse(service.settings); | 355 | service.settings = JSON.parse(service.settings); |
342 | } | 356 | } |
343 | 357 | ||
diff --git a/src/internal-server/app/Middleware/ConvertEmptyStringsToNull.js b/src/internal-server/app/Middleware/ConvertEmptyStringsToNull.js index 87f1f6c25..9591cdc41 100644 --- a/src/internal-server/app/Middleware/ConvertEmptyStringsToNull.js +++ b/src/internal-server/app/Middleware/ConvertEmptyStringsToNull.js | |||
@@ -1,6 +1,6 @@ | |||
1 | class ConvertEmptyStringsToNull { | 1 | class ConvertEmptyStringsToNull { |
2 | async handle({ request }, next) { | 2 | async handle({ request }, next) { |
3 | if (Object.keys(request.body).length) { | 3 | if (Object.keys(request.body).length > 0) { |
4 | request.body = Object.assign( | 4 | request.body = Object.assign( |
5 | ...Object.keys(request.body).map(key => ({ | 5 | ...Object.keys(request.body).map(key => ({ |
6 | [key]: request.body[key] !== '' ? request.body[key] : null, | 6 | [key]: request.body[key] !== '' ? request.body[key] : null, |
diff --git a/src/internal-server/app/Models/Recipe.js b/src/internal-server/app/Models/Recipe.js index bd9741114..f9370e206 100644 --- a/src/internal-server/app/Models/Recipe.js +++ b/src/internal-server/app/Models/Recipe.js | |||
@@ -1,7 +1,6 @@ | |||
1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | 1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ |
2 | const Model = use('Model'); | 2 | const Model = use('Model'); |
3 | 3 | ||
4 | class Recipe extends Model { | 4 | class Recipe extends Model {} |
5 | } | ||
6 | 5 | ||
7 | module.exports = Recipe; | 6 | module.exports = Recipe; |
diff --git a/src/internal-server/app/Models/Service.js b/src/internal-server/app/Models/Service.js index a2e5c981e..95321686c 100644 --- a/src/internal-server/app/Models/Service.js +++ b/src/internal-server/app/Models/Service.js | |||
@@ -1,7 +1,6 @@ | |||
1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | 1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ |
2 | const Model = use('Model'); | 2 | const Model = use('Model'); |
3 | 3 | ||
4 | class Service extends Model { | 4 | class Service extends Model {} |
5 | } | ||
6 | 5 | ||
7 | module.exports = Service; | 6 | module.exports = Service; |
diff --git a/src/internal-server/app/Models/Token.js b/src/internal-server/app/Models/Token.js index 83e989117..1388b94ad 100644 --- a/src/internal-server/app/Models/Token.js +++ b/src/internal-server/app/Models/Token.js | |||
@@ -1,7 +1,6 @@ | |||
1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | 1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ |
2 | const Model = use('Model'); | 2 | const Model = use('Model'); |
3 | 3 | ||
4 | class Token extends Model { | 4 | class Token extends Model {} |
5 | } | ||
6 | 5 | ||
7 | module.exports = Token; | 6 | module.exports = Token; |
diff --git a/src/internal-server/app/Models/User.js b/src/internal-server/app/Models/User.js index 907710d8d..f17f04c3e 100644 --- a/src/internal-server/app/Models/User.js +++ b/src/internal-server/app/Models/User.js | |||
@@ -2,7 +2,6 @@ | |||
2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | 2 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ |
3 | const Model = use('Model'); | 3 | const Model = use('Model'); |
4 | 4 | ||
5 | class User extends Model { | 5 | class User extends Model {} |
6 | } | ||
7 | 6 | ||
8 | module.exports = User; | 7 | module.exports = User; |
diff --git a/src/internal-server/app/Models/Workspace.js b/src/internal-server/app/Models/Workspace.js index dcf39ac75..c47c02e37 100644 --- a/src/internal-server/app/Models/Workspace.js +++ b/src/internal-server/app/Models/Workspace.js | |||
@@ -1,7 +1,6 @@ | |||
1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | 1 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ |
2 | const Model = use('Model'); | 2 | const Model = use('Model'); |
3 | 3 | ||
4 | class Workspace extends Model { | 4 | class Workspace extends Model {} |
5 | } | ||
6 | 5 | ||
7 | module.exports = Workspace; | 6 | module.exports = Workspace; |
diff --git a/src/internal-server/config/shield.js b/src/internal-server/config/shield.js index 76f430e91..4ff22c3f9 100644 --- a/src/internal-server/config/shield.js +++ b/src/internal-server/config/shield.js | |||
@@ -25,8 +25,7 @@ module.exports = { | |||
25 | | } | 25 | | } |
26 | | | 26 | | |
27 | */ | 27 | */ |
28 | directives: { | 28 | directives: {}, |
29 | }, | ||
30 | /* | 29 | /* |
31 | |-------------------------------------------------------------------------- | 30 | |-------------------------------------------------------------------------- |
32 | | Report only | 31 | | Report only |
diff --git a/src/internal-server/public/js/transfer.js b/src/internal-server/public/js/transfer.js index 8382bba02..36fdbd61a 100644 --- a/src/internal-server/public/js/transfer.js +++ b/src/internal-server/public/js/transfer.js | |||
@@ -1,13 +1,17 @@ | |||
1 | const submitBtn = document.getElementById('submit'); | 1 | const submitBtn = document.querySelector('#submit'); |
2 | const fileInput = document.getElementById('file'); | 2 | const fileInput = document.querySelector('#file'); |
3 | const fileOutput = document.getElementById('fileoutput'); | 3 | const fileOutput = document.querySelector('#fileoutput'); |
4 | 4 | ||
5 | fileInput.addEventListener('change', () => { | 5 | fileInput?.addEventListener('change', () => { |
6 | const reader = new FileReader(); | 6 | const reader = new FileReader(); |
7 | reader.onload = () => { | 7 | reader.addEventListener('load', () => { |
8 | const text = reader.result; | 8 | const text = reader.result; |
9 | fileOutput.value = text; | 9 | if (fileOutput) { |
10 | submitBtn.disabled = false; | 10 | fileOutput.value = text; |
11 | }; | 11 | } |
12 | if (submitBtn) { | ||
13 | submitBtn.disabled = false; | ||
14 | } | ||
15 | }); | ||
12 | reader.readAsText(fileInput.files[0]); | 16 | reader.readAsText(fileInput.files[0]); |
13 | }); | 17 | }); |
diff --git a/src/internal-server/start/kernel.js b/src/internal-server/start/kernel.js index 7b540f829..f72e445f2 100644 --- a/src/internal-server/start/kernel.js +++ b/src/internal-server/start/kernel.js | |||
@@ -32,8 +32,7 @@ const globalMiddleware = [ | |||
32 | | Route.get().middleware('auth') | 32 | | Route.get().middleware('auth') |
33 | | | 33 | | |
34 | */ | 34 | */ |
35 | const namedMiddleware = { | 35 | const namedMiddleware = {}; |
36 | }; | ||
37 | 36 | ||
38 | /* | 37 | /* |
39 | |-------------------------------------------------------------------------- | 38 | |-------------------------------------------------------------------------- |
@@ -45,11 +44,8 @@ const namedMiddleware = { | |||
45 | | control over request lifecycle. | 44 | | control over request lifecycle. |
46 | | | 45 | | |
47 | */ | 46 | */ |
48 | const serverMiddleware = [ | 47 | const serverMiddleware = ['Adonis/Middleware/Static']; |
49 | 'Adonis/Middleware/Static', | ||
50 | ]; | ||
51 | 48 | ||
52 | Server | 49 | Server.registerGlobal(globalMiddleware) |
53 | .registerGlobal(globalMiddleware) | ||
54 | .registerNamed(namedMiddleware) | 50 | .registerNamed(namedMiddleware) |
55 | .use(serverMiddleware); | 51 | .use(serverMiddleware); |
diff --git a/src/internal-server/start/migrate.js b/src/internal-server/start/migrate.js index c27e07bc5..0f25240cc 100644 --- a/src/internal-server/start/migrate.js +++ b/src/internal-server/start/migrate.js | |||
@@ -6,29 +6,39 @@ const { ferdiVersion } = require('../../environment'); | |||
6 | const Database = use('Database'); | 6 | const Database = use('Database'); |
7 | const User = use('App/Models/User'); | 7 | const User = use('App/Models/User'); |
8 | 8 | ||
9 | const migrateLog = (text) => { | 9 | const migrateLog = text => { |
10 | console.log('\x1b[36m%s\x1b[0m', 'Ferdi Migration:', '\x1b[0m', text); | 10 | console.log('\u001B[36m%s\u001B[0m', 'Ferdi Migration:', '\u001B[0m', text); |
11 | }; | 11 | }; |
12 | 12 | ||
13 | module.exports = async () => { | 13 | module.exports = async () => { |
14 | migrateLog('🧙 Running database migration wizard'); | 14 | migrateLog('🧙 Running database migration wizard'); |
15 | 15 | ||
16 | // Make sure user table exists | 16 | // Make sure user table exists |
17 | await Database.raw('CREATE TABLE IF NOT EXISTS `users` (`id` integer not null primary key autoincrement, `settings` text, `created_at` datetime, `updated_at` datetime);'); | 17 | await Database.raw( |
18 | 'CREATE TABLE IF NOT EXISTS `users` (`id` integer not null primary key autoincrement, `settings` text, `created_at` datetime, `updated_at` datetime);', | ||
19 | ); | ||
18 | 20 | ||
19 | const user = await User.find(1); | 21 | const user = await User.find(1); |
20 | let settings; | 22 | let settings; |
21 | if (!user) { | 23 | if (!user) { |
22 | migrateLog('🎩 Migrating from old Ferdi version as user doesn\'t exist'); | 24 | migrateLog("🎩 Migrating from old Ferdi version as user doesn't exist"); |
23 | 25 | ||
24 | // Create new user | 26 | // Create new user |
25 | await Database.raw('INSERT INTO "users" ("id") VALUES (\'1\');'); | 27 | await Database.raw('INSERT INTO "users" ("id") VALUES (\'1\');'); |
26 | } else { | 28 | } else { |
27 | settings = typeof user.settings === 'string' ? JSON.parse(user.settings) : user.settings; | 29 | settings = |
30 | typeof user.settings === 'string' | ||
31 | ? JSON.parse(user.settings) | ||
32 | : user.settings; | ||
28 | } | 33 | } |
29 | 34 | ||
30 | if (!settings || !settings.db_version || settings.db_version !== ferdiVersion) { | 35 | if ( |
31 | const srcVersion = settings && settings.db_version ? settings.db_version : '5.4.0-beta.2'; | 36 | !settings || |
37 | !settings.db_version || | ||
38 | settings.db_version !== ferdiVersion | ||
39 | ) { | ||
40 | const srcVersion = | ||
41 | settings && settings.db_version ? settings.db_version : '5.4.0-beta.2'; | ||
32 | migrateLog(`🔮 Migrating table from ${srcVersion} to ${ferdiVersion}`); | 42 | migrateLog(`🔮 Migrating table from ${srcVersion} to ${ferdiVersion}`); |
33 | 43 | ||
34 | // Migrate database to current Ferdi version | 44 | // Migrate database to current Ferdi version |
diff --git a/src/internal-server/test.js b/src/internal-server/test.js index 8d4807d06..ef85743f3 100644 --- a/src/internal-server/test.js +++ b/src/internal-server/test.js | |||
@@ -6,4 +6,4 @@ const dummyUserFolder = path.join(__dirname, 'user_data'); | |||
6 | 6 | ||
7 | fs.ensureDirSync(dummyUserFolder); | 7 | fs.ensureDirSync(dummyUserFolder); |
8 | 8 | ||
9 | server(dummyUserFolder, 45568); | 9 | server(dummyUserFolder, 45_568); |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 8e2d8bdca..563db087b 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -952,7 +952,7 @@ class FranzMenu { | |||
952 | }, | 952 | }, |
953 | ); | 953 | ); |
954 | 954 | ||
955 | services.allDisplayed.forEach((service, i) => | 955 | for (const [i, service] of services.allDisplayed.entries()) { |
956 | menu.push({ | 956 | menu.push({ |
957 | label: this._getServiceName(service), | 957 | label: this._getServiceName(service), |
958 | accelerator: i < 9 ? `${cmdOrCtrlShortcutKey()}+${i + 1}` : null, | 958 | accelerator: i < 9 ? `${cmdOrCtrlShortcutKey()}+${i + 1}` : null, |
@@ -965,8 +965,8 @@ class FranzMenu { | |||
965 | app.mainWindow.restore(); | 965 | app.mainWindow.restore(); |
966 | } | 966 | } |
967 | }, | 967 | }, |
968 | }), | 968 | }); |
969 | ); | 969 | } |
970 | 970 | ||
971 | if ( | 971 | if ( |
972 | services.active && | 972 | services.active && |
@@ -1018,23 +1018,23 @@ class FranzMenu { | |||
1018 | }); | 1018 | }); |
1019 | } | 1019 | } |
1020 | 1020 | ||
1021 | menu.push({ | 1021 | menu.push( |
1022 | type: 'separator', | 1022 | { |
1023 | }); | 1023 | type: 'separator', |
1024 | |||
1025 | // Default workspace | ||
1026 | menu.push({ | ||
1027 | label: intl.formatMessage(menuItems.defaultWorkspace), | ||
1028 | accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+0`, | ||
1029 | type: 'radio', | ||
1030 | checked: !activeWorkspace, | ||
1031 | click: () => { | ||
1032 | workspaceActions.deactivate(); | ||
1033 | }, | 1024 | }, |
1034 | }); | 1025 | { |
1026 | label: intl.formatMessage(menuItems.defaultWorkspace), | ||
1027 | accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+0`, | ||
1028 | type: 'radio', | ||
1029 | checked: !activeWorkspace, | ||
1030 | click: () => { | ||
1031 | workspaceActions.deactivate(); | ||
1032 | }, | ||
1033 | }, | ||
1034 | ); | ||
1035 | 1035 | ||
1036 | // Workspace items | 1036 | // Workspace items |
1037 | workspaces.forEach((workspace, i) => | 1037 | for (const [i, workspace] of workspaces.entries()) { |
1038 | menu.push({ | 1038 | menu.push({ |
1039 | label: workspace.name, | 1039 | label: workspace.name, |
1040 | accelerator: | 1040 | accelerator: |
@@ -1044,8 +1044,8 @@ class FranzMenu { | |||
1044 | click: () => { | 1044 | click: () => { |
1045 | workspaceActions.activate({ workspace }); | 1045 | workspaceActions.activate({ workspace }); |
1046 | }, | 1046 | }, |
1047 | }), | 1047 | }); |
1048 | ); | 1048 | } |
1049 | 1049 | ||
1050 | return menu; | 1050 | return menu; |
1051 | } | 1051 | } |
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js index 3397afdb2..c80931200 100644 --- a/src/lib/TouchBar.js +++ b/src/lib/TouchBar.js | |||
@@ -15,8 +15,8 @@ export default class FranzTouchBar { | |||
15 | if (isMac && semver.gt(osRelease, '16.6.0')) { | 15 | if (isMac && semver.gt(osRelease, '16.6.0')) { |
16 | this.build = autorun(this._build.bind(this)); | 16 | this.build = autorun(this._build.bind(this)); |
17 | } | 17 | } |
18 | } catch (err) { | 18 | } catch (error) { |
19 | console.error(err); | 19 | console.error(error); |
20 | } | 20 | } |
21 | } | 21 | } |
22 | 22 | ||
@@ -27,7 +27,7 @@ export default class FranzTouchBar { | |||
27 | const { TouchBarButton, TouchBarSpacer } = TouchBar; | 27 | const { TouchBarButton, TouchBarSpacer } = TouchBar; |
28 | 28 | ||
29 | const buttons = []; | 29 | const buttons = []; |
30 | this.stores.services.allDisplayed.forEach(((service) => { | 30 | for (const service of this.stores.services.allDisplayed) { |
31 | buttons.push(new TouchBarButton({ | 31 | buttons.push(new TouchBarButton({ |
32 | label: `${service.name}${service.unreadDirectMessageCount > 0 | 32 | label: `${service.name}${service.unreadDirectMessageCount > 0 |
33 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 | 33 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 |
@@ -38,7 +38,7 @@ export default class FranzTouchBar { | |||
38 | this.actions.service.setActive({ serviceId: service.id }); | 38 | this.actions.service.setActive({ serviceId: service.id }); |
39 | }, | 39 | }, |
40 | }), new TouchBarSpacer({ size: 'small' })); | 40 | }), new TouchBarSpacer({ size: 'small' })); |
41 | })); | 41 | } |
42 | 42 | ||
43 | const touchBar = new TouchBar({ items: buttons }); | 43 | const touchBar = new TouchBar({ items: buttons }); |
44 | currentWindow.setTouchBar(touchBar); | 44 | currentWindow.setTouchBar(touchBar); |
diff --git a/src/models/News.ts b/src/models/News.ts index a6ff86dda..4fc21f590 100644 --- a/src/models/News.ts +++ b/src/models/News.ts | |||
@@ -1,5 +1,3 @@ | |||
1 | // @flow | ||
2 | |||
3 | import { ifUndefinedString, ifUndefinedBoolean } from '../jsUtils'; | 1 | import { ifUndefinedString, ifUndefinedBoolean } from '../jsUtils'; |
4 | 2 | ||
5 | interface INews { | 3 | interface INews { |
@@ -20,11 +18,11 @@ export default class News { | |||
20 | 18 | ||
21 | constructor(data: INews) { | 19 | constructor(data: INews) { |
22 | if (!data) { | 20 | if (!data) { |
23 | throw Error('News config not valid'); | 21 | throw new Error('News config not valid'); |
24 | } | 22 | } |
25 | 23 | ||
26 | if (!data.id) { | 24 | if (!data.id) { |
27 | throw Error('News requires Id'); | 25 | throw new Error('News requires Id'); |
28 | } | 26 | } |
29 | 27 | ||
30 | this.id = data.id; | 28 | this.id = data.id; |
diff --git a/src/models/Recipe.ts b/src/models/Recipe.ts index 0a93fbc5a..6022fb520 100644 --- a/src/models/Recipe.ts +++ b/src/models/Recipe.ts | |||
@@ -68,16 +68,16 @@ export default class Recipe { | |||
68 | // TODO: Need to reconcile which of these are optional/mandatory | 68 | // TODO: Need to reconcile which of these are optional/mandatory |
69 | constructor(data: IRecipe) { | 69 | constructor(data: IRecipe) { |
70 | if (!data) { | 70 | if (!data) { |
71 | throw Error('Recipe config not valid'); | 71 | throw new Error('Recipe config not valid'); |
72 | } | 72 | } |
73 | 73 | ||
74 | if (!data.id) { | 74 | if (!data.id) { |
75 | // Ferdi 4 recipes do not have an Id | 75 | // Ferdi 4 recipes do not have an Id |
76 | throw Error(`Recipe '${data.name}' requires Id`); | 76 | throw new Error(`Recipe '${data.name}' requires Id`); |
77 | } | 77 | } |
78 | 78 | ||
79 | if (!semver.valid(data.version)) { | 79 | if (!semver.valid(data.version)) { |
80 | throw Error(`Version ${data.version} of recipe '${data.name}' is not a valid semver version`); | 80 | throw new Error(`Version ${data.version} of recipe '${data.name}' is not a valid semver version`); |
81 | } | 81 | } |
82 | 82 | ||
83 | this.id = data.id || this.id; | 83 | this.id = data.id || this.id; |
diff --git a/src/models/RecipePreview.ts b/src/models/RecipePreview.ts index 4d2cc8450..fb8cb3e3e 100644 --- a/src/models/RecipePreview.ts +++ b/src/models/RecipePreview.ts | |||
@@ -1,5 +1,3 @@ | |||
1 | // @flow | ||
2 | |||
3 | interface IRecipePreview { | 1 | interface IRecipePreview { |
4 | id: string; | 2 | id: string; |
5 | name: string; | 3 | name: string; |
@@ -21,11 +19,11 @@ export default class RecipePreview { | |||
21 | 19 | ||
22 | constructor(data: IRecipePreview) { | 20 | constructor(data: IRecipePreview) { |
23 | if (!data) { | 21 | if (!data) { |
24 | throw Error('RecipePreview config not valid'); | 22 | throw new Error('RecipePreview config not valid'); |
25 | } | 23 | } |
26 | 24 | ||
27 | if (!data.id) { | 25 | if (!data.id) { |
28 | throw Error(`RecipePreview '${data.name}' requires Id`); | 26 | throw new Error(`RecipePreview '${data.name}' requires Id`); |
29 | } | 27 | } |
30 | 28 | ||
31 | Object.assign(this, data); | 29 | Object.assign(this, data); |
diff --git a/src/models/Service.js b/src/models/Service.js index 4ee054b2b..cc001f98d 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -8,7 +8,11 @@ import { todosStore } from '../features/todos'; | |||
8 | import { isValidExternalURL } from '../helpers/url-helpers'; | 8 | import { isValidExternalURL } from '../helpers/url-helpers'; |
9 | import UserAgent from './UserAgent'; | 9 | import UserAgent from './UserAgent'; |
10 | import { DEFAULT_SERVICE_ORDER } from '../config'; | 10 | import { DEFAULT_SERVICE_ORDER } from '../config'; |
11 | import { ifUndefinedString, ifUndefinedBoolean, ifUndefinedNumber } from '../jsUtils'; | 11 | import { |
12 | ifUndefinedString, | ||
13 | ifUndefinedBoolean, | ||
14 | ifUndefinedNumber, | ||
15 | } from '../jsUtils'; | ||
12 | 16 | ||
13 | const debug = require('debug')('Ferdi:Service'); | 17 | const debug = require('debug')('Ferdi:Service'); |
14 | 18 | ||
@@ -95,11 +99,11 @@ export default class Service { | |||
95 | 99 | ||
96 | constructor(data, recipe) { | 100 | constructor(data, recipe) { |
97 | if (!data) { | 101 | if (!data) { |
98 | throw Error('Service config not valid'); | 102 | throw new Error('Service config not valid'); |
99 | } | 103 | } |
100 | 104 | ||
101 | if (!recipe) { | 105 | if (!recipe) { |
102 | throw Error('Service recipe not valid'); | 106 | throw new Error('Service recipe not valid'); |
103 | } | 107 | } |
104 | 108 | ||
105 | this.recipe = recipe; | 109 | this.recipe = recipe; |
@@ -115,22 +119,51 @@ export default class Service { | |||
115 | 119 | ||
116 | this.order = ifUndefinedNumber(data.order, this.order); | 120 | this.order = ifUndefinedNumber(data.order, this.order); |
117 | this.isEnabled = ifUndefinedBoolean(data.isEnabled, this.isEnabled); | 121 | this.isEnabled = ifUndefinedBoolean(data.isEnabled, this.isEnabled); |
118 | this.isNotificationEnabled = ifUndefinedBoolean(data.isNotificationEnabled, this.isNotificationEnabled); | 122 | this.isNotificationEnabled = ifUndefinedBoolean( |
119 | this.isBadgeEnabled = ifUndefinedBoolean(data.isBadgeEnabled, this.isBadgeEnabled); | 123 | data.isNotificationEnabled, |
120 | this.isIndirectMessageBadgeEnabled = ifUndefinedBoolean(data.isIndirectMessageBadgeEnabled, this.isIndirectMessageBadgeEnabled); | 124 | this.isNotificationEnabled, |
125 | ); | ||
126 | this.isBadgeEnabled = ifUndefinedBoolean( | ||
127 | data.isBadgeEnabled, | ||
128 | this.isBadgeEnabled, | ||
129 | ); | ||
130 | this.isIndirectMessageBadgeEnabled = ifUndefinedBoolean( | ||
131 | data.isIndirectMessageBadgeEnabled, | ||
132 | this.isIndirectMessageBadgeEnabled, | ||
133 | ); | ||
121 | this.isMuted = ifUndefinedBoolean(data.isMuted, this.isMuted); | 134 | this.isMuted = ifUndefinedBoolean(data.isMuted, this.isMuted); |
122 | this.isDarkModeEnabled = ifUndefinedBoolean(data.isDarkModeEnabled, this.isDarkModeEnabled); | 135 | this.isDarkModeEnabled = ifUndefinedBoolean( |
123 | this.darkReaderSettings = ifUndefinedString(data.darkReaderSettings, this.darkReaderSettings); | 136 | data.isDarkModeEnabled, |
124 | this.hasCustomUploadedIcon = ifUndefinedBoolean(data.hasCustomIcon, this.hasCustomUploadedIcon); | 137 | this.isDarkModeEnabled, |
138 | ); | ||
139 | this.darkReaderSettings = ifUndefinedString( | ||
140 | data.darkReaderSettings, | ||
141 | this.darkReaderSettings, | ||
142 | ); | ||
143 | this.hasCustomUploadedIcon = ifUndefinedBoolean( | ||
144 | data.hasCustomIcon, | ||
145 | this.hasCustomUploadedIcon, | ||
146 | ); | ||
125 | this.proxy = ifUndefinedString(data.proxy, this.proxy); | 147 | this.proxy = ifUndefinedString(data.proxy, this.proxy); |
126 | this.spellcheckerLanguage = ifUndefinedString(data.spellcheckerLanguage, this.spellcheckerLanguage); | 148 | this.spellcheckerLanguage = ifUndefinedString( |
127 | this.userAgentPref = ifUndefinedString(data.userAgentPref, this.userAgentPref); | 149 | data.spellcheckerLanguage, |
128 | this.isHibernationEnabled = ifUndefinedBoolean(data.isHibernationEnabled, this.isHibernationEnabled); | 150 | this.spellcheckerLanguage, |
151 | ); | ||
152 | this.userAgentPref = ifUndefinedString( | ||
153 | data.userAgentPref, | ||
154 | this.userAgentPref, | ||
155 | ); | ||
156 | this.isHibernationEnabled = ifUndefinedBoolean( | ||
157 | data.isHibernationEnabled, | ||
158 | this.isHibernationEnabled, | ||
159 | ); | ||
129 | 160 | ||
130 | // Check if "Hibernate on Startup" is enabled and hibernate all services except active one | 161 | // Check if "Hibernate on Startup" is enabled and hibernate all services except active one |
131 | const { hibernateOnStartup } = window.ferdi.stores.settings.app; | 162 | const { hibernateOnStartup } = window.ferdi.stores.settings.app; |
132 | // The service store is probably not loaded yet so we need to use localStorage data to get active service | 163 | // The service store is probably not loaded yet so we need to use localStorage data to get active service |
133 | const isActive = window.localStorage.service && JSON.parse(window.localStorage.service).activeService === this.id; | 164 | const isActive = |
165 | window.localStorage.service && | ||
166 | JSON.parse(window.localStorage.service).activeService === this.id; | ||
134 | if (hibernateOnStartup && !isActive) { | 167 | if (hibernateOnStartup && !isActive) { |
135 | this.isHibernationRequested = true; | 168 | this.isHibernationRequested = true; |
136 | } | 169 | } |
@@ -189,9 +222,14 @@ export default class Service { | |||
189 | if (this.recipe.hasCustomUrl && this.customUrl) { | 222 | if (this.recipe.hasCustomUrl && this.customUrl) { |
190 | let url; | 223 | let url; |
191 | try { | 224 | try { |
192 | url = normalizeUrl(this.customUrl, { stripWWW: false, removeTrailingSlash: false }); | 225 | url = normalizeUrl(this.customUrl, { |
193 | } catch (err) { | 226 | stripWWW: false, |
194 | console.error(`Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`); | 227 | removeTrailingSlash: false, |
228 | }); | ||
229 | } catch { | ||
230 | console.error( | ||
231 | `Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`, | ||
232 | ); | ||
195 | } | 233 | } |
196 | 234 | ||
197 | if (typeof this.recipe.buildUrl === 'function') { | 235 | if (typeof this.recipe.buildUrl === 'function') { |
@@ -241,7 +279,9 @@ export default class Service { | |||
241 | } | 279 | } |
242 | 280 | ||
243 | initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { | 281 | initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { |
244 | const webviewWebContents = webContents.fromId(this.webview.getWebContentsId()); | 282 | const webviewWebContents = webContents.fromId( |
283 | this.webview.getWebContentsId(), | ||
284 | ); | ||
245 | 285 | ||
246 | this.userAgentModel.setWebviewReference(this.webview); | 286 | this.userAgentModel.setWebviewReference(this.webview); |
247 | 287 | ||
@@ -270,9 +310,15 @@ export default class Service { | |||
270 | debug(this.name, 'knownCertificateHosts is not defined in the recipe'); | 310 | debug(this.name, 'knownCertificateHosts is not defined in the recipe'); |
271 | } | 311 | } |
272 | 312 | ||
273 | this.webview.addEventListener('ipc-message', async (e) => { | 313 | this.webview.addEventListener('ipc-message', async e => { |
274 | if (e.channel === 'inject-js-unsafe') { | 314 | if (e.channel === 'inject-js-unsafe') { |
275 | await Promise.all(e.args.map((script) => this.webview.executeJavaScript(`"use strict"; (() => { ${script} })();`))); | 315 | await Promise.all( |
316 | e.args.map(script => | ||
317 | this.webview.executeJavaScript( | ||
318 | `"use strict"; (() => { ${script} })();`, | ||
319 | ), | ||
320 | ), | ||
321 | ); | ||
276 | } else { | 322 | } else { |
277 | handleIPCMessage({ | 323 | handleIPCMessage({ |
278 | serviceId: this.id, | 324 | serviceId: this.id, |
@@ -282,27 +328,33 @@ export default class Service { | |||
282 | } | 328 | } |
283 | }); | 329 | }); |
284 | 330 | ||
285 | this.webview.addEventListener('new-window', (event, url, frameName, options) => { | 331 | this.webview.addEventListener( |
286 | debug('new-window', event, url, frameName, options); | 332 | 'new-window', |
287 | if (!isValidExternalURL(event.url)) { | 333 | (event, url, frameName, options) => { |
288 | return; | 334 | debug('new-window', event, url, frameName, options); |
289 | } | 335 | if (!isValidExternalURL(event.url)) { |
290 | if (event.disposition === 'foreground-tab' || event.disposition === 'background-tab') { | 336 | return; |
291 | openWindow({ | 337 | } |
292 | event, | 338 | if ( |
293 | url, | 339 | event.disposition === 'foreground-tab' || |
294 | frameName, | 340 | event.disposition === 'background-tab' |
295 | options, | 341 | ) { |
296 | }); | 342 | openWindow({ |
297 | } else { | 343 | event, |
298 | ipcRenderer.send('open-browser-window', { | 344 | url, |
299 | url: event.url, | 345 | frameName, |
300 | serviceId: this.id, | 346 | options, |
301 | }); | 347 | }); |
302 | } | 348 | } else { |
303 | }); | 349 | ipcRenderer.send('open-browser-window', { |
350 | url: event.url, | ||
351 | serviceId: this.id, | ||
352 | }); | ||
353 | } | ||
354 | }, | ||
355 | ); | ||
304 | 356 | ||
305 | this.webview.addEventListener('did-start-loading', (event) => { | 357 | this.webview.addEventListener('did-start-loading', event => { |
306 | debug('Did start load', this.name, event); | 358 | debug('Did start load', this.name, event); |
307 | 359 | ||
308 | this.hasCrashed = false; | 360 | this.hasCrashed = false; |
@@ -321,9 +373,13 @@ export default class Service { | |||
321 | this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this)); | 373 | this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this)); |
322 | this.webview.addEventListener('did-navigate', didLoad.bind(this)); | 374 | this.webview.addEventListener('did-navigate', didLoad.bind(this)); |
323 | 375 | ||
324 | this.webview.addEventListener('did-fail-load', (event) => { | 376 | this.webview.addEventListener('did-fail-load', event => { |
325 | debug('Service failed to load', this.name, event); | 377 | debug('Service failed to load', this.name, event); |
326 | if (event.isMainFrame && event.errorCode !== -21 && event.errorCode !== -3) { | 378 | if ( |
379 | event.isMainFrame && | ||
380 | event.errorCode !== -21 && | ||
381 | event.errorCode !== -3 | ||
382 | ) { | ||
327 | this.isError = true; | 383 | this.isError = true; |
328 | this.errorMessage = event.errorDescription; | 384 | this.errorMessage = event.errorDescription; |
329 | this.isLoading = false; | 385 | this.isLoading = false; |
@@ -365,12 +421,12 @@ export default class Service { | |||
365 | 421 | ||
366 | initializeWebViewListener() { | 422 | initializeWebViewListener() { |
367 | if (this.webview && this.recipe.events) { | 423 | if (this.webview && this.recipe.events) { |
368 | Object.keys(this.recipe.events).forEach((eventName) => { | 424 | for (const eventName of Object.keys(this.recipe.events)) { |
369 | const eventHandler = this.recipe[this.recipe.events[eventName]]; | 425 | const eventHandler = this.recipe[this.recipe.events[eventName]]; |
370 | if (typeof eventHandler === 'function') { | 426 | if (typeof eventHandler === 'function') { |
371 | this.webview.addEventListener(eventName, eventHandler); | 427 | this.webview.addEventListener(eventName, eventHandler); |
372 | } | 428 | } |
373 | }); | 429 | } |
374 | } | 430 | } |
375 | } | 431 | } |
376 | 432 | ||
diff --git a/src/models/User.ts b/src/models/User.ts index 54a6838df..a04d46d3c 100644 --- a/src/models/User.ts +++ b/src/models/User.ts | |||
@@ -43,11 +43,11 @@ export default class User { | |||
43 | 43 | ||
44 | constructor(data: IUser) { | 44 | constructor(data: IUser) { |
45 | if (!data) { | 45 | if (!data) { |
46 | throw Error('User config not valid'); | 46 | throw new Error('User config not valid'); |
47 | } | 47 | } |
48 | 48 | ||
49 | if (!data.id) { | 49 | if (!data.id) { |
50 | throw Error('User requires Id'); | 50 | throw new Error('User requires Id'); |
51 | } | 51 | } |
52 | 52 | ||
53 | this.id = data.id; | 53 | this.id = data.id; |
diff --git a/src/models/UserAgent.js b/src/models/UserAgent.js index 930ae19ef..8ec274aa5 100644 --- a/src/models/UserAgent.js +++ b/src/models/UserAgent.js | |||
@@ -1,9 +1,4 @@ | |||
1 | import { | 1 | import { action, computed, observe, observable } from 'mobx'; |
2 | action, | ||
3 | computed, | ||
4 | observe, | ||
5 | observable, | ||
6 | } from 'mobx'; | ||
7 | 2 | ||
8 | import defaultUserAgent from '../helpers/userAgent-helpers'; | 3 | import defaultUserAgent from '../helpers/userAgent-helpers'; |
9 | 4 | ||
@@ -27,7 +22,7 @@ export default class UserAgent { | |||
27 | this.getUserAgent = overrideUserAgent; | 22 | this.getUserAgent = overrideUserAgent; |
28 | } | 23 | } |
29 | 24 | ||
30 | observe(this, 'webview', (change) => { | 25 | observe(this, 'webview', change => { |
31 | const { oldValue, newValue } = change; | 26 | const { oldValue, newValue } = change; |
32 | if (oldValue !== null) { | 27 | if (oldValue !== null) { |
33 | this._removeWebviewEvents(oldValue); | 28 | this._removeWebviewEvents(oldValue); |
@@ -64,11 +59,13 @@ export default class UserAgent { | |||
64 | 59 | ||
65 | @computed get userAgentWithoutChromeVersion() { | 60 | @computed get userAgentWithoutChromeVersion() { |
66 | const withChrome = this.userAgentWithChromeVersion; | 61 | const withChrome = this.userAgentWithChromeVersion; |
67 | return withChrome.replace(/Chrome\/[0-9.]+/, 'Chrome'); | 62 | return withChrome.replace(/Chrome\/[\d.]+/, 'Chrome'); |
68 | } | 63 | } |
69 | 64 | ||
70 | @computed get userAgent() { | 65 | @computed get userAgent() { |
71 | return this.chromelessUserAgent ? this.userAgentWithoutChromeVersion : this.userAgentWithChromeVersion; | 66 | return this.chromelessUserAgent |
67 | ? this.userAgentWithoutChromeVersion | ||
68 | : this.userAgentWithChromeVersion; | ||
72 | } | 69 | } |
73 | 70 | ||
74 | @action setWebviewReference(webview) { | 71 | @action setWebviewReference(webview) { |
@@ -95,10 +92,10 @@ export default class UserAgent { | |||
95 | _addWebviewEvents(webview) { | 92 | _addWebviewEvents(webview) { |
96 | debug('Adding event handlers'); | 93 | debug('Adding event handlers'); |
97 | 94 | ||
98 | this._willNavigateListener = (event) => this._handleNavigate(event.url, true); | 95 | this._willNavigateListener = event => this._handleNavigate(event.url, true); |
99 | webview.addEventListener('will-navigate', this._willNavigateListener); | 96 | webview.addEventListener('will-navigate', this._willNavigateListener); |
100 | 97 | ||
101 | this._didNavigateListener = (event) => this._handleNavigate(event.url); | 98 | this._didNavigateListener = event => this._handleNavigate(event.url); |
102 | webview.addEventListener('did-navigate', this._didNavigateListener); | 99 | webview.addEventListener('did-navigate', this._didNavigateListener); |
103 | } | 100 | } |
104 | 101 | ||
diff --git a/src/prop-types.ts b/src/prop-types.ts index 459b9a7b9..07607f105 100644 --- a/src/prop-types.ts +++ b/src/prop-types.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import PropTypes from 'prop-types'; | 1 | import PropTypes from 'prop-types'; |
2 | 2 | ||
3 | // eslint-disable-next-line | ||
4 | export const oneOrManyChildElements = PropTypes.oneOfType([ | 3 | export const oneOrManyChildElements = PropTypes.oneOfType([ |
5 | PropTypes.arrayOf(PropTypes.element), | 4 | PropTypes.arrayOf(PropTypes.element), |
6 | PropTypes.element, | 5 | PropTypes.element, |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 469e7519e..85f74a91e 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -251,16 +251,14 @@ export default class AppStore extends Store { | |||
251 | // macOS catalina notifications hack | 251 | // macOS catalina notifications hack |
252 | // notifications got stuck after upgrade but forcing a notification | 252 | // notifications got stuck after upgrade but forcing a notification |
253 | // via `new Notification` triggered the permission request | 253 | // via `new Notification` triggered the permission request |
254 | if (isMac) { | 254 | if (isMac && !localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { |
255 | if (!localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { | 255 | debug('Triggering macOS Catalina notification permission trigger'); |
256 | debug('Triggering macOS Catalina notification permission trigger'); | 256 | // eslint-disable-next-line no-new |
257 | // eslint-disable-next-line no-new | 257 | new window.Notification('Welcome to Ferdi 5', { |
258 | new window.Notification('Welcome to Ferdi 5', { | 258 | body: 'Have a wonderful day & happy messaging.', |
259 | body: 'Have a wonderful day & happy messaging.', | 259 | }); |
260 | }); | ||
261 | 260 | ||
262 | localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); | 261 | localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); |
263 | } | ||
264 | } | 262 | } |
265 | } | 263 | } |
266 | 264 | ||
@@ -325,7 +323,7 @@ export default class AppStore extends Store { | |||
325 | 323 | ||
326 | debug('New notification', title, options); | 324 | debug('New notification', title, options); |
327 | 325 | ||
328 | notification.onclick = () => { | 326 | notification.addEventListener('click', () => { |
329 | if (serviceId) { | 327 | if (serviceId) { |
330 | this.actions.service.sendIPCMessage({ | 328 | this.actions.service.sendIPCMessage({ |
331 | channel: `notification-onclick:${notificationId}`, | 329 | channel: `notification-onclick:${notificationId}`, |
@@ -346,7 +344,7 @@ export default class AppStore extends Store { | |||
346 | 344 | ||
347 | debug('Notification click handler'); | 345 | debug('Notification click handler'); |
348 | } | 346 | } |
349 | }; | 347 | }); |
350 | } | 348 | } |
351 | 349 | ||
352 | @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) { | 350 | @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) { |
@@ -360,7 +358,7 @@ export default class AppStore extends Store { | |||
360 | ) { | 358 | ) { |
361 | indicator = 0; | 359 | indicator = 0; |
362 | } else { | 360 | } else { |
363 | indicator = parseInt(indicator, 10); | 361 | indicator = Number.parseInt(indicator, 10); |
364 | } | 362 | } |
365 | 363 | ||
366 | ipcRenderer.send('updateAppIndicator', { | 364 | ipcRenderer.send('updateAppIndicator', { |
@@ -379,8 +377,8 @@ export default class AppStore extends Store { | |||
379 | debug('disabling launch on startup'); | 377 | debug('disabling launch on startup'); |
380 | autoLauncher.disable(); | 378 | autoLauncher.disable(); |
381 | } | 379 | } |
382 | } catch (err) { | 380 | } catch (error) { |
383 | console.warn(err); | 381 | console.warn(error); |
384 | } | 382 | } |
385 | } | 383 | } |
386 | 384 | ||
@@ -438,7 +436,7 @@ export default class AppStore extends Store { | |||
438 | const allServiceIds = await getServiceIdsFromPartitions(); | 436 | const allServiceIds = await getServiceIdsFromPartitions(); |
439 | const allOrphanedServiceIds = allServiceIds.filter( | 437 | const allOrphanedServiceIds = allServiceIds.filter( |
440 | id => | 438 | id => |
441 | !this.stores.services.all.find( | 439 | !this.stores.services.all.some( |
442 | s => id.replace('service-', '') === s.id, | 440 | s => id.replace('service-', '') === s.id, |
443 | ), | 441 | ), |
444 | ); | 442 | ); |
@@ -447,8 +445,8 @@ export default class AppStore extends Store { | |||
447 | await Promise.all( | 445 | await Promise.all( |
448 | allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)), | 446 | allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)), |
449 | ); | 447 | ); |
450 | } catch (ex) { | 448 | } catch (error) { |
451 | console.log('Error while deleting service partition directory - ', ex); | 449 | console.log('Error while deleting service partition directory -', error); |
452 | } | 450 | } |
453 | await Promise.all( | 451 | await Promise.all( |
454 | this.stores.services.all.map(s => | 452 | this.stores.services.all.map(s => |
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 1d50dd714..8e0134d7f 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js | |||
@@ -51,7 +51,9 @@ export default class FeaturesStore extends Store { | |||
51 | let requestResult = {}; | 51 | let requestResult = {}; |
52 | try { | 52 | try { |
53 | requestResult = this.featuresRequest.execute().result; | 53 | requestResult = this.featuresRequest.execute().result; |
54 | } catch (e) {} // eslint-disable-line no-empty | 54 | } catch (error) { |
55 | console.error(error); | ||
56 | } | ||
55 | Object.assign(features, requestResult); | 57 | Object.assign(features, requestResult); |
56 | } | 58 | } |
57 | runInAction('FeaturesStore::_updateFeatures', () => { | 59 | runInAction('FeaturesStore::_updateFeatures', () => { |
@@ -69,15 +71,15 @@ export default class FeaturesStore extends Store { | |||
69 | } | 71 | } |
70 | 72 | ||
71 | _setupFeatures() { | 73 | _setupFeatures() { |
72 | serviceProxy(this.stores, this.actions); | 74 | serviceProxy(this.stores); |
73 | basicAuth(this.stores, this.actions); | 75 | basicAuth(); |
74 | workspaces(this.stores, this.actions); | 76 | workspaces(this.stores, this.actions); |
75 | quickSwitch(this.stores, this.actions); | 77 | quickSwitch(); |
76 | nightlyBuilds(this.stores, this.actions); | 78 | nightlyBuilds(); |
77 | publishDebugInfo(this.stores, this.actions); | 79 | publishDebugInfo(); |
78 | settingsWS(this.stores, this.actions); | 80 | settingsWS(this.stores, this.actions); |
79 | communityRecipes(this.stores, this.actions); | 81 | communityRecipes(this.stores, this.actions); |
80 | todos(this.stores, this.actions); | 82 | todos(this.stores, this.actions); |
81 | appearance(this.stores, this.actions); | 83 | appearance(this.stores); |
82 | } | 84 | } |
83 | } | 85 | } |
diff --git a/src/stores/GlobalErrorStore.js b/src/stores/GlobalErrorStore.js index aacaa247f..7cbfdc608 100644 --- a/src/stores/GlobalErrorStore.js +++ b/src/stores/GlobalErrorStore.js | |||
@@ -12,9 +12,9 @@ export default class GlobalErrorStore extends Store { | |||
12 | constructor(...args) { | 12 | constructor(...args) { |
13 | super(...args); | 13 | super(...args); |
14 | 14 | ||
15 | window.onerror = (...errorArgs) => { | 15 | window.addEventListener('error', (...errorArgs) => { |
16 | this._handleConsoleError.call(this, ['error', ...errorArgs]); | 16 | this._handleConsoleError.call(this, ['error', ...errorArgs]); |
17 | }; | 17 | }); |
18 | 18 | ||
19 | const origConsoleError = console.error; | 19 | const origConsoleError = console.error; |
20 | window.console.error = (...errorArgs) => { | 20 | window.console.error = (...errorArgs) => { |
@@ -38,7 +38,7 @@ export default class GlobalErrorStore extends Store { | |||
38 | } | 38 | } |
39 | 39 | ||
40 | _handleConsoleError(type, error, url, line) { | 40 | _handleConsoleError(type, error, url, line) { |
41 | if (typeof type === 'object' && type.length && type.length >= 1) { | 41 | if (typeof type === 'object' && type.length > 0) { |
42 | this.messages.push({ | 42 | this.messages.push({ |
43 | type: type[0], | 43 | type: type[0], |
44 | info: type, | 44 | info: type, |
@@ -53,14 +53,14 @@ export default class GlobalErrorStore extends Store { | |||
53 | } | 53 | } |
54 | } | 54 | } |
55 | 55 | ||
56 | _handleRequests = action(async (request) => { | 56 | _handleRequests = action(async request => { |
57 | if (request.isError) { | 57 | if (request.isError) { |
58 | this.error = request.error; | 58 | this.error = request.error; |
59 | 59 | ||
60 | if (request.error.json) { | 60 | if (request.error.json) { |
61 | try { | 61 | try { |
62 | this.response = await request.error.json(); | 62 | this.response = await request.error.json(); |
63 | } catch (error) { | 63 | } catch { |
64 | this.response = {}; | 64 | this.response = {}; |
65 | } | 65 | } |
66 | if (this.error.status === 401) { | 66 | if (this.error.status === 401) { |
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js index d2acebb75..bfbdc57a8 100644 --- a/src/stores/RecipesStore.js +++ b/src/stores/RecipesStore.js | |||
@@ -25,9 +25,7 @@ export default class RecipesStore extends Store { | |||
25 | this.actions.recipe.update.listen(this._update.bind(this)); | 25 | this.actions.recipe.update.listen(this._update.bind(this)); |
26 | 26 | ||
27 | // Reactions | 27 | // Reactions |
28 | this.registerReactions([ | 28 | this.registerReactions([this._checkIfRecipeIsInstalled.bind(this)]); |
29 | this._checkIfRecipeIsInstalled.bind(this), | ||
30 | ]); | ||
31 | } | 29 | } |
32 | 30 | ||
33 | setup() { | 31 | setup() { |
@@ -39,7 +37,10 @@ export default class RecipesStore extends Store { | |||
39 | } | 37 | } |
40 | 38 | ||
41 | @computed get active() { | 39 | @computed get active() { |
42 | const match = matchRoute('/settings/services/add/:id', this.stores.router.location.pathname); | 40 | const match = matchRoute( |
41 | '/settings/services/add/:id', | ||
42 | this.stores.router.location.pathname, | ||
43 | ); | ||
43 | if (match) { | 44 | if (match) { |
44 | const activeRecipe = this.one(match.id); | 45 | const activeRecipe = this.one(match.id); |
45 | if (activeRecipe) { | 46 | if (activeRecipe) { |
@@ -53,11 +54,11 @@ export default class RecipesStore extends Store { | |||
53 | } | 54 | } |
54 | 55 | ||
55 | @computed get recipeIdForServices() { | 56 | @computed get recipeIdForServices() { |
56 | return this.stores.services.all.map((s) => s.recipe.id); | 57 | return this.stores.services.all.map(s => s.recipe.id); |
57 | } | 58 | } |
58 | 59 | ||
59 | one(id) { | 60 | one(id) { |
60 | return this.all.find((recipe) => recipe.id === id); | 61 | return this.all.find(recipe => recipe.id === id); |
61 | } | 62 | } |
62 | 63 | ||
63 | isInstalled(id) { | 64 | isInstalled(id) { |
@@ -77,41 +78,43 @@ export default class RecipesStore extends Store { | |||
77 | const recipes = {}; | 78 | const recipes = {}; |
78 | 79 | ||
79 | // Hackfix, reference this.all to fetch services | 80 | // Hackfix, reference this.all to fetch services |
80 | debug(`Check Recipe updates for ${this.all.map((recipe) => recipe.id)}`); | 81 | debug(`Check Recipe updates for ${this.all.map(recipe => recipe.id)}`); |
81 | 82 | ||
82 | recipeIds.forEach((r) => { | 83 | for (const r of recipeIds) { |
83 | const recipe = this.one(r); | 84 | const recipe = this.one(r); |
84 | recipes[r] = recipe.version; | 85 | recipes[r] = recipe.version; |
85 | }); | 86 | } |
86 | 87 | ||
87 | if (Object.keys(recipes).length === 0) return; | 88 | if (Object.keys(recipes).length === 0) return; |
88 | 89 | ||
89 | const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes)._promise; | 90 | const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes) |
91 | ._promise; | ||
90 | 92 | ||
91 | // Check for local updates | 93 | // Check for local updates |
92 | const allJsonFile = asarRecipesPath('all.json'); | 94 | const allJsonFile = asarRecipesPath('all.json'); |
93 | const allJson = readJSONSync(allJsonFile); | 95 | const allJson = readJSONSync(allJsonFile); |
94 | const localUpdates = []; | 96 | const localUpdates = []; |
95 | 97 | ||
96 | Object.keys(recipes).forEach((recipe) => { | 98 | for (const recipe of Object.keys(recipes)) { |
97 | const version = recipes[recipe]; | 99 | const version = recipes[recipe]; |
98 | 100 | ||
99 | // Find recipe in local recipe repository | 101 | // Find recipe in local recipe repository |
100 | const localRecipe = allJson.find((r) => r.id === recipe); | 102 | const localRecipe = allJson.find(r => r.id === recipe); |
101 | 103 | ||
102 | if (localRecipe && semver.lt(version, localRecipe.version)) { | 104 | if (localRecipe && semver.lt(version, localRecipe.version)) { |
103 | localUpdates.push(recipe); | 105 | localUpdates.push(recipe); |
104 | } | 106 | } |
105 | }); | 107 | } |
106 | 108 | ||
107 | const updates = [ | 109 | const updates = [...remoteUpdates, ...localUpdates]; |
108 | ...remoteUpdates, | 110 | debug( |
109 | ...localUpdates, | 111 | 'Got update information (local, remote):', |
110 | ]; | 112 | localUpdates, |
111 | debug('Got update information (local, remote):', localUpdates, remoteUpdates); | 113 | remoteUpdates, |
114 | ); | ||
112 | 115 | ||
113 | const length = updates.length - 1; | 116 | const length = updates.length - 1; |
114 | const syncUpdate = async (i) => { | 117 | const syncUpdate = async i => { |
115 | const update = updates[i]; | 118 | const update = updates[i]; |
116 | 119 | ||
117 | this.actions.recipe.install({ recipeId: update }); | 120 | this.actions.recipe.install({ recipeId: update }); |
@@ -134,7 +137,9 @@ export default class RecipesStore extends Store { | |||
134 | async _checkIfRecipeIsInstalled() { | 137 | async _checkIfRecipeIsInstalled() { |
135 | const { router } = this.stores; | 138 | const { router } = this.stores; |
136 | 139 | ||
137 | const match = router.location && matchRoute('/settings/services/add/:id', router.location.pathname); | 140 | const match = |
141 | router.location && | ||
142 | matchRoute('/settings/services/add/:id', router.location.pathname); | ||
138 | if (match) { | 143 | if (match) { |
139 | const recipeId = match.id; | 144 | const recipeId = match.id; |
140 | 145 | ||
@@ -142,9 +147,11 @@ export default class RecipesStore extends Store { | |||
142 | router.push('/settings/recipes'); | 147 | router.push('/settings/recipes'); |
143 | debug(`Recipe ${recipeId} is not installed, trying to install it`); | 148 | debug(`Recipe ${recipeId} is not installed, trying to install it`); |
144 | 149 | ||
145 | const recipe = await this.installRecipeRequest.execute(recipeId)._promise; | 150 | const recipe = await this.installRecipeRequest.execute(recipeId) |
151 | ._promise; | ||
146 | if (recipe) { | 152 | if (recipe) { |
147 | await this.allRecipesRequest.invalidate({ immediately: true })._promise; | 153 | await this.allRecipesRequest.invalidate({ immediately: true }) |
154 | ._promise; | ||
148 | router.push(`/settings/services/add/${recipeId}`); | 155 | router.push(`/settings/services/add/${recipeId}`); |
149 | } else { | 156 | } else { |
150 | router.push('/settings/recipes'); | 157 | router.push('/settings/recipes'); |
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js index a92f4c685..6d2f2ef91 100644 --- a/src/stores/RequestStore.js +++ b/src/stores/RequestStore.js | |||
@@ -13,7 +13,7 @@ export default class RequestStore extends Store { | |||
13 | 13 | ||
14 | @observable showRequiredRequestsError = false; | 14 | @observable showRequiredRequestsError = false; |
15 | 15 | ||
16 | @observable localServerPort = 45569; | 16 | @observable localServerPort = 45_569; |
17 | 17 | ||
18 | retries = 0; | 18 | retries = 0; |
19 | 19 | ||
@@ -22,11 +22,11 @@ export default class RequestStore extends Store { | |||
22 | constructor(...args) { | 22 | constructor(...args) { |
23 | super(...args); | 23 | super(...args); |
24 | 24 | ||
25 | this.actions.requests.retryRequiredRequests.listen(this._retryRequiredRequests.bind(this)); | 25 | this.actions.requests.retryRequiredRequests.listen( |
26 | this._retryRequiredRequests.bind(this), | ||
27 | ); | ||
26 | 28 | ||
27 | this.registerReactions([ | 29 | this.registerReactions([this._autoRetry.bind(this)]); |
28 | this._autoRetry.bind(this), | ||
29 | ]); | ||
30 | } | 30 | } |
31 | 31 | ||
32 | setup() { | 32 | setup() { |
@@ -41,13 +41,11 @@ export default class RequestStore extends Store { | |||
41 | } | 41 | } |
42 | 42 | ||
43 | @computed get areRequiredRequestsSuccessful() { | 43 | @computed get areRequiredRequestsSuccessful() { |
44 | return !this.userInfoRequest.isError | 44 | return !this.userInfoRequest.isError && !this.servicesRequest.isError; |
45 | && !this.servicesRequest.isError; | ||
46 | } | 45 | } |
47 | 46 | ||
48 | @computed get areRequiredRequestsLoading() { | 47 | @computed get areRequiredRequestsLoading() { |
49 | return this.userInfoRequest.isExecuting | 48 | return this.userInfoRequest.isExecuting || this.servicesRequest.isExecuting; |
50 | || this.servicesRequest.isExecuting; | ||
51 | } | 49 | } |
52 | 50 | ||
53 | @action _retryRequiredRequests() { | 51 | @action _retryRequiredRequests() { |
@@ -67,7 +65,7 @@ export default class RequestStore extends Store { | |||
67 | } | 65 | } |
68 | 66 | ||
69 | this._autoRetry(); | 67 | this._autoRetry(); |
70 | debug(`Retry required requests delayed in ${(delay) / 1000}s`); | 68 | debug(`Retry required requests delayed in ${delay / 1000}s`); |
71 | }, delay); | 69 | }, delay); |
72 | } | 70 | } |
73 | } | 71 | } |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 75bc71388..67fd4103f 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -10,7 +10,10 @@ import Request from './lib/Request'; | |||
10 | import CachedRequest from './lib/CachedRequest'; | 10 | import CachedRequest from './lib/CachedRequest'; |
11 | import { matchRoute } from '../helpers/routing-helpers'; | 11 | import { matchRoute } from '../helpers/routing-helpers'; |
12 | import { isInTimeframe } from '../helpers/schedule-helpers'; | 12 | import { isInTimeframe } from '../helpers/schedule-helpers'; |
13 | import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; | 13 | import { |
14 | getRecipeDirectory, | ||
15 | getDevRecipeDirectory, | ||
16 | } from '../helpers/recipe-helpers'; | ||
14 | import { workspaceStore } from '../features/workspaces'; | 17 | import { workspaceStore } from '../features/workspaces'; |
15 | import { KEEP_WS_LOADED_USID } from '../config'; | 18 | import { KEEP_WS_LOADED_USID } from '../config'; |
16 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 19 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
@@ -125,63 +128,49 @@ export default class ServicesStore extends Store { | |||
125 | setup() { | 128 | setup() { |
126 | // Single key reactions for the sake of your CPU | 129 | // Single key reactions for the sake of your CPU |
127 | reaction( | 130 | reaction( |
128 | () => ( | 131 | () => this.stores.settings.app.enableSpellchecking, |
129 | this.stores.settings.app.enableSpellchecking | ||
130 | ), | ||
131 | () => { | 132 | () => { |
132 | this._shareSettingsWithServiceProcess(); | 133 | this._shareSettingsWithServiceProcess(); |
133 | }, | 134 | }, |
134 | ); | 135 | ); |
135 | 136 | ||
136 | reaction( | 137 | reaction( |
137 | () => ( | 138 | () => this.stores.settings.app.spellcheckerLanguage, |
138 | this.stores.settings.app.spellcheckerLanguage | ||
139 | ), | ||
140 | () => { | 139 | () => { |
141 | this._shareSettingsWithServiceProcess(); | 140 | this._shareSettingsWithServiceProcess(); |
142 | }, | 141 | }, |
143 | ); | 142 | ); |
144 | 143 | ||
145 | reaction( | 144 | reaction( |
146 | () => ( | 145 | () => this.stores.settings.app.darkMode, |
147 | this.stores.settings.app.darkMode | ||
148 | ), | ||
149 | () => { | 146 | () => { |
150 | this._shareSettingsWithServiceProcess(); | 147 | this._shareSettingsWithServiceProcess(); |
151 | }, | 148 | }, |
152 | ); | 149 | ); |
153 | 150 | ||
154 | reaction( | 151 | reaction( |
155 | () => ( | 152 | () => this.stores.settings.app.adaptableDarkMode, |
156 | this.stores.settings.app.adaptableDarkMode | ||
157 | ), | ||
158 | () => { | 153 | () => { |
159 | this._shareSettingsWithServiceProcess(); | 154 | this._shareSettingsWithServiceProcess(); |
160 | }, | 155 | }, |
161 | ); | 156 | ); |
162 | 157 | ||
163 | reaction( | 158 | reaction( |
164 | () => ( | 159 | () => this.stores.settings.app.universalDarkMode, |
165 | this.stores.settings.app.universalDarkMode | ||
166 | ), | ||
167 | () => { | 160 | () => { |
168 | this._shareSettingsWithServiceProcess(); | 161 | this._shareSettingsWithServiceProcess(); |
169 | }, | 162 | }, |
170 | ); | 163 | ); |
171 | 164 | ||
172 | reaction( | 165 | reaction( |
173 | () => ( | 166 | () => this.stores.settings.app.searchEngine, |
174 | this.stores.settings.app.searchEngine | ||
175 | ), | ||
176 | () => { | 167 | () => { |
177 | this._shareSettingsWithServiceProcess(); | 168 | this._shareSettingsWithServiceProcess(); |
178 | }, | 169 | }, |
179 | ); | 170 | ); |
180 | 171 | ||
181 | reaction( | 172 | reaction( |
182 | () => ( | 173 | () => this.stores.settings.app.clipboardNotifications, |
183 | this.stores.settings.app.clipboardNotifications | ||
184 | ), | ||
185 | () => { | 174 | () => { |
186 | this._shareSettingsWithServiceProcess(); | 175 | this._shareSettingsWithServiceProcess(); |
187 | }, | 176 | }, |
@@ -215,12 +204,12 @@ export default class ServicesStore extends Store { | |||
215 | * Run various maintenance tasks on services | 204 | * Run various maintenance tasks on services |
216 | */ | 205 | */ |
217 | _serviceMaintenance() { | 206 | _serviceMaintenance() { |
218 | this.enabled.forEach(service => { | 207 | for (const service of this.enabled) { |
219 | // Defines which services should be hibernated or woken up | 208 | // Defines which services should be hibernated or woken up |
220 | if (!service.isActive) { | 209 | if (!service.isActive) { |
221 | if ( | 210 | if ( |
222 | !service.lastHibernated && | 211 | !service.lastHibernated && |
223 | (Date.now() - service.lastUsed) > | 212 | Date.now() - service.lastUsed > |
224 | ms(`${this.stores.settings.all.app.hibernationStrategy}s`) | 213 | ms(`${this.stores.settings.all.app.hibernationStrategy}s`) |
225 | ) { | 214 | ) { |
226 | // If service is stale, hibernate it. | 215 | // If service is stale, hibernate it. |
@@ -230,8 +219,8 @@ export default class ServicesStore extends Store { | |||
230 | if ( | 219 | if ( |
231 | service.lastHibernated && | 220 | service.lastHibernated && |
232 | Number(this.stores.settings.all.app.wakeUpStrategy) > 0 && | 221 | Number(this.stores.settings.all.app.wakeUpStrategy) > 0 && |
233 | (Date.now() - service.lastHibernated) > | 222 | Date.now() - service.lastHibernated > |
234 | ms(`${this.stores.settings.all.app.wakeUpStrategy}s`) | 223 | ms(`${this.stores.settings.all.app.wakeUpStrategy}s`) |
235 | ) { | 224 | ) { |
236 | // If service is in hibernation and the wakeup time has elapsed, wake it. | 225 | // If service is in hibernation and the wakeup time has elapsed, wake it. |
237 | this._awake({ serviceId: service.id }); | 226 | this._awake({ serviceId: service.id }); |
@@ -240,7 +229,7 @@ export default class ServicesStore extends Store { | |||
240 | 229 | ||
241 | if ( | 230 | if ( |
242 | service.lastPoll && | 231 | service.lastPoll && |
243 | (service.lastPoll - service.lastPollAnswer) > ms('1m') | 232 | service.lastPoll - service.lastPollAnswer > ms('1m') |
244 | ) { | 233 | ) { |
245 | // If service did not reply for more than 1m try to reload. | 234 | // If service did not reply for more than 1m try to reload. |
246 | if (!service.isActive) { | 235 | if (!service.isActive) { |
@@ -261,7 +250,7 @@ export default class ServicesStore extends Store { | |||
261 | service.lostRecipeConnection = false; | 250 | service.lostRecipeConnection = false; |
262 | service.lostRecipeReloadAttempt = 0; | 251 | service.lostRecipeReloadAttempt = 0; |
263 | } | 252 | } |
264 | }); | 253 | } |
265 | } | 254 | } |
266 | 255 | ||
267 | // Computed props | 256 | // Computed props |
@@ -270,8 +259,7 @@ export default class ServicesStore extends Store { | |||
270 | const services = this.allServicesRequest.execute().result; | 259 | const services = this.allServicesRequest.execute().result; |
271 | if (services) { | 260 | if (services) { |
272 | return observable( | 261 | return observable( |
273 | services | 262 | [...services] |
274 | .slice() | ||
275 | .slice() | 263 | .slice() |
276 | .sort((a, b) => a.order - b.order) | 264 | .sort((a, b) => a.order - b.order) |
277 | .map((s, index) => { | 265 | .map((s, index) => { |
@@ -318,11 +306,11 @@ export default class ServicesStore extends Store { | |||
318 | // Check if workspace needs to be kept loaded | 306 | // Check if workspace needs to be kept loaded |
319 | if (workspace.services.includes(KEEP_WS_LOADED_USID)) { | 307 | if (workspace.services.includes(KEEP_WS_LOADED_USID)) { |
320 | // Get services for workspace | 308 | // Get services for workspace |
321 | const serviceIDs = workspace.services.filter( | 309 | const serviceIDs = new Set( |
322 | i => i !== KEEP_WS_LOADED_USID, | 310 | workspace.services.filter(i => i !== KEEP_WS_LOADED_USID), |
323 | ); | 311 | ); |
324 | const wsServices = filteredServices.filter(service => | 312 | const wsServices = filteredServices.filter(service => |
325 | serviceIDs.includes(service.id), | 313 | serviceIDs.has(service.id), |
326 | ); | 314 | ); |
327 | 315 | ||
328 | displayedServices = [...displayedServices, ...wsServices]; | 316 | displayedServices = [...displayedServices, ...wsServices]; |
@@ -410,12 +398,14 @@ export default class ServicesStore extends Store { | |||
410 | customIcon: false, | 398 | customIcon: false, |
411 | isDarkModeEnabled: false, | 399 | isDarkModeEnabled: false, |
412 | spellcheckerLanguage: | 400 | spellcheckerLanguage: |
413 | SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], | 401 | SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], |
414 | userAgentPref: '', | 402 | userAgentPref: '', |
415 | ...serviceData, | 403 | ...serviceData, |
416 | }; | 404 | }; |
417 | 405 | ||
418 | const data = skipCleanup ? serviceData : this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); | 406 | const data = skipCleanup |
407 | ? serviceData | ||
408 | : this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); | ||
419 | 409 | ||
420 | const response = await this.createServiceRequest.execute(recipeId, data) | 410 | const response = await this.createServiceRequest.execute(recipeId, data) |
421 | ._promise; | 411 | ._promise; |
@@ -562,7 +552,8 @@ export default class ServicesStore extends Store { | |||
562 | // Write your scripts here | 552 | // Write your scripts here |
563 | console.log("Hello, World!", config); | 553 | console.log("Hello, World!", config); |
564 | }; | 554 | }; |
565 | `); | 555 | `, |
556 | ); | ||
566 | } | 557 | } |
567 | } else { | 558 | } else { |
568 | ensureFileSync(filePath); | 559 | ensureFileSync(filePath); |
@@ -580,9 +571,9 @@ export default class ServicesStore extends Store { | |||
580 | if (!keepActiveRoute) this.stores.router.push('/'); | 571 | if (!keepActiveRoute) this.stores.router.push('/'); |
581 | const service = this.one(serviceId); | 572 | const service = this.one(serviceId); |
582 | 573 | ||
583 | this.all.forEach(s => { | 574 | for (const s of this.all) { |
584 | s.isActive = false; | 575 | s.isActive = false; |
585 | }); | 576 | } |
586 | service.isActive = true; | 577 | service.isActive = true; |
587 | this._awake({ serviceId: service.id }); | 578 | this._awake({ serviceId: service.id }); |
588 | 579 | ||
@@ -618,9 +609,9 @@ export default class ServicesStore extends Store { | |||
618 | this.allDisplayed.length, | 609 | this.allDisplayed.length, |
619 | ); | 610 | ); |
620 | 611 | ||
621 | this.all.forEach(s => { | 612 | for (const s of this.all) { |
622 | s.isActive = false; | 613 | s.isActive = false; |
623 | }); | 614 | } |
624 | this.allDisplayed[nextIndex].isActive = true; | 615 | this.allDisplayed[nextIndex].isActive = true; |
625 | } | 616 | } |
626 | 617 | ||
@@ -631,9 +622,9 @@ export default class ServicesStore extends Store { | |||
631 | this.allDisplayed.length, | 622 | this.allDisplayed.length, |
632 | ); | 623 | ); |
633 | 624 | ||
634 | this.all.forEach(s => { | 625 | for (const s of this.all) { |
635 | s.isActive = false; | 626 | s.isActive = false; |
636 | }); | 627 | } |
637 | this.allDisplayed[prevIndex].isActive = true; | 628 | this.allDisplayed[prevIndex].isActive = true; |
638 | } | 629 | } |
639 | 630 | ||
@@ -699,101 +690,128 @@ export default class ServicesStore extends Store { | |||
699 | @action _handleIPCMessage({ serviceId, channel, args }) { | 690 | @action _handleIPCMessage({ serviceId, channel, args }) { |
700 | const service = this.one(serviceId); | 691 | const service = this.one(serviceId); |
701 | 692 | ||
702 | if (channel === 'hello') { | 693 | // eslint-disable-next-line default-case |
703 | debug('Received hello event from', serviceId); | 694 | switch (channel) { |
704 | 695 | case 'hello': { | |
705 | this._initRecipePolling(service.id); | 696 | debug('Received hello event from', serviceId); |
706 | this._initializeServiceRecipeInWebview(serviceId); | ||
707 | this._shareSettingsWithServiceProcess(); | ||
708 | } else if (channel === 'alive') { | ||
709 | service.lastPollAnswer = Date.now(); | ||
710 | } else if (channel === 'message-counts') { | ||
711 | debug(`Received unread message info from '${serviceId}'`, args[0]); | ||
712 | 697 | ||
713 | this.actions.service.setUnreadMessageCount({ | 698 | this._initRecipePolling(service.id); |
714 | serviceId, | 699 | this._initializeServiceRecipeInWebview(serviceId); |
715 | count: { | 700 | this._shareSettingsWithServiceProcess(); |
716 | direct: args[0].direct, | ||
717 | indirect: args[0].indirect, | ||
718 | }, | ||
719 | }); | ||
720 | } else if (channel === 'notification') { | ||
721 | const { options } = args[0]; | ||
722 | 701 | ||
723 | // Check if we are in scheduled Do-not-Disturb time | 702 | break; |
724 | const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } = | 703 | } |
725 | this.stores.settings.all.app; | 704 | case 'alive': { |
705 | service.lastPollAnswer = Date.now(); | ||
726 | 706 | ||
727 | if ( | 707 | break; |
728 | scheduledDNDEnabled && | ||
729 | isInTimeframe(scheduledDNDStart, scheduledDNDEnd) | ||
730 | ) { | ||
731 | return; | ||
732 | } | 708 | } |
709 | case 'message-counts': { | ||
710 | debug(`Received unread message info from '${serviceId}'`, args[0]); | ||
733 | 711 | ||
734 | if ( | 712 | this.actions.service.setUnreadMessageCount({ |
735 | service.recipe.hasNotificationSound || | 713 | serviceId, |
736 | service.isMuted || | 714 | count: { |
737 | this.stores.settings.all.app.isAppMuted | 715 | direct: args[0].direct, |
738 | ) { | 716 | indirect: args[0].indirect, |
739 | Object.assign(options, { | 717 | }, |
740 | silent: true, | ||
741 | }); | 718 | }); |
719 | |||
720 | break; | ||
742 | } | 721 | } |
722 | case 'notification': { | ||
723 | const { options } = args[0]; | ||
743 | 724 | ||
744 | if (service.isNotificationEnabled) { | 725 | // Check if we are in scheduled Do-not-Disturb time |
745 | let title = `Notification from ${service.name}`; | 726 | const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } = |
746 | if (!this.stores.settings.all.app.privateNotifications) { | 727 | this.stores.settings.all.app; |
747 | options.body = typeof options.body === 'string' ? options.body : ''; | 728 | |
748 | title = | 729 | if ( |
749 | typeof args[0].title === 'string' ? args[0].title : service.name; | 730 | scheduledDNDEnabled && |
750 | } else { | 731 | isInTimeframe(scheduledDNDStart, scheduledDNDEnd) |
751 | // Remove message data from notification in private mode | 732 | ) { |
752 | options.body = ''; | 733 | return; |
753 | options.icon = '/assets/img/notification-badge.gif'; | ||
754 | } | 734 | } |
755 | 735 | ||
756 | console.log(title, options); | 736 | if ( |
737 | service.recipe.hasNotificationSound || | ||
738 | service.isMuted || | ||
739 | this.stores.settings.all.app.isAppMuted | ||
740 | ) { | ||
741 | Object.assign(options, { | ||
742 | silent: true, | ||
743 | }); | ||
744 | } | ||
757 | 745 | ||
758 | this.actions.app.notify({ | 746 | if (service.isNotificationEnabled) { |
759 | notificationId: args[0].notificationId, | 747 | let title = `Notification from ${service.name}`; |
760 | title, | 748 | if (!this.stores.settings.all.app.privateNotifications) { |
761 | options, | 749 | options.body = typeof options.body === 'string' ? options.body : ''; |
762 | serviceId, | 750 | title = |
763 | }); | 751 | typeof args[0].title === 'string' ? args[0].title : service.name; |
752 | } else { | ||
753 | // Remove message data from notification in private mode | ||
754 | options.body = ''; | ||
755 | options.icon = '/assets/img/notification-badge.gif'; | ||
756 | } | ||
757 | |||
758 | console.log(title, options); | ||
759 | |||
760 | this.actions.app.notify({ | ||
761 | notificationId: args[0].notificationId, | ||
762 | title, | ||
763 | options, | ||
764 | serviceId, | ||
765 | }); | ||
766 | } | ||
767 | |||
768 | break; | ||
764 | } | 769 | } |
765 | } else if (channel === 'avatar') { | 770 | case 'avatar': { |
766 | const url = args[0]; | 771 | const url = args[0]; |
767 | if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { | 772 | if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { |
768 | service.customIconUrl = url; | 773 | service.customIconUrl = url; |
774 | |||
775 | this.actions.service.updateService({ | ||
776 | serviceId, | ||
777 | serviceData: { | ||
778 | customIconUrl: url, | ||
779 | }, | ||
780 | redirect: false, | ||
781 | }); | ||
782 | } | ||
769 | 783 | ||
770 | this.actions.service.updateService({ | 784 | break; |
771 | serviceId, | ||
772 | serviceData: { | ||
773 | customIconUrl: url, | ||
774 | }, | ||
775 | redirect: false, | ||
776 | }); | ||
777 | } | 785 | } |
778 | } else if (channel === 'new-window') { | 786 | case 'new-window': { |
779 | const url = args[0]; | 787 | const url = args[0]; |
780 | 788 | ||
781 | this.actions.app.openExternalUrl({ url }); | 789 | this.actions.app.openExternalUrl({ url }); |
782 | } else if (channel === 'set-service-spellchecker-language') { | 790 | |
783 | if (!args) { | 791 | break; |
784 | console.warn('Did not receive locale'); | 792 | } |
785 | } else { | 793 | case 'set-service-spellchecker-language': { |
786 | this.actions.service.updateService({ | 794 | if (!args) { |
787 | serviceId, | 795 | console.warn('Did not receive locale'); |
788 | serviceData: { | 796 | } else { |
789 | spellcheckerLanguage: args[0] === 'reset' ? '' : args[0], | 797 | this.actions.service.updateService({ |
790 | }, | 798 | serviceId, |
791 | redirect: false, | 799 | serviceData: { |
792 | }); | 800 | spellcheckerLanguage: args[0] === 'reset' ? '' : args[0], |
801 | }, | ||
802 | redirect: false, | ||
803 | }); | ||
804 | } | ||
805 | |||
806 | break; | ||
807 | } | ||
808 | case 'feature:todos': { | ||
809 | Object.assign(args[0].data, { serviceId }); | ||
810 | this.actions.todos.handleHostMessage(args[0]); | ||
811 | |||
812 | break; | ||
793 | } | 813 | } |
794 | } else if (channel === 'feature:todos') { | 814 | // No default |
795 | Object.assign(args[0].data, { serviceId }); | ||
796 | this.actions.todos.handleHostMessage(args[0]); | ||
797 | } | 815 | } |
798 | } | 816 | } |
799 | 817 | ||
@@ -809,13 +827,13 @@ export default class ServicesStore extends Store { | |||
809 | } | 827 | } |
810 | 828 | ||
811 | @action _sendIPCMessageToAllServices({ channel, args }) { | 829 | @action _sendIPCMessageToAllServices({ channel, args }) { |
812 | this.all.forEach(s => | 830 | for (const s of this.all) { |
813 | this.actions.service.sendIPCMessage({ | 831 | this.actions.service.sendIPCMessage({ |
814 | serviceId: s.id, | 832 | serviceId: s.id, |
815 | channel, | 833 | channel, |
816 | args, | 834 | args, |
817 | }), | 835 | }); |
818 | ); | 836 | } |
819 | } | 837 | } |
820 | 838 | ||
821 | @action _openWindow({ event }) { | 839 | @action _openWindow({ event }) { |
@@ -863,11 +881,11 @@ export default class ServicesStore extends Store { | |||
863 | } | 881 | } |
864 | 882 | ||
865 | @action _reloadAll() { | 883 | @action _reloadAll() { |
866 | this.enabled.forEach(s => | 884 | for (const s of this.enabled) { |
867 | this._reload({ | 885 | this._reload({ |
868 | serviceId: s.id, | 886 | serviceId: s.id, |
869 | }), | 887 | }); |
870 | ); | 888 | } |
871 | } | 889 | } |
872 | 890 | ||
873 | @action _reloadUpdatedServices() { | 891 | @action _reloadUpdatedServices() { |
@@ -901,17 +919,17 @@ export default class ServicesStore extends Store { | |||
901 | 919 | ||
902 | const services = {}; | 920 | const services = {}; |
903 | // TODO: simplify this | 921 | // TODO: simplify this |
904 | this.all.forEach((s, index) => { | 922 | for (const [index] of this.all.entries()) { |
905 | services[this.all[index].id] = index; | 923 | services[this.all[index].id] = index; |
906 | }); | 924 | } |
907 | 925 | ||
908 | this.reorderServicesRequest.execute(services); | 926 | this.reorderServicesRequest.execute(services); |
909 | this.allServicesRequest.patch(data => { | 927 | this.allServicesRequest.patch(data => { |
910 | data.forEach(s => { | 928 | for (const s of data) { |
911 | const service = s; | 929 | const service = s; |
912 | 930 | ||
913 | service.order = services[s.id]; | 931 | service.order = services[s.id]; |
914 | }); | 932 | } |
915 | }); | 933 | }); |
916 | } | 934 | } |
917 | 935 | ||
@@ -1001,13 +1019,14 @@ export default class ServicesStore extends Store { | |||
1001 | }`, | 1019 | }`, |
1002 | ); | 1020 | ); |
1003 | 1021 | ||
1022 | // eslint-disable-next-line unicorn/consistent-function-scoping | ||
1004 | const resetTimer = service => { | 1023 | const resetTimer = service => { |
1005 | service.lastPollAnswer = Date.now(); | 1024 | service.lastPollAnswer = Date.now(); |
1006 | service.lastPoll = Date.now(); | 1025 | service.lastPoll = Date.now(); |
1007 | }; | 1026 | }; |
1008 | 1027 | ||
1009 | if (!serviceId) { | 1028 | if (!serviceId) { |
1010 | this.allDisplayed.forEach(service => resetTimer(service)); | 1029 | for (const service of this.allDisplayed) resetTimer(service); |
1011 | } else { | 1030 | } else { |
1012 | const service = this.one(serviceId); | 1031 | const service = this.one(serviceId); |
1013 | if (service) { | 1032 | if (service) { |
@@ -1043,7 +1062,7 @@ export default class ServicesStore extends Store { | |||
1043 | 1062 | ||
1044 | _mapActiveServiceToServiceModelReaction() { | 1063 | _mapActiveServiceToServiceModelReaction() { |
1045 | const { activeService } = this.stores.settings.all.service; | 1064 | const { activeService } = this.stores.settings.all.service; |
1046 | if (this.allDisplayed.length) { | 1065 | if (this.allDisplayed.length > 0) { |
1047 | this.allDisplayed.map(service => | 1066 | this.allDisplayed.map(service => |
1048 | Object.assign(service, { | 1067 | Object.assign(service, { |
1049 | isActive: activeService | 1068 | isActive: activeService |
@@ -1102,14 +1121,14 @@ export default class ServicesStore extends Store { | |||
1102 | const { enabled } = this; | 1121 | const { enabled } = this; |
1103 | const { isAppMuted } = this.stores.settings.app; | 1122 | const { isAppMuted } = this.stores.settings.app; |
1104 | 1123 | ||
1105 | enabled.forEach(service => { | 1124 | for (const service of enabled) { |
1106 | const { isAttached } = service; | 1125 | const { isAttached } = service; |
1107 | const isMuted = isAppMuted || service.isMuted; | 1126 | const isMuted = isAppMuted || service.isMuted; |
1108 | 1127 | ||
1109 | if (isAttached) { | 1128 | if (isAttached) { |
1110 | service.webview.audioMuted = isMuted; | 1129 | service.webview.audioMuted = isMuted; |
1111 | } | 1130 | } |
1112 | }); | 1131 | } |
1113 | } | 1132 | } |
1114 | 1133 | ||
1115 | _shareSettingsWithServiceProcess() { | 1134 | _shareSettingsWithServiceProcess() { |
@@ -1151,7 +1170,7 @@ export default class ServicesStore extends Store { | |||
1151 | 1170 | ||
1152 | if ( | 1171 | if ( |
1153 | this.allDisplayed.findIndex(service => service.isActive) === -1 && | 1172 | this.allDisplayed.findIndex(service => service.isActive) === -1 && |
1154 | this.allDisplayed.length !== 0 | 1173 | this.allDisplayed.length > 0 |
1155 | ) { | 1174 | ) { |
1156 | debug('No active service found, setting active service to index 0'); | 1175 | debug('No active service found, setting active service to index 0'); |
1157 | 1176 | ||
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index 9aade974c..690a18374 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -1,11 +1,11 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { getCurrentWindow } from '@electron/remote'; | 2 | import { getCurrentWindow } from '@electron/remote'; |
3 | import { | 3 | import { action, computed, observable, reaction } from 'mobx'; |
4 | action, computed, observable, reaction, | ||
5 | } from 'mobx'; | ||
6 | import localStorage from 'mobx-localstorage'; | 4 | import localStorage from 'mobx-localstorage'; |
7 | import { | 5 | import { |
8 | FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER, SEARCH_ENGINE_DDG, | 6 | FILE_SYSTEM_SETTINGS_TYPES, |
7 | LOCAL_SERVER, | ||
8 | SEARCH_ENGINE_DDG, | ||
9 | } from '../config'; | 9 | } from '../config'; |
10 | import { API, DEFAULT_APP_SETTINGS } from '../environment'; | 10 | import { API, DEFAULT_APP_SETTINGS } from '../environment'; |
11 | import { getLocale } from '../helpers/i18n-helpers'; | 11 | import { getLocale } from '../helpers/i18n-helpers'; |
@@ -17,7 +17,10 @@ import Store from './lib/Store'; | |||
17 | const debug = require('debug')('Ferdi:SettingsStore'); | 17 | const debug = require('debug')('Ferdi:SettingsStore'); |
18 | 18 | ||
19 | export default class SettingsStore extends Store { | 19 | export default class SettingsStore extends Store { |
20 | @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); | 20 | @observable updateAppSettingsRequest = new Request( |
21 | this.api.local, | ||
22 | 'updateAppSettings', | ||
23 | ); | ||
21 | 24 | ||
22 | startup = true; | 25 | startup = true; |
23 | 26 | ||
@@ -40,9 +43,7 @@ export default class SettingsStore extends Store { | |||
40 | await this._migrate(); | 43 | await this._migrate(); |
41 | 44 | ||
42 | reaction( | 45 | reaction( |
43 | () => ( | 46 | () => this.all.app.autohideMenuBar, |
44 | this.all.app.autohideMenuBar | ||
45 | ), | ||
46 | () => { | 47 | () => { |
47 | const currentWindow = getCurrentWindow(); | 48 | const currentWindow = getCurrentWindow(); |
48 | currentWindow.setMenuBarVisibility(!this.all.app.autohideMenuBar); | 49 | currentWindow.setMenuBarVisibility(!this.all.app.autohideMenuBar); |
@@ -51,10 +52,8 @@ export default class SettingsStore extends Store { | |||
51 | ); | 52 | ); |
52 | 53 | ||
53 | reaction( | 54 | reaction( |
54 | () => ( | 55 | () => this.all.app.server, |
55 | this.all.app.server | 56 | server => { |
56 | ), | ||
57 | (server) => { | ||
58 | if (server === LOCAL_SERVER) { | 57 | if (server === LOCAL_SERVER) { |
59 | ipcRenderer.send('startLocalServer'); | 58 | ipcRenderer.send('startLocalServer'); |
60 | } | 59 | } |
@@ -65,7 +64,10 @@ export default class SettingsStore extends Store { | |||
65 | // Inactivity lock timer | 64 | // Inactivity lock timer |
66 | let inactivityTimer; | 65 | let inactivityTimer; |
67 | getCurrentWindow().on('blur', () => { | 66 | getCurrentWindow().on('blur', () => { |
68 | if (this.all.app.lockingFeatureEnabled && this.all.app.inactivityLock !== 0) { | 67 | if ( |
68 | this.all.app.lockingFeatureEnabled && | ||
69 | this.all.app.inactivityLock !== 0 | ||
70 | ) { | ||
69 | inactivityTimer = setTimeout(() => { | 71 | inactivityTimer = setTimeout(() => { |
70 | this.actions.settings.update({ | 72 | this.actions.settings.update({ |
71 | type: 'app', | 73 | type: 'app', |
@@ -84,7 +86,11 @@ export default class SettingsStore extends Store { | |||
84 | 86 | ||
85 | ipcRenderer.on('appSettings', (event, resp) => { | 87 | ipcRenderer.on('appSettings', (event, resp) => { |
86 | // Lock on startup if enabled in settings | 88 | // Lock on startup if enabled in settings |
87 | if (this.startup && resp.type === 'app' && resp.data.lockingFeatureEnabled) { | 89 | if ( |
90 | this.startup && | ||
91 | resp.type === 'app' && | ||
92 | resp.data.lockingFeatureEnabled | ||
93 | ) { | ||
88 | this.startup = false; | 94 | this.startup = false; |
89 | process.nextTick(() => { | 95 | process.nextTick(() => { |
90 | if (!this.all.app.locked) { | 96 | if (!this.all.app.locked) { |
@@ -97,9 +103,9 @@ export default class SettingsStore extends Store { | |||
97 | ipcRenderer.send('initialAppSettings', resp); | 103 | ipcRenderer.send('initialAppSettings', resp); |
98 | }); | 104 | }); |
99 | 105 | ||
100 | this.fileSystemSettingsTypes.forEach((type) => { | 106 | for (const type of this.fileSystemSettingsTypes) { |
101 | ipcRenderer.send('getAppSettings', type); | 107 | ipcRenderer.send('getAppSettings', type); |
102 | }); | 108 | } |
103 | } | 109 | } |
104 | 110 | ||
105 | @computed get app() { | 111 | @computed get app() { |
@@ -111,15 +117,19 @@ export default class SettingsStore extends Store { | |||
111 | } | 117 | } |
112 | 118 | ||
113 | @computed get service() { | 119 | @computed get service() { |
114 | return localStorage.getItem('service') || { | 120 | return ( |
115 | activeService: '', | 121 | localStorage.getItem('service') || { |
116 | }; | 122 | activeService: '', |
123 | } | ||
124 | ); | ||
117 | } | 125 | } |
118 | 126 | ||
119 | @computed get stats() { | 127 | @computed get stats() { |
120 | return localStorage.getItem('stats') || { | 128 | return ( |
121 | activeService: '', | 129 | localStorage.getItem('stats') || { |
122 | }; | 130 | activeService: '', |
131 | } | ||
132 | ); | ||
123 | } | 133 | } |
124 | 134 | ||
125 | @computed get migration() { | 135 | @computed get migration() { |
@@ -230,9 +240,7 @@ export default class SettingsStore extends Store { | |||
230 | }); | 240 | }); |
231 | 241 | ||
232 | this._ensureMigrationAndMarkDone('5.4.4-beta.2-settings', () => { | 242 | this._ensureMigrationAndMarkDone('5.4.4-beta.2-settings', () => { |
233 | const { | 243 | const { showServiceNavigationBar } = this.all.app; |
234 | showServiceNavigationBar, | ||
235 | } = this.all.app; | ||
236 | 244 | ||
237 | this.actions.settings.update({ | 245 | this.actions.settings.update({ |
238 | type: 'app', | 246 | type: 'app', |
@@ -248,7 +256,7 @@ export default class SettingsStore extends Store { | |||
248 | data: { | 256 | data: { |
249 | todoServer: 'isUsingCustomTodoService', | 257 | todoServer: 'isUsingCustomTodoService', |
250 | customTodoServer: legacySettings.todoServer, | 258 | customTodoServer: legacySettings.todoServer, |
251 | automaticUpdates: !(legacySettings.noUpdates), | 259 | automaticUpdates: !legacySettings.noUpdates, |
252 | }, | 260 | }, |
253 | }); | 261 | }); |
254 | 262 | ||
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 2e009893a..e3d57c662 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -46,7 +46,10 @@ export default class UserStore extends Store { | |||
46 | 46 | ||
47 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); | 47 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); |
48 | 48 | ||
49 | @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); | 49 | @observable getLegacyServicesRequest = new CachedRequest( |
50 | this.api.user, | ||
51 | 'getLegacyServices', | ||
52 | ); | ||
50 | 53 | ||
51 | @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); | 54 | @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); |
52 | 55 | ||
@@ -81,13 +84,17 @@ export default class UserStore extends Store { | |||
81 | 84 | ||
82 | // Register action handlers | 85 | // Register action handlers |
83 | this.actions.user.login.listen(this._login.bind(this)); | 86 | this.actions.user.login.listen(this._login.bind(this)); |
84 | this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); | 87 | this.actions.user.retrievePassword.listen( |
88 | this._retrievePassword.bind(this), | ||
89 | ); | ||
85 | this.actions.user.logout.listen(this._logout.bind(this)); | 90 | this.actions.user.logout.listen(this._logout.bind(this)); |
86 | this.actions.user.signup.listen(this._signup.bind(this)); | 91 | this.actions.user.signup.listen(this._signup.bind(this)); |
87 | this.actions.user.invite.listen(this._invite.bind(this)); | 92 | this.actions.user.invite.listen(this._invite.bind(this)); |
88 | this.actions.user.update.listen(this._update.bind(this)); | 93 | this.actions.user.update.listen(this._update.bind(this)); |
89 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); | 94 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); |
90 | this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); | 95 | this.actions.user.importLegacyServices.listen( |
96 | this._importLegacyServices.bind(this), | ||
97 | ); | ||
91 | this.actions.user.delete.listen(this._delete.bind(this)); | 98 | this.actions.user.delete.listen(this._delete.bind(this)); |
92 | 99 | ||
93 | // Reactions | 100 | // Reactions |
@@ -176,7 +183,14 @@ export default class UserStore extends Store { | |||
176 | } | 183 | } |
177 | 184 | ||
178 | @action async _signup({ | 185 | @action async _signup({ |
179 | firstname, lastname, email, password, accountType, company, plan, currency, | 186 | firstname, |
187 | lastname, | ||
188 | email, | ||
189 | password, | ||
190 | accountType, | ||
191 | company, | ||
192 | plan, | ||
193 | currency, | ||
180 | }) { | 194 | }) { |
181 | const authToken = await this.signupRequest.execute({ | 195 | const authToken = await this.signupRequest.execute({ |
182 | firstname, | 196 | firstname, |
@@ -205,7 +219,7 @@ export default class UserStore extends Store { | |||
205 | } | 219 | } |
206 | 220 | ||
207 | @action async _invite({ invites }) { | 221 | @action async _invite({ invites }) { |
208 | const data = invites.filter((invite) => invite.email !== ''); | 222 | const data = invites.filter(invite => invite.email !== ''); |
209 | 223 | ||
210 | const response = await this.inviteRequest.execute(data)._promise; | 224 | const response = await this.inviteRequest.execute(data)._promise; |
211 | 225 | ||
@@ -220,7 +234,8 @@ export default class UserStore extends Store { | |||
220 | @action async _update({ userData }) { | 234 | @action async _update({ userData }) { |
221 | if (!this.isLoggedIn) return; | 235 | if (!this.isLoggedIn) return; |
222 | 236 | ||
223 | const response = await this.updateUserInfoRequest.execute(userData)._promise; | 237 | const response = await this.updateUserInfoRequest.execute(userData) |
238 | ._promise; | ||
224 | 239 | ||
225 | this.getUserInfoRequest.patch(() => response.data); | 240 | this.getUserInfoRequest.patch(() => response.data); |
226 | this.actionStatus = response.status || []; | 241 | this.actionStatus = response.status || []; |
@@ -250,19 +265,27 @@ export default class UserStore extends Store { | |||
250 | this.isImportLegacyServicesExecuting = true; | 265 | this.isImportLegacyServicesExecuting = true; |
251 | 266 | ||
252 | // Reduces recipe duplicates | 267 | // Reduces recipe duplicates |
253 | const recipes = services.filter((obj, pos, arr) => arr.map((mapObj) => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map((s) => s.recipe.id); | 268 | const recipes = services |
269 | .filter( | ||
270 | (obj, pos, arr) => | ||
271 | arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos, | ||
272 | ) | ||
273 | .map(s => s.recipe.id); | ||
254 | 274 | ||
255 | // Install recipes | 275 | // Install recipes |
256 | for (const recipe of recipes) { // eslint-disable-line no-unused-vars | 276 | for (const recipe of recipes) { |
257 | // eslint-disable-next-line | 277 | // eslint-disable-line no-unused-vars |
278 | // eslint-disable-next-line no-await-in-loop | ||
258 | await this.stores.recipes._install({ recipeId: recipe }); | 279 | await this.stores.recipes._install({ recipeId: recipe }); |
259 | } | 280 | } |
260 | 281 | ||
261 | for (const service of services) { // eslint-disable-line no-unused-vars | 282 | for (const service of services) { |
283 | // eslint-disable-line no-unused-vars | ||
262 | this.actions.service.createFromLegacyService({ | 284 | this.actions.service.createFromLegacyService({ |
263 | data: service, | 285 | data: service, |
264 | }); | 286 | }); |
265 | await this.stores.services.createServiceRequest._promise; // eslint-disable-line | 287 | // eslint-disable-next-line no-await-in-loop |
288 | await this.stores.services.createServiceRequest._promise; | ||
266 | } | 289 | } |
267 | 290 | ||
268 | this.isImportLegacyServicesExecuting = false; | 291 | this.isImportLegacyServicesExecuting = false; |
@@ -281,8 +304,7 @@ export default class UserStore extends Store { | |||
281 | 304 | ||
282 | const { router } = this.stores; | 305 | const { router } = this.stores; |
283 | const currentRoute = window.location.hash; | 306 | const currentRoute = window.location.hash; |
284 | if (!this.isLoggedIn | 307 | if (!this.isLoggedIn && currentRoute.includes('token=')) { |
285 | && currentRoute.includes('token=')) { | ||
286 | router.push(this.WELCOME_ROUTE); | 308 | router.push(this.WELCOME_ROUTE); |
287 | const token = currentRoute.split('=')[1]; | 309 | const token = currentRoute.split('=')[1]; |
288 | 310 | ||
@@ -293,20 +315,18 @@ export default class UserStore extends Store { | |||
293 | this._tokenLogin(token); | 315 | this._tokenLogin(token); |
294 | }, 1000); | 316 | }, 1000); |
295 | } | 317 | } |
296 | } else if (!this.isLoggedIn | 318 | } else if (!this.isLoggedIn && !currentRoute.includes(this.BASE_ROUTE)) { |
297 | && !currentRoute.includes(this.BASE_ROUTE)) { | ||
298 | router.push(this.WELCOME_ROUTE); | 319 | router.push(this.WELCOME_ROUTE); |
299 | } else if (this.isLoggedIn | 320 | } else if (this.isLoggedIn && currentRoute === this.LOGOUT_ROUTE) { |
300 | && currentRoute === this.LOGOUT_ROUTE) { | ||
301 | this.actions.user.logout(); | 321 | this.actions.user.logout(); |
302 | router.push(this.LOGIN_ROUTE); | 322 | router.push(this.LOGIN_ROUTE); |
303 | } else if (this.isLoggedIn | 323 | } else if ( |
304 | && currentRoute.includes(this.BASE_ROUTE) | 324 | this.isLoggedIn && |
305 | && (this.hasCompletedSignup | 325 | currentRoute.includes(this.BASE_ROUTE) && |
306 | || this.hasCompletedSignup === null)) { | 326 | (this.hasCompletedSignup || this.hasCompletedSignup === null) && |
307 | if (!isDevMode) { | 327 | !isDevMode |
308 | this.stores.router.push('/'); | 328 | ) { |
309 | } | 329 | this.stores.router.push('/'); |
310 | } | 330 | } |
311 | }; | 331 | }; |
312 | 332 | ||
@@ -316,7 +336,7 @@ export default class UserStore extends Store { | |||
316 | let data; | 336 | let data; |
317 | try { | 337 | try { |
318 | data = await this.getUserInfoRequest.execute()._promise; | 338 | data = await this.getUserInfoRequest.execute()._promise; |
319 | } catch (e) { | 339 | } catch { |
320 | return false; | 340 | return false; |
321 | } | 341 | } |
322 | 342 | ||
@@ -336,12 +356,12 @@ export default class UserStore extends Store { | |||
336 | try { | 356 | try { |
337 | const decoded = jwt.decode(authToken); | 357 | const decoded = jwt.decode(authToken); |
338 | 358 | ||
339 | return ({ | 359 | return { |
340 | id: decoded.userId, | 360 | id: decoded.userId, |
341 | tokenExpiry: moment.unix(decoded.exp).toISOString(), | 361 | tokenExpiry: moment.unix(decoded.exp).toISOString(), |
342 | authToken, | 362 | authToken, |
343 | }); | 363 | }; |
344 | } catch (err) { | 364 | } catch { |
345 | this._logout(); | 365 | this._logout(); |
346 | return false; | 366 | return false; |
347 | } | 367 | } |
@@ -372,7 +392,7 @@ export default class UserStore extends Store { | |||
372 | async _migrateUserLocale() { | 392 | async _migrateUserLocale() { |
373 | try { | 393 | try { |
374 | await this.getUserInfoRequest._promise; | 394 | await this.getUserInfoRequest._promise; |
375 | } catch (e) { | 395 | } catch { |
376 | return false; | 396 | return false; |
377 | } | 397 | } |
378 | 398 | ||
diff --git a/src/stores/index.ts b/src/stores/index.ts index 4cd4e92ea..1760ddfa2 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts | |||
@@ -34,10 +34,10 @@ export default (api, actions, router) => { | |||
34 | }); | 34 | }); |
35 | 35 | ||
36 | // Initialize all stores | 36 | // Initialize all stores |
37 | Object.keys(stores).forEach(name => { | 37 | for (const name of Object.keys(stores)) { |
38 | if (stores[name] && stores[name].initialize) { | 38 | if (stores[name] && stores[name].initialize) { |
39 | stores[name].initialize(); | 39 | stores[name].initialize(); |
40 | } | 40 | } |
41 | }); | 41 | } |
42 | return stores; | 42 | return stores; |
43 | }; | 43 | }; |
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js index 94f615144..a6dd47f7d 100644 --- a/src/stores/lib/CachedRequest.js +++ b/src/stores/lib/CachedRequest.js | |||
@@ -1,4 +1,3 @@ | |||
1 | // @flow | ||
2 | import { action } from 'mobx'; | 1 | import { action } from 'mobx'; |
3 | import { isEqual, remove } from 'lodash'; | 2 | import { isEqual, remove } from 'lodash'; |
4 | import Request from './Request'; | 3 | import Request from './Request'; |
@@ -30,48 +29,60 @@ export default class CachedRequest extends Request { | |||
30 | 29 | ||
31 | // This timeout is necessary to avoid warnings from mobx | 30 | // This timeout is necessary to avoid warnings from mobx |
32 | // regarding triggering actions as side-effect of getters | 31 | // regarding triggering actions as side-effect of getters |
33 | setTimeout(action(() => { | 32 | setTimeout( |
34 | this.isExecuting = true; | 33 | action(() => { |
35 | // Apply the previous result from this call immediately (cached) | 34 | this.isExecuting = true; |
36 | if (existingApiCall) { | 35 | // Apply the previous result from this call immediately (cached) |
37 | this.result = existingApiCall.result; | 36 | if (existingApiCall) { |
38 | } | 37 | this.result = existingApiCall.result; |
39 | }), 0); | 38 | } |
39 | }), | ||
40 | 0, | ||
41 | ); | ||
40 | 42 | ||
41 | // Issue api call & save it as promise that is handled to update the results of the operation | 43 | // Issue api call & save it as promise that is handled to update the results of the operation |
42 | this._promise = new Promise((resolve) => { | 44 | this._promise = new Promise(resolve => { |
43 | this._api[this._method](...callArgs) | 45 | this._api[this._method](...callArgs) |
44 | .then((result) => { | 46 | .then(result => { |
45 | setTimeout(action(() => { | 47 | setTimeout( |
46 | this.result = result; | 48 | action(() => { |
47 | if (this._currentApiCall) this._currentApiCall.result = result; | 49 | this.result = result; |
48 | this.isExecuting = false; | 50 | if (this._currentApiCall) this._currentApiCall.result = result; |
49 | this.isError = false; | 51 | this.isExecuting = false; |
50 | this.wasExecuted = true; | 52 | this.isError = false; |
51 | this._isInvalidated = false; | 53 | this.wasExecuted = true; |
52 | this._isWaitingForResponse = false; | 54 | this._isInvalidated = false; |
53 | this._triggerHooks(); | 55 | this._isWaitingForResponse = false; |
54 | resolve(result); | 56 | this._triggerHooks(); |
55 | }), 1); | 57 | resolve(result); |
58 | }), | ||
59 | 1, | ||
60 | ); | ||
56 | return result; | 61 | return result; |
57 | }) | 62 | }) |
58 | .catch(action((error) => { | 63 | .catch( |
59 | setTimeout(action(() => { | 64 | action(error => { |
60 | this.error = error; | 65 | setTimeout( |
61 | this.isExecuting = false; | 66 | action(() => { |
62 | this.isError = true; | 67 | this.error = error; |
63 | this.wasExecuted = true; | 68 | this.isExecuting = false; |
64 | this._isWaitingForResponse = false; | 69 | this.isError = true; |
65 | this._triggerHooks(); | 70 | this.wasExecuted = true; |
66 | // reject(error); | 71 | this._isWaitingForResponse = false; |
67 | }), 1); | 72 | this._triggerHooks(); |
68 | })); | 73 | // reject(error); |
74 | }), | ||
75 | 1, | ||
76 | ); | ||
77 | }), | ||
78 | ); | ||
69 | }); | 79 | }); |
70 | 80 | ||
71 | this._isWaitingForResponse = true; | 81 | this._isWaitingForResponse = true; |
72 | return this; | 82 | return this; |
73 | } | 83 | } |
74 | 84 | ||
85 | // eslint-disable-next-line unicorn/no-object-as-default-parameter | ||
75 | invalidate(options = { immediately: false }) { | 86 | invalidate(options = { immediately: false }) { |
76 | this._isInvalidated = true; | 87 | this._isInvalidated = true; |
77 | if (options.immediately && this._currentApiCall) { | 88 | if (options.immediately && this._currentApiCall) { |
@@ -81,18 +92,21 @@ export default class CachedRequest extends Request { | |||
81 | } | 92 | } |
82 | 93 | ||
83 | patch(modify) { | 94 | patch(modify) { |
84 | return new Promise((resolve) => { | 95 | return new Promise(resolve => { |
85 | setTimeout(action(() => { | 96 | setTimeout( |
86 | const override = modify(this.result); | 97 | action(() => { |
87 | if (override !== undefined) this.result = override; | 98 | const override = modify(this.result); |
88 | if (this._currentApiCall) this._currentApiCall.result = this.result; | 99 | if (override !== undefined) this.result = override; |
89 | resolve(this); | 100 | if (this._currentApiCall) this._currentApiCall.result = this.result; |
90 | }), 0); | 101 | resolve(this); |
102 | }), | ||
103 | 0, | ||
104 | ); | ||
91 | }); | 105 | }); |
92 | } | 106 | } |
93 | 107 | ||
94 | removeCacheForCallWith(...args) { | 108 | removeCacheForCallWith(...args) { |
95 | remove(this._apiCalls, (c) => isEqual(c.args, args)); | 109 | remove(this._apiCalls, c => isEqual(c.args, args)); |
96 | } | 110 | } |
97 | 111 | ||
98 | _addApiCall(args) { | 112 | _addApiCall(args) { |
@@ -102,6 +116,6 @@ export default class CachedRequest extends Request { | |||
102 | } | 116 | } |
103 | 117 | ||
104 | _findApiCall(args) { | 118 | _findApiCall(args) { |
105 | return this._apiCalls.find((c) => isEqual(c.args, args)); | 119 | return this._apiCalls.find(c => isEqual(c.args, args)); |
106 | } | 120 | } |
107 | } | 121 | } |
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js index 32ffe4367..39f32729a 100644 --- a/src/stores/lib/Request.js +++ b/src/stores/lib/Request.js | |||
@@ -107,7 +107,7 @@ export default class Request { | |||
107 | } | 107 | } |
108 | 108 | ||
109 | _triggerHooks() { | 109 | _triggerHooks() { |
110 | Request._hooks.forEach((hook) => hook(this)); | 110 | for (const hook of Request._hooks) hook(this); |
111 | } | 111 | } |
112 | 112 | ||
113 | reset = () => { | 113 | reset = () => { |
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js index b03a7e725..b39070ce8 100644 --- a/src/stores/lib/Store.js +++ b/src/stores/lib/Store.js | |||
@@ -28,18 +28,18 @@ export default class Store { | |||
28 | } | 28 | } |
29 | 29 | ||
30 | registerReactions(reactions) { | 30 | registerReactions(reactions) { |
31 | reactions.forEach((reaction) => this._reactions.push(new Reaction(reaction))); | 31 | for (const reaction of reactions) this._reactions.push(new Reaction(reaction)); |
32 | } | 32 | } |
33 | 33 | ||
34 | setup() {} | 34 | setup() {} |
35 | 35 | ||
36 | initialize() { | 36 | initialize() { |
37 | this.setup(); | 37 | this.setup(); |
38 | this._reactions.forEach((reaction) => reaction.start()); | 38 | for (const reaction of this._reactions) reaction.start(); |
39 | } | 39 | } |
40 | 40 | ||
41 | teardown() { | 41 | teardown() { |
42 | this._reactions.forEach((reaction) => reaction.stop()); | 42 | for (const reaction of this._reactions) reaction.stop(); |
43 | } | 43 | } |
44 | 44 | ||
45 | resetStatus() { | 45 | resetStatus() { |
diff --git a/src/webview/badge.ts b/src/webview/badge.ts index 024e29b3f..8e8b66c0c 100644 --- a/src/webview/badge.ts +++ b/src/webview/badge.ts | |||
@@ -21,7 +21,7 @@ export class BadgeHandler { | |||
21 | // Parse number to integer | 21 | // Parse number to integer |
22 | // This will correct errors that recipes may introduce, e.g. | 22 | // This will correct errors that recipes may introduce, e.g. |
23 | // by sending a String instead of an integer | 23 | // by sending a String instead of an integer |
24 | const parsedNumber = parseInt(text.toString(), 10); | 24 | const parsedNumber = Number.parseInt(text.toString(), 10); |
25 | const adjustedNumber = Number.isNaN(parsedNumber) ? 0 : parsedNumber; | 25 | const adjustedNumber = Number.isNaN(parsedNumber) ? 0 : parsedNumber; |
26 | return Math.max(adjustedNumber, 0); | 26 | return Math.max(adjustedNumber, 0); |
27 | } | 27 | } |
diff --git a/src/webview/contextMenuBuilder.js b/src/webview/contextMenuBuilder.js index 8c39e6d04..eb15aa138 100644 --- a/src/webview/contextMenuBuilder.js +++ b/src/webview/contextMenuBuilder.js | |||
@@ -6,7 +6,8 @@ | |||
6 | * | 6 | * |
7 | * Source: https://github.com/electron-userland/electron-spellchecker/blob/master/src/context-menu-builder.js | 7 | * Source: https://github.com/electron-userland/electron-spellchecker/blob/master/src/context-menu-builder.js |
8 | */ | 8 | */ |
9 | import { clipboard, ipcRenderer, nativeImage } from 'electron'; | 9 | // eslint-disable-next-line no-unused-vars |
10 | import { clipboard, ipcRenderer, nativeImage, WebContents } from 'electron'; | ||
10 | import { Menu, MenuItem } from '@electron/remote'; | 11 | import { Menu, MenuItem } from '@electron/remote'; |
11 | import { cmdOrCtrlShortcutKey, isMac } from '../environment'; | 12 | import { cmdOrCtrlShortcutKey, isMac } from '../environment'; |
12 | 13 | ||
@@ -16,7 +17,8 @@ import { openExternalUrl } from '../helpers/url-helpers'; | |||
16 | const { URL } = require('url'); | 17 | const { URL } = require('url'); |
17 | 18 | ||
18 | function matchesWord(string) { | 19 | function matchesWord(string) { |
19 | const regex = /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+/g; | 20 | const regex = |
21 | /[A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+/g; | ||
20 | 22 | ||
21 | return string.match(regex); | 23 | return string.match(regex); |
22 | } | 24 | } |
@@ -56,12 +58,12 @@ module.exports = class ContextMenuBuilder { | |||
56 | /** | 58 | /** |
57 | * Creates an instance of ContextMenuBuilder | 59 | * Creates an instance of ContextMenuBuilder |
58 | * | 60 | * |
59 | * @param {webContents} webContents Current webContents | 61 | * @param {WebContents} webContents Current webContents |
60 | * @param {Boolean} debugMode If true, display the "Inspect Element" menu item. | 62 | * @param {Boolean} debugMode If true, display the "Inspect Element" menu item. |
61 | * @param {function} processMenu If passed, this method will be passed the menu to change | 63 | * @param {function} processMenu If passed, this method will be passed the menu to change |
62 | * it prior to display. Signature: (menu, info) => menu | 64 | * it prior to display. Signature: (menu, info) => menu |
63 | */ | 65 | */ |
64 | constructor(webContents, debugMode = false, processMenu = (m) => m) { | 66 | constructor(webContents, debugMode = false, processMenu = m => m) { |
65 | this.debugMode = debugMode; | 67 | this.debugMode = debugMode; |
66 | this.processMenu = processMenu; | 68 | this.processMenu = processMenu; |
67 | this.menu = null; | 69 | this.menu = null; |
@@ -115,7 +117,10 @@ module.exports = class ContextMenuBuilder { | |||
115 | return this.buildMenuForImage(info); | 117 | return this.buildMenuForImage(info); |
116 | } | 118 | } |
117 | 119 | ||
118 | if (info.isEditable || (info.inputFieldType && info.inputFieldType !== 'none')) { | 120 | if ( |
121 | info.isEditable || | ||
122 | (info.inputFieldType && info.inputFieldType !== 'none') | ||
123 | ) { | ||
119 | return this.buildMenuForTextInput(info); | 124 | return this.buildMenuForTextInput(info); |
120 | } | 125 | } |
121 | 126 | ||
@@ -157,12 +162,17 @@ module.exports = class ContextMenuBuilder { | |||
157 | const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); | 162 | const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); |
158 | 163 | ||
159 | const copyLink = new MenuItem({ | 164 | const copyLink = new MenuItem({ |
160 | label: isEmailAddress ? this.stringTable.copyMail() : this.stringTable.copyLinkUrl(), | 165 | label: isEmailAddress |
166 | ? this.stringTable.copyMail() | ||
167 | : this.stringTable.copyLinkUrl(), | ||
161 | click: () => { | 168 | click: () => { |
162 | // Omit the mailto: portion of the link; we just want the address | 169 | // Omit the mailto: portion of the link; we just want the address |
163 | const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; | 170 | const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; |
164 | clipboard.writeText(url); | 171 | clipboard.writeText(url); |
165 | this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Link URL copied: ${url}`); | 172 | this._sendNotificationOnClipboardEvent( |
173 | menuInfo.clipboardNotifications, | ||
174 | () => `Link URL copied: ${url}`, | ||
175 | ); | ||
166 | }, | 176 | }, |
167 | }); | 177 | }); |
168 | 178 | ||
@@ -250,11 +260,13 @@ module.exports = class ContextMenuBuilder { | |||
250 | const webContents = this.getWebContents(); | 260 | const webContents = this.getWebContents(); |
251 | // Add each spelling suggestion | 261 | // Add each spelling suggestion |
252 | for (const suggestion of menuInfo.dictionarySuggestions) { | 262 | for (const suggestion of menuInfo.dictionarySuggestions) { |
253 | menu.append(new MenuItem({ | 263 | menu.append( |
254 | label: suggestion, | 264 | new MenuItem({ |
255 | // eslint-disable-next-line no-loop-func | 265 | label: suggestion, |
256 | click: () => webContents.replaceMisspelling(suggestion), | 266 | // eslint-disable-next-line no-loop-func |
257 | })); | 267 | click: () => webContents.replaceMisspelling(suggestion), |
268 | }), | ||
269 | ); | ||
258 | } | 270 | } |
259 | 271 | ||
260 | // Allow users to add the misspelled word to the dictionary | 272 | // Allow users to add the misspelled word to the dictionary |
@@ -262,7 +274,10 @@ module.exports = class ContextMenuBuilder { | |||
262 | menu.append( | 274 | menu.append( |
263 | new MenuItem({ | 275 | new MenuItem({ |
264 | label: this.stringTable.addToDictionary(), | 276 | label: this.stringTable.addToDictionary(), |
265 | click: () => webContents.session.addWordToSpellCheckerDictionary(menuInfo.misspelledWord), | 277 | click: () => |
278 | webContents.session.addWordToSpellCheckerDictionary( | ||
279 | menuInfo.misspelledWord, | ||
280 | ), | ||
266 | }), | 281 | }), |
267 | ); | 282 | ); |
268 | } | 283 | } |
@@ -274,7 +289,7 @@ module.exports = class ContextMenuBuilder { | |||
274 | * Adds search-related menu items. | 289 | * Adds search-related menu items. |
275 | */ | 290 | */ |
276 | addSearchItems(menu, menuInfo) { | 291 | addSearchItems(menu, menuInfo) { |
277 | if (!menuInfo.selectionText || menuInfo.selectionText.length < 1) { | 292 | if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { |
278 | return menu; | 293 | return menu; |
279 | } | 294 | } |
280 | 295 | ||
@@ -287,7 +302,9 @@ module.exports = class ContextMenuBuilder { | |||
287 | const webContents = this.getWebContents(); | 302 | const webContents = this.getWebContents(); |
288 | 303 | ||
289 | const lookUpDefinition = new MenuItem({ | 304 | const lookUpDefinition = new MenuItem({ |
290 | label: this.stringTable.lookUpDefinition({ word: menuInfo.selectionText.trim() }), | 305 | label: this.stringTable.lookUpDefinition({ |
306 | word: menuInfo.selectionText.trim(), | ||
307 | }), | ||
291 | click: () => webContents.showDefinitionForSelection(), | 308 | click: () => webContents.showDefinitionForSelection(), |
292 | }); | 309 | }); |
293 | 310 | ||
@@ -295,9 +312,13 @@ module.exports = class ContextMenuBuilder { | |||
295 | } | 312 | } |
296 | 313 | ||
297 | const search = new MenuItem({ | 314 | const search = new MenuItem({ |
298 | label: this.stringTable.searchWith({ searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine] }), | 315 | label: this.stringTable.searchWith({ |
316 | searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine], | ||
317 | }), | ||
299 | click: () => { | 318 | click: () => { |
300 | const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ searchTerm: encodeURIComponent(menuInfo.selectionText) }); | 319 | const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ |
320 | searchTerm: encodeURIComponent(menuInfo.selectionText), | ||
321 | }); | ||
301 | openExternalUrl(url, true); | 322 | openExternalUrl(url, true); |
302 | }, | 323 | }, |
303 | }); | 324 | }); |
@@ -319,10 +340,14 @@ module.exports = class ContextMenuBuilder { | |||
319 | const copyImage = new MenuItem({ | 340 | const copyImage = new MenuItem({ |
320 | label: this.stringTable.copyImage(), | 341 | label: this.stringTable.copyImage(), |
321 | click: () => { | 342 | click: () => { |
322 | const result = this.convertImageToBase64(menuInfo.srcURL, | 343 | const result = this.convertImageToBase64(menuInfo.srcURL, dataURL => |
323 | (dataURL) => clipboard.writeImage(nativeImage.createFromDataURL(dataURL))); | 344 | clipboard.writeImage(nativeImage.createFromDataURL(dataURL)), |
324 | 345 | ); | |
325 | this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image copied from URL: ${menuInfo.srcURL}`); | 346 | |
347 | this._sendNotificationOnClipboardEvent( | ||
348 | menuInfo.clipboardNotifications, | ||
349 | () => `Image copied from URL: ${menuInfo.srcURL}`, | ||
350 | ); | ||
326 | return result; | 351 | return result; |
327 | }, | 352 | }, |
328 | }); | 353 | }); |
@@ -333,7 +358,10 @@ module.exports = class ContextMenuBuilder { | |||
333 | label: this.stringTable.copyImageUrl(), | 358 | label: this.stringTable.copyImageUrl(), |
334 | click: () => { | 359 | click: () => { |
335 | const result = clipboard.writeText(menuInfo.srcURL); | 360 | const result = clipboard.writeText(menuInfo.srcURL); |
336 | this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image URL copied: ${menuInfo.srcURL}`); | 361 | this._sendNotificationOnClipboardEvent( |
362 | menuInfo.clipboardNotifications, | ||
363 | () => `Image URL copied: ${menuInfo.srcURL}`, | ||
364 | ); | ||
337 | return result; | 365 | return result; |
338 | }, | 366 | }, |
339 | }); | 367 | }); |
@@ -345,20 +373,22 @@ module.exports = class ContextMenuBuilder { | |||
345 | const downloadImage = new MenuItem({ | 373 | const downloadImage = new MenuItem({ |
346 | label: this.stringTable.downloadImage(), | 374 | label: this.stringTable.downloadImage(), |
347 | click: () => { | 375 | click: () => { |
348 | const urlWithoutBlob = menuInfo.srcURL.substr(5); | 376 | const urlWithoutBlob = menuInfo.srcURL.slice(5); |
349 | this.convertImageToBase64(menuInfo.srcURL, | 377 | this.convertImageToBase64(menuInfo.srcURL, dataURL => { |
350 | (dataURL) => { | 378 | const url = new window.URL(urlWithoutBlob); |
351 | const url = new window.URL(urlWithoutBlob); | 379 | const fileName = url.pathname.slice(1); |
352 | const fileName = url.pathname.substr(1); | 380 | ipcRenderer.send('download-file', { |
353 | ipcRenderer.send('download-file', { | 381 | content: dataURL, |
354 | content: dataURL, | 382 | fileOptions: { |
355 | fileOptions: { | 383 | name: fileName, |
356 | name: fileName, | 384 | mime: 'image/png', |
357 | mime: 'image/png', | 385 | }, |
358 | }, | ||
359 | }); | ||
360 | }); | 386 | }); |
361 | this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Image downloaded: ${urlWithoutBlob}`); | 387 | }); |
388 | this._sendNotificationOnClipboardEvent( | ||
389 | menuInfo.clipboardNotifications, | ||
390 | () => `Image downloaded: ${urlWithoutBlob}`, | ||
391 | ); | ||
362 | }, | 392 | }, |
363 | }); | 393 | }); |
364 | 394 | ||
@@ -373,12 +403,14 @@ module.exports = class ContextMenuBuilder { | |||
373 | */ | 403 | */ |
374 | addCut(menu, menuInfo) { | 404 | addCut(menu, menuInfo) { |
375 | const webContents = this.getWebContents(); | 405 | const webContents = this.getWebContents(); |
376 | menu.append(new MenuItem({ | 406 | menu.append( |
377 | label: this.stringTable.cut(), | 407 | new MenuItem({ |
378 | accelerator: `${cmdOrCtrlShortcutKey()}+X`, | 408 | label: this.stringTable.cut(), |
379 | enabled: menuInfo.editFlags.canCut, | 409 | accelerator: `${cmdOrCtrlShortcutKey()}+X`, |
380 | click: () => webContents.cut(), | 410 | enabled: menuInfo.editFlags.canCut, |
381 | })); | 411 | click: () => webContents.cut(), |
412 | }), | ||
413 | ); | ||
382 | 414 | ||
383 | return menu; | 415 | return menu; |
384 | } | 416 | } |
@@ -388,12 +420,14 @@ module.exports = class ContextMenuBuilder { | |||
388 | */ | 420 | */ |
389 | addCopy(menu, menuInfo) { | 421 | addCopy(menu, menuInfo) { |
390 | const webContents = this.getWebContents(); | 422 | const webContents = this.getWebContents(); |
391 | menu.append(new MenuItem({ | 423 | menu.append( |
392 | label: this.stringTable.copy(), | 424 | new MenuItem({ |
393 | accelerator: `${cmdOrCtrlShortcutKey()}+C`, | 425 | label: this.stringTable.copy(), |
394 | enabled: menuInfo.editFlags.canCopy, | 426 | accelerator: `${cmdOrCtrlShortcutKey()}+C`, |
395 | click: () => webContents.copy(), | 427 | enabled: menuInfo.editFlags.canCopy, |
396 | })); | 428 | click: () => webContents.copy(), |
429 | }), | ||
430 | ); | ||
397 | 431 | ||
398 | return menu; | 432 | return menu; |
399 | } | 433 | } |
@@ -403,21 +437,23 @@ module.exports = class ContextMenuBuilder { | |||
403 | */ | 437 | */ |
404 | addPaste(menu, menuInfo) { | 438 | addPaste(menu, menuInfo) { |
405 | const webContents = this.getWebContents(); | 439 | const webContents = this.getWebContents(); |
406 | menu.append(new MenuItem({ | 440 | menu.append( |
407 | label: this.stringTable.paste(), | 441 | new MenuItem({ |
408 | accelerator: `${cmdOrCtrlShortcutKey()}+V`, | 442 | label: this.stringTable.paste(), |
409 | enabled: menuInfo.editFlags.canPaste, | 443 | accelerator: `${cmdOrCtrlShortcutKey()}+V`, |
410 | click: () => webContents.paste(), | 444 | enabled: menuInfo.editFlags.canPaste, |
411 | })); | 445 | click: () => webContents.paste(), |
446 | }), | ||
447 | ); | ||
412 | 448 | ||
413 | return menu; | 449 | return menu; |
414 | } | 450 | } |
415 | 451 | ||
416 | addPastePlain(menu, menuInfo) { | 452 | addPastePlain(menu, menuInfo) { |
417 | if ( | 453 | if ( |
418 | menuInfo.editFlags.canPaste | 454 | menuInfo.editFlags.canPaste && |
419 | && !menuInfo.linkText | 455 | !menuInfo.linkText && |
420 | && !menuInfo.hasImageContents | 456 | !menuInfo.hasImageContents |
421 | ) { | 457 | ) { |
422 | const webContents = this.getWebContents(); | 458 | const webContents = this.getWebContents(); |
423 | menu.append( | 459 | menu.append( |
@@ -463,21 +499,21 @@ module.exports = class ContextMenuBuilder { | |||
463 | * @param {String} outputFormat The image format to use, defaults to 'image/png' | 499 | * @param {String} outputFormat The image format to use, defaults to 'image/png' |
464 | */ | 500 | */ |
465 | convertImageToBase64(url, callback, outputFormat = 'image/png') { | 501 | convertImageToBase64(url, callback, outputFormat = 'image/png') { |
466 | let canvas = document.createElement('CANVAS'); | 502 | let canvas = document.createElement('canvas'); |
467 | const ctx = canvas.getContext('2d'); | 503 | const ctx = canvas.getContext('2d'); |
468 | // eslint-disable-next-line no-undef | 504 | // eslint-disable-next-line no-undef |
469 | const img = new Image(); | 505 | const img = new Image(); |
470 | img.crossOrigin = 'Anonymous'; | 506 | img.crossOrigin = 'Anonymous'; |
471 | 507 | ||
472 | img.onload = () => { | 508 | img.addEventListener('load', () => { |
473 | canvas.height = img.height; | 509 | canvas.height = img.height; |
474 | canvas.width = img.width; | 510 | canvas.width = img.width; |
475 | ctx.drawImage(img, 0, 0); | 511 | ctx?.drawImage(img, 0, 0); |
476 | 512 | ||
477 | const dataURL = canvas.toDataURL(outputFormat); | 513 | const dataURL = canvas.toDataURL(outputFormat); |
478 | canvas = null; | 514 | canvas = null; |
479 | callback(dataURL); | 515 | callback(dataURL); |
480 | }; | 516 | }); |
481 | 517 | ||
482 | img.src = url; | 518 | img.src = url; |
483 | } | 519 | } |
@@ -487,12 +523,14 @@ module.exports = class ContextMenuBuilder { | |||
487 | */ | 523 | */ |
488 | goBack(menu) { | 524 | goBack(menu) { |
489 | const webContents = this.getWebContents(); | 525 | const webContents = this.getWebContents(); |
490 | menu.append(new MenuItem({ | 526 | menu.append( |
491 | label: this.stringTable.goBack(), | 527 | new MenuItem({ |
492 | accelerator: `${cmdOrCtrlShortcutKey()}+left`, | 528 | label: this.stringTable.goBack(), |
493 | enabled: webContents.canGoBack(), | 529 | accelerator: `${cmdOrCtrlShortcutKey()}+left`, |
494 | click: () => webContents.goBack(), | 530 | enabled: webContents.canGoBack(), |
495 | })); | 531 | click: () => webContents.goBack(), |
532 | }), | ||
533 | ); | ||
496 | 534 | ||
497 | return menu; | 535 | return menu; |
498 | } | 536 | } |
@@ -502,12 +540,14 @@ module.exports = class ContextMenuBuilder { | |||
502 | */ | 540 | */ |
503 | goForward(menu) { | 541 | goForward(menu) { |
504 | const webContents = this.getWebContents(); | 542 | const webContents = this.getWebContents(); |
505 | menu.append(new MenuItem({ | 543 | menu.append( |
506 | label: this.stringTable.goForward(), | 544 | new MenuItem({ |
507 | accelerator: `${cmdOrCtrlShortcutKey()}+right`, | 545 | label: this.stringTable.goForward(), |
508 | enabled: webContents.canGoForward(), | 546 | accelerator: `${cmdOrCtrlShortcutKey()}+right`, |
509 | click: () => webContents.goForward(), | 547 | enabled: webContents.canGoForward(), |
510 | })); | 548 | click: () => webContents.goForward(), |
549 | }), | ||
550 | ); | ||
511 | 551 | ||
512 | return menu; | 552 | return menu; |
513 | } | 553 | } |
@@ -516,14 +556,19 @@ module.exports = class ContextMenuBuilder { | |||
516 | * Adds the 'copy page url' menu item. | 556 | * Adds the 'copy page url' menu item. |
517 | */ | 557 | */ |
518 | copyPageUrl(menu, menuInfo) { | 558 | copyPageUrl(menu, menuInfo) { |
519 | menu.append(new MenuItem({ | 559 | menu.append( |
520 | label: this.stringTable.copyPageUrl(), | 560 | new MenuItem({ |
521 | enabled: true, | 561 | label: this.stringTable.copyPageUrl(), |
522 | click: () => { | 562 | enabled: true, |
523 | clipboard.writeText(window.location.href); | 563 | click: () => { |
524 | this._sendNotificationOnClipboardEvent(menuInfo.clipboardNotifications, () => `Page URL copied: ${window.location.href}`); | 564 | clipboard.writeText(window.location.href); |
525 | }, | 565 | this._sendNotificationOnClipboardEvent( |
526 | })); | 566 | menuInfo.clipboardNotifications, |
567 | () => `Page URL copied: ${window.location.href}`, | ||
568 | ); | ||
569 | }, | ||
570 | }), | ||
571 | ); | ||
527 | 572 | ||
528 | return menu; | 573 | return menu; |
529 | } | 574 | } |
@@ -533,15 +578,17 @@ module.exports = class ContextMenuBuilder { | |||
533 | */ | 578 | */ |
534 | goToHomePage(menu, menuInfo) { | 579 | goToHomePage(menu, menuInfo) { |
535 | const baseURL = new URL(menuInfo.pageURL); | 580 | const baseURL = new URL(menuInfo.pageURL); |
536 | menu.append(new MenuItem({ | 581 | menu.append( |
537 | label: this.stringTable.goToHomePage(), | 582 | new MenuItem({ |
538 | accelerator: `${cmdOrCtrlShortcutKey()}+Home`, | 583 | label: this.stringTable.goToHomePage(), |
539 | enabled: true, | 584 | accelerator: `${cmdOrCtrlShortcutKey()}+Home`, |
540 | click: () => { | 585 | enabled: true, |
541 | // webContents.loadURL(baseURL.origin); | 586 | click: () => { |
542 | window.location.href = baseURL.origin; | 587 | // webContents.loadURL(baseURL.origin); |
543 | }, | 588 | window.location.href = baseURL.origin; |
544 | })); | 589 | }, |
590 | }), | ||
591 | ); | ||
545 | 592 | ||
546 | return menu; | 593 | return menu; |
547 | } | 594 | } |
@@ -550,13 +597,15 @@ module.exports = class ContextMenuBuilder { | |||
550 | * Adds the 'open in browser' menu item. | 597 | * Adds the 'open in browser' menu item. |
551 | */ | 598 | */ |
552 | openInBrowser(menu, menuInfo) { | 599 | openInBrowser(menu, menuInfo) { |
553 | menu.append(new MenuItem({ | 600 | menu.append( |
554 | label: this.stringTable.openInBrowser(), | 601 | new MenuItem({ |
555 | enabled: true, | 602 | label: this.stringTable.openInBrowser(), |
556 | click: () => { | 603 | enabled: true, |
557 | openExternalUrl(menuInfo.pageURL, true); | 604 | click: () => { |
558 | }, | 605 | openExternalUrl(menuInfo.pageURL, true); |
559 | })); | 606 | }, |
607 | }), | ||
608 | ); | ||
560 | 609 | ||
561 | return menu; | 610 | return menu; |
562 | } | 611 | } |
@@ -566,9 +615,8 @@ module.exports = class ContextMenuBuilder { | |||
566 | return; | 615 | return; |
567 | } | 616 | } |
568 | // eslint-disable-next-line no-new | 617 | // eslint-disable-next-line no-new |
569 | new window.Notification('Data copied into Clipboard', | 618 | new window.Notification('Data copied into Clipboard', { |
570 | { | 619 | body: notificationText(), |
571 | body: notificationText(), | 620 | }); |
572 | }); | ||
573 | } | 621 | } |
574 | }; | 622 | }; |
diff --git a/src/webview/darkmode.ts b/src/webview/darkmode.ts index e06c22f11..7b9407049 100644 --- a/src/webview/darkmode.ts +++ b/src/webview/darkmode.ts | |||
@@ -1,5 +1,3 @@ | |||
1 | /* eslint no-bitwise: ["error", { "int32Hint": true }] */ | ||
2 | |||
3 | import { join } from 'path'; | 1 | import { join } from 'path'; |
4 | import { pathExistsSync, readFileSync } from 'fs-extra'; | 2 | import { pathExistsSync, readFileSync } from 'fs-extra'; |
5 | 3 | ||
@@ -7,7 +5,9 @@ const debug = require('debug')('Ferdi:DarkMode'); | |||
7 | 5 | ||
8 | const chars = [...'abcdefghijklmnopqrstuvwxyz']; | 6 | const chars = [...'abcdefghijklmnopqrstuvwxyz']; |
9 | 7 | ||
10 | const ID = [...Array(20)].map(() => chars[Math.random() * chars.length | 0]).join(''); | 8 | const ID = [...Array.from({ length: 20 })] |
9 | .map(() => chars[Math.trunc(Math.random() * chars.length)]) | ||
10 | .join(''); | ||
11 | 11 | ||
12 | export function injectDarkModeStyle(recipePath: string) { | 12 | export function injectDarkModeStyle(recipePath: string) { |
13 | const darkModeStyle = join(recipePath, 'darkmode.css'); | 13 | const darkModeStyle = join(recipePath, 'darkmode.css'); |
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js index 157da7693..4085b925b 100644 --- a/src/webview/lib/RecipeWebview.js +++ b/src/webview/lib/RecipeWebview.js | |||
@@ -1,5 +1,5 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { exists, pathExistsSync, readFileSync } from 'fs-extra'; | 2 | import { pathExistsSync, readFileSync, existsSync } from 'fs-extra'; |
3 | 3 | ||
4 | const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); | 4 | const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); |
5 | 5 | ||
@@ -62,12 +62,13 @@ class RecipeWebview { | |||
62 | * be an absolute path to the file | 62 | * be an absolute path to the file |
63 | */ | 63 | */ |
64 | injectCSS(...files) { | 64 | injectCSS(...files) { |
65 | files.forEach(async (file) => { | 65 | // eslint-disable-next-line unicorn/no-array-for-each |
66 | files.forEach(file => { | ||
66 | if (pathExistsSync(file)) { | 67 | if (pathExistsSync(file)) { |
67 | const styles = document.createElement('style'); | 68 | const styles = document.createElement('style'); |
68 | styles.innerHTML = readFileSync(file, 'utf8'); | 69 | styles.innerHTML = readFileSync(file, 'utf8'); |
69 | 70 | ||
70 | document.querySelector('head').appendChild(styles); | 71 | document.querySelector('head').append(styles); |
71 | 72 | ||
72 | debug('Append styles', styles); | 73 | debug('Append styles', styles); |
73 | } | 74 | } |
@@ -75,14 +76,16 @@ class RecipeWebview { | |||
75 | } | 76 | } |
76 | 77 | ||
77 | injectJSUnsafe(...files) { | 78 | injectJSUnsafe(...files) { |
78 | Promise.all(files.map(async (file) => { | 79 | Promise.all( |
79 | if (await exists(file)) { | 80 | files.map(file => { |
80 | return readFileSync(file, 'utf8'); | 81 | if (existsSync(file)) { |
81 | } | 82 | return readFileSync(file, 'utf8'); |
82 | debug('Script not found', file); | 83 | } |
83 | return null; | 84 | debug('Script not found', file); |
84 | })).then(async (scripts) => { | 85 | return null; |
85 | const scriptsFound = scripts.filter((script) => script !== null); | 86 | }), |
87 | ).then(scripts => { | ||
88 | const scriptsFound = scripts.filter(script => script !== null); | ||
86 | if (scriptsFound.length > 0) { | 89 | if (scriptsFound.length > 0) { |
87 | debug('Inject scripts to main world', scriptsFound); | 90 | debug('Inject scripts to main world', scriptsFound); |
88 | ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound); | 91 | ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound); |
diff --git a/src/webview/lib/Userscript.js b/src/webview/lib/Userscript.js index 2043d9fff..bed2b1ff8 100644 --- a/src/webview/lib/Userscript.js +++ b/src/webview/lib/Userscript.js | |||
@@ -28,7 +28,7 @@ export default class Userscript { | |||
28 | * | 28 | * |
29 | * @param {*} settings | 29 | * @param {*} settings |
30 | */ | 30 | */ |
31 | // eslint-disable-next-line | 31 | // eslint-disable-next-line camelcase |
32 | internal_setSettings(settings) { | 32 | internal_setSettings(settings) { |
33 | // This is needed to get a clean JS object from the settings itself to provide better accessibility | 33 | // This is needed to get a clean JS object from the settings itself to provide better accessibility |
34 | // Otherwise this will be a mobX instance | 34 | // Otherwise this will be a mobX instance |
@@ -95,9 +95,7 @@ export default class Userscript { | |||
95 | * @param {*} value | 95 | * @param {*} value |
96 | */ | 96 | */ |
97 | set(key, value) { | 97 | set(key, value) { |
98 | window.localStorage.setItem( | 98 | window.localStorage.setItem(`ferdi-user-${key}`, JSON.stringify(value)); |
99 | `ferdi-user-${key}`, JSON.stringify(value), | ||
100 | ); | ||
101 | } | 99 | } |
102 | 100 | ||
103 | /** | 101 | /** |
@@ -107,9 +105,7 @@ export default class Userscript { | |||
107 | * @return Value of the key | 105 | * @return Value of the key |
108 | */ | 106 | */ |
109 | get(key) { | 107 | get(key) { |
110 | return JSON.parse(window.localStorage.getItem( | 108 | return JSON.parse(window.localStorage.getItem(`ferdi-user-${key}`)); |
111 | `ferdi-user-${key}`, | ||
112 | )); | ||
113 | } | 109 | } |
114 | 110 | ||
115 | /** | 111 | /** |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index a3ae4513f..d7032da3f 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -1,3 +1,4 @@ | |||
1 | /* eslint-disable global-require */ | ||
1 | /* eslint-disable import/first */ | 2 | /* eslint-disable import/first */ |
2 | import { contextBridge, desktopCapturer, ipcRenderer } from 'electron'; | 3 | import { contextBridge, desktopCapturer, ipcRenderer } from 'electron'; |
3 | import { BrowserWindow, getCurrentWebContents } from '@electron/remote'; | 4 | import { BrowserWindow, getCurrentWebContents } from '@electron/remote'; |
@@ -104,16 +105,13 @@ window.open = (url, frameName, features) => { | |||
104 | // then overwrite the corresponding field of the window object by injected JS. | 105 | // then overwrite the corresponding field of the window object by injected JS. |
105 | contextBridge.exposeInMainWorld('ferdi', { | 106 | contextBridge.exposeInMainWorld('ferdi', { |
106 | open: window.open, | 107 | open: window.open, |
107 | setBadge: (direct, indirect) => | 108 | setBadge: (direct, indirect) => badgeHandler.setBadge(direct, indirect), |
108 | badgeHandler.setBadge(direct, indirect), | 109 | safeParseInt: text => badgeHandler.safeParseInt(text), |
109 | safeParseInt: (text) => | ||
110 | badgeHandler.safeParseInt(text), | ||
111 | displayNotification: (title, options) => | 110 | displayNotification: (title, options) => |
112 | notificationsHandler.displayNotification(title, options), | 111 | notificationsHandler.displayNotification(title, options), |
113 | clearStorageData: (storageLocations) => | 112 | clearStorageData: storageLocations => |
114 | sessionHandler.clearStorageData(storageLocations), | 113 | sessionHandler.clearStorageData(storageLocations), |
115 | releaseServiceWorkers: () => | 114 | releaseServiceWorkers: () => sessionHandler.releaseServiceWorkers(), |
116 | sessionHandler.releaseServiceWorkers(), | ||
117 | getDisplayMediaSelector, | 115 | getDisplayMediaSelector, |
118 | getCurrentWebContents, | 116 | getCurrentWebContents, |
119 | BrowserWindow, | 117 | BrowserWindow, |
@@ -173,12 +171,12 @@ class RecipeController { | |||
173 | findInPage = null; | 171 | findInPage = null; |
174 | 172 | ||
175 | async initialize() { | 173 | async initialize() { |
176 | Object.keys(this.ipcEvents).forEach(channel => { | 174 | for (const channel of Object.keys(this.ipcEvents)) { |
177 | ipcRenderer.on(channel, (...args) => { | 175 | ipcRenderer.on(channel, (...args) => { |
178 | debug('Received IPC event for channel', channel, 'with', ...args); | 176 | debug('Received IPC event for channel', channel, 'with', ...args); |
179 | this[this.ipcEvents[channel]](...args); | 177 | this[this.ipcEvents[channel]](...args); |
180 | }); | 178 | }); |
181 | }); | 179 | } |
182 | 180 | ||
183 | debug('Send "hello" to host'); | 181 | debug('Send "hello" to host'); |
184 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); | 182 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); |
@@ -210,7 +208,7 @@ class RecipeController { | |||
210 | delete require.cache[require.resolve(modulePath)]; | 208 | delete require.cache[require.resolve(modulePath)]; |
211 | try { | 209 | try { |
212 | this.recipe = new RecipeWebview(badgeHandler, notificationsHandler); | 210 | this.recipe = new RecipeWebview(badgeHandler, notificationsHandler); |
213 | // eslint-disable-next-line | 211 | // eslint-disable-next-line import/no-dynamic-require |
214 | require(modulePath)(this.recipe, { ...config, recipe }); | 212 | require(modulePath)(this.recipe, { ...config, recipe }); |
215 | debug('Initialize Recipe', config, recipe); | 213 | debug('Initialize Recipe', config, recipe); |
216 | 214 | ||
@@ -218,8 +216,8 @@ class RecipeController { | |||
218 | 216 | ||
219 | // Make sure to update the WebView, otherwise the custom darkmode handler may not be used | 217 | // Make sure to update the WebView, otherwise the custom darkmode handler may not be used |
220 | this.update(); | 218 | this.update(); |
221 | } catch (err) { | 219 | } catch (error) { |
222 | console.error('Recipe initialization failed', err); | 220 | console.error('Recipe initialization failed', error); |
223 | } | 221 | } |
224 | 222 | ||
225 | this.loadUserFiles(recipe, config); | 223 | this.loadUserFiles(recipe, config); |
@@ -234,12 +232,12 @@ class RecipeController { | |||
234 | const data = readFileSync(userCss); | 232 | const data = readFileSync(userCss); |
235 | styles.innerHTML += data.toString(); | 233 | styles.innerHTML += data.toString(); |
236 | } | 234 | } |
237 | document.querySelector('head').appendChild(styles); | 235 | document.querySelector('head').append(styles); |
238 | 236 | ||
239 | const userJs = join(recipe.path, 'user.js'); | 237 | const userJs = join(recipe.path, 'user.js'); |
240 | if (pathExistsSync(userJs)) { | 238 | if (pathExistsSync(userJs)) { |
241 | const loadUserJs = () => { | 239 | const loadUserJs = () => { |
242 | // eslint-disable-next-line | 240 | // eslint-disable-next-line import/no-dynamic-require |
243 | const userJsModule = require(userJs); | 241 | const userJsModule = require(userJs); |
244 | 242 | ||
245 | if (typeof userJsModule === 'function') { | 243 | if (typeof userJsModule === 'function') { |
@@ -392,15 +390,14 @@ class RecipeController { | |||
392 | } | 390 | } |
393 | 391 | ||
394 | // Remove dark reader if (universal) dark mode was just disabled | 392 | // Remove dark reader if (universal) dark mode was just disabled |
395 | if (this.universalDarkModeInjected) { | 393 | if ( |
396 | if ( | 394 | this.universalDarkModeInjected && |
397 | !this.settings.app.darkMode || | 395 | (!this.settings.app.darkMode || |
398 | !this.settings.service.isDarkModeEnabled || | 396 | !this.settings.service.isDarkModeEnabled || |
399 | !this.settings.app.universalDarkMode | 397 | !this.settings.app.universalDarkMode) |
400 | ) { | 398 | ) { |
401 | disableDarkMode(); | 399 | disableDarkMode(); |
402 | this.universalDarkModeInjected = false; | 400 | this.universalDarkModeInjected = false; |
403 | } | ||
404 | } | 401 | } |
405 | } | 402 | } |
406 | 403 | ||
diff --git a/src/webview/sessionHandler.ts b/src/webview/sessionHandler.ts index 6a7e62ac5..4961da12b 100644 --- a/src/webview/sessionHandler.ts +++ b/src/webview/sessionHandler.ts | |||
@@ -9,20 +9,21 @@ export class SessionHandler { | |||
9 | const { session } = getCurrentWebContents(); | 9 | const { session } = getCurrentWebContents(); |
10 | session.flushStorageData(); | 10 | session.flushStorageData(); |
11 | session.clearStorageData({ storages: storageLocations }); | 11 | session.clearStorageData({ storages: storageLocations }); |
12 | } catch (err) { | 12 | } catch (error) { |
13 | debug(err); | 13 | debug(error); |
14 | } | 14 | } |
15 | } | 15 | } |
16 | 16 | ||
17 | async releaseServiceWorkers() { | 17 | async releaseServiceWorkers() { |
18 | try { | 18 | try { |
19 | const registrations = await window.navigator.serviceWorker.getRegistrations(); | 19 | const registrations = |
20 | registrations.forEach(r => { | 20 | await window.navigator.serviceWorker.getRegistrations(); |
21 | r.unregister(); | 21 | for (const registration of registrations) { |
22 | registration.unregister(); | ||
22 | debug('ServiceWorker unregistered'); | 23 | debug('ServiceWorker unregistered'); |
23 | }); | 24 | } |
24 | } catch (err) { | 25 | } catch (error) { |
25 | debug(err); | 26 | debug(error); |
26 | } | 27 | } |
27 | } | 28 | } |
28 | } | 29 | } |
diff --git a/src/webview/spellchecker.ts b/src/webview/spellchecker.ts index 30b4ef075..d0f6663d5 100644 --- a/src/webview/spellchecker.ts +++ b/src/webview/spellchecker.ts | |||
@@ -11,7 +11,7 @@ debug('Spellchecker default locale is', defaultLocale); | |||
11 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier: string) { | 11 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier: string) { |
12 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter((key) => key.toLocaleLowerCase() === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | 12 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter((key) => key.toLocaleLowerCase() === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); |
13 | 13 | ||
14 | return locales.length >= 1 ? locales[0] : null; | 14 | return locales.length > 0 ? locales[0] : null; |
15 | } | 15 | } |
16 | 16 | ||
17 | export function switchDict(locale: string) { | 17 | export function switchDict(locale: string) { |
diff --git a/uidev/src/index.tsx b/uidev/src/index.tsx index 99658b184..5aa3979c8 100644 --- a/uidev/src/index.tsx +++ b/uidev/src/index.tsx | |||
@@ -6,4 +6,4 @@ const app = () => ( | |||
6 | <App /> | 6 | <App /> |
7 | ); | 7 | ); |
8 | 8 | ||
9 | render(app(), document.getElementById('root')); | 9 | render(app(), document.querySelector('#root')); |