aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar André Oliveira <37463445+SpecialAro@users.noreply.github.com>2022-07-19 12:52:31 +0100
committerLibravatar GitHub <noreply@github.com>2022-07-19 12:52:31 +0100
commit3bb1ca7825a0381ddd8dbe7f44f7dcf4a788b165 (patch)
tree6b414b9ef3be7656da1717b0d6def62e95d1fb90
parentfix: remove autoHibernate (diff)
downloadferdium-app-3bb1ca7825a0381ddd8dbe7f44f7dcf4a788b165.tar.gz
ferdium-app-3bb1ca7825a0381ddd8dbe7f44f7dcf4a788b165.tar.zst
ferdium-app-3bb1ca7825a0381ddd8dbe7f44f7dcf4a788b165.zip
Feature: Add Release Notes (#491)
Co-authored-by: Vijay A <vraravam@users.noreply.github.com> Co-authored-by: Ricardo Cino <ricardo@cino.io>
-rw-r--r--package-lock.json252
-rw-r--r--package.json2
-rw-r--r--src/components/AppUpdateInfoBar.tsx23
-rw-r--r--src/components/auth/AuthLayout.jsx5
-rw-r--r--src/components/layout/AppLayout.jsx (renamed from src/components/layout/AppLayout.js)166
-rw-r--r--src/components/settings/SettingsLayout.jsx4
-rw-r--r--src/components/settings/navigation/SettingsNavigation.jsx24
-rw-r--r--src/components/settings/releaseNotes/ReleaseNotesDashboard.tsx99
-rw-r--r--src/components/settings/releaseNotes/ReleaseNotesLayout.tsx79
-rw-r--r--src/components/settings/settings/EditSettingsForm.jsx31
-rw-r--r--src/components/ui/effects/Appear.tsx10
-rw-r--r--src/containers/auth/AuthLayoutContainer.tsx1
-rw-r--r--src/containers/auth/AuthReleaseNotesScreen.tsx107
-rw-r--r--src/containers/layout/AppLayoutContainer.tsx1
-rw-r--r--src/containers/settings/EditSettingsScreen.tsx3
-rw-r--r--src/containers/settings/ReleaseNotesScreen.tsx16
-rw-r--r--src/containers/settings/ReleaseNotesWindow.tsx42
-rw-r--r--src/features/workspaces/components/WorkspaceItem.tsx10
-rw-r--r--src/helpers/update-helpers.ts65
-rw-r--r--src/i18n/locales/en-US.json5
-rw-r--r--src/jsUtils.ts2
-rw-r--r--src/lib/Menu.js12
-rw-r--r--src/routes.tsx22
-rw-r--r--src/stores/AppStore.ts6
-rw-r--r--src/styles/auth.scss59
-rw-r--r--src/styles/settings.scss95
-rw-r--r--test/helpers/update-helpers.test.ts89
-rw-r--r--test/jsUtils.test.ts12
-rw-r--r--tsconfig.json3
29 files changed, 1124 insertions, 121 deletions
diff --git a/package-lock.json b/package-lock.json
index ae32a4ec9..e191b216b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
25 "@krisdages/electron-process-manager": "3.0.0", 25 "@krisdages/electron-process-manager": "3.0.0",
26 "@mdi/js": "6.9.96", 26 "@mdi/js": "6.9.96",
27 "@mdi/react": "1.6.0", 27 "@mdi/react": "1.6.0",
28 "@octokit/core": "4.0.4",
28 "@superwf/mobx-react-router": "7.4.0", 29 "@superwf/mobx-react-router": "7.4.0",
29 "auto-launch": "5.0.5", 30 "auto-launch": "5.0.5",
30 "btoa": "1.2.1", 31 "btoa": "1.2.1",
@@ -47,6 +48,7 @@
47 "languagedetect": "2.0.0", 48 "languagedetect": "2.0.0",
48 "lodash": "4.17.21", 49 "lodash": "4.17.21",
49 "macos-version": "5.2.1", 50 "macos-version": "5.2.1",
51 "markdown-to-jsx": "7.1.7",
50 "mime-types": "2.1.35", 52 "mime-types": "2.1.35",
51 "minimist": "1.2.6", 53 "minimist": "1.2.6",
52 "mobx": "6.6.1", 54 "mobx": "6.6.1",
@@ -4354,6 +4356,118 @@
4354 "node": ">=10" 4356 "node": ">=10"
4355 } 4357 }
4356 }, 4358 },
4359 "node_modules/@octokit/auth-token": {
4360 "version": "3.0.0",
4361 "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.0.tgz",
4362 "integrity": "sha512-MDNFUBcJIptB9At7HiV7VCvU3NcL4GnfCQaP8C5lrxWrRPMJBnemYtehaKSOlaM7AYxeRyj9etenu8LVpSpVaQ==",
4363 "dependencies": {
4364 "@octokit/types": "^6.0.3"
4365 },
4366 "engines": {
4367 "node": ">= 14"
4368 }
4369 },
4370 "node_modules/@octokit/core": {
4371 "version": "4.0.4",
4372 "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.0.4.tgz",
4373 "integrity": "sha512-sUpR/hc4Gc7K34o60bWC7WUH6Q7T6ftZ2dUmepSyJr9PRF76/qqkWjE2SOEzCqLA5W83SaISymwKtxks+96hPQ==",
4374 "dependencies": {
4375 "@octokit/auth-token": "^3.0.0",
4376 "@octokit/graphql": "^5.0.0",
4377 "@octokit/request": "^6.0.0",
4378 "@octokit/request-error": "^3.0.0",
4379 "@octokit/types": "^6.0.3",
4380 "before-after-hook": "^2.2.0",
4381 "universal-user-agent": "^6.0.0"
4382 },
4383 "engines": {
4384 "node": ">= 14"
4385 }
4386 },
4387 "node_modules/@octokit/endpoint": {
4388 "version": "7.0.0",
4389 "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.0.tgz",
4390 "integrity": "sha512-Kz/mIkOTjs9rV50hf/JK9pIDl4aGwAtT8pry6Rpy+hVXkAPhXanNQRxMoq6AeRgDCZR6t/A1zKniY2V1YhrzlQ==",
4391 "dependencies": {
4392 "@octokit/types": "^6.0.3",
4393 "is-plain-object": "^5.0.0",
4394 "universal-user-agent": "^6.0.0"
4395 },
4396 "engines": {
4397 "node": ">= 14"
4398 }
4399 },
4400 "node_modules/@octokit/endpoint/node_modules/is-plain-object": {
4401 "version": "5.0.0",
4402 "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
4403 "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
4404 "engines": {
4405 "node": ">=0.10.0"
4406 }
4407 },
4408 "node_modules/@octokit/graphql": {
4409 "version": "5.0.0",
4410 "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.0.tgz",
4411 "integrity": "sha512-1ZZ8tX4lUEcLPvHagfIVu5S2xpHYXAmgN0+95eAOPoaVPzCfUXJtA5vASafcpWcO86ze0Pzn30TAx72aB2aguQ==",
4412 "dependencies": {
4413 "@octokit/request": "^6.0.0",
4414 "@octokit/types": "^6.0.3",
4415 "universal-user-agent": "^6.0.0"
4416 },
4417 "engines": {
4418 "node": ">= 14"
4419 }
4420 },
4421 "node_modules/@octokit/openapi-types": {
4422 "version": "12.9.0",
4423 "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.9.0.tgz",
4424 "integrity": "sha512-x0wjPEnD487oMjODOSIDdVNBebyrAPE4edY0bsxp/ZX1XPPnWQWXseixbhMa5KcwpbHVdk4qbC3zzedoMdP/YQ=="
4425 },
4426 "node_modules/@octokit/request": {
4427 "version": "6.2.0",
4428 "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.0.tgz",
4429 "integrity": "sha512-7IAmHnaezZrgUqtRShMlByJK33MT9ZDnMRgZjnRrRV9a/jzzFwKGz0vxhFU6i7VMLraYcQ1qmcAOin37Kryq+Q==",
4430 "dependencies": {
4431 "@octokit/endpoint": "^7.0.0",
4432 "@octokit/request-error": "^3.0.0",
4433 "@octokit/types": "^6.16.1",
4434 "is-plain-object": "^5.0.0",
4435 "node-fetch": "^2.6.7",
4436 "universal-user-agent": "^6.0.0"
4437 },
4438 "engines": {
4439 "node": ">= 14"
4440 }
4441 },
4442 "node_modules/@octokit/request-error": {
4443 "version": "3.0.0",
4444 "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.0.tgz",
4445 "integrity": "sha512-WBtpzm9lR8z4IHIMtOqr6XwfkGvMOOILNLxsWvDwtzm/n7f5AWuqJTXQXdDtOvPfTDrH4TPhEvW2qMlR4JFA2w==",
4446 "dependencies": {
4447 "@octokit/types": "^6.0.3",
4448 "deprecation": "^2.0.0",
4449 "once": "^1.4.0"
4450 },
4451 "engines": {
4452 "node": ">= 14"
4453 }
4454 },
4455 "node_modules/@octokit/request/node_modules/is-plain-object": {
4456 "version": "5.0.0",
4457 "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
4458 "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
4459 "engines": {
4460 "node": ">=0.10.0"
4461 }
4462 },
4463 "node_modules/@octokit/types": {
4464 "version": "6.39.0",
4465 "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.39.0.tgz",
4466 "integrity": "sha512-Mq4N9sOAYCitTsBtDdRVrBE80lIrMBhL9Jbrw0d+j96BAzlq4V+GLHFJbHokEsVvO/9tQupQdoFdgVYhD2C8UQ==",
4467 "dependencies": {
4468 "@octokit/openapi-types": "^12.7.0"
4469 }
4470 },
4357 "node_modules/@sideway/address": { 4471 "node_modules/@sideway/address": {
4358 "version": "4.1.4", 4472 "version": "4.1.4",
4359 "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", 4473 "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -7150,6 +7264,11 @@
7150 "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 7264 "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
7151 "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" 7265 "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
7152 }, 7266 },
7267 "node_modules/before-after-hook": {
7268 "version": "2.2.2",
7269 "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
7270 "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
7271 },
7153 "node_modules/big-integer": { 7272 "node_modules/big-integer": {
7154 "version": "1.6.51", 7273 "version": "1.6.51",
7155 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", 7274 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -9599,6 +9718,11 @@
9599 "node": ">= 0.6" 9718 "node": ">= 0.6"
9600 } 9719 }
9601 }, 9720 },
9721 "node_modules/deprecation": {
9722 "version": "2.3.1",
9723 "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
9724 "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
9725 },
9602 "node_modules/destroy": { 9726 "node_modules/destroy": {
9603 "version": "1.0.4", 9727 "version": "1.0.4",
9604 "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 9728 "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
@@ -18159,6 +18283,17 @@
18159 "node": ">=0.10.0" 18283 "node": ">=0.10.0"
18160 } 18284 }
18161 }, 18285 },
18286 "node_modules/markdown-to-jsx": {
18287 "version": "7.1.7",
18288 "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
18289 "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
18290 "engines": {
18291 "node": ">= 10"
18292 },
18293 "peerDependencies": {
18294 "react": ">= 0.14.0"
18295 }
18296 },
18162 "node_modules/matchdep": { 18297 "node_modules/matchdep": {
18163 "version": "2.0.0", 18298 "version": "2.0.0",
18164 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", 18299 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@@ -25483,6 +25618,11 @@
25483 "node": ">=8" 25618 "node": ">=8"
25484 } 25619 }
25485 }, 25620 },
25621 "node_modules/universal-user-agent": {
25622 "version": "6.0.0",
25623 "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
25624 "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
25625 },
25486 "node_modules/universalify": { 25626 "node_modules/universalify": {
25487 "version": "2.0.0", 25627 "version": "2.0.0",
25488 "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 25628 "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -30464,6 +30604,98 @@
30464 } 30604 }
30465 } 30605 }
30466 }, 30606 },
30607 "@octokit/auth-token": {
30608 "version": "3.0.0",
30609 "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.0.tgz",
30610 "integrity": "sha512-MDNFUBcJIptB9At7HiV7VCvU3NcL4GnfCQaP8C5lrxWrRPMJBnemYtehaKSOlaM7AYxeRyj9etenu8LVpSpVaQ==",
30611 "requires": {
30612 "@octokit/types": "^6.0.3"
30613 }
30614 },
30615 "@octokit/core": {
30616 "version": "4.0.4",
30617 "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.0.4.tgz",
30618 "integrity": "sha512-sUpR/hc4Gc7K34o60bWC7WUH6Q7T6ftZ2dUmepSyJr9PRF76/qqkWjE2SOEzCqLA5W83SaISymwKtxks+96hPQ==",
30619 "requires": {
30620 "@octokit/auth-token": "^3.0.0",
30621 "@octokit/graphql": "^5.0.0",
30622 "@octokit/request": "^6.0.0",
30623 "@octokit/request-error": "^3.0.0",
30624 "@octokit/types": "^6.0.3",
30625 "before-after-hook": "^2.2.0",
30626 "universal-user-agent": "^6.0.0"
30627 }
30628 },
30629 "@octokit/endpoint": {
30630 "version": "7.0.0",
30631 "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.0.tgz",
30632 "integrity": "sha512-Kz/mIkOTjs9rV50hf/JK9pIDl4aGwAtT8pry6Rpy+hVXkAPhXanNQRxMoq6AeRgDCZR6t/A1zKniY2V1YhrzlQ==",
30633 "requires": {
30634 "@octokit/types": "^6.0.3",
30635 "is-plain-object": "^5.0.0",
30636 "universal-user-agent": "^6.0.0"
30637 },
30638 "dependencies": {
30639 "is-plain-object": {
30640 "version": "5.0.0",
30641 "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
30642 "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
30643 }
30644 }
30645 },
30646 "@octokit/graphql": {
30647 "version": "5.0.0",
30648 "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.0.tgz",
30649 "integrity": "sha512-1ZZ8tX4lUEcLPvHagfIVu5S2xpHYXAmgN0+95eAOPoaVPzCfUXJtA5vASafcpWcO86ze0Pzn30TAx72aB2aguQ==",
30650 "requires": {
30651 "@octokit/request": "^6.0.0",
30652 "@octokit/types": "^6.0.3",
30653 "universal-user-agent": "^6.0.0"
30654 }
30655 },
30656 "@octokit/openapi-types": {
30657 "version": "12.9.0",
30658 "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.9.0.tgz",
30659 "integrity": "sha512-x0wjPEnD487oMjODOSIDdVNBebyrAPE4edY0bsxp/ZX1XPPnWQWXseixbhMa5KcwpbHVdk4qbC3zzedoMdP/YQ=="
30660 },
30661 "@octokit/request": {
30662 "version": "6.2.0",
30663 "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.0.tgz",
30664 "integrity": "sha512-7IAmHnaezZrgUqtRShMlByJK33MT9ZDnMRgZjnRrRV9a/jzzFwKGz0vxhFU6i7VMLraYcQ1qmcAOin37Kryq+Q==",
30665 "requires": {
30666 "@octokit/endpoint": "^7.0.0",
30667 "@octokit/request-error": "^3.0.0",
30668 "@octokit/types": "^6.16.1",
30669 "is-plain-object": "^5.0.0",
30670 "node-fetch": "^2.6.7",
30671 "universal-user-agent": "^6.0.0"
30672 },
30673 "dependencies": {
30674 "is-plain-object": {
30675 "version": "5.0.0",
30676 "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
30677 "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
30678 }
30679 }
30680 },
30681 "@octokit/request-error": {
30682 "version": "3.0.0",
30683 "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.0.tgz",
30684 "integrity": "sha512-WBtpzm9lR8z4IHIMtOqr6XwfkGvMOOILNLxsWvDwtzm/n7f5AWuqJTXQXdDtOvPfTDrH4TPhEvW2qMlR4JFA2w==",
30685 "requires": {
30686 "@octokit/types": "^6.0.3",
30687 "deprecation": "^2.0.0",
30688 "once": "^1.4.0"
30689 }
30690 },
30691 "@octokit/types": {
30692 "version": "6.39.0",
30693 "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.39.0.tgz",
30694 "integrity": "sha512-Mq4N9sOAYCitTsBtDdRVrBE80lIrMBhL9Jbrw0d+j96BAzlq4V+GLHFJbHokEsVvO/9tQupQdoFdgVYhD2C8UQ==",
30695 "requires": {
30696 "@octokit/openapi-types": "^12.7.0"
30697 }
30698 },
30467 "@sideway/address": { 30699 "@sideway/address": {
30468 "version": "4.1.4", 30700 "version": "4.1.4",
30469 "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", 30701 "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -32721,6 +32953,11 @@
32721 "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 32953 "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
32722 "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" 32954 "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
32723 }, 32955 },
32956 "before-after-hook": {
32957 "version": "2.2.2",
32958 "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
32959 "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
32960 },
32724 "big-integer": { 32961 "big-integer": {
32725 "version": "1.6.51", 32962 "version": "1.6.51",
32726 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", 32963 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -34642,6 +34879,11 @@
34642 "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 34879 "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
34643 "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" 34880 "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="
34644 }, 34881 },
34882 "deprecation": {
34883 "version": "2.3.1",
34884 "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
34885 "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
34886 },
34645 "destroy": { 34887 "destroy": {
34646 "version": "1.0.4", 34888 "version": "1.0.4",
34647 "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 34889 "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
@@ -41317,6 +41559,11 @@
41317 "object-visit": "^1.0.0" 41559 "object-visit": "^1.0.0"
41318 } 41560 }
41319 }, 41561 },
41562 "markdown-to-jsx": {
41563 "version": "7.1.7",
41564 "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
41565 "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w=="
41566 },
41320 "matchdep": { 41567 "matchdep": {
41321 "version": "2.0.0", 41568 "version": "2.0.0",
41322 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", 41569 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@@ -47045,6 +47292,11 @@
47045 "crypto-random-string": "^2.0.0" 47292 "crypto-random-string": "^2.0.0"
47046 } 47293 }
47047 }, 47294 },
47295 "universal-user-agent": {
47296 "version": "6.0.0",
47297 "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
47298 "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
47299 },
47048 "universalify": { 47300 "universalify": {
47049 "version": "2.0.0", 47301 "version": "2.0.0",
47050 "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 47302 "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
diff --git a/package.json b/package.json
index 2d2c5c6da..2ff08af3e 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
60 "@krisdages/electron-process-manager": "3.0.0", 60 "@krisdages/electron-process-manager": "3.0.0",
61 "@mdi/js": "6.9.96", 61 "@mdi/js": "6.9.96",
62 "@mdi/react": "1.6.0", 62 "@mdi/react": "1.6.0",
63 "@octokit/core": "4.0.4",
63 "@superwf/mobx-react-router": "7.4.0", 64 "@superwf/mobx-react-router": "7.4.0",
64 "auto-launch": "5.0.5", 65 "auto-launch": "5.0.5",
65 "btoa": "1.2.1", 66 "btoa": "1.2.1",
@@ -82,6 +83,7 @@
82 "languagedetect": "2.0.0", 83 "languagedetect": "2.0.0",
83 "lodash": "4.17.21", 84 "lodash": "4.17.21",
84 "macos-version": "5.2.1", 85 "macos-version": "5.2.1",
86 "markdown-to-jsx": "7.1.7",
85 "mime-types": "2.1.35", 87 "mime-types": "2.1.35",
86 "minimist": "1.2.6", 88 "minimist": "1.2.6",
87 "mobx": "6.6.1", 89 "mobx": "6.6.1",
diff --git a/src/components/AppUpdateInfoBar.tsx b/src/components/AppUpdateInfoBar.tsx
index 48efbfb7f..3ff488b74 100644
--- a/src/components/AppUpdateInfoBar.tsx
+++ b/src/components/AppUpdateInfoBar.tsx
@@ -2,10 +2,10 @@ import { defineMessages, useIntl } from 'react-intl';
2 2
3import { mdiInformation } from '@mdi/js'; 3import { mdiInformation } from '@mdi/js';
4import InfoBar from './ui/InfoBar'; 4import InfoBar from './ui/InfoBar';
5import { GITHUB_FERDIUM_URL } from '../config';
6import { openExternalUrl } from '../helpers/url-helpers';
7import Icon from './ui/icon'; 5import Icon from './ui/icon';
8 6
7import { onAuthGoToReleaseNotes } from '../helpers/update-helpers';
8
9const messages = defineMessages({ 9const messages = defineMessages({
10 updateAvailable: { 10 updateAvailable: {
11 id: 'infobar.updateAvailable', 11 id: 'infobar.updateAvailable',
@@ -24,9 +24,14 @@ const messages = defineMessages({
24type Props = { 24type Props = {
25 onInstallUpdate: () => void; 25 onInstallUpdate: () => void;
26 onHide: () => void; 26 onHide: () => void;
27 updateVersionParsed: string;
27}; 28};
28 29
29const AppUpdateInfoBar = ({ onInstallUpdate, onHide }: Props) => { 30const AppUpdateInfoBar = ({
31 onInstallUpdate,
32 updateVersionParsed,
33 onHide,
34}: Props) => {
30 const intl = useIntl(); 35 const intl = useIntl();
31 36
32 return ( 37 return (
@@ -41,12 +46,12 @@ const AppUpdateInfoBar = ({ onInstallUpdate, onHide }: Props) => {
41 <button 46 <button
42 className="info-bar__inline-button" 47 className="info-bar__inline-button"
43 type="button" 48 type="button"
44 onClick={() => 49 onClick={() => {
45 openExternalUrl( 50 window.location.href = onAuthGoToReleaseNotes(
46 `${GITHUB_FERDIUM_URL}/ferdium-app/blob/develop/CHANGELOG.md`, 51 window.location.href,
47 true, 52 updateVersionParsed,
48 ) 53 );
49 } 54 }}
50 > 55 >
51 <u>{intl.formatMessage(messages.changelog)}</u> 56 <u>{intl.formatMessage(messages.changelog)}</u>
52 </button> 57 </button>
diff --git a/src/components/auth/AuthLayout.jsx b/src/components/auth/AuthLayout.jsx
index 8a88cedb1..5c87c3080 100644
--- a/src/components/auth/AuthLayout.jsx
+++ b/src/components/auth/AuthLayout.jsx
@@ -14,6 +14,7 @@ import {
14 oneOrManyChildElements, 14 oneOrManyChildElements,
15 globalError as globalErrorPropType, 15 globalError as globalErrorPropType,
16} from '../../prop-types'; 16} from '../../prop-types';
17import { updateVersionParse } from '../../helpers/update-helpers';
17import globalMessages from '../../i18n/globalMessages'; 18import globalMessages from '../../i18n/globalMessages';
18 19
19import { isWindows } from '../../environment'; 20import { isWindows } from '../../environment';
@@ -34,6 +35,7 @@ class AuthLayout extends Component {
34 isFullScreen: PropTypes.bool.isRequired, 35 isFullScreen: PropTypes.bool.isRequired,
35 installAppUpdate: PropTypes.func.isRequired, 36 installAppUpdate: PropTypes.func.isRequired,
36 appUpdateIsDownloaded: PropTypes.bool.isRequired, 37 appUpdateIsDownloaded: PropTypes.bool.isRequired,
38 updateVersion: PropTypes.string.isRequired,
37 }; 39 };
38 40
39 constructor() { 41 constructor() {
@@ -55,10 +57,10 @@ class AuthLayout extends Component {
55 isFullScreen, 57 isFullScreen,
56 installAppUpdate, 58 installAppUpdate,
57 appUpdateIsDownloaded, 59 appUpdateIsDownloaded,
60 updateVersion,
58 } = this.props; 61 } = this.props;
59 62
60 const { intl } = this.props; 63 const { intl } = this.props;
61
62 let serverNameParse = serverName(); 64 let serverNameParse = serverName();
63 serverNameParse = 65 serverNameParse =
64 serverNameParse === 'Custom' ? 'your Custom Server' : serverNameParse; 66 serverNameParse === 'Custom' ? 'your Custom Server' : serverNameParse;
@@ -81,6 +83,7 @@ class AuthLayout extends Component {
81 {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( 83 {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && (
82 <AppUpdateInfoBar 84 <AppUpdateInfoBar
83 onInstallUpdate={installAppUpdate} 85 onInstallUpdate={installAppUpdate}
86 updateVersionParsed={updateVersionParse(updateVersion)}
84 onHide={() => { 87 onHide={() => {
85 this.setState({ shouldShowAppUpdateInfoBar: false }); 88 this.setState({ shouldShowAppUpdateInfoBar: false });
86 }} 89 }}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.jsx
index f7860afc6..685839c0a 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.jsx
@@ -13,6 +13,7 @@ import { Component as BasicAuth } from '../../features/basicAuth';
13import { Component as QuickSwitch } from '../../features/quickSwitch'; 13import { Component as QuickSwitch } from '../../features/quickSwitch';
14import { Component as PublishDebugInfo } from '../../features/publishDebugInfo'; 14import { Component as PublishDebugInfo } from '../../features/publishDebugInfo';
15import ErrorBoundary from '../util/ErrorBoundary'; 15import ErrorBoundary from '../util/ErrorBoundary';
16import { updateVersionParse } from '../../helpers/update-helpers';
16 17
17// import globalMessages from '../../i18n/globalMessages'; 18// import globalMessages from '../../i18n/globalMessages';
18 19
@@ -94,10 +95,14 @@ class AppLayout extends Component {
94 areRequiredRequestsLoading: PropTypes.bool.isRequired, 95 areRequiredRequestsLoading: PropTypes.bool.isRequired,
95 }; 96 };
96 97
97 state = { 98 constructor(props) {
98 shouldShowAppUpdateInfoBar: true, 99 super(props);
99 shouldShowServicesUpdatedInfoBar: true, 100
100 }; 101 this.state = {
102 shouldShowAppUpdateInfoBar: true,
103 shouldShowServicesUpdatedInfoBar: true,
104 };
105 }
101 106
102 render() { 107 render() {
103 const { 108 const {
@@ -115,6 +120,7 @@ class AppLayout extends Component {
115 areRequiredRequestsSuccessful, 120 areRequiredRequestsSuccessful,
116 retryRequiredRequests, 121 retryRequiredRequests,
117 areRequiredRequestsLoading, 122 areRequiredRequestsLoading,
123 updateVersion,
118 } = this.props; 124 } = this.props;
119 125
120 const { intl } = this.props; 126 const { intl } = this.props;
@@ -126,86 +132,90 @@ class AppLayout extends Component {
126 132
127 return ( 133 return (
128 <> 134 <>
129 {isMac && !isFullScreen && ( 135 {isMac && !isFullScreen && <div className="window-draggable" />}
130 <div className="window-draggable" /> 136 <ErrorBoundary>
131 )} 137 <div className="app">
132 <ErrorBoundary> 138 {isWindows && !isFullScreen && (
133 <div className="app"> 139 <TitleBar
134 {isWindows && !isFullScreen && ( 140 menu={window['ferdium'].menu.template}
135 <TitleBar 141 icon="assets/images/logo.svg"
136 menu={window['ferdium'].menu.template} 142 />
137 icon="assets/images/logo.svg" 143 )}
138 /> 144 {isMac && !isFullScreen && (
139 )} 145 <span
140 {isMac && !isFullScreen && ( 146 onDoubleClick={toggleFullScreen}
141 <span 147 className={classes.titleBar}
142 onDoubleClick={toggleFullScreen} 148 />
143 className={classes.titleBar} 149 )}
144 /> 150 <div className={`app__content ${classes.appContent}`}>
145 )} 151 {workspacesDrawer}
146 <div className={`app__content ${classes.appContent}`}> 152 {sidebar}
147 {workspacesDrawer} 153 <div className="app__service">
148 {sidebar} 154 <WorkspaceSwitchingIndicator />
149 <div className="app__service"> 155 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
150 <WorkspaceSwitchingIndicator /> 156 <InfoBar
151 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 157 type="danger"
152 <InfoBar 158 ctaLabel="Try again"
153 type="danger" 159 ctaLoading={areRequiredRequestsLoading}
154 ctaLabel="Try again" 160 sticky
155 ctaLoading={areRequiredRequestsLoading} 161 onClick={retryRequiredRequests}
156 sticky 162 >
157 onClick={retryRequiredRequests} 163 <Icon icon={mdiFlash} />
158 > 164 {intl.formatMessage(messages.requiredRequestsFailed)}
159 <Icon icon={mdiFlash} /> 165 </InfoBar>
160 {intl.formatMessage(messages.requiredRequestsFailed)} 166 )}
161 </InfoBar> 167 {authRequestFailed && (
162 )}
163 {authRequestFailed && (
164 <InfoBar
165 type="danger"
166 ctaLabel="Try again"
167 ctaLoading={areRequiredRequestsLoading}
168 sticky
169 onClick={retryRequiredRequests}
170 >
171 <Icon icon={mdiFlash} />
172 {intl.formatMessage(messages.authRequestFailed)}
173 </InfoBar>
174 )}
175 {automaticUpdates && showServicesUpdatedInfoBar &&
176 this.state.shouldShowServicesUpdatedInfoBar && (
177 <InfoBar 168 <InfoBar
178 type="primary" 169 type="danger"
179 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 170 ctaLabel="Try again"
180 onClick={() => window.location.reload()} 171 ctaLoading={areRequiredRequestsLoading}
181 onHide={() => { 172 sticky
182 this.setState({ 173 onClick={retryRequiredRequests}
183 shouldShowServicesUpdatedInfoBar: false,
184 });
185 }}
186 > 174 >
187 <Icon icon={mdiPowerPlug} /> 175 <Icon icon={mdiFlash} />
188 {intl.formatMessage(messages.servicesUpdated)} 176 {intl.formatMessage(messages.authRequestFailed)}
189 </InfoBar> 177 </InfoBar>
190 )} 178 )}
191 {automaticUpdates && appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( 179 {automaticUpdates &&
192 <AppUpdateInfoBar 180 showServicesUpdatedInfoBar &&
193 onInstallUpdate={installAppUpdate} 181 this.state.shouldShowServicesUpdatedInfoBar && (
194 onHide={() => { 182 <InfoBar
195 this.setState({ shouldShowAppUpdateInfoBar: false }); 183 type="primary"
196 }} 184 ctaLabel={intl.formatMessage(
197 /> 185 messages.buttonReloadServices,
198 )} 186 )}
199 <BasicAuth /> 187 onClick={() => window.location.reload()}
200 <QuickSwitch /> 188 onHide={() => {
201 <PublishDebugInfo /> 189 this.setState({
202 {services} 190 shouldShowServicesUpdatedInfoBar: false,
203 <Outlet /> 191 });
192 }}
193 >
194 <Icon icon={mdiPowerPlug} />
195 {intl.formatMessage(messages.servicesUpdated)}
196 </InfoBar>
197 )}
198 {automaticUpdates &&
199 appUpdateIsDownloaded &&
200 this.state.shouldShowAppUpdateInfoBar && (
201 <AppUpdateInfoBar
202 onInstallUpdate={installAppUpdate}
203 updateVersionParsed={updateVersionParse(updateVersion)}
204 onHide={() => {
205 this.setState({ shouldShowAppUpdateInfoBar: false });
206 }}
207 />
208 )}
209 <BasicAuth />
210 <QuickSwitch />
211 <PublishDebugInfo />
212 {services}
213 <Outlet />
214 </div>
215 <Todos />
204 </div> 216 </div>
205 <Todos />
206 </div> 217 </div>
207 </div> 218 </ErrorBoundary>
208 </ErrorBoundary>
209 </> 219 </>
210 ); 220 );
211 } 221 }
diff --git a/src/components/settings/SettingsLayout.jsx b/src/components/settings/SettingsLayout.jsx
index dea4bb387..989c428f2 100644
--- a/src/components/settings/SettingsLayout.jsx
+++ b/src/components/settings/SettingsLayout.jsx
@@ -8,6 +8,7 @@ import { Outlet } from 'react-router-dom';
8import ErrorBoundary from '../util/ErrorBoundary'; 8import ErrorBoundary from '../util/ErrorBoundary';
9import Appear from '../ui/effects/Appear'; 9import Appear from '../ui/effects/Appear';
10import Icon from '../ui/icon'; 10import Icon from '../ui/icon';
11import { isEscKeyPress } from '../../jsUtils';
11 12
12const messages = defineMessages({ 13const messages = defineMessages({
13 closeSettings: { 14 closeSettings: {
@@ -36,8 +37,7 @@ class SettingsLayout extends Component {
36 } 37 }
37 38
38 handleKeyDown(e) { 39 handleKeyDown(e) {
39 if (e.keyCode === 27) { 40 if (isEscKeyPress(e.keyCode)) {
40 // escape key
41 this.props.closeSettings(); 41 this.props.closeSettings();
42 } 42 }
43 } 43 }
diff --git a/src/components/settings/navigation/SettingsNavigation.jsx b/src/components/settings/navigation/SettingsNavigation.jsx
index bbbe8d888..e1242a7fe 100644
--- a/src/components/settings/navigation/SettingsNavigation.jsx
+++ b/src/components/settings/navigation/SettingsNavigation.jsx
@@ -5,7 +5,11 @@ import { inject, observer } from 'mobx-react';
5import { RouterStore } from '@superwf/mobx-react-router'; 5import { RouterStore } from '@superwf/mobx-react-router';
6 6
7import { NavLink } from 'react-router-dom'; 7import { NavLink } from 'react-router-dom';
8import { LOCAL_SERVER, LIVE_FERDIUM_API, LIVE_FRANZ_API } from '../../../config'; 8import {
9 LOCAL_SERVER,
10 LIVE_FERDIUM_API,
11 LIVE_FRANZ_API,
12} from '../../../config';
9import UIStore from '../../../stores/UIStore'; 13import UIStore from '../../../stores/UIStore';
10import SettingsStore from '../../../stores/SettingsStore'; 14import SettingsStore from '../../../stores/SettingsStore';
11import UserStore from '../../../stores/UserStore'; 15import UserStore from '../../../stores/UserStore';
@@ -32,6 +36,10 @@ const messages = defineMessages({
32 id: 'settings.navigation.team', 36 id: 'settings.navigation.team',
33 defaultMessage: 'Manage Team', 37 defaultMessage: 'Manage Team',
34 }, 38 },
39 releaseNotes: {
40 id: 'settings.navigation.releaseNotes',
41 defaultMessage: 'Release Notes',
42 },
35 supportFerdium: { 43 supportFerdium: {
36 id: 'settings.navigation.supportFerdium', 44 id: 'settings.navigation.supportFerdium',
37 defaultMessage: 'About Ferdium', 45 defaultMessage: 'About Ferdium',
@@ -165,6 +173,16 @@ class SettingsNavigation extends Component {
165 )} 173 )}
166 </NavLink> 174 </NavLink>
167 <NavLink 175 <NavLink
176 to="/settings/releasenotes"
177 className={({ isActive }) =>
178 isActive
179 ? 'settings-navigation__link is-active'
180 : 'settings-navigation__link'
181 }
182 >
183 {intl.formatMessage(messages.releaseNotes)}
184 </NavLink>
185 <NavLink
168 to="/settings/support" 186 to="/settings/support"
169 className={({ isActive }) => 187 className={({ isActive }) =>
170 isActive 188 isActive
@@ -190,4 +208,6 @@ class SettingsNavigation extends Component {
190 } 208 }
191} 209}
192 210
193export default injectIntl(inject('stores', 'actions')(observer(SettingsNavigation))); 211export default injectIntl(
212 inject('stores', 'actions')(observer(SettingsNavigation)),
213);
diff --git a/src/components/settings/releaseNotes/ReleaseNotesDashboard.tsx b/src/components/settings/releaseNotes/ReleaseNotesDashboard.tsx
new file mode 100644
index 000000000..d0be82312
--- /dev/null
+++ b/src/components/settings/releaseNotes/ReleaseNotesDashboard.tsx
@@ -0,0 +1,99 @@
1import { Component } from 'react';
2import { observer } from 'mobx-react';
3import { defineMessages, injectIntl } from 'react-intl';
4import Markdown from 'markdown-to-jsx';
5import { openExternalUrl } from '../../../helpers/url-helpers';
6import { ferdiumVersion } from '../../../environment-remote';
7import {
8 getFerdiumVersion,
9 getUpdateInfoFromGH,
10} from '../../../helpers/update-helpers';
11
12const messages = defineMessages({
13 headline: {
14 id: 'settings.releasenotes.headline',
15 defaultMessage: 'Release Notes',
16 },
17 connectionError: {
18 id: 'settings.releasenotes.connectionError',
19 defaultMessage:
20 'An error occured when connecting to Github, please try again later.',
21 },
22 connectionErrorPageMissing: {
23 id: 'settings.releasenotes.connectionErrorPageMissing',
24 defaultMessage:
25 'An error occured when connecting to Github, the page you are looking for is missing.',
26 },
27});
28
29interface IProps {
30 intl: any;
31}
32
33class ReleaseNotesDashboard extends Component<IProps> {
34 state = {
35 data: '',
36 };
37
38 constructor(props) {
39 super(props);
40
41 this.state = { data: '' };
42 }
43
44 async componentDidMount() {
45 const { intl } = this.props;
46
47 const data = await getUpdateInfoFromGH(
48 window.location.href,
49 ferdiumVersion,
50 intl,
51 );
52
53 this.setState({
54 data,
55 });
56
57 for (const link of document.querySelectorAll('.releasenotes__body a')) {
58 link.addEventListener('click', this.handleClick.bind(this), false);
59 }
60 }
61
62 handleClick(e) {
63 e.preventDefault();
64 openExternalUrl(e.target.href);
65 }
66
67 componentWillUnmount() {
68 document.removeEventListener(
69 'click',
70 // eslint-disable-next-line unicorn/no-invalid-remove-event-listener
71 this.handleClick.bind(this),
72 false,
73 );
74 }
75
76 render() {
77 const { intl } = this.props;
78
79 const { data } = this.state;
80 return (
81 <div className="settings__main">
82 <div className="settings__header">
83 <span className="settings__header-item">
84 Ferdium {getFerdiumVersion(window.location.href, ferdiumVersion)}{' '}
85 {' | '}
86 </span>
87 <span className="settings__header-item__secondary">
88 {intl.formatMessage(messages.headline)}
89 </span>
90 </div>
91 <div className="settings__body releasenotes__body">
92 <Markdown options={{ wrapper: 'article' }}>{data}</Markdown>
93 </div>
94 </div>
95 );
96 }
97}
98
99export default injectIntl(observer(ReleaseNotesDashboard));
diff --git a/src/components/settings/releaseNotes/ReleaseNotesLayout.tsx b/src/components/settings/releaseNotes/ReleaseNotesLayout.tsx
new file mode 100644
index 000000000..ee0ba75a8
--- /dev/null
+++ b/src/components/settings/releaseNotes/ReleaseNotesLayout.tsx
@@ -0,0 +1,79 @@
1import { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import { defineMessages, injectIntl } from 'react-intl';
4
5import { mdiClose } from '@mdi/js';
6import { Outlet } from 'react-router-dom';
7import { StoresProps } from '../../../@types/ferdium-components.types';
8import ErrorBoundary from '../../util/ErrorBoundary';
9import Appear from '../../ui/effects/Appear';
10import Icon from '../../ui/icon';
11import { isEscKeyPress } from '../../../jsUtils';
12
13const messages = defineMessages({
14 closeSettings: {
15 id: 'settings.app.closeSettings',
16 defaultMessage: 'Close settings',
17 },
18});
19
20interface IProps extends StoresProps {
21 intl: any;
22}
23
24class ReleaseNotesLayout extends Component<IProps> {
25 componentDidMount() {
26 document.addEventListener('keydown', this.handleKeyDown.bind(this), false);
27 }
28
29 componentWillUnmount() {
30 document.removeEventListener(
31 'keydown',
32 // eslint-disable-next-line unicorn/no-invalid-remove-event-listener
33 this.handleKeyDown.bind(this),
34 false,
35 );
36 }
37
38 handleKeyDown(e) {
39 if (isEscKeyPress(e.keyCode)) {
40 this.props.actions.ui.closeSettings();
41 }
42 }
43
44 render() {
45 const { closeSettings } = this.props.actions.ui;
46
47 const { intl } = this.props;
48
49 return (
50 <Appear transitionName="fadeIn-fast">
51 <div className="settings-wrapper">
52 <ErrorBoundary>
53 <button
54 type="button"
55 className="settings-wrapper__action"
56 onClick={closeSettings}
57 aria-label={intl.formatMessage(messages.closeSettings)}
58 />
59 <div className="settings franz-form">
60 <Outlet />
61 <button
62 type="button"
63 className="settings__close"
64 onClick={closeSettings}
65 aria-label={intl.formatMessage(messages.closeSettings)}
66 >
67 <Icon icon={mdiClose} size={1.35} />
68 </button>
69 </div>
70 </ErrorBoundary>
71 </div>
72 </Appear>
73 );
74 }
75}
76
77export default injectIntl<'intl', IProps>(
78 inject('stores', 'actions')(observer(ReleaseNotesLayout)),
79);
diff --git a/src/components/settings/settings/EditSettingsForm.jsx b/src/components/settings/settings/EditSettingsForm.jsx
index cb1d261f8..a10d89570 100644
--- a/src/components/settings/settings/EditSettingsForm.jsx
+++ b/src/components/settings/settings/EditSettingsForm.jsx
@@ -15,6 +15,13 @@ import Input from '../../ui/Input';
15import ColorPickerInput from '../../ui/ColorPickerInput'; 15import ColorPickerInput from '../../ui/ColorPickerInput';
16import Infobox from '../../ui/Infobox'; 16import Infobox from '../../ui/Infobox';
17import { H1, H2, H3, H5 } from '../../ui/headline'; 17import { H1, H2, H3, H5 } from '../../ui/headline';
18import {
19 ferdiumVersion,
20 userDataPath,
21 userDataRecipesPath,
22} from '../../../environment-remote';
23
24import { updateVersionParse } from '../../../helpers/update-helpers';
18 25
19import { 26import {
20 DEFAULT_ACCENT_COLOR, 27 DEFAULT_ACCENT_COLOR,
@@ -25,11 +32,6 @@ import {
25 SPLIT_COLUMNS_MIN, 32 SPLIT_COLUMNS_MIN,
26} from '../../../config'; 33} from '../../../config';
27import { isMac, isWindows, lockFerdiumShortcutKey } from '../../../environment'; 34import { isMac, isWindows, lockFerdiumShortcutKey } from '../../../environment';
28import {
29 ferdiumVersion,
30 userDataPath,
31 userDataRecipesPath,
32} from '../../../environment-remote';
33import { openExternalUrl, openPath } from '../../../helpers/url-helpers'; 35import { openExternalUrl, openPath } from '../../../helpers/url-helpers';
34import globalMessages from '../../../i18n/globalMessages'; 36import globalMessages from '../../../i18n/globalMessages';
35import Icon from '../../ui/icon'; 37import Icon from '../../ui/icon';
@@ -213,6 +215,10 @@ const messages = defineMessages({
213 id: 'settings.app.buttonInstallUpdate', 215 id: 'settings.app.buttonInstallUpdate',
214 defaultMessage: 'Restart & install update', 216 defaultMessage: 'Restart & install update',
215 }, 217 },
218 buttonShowChangelog: {
219 id: 'settings.app.buttonShowChangelog',
220 defaultMessage: 'Show changelog',
221 },
216 updateStatusSearching: { 222 updateStatusSearching: {
217 id: 'settings.app.updateStatusSearching', 223 id: 'settings.app.updateStatusSearching',
218 defaultMessage: 'Searching for updates...', 224 defaultMessage: 'Searching for updates...',
@@ -328,6 +334,7 @@ class EditSettingsForm extends Component {
328 checkForUpdates, 334 checkForUpdates,
329 installUpdate, 335 installUpdate,
330 form, 336 form,
337 updateVersion,
331 isCheckingForUpdates, 338 isCheckingForUpdates,
332 isAdaptableDarkModeEnabled, 339 isAdaptableDarkModeEnabled,
333 isUseGrayscaleServicesEnabled, 340 isUseGrayscaleServicesEnabled,
@@ -1002,6 +1009,20 @@ class EditSettingsForm extends Component {
1002 loaded={!isCheckingForUpdates || !isUpdateAvailable} 1009 loaded={!isCheckingForUpdates || !isUpdateAvailable}
1003 /> 1010 />
1004 )} 1011 )}
1012 {(isUpdateAvailable || updateIsReadyToInstall) && (
1013 <Button
1014 className="settings__updates__changelog-button"
1015 label={intl.formatMessage(
1016 messages.buttonShowChangelog,
1017 )}
1018 onClick={() => {
1019 window.location.href = `#/releasenotes${updateVersionParse(
1020 updateVersion,
1021 )}`;
1022 }}
1023 />
1024 )}
1025 <br />
1005 <br /> 1026 <br />
1006 </div> 1027 </div>
1007 <p> 1028 <p>
diff --git a/src/components/ui/effects/Appear.tsx b/src/components/ui/effects/Appear.tsx
index 117c02f97..228c7888a 100644
--- a/src/components/ui/effects/Appear.tsx
+++ b/src/components/ui/effects/Appear.tsx
@@ -1,10 +1,10 @@
1import { ReactChildren, useEffect, useState } from 'react'; 1import { ReactNode, useEffect, useState } from 'react';
2import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 2import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
3 3
4type Props = { 4type Props = {
5 children: ReactChildren; 5 children: ReactNode;
6 transitionName: string; 6 transitionName: string;
7 className: string; 7 className?: string;
8}; 8};
9const Appear = ({ 9const Appear = ({
10 children, 10 children,
@@ -36,4 +36,8 @@ const Appear = ({
36 ); 36 );
37}; 37};
38 38
39Appear.defaultProps = {
40 className: '',
41};
42
39export default Appear; 43export default Appear;
diff --git a/src/containers/auth/AuthLayoutContainer.tsx b/src/containers/auth/AuthLayoutContainer.tsx
index 8d31cfb48..6fc6713f1 100644
--- a/src/containers/auth/AuthLayoutContainer.tsx
+++ b/src/containers/auth/AuthLayoutContainer.tsx
@@ -49,6 +49,7 @@ class AuthLayoutContainer extends Component<AuthLayoutContainerProps> {
49 appUpdateIsDownloaded={ 49 appUpdateIsDownloaded={
50 app.updateStatus === app.updateStatusTypes.DOWNLOADED 50 app.updateStatus === app.updateStatusTypes.DOWNLOADED
51 } 51 }
52 updateVersion={app.updateVersion}
52 > 53 >
53 <Outlet /> 54 <Outlet />
54 </AuthLayout> 55 </AuthLayout>
diff --git a/src/containers/auth/AuthReleaseNotesScreen.tsx b/src/containers/auth/AuthReleaseNotesScreen.tsx
new file mode 100644
index 000000000..c717529fa
--- /dev/null
+++ b/src/containers/auth/AuthReleaseNotesScreen.tsx
@@ -0,0 +1,107 @@
1import { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3
4import { defineMessages, injectIntl } from 'react-intl';
5import Markdown from 'markdown-to-jsx';
6import { mdiArrowLeftCircle } from '@mdi/js';
7import { openExternalUrl } from '../../helpers/url-helpers';
8import Icon from '../../components/ui/icon';
9import { ferdiumVersion } from '../../environment-remote';
10import {
11 getFerdiumVersion,
12 getUpdateInfoFromGH,
13} from '../../helpers/update-helpers';
14
15const messages = defineMessages({
16 headline: {
17 id: 'settings.releasenotes.headline',
18 defaultMessage: 'Release Notes',
19 },
20});
21
22interface IProps {
23 intl: any;
24}
25
26class AuthReleaseNotesScreen extends Component<IProps> {
27 state = {
28 data: '',
29 };
30
31 constructor(props) {
32 super(props);
33
34 this.state = { data: '' };
35 }
36
37 async componentDidMount() {
38 const { intl } = this.props;
39
40 const data = await getUpdateInfoFromGH(
41 window.location.href,
42 ferdiumVersion,
43 intl,
44 );
45
46 this.setState({
47 data,
48 });
49
50 for (const link of document.querySelectorAll('.releasenotes__body a')) {
51 link.addEventListener('click', this.handleClick.bind(this), false);
52 }
53 }
54
55 handleClick(e) {
56 e.preventDefault();
57 openExternalUrl(e.target.href);
58 }
59
60 componentWillUnmount() {
61 document.removeEventListener(
62 'click',
63 // eslint-disable-next-line unicorn/no-invalid-remove-event-listener
64 this.handleClick.bind(this),
65 false,
66 );
67 }
68
69 render() {
70 const { intl } = this.props;
71
72 const { data } = this.state;
73 return (
74 <div className="auth__container auth__container--releasenotes">
75 <div className="auth__main--releasenotes">
76 <div className="auth__header">
77 <span className="auth__header-item">
78 Ferdium {getFerdiumVersion(window.location.href, ferdiumVersion)}{' '}
79 {' | '}
80 </span>
81 <span className="auth__header-item__secondary">
82 {intl.formatMessage(messages.headline)}
83 </span>
84 </div>
85 <div className="auth__body releasenotes__body">
86 <Markdown options={{ wrapper: 'article' }}>{data}</Markdown>
87 </div>
88 <div className="auth__help">
89 <button
90 type="button"
91 onClick={() => {
92 // For some reason <Link> doesn't work here. So we hard code the path to take us to the Welcome Screen
93 window.location.href = '#/auth/welcome';
94 }}
95 >
96 <Icon icon={mdiArrowLeftCircle} size={1.5} />
97 </button>
98 </div>
99 </div>
100 </div>
101 );
102 }
103}
104
105export default injectIntl<'intl', IProps>(
106 inject('stores', 'actions')(observer(AuthReleaseNotesScreen)),
107);
diff --git a/src/containers/layout/AppLayoutContainer.tsx b/src/containers/layout/AppLayoutContainer.tsx
index a4857a426..9c03b0ba4 100644
--- a/src/containers/layout/AppLayoutContainer.tsx
+++ b/src/containers/layout/AppLayoutContainer.tsx
@@ -157,6 +157,7 @@ class AppLayoutContainer extends Component<AppLayoutContainerProps> {
157 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful} 157 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful}
158 retryRequiredRequests={retryRequiredRequests} 158 retryRequiredRequests={retryRequiredRequests}
159 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 159 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
160 updateVersion={app.updateVersion}
160 > 161 >
161 <Outlet /> 162 <Outlet />
162 </AppLayout> 163 </AppLayout>
diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx
index a6c4561dd..fbbed629a 100644
--- a/src/containers/settings/EditSettingsScreen.tsx
+++ b/src/containers/settings/EditSettingsScreen.tsx
@@ -853,6 +853,7 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> {
853 const { app } = this.props.stores; 853 const { app } = this.props.stores;
854 const { 854 const {
855 updateStatus, 855 updateStatus,
856 updateVersion,
856 updateStatusTypes, 857 updateStatusTypes,
857 isClearingAllCache, 858 isClearingAllCache,
858 lockingFeatureEnabled, 859 lockingFeatureEnabled,
@@ -860,13 +861,13 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> {
860 const { checkForUpdates, installUpdate, clearAllCache } = 861 const { checkForUpdates, installUpdate, clearAllCache } =
861 this.props.actions.app; 862 this.props.actions.app;
862 const form = this.prepareForm(); 863 const form = this.prepareForm();
863
864 return ( 864 return (
865 <ErrorBoundary> 865 <ErrorBoundary>
866 <EditSettingsForm 866 <EditSettingsForm
867 form={form} 867 form={form}
868 checkForUpdates={checkForUpdates} 868 checkForUpdates={checkForUpdates}
869 installUpdate={installUpdate} 869 installUpdate={installUpdate}
870 updateVersion={updateVersion}
870 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING} 871 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
871 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE} 872 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
872 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE} 873 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
diff --git a/src/containers/settings/ReleaseNotesScreen.tsx b/src/containers/settings/ReleaseNotesScreen.tsx
new file mode 100644
index 000000000..c3014d187
--- /dev/null
+++ b/src/containers/settings/ReleaseNotesScreen.tsx
@@ -0,0 +1,16 @@
1import { Component, ReactElement } from 'react';
2
3import ReleaseNotes from '../../components/settings/releaseNotes/ReleaseNotesDashboard';
4import ErrorBoundary from '../../components/util/ErrorBoundary';
5
6class ReleaseNotesScreen extends Component {
7 render(): ReactElement {
8 return (
9 <ErrorBoundary>
10 <ReleaseNotes />
11 </ErrorBoundary>
12 );
13 }
14}
15
16export default ReleaseNotesScreen;
diff --git a/src/containers/settings/ReleaseNotesWindow.tsx b/src/containers/settings/ReleaseNotesWindow.tsx
new file mode 100644
index 000000000..3e43727d0
--- /dev/null
+++ b/src/containers/settings/ReleaseNotesWindow.tsx
@@ -0,0 +1,42 @@
1import { inject, observer } from 'mobx-react';
2import { Component, ReactPortal } from 'react';
3import ReactDOM from 'react-dom';
4import { Outlet } from 'react-router-dom';
5
6import { StoresProps } from '../../@types/ferdium-components.types';
7import Layout from '../../components/settings/releaseNotes/ReleaseNotesLayout';
8import ErrorBoundary from '../../components/util/ErrorBoundary';
9
10class SettingsContainer extends Component<StoresProps> {
11 portalRoot: any;
12
13 el: HTMLDivElement;
14
15 constructor(props: StoresProps) {
16 super(props);
17
18 this.portalRoot = document.querySelector('#portalContainer');
19 this.el = document.createElement('div');
20 }
21
22 componentDidMount(): void {
23 this.portalRoot.append(this.el);
24 }
25
26 componentWillUnmount(): void {
27 this.el.remove();
28 }
29
30 render(): ReactPortal {
31 return ReactDOM.createPortal(
32 <ErrorBoundary>
33 <Layout {...this.props}>
34 <Outlet />
35 </Layout>
36 </ErrorBoundary>,
37 this.el,
38 );
39 }
40}
41
42export default inject('stores', 'actions')(observer(SettingsContainer));
diff --git a/src/features/workspaces/components/WorkspaceItem.tsx b/src/features/workspaces/components/WorkspaceItem.tsx
index f46375c7a..eb33a0376 100644
--- a/src/features/workspaces/components/WorkspaceItem.tsx
+++ b/src/features/workspaces/components/WorkspaceItem.tsx
@@ -1,8 +1,6 @@
1import { Component } from 'react'; 1import { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 2import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 3import injectSheet from 'react-jss';
5
6import Workspace from '../models/Workspace'; 4import Workspace from '../models/Workspace';
7 5
8const styles = theme => ({ 6const styles = theme => ({
@@ -18,17 +16,11 @@ const styles = theme => ({
18 16
19type Props = { 17type Props = {
20 classes: any; 18 classes: any;
21 workspace: any; 19 workspace: typeof Workspace;
22 onItemClick: (workspace) => void; 20 onItemClick: (workspace) => void;
23}; 21};
24 22
25class WorkspaceItem extends Component<Props> { 23class WorkspaceItem extends Component<Props> {
26 static propTypes = {
27 classes: PropTypes.object.isRequired,
28 workspace: PropTypes.instanceOf(Workspace).isRequired,
29 onItemClick: PropTypes.func.isRequired,
30 };
31
32 render() { 24 render() {
33 const { classes, workspace, onItemClick } = this.props; 25 const { classes, workspace, onItemClick } = this.props;
34 26
diff --git a/src/helpers/update-helpers.ts b/src/helpers/update-helpers.ts
new file mode 100644
index 000000000..daeef5413
--- /dev/null
+++ b/src/helpers/update-helpers.ts
@@ -0,0 +1,65 @@
1import { Octokit } from '@octokit/core';
2import { defineMessages, IntlShape } from 'react-intl';
3
4export function getFerdiumVersion(
5 currentLocation: string,
6 ferdiumVersion: string,
7): string {
8 const matches = currentLocation?.match(/version=([^&]*)/);
9 if (matches !== null) {
10 return `v${matches[1]}`;
11 }
12 return `v${ferdiumVersion}`;
13}
14
15export function updateVersionParse(updateVersion: string): string {
16 return updateVersion !== '' ? `?version=${updateVersion}` : '';
17}
18
19export function onAuthGoToReleaseNotes(
20 currentLocation: string,
21 updateVersionParsed: string = '',
22): string {
23 return currentLocation.includes('#/auth')
24 ? `#/auth/releasenotes${updateVersionParsed}`
25 : `#/releasenotes${updateVersionParsed}`;
26}
27
28const messages = defineMessages({
29 connectionError: {
30 id: 'settings.releasenotes.connectionError',
31 defaultMessage:
32 'An error occured when connecting to Github, please try again later.',
33 },
34 connectionErrorPageMissing: {
35 id: 'settings.releasenotes.connectionErrorPageMissing',
36 defaultMessage:
37 'An error occured when connecting to Github, the page you are looking for is missing.',
38 },
39});
40
41export async function getUpdateInfoFromGH(
42 currentLocation: string,
43 ferdiumVersion: string,
44 intl: IntlShape,
45): Promise<string> {
46 const octokit = new Octokit();
47 try {
48 const response = await octokit.request(
49 'GET /repos/{owner}/{repo}/releases/tags/{tag}',
50 {
51 owner: 'ferdium',
52 repo: 'ferdium-app',
53 tag: getFerdiumVersion(currentLocation, ferdiumVersion),
54 },
55 );
56
57 if (response.status === 200) {
58 const json = response.data.body;
59 return json || `### ${intl.formatMessage(messages.connectionError)}`;
60 }
61 return `### ${intl.formatMessage(messages.connectionError)}`;
62 } catch {
63 return `### ${intl.formatMessage(messages.connectionErrorPageMissing)}`;
64 }
65}
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 3c3beea6f..d45ab5c3c 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -194,6 +194,7 @@
194 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Open Service Recipes folder", 194 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Open Service Recipes folder",
195 "settings.app.buttonOpenImportExport": "Import / Export", 195 "settings.app.buttonOpenImportExport": "Import / Export",
196 "settings.app.buttonSearchForUpdate": "Check for updates", 196 "settings.app.buttonSearchForUpdate": "Check for updates",
197 "settings.app.buttonShowChangelog": "Show changelog",
197 "settings.app.cacheInfo": "Ferdium cache is currently using {size} of disk space.", 198 "settings.app.cacheInfo": "Ferdium cache is currently using {size} of disk space.",
198 "settings.app.cacheNotCleared": "Couldn't clear all cache", 199 "settings.app.cacheNotCleared": "Couldn't clear all cache",
199 "settings.app.closeSettings": "Close settings", 200 "settings.app.closeSettings": "Close settings",
@@ -302,6 +303,7 @@
302 "settings.navigation.account": "Account", 303 "settings.navigation.account": "Account",
303 "settings.navigation.availableServices": "Available services", 304 "settings.navigation.availableServices": "Available services",
304 "settings.navigation.logout": "Logout", 305 "settings.navigation.logout": "Logout",
306 "settings.navigation.releaseNotes": "Release Notes",
305 "settings.navigation.supportFerdium": "About Ferdium", 307 "settings.navigation.supportFerdium": "About Ferdium",
306 "settings.navigation.team": "Manage Team", 308 "settings.navigation.team": "Manage Team",
307 "settings.navigation.yourServices": "Your services", 309 "settings.navigation.yourServices": "Your services",
@@ -319,6 +321,9 @@
319 "settings.recipes.missingService": "Missing a service?", 321 "settings.recipes.missingService": "Missing a service?",
320 "settings.recipes.nothingFound": "Sorry, but no service matched your search term - but you can still probably add it using the \"Custom Website\" option. Please note that the website might show more services that have been added to Ferdium since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdium.", 322 "settings.recipes.nothingFound": "Sorry, but no service matched your search term - but you can still probably add it using the \"Custom Website\" option. Please note that the website might show more services that have been added to Ferdium since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdium.",
321 "settings.recipes.servicesSuccessfulAddedInfo": "Service successfully added", 323 "settings.recipes.servicesSuccessfulAddedInfo": "Service successfully added",
324 "settings.releasenotes.connectionError": "An error occured when connecting to Github, please try again later.",
325 "settings.releasenotes.connectionErrorPageMissing": "An error occured when connecting to Github, the page you are looking for is missing.",
326 "settings.releasenotes.headline": "Release Notes",
322 "settings.searchService": "Search service", 327 "settings.searchService": "Search service",
323 "settings.service.error.goBack": "Back to services", 328 "settings.service.error.goBack": "Back to services",
324 "settings.service.error.headline": "Error", 329 "settings.service.error.headline": "Error",
diff --git a/src/jsUtils.ts b/src/jsUtils.ts
index f5b39a000..36d70da25 100644
--- a/src/jsUtils.ts
+++ b/src/jsUtils.ts
@@ -20,3 +20,5 @@ export const convertToJSON = (data: string | any | undefined | null) =>
20 20
21export const cleanseJSObject = (data: any | undefined | null) => 21export const cleanseJSObject = (data: any | undefined | null) =>
22 JSON.parse(JSON.stringify(data)); 22 JSON.parse(JSON.stringify(data));
23
24export const isEscKeyPress = (keyCode : Number) => keyCode === 27;
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index 8fd3e9e01..41b4aa9f7 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -32,11 +32,7 @@ import {
32 nodeVersion, 32 nodeVersion,
33 osArch, 33 osArch,
34} from '../environment'; 34} from '../environment';
35import { 35import { CUSTOM_WEBSITE_RECIPE_ID, LIVE_API_FERDIUM_WEBSITE } from '../config';
36 CUSTOM_WEBSITE_RECIPE_ID,
37 GITHUB_FERDIUM_URL,
38 LIVE_API_FERDIUM_WEBSITE,
39} from '../config';
40import { ferdiumVersion } from '../environment-remote'; 36import { ferdiumVersion } from '../environment-remote';
41import { todoActions } from '../features/todos/actions'; 37import { todoActions } from '../features/todos/actions';
42import workspaceActions from '../features/workspaces/actions'; 38import workspaceActions from '../features/workspaces/actions';
@@ -44,6 +40,7 @@ import { workspaceStore } from '../features/workspaces/index';
44import { importExportURL, serverBase, serverName } from '../api/apiBase'; 40import { importExportURL, serverBase, serverName } from '../api/apiBase';
45import { openExternalUrl } from '../helpers/url-helpers'; 41import { openExternalUrl } from '../helpers/url-helpers';
46import globalMessages from '../i18n/globalMessages'; 42import globalMessages from '../i18n/globalMessages';
43import { onAuthGoToReleaseNotes } from '../helpers/update-helpers';
47 44
48// @ts-expect-error Cannot find module '../buildInfo.json' or its corresponding type declarations. 45// @ts-expect-error Cannot find module '../buildInfo.json' or its corresponding type declarations.
49import * as buildInfo from '../buildInfo.json'; 46import * as buildInfo from '../buildInfo.json';
@@ -586,10 +583,7 @@ const _titleBarTemplateFactory = (intl, locked) => [
586 { 583 {
587 label: intl.formatMessage(menuItems.changelog), 584 label: intl.formatMessage(menuItems.changelog),
588 click() { 585 click() {
589 openExternalUrl( 586 window.location.href = onAuthGoToReleaseNotes(window.location.href);
590 `${GITHUB_FERDIUM_URL}/ferdium-app/releases/tag/v${ferdiumVersion}`,
591 true,
592 );
593 }, 587 },
594 }, 588 },
595 { 589 {
diff --git a/src/routes.tsx b/src/routes.tsx
index 1f929531e..478d3dfe8 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -9,6 +9,7 @@ import {
9 9
10import AppLayoutContainer from './containers/layout/AppLayoutContainer'; 10import AppLayoutContainer from './containers/layout/AppLayoutContainer';
11import SettingsWindow from './containers/settings/SettingsWindow'; 11import SettingsWindow from './containers/settings/SettingsWindow';
12import ReleaseNotesWindow from './containers/settings/ReleaseNotesWindow';
12import RecipesScreen from './containers/settings/RecipesScreen'; 13import RecipesScreen from './containers/settings/RecipesScreen';
13import ServicesScreen from './containers/settings/ServicesScreen'; 14import ServicesScreen from './containers/settings/ServicesScreen';
14import EditServiceScreen from './containers/settings/EditServiceScreen'; 15import EditServiceScreen from './containers/settings/EditServiceScreen';
@@ -18,8 +19,10 @@ import EditUserScreen from './containers/settings/EditUserScreen';
18import EditSettingsScreen from './containers/settings/EditSettingsScreen'; 19import EditSettingsScreen from './containers/settings/EditSettingsScreen';
19import InviteSettingsScreen from './containers/settings/InviteScreen'; 20import InviteSettingsScreen from './containers/settings/InviteScreen';
20import SupportFerdiumScreen from './containers/settings/SupportScreen'; 21import SupportFerdiumScreen from './containers/settings/SupportScreen';
22import ReleaseNotesScreen from './containers/settings/ReleaseNotesScreen';
21import WelcomeScreen from './containers/auth/WelcomeScreen'; 23import WelcomeScreen from './containers/auth/WelcomeScreen';
22import LoginScreen from './containers/auth/LoginScreen'; 24import LoginScreen from './containers/auth/LoginScreen';
25import AuthReleaseNotesScreen from './containers/auth/AuthReleaseNotesScreen';
23import PasswordScreen from './containers/auth/PasswordScreen'; 26import PasswordScreen from './containers/auth/PasswordScreen';
24import ChangeServerScreen from './containers/auth/ChangeServerScreen'; 27import ChangeServerScreen from './containers/auth/ChangeServerScreen';
25import SignupScreen from './containers/auth/SignupScreen'; 28import SignupScreen from './containers/auth/SignupScreen';
@@ -100,10 +103,25 @@ class FerdiumRoutes extends Component<Props> {
100 path="/auth/logout" 103 path="/auth/logout"
101 element={<LoginScreen {...routeProps} {...errorProps} />} 104 element={<LoginScreen {...routeProps} {...errorProps} />}
102 /> 105 />
106 <Route
107 path="/auth/releasenotes"
108 element={
109 <AuthReleaseNotesScreen {...routeProps} {...errorProps} />
110 }
111 />
103 </Route> 112 </Route>
104 113
105 <Route path="/" element={<AppLayoutContainer {...routeProps} />}> 114 <Route path="/" element={<AppLayoutContainer {...routeProps} />}>
106 <Route 115 <Route
116 path="/releasenotes"
117 element={<ReleaseNotesWindow {...this.props} />}
118 >
119 <Route
120 path="/releasenotes"
121 element={<ReleaseNotesScreen {...this.props} />}
122 />
123 </Route>
124 <Route
107 path="/settings" 125 path="/settings"
108 element={<SettingsWindow {...this.props} />} 126 element={<SettingsWindow {...this.props} />}
109 > 127 >
@@ -155,6 +173,10 @@ class FerdiumRoutes extends Component<Props> {
155 path="/settings/support" 173 path="/settings/support"
156 element={<SupportFerdiumScreen {...this.props} />} 174 element={<SupportFerdiumScreen {...this.props} />}
157 /> 175 />
176 <Route
177 path="/settings/releasenotes"
178 element={<ReleaseNotesScreen {...this.props} />}
179 />
158 </Route> 180 </Route>
159 </Route> 181 </Route>
160 </Routes> 182 </Routes>
diff --git a/src/stores/AppStore.ts b/src/stores/AppStore.ts
index 116e64756..af1a0daae 100644
--- a/src/stores/AppStore.ts
+++ b/src/stores/AppStore.ts
@@ -82,6 +82,8 @@ export default class AppStore extends TypedStore {
82 82
83 @observable updateStatus = ''; 83 @observable updateStatus = '';
84 84
85 @observable updateVersion = '';
86
85 @observable locale = ferdiumLocale; 87 @observable locale = ferdiumLocale;
86 88
87 @observable isSystemMuteOverridden = false; 89 @observable isSystemMuteOverridden = false;
@@ -94,7 +96,8 @@ export default class AppStore extends TypedStore {
94 96
95 @observable isFocused = true; 97 @observable isFocused = true;
96 98
97 @observable lockingFeatureEnabled = DEFAULT_APP_SETTINGS.lockingFeatureEnabled; 99 @observable lockingFeatureEnabled =
100 DEFAULT_APP_SETTINGS.lockingFeatureEnabled;
98 101
99 @observable launchInBackground = DEFAULT_APP_SETTINGS.autoLaunchInBackground; 102 @observable launchInBackground = DEFAULT_APP_SETTINGS.autoLaunchInBackground;
100 103
@@ -181,6 +184,7 @@ export default class AppStore extends TypedStore {
181 ipcRenderer.on('autoUpdate', (_, data) => { 184 ipcRenderer.on('autoUpdate', (_, data) => {
182 if (this.updateStatus !== this.updateStatusTypes.FAILED) { 185 if (this.updateStatus !== this.updateStatusTypes.FAILED) {
183 if (data.available) { 186 if (data.available) {
187 this.updateVersion = data.version;
184 this.updateStatus = this.updateStatusTypes.AVAILABLE; 188 this.updateStatus = this.updateStatusTypes.AVAILABLE;
185 if (isMac && this.stores.settings.app.automaticUpdates) { 189 if (isMac && this.stores.settings.app.automaticUpdates) {
186 app.dock.bounce(); 190 app.dock.bounce();
diff --git a/src/styles/auth.scss b/src/styles/auth.scss
index e60c4971c..53c03a40a 100644
--- a/src/styles/auth.scss
+++ b/src/styles/auth.scss
@@ -61,6 +61,12 @@
61 width: 350px; 61 width: 350px;
62 62
63 &.auth__container--signup { width: 450px; } 63 &.auth__container--signup { width: 450px; }
64
65 &.auth__container--releasenotes {
66 width: 90%;
67 height: -webkit-fill-available;
68 max-height: 80%;
69 }
64 } 70 }
65 71
66 .auth__logo { 72 .auth__logo {
@@ -92,6 +98,32 @@
92 } 98 }
93 } 99 }
94 100
101 .releasenotes__body {
102 overflow-y: scroll;
103
104 &::-webkit-scrollbar {
105 width: 8px;
106 }
107
108 /* Track */
109 &::-webkit-scrollbar-track {
110 background: none;
111 border-radius: 10px;
112 -webkit-border-radius: 10px;
113 }
114
115 /* Handle */
116 &::-webkit-scrollbar-thumb {
117 background: $theme-gray-lighter;
118 border-radius: 10px;
119 -webkit-border-radius: 10px;
120 }
121
122 &::-webkit-scrollbar-thumb:window-inactive {
123 background: none;
124 }
125 }
126
95 .touchid__button { 127 .touchid__button {
96 margin-bottom: 25px; 128 margin-bottom: 25px;
97 } 129 }
@@ -135,6 +167,7 @@
135 padding-top: 2%; 167 padding-top: 2%;
136 padding-bottom: 2%; 168 padding-bottom: 2%;
137 justify-content: center; 169 justify-content: center;
170 height: fit-content;
138 } 171 }
139 172
140 .auth__adlk { 173 .auth__adlk {
@@ -168,3 +201,29 @@
168 text-align: center; 201 text-align: center;
169 } 202 }
170} 203}
204
205.auth__main--releasenotes {
206 margin: 4% 2% 0% 4%;
207 display: flex;
208 flex-direction: column;
209 justify-content: center;
210 height: -webkit-fill-available;
211}
212
213.auth__header {
214 font-size: x-large;
215 display: inline-flex;
216 flex-direction: row;
217 align-content: center;
218 justify-content: center;
219 flex-wrap: wrap;
220 height: fit-content;
221
222 &-item__secondary {
223 padding-left: 5px
224 }
225}
226
227.auth__body {
228 margin-top: 2%;
229}
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 76d167fd4..f16fc91fd 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -234,6 +234,10 @@
234 234
235 .settings__header-item { 235 .settings__header-item {
236 @extend %headline; 236 @extend %headline;
237
238 &__secondary {
239 padding-left: 5px
240 }
237 } 241 }
238 242
239 .separator { 243 .separator {
@@ -630,3 +634,94 @@
630 display: inline-block; 634 display: inline-block;
631 } 635 }
632} 636}
637
638.settings__updates__changelog-button {
639 margin-left: 2% !important;
640}
641
642.releasenotes__body {
643 line-height: 150%;
644
645 h1 {
646 margin-top: 0;
647 font-weight: 400;
648 font-size: xx-large;
649 text-align: center;
650 }
651
652 h2 {
653 margin-top: 10%;
654 font-weight: 400;
655 font-size: x-large;
656 text-align: center;
657 margin-bottom: 2%;
658 }
659
660 h3 {
661 margin-top: 5%;
662 font-weight: 400;
663 font-size: large;
664 text-align: center;
665 margin-bottom: 2%;
666 }
667
668 h4 {
669 margin-top: 5%;
670 font-weight: 400;
671 font-size: medium;
672 text-align: center;
673 margin-bottom: 2%;
674 }
675
676 h5 {
677 margin-top: 5%;
678 font-weight: 400;
679 text-align: center;
680 margin-bottom: 2%;
681 }
682
683 p {
684 margin-top: 4%;
685 margin-bottom: 4%;
686 user-select: text;
687 }
688
689 a {
690 color: rgb(0, 102, 255) !important;
691 user-select: text;
692 }
693
694 code {
695 background: #f4f4f4;
696 border: 1px solid #ddd;
697 border-radius: 5px;
698 padding: 0.2em 0.4em;
699 font-size: 85%;
700 color: #666;
701 font-family: monospace;
702 margin-bottom: 1.6em;
703 max-width: 100%;
704 overflow: auto;
705 user-select: text;
706 }
707
708 ul {
709 padding-left: 2rem;
710 }
711
712 li {
713 margin-top: 2%;
714 list-style-type: disc;
715 user-select: text;
716 }
717
718 img {
719 max-width: 100%;
720 margin-top: 2%;
721 margin-bottom: 2%;
722 align-items: center;
723 display: block;
724 margin-left: auto;
725 margin-right: auto;
726 }
727}
diff --git a/test/helpers/update-helpers.test.ts b/test/helpers/update-helpers.test.ts
new file mode 100644
index 000000000..0494c6855
--- /dev/null
+++ b/test/helpers/update-helpers.test.ts
@@ -0,0 +1,89 @@
1import * as update_helpers from '../../src/helpers/update-helpers';
2
3describe('getFerdiumVersion', () => {
4 const baseVersion = '6.0.0-nightly.3';
5 it(`returns ${baseVersion} for empty string`, () => {
6 const result = update_helpers.getFerdiumVersion('', baseVersion);
7 expect(result).toEqual(`v${baseVersion}`);
8 });
9
10 it(`returns ${baseVersion} for ${baseVersion}`, () => {
11 const result = update_helpers.getFerdiumVersion('', baseVersion);
12 expect(result).toEqual(`v${baseVersion}`);
13 });
14
15 it(`returns v6.0.0-beta.3`, () => {
16 const result = update_helpers.getFerdiumVersion(
17 '?version=6.0.0-beta.3',
18 baseVersion,
19 );
20 expect(result).toEqual(`v6.0.0-beta.3`);
21 });
22
23 it(`returns v6.0.0`, () => {
24 const result = update_helpers.getFerdiumVersion(
25 '?version=6.0.0',
26 baseVersion,
27 );
28 expect(result).toEqual(`v6.0.0`);
29 });
30
31 it(`returns ${baseVersion}`, () => {
32 const result = update_helpers.getFerdiumVersion(
33 'http://test/=6.0.0',
34 baseVersion,
35 );
36 expect(result).toEqual(`v${baseVersion}`);
37 });
38
39 it(`returns ${baseVersion} for missing 'version='`, () => {
40 const result = update_helpers.getFerdiumVersion(
41 'http://test/',
42 baseVersion,
43 );
44 expect(result).toEqual(`v${baseVersion}`);
45 });
46});
47
48describe('updateVersionParse', () => {
49 it(`returns empty string for empty string`, () => {
50 const result = update_helpers.updateVersionParse('');
51 expect(result).toEqual('');
52 });
53 it(`returns '?version=x.x for x.x`, () => {
54 const result = update_helpers.updateVersionParse('6.0.0');
55 expect(result).toEqual('?version=6.0.0');
56 });
57});
58
59describe('onAuthGoToReleaseNotes', () => {
60 it(`returns '#/releasenotes' string for empty string`, () => {
61 const result = update_helpers.onAuthGoToReleaseNotes('', '');
62 expect(result).toEqual('#/releasenotes');
63 });
64
65 it(`returns '#/releasenotes' string for empty string`, () => {
66 const result = update_helpers.onAuthGoToReleaseNotes('', '?version=6.0.0');
67 expect(result).toEqual('#/releasenotes?version=6.0.0');
68 });
69
70 it(`returns '#/releasenotes' string for empty string`, () => {
71 const result = update_helpers.onAuthGoToReleaseNotes('');
72 expect(result).toEqual('#/releasenotes');
73 });
74
75 it(`returns '#/releasenotes' string for empty string`, () => {
76 const result = update_helpers.onAuthGoToReleaseNotes('#/auth', '');
77 expect(result).toEqual('#/auth/releasenotes');
78 });
79
80 it(`returns '#/releasenotes' string for empty string`, () => {
81 const result = update_helpers.onAuthGoToReleaseNotes('#/auth', '?version=6.0.0');
82 expect(result).toEqual('#/auth/releasenotes?version=6.0.0');
83 });
84
85 it(`returns '#/releasenotes' string for empty string`, () => {
86 const result = update_helpers.onAuthGoToReleaseNotes('#/auth');
87 expect(result).toEqual('#/auth/releasenotes');
88 });
89});
diff --git a/test/jsUtils.test.ts b/test/jsUtils.test.ts
index 8ef69b46f..406326d4b 100644
--- a/test/jsUtils.test.ts
+++ b/test/jsUtils.test.ts
@@ -110,4 +110,16 @@ describe('jsUtils', () => {
110 expect(result).toEqual([{ a: 'b' }, { c: 'd' }]); 110 expect(result).toEqual([{ a: 'b' }, { c: 'd' }]);
111 }); 111 });
112 }); 112 });
113
114 describe('isEscKeyPress', () => {
115 it('returns true if the key number is 27', () => {
116 const result = jsUtils.isEscKeyPress(27);
117 expect(result).toEqual(true);
118 });
119
120 it('returns false if the key number is 27', () => {
121 const result = jsUtils.isEscKeyPress(28);
122 expect(result).toEqual(false);
123 });
124 });
113}); 125});
diff --git a/tsconfig.json b/tsconfig.json
index 7a50a4a2d..d1465de86 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,8 @@
7 "target": "esnext", 7 "target": "esnext",
8 "lib": [ 8 "lib": [
9 "esnext", 9 "esnext",
10 "dom" 10 "dom",
11 "dom.iterable"
11 ], 12 ],
12 "module": "CommonJS", 13 "module": "CommonJS",
13 "jsx": "react-jsx", 14 "jsx": "react-jsx",