diff options
author | 2021-11-18 17:37:45 +0100 | |
---|---|---|
committer | 2021-11-18 22:07:45 +0530 | |
commit | b37a6b07b39c8c7827052dc6fb97f490f1e0f514 (patch) | |
tree | 0276e7c51f5ebfa14c566def7aac39f014c2291d /src | |
parent | Update github issues template [skip ci] (diff) | |
download | ferdium-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
Diffstat (limited to 'src')
-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.ts | 2 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 12 | ||||
-rw-r--r-- | src/components/ui/Tabs/TabItem.tsx | 4 | ||||
-rw-r--r-- | src/features/communityRecipes/store.ts | 2 | ||||
-rw-r--r-- | src/features/publishDebugInfo/index.ts (renamed from src/features/publishDebugInfo/index.js) | 0 | ||||
-rw-r--r-- | src/i18n/translations.ts | 24 | ||||
-rw-r--r-- | src/internal-server/database/factory.js | 1 | ||||
-rw-r--r-- | src/routes.tsx (renamed from src/routes.js) | 17 | ||||
-rw-r--r-- | src/stores/AppStore.js | 4 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 56 | ||||
-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 |
14 files changed, 163 insertions, 175 deletions
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 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactNode } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | 2 | import { inject, observer } from 'mobx-react'; |
4 | import { IntlProvider } from 'react-intl'; | 3 | import { IntlProvider } from 'react-intl'; |
5 | 4 | ||
6 | import { oneOrManyChildElements } from './prop-types'; | 5 | import { generatedTranslations } from './i18n/translations'; |
7 | import translations from './i18n/translations'; | ||
8 | import UserStore from './stores/UserStore'; | 6 | import UserStore from './stores/UserStore'; |
9 | import AppStore from './stores/AppStore'; | 7 | import AppStore from './stores/AppStore'; |
10 | 8 | ||
9 | const translations = generatedTranslations(); | ||
10 | |||
11 | type 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 |
13 | class I18N extends Component { | 21 | class 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 | ||
34 | I18N.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 | |||
42 | export default I18N; | 42 | export 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'; |
15 | import fetch from 'electron-fetch'; | 16 | import fetch from 'electron-fetch'; |
16 | 17 | ||
@@ -40,12 +41,12 @@ const debug = require('debug')('Ferdi:ServerApi'); | |||
40 | module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory()); | 41 | module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory()); |
41 | 42 | ||
42 | export default class ServerApi { | 43 | export 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 | ||
32 | export const sendAuthRequest = ( | 32 | export 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 @@ | |||
1 | export 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 */ |
3 | import { APP_LOCALES } from './languages'; | 2 | import { APP_LOCALES } from './languages'; |
4 | 3 | ||
5 | const translations = []; | 4 | export const generatedTranslations = () => { |
6 | for (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 | }; | |
16 | module.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 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | 2 | import { inject, observer } from 'mobx-react'; |
4 | import { Router, Route, IndexRedirect } from 'react-router'; | 3 | import { Router, Route, IndexRedirect } from 'react-router'; |
5 | 4 | ||
@@ -30,9 +29,16 @@ import { WORKSPACES_ROUTES } from './features/workspaces/constants'; | |||
30 | 29 | ||
31 | import SettingsStore from './stores/SettingsStore'; | 30 | import SettingsStore from './stores/SettingsStore'; |
32 | 31 | ||
32 | type 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 |
35 | class Routes extends Component { | 41 | class 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 | ||
91 | Routes.wrappedComponent.propTypes = { | ||
92 | stores: PropTypes.shape({ | ||
93 | settings: PropTypes.instanceOf(SettingsStore).isRequired, | ||
94 | }).isRequired, | ||
95 | history: PropTypes.any.isRequired, | ||
96 | }; | ||
97 | |||
98 | export default Routes; | 97 | export 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'; | |||
19 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; | 19 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; |
20 | import { isMac, electronVersion, osRelease } from '../environment'; | 20 | import { isMac, electronVersion, osRelease } from '../environment'; |
21 | import { ferdiVersion, userDataPath, ferdiLocale } from '../environment-remote'; | 21 | import { ferdiVersion, userDataPath, ferdiLocale } from '../environment-remote'; |
22 | import locales from '../i18n/translations'; | 22 | import { generatedTranslations } from '../i18n/translations'; |
23 | import { getLocale } from '../helpers/i18n-helpers'; | 23 | import { getLocale } from '../helpers/i18n-helpers'; |
24 | 24 | ||
25 | import { | 25 | import { |
@@ -42,6 +42,8 @@ const autoLauncher = new AutoLaunch({ | |||
42 | const CATALINA_NOTIFICATION_HACK_KEY = | 42 | const CATALINA_NOTIFICATION_HACK_KEY = |
43 | '_temp_askedForCatalinaNotificationPermissions'; | 43 | '_temp_askedForCatalinaNotificationPermissions'; |
44 | 44 | ||
45 | const locales = generatedTranslations(); | ||
46 | |||
45 | export default class AppStore extends Store { | 47 | export 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'; | |||
5 | const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); | 5 | const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); |
6 | 6 | ||
7 | class RecipeWebview { | 7 | class 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 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | type Recipe = { |
2 | setBadge: (direct: number, indirect: number) => void; | ||
3 | setDialogTitle: (title: string) => void; | ||
4 | injectCSS: (css: string | string[]) => void; | ||
5 | }; | ||
2 | 6 | ||
3 | export default class Userscript { | 7 | export 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 | } |