aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Markus Hatvan <markus_hatvan@aon.at>2021-11-18 17:37:45 +0100
committerLibravatar GitHub <noreply@github.com>2021-11-18 22:07:45 +0530
commitb37a6b07b39c8c7827052dc6fb97f490f1e0f514 (patch)
tree0276e7c51f5ebfa14c566def7aac39f014c2291d
parentUpdate github issues template [skip ci] (diff)
downloadferdium-app-b37a6b07b39c8c7827052dc6fb97f490f1e0f514.tar.gz
ferdium-app-b37a6b07b39c8c7827052dc6fb97f490f1e0f514.tar.zst
ferdium-app-b37a6b07b39c8c7827052dc6fb97f490f1e0f514.zip
chore: convert various files to TS (#2246)
* convert various files to TS * removed outdated docs/example-feature folder * turn off unicorn/no-empty-file * update eslint config
-rw-r--r--.eslintrc.js6
-rw-r--r--docs/example-feature/actions.js10
-rw-r--r--docs/example-feature/api.js5
-rw-r--r--docs/example-feature/state.js14
-rw-r--r--docs/example-feature/store.js36
-rw-r--r--src/I18n.tsx (renamed from src/I18n.js)26
-rw-r--r--src/api/server/ServerApi.ts (renamed from src/api/server/ServerApi.js)98
-rw-r--r--src/api/utils/auth.ts2
-rw-r--r--src/components/services/content/ServiceWebview.js12
-rw-r--r--src/components/ui/Tabs/TabItem.tsx4
-rw-r--r--src/features/communityRecipes/store.ts2
-rw-r--r--src/features/publishDebugInfo/index.ts (renamed from src/features/publishDebugInfo/index.js)0
-rw-r--r--src/i18n/translations.ts24
-rw-r--r--src/internal-server/database/factory.js1
-rw-r--r--src/routes.tsx (renamed from src/routes.js)17
-rw-r--r--src/stores/AppStore.js4
-rw-r--r--src/stores/ServicesStore.js56
-rw-r--r--src/webview/lib/RecipeWebview.ts (renamed from src/webview/lib/RecipeWebview.js)15
-rw-r--r--src/webview/lib/Userscript.ts (renamed from src/webview/lib/Userscript.js)77
19 files changed, 169 insertions, 240 deletions
diff --git a/.eslintrc.js b/.eslintrc.js
index b18927381..f7ca7b620 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -83,6 +83,7 @@ module.exports = {
83 'react/state-in-constructor': 0, 83 'react/state-in-constructor': 0,
84 'react/static-property-placement': 0, 84 'react/static-property-placement': 0,
85 'react/function-component-definition': 0, 85 'react/function-component-definition': 0,
86 'react/jsx-no-useless-fragment': 0,
86 // eslint-plugin-jsx-a11y 87 // eslint-plugin-jsx-a11y
87 'jsx-a11y/click-events-have-key-events': 1, 88 'jsx-a11y/click-events-have-key-events': 1,
88 'jsx-a11y/mouse-events-have-key-events': 1, 89 'jsx-a11y/mouse-events-have-key-events': 1,
@@ -116,6 +117,8 @@ module.exports = {
116 }, 117 },
117 ], 118 ],
118 'unicorn/consistent-destructuring': 0, 119 'unicorn/consistent-destructuring': 0,
120 // INFO: Turned off due to src/internal-server/database/factory.js
121 'unicorn/no-empty-file': 0,
119 // eslint-plugin-prettier 122 // eslint-plugin-prettier
120 'prettier/prettier': 1, 123 'prettier/prettier': 1,
121 }, 124 },
@@ -167,6 +170,7 @@ module.exports = {
167 'react/state-in-constructor': 1, 170 'react/state-in-constructor': 1,
168 'react/sort-comp': 0, 171 'react/sort-comp': 0,
169 'react/function-component-definition': 0, 172 'react/function-component-definition': 0,
173 'react/jsx-no-useless-fragment': 0,
170 // eslint-plugin-jsx-a11y 174 // eslint-plugin-jsx-a11y
171 'jsx-a11y/click-events-have-key-events': 1, 175 'jsx-a11y/click-events-have-key-events': 1,
172 'jsx-a11y/no-static-element-interactions': 1, 176 'jsx-a11y/no-static-element-interactions': 1,
@@ -189,6 +193,8 @@ module.exports = {
189 }, 193 },
190 ], 194 ],
191 'unicorn/consistent-destructuring': 0, 195 'unicorn/consistent-destructuring': 0,
196 // INFO: Turned off due to src/internal-server/database/factory.js
197 'unicorn/no-empty-file': 0,
192 // eslint-plugin-prettier 198 // eslint-plugin-prettier
193 'prettier/prettier': 1, 199 'prettier/prettier': 1,
194 }, 200 },
diff --git a/docs/example-feature/actions.js b/docs/example-feature/actions.js
deleted file mode 100644
index c4d49b708..000000000
--- a/docs/example-feature/actions.js
+++ /dev/null
@@ -1,10 +0,0 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../src/actions/lib/actions';
3
4export const exampleFeatureActions = createActionsFromDefinitions({
5 greet: {
6 name: PropTypes.string.isRequired,
7 },
8}, PropTypes.checkPropTypes);
9
10export default exampleFeatureActions;
diff --git a/docs/example-feature/api.js b/docs/example-feature/api.js
deleted file mode 100644
index d9c769c91..000000000
--- a/docs/example-feature/api.js
+++ /dev/null
@@ -1,5 +0,0 @@
1export default {
2 async getName() {
3 return Promise.resolve('Ferdi');
4 },
5};
diff --git a/docs/example-feature/state.js b/docs/example-feature/state.js
deleted file mode 100644
index 676717da7..000000000
--- a/docs/example-feature/state.js
+++ /dev/null
@@ -1,14 +0,0 @@
1import { observable } from 'mobx';
2
3const defaultState = {
4 name: null,
5 isFeatureActive: false,
6};
7
8export const exampleFeatureState = observable(defaultState);
9
10export function resetState() {
11 Object.assign(exampleFeatureState, defaultState);
12}
13
14export default exampleFeatureState;
diff --git a/docs/example-feature/store.js b/docs/example-feature/store.js
deleted file mode 100644
index 9fc86de36..000000000
--- a/docs/example-feature/store.js
+++ /dev/null
@@ -1,36 +0,0 @@
1import { action, observable, reaction } from 'mobx';
2import Store from '../../src/stores/lib/Store';
3import Request from '../../src/stores/lib/Request';
4
5const debug = require('debug')('Ferdi:feature:EXAMPLE_FEATURE:store');
6
7export class ExampleFeatureStore extends Store {
8 @observable getNameRequest = new Request(this.api, 'getName');
9
10 constructor(stores, api, actions, state) {
11 super(stores, api, actions);
12 this.state = state;
13 }
14
15 setup() {
16 debug('fetching name from api');
17 this.getNameRequest.execute();
18
19 // Update the name on the state when the request resolved
20 reaction(
21 () => (
22 this.getNameRequest.result
23 ),
24 (name) => {
25 this._setName(name);
26 },
27 );
28 }
29
30 @action _setName = (name) => {
31 debug('setting name', name);
32 this.state.name = name;
33 };
34}
35
36export default ExampleFeatureStore;
diff --git a/src/I18n.js b/src/I18n.tsx
index b10c5a94b..39b5273c1 100644
--- a/src/I18n.js
+++ b/src/I18n.tsx
@@ -1,16 +1,24 @@
1import { Component } from 'react'; 1import { Component, ReactNode } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
4import { IntlProvider } from 'react-intl'; 3import { IntlProvider } from 'react-intl';
5 4
6import { oneOrManyChildElements } from './prop-types'; 5import { generatedTranslations } from './i18n/translations';
7import translations from './i18n/translations';
8import UserStore from './stores/UserStore'; 6import UserStore from './stores/UserStore';
9import AppStore from './stores/AppStore'; 7import AppStore from './stores/AppStore';
10 8
9const translations = generatedTranslations();
10
11type Props = {
12 stores: {
13 app: typeof AppStore;
14 user: typeof UserStore;
15 };
16 children: ReactNode;
17};
18
11@inject('stores') 19@inject('stores')
12@observer 20@observer
13class I18N extends Component { 21class I18N extends Component<Props> {
14 componentDidUpdate() { 22 componentDidUpdate() {
15 window['ferdi'].menu.rebuild(); 23 window['ferdi'].menu.rebuild();
16 } 24 }
@@ -31,12 +39,4 @@ class I18N extends Component {
31 } 39 }
32} 40}
33 41
34I18N.wrappedComponent.propTypes = {
35 stores: PropTypes.shape({
36 app: PropTypes.instanceOf(AppStore).isRequired,
37 user: PropTypes.instanceOf(UserStore).isRequired,
38 }).isRequired,
39 children: oneOrManyChildElements.isRequired,
40};
41
42export default I18N; 42export default I18N;
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.ts
index bcc72ffdc..2fd1a8d0d 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.ts
@@ -11,6 +11,7 @@ import {
11 pathExistsSync, 11 pathExistsSync,
12 readJsonSync, 12 readJsonSync,
13 removeSync, 13 removeSync,
14 PathOrFileDescriptor,
14} from 'fs-extra'; 15} from 'fs-extra';
15import fetch from 'electron-fetch'; 16import fetch from 'electron-fetch';
16 17
@@ -40,12 +41,12 @@ const debug = require('debug')('Ferdi:ServerApi');
40module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory()); 41module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory());
41 42
42export default class ServerApi { 43export default class ServerApi {
43 recipePreviews = []; 44 recipePreviews: any[] = [];
44 45
45 recipes = []; 46 recipes: any[] = [];
46 47
47 // User 48 // User
48 async login(email, passwordHash) { 49 async login(email: string, passwordHash: string) {
49 const request = await sendAuthRequest( 50 const request = await sendAuthRequest(
50 `${apiBase()}/auth/login`, 51 `${apiBase()}/auth/login`,
51 { 52 {
@@ -57,7 +58,7 @@ export default class ServerApi {
57 false, 58 false,
58 ); 59 );
59 if (!request.ok) { 60 if (!request.ok) {
60 throw request; 61 throw new Error(request.statusText);
61 } 62 }
62 const u = await request.json(); 63 const u = await request.json();
63 64
@@ -65,7 +66,7 @@ export default class ServerApi {
65 return u.token; 66 return u.token;
66 } 67 }
67 68
68 async signup(data) { 69 async signup(data: any) {
69 const request = await sendAuthRequest( 70 const request = await sendAuthRequest(
70 `${apiBase()}/auth/signup`, 71 `${apiBase()}/auth/signup`,
71 { 72 {
@@ -75,7 +76,7 @@ export default class ServerApi {
75 false, 76 false,
76 ); 77 );
77 if (!request.ok) { 78 if (!request.ok) {
78 throw request; 79 throw new Error(request.statusText);
79 } 80 }
80 const u = await request.json(); 81 const u = await request.json();
81 82
@@ -83,20 +84,20 @@ export default class ServerApi {
83 return u.token; 84 return u.token;
84 } 85 }
85 86
86 async inviteUser(data) { 87 async inviteUser(data: any) {
87 const request = await sendAuthRequest(`${apiBase()}/invite`, { 88 const request = await sendAuthRequest(`${apiBase()}/invite`, {
88 method: 'POST', 89 method: 'POST',
89 body: JSON.stringify(data), 90 body: JSON.stringify(data),
90 }); 91 });
91 if (!request.ok) { 92 if (!request.ok) {
92 throw request; 93 throw new Error(request.statusText);
93 } 94 }
94 95
95 debug('ServerApi::inviteUser'); 96 debug('ServerApi::inviteUser');
96 return true; 97 return true;
97 } 98 }
98 99
99 async retrievePassword(email) { 100 async retrievePassword(email: string) {
100 const request = await sendAuthRequest( 101 const request = await sendAuthRequest(
101 `${apiBase()}/auth/password`, 102 `${apiBase()}/auth/password`,
102 { 103 {
@@ -108,7 +109,7 @@ export default class ServerApi {
108 false, 109 false,
109 ); 110 );
110 if (!request.ok) { 111 if (!request.ok) {
111 throw request; 112 throw new Error(request.statusText);
112 } 113 }
113 const r = await request.json(); 114 const r = await request.json();
114 115
@@ -123,7 +124,7 @@ export default class ServerApi {
123 124
124 const request = await sendAuthRequest(`${apiBase()}/me`); 125 const request = await sendAuthRequest(`${apiBase()}/me`);
125 if (!request.ok) { 126 if (!request.ok) {
126 throw request; 127 throw new Error(request.statusText);
127 } 128 }
128 const data = await request.json(); 129 const data = await request.json();
129 130
@@ -133,13 +134,13 @@ export default class ServerApi {
133 return user; 134 return user;
134 } 135 }
135 136
136 async updateUserInfo(data) { 137 async updateUserInfo(data: any) {
137 const request = await sendAuthRequest(`${apiBase()}/me`, { 138 const request = await sendAuthRequest(`${apiBase()}/me`, {
138 method: 'PUT', 139 method: 'PUT',
139 body: JSON.stringify(data), 140 body: JSON.stringify(data),
140 }); 141 });
141 if (!request.ok) { 142 if (!request.ok) {
142 throw request; 143 throw new Error(request.statusText);
143 } 144 }
144 const updatedData = await request.json(); 145 const updatedData = await request.json();
145 146
@@ -155,7 +156,7 @@ export default class ServerApi {
155 method: 'DELETE', 156 method: 'DELETE',
156 }); 157 });
157 if (!request.ok) { 158 if (!request.ok) {
158 throw request; 159 throw new Error(request.statusText);
159 } 160 }
160 const data = await request.json(); 161 const data = await request.json();
161 162
@@ -171,7 +172,7 @@ export default class ServerApi {
171 172
172 const request = await sendAuthRequest(`${apiBase()}/me/services`); 173 const request = await sendAuthRequest(`${apiBase()}/me/services`);
173 if (!request.ok) { 174 if (!request.ok) {
174 throw request; 175 throw new Error(request.statusText);
175 } 176 }
176 const data = await request.json(); 177 const data = await request.json();
177 178
@@ -181,13 +182,13 @@ export default class ServerApi {
181 return filteredServices; 182 return filteredServices;
182 } 183 }
183 184
184 async createService(recipeId, data) { 185 async createService(recipeId: string, data: { iconFile: any }) {
185 const request = await sendAuthRequest(`${apiBase()}/service`, { 186 const request = await sendAuthRequest(`${apiBase()}/service`, {
186 method: 'POST', 187 method: 'POST',
187 body: JSON.stringify({ recipeId, ...data }), 188 body: JSON.stringify({ recipeId, ...data }),
188 }); 189 });
189 if (!request.ok) { 190 if (!request.ok) {
190 throw request; 191 throw new Error(request.statusText);
191 } 192 }
192 const serviceData = await request.json(); 193 const serviceData = await request.json();
193 194
@@ -208,7 +209,7 @@ export default class ServerApi {
208 return service; 209 return service;
209 } 210 }
210 211
211 async updateService(serviceId, rawData) { 212 async updateService(serviceId: string, rawData: any) {
212 const data = rawData; 213 const data = rawData;
213 214
214 if (data.iconFile) { 215 if (data.iconFile) {
@@ -221,7 +222,7 @@ export default class ServerApi {
221 }); 222 });
222 223
223 if (!request.ok) { 224 if (!request.ok) {
224 throw request; 225 throw new Error(request.statusText);
225 } 226 }
226 227
227 const serviceData = await request.json(); 228 const serviceData = await request.json();
@@ -234,12 +235,13 @@ export default class ServerApi {
234 return service; 235 return service;
235 } 236 }
236 237
237 async uploadServiceIcon(serviceId, icon) { 238 async uploadServiceIcon(serviceId: string, icon: string | Blob) {
238 const formData = new FormData(); 239 const formData = new FormData();
239 formData.append('icon', icon); 240 formData.append('icon', icon);
240 241
241 const requestData = prepareAuthRequest({ 242 const requestData = prepareAuthRequest({
242 method: 'PUT', 243 method: 'PUT',
244 // @ts-expect-error Argument of type '{ method: string; body: FormData; }' is not assignable to parameter of type '{ method: string; }'.
243 body: formData, 245 body: formData,
244 }); 246 });
245 247
@@ -247,11 +249,12 @@ export default class ServerApi {
247 249
248 const request = await window.fetch( 250 const request = await window.fetch(
249 `${apiBase()}/service/${serviceId}`, 251 `${apiBase()}/service/${serviceId}`,
252 // @ts-expect-error Argument of type '{ method: string; } & { mode: string; headers: any; }' is not assignable to parameter of type 'RequestInit | undefined'.
250 requestData, 253 requestData,
251 ); 254 );
252 255
253 if (!request.ok) { 256 if (!request.ok) {
254 throw request; 257 throw new Error(request.statusText);
255 } 258 }
256 259
257 const serviceData = await request.json(); 260 const serviceData = await request.json();
@@ -259,25 +262,25 @@ export default class ServerApi {
259 return serviceData.data; 262 return serviceData.data;
260 } 263 }
261 264
262 async reorderService(data) { 265 async reorderService(data: any) {
263 const request = await sendAuthRequest(`${apiBase()}/service/reorder`, { 266 const request = await sendAuthRequest(`${apiBase()}/service/reorder`, {
264 method: 'PUT', 267 method: 'PUT',
265 body: JSON.stringify(data), 268 body: JSON.stringify(data),
266 }); 269 });
267 if (!request.ok) { 270 if (!request.ok) {
268 throw request; 271 throw new Error(request.statusText);
269 } 272 }
270 const serviceData = await request.json(); 273 const serviceData = await request.json();
271 debug('ServerApi::reorderService resolves', serviceData); 274 debug('ServerApi::reorderService resolves', serviceData);
272 return serviceData; 275 return serviceData;
273 } 276 }
274 277
275 async deleteService(id) { 278 async deleteService(id: string) {
276 const request = await sendAuthRequest(`${apiBase()}/service/${id}`, { 279 const request = await sendAuthRequest(`${apiBase()}/service/${id}`, {
277 method: 'DELETE', 280 method: 'DELETE',
278 }); 281 });
279 if (!request.ok) { 282 if (!request.ok) {
280 throw request; 283 throw new Error(request.statusText);
281 } 284 }
282 const data = await request.json(); 285 const data = await request.json();
283 286
@@ -291,7 +294,7 @@ export default class ServerApi {
291 async getDefaultFeatures() { 294 async getDefaultFeatures() {
292 const request = await sendAuthRequest(`${apiBase()}/features/default`); 295 const request = await sendAuthRequest(`${apiBase()}/features/default`);
293 if (!request.ok) { 296 if (!request.ok) {
294 throw request; 297 throw new Error(request.statusText);
295 } 298 }
296 const data = await request.json(); 299 const data = await request.json();
297 300
@@ -307,7 +310,7 @@ export default class ServerApi {
307 310
308 const request = await sendAuthRequest(`${apiBase()}/features`); 311 const request = await sendAuthRequest(`${apiBase()}/features`);
309 if (!request.ok) { 312 if (!request.ok) {
310 throw request; 313 throw new Error(request.statusText);
311 } 314 }
312 const data = await request.json(); 315 const data = await request.json();
313 316
@@ -341,13 +344,13 @@ export default class ServerApi {
341 return this.recipes; 344 return this.recipes;
342 } 345 }
343 346
344 async getRecipeUpdates(recipeVersions) { 347 async getRecipeUpdates(recipeVersions: any) {
345 const request = await sendAuthRequest(`${apiBase()}/recipes/update`, { 348 const request = await sendAuthRequest(`${apiBase()}/recipes/update`, {
346 method: 'POST', 349 method: 'POST',
347 body: JSON.stringify(recipeVersions), 350 body: JSON.stringify(recipeVersions),
348 }); 351 });
349 if (!request.ok) { 352 if (!request.ok) {
350 throw request; 353 throw new Error(request.statusText);
351 } 354 }
352 const recipes = await request.json(); 355 const recipes = await request.json();
353 debug('ServerApi::getRecipeUpdates resolves', recipes); 356 debug('ServerApi::getRecipeUpdates resolves', recipes);
@@ -357,7 +360,7 @@ export default class ServerApi {
357 // Recipes Previews 360 // Recipes Previews
358 async getRecipePreviews() { 361 async getRecipePreviews() {
359 const request = await sendAuthRequest(`${apiBase()}/recipes`); 362 const request = await sendAuthRequest(`${apiBase()}/recipes`);
360 if (!request.ok) throw request; 363 if (!request.ok) throw new Error(request.statusText);
361 const data = await request.json(); 364 const data = await request.json();
362 const recipePreviews = this._mapRecipePreviewModel(data); 365 const recipePreviews = this._mapRecipePreviewModel(data);
363 debug('ServerApi::getRecipes resolves', recipePreviews); 366 debug('ServerApi::getRecipes resolves', recipePreviews);
@@ -367,7 +370,7 @@ export default class ServerApi {
367 async getFeaturedRecipePreviews() { 370 async getFeaturedRecipePreviews() {
368 // TODO: If we are hitting the internal-server, we need to return an empty list, else we can hit the remote server and get the data 371 // TODO: If we are hitting the internal-server, we need to return an empty list, else we can hit the remote server and get the data
369 const request = await sendAuthRequest(`${apiBase()}/recipes/popular`); 372 const request = await sendAuthRequest(`${apiBase()}/recipes/popular`);
370 if (!request.ok) throw request; 373 if (!request.ok) throw new Error(request.statusText);
371 374
372 const data = await request.json(); 375 const data = await request.json();
373 const recipePreviews = this._mapRecipePreviewModel(data); 376 const recipePreviews = this._mapRecipePreviewModel(data);
@@ -375,10 +378,10 @@ export default class ServerApi {
375 return recipePreviews; 378 return recipePreviews;
376 } 379 }
377 380
378 async searchRecipePreviews(needle) { 381 async searchRecipePreviews(needle: string) {
379 const url = `${apiBase()}/recipes/search?needle=${needle}`; 382 const url = `${apiBase()}/recipes/search?needle=${needle}`;
380 const request = await sendAuthRequest(url); 383 const request = await sendAuthRequest(url);
381 if (!request.ok) throw request; 384 if (!request.ok) throw new Error(request.statusText);
382 385
383 const data = await request.json(); 386 const data = await request.json();
384 const recipePreviews = this._mapRecipePreviewModel(data); 387 const recipePreviews = this._mapRecipePreviewModel(data);
@@ -386,7 +389,7 @@ export default class ServerApi {
386 return recipePreviews; 389 return recipePreviews;
387 } 390 }
388 391
389 async getRecipePackage(recipeId) { 392 async getRecipePackage(recipeId: string) {
390 try { 393 try {
391 const recipesDirectory = userDataRecipesPath(); 394 const recipesDirectory = userDataRecipesPath();
392 const recipeTempDirectory = join(recipesDirectory, 'temp', recipeId); 395 const recipeTempDirectory = join(recipesDirectory, 'temp', recipeId);
@@ -396,7 +399,7 @@ export default class ServerApi {
396 399
397 ensureDirSync(recipeTempDirectory); 400 ensureDirSync(recipeTempDirectory);
398 401
399 let archivePath; 402 let archivePath: PathOrFileDescriptor;
400 403
401 if (pathExistsSync(internalRecipeFile)) { 404 if (pathExistsSync(internalRecipeFile)) {
402 debug('[ServerApi::getRecipePackage] Using internal recipe file'); 405 debug('[ServerApi::getRecipePackage] Using internal recipe file');
@@ -416,6 +419,7 @@ export default class ServerApi {
416 419
417 await sleep(10); 420 await sleep(10);
418 421
422 // @ts-expect-error No overload matches this call.
419 await tar.x({ 423 await tar.x({
420 file: archivePath, 424 file: archivePath,
421 cwd: recipeTempDirectory, 425 cwd: recipeTempDirectory,
@@ -455,7 +459,7 @@ export default class ServerApi {
455 false, 459 false,
456 ); 460 );
457 if (!request.ok) { 461 if (!request.ok) {
458 throw request; 462 throw new Error(request.statusText);
459 } 463 }
460 debug('ServerApi::healthCheck resolves'); 464 debug('ServerApi::healthCheck resolves');
461 } 465 }
@@ -468,7 +472,7 @@ export default class ServerApi {
468 472
469 if (Object.prototype.hasOwnProperty.call(config, 'services')) { 473 if (Object.prototype.hasOwnProperty.call(config, 'services')) {
470 const services = await Promise.all( 474 const services = await Promise.all(
471 config.services.map(async s => { 475 config.services.map(async (s: { service: any }) => {
472 const service = s; 476 const service = s;
473 const request = await sendAuthRequest( 477 const request = await sendAuthRequest(
474 `${apiBase()}/recipes/${s.service}`, 478 `${apiBase()}/recipes/${s.service}`,
@@ -476,6 +480,7 @@ export default class ServerApi {
476 480
477 if (request.status === 200) { 481 if (request.status === 200) {
478 const data = await request.json(); 482 const data = await request.json();
483 // @ts-expect-error Property 'recipe' does not exist on type '{ service: any; }'.
479 service.recipe = new RecipePreviewModel(data); 484 service.recipe = new RecipePreviewModel(data);
480 } 485 }
481 486
@@ -494,18 +499,18 @@ export default class ServerApi {
494 } 499 }
495 500
496 // Helper 501 // Helper
497 async _mapServiceModels(services) { 502 async _mapServiceModels(services: any[]) {
498 const recipes = services.map(s => s.recipeId); 503 const recipes = services.map((s: { recipeId: string }) => s.recipeId);
499 await this._bulkRecipeCheck(recipes); 504 await this._bulkRecipeCheck(recipes);
500 /* eslint-disable no-return-await */ 505 /* eslint-disable no-return-await */
501 return Promise.all( 506 return Promise.all(
502 services.map(async service => await this._prepareServiceModel(service)), 507 services.map(async (service: any) => this._prepareServiceModel(service)),
503 ); 508 );
504 /* eslint-enable no-return-await */ 509 /* eslint-enable no-return-await */
505 } 510 }
506 511
507 async _prepareServiceModel(service) { 512 async _prepareServiceModel(service: { recipeId: string }) {
508 let recipe; 513 let recipe: undefined;
509 try { 514 try {
510 recipe = this.recipes.find(r => r.id === service.recipeId); 515 recipe = this.recipes.find(r => r.id === service.recipeId);
511 516
@@ -521,14 +526,15 @@ export default class ServerApi {
521 } 526 }
522 } 527 }
523 528
524 async _bulkRecipeCheck(unfilteredRecipes) { 529 async _bulkRecipeCheck(unfilteredRecipes: any[]) {
525 // Filter recipe duplicates as we don't need to download 3 Slack recipes 530 // Filter recipe duplicates as we don't need to download 3 Slack recipes
526 const recipes = unfilteredRecipes.filter( 531 const recipes = unfilteredRecipes.filter(
527 (elem, pos, arr) => arr.indexOf(elem) === pos, 532 (elem: any, pos: number, arr: string | any[]) =>
533 arr.indexOf(elem) === pos,
528 ); 534 );
529 535
530 return Promise.all( 536 return Promise.all(
531 recipes.map(async recipeId => { 537 recipes.map(async (recipeId: string) => {
532 let recipe = this.recipes.find(r => r.id === recipeId); 538 let recipe = this.recipes.find(r => r.id === recipeId);
533 539
534 if (!recipe) { 540 if (!recipe) {
@@ -554,7 +560,7 @@ export default class ServerApi {
554 ).catch(error => console.error("Can't load recipe", error)); 560 ).catch(error => console.error("Can't load recipe", error));
555 } 561 }
556 562
557 _mapRecipePreviewModel(recipes) { 563 _mapRecipePreviewModel(recipes: any[]) {
558 return recipes 564 return recipes
559 .map(recipe => { 565 .map(recipe => {
560 try { 566 try {
diff --git a/src/api/utils/auth.ts b/src/api/utils/auth.ts
index 98295d1a4..899881e88 100644
--- a/src/api/utils/auth.ts
+++ b/src/api/utils/auth.ts
@@ -31,7 +31,7 @@ export const prepareAuthRequest = (
31 31
32export const sendAuthRequest = ( 32export const sendAuthRequest = (
33 url: RequestInfo, 33 url: RequestInfo,
34 options: { method: string } | undefined, 34 options?: { method: string; headers?: any; body?: any },
35 auth?: boolean, 35 auth?: boolean,
36) => 36) =>
37 // @ts-expect-error Argument of type '{ method: string; } & { mode: string; headers: any; }' is not assignable to parameter of type 'RequestInit | undefined'. 37 // @ts-expect-error Argument of type '{ method: string; } & { mode: string; headers: any; }' is not assignable to parameter of type 'RequestInit | undefined'.
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 187785f82..c70494edd 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -31,11 +31,13 @@ class ServiceWebview extends Component {
31 debug('Service logged a message:', e.message); 31 debug('Service logged a message:', e.message);
32 }); 32 });
33 this.webview.view.addEventListener('did-navigate', () => { 33 this.webview.view.addEventListener('did-navigate', () => {
34 document.title = `Ferdi - ${this.props.service.name} ${ 34 if (this.props.service._webview) {
35 this.props.service.dialogTitle 35 document.title = `Ferdi - ${this.props.service.name} ${
36 ? ` - ${this.props.service.dialogTitle}` 36 this.props.service.dialogTitle
37 : '' 37 ? ` - ${this.props.service.dialogTitle}`
38 } ${`- ${this.props.service._webview.getTitle()}`}`; 38 : ''
39 } ${`- ${this.props.service._webview.getTitle()}`}`;
40 }
39 }); 41 });
40 } 42 }
41 }, 43 },
diff --git a/src/components/ui/Tabs/TabItem.tsx b/src/components/ui/Tabs/TabItem.tsx
index 81ea0ea2b..9fcc3c41e 100644
--- a/src/components/ui/Tabs/TabItem.tsx
+++ b/src/components/ui/Tabs/TabItem.tsx
@@ -1,3 +1 @@
1export const TabItem = ({ children }) => { export const TabItem = ({ children }) => <>{children}</>;
2 children;
3};
diff --git a/src/features/communityRecipes/store.ts b/src/features/communityRecipes/store.ts
index a8d358ba0..c7a51c311 100644
--- a/src/features/communityRecipes/store.ts
+++ b/src/features/communityRecipes/store.ts
@@ -26,7 +26,7 @@ export class CommunityRecipesStore extends FeatureStore {
26 (recipePreview: { isDevRecipe: boolean; author: any[] }) => { 26 (recipePreview: { isDevRecipe: boolean; author: any[] }) => {
27 // TODO: Need to figure out if this is even necessary/used 27 // TODO: Need to figure out if this is even necessary/used
28 recipePreview.isDevRecipe = !!recipePreview.author.some( 28 recipePreview.isDevRecipe = !!recipePreview.author.some(
29 (author: { email: any }) => 29 (author: { email: string }) =>
30 author.email === this.stores.user.data.email, 30 author.email === this.stores.user.data.email,
31 ); 31 );
32 32
diff --git a/src/features/publishDebugInfo/index.js b/src/features/publishDebugInfo/index.ts
index 43841b530..43841b530 100644
--- a/src/features/publishDebugInfo/index.js
+++ b/src/features/publishDebugInfo/index.ts
diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts
index cc5ecf83a..9b23497e1 100644
--- a/src/i18n/translations.ts
+++ b/src/i18n/translations.ts
@@ -1,16 +1,16 @@
1/* eslint-disable import/no-import-module-exports */
2/* eslint-disable global-require */ 1/* eslint-disable global-require */
3import { APP_LOCALES } from './languages'; 2import { APP_LOCALES } from './languages';
4 3
5const translations = []; 4export const generatedTranslations = () => {
6for (const key of Object.keys(APP_LOCALES)) { 5 const translations = [];
7 try { 6 for (const key of Object.keys(APP_LOCALES)) {
8 // eslint-disable-next-line import/no-dynamic-require 7 try {
9 const translation = require(`./locales/${key}.json`); 8 // eslint-disable-next-line import/no-dynamic-require
10 translations[key] = translation; 9 const translation = require(`./locales/${key}.json`);
11 } catch { 10 translations[key] = translation;
12 console.warn(`Can't find translations for ${key}`); 11 } catch {
12 console.warn(`Can't find translations for ${key}`);
13 }
13 } 14 }
14} 15 return translations;
15 16};
16module.exports = translations;
diff --git a/src/internal-server/database/factory.js b/src/internal-server/database/factory.js
index 8cd45a80d..8534fc20a 100644
--- a/src/internal-server/database/factory.js
+++ b/src/internal-server/database/factory.js
@@ -1,4 +1,3 @@
1/* eslint-disable unicorn/no-empty-file */
2/* 1/*
3|-------------------------------------------------------------------------- 2|--------------------------------------------------------------------------
4| Factory 3| Factory
diff --git a/src/routes.js b/src/routes.tsx
index 9891e5d43..569da06a7 100644
--- a/src/routes.js
+++ b/src/routes.tsx
@@ -1,5 +1,4 @@
1import { Component } from 'react'; 1import { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
4import { Router, Route, IndexRedirect } from 'react-router'; 3import { Router, Route, IndexRedirect } from 'react-router';
5 4
@@ -30,9 +29,16 @@ import { WORKSPACES_ROUTES } from './features/workspaces/constants';
30 29
31import SettingsStore from './stores/SettingsStore'; 30import SettingsStore from './stores/SettingsStore';
32 31
32type Props = {
33 stores: {
34 settings: typeof SettingsStore;
35 };
36 history: any;
37};
38
33@inject('stores', 'actions') 39@inject('stores', 'actions')
34@observer 40@observer
35class Routes extends Component { 41class Routes extends Component<Props> {
36 render() { 42 render() {
37 const { locked } = this.props.stores.settings.app; 43 const { locked } = this.props.stores.settings.app;
38 44
@@ -88,11 +94,4 @@ class Routes extends Component {
88 } 94 }
89} 95}
90 96
91Routes.wrappedComponent.propTypes = {
92 stores: PropTypes.shape({
93 settings: PropTypes.instanceOf(SettingsStore).isRequired,
94 }).isRequired,
95 history: PropTypes.any.isRequired,
96};
97
98export default Routes; 97export default Routes;
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index d652276ea..5881e37a4 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -19,7 +19,7 @@ import Request from './lib/Request';
19import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; 19import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config';
20import { isMac, electronVersion, osRelease } from '../environment'; 20import { isMac, electronVersion, osRelease } from '../environment';
21import { ferdiVersion, userDataPath, ferdiLocale } from '../environment-remote'; 21import { ferdiVersion, userDataPath, ferdiLocale } from '../environment-remote';
22import locales from '../i18n/translations'; 22import { generatedTranslations } from '../i18n/translations';
23import { getLocale } from '../helpers/i18n-helpers'; 23import { getLocale } from '../helpers/i18n-helpers';
24 24
25import { 25import {
@@ -42,6 +42,8 @@ const autoLauncher = new AutoLaunch({
42const CATALINA_NOTIFICATION_HACK_KEY = 42const CATALINA_NOTIFICATION_HACK_KEY =
43 '_temp_askedForCatalinaNotificationPermissions'; 43 '_temp_askedForCatalinaNotificationPermissions';
44 44
45const locales = generatedTranslations();
46
45export default class AppStore extends Store { 47export default class AppStore extends Store {
46 updateStatusTypes = { 48 updateStatusTypes = {
47 CHECKING: 'CHECKING', 49 CHECKING: 'CHECKING',
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 16deb91c5..3d418c4c5 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -655,20 +655,20 @@ export default class ServicesStore extends Store {
655 655
656 @action _setWebviewReference({ serviceId, webview }) { 656 @action _setWebviewReference({ serviceId, webview }) {
657 const service = this.one(serviceId); 657 const service = this.one(serviceId);
658 658 if (service) {
659 service.webview = webview; 659 service.webview = webview;
660 660
661 if (!service.isAttached) { 661 if (!service.isAttached) {
662 debug('Webview is not attached, initializing'); 662 debug('Webview is not attached, initializing');
663 service.initializeWebViewEvents({ 663 service.initializeWebViewEvents({
664 handleIPCMessage: this.actions.service.handleIPCMessage, 664 handleIPCMessage: this.actions.service.handleIPCMessage,
665 openWindow: this.actions.service.openWindow, 665 openWindow: this.actions.service.openWindow,
666 stores: this.stores, 666 stores: this.stores,
667 }); 667 });
668 service.initializeWebViewListener(); 668 service.initializeWebViewListener();
669 }
670 service.isAttached = true;
669 } 671 }
670
671 service.isAttached = true;
672 } 672 }
673 673
674 @action _detachService({ service }) { 674 @action _detachService({ service }) {
@@ -690,20 +690,22 @@ export default class ServicesStore extends Store {
690 // TODO: add checks to not focus service when router path is /settings or /auth 690 // TODO: add checks to not focus service when router path is /settings or /auth
691 const service = this.active; 691 const service = this.active;
692 if (service) { 692 if (service) {
693 document.title = `Ferdi - ${service.name} ${ 693 if (service._webview) {
694 service.dialogTitle ? ` - ${service.dialogTitle}` : '' 694 document.title = `Ferdi - ${service.name} ${
695 } ${service._webview ? `- ${service._webview.getTitle()}` : ''}`; 695 service.dialogTitle ? ` - ${service.dialogTitle}` : ''
696 this._focusService({ serviceId: service.id }); 696 } ${service._webview ? `- ${service._webview.getTitle()}` : ''}`;
697 if (this.stores.settings.app.splitMode && !focusEvent) { 697 this._focusService({ serviceId: service.id });
698 setTimeout(() => { 698 if (this.stores.settings.app.splitMode && !focusEvent) {
699 document 699 setTimeout(() => {
700 .querySelector('.services__webview-wrapper.is-active') 700 document
701 .scrollIntoView({ 701 .querySelector('.services__webview-wrapper.is-active')
702 behavior: 'smooth', 702 .scrollIntoView({
703 block: 'end', 703 behavior: 'smooth',
704 inline: 'nearest', 704 block: 'end',
705 }); 705 inline: 'nearest',
706 }, 10); 706 });
707 }, 10);
708 }
707 } 709 }
708 } else { 710 } else {
709 debug('No service is active'); 711 debug('No service is active');
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.ts
index ebe88ed85..09dc462ed 100644
--- a/src/webview/lib/RecipeWebview.js
+++ b/src/webview/lib/RecipeWebview.ts
@@ -5,6 +5,14 @@ import { pathExistsSync, readFileSync, existsSync } from 'fs-extra';
5const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); 5const debug = require('debug')('Ferdi:Plugin:RecipeWebview');
6 6
7class RecipeWebview { 7class RecipeWebview {
8 badgeHandler: any;
9
10 dialogTitleHandler: any;
11
12 notificationsHandler: any;
13
14 sessionHandler: any;
15
8 constructor( 16 constructor(
9 badgeHandler, 17 badgeHandler,
10 dialogTitleHandler, 18 dialogTitleHandler,
@@ -97,9 +105,12 @@ class RecipeWebview {
97 const styles = document.createElement('style'); 105 const styles = document.createElement('style');
98 styles.innerHTML = readFileSync(file, 'utf8'); 106 styles.innerHTML = readFileSync(file, 'utf8');
99 107
100 document.querySelector('head').append(styles); 108 const head = document.querySelector('head');
101 109
102 debug('Append styles', styles); 110 if (head) {
111 head.append(styles);
112 debug('Append styles', styles);
113 }
103 } 114 }
104 }); 115 });
105 } 116 }
diff --git a/src/webview/lib/Userscript.js b/src/webview/lib/Userscript.ts
index f7bb99206..c50941dc7 100644
--- a/src/webview/lib/Userscript.js
+++ b/src/webview/lib/Userscript.ts
@@ -1,8 +1,12 @@
1import { ipcRenderer } from 'electron'; 1type Recipe = {
2 setBadge: (direct: number, indirect: number) => void;
3 setDialogTitle: (title: string) => void;
4 injectCSS: (css: string | string[]) => void;
5};
2 6
3export default class Userscript { 7export default class Userscript {
4 // Current ./lib/RecipeWebview instance 8 // Current ./lib/RecipeWebview instance
5 recipe = null; 9 recipe: Recipe | null = null;
6 10
7 // Current ./recipe.js instance 11 // Current ./recipe.js instance
8 controller = null; 12 controller = null;
@@ -13,8 +17,6 @@ export default class Userscript {
13 // Ferdi and service settings 17 // Ferdi and service settings
14 settings = {}; 18 settings = {};
15 19
16 settingsUpdateHandler = null;
17
18 constructor(recipe, controller, config) { 20 constructor(recipe, controller, config) {
19 this.recipe = recipe; 21 this.recipe = recipe;
20 this.controller = controller; 22 this.controller = controller;
@@ -29,31 +31,18 @@ export default class Userscript {
29 * @param {*} settings 31 * @param {*} settings
30 */ 32 */
31 // eslint-disable-next-line camelcase 33 // eslint-disable-next-line camelcase
32 internal_setSettings(settings) { 34 internal_setSettings(settings: any) {
33 // This is needed to get a clean JS object from the settings itself to provide better accessibility 35 // 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 36 // Otherwise this will be a mobX instance
35 this.settings = JSON.parse(JSON.stringify(settings)); 37 this.settings = JSON.parse(JSON.stringify(settings));
36
37 if (typeof this.settingsUpdateHandler === 'function') {
38 this.settingsUpdateHandler();
39 }
40 }
41
42 /**
43 * Register a settings handler to be executed when the settings change
44 *
45 * @param {function} handler
46 */
47 onSettingsUpdate(handler) {
48 this.settingsUpdateHandler = handler;
49 } 38 }
50 39
51 /** 40 /**
52 * Set badge count for the current service 41 * Set badge count for the current service
53 * @param {*} direct Direct messages 42 * @param {number} direct Direct messages
54 * @param {*} indirect Indirect messages 43 * @param {number} indirect Indirect messages
55 */ 44 */
56 setBadge(direct = 0, indirect = 0) { 45 setBadge(direct: number = 0, indirect: number = 0) {
57 if (this.recipe && this.recipe.setBadge) { 46 if (this.recipe && this.recipe.setBadge) {
58 this.recipe.setBadge(direct, indirect); 47 this.recipe.setBadge(direct, indirect);
59 } 48 }
@@ -63,7 +52,7 @@ export default class Userscript {
63 * Set active dialog title to the app title 52 * Set active dialog title to the app title
64 * @param {*} title Dialog title 53 * @param {*} title Dialog title
65 */ 54 */
66 setDialogTitle(title) { 55 setDialogTitle(title: string) {
67 if (this.recipe && this.recipe.setDialogTitle) { 56 if (this.recipe && this.recipe.setDialogTitle) {
68 this.recipe.setDialogTitle(title); 57 this.recipe.setDialogTitle(title);
69 } 58 }
@@ -74,8 +63,9 @@ export default class Userscript {
74 * 63 *
75 * @param {...string} files 64 * @param {...string} files
76 */ 65 */
77 injectCSSFiles(...files) { 66 injectCSSFiles(...files: string[]) {
78 if (this.recipe && this.recipe.injectCSS) { 67 if (this.recipe && this.recipe.injectCSS) {
68 // @ts-expect-error A spread argument must either have a tuple type or be passed to a rest parameter.
79 this.recipe.injectCSS(...files); 69 this.recipe.injectCSS(...files);
80 } 70 }
81 } 71 }
@@ -85,54 +75,33 @@ export default class Userscript {
85 * 75 *
86 * @param {string} css 76 * @param {string} css
87 */ 77 */
88 injectCSS(css) { 78 injectCSS(css: string) {
89 const style = document.createElement('style'); 79 const style = document.createElement('style');
90 style.textContent = css; 80 style.textContent = css;
91 document.head.append(style); 81 document.head.append(style);
92 } 82 }
93 83
94 /** 84 /**
95 * Open "Find in Page" popup
96 */
97 openFindInPage() {
98 this.controller.openFindInPage();
99 }
100
101 /**
102 * Set or update value in storage 85 * Set or update value in storage
103 * 86 *
104 * @param {*} key 87 * @param {string} key
105 * @param {*} value 88 * @param {any} value
106 */ 89 */
107 set(key, value) { 90 set(key: string, value: string) {
108 window.localStorage.setItem(`ferdi-user-${key}`, JSON.stringify(value)); 91 window.localStorage.setItem(`ferdi-user-${key}`, JSON.stringify(value));
109 } 92 }
110 93
111 /** 94 /**
112 * Get value from storage 95 * Get value from storage
113 * 96 *
114 * @param {*} key 97 * @param {string} key
115 * @return Value of the key 98 * @return Value of the key
116 */ 99 */
117 get(key) { 100 get(key: string) {
118 return JSON.parse(window.localStorage.getItem(`ferdi-user-${key}`)); 101 const ferdiUserKey = window.localStorage.getItem(`ferdi-user-${key}`);
119 }
120 102
121 /** 103 if (ferdiUserKey) {
122 * Open a URL in an external browser 104 return JSON.parse(ferdiUserKey);
123 * 105 }
124 * @param {*} url
125 */
126 externalOpen(url) {
127 ipcRenderer.sendToHost('new-window', url);
128 }
129
130 /**
131 * Open a URL in the current service
132 *
133 * @param {*} url
134 */
135 internalOpen(url) {
136 window.location.href = url;
137 } 106 }
138} 107}