aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package.json4
-rw-r--r--pnpm-lock.yaml168
m---------recipes0
-rw-r--r--src/api/server/ServerApi.ts14
-rw-r--r--src/components/MediaSource.tsx171
-rw-r--r--src/components/services/content/ServiceView.tsx2
-rw-r--r--src/components/services/tabs/TabItem.tsx8
-rw-r--r--src/components/settings/services/EditServiceForm.tsx1
-rw-r--r--src/components/settings/settings/EditSettingsForm.tsx125
-rw-r--r--src/config.ts5
-rw-r--r--src/containers/auth/SetupAssistantScreen.tsx5
-rw-r--r--src/containers/settings/EditServiceScreen.tsx13
-rw-r--r--src/containers/settings/EditSettingsScreen.tsx244
-rw-r--r--src/electron/ipc-api/dnd.ts1
-rw-r--r--src/electron/ipc-api/download.ts10
-rw-r--r--src/environment.ts2
-rw-r--r--src/features/workspaces/components/WorkspaceDrawerItem.tsx8
-rw-r--r--src/helpers/favicon-helpers.ts3
-rw-r--r--src/i18n/locales/de.json21
-rw-r--r--src/i18n/locales/en-US.json12
-rw-r--r--src/i18n/locales/he.json12
-rw-r--r--src/i18n/locales/lv.json28
-rw-r--r--src/i18n/locales/ru.json21
-rw-r--r--src/index.ts32
-rw-r--r--src/internal-server/app/Controllers/Http/ServiceController.js3
-rw-r--r--src/jsUtils.ts21
-rw-r--r--src/lib/Menu.ts17
-rw-r--r--src/models/Recipe.ts5
-rw-r--r--src/models/Service.ts25
-rw-r--r--src/stores/ServicesStore.ts14
-rw-r--r--src/styles/capture-sources.scss75
-rw-r--r--src/styles/main.scss1
-rw-r--r--src/webview/contextMenuBuilder.ts59
-rw-r--r--src/webview/notifications.ts75
-rw-r--r--src/webview/recipe.ts9
-rw-r--r--src/webview/screenshare.ts154
-rw-r--r--test/jsUtils.test.ts46
37 files changed, 987 insertions, 427 deletions
diff --git a/package.json b/package.json
index 1c3502ee3..03ce9a1f8 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
3 "productName": "Ferdium", 3 "productName": "Ferdium",
4 "desktopName": "ferdium.desktop", 4 "desktopName": "ferdium.desktop",
5 "appId": "org.ferdium.ferdium-app", 5 "appId": "org.ferdium.ferdium-app",
6 "version": "6.7.3-nightly.15", 6 "version": "6.7.4",
7 "description": "Messaging app for WhatsApp, Slack, Telegram, Hangouts and many many more.", 7 "description": "Messaging app for WhatsApp, Slack, Telegram, Hangouts and many many more.",
8 "author": "Ferdium Contributors <hello@ferdium.org> (https://ferdium.org/)", 8 "author": "Ferdium Contributors <hello@ferdium.org> (https://ferdium.org/)",
9 "license": "Apache-2.0", 9 "license": "Apache-2.0",
@@ -163,7 +163,7 @@
163 "chalk": "5.3.0", 163 "chalk": "5.3.0",
164 "concurrently": "8.2.2", 164 "concurrently": "8.2.2",
165 "cross-env": "7.0.3", 165 "cross-env": "7.0.3",
166 "electron": "30.0.1", 166 "electron": "30.0.6",
167 "electron-builder": "24.13.3", 167 "electron-builder": "24.13.3",
168 "esbuild": "0.16.17", 168 "esbuild": "0.16.17",
169 "esbuild-plugin-copy": "2.1.1", 169 "esbuild-plugin-copy": "2.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 341b0905c..934011d21 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -40,7 +40,7 @@ dependencies:
40 version: 5.1.0 40 version: 5.1.0
41 '@electron/remote': 41 '@electron/remote':
42 specifier: 2.1.2 42 specifier: 2.1.2
43 version: 2.1.2(electron@30.0.1) 43 version: 2.1.2(electron@30.0.6)
44 '@emotion/react': 44 '@emotion/react':
45 specifier: 11.11.4 45 specifier: 11.11.4
46 version: 11.11.4(@types/react@18.2.77)(react@18.2.0) 46 version: 11.11.4(@types/react@18.2.77)(react@18.2.0)
@@ -70,7 +70,7 @@ dependencies:
70 version: 7.4.0(history@5.3.0)(mobx@6.12.3)(path-to-regexp@6.2.2) 70 version: 7.4.0(history@5.3.0)(mobx@6.12.3)(path-to-regexp@6.2.2)
71 '@syed_umair/electron-process-manager': 71 '@syed_umair/electron-process-manager':
72 specifier: 1.1.0 72 specifier: 1.1.0
73 version: 1.1.0(electron@30.0.1) 73 version: 1.1.0(electron@30.0.6)
74 auto-launch: 74 auto-launch:
75 specifier: 5.0.6 75 specifier: 5.0.6
76 version: 5.0.6 76 version: 5.0.6
@@ -202,7 +202,7 @@ dependencies:
202 version: 10.10.0(react@18.2.0) 202 version: 10.10.0(react@18.2.0)
203 react-loader-spinner: 203 react-loader-spinner:
204 specifier: 5.4.5 204 specifier: 5.4.5
205 version: 5.4.5(@babel/core@7.24.4)(react-dom@18.2.0)(react@18.2.0) 205 version: 5.4.5(@babel/core@7.24.5)(react-dom@18.2.0)(react@18.2.0)
206 react-modal: 206 react-modal:
207 specifier: 3.16.1 207 specifier: 3.16.1
208 version: 3.16.1(react-dom@18.2.0)(react@18.2.0) 208 version: 3.16.1(react-dom@18.2.0)(react@18.2.0)
@@ -352,8 +352,8 @@ devDependencies:
352 specifier: 7.0.3 352 specifier: 7.0.3
353 version: 7.0.3 353 version: 7.0.3
354 electron: 354 electron:
355 specifier: 30.0.1 355 specifier: 30.0.6
356 version: 30.0.1 356 version: 30.0.6
357 electron-builder: 357 electron-builder:
358 specifier: 24.13.3 358 specifier: 24.13.3
359 version: 24.13.3(electron-builder-squirrel-windows@24.13.3) 359 version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
@@ -401,7 +401,7 @@ devDependencies:
401 version: 4.6.0(eslint@8.57.0) 401 version: 4.6.0(eslint@8.57.0)
402 eslint-plugin-sonar: 402 eslint-plugin-sonar:
403 specifier: 0.13.2 403 specifier: 0.13.2
404 version: 0.13.2(@babel/core@7.24.4)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) 404 version: 0.13.2(@babel/core@7.24.5)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5)
405 eslint-plugin-unicorn: 405 eslint-plugin-unicorn:
406 specifier: 52.0.0 406 specifier: 52.0.0
407 version: 52.0.0(eslint@8.57.0) 407 version: 52.0.0(eslint@8.57.0)
@@ -645,8 +645,8 @@ packages:
645 resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} 645 resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==}
646 engines: {node: '>=6.9.0'} 646 engines: {node: '>=6.9.0'}
647 dependencies: 647 dependencies:
648 '@babel/highlight': 7.24.2 648 '@babel/highlight': 7.24.5
649 picocolors: 1.0.0 649 picocolors: 1.0.1
650 650
651 /@babel/compat-data@7.22.20: 651 /@babel/compat-data@7.22.20:
652 resolution: {integrity: sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==} 652 resolution: {integrity: sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==}
@@ -680,20 +680,20 @@ packages:
680 - supports-color 680 - supports-color
681 dev: true 681 dev: true
682 682
683 /@babel/core@7.24.4: 683 /@babel/core@7.24.5:
684 resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} 684 resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==}
685 engines: {node: '>=6.9.0'} 685 engines: {node: '>=6.9.0'}
686 dependencies: 686 dependencies:
687 '@ampproject/remapping': 2.3.0 687 '@ampproject/remapping': 2.3.0
688 '@babel/code-frame': 7.24.2 688 '@babel/code-frame': 7.24.2
689 '@babel/generator': 7.24.4 689 '@babel/generator': 7.24.5
690 '@babel/helper-compilation-targets': 7.23.6 690 '@babel/helper-compilation-targets': 7.23.6
691 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) 691 '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5)
692 '@babel/helpers': 7.24.4 692 '@babel/helpers': 7.24.5
693 '@babel/parser': 7.24.4 693 '@babel/parser': 7.24.5
694 '@babel/template': 7.24.0 694 '@babel/template': 7.24.0
695 '@babel/traverse': 7.24.1 695 '@babel/traverse': 7.24.5
696 '@babel/types': 7.24.0 696 '@babel/types': 7.24.5
697 convert-source-map: 2.0.0 697 convert-source-map: 2.0.0
698 debug: 4.3.4(supports-color@5.5.0) 698 debug: 4.3.4(supports-color@5.5.0)
699 gensync: 1.0.0-beta.2 699 gensync: 1.0.0-beta.2
@@ -702,14 +702,14 @@ packages:
702 transitivePeerDependencies: 702 transitivePeerDependencies:
703 - supports-color 703 - supports-color
704 704
705 /@babel/eslint-parser@7.23.10(@babel/core@7.24.4)(eslint@8.57.0): 705 /@babel/eslint-parser@7.23.10(@babel/core@7.24.5)(eslint@8.57.0):
706 resolution: {integrity: sha512-3wSYDPZVnhseRnxRJH6ZVTNknBz76AEnyC+AYYhasjP3Yy23qz0ERR7Fcd2SHmYuSFJ2kY9gaaDd3vyqU09eSw==} 706 resolution: {integrity: sha512-3wSYDPZVnhseRnxRJH6ZVTNknBz76AEnyC+AYYhasjP3Yy23qz0ERR7Fcd2SHmYuSFJ2kY9gaaDd3vyqU09eSw==}
707 engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} 707 engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
708 peerDependencies: 708 peerDependencies:
709 '@babel/core': ^7.11.0 709 '@babel/core': ^7.11.0
710 eslint: ^7.5.0 || ^8.0.0 710 eslint: ^7.5.0 || ^8.0.0
711 dependencies: 711 dependencies:
712 '@babel/core': 7.24.4 712 '@babel/core': 7.24.5
713 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 713 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1
714 eslint: 8.57.0 714 eslint: 8.57.0
715 eslint-visitor-keys: 2.1.0 715 eslint-visitor-keys: 2.1.0
@@ -725,11 +725,11 @@ packages:
725 '@jridgewell/trace-mapping': 0.3.20 725 '@jridgewell/trace-mapping': 0.3.20
726 jsesc: 2.5.2 726 jsesc: 2.5.2
727 727
728 /@babel/generator@7.24.4: 728 /@babel/generator@7.24.5:
729 resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} 729 resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==}
730 engines: {node: '>=6.9.0'} 730 engines: {node: '>=6.9.0'}
731 dependencies: 731 dependencies:
732 '@babel/types': 7.24.0 732 '@babel/types': 7.24.5
733 '@jridgewell/gen-mapping': 0.3.5 733 '@jridgewell/gen-mapping': 0.3.5
734 '@jridgewell/trace-mapping': 0.3.25 734 '@jridgewell/trace-mapping': 0.3.25
735 jsesc: 2.5.2 735 jsesc: 2.5.2
@@ -785,6 +785,12 @@ packages:
785 dependencies: 785 dependencies:
786 '@babel/types': 7.23.5 786 '@babel/types': 7.23.5
787 787
788 /@babel/helper-module-imports@7.24.3:
789 resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==}
790 engines: {node: '>=6.9.0'}
791 dependencies:
792 '@babel/types': 7.24.5
793
788 /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5): 794 /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5):
789 resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} 795 resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
790 engines: {node: '>=6.9.0'} 796 engines: {node: '>=6.9.0'}
@@ -799,18 +805,18 @@ packages:
799 '@babel/helper-validator-identifier': 7.22.20 805 '@babel/helper-validator-identifier': 7.22.20
800 dev: true 806 dev: true
801 807
802 /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): 808 /@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5):
803 resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} 809 resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==}
804 engines: {node: '>=6.9.0'} 810 engines: {node: '>=6.9.0'}
805 peerDependencies: 811 peerDependencies:
806 '@babel/core': ^7.0.0 812 '@babel/core': ^7.0.0
807 dependencies: 813 dependencies:
808 '@babel/core': 7.24.4 814 '@babel/core': 7.24.5
809 '@babel/helper-environment-visitor': 7.22.20 815 '@babel/helper-environment-visitor': 7.22.20
810 '@babel/helper-module-imports': 7.22.15 816 '@babel/helper-module-imports': 7.24.3
811 '@babel/helper-simple-access': 7.22.5 817 '@babel/helper-simple-access': 7.24.5
812 '@babel/helper-split-export-declaration': 7.22.6 818 '@babel/helper-split-export-declaration': 7.24.5
813 '@babel/helper-validator-identifier': 7.22.20 819 '@babel/helper-validator-identifier': 7.24.5
814 820
815 /@babel/helper-plugin-utils@7.22.5: 821 /@babel/helper-plugin-utils@7.22.5:
816 resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} 822 resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==}
@@ -821,6 +827,13 @@ packages:
821 engines: {node: '>=6.9.0'} 827 engines: {node: '>=6.9.0'}
822 dependencies: 828 dependencies:
823 '@babel/types': 7.23.5 829 '@babel/types': 7.23.5
830 dev: true
831
832 /@babel/helper-simple-access@7.24.5:
833 resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==}
834 engines: {node: '>=6.9.0'}
835 dependencies:
836 '@babel/types': 7.24.5
824 837
825 /@babel/helper-split-export-declaration@7.22.6: 838 /@babel/helper-split-export-declaration@7.22.6:
826 resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} 839 resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
@@ -828,6 +841,12 @@ packages:
828 dependencies: 841 dependencies:
829 '@babel/types': 7.23.5 842 '@babel/types': 7.23.5
830 843
844 /@babel/helper-split-export-declaration@7.24.5:
845 resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==}
846 engines: {node: '>=6.9.0'}
847 dependencies:
848 '@babel/types': 7.24.5
849
831 /@babel/helper-string-parser@7.23.4: 850 /@babel/helper-string-parser@7.23.4:
832 resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} 851 resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
833 engines: {node: '>=6.9.0'} 852 engines: {node: '>=6.9.0'}
@@ -840,6 +859,10 @@ packages:
840 resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} 859 resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
841 engines: {node: '>=6.9.0'} 860 engines: {node: '>=6.9.0'}
842 861
862 /@babel/helper-validator-identifier@7.24.5:
863 resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==}
864 engines: {node: '>=6.9.0'}
865
843 /@babel/helper-validator-option@7.22.15: 866 /@babel/helper-validator-option@7.22.15:
844 resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} 867 resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==}
845 engines: {node: '>=6.9.0'} 868 engines: {node: '>=6.9.0'}
@@ -860,13 +883,13 @@ packages:
860 - supports-color 883 - supports-color
861 dev: true 884 dev: true
862 885
863 /@babel/helpers@7.24.4: 886 /@babel/helpers@7.24.5:
864 resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} 887 resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==}
865 engines: {node: '>=6.9.0'} 888 engines: {node: '>=6.9.0'}
866 dependencies: 889 dependencies:
867 '@babel/template': 7.24.0 890 '@babel/template': 7.24.0
868 '@babel/traverse': 7.24.1 891 '@babel/traverse': 7.24.5
869 '@babel/types': 7.24.0 892 '@babel/types': 7.24.5
870 transitivePeerDependencies: 893 transitivePeerDependencies:
871 - supports-color 894 - supports-color
872 895
@@ -878,14 +901,14 @@ packages:
878 chalk: 2.4.2 901 chalk: 2.4.2
879 js-tokens: 4.0.0 902 js-tokens: 4.0.0
880 903
881 /@babel/highlight@7.24.2: 904 /@babel/highlight@7.24.5:
882 resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} 905 resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==}
883 engines: {node: '>=6.9.0'} 906 engines: {node: '>=6.9.0'}
884 dependencies: 907 dependencies:
885 '@babel/helper-validator-identifier': 7.22.20 908 '@babel/helper-validator-identifier': 7.24.5
886 chalk: 2.4.2 909 chalk: 2.4.2
887 js-tokens: 4.0.0 910 js-tokens: 4.0.0
888 picocolors: 1.0.0 911 picocolors: 1.0.1
889 912
890 /@babel/parser@7.23.5: 913 /@babel/parser@7.23.5:
891 resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==} 914 resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==}
@@ -894,12 +917,12 @@ packages:
894 dependencies: 917 dependencies:
895 '@babel/types': 7.23.5 918 '@babel/types': 7.23.5
896 919
897 /@babel/parser@7.24.4: 920 /@babel/parser@7.24.5:
898 resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} 921 resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==}
899 engines: {node: '>=6.0.0'} 922 engines: {node: '>=6.0.0'}
900 hasBin: true 923 hasBin: true
901 dependencies: 924 dependencies:
902 '@babel/types': 7.24.0 925 '@babel/types': 7.24.5
903 926
904 /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5): 927 /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5):
905 resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} 928 resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
@@ -956,13 +979,13 @@ packages:
956 '@babel/helper-plugin-utils': 7.22.5 979 '@babel/helper-plugin-utils': 7.22.5
957 dev: true 980 dev: true
958 981
959 /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.24.4): 982 /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.24.5):
960 resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} 983 resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==}
961 engines: {node: '>=6.9.0'} 984 engines: {node: '>=6.9.0'}
962 peerDependencies: 985 peerDependencies:
963 '@babel/core': ^7.0.0-0 986 '@babel/core': ^7.0.0-0
964 dependencies: 987 dependencies:
965 '@babel/core': 7.24.4 988 '@babel/core': 7.24.5
966 '@babel/helper-plugin-utils': 7.22.5 989 '@babel/helper-plugin-utils': 7.22.5
967 dev: false 990 dev: false
968 991
@@ -1072,8 +1095,8 @@ packages:
1072 engines: {node: '>=6.9.0'} 1095 engines: {node: '>=6.9.0'}
1073 dependencies: 1096 dependencies:
1074 '@babel/code-frame': 7.24.2 1097 '@babel/code-frame': 7.24.2
1075 '@babel/parser': 7.24.4 1098 '@babel/parser': 7.24.5
1076 '@babel/types': 7.24.0 1099 '@babel/types': 7.24.5
1077 1100
1078 /@babel/traverse@7.23.5(supports-color@5.5.0): 1101 /@babel/traverse@7.23.5(supports-color@5.5.0):
1079 resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==} 1102 resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==}
@@ -1092,18 +1115,18 @@ packages:
1092 transitivePeerDependencies: 1115 transitivePeerDependencies:
1093 - supports-color 1116 - supports-color
1094 1117
1095 /@babel/traverse@7.24.1: 1118 /@babel/traverse@7.24.5:
1096 resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} 1119 resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==}
1097 engines: {node: '>=6.9.0'} 1120 engines: {node: '>=6.9.0'}
1098 dependencies: 1121 dependencies:
1099 '@babel/code-frame': 7.24.2 1122 '@babel/code-frame': 7.24.2
1100 '@babel/generator': 7.24.4 1123 '@babel/generator': 7.24.5
1101 '@babel/helper-environment-visitor': 7.22.20 1124 '@babel/helper-environment-visitor': 7.22.20
1102 '@babel/helper-function-name': 7.23.0 1125 '@babel/helper-function-name': 7.23.0
1103 '@babel/helper-hoist-variables': 7.22.5 1126 '@babel/helper-hoist-variables': 7.22.5
1104 '@babel/helper-split-export-declaration': 7.22.6 1127 '@babel/helper-split-export-declaration': 7.24.5
1105 '@babel/parser': 7.24.4 1128 '@babel/parser': 7.24.5
1106 '@babel/types': 7.24.0 1129 '@babel/types': 7.24.5
1107 debug: 4.3.4(supports-color@5.5.0) 1130 debug: 4.3.4(supports-color@5.5.0)
1108 globals: 11.12.0 1131 globals: 11.12.0
1109 transitivePeerDependencies: 1132 transitivePeerDependencies:
@@ -1117,12 +1140,12 @@ packages:
1117 '@babel/helper-validator-identifier': 7.22.20 1140 '@babel/helper-validator-identifier': 7.22.20
1118 to-fast-properties: 2.0.0 1141 to-fast-properties: 2.0.0
1119 1142
1120 /@babel/types@7.24.0: 1143 /@babel/types@7.24.5:
1121 resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} 1144 resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==}
1122 engines: {node: '>=6.9.0'} 1145 engines: {node: '>=6.9.0'}
1123 dependencies: 1146 dependencies:
1124 '@babel/helper-string-parser': 7.24.1 1147 '@babel/helper-string-parser': 7.24.1
1125 '@babel/helper-validator-identifier': 7.22.20 1148 '@babel/helper-validator-identifier': 7.24.5
1126 to-fast-properties: 2.0.0 1149 to-fast-properties: 2.0.0
1127 1150
1128 /@bcoe/v8-coverage@0.2.3: 1151 /@bcoe/v8-coverage@0.2.3:
@@ -1471,12 +1494,12 @@ packages:
1471 - supports-color 1494 - supports-color
1472 dev: true 1495 dev: true
1473 1496
1474 /@electron/remote@2.1.2(electron@30.0.1): 1497 /@electron/remote@2.1.2(electron@30.0.6):
1475 resolution: {integrity: sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==} 1498 resolution: {integrity: sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==}
1476 peerDependencies: 1499 peerDependencies:
1477 electron: '>= 13.0.0' 1500 electron: '>= 13.0.0'
1478 dependencies: 1501 dependencies:
1479 electron: 30.0.1 1502 electron: 30.0.6
1480 dev: false 1503 dev: false
1481 1504
1482 /@electron/universal@1.5.1: 1505 /@electron/universal@1.5.1:
@@ -3001,12 +3024,12 @@ packages:
3001 path-to-regexp: 6.2.2 3024 path-to-regexp: 6.2.2
3002 dev: false 3025 dev: false
3003 3026
3004 /@syed_umair/electron-process-manager@1.1.0(electron@30.0.1): 3027 /@syed_umair/electron-process-manager@1.1.0(electron@30.0.6):
3005 resolution: {integrity: sha512-rbDJ1GQ51DcsP+354WpI/3xWOra1HH4cT1jA0maK8VJogn7s5BiMO2XkSBaCoeMCvTIIwx7Y6dgUHpfnL2M6wg==} 3028 resolution: {integrity: sha512-rbDJ1GQ51DcsP+354WpI/3xWOra1HH4cT1jA0maK8VJogn7s5BiMO2XkSBaCoeMCvTIIwx7Y6dgUHpfnL2M6wg==}
3006 peerDependencies: 3029 peerDependencies:
3007 electron: '>=21' 3030 electron: '>=21'
3008 dependencies: 3031 dependencies:
3009 electron: 30.0.1 3032 electron: 30.0.6
3010 electron-process-reporter: 1.4.0 3033 electron-process-reporter: 1.4.0
3011 dev: false 3034 dev: false
3012 3035
@@ -4512,17 +4535,17 @@ packages:
4512 resolve: 1.22.8 4535 resolve: 1.22.8
4513 dev: false 4536 dev: false
4514 4537
4515 /babel-plugin-styled-components@2.1.4(@babel/core@7.24.4)(styled-components@5.3.11): 4538 /babel-plugin-styled-components@2.1.4(@babel/core@7.24.5)(styled-components@5.3.11):
4516 resolution: {integrity: sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==} 4539 resolution: {integrity: sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==}
4517 peerDependencies: 4540 peerDependencies:
4518 styled-components: '>= 2' 4541 styled-components: '>= 2'
4519 dependencies: 4542 dependencies:
4520 '@babel/helper-annotate-as-pure': 7.22.5 4543 '@babel/helper-annotate-as-pure': 7.22.5
4521 '@babel/helper-module-imports': 7.22.15 4544 '@babel/helper-module-imports': 7.22.15
4522 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.4) 4545 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.5)
4523 lodash: 4.17.21 4546 lodash: 4.17.21
4524 picomatch: 2.3.1 4547 picomatch: 2.3.1
4525 styled-components: 5.3.11(@babel/core@7.24.4)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) 4548 styled-components: 5.3.11(@babel/core@7.24.5)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
4526 transitivePeerDependencies: 4549 transitivePeerDependencies:
4527 - '@babel/core' 4550 - '@babel/core'
4528 dev: false 4551 dev: false
@@ -4699,7 +4722,7 @@ packages:
4699 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 4722 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
4700 hasBin: true 4723 hasBin: true
4701 dependencies: 4724 dependencies:
4702 caniuse-lite: 1.0.30001611 4725 caniuse-lite: 1.0.30001620
4703 electron-to-chromium: 1.4.710 4726 electron-to-chromium: 1.4.710
4704 node-releases: 2.0.14 4727 node-releases: 2.0.14
4705 update-browserslist-db: 1.0.13(browserslist@4.23.0) 4728 update-browserslist-db: 1.0.13(browserslist@4.23.0)
@@ -4950,8 +4973,8 @@ packages:
4950 resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} 4973 resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
4951 dev: false 4974 dev: false
4952 4975
4953 /caniuse-lite@1.0.30001611: 4976 /caniuse-lite@1.0.30001620:
4954 resolution: {integrity: sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==} 4977 resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==}
4955 4978
4956 /caseless@0.12.0: 4979 /caseless@0.12.0:
4957 resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} 4980 resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@@ -6194,8 +6217,8 @@ packages:
6194 mkdirp: 0.5.6 6217 mkdirp: 0.5.6
6195 dev: false 6218 dev: false
6196 6219
6197 /electron@30.0.1: 6220 /electron@30.0.6:
6198 resolution: {integrity: sha512-iwxkI/n2wBd29NH7TH0ZY8aWGzCoKpzJz+D10u7aGSJi1TV6d4MSM3rWyKvT/UkAHkTKOEgYfUyCa2vWQm8L0g==} 6221 resolution: {integrity: sha512-PkhEPFdpYcTzjAO3gMHZ+map7g2+xCrMDedo/L1i0ir2BRXvAB93IkTJX497U6Srb/09r2cFt+k20VPNVCdw3Q==}
6199 engines: {node: '>= 12.20.55'} 6222 engines: {node: '>= 12.20.55'}
6200 hasBin: true 6223 hasBin: true
6201 requiresBuild: true 6224 requiresBuild: true
@@ -6947,7 +6970,7 @@ packages:
6947 string.prototype.matchall: 4.0.11 6970 string.prototype.matchall: 4.0.11
6948 dev: true 6971 dev: true
6949 6972
6950 /eslint-plugin-sonar@0.13.2(@babel/core@7.24.4)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5): 6973 /eslint-plugin-sonar@0.13.2(@babel/core@7.24.5)(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5):
6951 resolution: {integrity: sha512-7mZkk4/E2tGtIi3OCNCTmEWthweWzoAj9YcsxxIDI38r7q4RDusWDpsdGHPZXp/+QU8ZKkvmE11G4DtJW+k1kg==} 6974 resolution: {integrity: sha512-7mZkk4/E2tGtIi3OCNCTmEWthweWzoAj9YcsxxIDI38r7q4RDusWDpsdGHPZXp/+QU8ZKkvmE11G4DtJW+k1kg==}
6952 engines: {node: '>=14'} 6975 engines: {node: '>=14'}
6953 peerDependencies: 6976 peerDependencies:
@@ -6956,8 +6979,8 @@ packages:
6956 eslint: ^8.0.0 6979 eslint: ^8.0.0
6957 typescript: ^4.0.0 || ^5.0.0 6980 typescript: ^4.0.0 || ^5.0.0
6958 dependencies: 6981 dependencies:
6959 '@babel/core': 7.24.4 6982 '@babel/core': 7.24.5
6960 '@babel/eslint-parser': 7.23.10(@babel/core@7.24.4)(eslint@8.57.0) 6983 '@babel/eslint-parser': 7.23.10(@babel/core@7.24.5)(eslint@8.57.0)
6961 '@eslint-community/regexpp': 4.10.0 6984 '@eslint-community/regexpp': 4.10.0
6962 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) 6985 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5)
6963 '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) 6986 '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5)
@@ -11299,6 +11322,9 @@ packages:
11299 /picocolors@1.0.0: 11322 /picocolors@1.0.0:
11300 resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 11323 resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
11301 11324
11325 /picocolors@1.0.1:
11326 resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
11327
11302 /picomatch@2.3.1: 11328 /picomatch@2.3.1:
11303 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 11329 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
11304 engines: {node: '>=8.6'} 11330 engines: {node: '>=8.6'}
@@ -11935,7 +11961,7 @@ packages:
11935 resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} 11961 resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
11936 dev: false 11962 dev: false
11937 11963
11938 /react-loader-spinner@5.4.5(@babel/core@7.24.4)(react-dom@18.2.0)(react@18.2.0): 11964 /react-loader-spinner@5.4.5(@babel/core@7.24.5)(react-dom@18.2.0)(react@18.2.0):
11939 resolution: {integrity: sha512-32f+sb/v2tnNfyvnCCOS4fpyVHsGXjSyNo6QLniHcaj1XjKLxx14L2z0h6szRugOL8IEJ+53GPwNAdbkDqmy4g==} 11965 resolution: {integrity: sha512-32f+sb/v2tnNfyvnCCOS4fpyVHsGXjSyNo6QLniHcaj1XjKLxx14L2z0h6szRugOL8IEJ+53GPwNAdbkDqmy4g==}
11940 peerDependencies: 11966 peerDependencies:
11941 react: ^16.0.0 || ^17.0.0 || ^18.0.0 11967 react: ^16.0.0 || ^17.0.0 || ^18.0.0
@@ -11944,7 +11970,7 @@ packages:
11944 react: 18.2.0 11970 react: 18.2.0
11945 react-dom: 18.2.0(react@18.2.0) 11971 react-dom: 18.2.0(react@18.2.0)
11946 react-is: 18.2.0 11972 react-is: 18.2.0
11947 styled-components: 5.3.11(@babel/core@7.24.4)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) 11973 styled-components: 5.3.11(@babel/core@7.24.5)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
11948 styled-tools: 1.7.2 11974 styled-tools: 1.7.2
11949 transitivePeerDependencies: 11975 transitivePeerDependencies:
11950 - '@babel/core' 11976 - '@babel/core'
@@ -13379,7 +13405,7 @@ packages:
13379 engines: {node: '>=8'} 13405 engines: {node: '>=8'}
13380 dev: true 13406 dev: true
13381 13407
13382 /styled-components@5.3.11(@babel/core@7.24.4)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0): 13408 /styled-components@5.3.11(@babel/core@7.24.5)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0):
13383 resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==} 13409 resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==}
13384 engines: {node: '>=10'} 13410 engines: {node: '>=10'}
13385 peerDependencies: 13411 peerDependencies:
@@ -13392,7 +13418,7 @@ packages:
13392 '@emotion/is-prop-valid': 1.2.1 13418 '@emotion/is-prop-valid': 1.2.1
13393 '@emotion/stylis': 0.8.5 13419 '@emotion/stylis': 0.8.5
13394 '@emotion/unitless': 0.7.5 13420 '@emotion/unitless': 0.7.5
13395 babel-plugin-styled-components: 2.1.4(@babel/core@7.24.4)(styled-components@5.3.11) 13421 babel-plugin-styled-components: 2.1.4(@babel/core@7.24.5)(styled-components@5.3.11)
13396 css-to-react-native: 3.2.0 13422 css-to-react-native: 3.2.0
13397 hoist-non-react-statics: 3.3.2 13423 hoist-non-react-statics: 3.3.2
13398 react: 18.2.0 13424 react: 18.2.0
diff --git a/recipes b/recipes
Subproject 7ab6497fbd7bc64c3f2b17b587d273c9bbd155c Subproject ae724d09560576253952f8884497a8d797215ae
diff --git a/src/api/server/ServerApi.ts b/src/api/server/ServerApi.ts
index b5dd499ab..5434880dc 100644
--- a/src/api/server/ServerApi.ts
+++ b/src/api/server/ServerApi.ts
@@ -12,6 +12,7 @@ import {
12 statSync, 12 statSync,
13 writeFileSync, 13 writeFileSync,
14} from 'fs-extra'; 14} from 'fs-extra';
15import ms from 'ms';
15import tar from 'tar'; 16import tar from 'tar';
16 17
17import RecipeModel, { type IRecipe } from '../../models/Recipe'; 18import RecipeModel, { type IRecipe } from '../../models/Recipe';
@@ -460,7 +461,7 @@ export default class ServerApi {
460 } 461 }
461 debug(archivePath); 462 debug(archivePath);
462 463
463 await sleep(10); 464 await sleep(ms('10ms'));
464 465
465 await tar.x({ 466 await tar.x({
466 file: archivePath, 467 file: archivePath,
@@ -471,14 +472,21 @@ export default class ServerApi {
471 onwarn: x => debug('warn', recipeId, x), 472 onwarn: x => debug('warn', recipeId, x),
472 }); 473 });
473 474
474 await sleep(10); 475 await sleep(ms('10ms'));
475 476
476 const { id } = readJsonSync(join(recipeTempDirectory, 'package.json')); 477 const { id, defaultIcon } = readJsonSync(
478 join(recipeTempDirectory, 'package.json'),
479 );
477 const recipeDirectory = join(recipesDirectory, id); 480 const recipeDirectory = join(recipesDirectory, id);
478 copySync(recipeTempDirectory, recipeDirectory); 481 copySync(recipeTempDirectory, recipeDirectory);
479 removeSync(recipeTempDirectory); 482 removeSync(recipeTempDirectory);
480 removeSync(join(recipesDirectory, recipeId, 'recipe.tar.gz')); 483 removeSync(join(recipesDirectory, recipeId, 'recipe.tar.gz'));
481 484
485 // TODO: This is a temporary fix to remove svg icons from the user AppData. This should be removed after versions of all recipes have been bumped up
486 if (defaultIcon) {
487 removeSync(join(recipeDirectory, 'icon.svg'));
488 }
489
482 return id; 490 return id;
483 } 491 }
484 492
diff --git a/src/components/MediaSource.tsx b/src/components/MediaSource.tsx
new file mode 100644
index 000000000..34fbda227
--- /dev/null
+++ b/src/components/MediaSource.tsx
@@ -0,0 +1,171 @@
1import { ipcRenderer } from 'electron';
2import { type MouseEventHandler, useEffect, useState } from 'react';
3import {
4 type WrappedComponentProps,
5 defineMessages,
6 injectIntl,
7} from 'react-intl';
8import { SCREENSHARE_CANCELLED_BY_USER } from '../config';
9import { isWayland } from '../environment';
10import type Service from '../models/Service';
11import FullscreenLoader from './ui/FullscreenLoader';
12
13interface IProps extends WrappedComponentProps {
14 service: Service;
15}
16
17const messages = defineMessages({
18 loading: {
19 id: 'service.screenshare.loading',
20 defaultMessage: 'Loading screens and windows',
21 },
22 cancel: {
23 id: 'service.screenshare.cancel',
24 defaultMessage: 'Cancel',
25 },
26});
27
28interface ICancelButtonProps extends WrappedComponentProps {
29 handleOnClick: MouseEventHandler<HTMLButtonElement> | undefined;
30}
31
32const CancelButton = injectIntl(
33 ({ handleOnClick, intl }: ICancelButtonProps) => (
34 <li className="desktop-capturer-selection__item">
35 <button
36 type="button" // Add explicit type attribute
37 className="desktop-capturer-selection__btn"
38 data-id={SCREENSHARE_CANCELLED_BY_USER}
39 title="Cancel"
40 onClick={handleOnClick}
41 >
42 <span className="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">
43 {intl.formatMessage(messages.cancel)}
44 </span>
45 </button>
46 </li>
47 ),
48);
49
50function MediaSource(props: IProps) {
51 const { service, intl } = props;
52 const [sources, setSources] = useState<any>([]);
53 const [show, setShow] = useState<boolean>(false);
54 const [trackerId, setTrackerId] = useState<string | null>(null);
55 const [loadingSources, setLoadingSources] = useState<boolean>(false);
56
57 ipcRenderer.on(`select-capture-device:${service.id}`, (_event, data) => {
58 if (loadingSources) return;
59 setShow(true);
60 setTrackerId(data.trackerId);
61 });
62
63 const handleOnClick = (e: any) => {
64 const { id } = e.currentTarget.dataset;
65 window['ferdium'].actions.service.sendIPCMessage({
66 serviceId: service.id,
67 channel: `selected-media-source:${trackerId}`,
68 args: {
69 mediaSourceId: id,
70 },
71 });
72
73 setShow(false);
74 setSources([]);
75 setTrackerId(null);
76 };
77
78 // biome-ignore lint/correctness/useExhaustiveDependencies: This effect should only run when `show` changes
79 useEffect(() => {
80 if (show) {
81 setLoadingSources(true);
82 ipcRenderer
83 .invoke('get-desktop-capturer-sources')
84 .then(sources => {
85 if (isWayland) {
86 // On Linux, we do not need to prompt the user again for the source
87 handleOnClick({
88 currentTarget: { dataset: { id: sources[0].id } },
89 });
90 return;
91 }
92
93 setSources(sources);
94 setLoadingSources(false);
95 })
96 // silence the error
97 .catch(() => {
98 setShow(false);
99 setSources([]);
100 setLoadingSources(false);
101 });
102 } else {
103 setSources([]);
104 setLoadingSources(false);
105 }
106 }, [show]);
107
108 if (!show) {
109 return null;
110 }
111
112 if (loadingSources) {
113 return (
114 <div className="desktop-capturer-selection">
115 <ul
116 className="desktop-capturer-selection__list"
117 style={{
118 display: 'flex',
119 flexDirection: 'column',
120 alignItems: 'center',
121 }}
122 >
123 <FullscreenLoader title={intl.formatMessage(messages.loading)}>
124 <div style={{ display: 'flex', justifyContent: 'center' }}>
125 <CancelButton handleOnClick={handleOnClick} />
126 </div>
127 </FullscreenLoader>
128 </ul>
129 </div>
130 );
131 }
132
133 if (sources.length === 0) {
134 return (
135 <div className="desktop-capturer-selection">
136 <ul className="desktop-capturer-selection__list">
137 <li>No available sources.</li>
138 <CancelButton handleOnClick={handleOnClick} />
139 </ul>
140 </div>
141 );
142 }
143
144 return (
145 <div className="desktop-capturer-selection">
146 <ul className="desktop-capturer-selection__list">
147 {sources.map(({ id, name, thumbnail }) => (
148 <li className="desktop-capturer-selection__item" key={id}>
149 <button
150 type="button" // Add explicit type attribute
151 className="desktop-capturer-selection__btn"
152 data-id={id}
153 title={name}
154 onClick={handleOnClick}
155 >
156 <img
157 alt="Desktop capture preview"
158 className="desktop-capturer-selection__thumbnail"
159 src={thumbnail.toDataURL()}
160 />
161 <span className="desktop-capturer-selection__name">{name}</span>
162 </button>
163 </li>
164 ))}
165 </ul>
166 <CancelButton handleOnClick={handleOnClick} />
167 </div>
168 );
169}
170
171export default injectIntl(MediaSource);
diff --git a/src/components/services/content/ServiceView.tsx b/src/components/services/content/ServiceView.tsx
index 577473b5d..b7f539a5d 100644
--- a/src/components/services/content/ServiceView.tsx
+++ b/src/components/services/content/ServiceView.tsx
@@ -7,6 +7,7 @@ import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config';
7import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; 7import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
8import type ServiceModel from '../../../models/Service'; 8import type ServiceModel from '../../../models/Service';
9import type { RealStores } from '../../../stores'; 9import type { RealStores } from '../../../stores';
10import MediaSource from '../../MediaSource';
10import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; 11import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
11import WebviewLoader from '../../ui/WebviewLoader'; 12import WebviewLoader from '../../ui/WebviewLoader';
12import ServiceDisabled from './ServiceDisabled'; 13import ServiceDisabled from './ServiceDisabled';
@@ -164,6 +165,7 @@ class ServiceView extends Component<IProps, IState> {
164 ) : ( 165 ) : (
165 <> 166 <>
166 {showNavBar && <WebControlsScreen service={service} />} 167 {showNavBar && <WebControlsScreen service={service} />}
168 <MediaSource service={service} />
167 <ServiceWebview 169 <ServiceWebview
168 service={service} 170 service={service}
169 setWebviewReference={setWebviewRef} 171 setWebviewReference={setWebviewRef}
diff --git a/src/components/services/tabs/TabItem.tsx b/src/components/services/tabs/TabItem.tsx
index 0e999506a..94c0b7e7f 100644
--- a/src/components/services/tabs/TabItem.tsx
+++ b/src/components/services/tabs/TabItem.tsx
@@ -364,10 +364,10 @@ class TabItem extends Component<IProps, IState> {
364 role="presentation" 364 role="presentation"
365 onContextMenu={() => menu.popup()} 365 onContextMenu={() => menu.popup()}
366 data-tooltip-id="tooltip-sidebar-button" 366 data-tooltip-id="tooltip-sidebar-button"
367 data-tooltip-content={`${service.name} ${acceleratorString( 367 data-tooltip-content={`${service.name} ${acceleratorString({
368 shortcutIndex, 368 index: shortcutIndex,
369 cmdOrCtrlShortcutKey(false), 369 keyCombo: cmdOrCtrlShortcutKey(false),
370 )}`} 370 })}`}
371 > 371 >
372 <img src={service.icon} className="tab-item__icon" alt="" /> 372 <img src={service.icon} className="tab-item__icon" alt="" />
373 {showServiceNameSetting && ( 373 {showServiceNameSetting && (
diff --git a/src/components/settings/services/EditServiceForm.tsx b/src/components/settings/services/EditServiceForm.tsx
index 69893c16c..00629b6b6 100644
--- a/src/components/settings/services/EditServiceForm.tsx
+++ b/src/components/settings/services/EditServiceForm.tsx
@@ -396,6 +396,7 @@ class EditServiceForm extends Component<IProps, IState> {
396 396
397 <div className="settings__settings-group"> 397 <div className="settings__settings-group">
398 <H3>{intl.formatMessage(messages.headlineAppearance)}</H3> 398 <H3>{intl.formatMessage(messages.headlineAppearance)}</H3>
399 <Toggle {...form.$('useFavicon').bind()} />
399 <Toggle {...form.$('isDarkModeEnabled').bind()} /> 400 <Toggle {...form.$('isDarkModeEnabled').bind()} />
400 {form.$('isDarkModeEnabled').value && ( 401 {form.$('isDarkModeEnabled').value && (
401 <> 402 <>
diff --git a/src/components/settings/settings/EditSettingsForm.tsx b/src/components/settings/settings/EditSettingsForm.tsx
index c3e8d46a0..2900aa2af 100644
--- a/src/components/settings/settings/EditSettingsForm.tsx
+++ b/src/components/settings/settings/EditSettingsForm.tsx
@@ -1,5 +1,6 @@
1import { systemPreferences } from '@electron/remote'; 1import { systemPreferences } from '@electron/remote';
2import { mdiGithub, mdiOpenInNew, mdiPowerPlug } from '@mdi/js'; 2import { mdiGithub, mdiOpenInNew, mdiPowerPlug } from '@mdi/js';
3import { ipcRenderer } from 'electron';
3import { noop } from 'lodash'; 4import { noop } from 'lodash';
4import { observer } from 'mobx-react'; 5import { observer } from 'mobx-react';
5import prettyBytes from 'pretty-bytes'; 6import prettyBytes from 'pretty-bytes';
@@ -191,6 +192,14 @@ const messages = defineMessages({
191 id: 'settings.app.subheadlineCache', 192 id: 'settings.app.subheadlineCache',
192 defaultMessage: 'Cache', 193 defaultMessage: 'Cache',
193 }, 194 },
195 subheadlineUserAgent: {
196 id: 'settings.app.subheadlineUserAgent',
197 defaultMessage: 'User Agent',
198 },
199 subheadlineDownloads: {
200 id: 'settings.app.subheadlineDownloads',
201 defaultMessage: 'Downloads',
202 },
194 cacheInfo: { 203 cacheInfo: {
195 id: 'settings.app.cacheInfo', 204 id: 'settings.app.cacheInfo',
196 defaultMessage: 'Ferdium cache is currently using {size} of disk space.', 205 defaultMessage: 'Ferdium cache is currently using {size} of disk space.',
@@ -281,6 +290,10 @@ const messages = defineMessages({
281 id: 'settings.app.buttonOpenFerdiumCertsFolder', 290 id: 'settings.app.buttonOpenFerdiumCertsFolder',
282 defaultMessage: 'Open certificates folder', 291 defaultMessage: 'Open certificates folder',
283 }, 292 },
293 buttonOpenFolderSelector: {
294 id: 'settings.app.buttonOpenFolderSelector',
295 defaultMessage: 'Open folder selector',
296 },
284}); 297});
285 298
286const Hr = (): ReactElement => ( 299const Hr = (): ReactElement => (
@@ -1034,49 +1047,87 @@ class EditSettingsForm extends Component<IProps, IState> {
1034 1047
1035 <Hr /> 1048 <Hr />
1036 1049
1037 <Input 1050 <div className="settings__settings-group">
1038 placeholder="User Agent" 1051 <H3>{intl.formatMessage(messages.subheadlineDownloads)}</H3>
1039 onChange={e => this.submit(e)} 1052
1040 {...form.$('userAgentPref').bind()} 1053 <Input
1041 /> 1054 placeholder="Default download folder"
1042 <p className="settings__help"> 1055 onChange={e => {
1043 {intl.formatMessage(globalMessages.userAgentHelp)} 1056 this.submit(e);
1044 </p> 1057 }}
1045 <p className="settings__help"> 1058 {...form.$('downloadFolderPath').bind()}
1046 {intl.formatMessage(messages.appRestartRequired)} 1059 />
1047 </p> 1060
1061 <Button
1062 buttonType="secondary"
1063 label={intl.formatMessage(
1064 messages.buttonOpenFolderSelector,
1065 )}
1066 className="settings__open-settings-cache-button"
1067 onClick={e => {
1068 ipcRenderer
1069 .invoke('download-folder-select')
1070 .then(path => {
1071 if (path) {
1072 form.$('downloadFolderPath').set(path);
1073 this.submit(e);
1074 }
1075 })
1076 .catch(console.error);
1077 }}
1078 disabled={isClearingAllCache}
1079 loaded={!isClearingAllCache}
1080 />
1081 </div>
1048 1082
1049 <Hr /> 1083 <Hr />
1050 1084
1051 <div className="settings__settings-group"> 1085 <div className="settings__settings-group">
1052 <H3>{intl.formatMessage(messages.subheadlineCache)}</H3> 1086 <Input
1053 <p> 1087 placeholder="User Agent"
1054 {intl.formatMessage(messages.cacheInfo, { 1088 onChange={e => this.submit(e)}
1055 size: cacheSize, 1089 {...form.$('userAgentPref').bind()}
1056 })} 1090 />
1091
1092 <p className="settings__help">
1093 {intl.formatMessage(globalMessages.userAgentHelp)}
1057 </p> 1094 </p>
1058 {notCleared && ( 1095 <p className="settings__help">
1059 <p>{intl.formatMessage(messages.cacheNotCleared)}</p> 1096 {intl.formatMessage(messages.appRestartRequired)}
1060 )} 1097 </p>
1098
1099 <Hr />
1100
1061 <div className="settings__settings-group"> 1101 <div className="settings__settings-group">
1062 <div className="settings__open-settings-cache-container"> 1102 <H3>{intl.formatMessage(messages.subheadlineCache)}</H3>
1063 <Button 1103 <p>
1064 buttonType="secondary" 1104 {intl.formatMessage(messages.cacheInfo, {
1065 label={intl.formatMessage(globalMessages.clearCache)} 1105 size: cacheSize,
1066 className="settings__open-settings-cache-button" 1106 })}
1067 onClick={() => { 1107 </p>
1068 onClearAllCache(); 1108 {notCleared && (
1069 this.onClearCacheClicked(); 1109 <p>{intl.formatMessage(messages.cacheNotCleared)}</p>
1070 }} 1110 )}
1071 disabled={isClearingAllCache} 1111 <div className="settings__settings-group">
1072 loaded={!isClearingAllCache} 1112 <div className="settings__open-settings-cache-container">
1073 /> 1113 <Button
1074 <Button 1114 buttonType="secondary"
1075 buttonType="secondary" 1115 label={intl.formatMessage(globalMessages.clearCache)}
1076 label="Open Process Manager" 1116 className="settings__open-settings-cache-button"
1077 className="settings__open-settings-cache-button" 1117 onClick={() => {
1078 onClick={openProcessManager} 1118 onClearAllCache();
1079 /> 1119 this.onClearCacheClicked();
1120 }}
1121 disabled={isClearingAllCache}
1122 loaded={!isClearingAllCache}
1123 />
1124 <Button
1125 buttonType="secondary"
1126 label="Open Process Manager"
1127 className="settings__open-settings-cache-button"
1128 onClick={openProcessManager}
1129 />
1130 </div>
1080 </div> 1131 </div>
1081 </div> 1132 </div>
1082 </div> 1133 </div>
diff --git a/src/config.ts b/src/config.ts
index f086c54ee..0034b00a6 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -49,6 +49,9 @@ export const WEBRTC_IP_HANDLING_POLICY = {
49 [disableWebRTCIPHandlingPolicy]: 'Do not expose public or local IPs', 49 [disableWebRTCIPHandlingPolicy]: 'Do not expose public or local IPs',
50}; 50};
51 51
52export const SCREENSHARE_CANCELLED_BY_USER =
53 'desktop-capturer-selection__cancel';
54
52// TODO: Need to convert many of these to i18n 55// TODO: Need to convert many of these to i18n
53export const HIBERNATION_STRATEGIES = { 56export const HIBERNATION_STRATEGIES = {
54 10: 'Extremely Fast Hibernation (10sec)', 57 10: 'Extremely Fast Hibernation (10sec)',
@@ -398,6 +401,7 @@ export const DEFAULT_APP_SETTINGS = {
398 automaticUpdates: true, 401 automaticUpdates: true,
399 universalDarkMode: true, 402 universalDarkMode: true,
400 userAgentPref: '', 403 userAgentPref: '',
404 downloadFolderPath: '',
401 adaptableDarkMode: true, 405 adaptableDarkMode: true,
402 accentColor: DEFAULT_ACCENT_COLOR, 406 accentColor: DEFAULT_ACCENT_COLOR,
403 progressbarAccentColor: DEFAULT_ACCENT_COLOR, 407 progressbarAccentColor: DEFAULT_ACCENT_COLOR,
@@ -440,6 +444,7 @@ export const DEFAULT_SERVICE_SETTINGS = {
440 isBadgeEnabled: true, 444 isBadgeEnabled: true,
441 isMediaBadgeEnabled: false, 445 isMediaBadgeEnabled: false,
442 trapLinkClicks: false, 446 trapLinkClicks: false,
447 useFavicon: false,
443 isMuted: false, 448 isMuted: false,
444 customIcon: false, 449 customIcon: false,
445 isDarkModeEnabled: false, 450 isDarkModeEnabled: false,
diff --git a/src/containers/auth/SetupAssistantScreen.tsx b/src/containers/auth/SetupAssistantScreen.tsx
index d15b4e6e1..b73b5705d 100644
--- a/src/containers/auth/SetupAssistantScreen.tsx
+++ b/src/containers/auth/SetupAssistantScreen.tsx
@@ -1,4 +1,5 @@
1import { inject, observer } from 'mobx-react'; 1import { inject, observer } from 'mobx-react';
2import ms from 'ms';
2import { Component, type ReactElement } from 'react'; 3import { Component, type ReactElement } from 'react';
3import type { StoresProps } from '../../@types/ferdium-components.types'; 4import type { StoresProps } from '../../@types/ferdium-components.types';
4import type { ILegacyServices } from '../../@types/legacy-types'; 5import type { ILegacyServices } from '../../@types/legacy-types';
@@ -83,11 +84,11 @@ class SetupAssistantScreen extends Component<IProps, IState> {
83 }); 84 });
84 85
85 // eslint-disable-next-line no-await-in-loop 86 // eslint-disable-next-line no-await-in-loop
86 await sleep(100); 87 await sleep(ms('100ms'));
87 } 88 }
88 89
89 this.setState({ isSettingUpServices: false }); 90 this.setState({ isSettingUpServices: false });
90 await sleep(100); 91 await sleep(ms('100ms'));
91 router.push('/'); 92 router.push('/');
92 } 93 }
93 94
diff --git a/src/containers/settings/EditServiceScreen.tsx b/src/containers/settings/EditServiceScreen.tsx
index f3b9b0857..e890e8695 100644
--- a/src/containers/settings/EditServiceScreen.tsx
+++ b/src/containers/settings/EditServiceScreen.tsx
@@ -96,6 +96,10 @@ const messages = defineMessages({
96 id: 'settings.service.form.trapLinkClicks', 96 id: 'settings.service.form.trapLinkClicks',
97 defaultMessage: 'Open URLs within Ferdium', 97 defaultMessage: 'Open URLs within Ferdium',
98 }, 98 },
99 useFavicon: {
100 id: 'settings.service.form.useFavicon',
101 defaultMessage: 'Use service favicon instead of default or custom icon',
102 },
99 onlyShowFavoritesInUnreadCount: { 103 onlyShowFavoritesInUnreadCount: {
100 id: 'settings.service.form.onlyShowFavoritesInUnreadCount', 104 id: 'settings.service.form.onlyShowFavoritesInUnreadCount',
101 defaultMessage: 'Only show Favorites in unread count', 105 defaultMessage: 'Only show Favorites in unread count',
@@ -258,6 +262,15 @@ class EditServiceScreen extends Component<IProps> {
258 default: DEFAULT_SERVICE_SETTINGS.trapLinkClicks, 262 default: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
259 type: 'checkbox', 263 type: 'checkbox',
260 }, 264 },
265 useFavicon: {
266 label: intl.formatMessage(messages.useFavicon),
267 value: ifUndefined<boolean>(
268 service?.useFavicon,
269 DEFAULT_SERVICE_SETTINGS.useFavicon,
270 ),
271 default: DEFAULT_SERVICE_SETTINGS.useFavicon,
272 type: 'checkbox',
273 },
261 isMuted: { 274 isMuted: {
262 label: intl.formatMessage(messages.enableAudio), 275 label: intl.formatMessage(messages.enableAudio),
263 value: !ifUndefined<boolean>( 276 value: !ifUndefined<boolean>(
diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx
index fdd9bd8bc..31e8d6484 100644
--- a/src/containers/settings/EditSettingsScreen.tsx
+++ b/src/containers/settings/EditSettingsScreen.tsx
@@ -341,6 +341,32 @@ const messages = defineMessages({
341 id: 'settings.app.form.keepAllWorkspacesLoaded', 341 id: 'settings.app.form.keepAllWorkspacesLoaded',
342 defaultMessage: 'Keep all workspaces loaded', 342 defaultMessage: 'Keep all workspaces loaded',
343 }, 343 },
344 downloadFolderPath: {
345 id: 'settings.app.form.downloadFolderPath',
346 defaultMessage:
347 'Default download folder (leave blank to be prompted for each download)',
348 },
349 restartDialogTitle: {
350 id: 'settings.app.restart.restartDialogTitle',
351 defaultMessage: 'Ferdium - Relaunch Application',
352 },
353 restartNow: {
354 id: 'settings.app.restart.restartNow',
355 defaultMessage: 'Restart now',
356 },
357 restartLater: {
358 id: 'settings.app.restart.restartLater',
359 defaultMessage: 'Restart later',
360 },
361 restartDialogMessage: {
362 id: 'settings.app.restart.restartDialogMessage',
363 defaultMessage: 'Do you want to relaunch Ferdium?',
364 },
365 restartDialogDetail: {
366 id: 'settings.app.restart.restartDialogDetail',
367 defaultMessage:
368 'You made a change that requires a restart. This will close Ferdium and restart it.',
369 },
344}); 370});
345 371
346interface EditSettingsScreenProps extends StoresProps, WrappedComponentProps {} 372interface EditSettingsScreenProps extends StoresProps, WrappedComponentProps {}
@@ -364,6 +390,7 @@ class EditSettingsScreen extends Component<
364 } 390 }
365 391
366 onSubmit(settingsData) { 392 onSubmit(settingsData) {
393 const { intl } = this.props;
367 const { todos, workspaces } = this.props.stores; 394 const { todos, workspaces } = this.props.stores;
368 const { 395 const {
369 app, 396 app,
@@ -386,97 +413,130 @@ class EditSettingsScreen extends Component<
386 413
387 debug(`Updating settings store with data: ${settingsData}`); 414 debug(`Updating settings store with data: ${settingsData}`);
388 415
416 const { app: currentSettings } = this.props.stores.settings.all;
417
418 const newSettings = {
419 runInBackground: Boolean(settingsData.runInBackground),
420 enableSystemTray: Boolean(settingsData.enableSystemTray),
421 reloadAfterResume: Boolean(settingsData.reloadAfterResume),
422 reloadAfterResumeTime: Number(settingsData.reloadAfterResumeTime),
423 startMinimized: Boolean(settingsData.startMinimized),
424 confirmOnQuit: Boolean(settingsData.confirmOnQuit),
425 minimizeToSystemTray: Boolean(settingsData.minimizeToSystemTray),
426 closeToSystemTray: Boolean(settingsData.closeToSystemTray),
427 privateNotifications: Boolean(settingsData.privateNotifications),
428 clipboardNotifications: Boolean(settingsData.clipboardNotifications),
429 notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage),
430 isTwoFactorAutoCatcherEnabled: Boolean(
431 settingsData.isTwoFactorAutoCatcherEnabled,
432 ),
433 twoFactorAutoCatcherMatcher: settingsData.twoFactorAutoCatcherMatcher,
434 navigationBarBehaviour: settingsData.navigationBarBehaviour,
435 webRTCIPHandlingPolicy: settingsData.webRTCIPHandlingPolicy,
436 searchEngine: settingsData.searchEngine,
437 translatorEngine: settingsData.translatorEngine,
438 translatorLanguage: settingsData.translatorLanguage,
439 sentry: Boolean(settingsData.sentry),
440 hibernateOnStartup: Boolean(settingsData.hibernateOnStartup),
441 hibernationStrategy: Number(settingsData.hibernationStrategy),
442 wakeUpStrategy: Number(settingsData.wakeUpStrategy),
443 wakeUpHibernationStrategy: Number(settingsData.wakeUpHibernationStrategy),
444 wakeUpHibernationSplay: Boolean(settingsData.wakeUpHibernationSplay),
445 predefinedTodoServer: settingsData.predefinedTodoServer,
446 customTodoServer: settingsData.customTodoServer,
447 isLockingFeatureEnabled: Boolean(settingsData.isLockingFeatureEnabled),
448 lockedPassword: useOriginalPassword
449 ? this.props.stores.settings.all.app.lockedPassword
450 : hash(String(settingsData.lockedPassword)),
451 useTouchIdToUnlock: Boolean(settingsData.useTouchIdToUnlock),
452 inactivityLock: Number(settingsData.inactivityLock),
453 scheduledDNDEnabled: Boolean(settingsData.scheduledDNDEnabled),
454 scheduledDNDStart: settingsData.scheduledDNDStart,
455 scheduledDNDEnd: settingsData.scheduledDNDEnd,
456 enableGPUAcceleration: Boolean(settingsData.enableGPUAcceleration),
457 downloadFolderPath: String(settingsData.downloadFolderPath),
458 enableGlobalHideShortcut: Boolean(settingsData.enableGlobalHideShortcut),
459 showDisabledServices: Boolean(settingsData.showDisabledServices),
460 showServiceName: Boolean(settingsData.showServiceName),
461 darkMode: Boolean(settingsData.darkMode),
462 adaptableDarkMode: Boolean(settingsData.adaptableDarkMode),
463 universalDarkMode: Boolean(settingsData.universalDarkMode),
464 splitMode: Boolean(settingsData.splitMode),
465 splitColumns: Number(settingsData.splitColumns),
466 serviceRibbonWidth: Number(settingsData.serviceRibbonWidth),
467 sidebarServicesLocation: Number(settingsData.sidebarServicesLocation),
468 iconSize: Number(settingsData.iconSize),
469 enableLongPressServiceHint: Boolean(
470 settingsData.enableLongPressServiceHint,
471 ),
472 useHorizontalStyle: Boolean(settingsData.useHorizontalStyle),
473 hideCollapseButton: Boolean(settingsData.hideCollapseButton),
474 hideRecipesButton: Boolean(settingsData.hideRecipesButton),
475 hideSplitModeButton: Boolean(settingsData.hideSplitModeButton),
476 useGrayscaleServices: Boolean(settingsData.useGrayscaleServices),
477 grayscaleServicesDim: Number(settingsData.grayscaleServicesDim),
478 hideWorkspacesButton: Boolean(settingsData.hideWorkspacesButton),
479 hideNotificationsButton: Boolean(settingsData.hideNotificationsButton),
480 hideSettingsButton: Boolean(settingsData.hideSettingsButton),
481 hideDownloadButton: Boolean(settingsData.hideDownloadButton),
482 alwaysShowWorkspaces: Boolean(settingsData.alwaysShowWorkspaces),
483 hideAllServicesWorkspace: Boolean(settingsData.hideAllServicesWorkspace),
484 accentColor: settingsData.accentColor,
485 progressbarAccentColor: settingsData.progressbarAccentColor,
486 showMessageBadgeWhenMuted: Boolean(
487 settingsData.showMessageBadgeWhenMuted,
488 ),
489 showDragArea: Boolean(settingsData.showDragArea),
490 enableSpellchecking: Boolean(settingsData.enableSpellchecking),
491 enableTranslator: Boolean(settingsData.enableTranslator),
492 useSelfSignedCertificates: Boolean(
493 settingsData.useSelfSignedCertificates,
494 ),
495 spellcheckerLanguage: settingsData.spellcheckerLanguage,
496 userAgentPref: settingsData.userAgentPref,
497 beta: Boolean(settingsData.beta), // we need this info in the main process as well
498 automaticUpdates: Boolean(settingsData.automaticUpdates), // we need this info in the main process as well
499 locale: settingsData.locale, // we need this info in the main process as well
500 };
501
502 const requiredRestartKeys = [
503 'webRTCIPHandlingPolicy',
504 'sentry',
505 'searchEngine',
506 'enableSpellchecking',
507 'spellcheckerLanguage',
508 'enableGlobalHideShortcut',
509 // 'userAgentPref', // TODO: this is an input field, so it changes on every key stroke
510 ];
511
512 // Check if any of the keys that require a restart have changed
513 const requiresRestart = requiredRestartKeys.some(
514 key => newSettings[key] !== currentSettings[key],
515 );
516
517 if (requiresRestart) {
518 debug('Settings require restart');
519
520 const options: Electron.MessageBoxOptions = {
521 type: 'warning',
522 buttons: [
523 intl.formatMessage(messages.restartNow),
524 intl.formatMessage(messages.restartLater),
525 ],
526 defaultId: 0,
527 cancelId: 1,
528 title: intl.formatMessage(messages.restartDialogTitle),
529 message: intl.formatMessage(messages.restartDialogMessage),
530 detail: intl.formatMessage(messages.restartDialogDetail),
531 };
532
533 ipcRenderer.send('relaunch-app', options);
534 }
535
389 settings.update({ 536 settings.update({
390 type: 'app', 537 type: 'app',
391 // TODO: The conversions might not be necessary once we convert to typescript 538 // TODO: The conversions might not be necessary once we convert to typescript
392 data: { 539 data: newSettings,
393 runInBackground: Boolean(settingsData.runInBackground),
394 enableSystemTray: Boolean(settingsData.enableSystemTray),
395 reloadAfterResume: Boolean(settingsData.reloadAfterResume),
396 reloadAfterResumeTime: Number(settingsData.reloadAfterResumeTime),
397 startMinimized: Boolean(settingsData.startMinimized),
398 confirmOnQuit: Boolean(settingsData.confirmOnQuit),
399 minimizeToSystemTray: Boolean(settingsData.minimizeToSystemTray),
400 closeToSystemTray: Boolean(settingsData.closeToSystemTray),
401 privateNotifications: Boolean(settingsData.privateNotifications),
402 clipboardNotifications: Boolean(settingsData.clipboardNotifications),
403 notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage),
404 isTwoFactorAutoCatcherEnabled: Boolean(
405 settingsData.isTwoFactorAutoCatcherEnabled,
406 ),
407 twoFactorAutoCatcherMatcher: settingsData.twoFactorAutoCatcherMatcher,
408 navigationBarBehaviour: settingsData.navigationBarBehaviour,
409 webRTCIPHandlingPolicy: settingsData.webRTCIPHandlingPolicy,
410 searchEngine: settingsData.searchEngine,
411 translatorEngine: settingsData.translatorEngine,
412 translatorLanguage: settingsData.translatorLanguage,
413 sentry: Boolean(settingsData.sentry),
414 hibernateOnStartup: Boolean(settingsData.hibernateOnStartup),
415 hibernationStrategy: Number(settingsData.hibernationStrategy),
416 wakeUpStrategy: Number(settingsData.wakeUpStrategy),
417 wakeUpHibernationStrategy: Number(
418 settingsData.wakeUpHibernationStrategy,
419 ),
420 wakeUpHibernationSplay: Boolean(settingsData.wakeUpHibernationSplay),
421 predefinedTodoServer: settingsData.predefinedTodoServer,
422 customTodoServer: settingsData.customTodoServer,
423 isLockingFeatureEnabled: Boolean(settingsData.isLockingFeatureEnabled),
424 lockedPassword: useOriginalPassword
425 ? this.props.stores.settings.all.app.lockedPassword
426 : hash(String(settingsData.lockedPassword)),
427 useTouchIdToUnlock: Boolean(settingsData.useTouchIdToUnlock),
428 inactivityLock: Number(settingsData.inactivityLock),
429 scheduledDNDEnabled: Boolean(settingsData.scheduledDNDEnabled),
430 scheduledDNDStart: settingsData.scheduledDNDStart,
431 scheduledDNDEnd: settingsData.scheduledDNDEnd,
432 enableGPUAcceleration: Boolean(settingsData.enableGPUAcceleration),
433 enableGlobalHideShortcut: Boolean(
434 settingsData.enableGlobalHideShortcut,
435 ),
436 showDisabledServices: Boolean(settingsData.showDisabledServices),
437 showServiceName: Boolean(settingsData.showServiceName),
438 darkMode: Boolean(settingsData.darkMode),
439 adaptableDarkMode: Boolean(settingsData.adaptableDarkMode),
440 universalDarkMode: Boolean(settingsData.universalDarkMode),
441 splitMode: Boolean(settingsData.splitMode),
442 splitColumns: Number(settingsData.splitColumns),
443 serviceRibbonWidth: Number(settingsData.serviceRibbonWidth),
444 sidebarServicesLocation: Number(settingsData.sidebarServicesLocation),
445 iconSize: Number(settingsData.iconSize),
446 enableLongPressServiceHint: Boolean(
447 settingsData.enableLongPressServiceHint,
448 ),
449 useHorizontalStyle: Boolean(settingsData.useHorizontalStyle),
450 hideCollapseButton: Boolean(settingsData.hideCollapseButton),
451 hideRecipesButton: Boolean(settingsData.hideRecipesButton),
452 hideSplitModeButton: Boolean(settingsData.hideSplitModeButton),
453 useGrayscaleServices: Boolean(settingsData.useGrayscaleServices),
454 grayscaleServicesDim: Number(settingsData.grayscaleServicesDim),
455 hideWorkspacesButton: Boolean(settingsData.hideWorkspacesButton),
456 hideNotificationsButton: Boolean(settingsData.hideNotificationsButton),
457 hideSettingsButton: Boolean(settingsData.hideSettingsButton),
458 hideDownloadButton: Boolean(settingsData.hideDownloadButton),
459 alwaysShowWorkspaces: Boolean(settingsData.alwaysShowWorkspaces),
460 hideAllServicesWorkspace: Boolean(
461 settingsData.hideAllServicesWorkspace,
462 ),
463 accentColor: settingsData.accentColor,
464 progressbarAccentColor: settingsData.progressbarAccentColor,
465 showMessageBadgeWhenMuted: Boolean(
466 settingsData.showMessageBadgeWhenMuted,
467 ),
468 showDragArea: Boolean(settingsData.showDragArea),
469 enableSpellchecking: Boolean(settingsData.enableSpellchecking),
470 enableTranslator: Boolean(settingsData.enableTranslator),
471 useSelfSignedCertificates: Boolean(
472 settingsData.useSelfSignedCertificates,
473 ),
474 spellcheckerLanguage: settingsData.spellcheckerLanguage,
475 userAgentPref: settingsData.userAgentPref,
476 beta: Boolean(settingsData.beta), // we need this info in the main process as well
477 automaticUpdates: Boolean(settingsData.automaticUpdates), // we need this info in the main process as well
478 locale: settingsData.locale, // we need this info in the main process as well
479 },
480 }); 540 });
481 541
482 user.update({ 542 user.update({
@@ -982,6 +1042,14 @@ class EditSettingsScreen extends Component<
982 default: DEFAULT_APP_SETTINGS.userAgentPref, 1042 default: DEFAULT_APP_SETTINGS.userAgentPref,
983 placeholder: defaultUserAgent(), 1043 placeholder: defaultUserAgent(),
984 }, 1044 },
1045 downloadFolderPath: {
1046 label: intl.formatMessage(messages.downloadFolderPath),
1047 value: ifUndefined<string>(
1048 settings.all.app.downloadFolderPath,
1049 DEFAULT_APP_SETTINGS.downloadFolderPath,
1050 ),
1051 default: DEFAULT_APP_SETTINGS.userAgentPref,
1052 },
985 darkMode: { 1053 darkMode: {
986 label: intl.formatMessage(messages.darkMode), 1054 label: intl.formatMessage(messages.darkMode),
987 value: ifUndefined<boolean>( 1055 value: ifUndefined<boolean>(
diff --git a/src/electron/ipc-api/dnd.ts b/src/electron/ipc-api/dnd.ts
index 30ace7c84..280521a9f 100644
--- a/src/electron/ipc-api/dnd.ts
+++ b/src/electron/ipc-api/dnd.ts
@@ -8,7 +8,6 @@ export default async () => {
8 if (!isMac) { 8 if (!isMac) {
9 return false; 9 return false;
10 } 10 }
11
12 const { getDoNotDisturb } = await import('macos-notification-state'); 11 const { getDoNotDisturb } = await import('macos-notification-state');
13 12
14 if (!getDoNotDisturb) { 13 if (!getDoNotDisturb) {
diff --git a/src/electron/ipc-api/download.ts b/src/electron/ipc-api/download.ts
index d749cb889..a306ba68d 100644
--- a/src/electron/ipc-api/download.ts
+++ b/src/electron/ipc-api/download.ts
@@ -52,4 +52,14 @@ export default (params: { mainWindow: BrowserWindow }) => {
52 } 52 }
53 }, 53 },
54 ); 54 );
55
56 ipcMain.handle('download-folder-select', async () => {
57 const result = await dialog.showOpenDialog(params.mainWindow, {
58 properties: ['openDirectory'],
59 });
60
61 if (result.canceled) return null;
62
63 return result.filePaths[0];
64 });
55}; 65};
diff --git a/src/environment.ts b/src/environment.ts
index 87e2f4f66..4a0399876 100644
--- a/src/environment.ts
+++ b/src/environment.ts
@@ -7,6 +7,8 @@ export const isWindows = process.platform === 'win32';
7export const isLinux = process.platform === 'linux'; 7export const isLinux = process.platform === 'linux';
8export const isWinPortable = process.env.PORTABLE_EXECUTABLE_FILE != null; 8export const isWinPortable = process.env.PORTABLE_EXECUTABLE_FILE != null;
9 9
10export const isWayland = isLinux && process.env.XDG_SESSION_TYPE === 'wayland';
11
10export const electronVersion: string = process.versions.electron ?? ''; 12export const electronVersion: string = process.versions.electron ?? '';
11export const chromeVersion: string = process.versions.chrome ?? ''; 13export const chromeVersion: string = process.versions.chrome ?? '';
12export const nodeVersion: string = process.versions.node; 14export const nodeVersion: string = process.versions.node;
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.tsx b/src/features/workspaces/components/WorkspaceDrawerItem.tsx
index 01a18ffb0..23851edf6 100644
--- a/src/features/workspaces/components/WorkspaceDrawerItem.tsx
+++ b/src/features/workspaces/components/WorkspaceDrawerItem.tsx
@@ -125,10 +125,10 @@ class WorkspaceDrawerItem extends Component<IProps> {
125 }} 125 }}
126 onKeyDown={noop} 126 onKeyDown={noop}
127 data-tooltip-id="tooltip-workspaces-drawer" 127 data-tooltip-id="tooltip-workspaces-drawer"
128 data-tooltip-content={acceleratorString( 128 data-tooltip-content={acceleratorString({
129 shortcutIndex, 129 index: shortcutIndex,
130 `${cmdOrCtrlShortcutKey(false)}+${altKey(false)}`, 130 keyCombo: `${cmdOrCtrlShortcutKey(false)}+${altKey(false)}`,
131 )} 131 })}
132 > 132 >
133 <span 133 <span
134 className={classnames([ 134 className={classnames([
diff --git a/src/helpers/favicon-helpers.ts b/src/helpers/favicon-helpers.ts
new file mode 100644
index 000000000..5743d938f
--- /dev/null
+++ b/src/helpers/favicon-helpers.ts
@@ -0,0 +1,3 @@
1export function getFaviconUrl(url: string, size: number = 128): string {
2 return `https://www.google.com/s2/favicons?sz=${size}&domain_url=${url}`;
3}
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index d2d317586..50ab8d4ce 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -16,6 +16,7 @@
16 "feature.publishDebugInfo.info": "Die Veröffentlichung deiner Debug-Informationen hilft uns, Probleme und Fehler in Ferdium zu finden. Indem du deine Debug-Informationen veröffentlichst, akzeptierst du die Datenschutzbestimmungen und Nutzungsbedingungen vom Ferdium Debugger", 16 "feature.publishDebugInfo.info": "Die Veröffentlichung deiner Debug-Informationen hilft uns, Probleme und Fehler in Ferdium zu finden. Indem du deine Debug-Informationen veröffentlichst, akzeptierst du die Datenschutzbestimmungen und Nutzungsbedingungen vom Ferdium Debugger",
17 "feature.publishDebugInfo.privacy": "Datenschutzrichtlinien", 17 "feature.publishDebugInfo.privacy": "Datenschutzrichtlinien",
18 "feature.publishDebugInfo.publish": "Akzeptieren und Veröffentlichen", 18 "feature.publishDebugInfo.publish": "Akzeptieren und Veröffentlichen",
19 "feature.publishDebugInfo.published": "Ihr Debug-Protokoll wurde veröffentlicht und ist nun verfügbar unter",
19 "feature.publishDebugInfo.terms": "Nutzungsbedingungen", 20 "feature.publishDebugInfo.terms": "Nutzungsbedingungen",
20 "feature.publishDebugInfo.title": "Debug-Informationen veröffentlichen", 21 "feature.publishDebugInfo.title": "Debug-Informationen veröffentlichen",
21 "feature.quickSwitch.info": "Wähle einen Dienst mit TAB, ↑ und ↓. Um einen Dienst zu öffnen, drücke ENTER.", 22 "feature.quickSwitch.info": "Wähle einen Dienst mit TAB, ↑ und ↓. Um einen Dienst zu öffnen, drücke ENTER.",
@@ -24,6 +25,7 @@
24 "global.api.unhealthy": "Verbindung zu {serverNameParse} Online-Diensten kann nicht hergestellt werden", 25 "global.api.unhealthy": "Verbindung zu {serverNameParse} Online-Diensten kann nicht hergestellt werden",
25 "global.cancel": "Abbrechen", 26 "global.cancel": "Abbrechen",
26 "global.clearCache": "Cache leeren", 27 "global.clearCache": "Cache leeren",
28 "global.downloads": "Downloads",
27 "global.edit": "Bearbeiten", 29 "global.edit": "Bearbeiten",
28 "global.no": "Nein", 30 "global.no": "Nein",
29 "global.notConnectedToTheInternet": "Du bist nicht mit dem Internet verbunden.", 31 "global.notConnectedToTheInternet": "Du bist nicht mit dem Internet verbunden.",
@@ -122,6 +124,7 @@
122 "menu.view.back": "Zurück", 124 "menu.view.back": "Zurück",
123 "menu.view.forward": "Vorwärts", 125 "menu.view.forward": "Vorwärts",
124 "menu.view.lockFerdium": "Ferdium sperren", 126 "menu.view.lockFerdium": "Ferdium sperren",
127 "menu.view.openProcessManager": "Prozessverwaltung öffnen",
125 "menu.view.openQuickSwitch": "Quick Switch öffnen", 128 "menu.view.openQuickSwitch": "Quick Switch öffnen",
126 "menu.view.reloadFerdium": "Ferdium neu laden", 129 "menu.view.reloadFerdium": "Ferdium neu laden",
127 "menu.view.reloadService": "Dienst neu laden", 130 "menu.view.reloadService": "Dienst neu laden",
@@ -161,6 +164,8 @@
161 "service.errorHandler.headline": "Oh nein!", 164 "service.errorHandler.headline": "Oh nein!",
162 "service.errorHandler.message": "Fehler", 165 "service.errorHandler.message": "Fehler",
163 "service.errorHandler.text": "{name} konnte nicht geladen werden.", 166 "service.errorHandler.text": "{name} konnte nicht geladen werden.",
167 "service.screenshare.cancel": "Abbrechen",
168 "service.screenshare.loading": "Bildschirme und Fenster werden geladen",
164 "service.webviewLoader.loading": "{service} wird geladen", 169 "service.webviewLoader.loading": "{service} wird geladen",
165 "services.getStarted": "Los geht's", 170 "services.getStarted": "Los geht's",
166 "services.login": "Bitte anmelden, um Ferdium nutzen zu können.", 171 "services.login": "Bitte anmelden, um Ferdium nutzen zu können.",
@@ -189,6 +194,7 @@
189 "settings.app.buttonOpenFerdiumCertsFolder": "Ordner „Zertifikate†öffnen", 194 "settings.app.buttonOpenFerdiumCertsFolder": "Ordner „Zertifikate†öffnen",
190 "settings.app.buttonOpenFerdiumProfileFolder": "Profilordner anzeigen", 195 "settings.app.buttonOpenFerdiumProfileFolder": "Profilordner anzeigen",
191 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Rezeptordner öffnen", 196 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Rezeptordner öffnen",
197 "settings.app.buttonOpenFolderSelector": "Ordnerauswahl öffnen",
192 "settings.app.buttonOpenImportExport": "Importieren/Exportieren", 198 "settings.app.buttonOpenImportExport": "Importieren/Exportieren",
193 "settings.app.buttonSearchForUpdate": "Nach Aktualisierungen suchen", 199 "settings.app.buttonSearchForUpdate": "Nach Aktualisierungen suchen",
194 "settings.app.buttonShowChangelog": "Änderungen zeigen", 200 "settings.app.buttonShowChangelog": "Änderungen zeigen",
@@ -208,6 +214,7 @@
208 "settings.app.form.confirmOnQuit": "Beim Beenden von Ferdium bestätigen", 214 "settings.app.form.confirmOnQuit": "Beim Beenden von Ferdium bestätigen",
209 "settings.app.form.customTodoServer": "Eigener Todo Server", 215 "settings.app.form.customTodoServer": "Eigener Todo Server",
210 "settings.app.form.darkMode": "Dark Mode aktivieren", 216 "settings.app.form.darkMode": "Dark Mode aktivieren",
217 "settings.app.form.downloadFolderPath": "Standard-Download-Ordner (leer lassen, um bei jedem Download gefragt zu werden)",
211 "settings.app.form.enableGPUAcceleration": "Hardwarebeschleunigung aktivieren", 218 "settings.app.form.enableGPUAcceleration": "Hardwarebeschleunigung aktivieren",
212 "settings.app.form.enableGlobalHideShortcut": "Allgemeine Verknüpfung zum Ausblenden von Ferdium aktivieren", 219 "settings.app.form.enableGlobalHideShortcut": "Allgemeine Verknüpfung zum Ausblenden von Ferdium aktivieren",
213 "settings.app.form.enableLock": "Passwort-Sperre aktivieren", 220 "settings.app.form.enableLock": "Passwort-Sperre aktivieren",
@@ -220,7 +227,9 @@
220 "settings.app.form.grayscaleServicesDim": "Graustufen Dimmwert", 227 "settings.app.form.grayscaleServicesDim": "Graustufen Dimmwert",
221 "settings.app.form.hibernateOnStartup": "Dienste beim Start in den Ruhezustand versetzen", 228 "settings.app.form.hibernateOnStartup": "Dienste beim Start in den Ruhezustand versetzen",
222 "settings.app.form.hibernationStrategy": "\"Service Hibernation\"-Strategie", 229 "settings.app.form.hibernationStrategy": "\"Service Hibernation\"-Strategie",
230 "settings.app.form.hideAllServicesWorkspace": "Arbeitsbereich „Alle Dienste“ ausblenden",
223 "settings.app.form.hideCollapseButton": "Schaltfläche „Ausklappen†ausblenden", 231 "settings.app.form.hideCollapseButton": "Schaltfläche „Ausklappen†ausblenden",
232 "settings.app.form.hideDownloadButton": "Schaltfläche „Downloads“ ausblenden",
224 "settings.app.form.hideNotificationsButton": "Schaltfläche „Benachrichtigungen und Töne†ausblenden", 233 "settings.app.form.hideNotificationsButton": "Schaltfläche „Benachrichtigungen und Töne†ausblenden",
225 "settings.app.form.hideRecipesButton": "Schaltfläche „Neuen Dienst hinzufügen†ausblenden", 234 "settings.app.form.hideRecipesButton": "Schaltfläche „Neuen Dienst hinzufügen†ausblenden",
226 "settings.app.form.hideSettingsButton": "Schaltfläche „Einstellungen†ausblenden", 235 "settings.app.form.hideSettingsButton": "Schaltfläche „Einstellungen†ausblenden",
@@ -278,10 +287,16 @@
278 "settings.app.hibernateInfo": "Standardmäßig öffnet Ferdium alle deine Dienste im Hintergrund, sodass diese bereit sind, wenn du sie verwenden möchtest. \"Service Hibernation\" wird deine Dienste nach einer bestimmten Zeitspanne schließen. Dies ist nützlich, um RAM zu sparen oder Dienste davon abzuhalten, deinen Computer zu verlangsamen.", 287 "settings.app.hibernateInfo": "Standardmäßig öffnet Ferdium alle deine Dienste im Hintergrund, sodass diese bereit sind, wenn du sie verwenden möchtest. \"Service Hibernation\" wird deine Dienste nach einer bestimmten Zeitspanne schließen. Dies ist nützlich, um RAM zu sparen oder Dienste davon abzuhalten, deinen Computer zu verlangsamen.",
279 "settings.app.inactivityLockInfo": "Minuten der Inaktivität, nach denen Ferdium automatisch sperren soll. Verwenden Sie 0, um dies zu deaktivieren", 288 "settings.app.inactivityLockInfo": "Minuten der Inaktivität, nach denen Ferdium automatisch sperren soll. Verwenden Sie 0, um dies zu deaktivieren",
280 "settings.app.infoOpenCertificatesFolder": "Um ein Zertifikat zu installieren, klicken Sie auf die Schaltfläche unten, um den Zertifikatsordner zu öffnen, und kopieren Sie es in den Ordner. Danach können Sie den Dienst aktualisieren (STRG/CMD + R). Zum Entfernen/Deinstallieren löschen Sie einfach die Zertifikatsdatei und starten Ferdium neu.", 289 "settings.app.infoOpenCertificatesFolder": "Um ein Zertifikat zu installieren, klicken Sie auf die Schaltfläche unten, um den Zertifikatsordner zu öffnen, und kopieren Sie es in den Ordner. Danach können Sie den Dienst aktualisieren (STRG/CMD + R). Zum Entfernen/Deinstallieren löschen Sie einfach die Zertifikatsdatei und starten Ferdium neu.",
290 "settings.app.lockInfo": "Die Passwortsperre ermöglicht es Ihnen, Ihre Nachrichten zu schützen.\nWenn Sie die Passwortsperre verwenden, werden Sie bei jedem Start von Ferdium aufgefordert, Ihr Passwort einzugeben, oder Sie sperren Ferdium selbst, indem Sie das Schlosssymbol in der unteren linken Ecke oder das Tastaturkürzel {lockShortcut} verwenden.",
281 "settings.app.lockedPassword": "Passwort", 291 "settings.app.lockedPassword": "Passwort",
282 "settings.app.lockedPasswordInfo": "Bitte stelle sicher, dass du ein Passwort setzt, an welches du dich erinnern kannst.\nSolltest du dieses Passwort vergessen, musst du Ferdium neu installieren.", 292 "settings.app.lockedPasswordInfo": "Bitte stelle sicher, dass du ein Passwort setzt, an welches du dich erinnern kannst.\nSolltest du dieses Passwort vergessen, musst du Ferdium neu installieren.",
283 "settings.app.overallTheme": "Allgemeines Farbschema", 293 "settings.app.overallTheme": "Allgemeines Farbschema",
284 "settings.app.progressbarTheme": "Farbschema der Fortschrittsanzeige", 294 "settings.app.progressbarTheme": "Farbschema der Fortschrittsanzeige",
295 "settings.app.restart.restartDialogDetail": "Sie haben eine Änderung vorgenommen, die einen Neustart erfordert. Dadurch wird Ferdium geschlossen und neu gestartet.",
296 "settings.app.restart.restartDialogMessage": "Möchten Sie Ferdium erneut starten?",
297 "settings.app.restart.restartDialogTitle": "Ferdium - Anwendung neu starten",
298 "settings.app.restart.restartLater": "Später neu starten",
299 "settings.app.restart.restartNow": "Jetzt neu starten",
285 "settings.app.restartRequired": "Änderungen werden erst nach einem Neustart wirksam", 300 "settings.app.restartRequired": "Änderungen werden erst nach einem Neustart wirksam",
286 "settings.app.scheduledDNDInfo": "Die geplante \"Nicht-stören\"-Funktion erlaubt es dir eine Zeitspanne festzulegen, in der du keine Benachrichtigungen von Ferdium erhalten möchtest.", 301 "settings.app.scheduledDNDInfo": "Die geplante \"Nicht-stören\"-Funktion erlaubt es dir eine Zeitspanne festzulegen, in der du keine Benachrichtigungen von Ferdium erhalten möchtest.",
287 "settings.app.scheduledDNDTimeInfo": "Zeiten im 24-Stunden-Format (z.B. 18:00). Endzeit kann vor Beginn der Startzeit sein (z.B. 17:00 Uhr, Ende 09:00), um die Funktion über Nacht zu aktivieren.", 302 "settings.app.scheduledDNDTimeInfo": "Zeiten im 24-Stunden-Format (z.B. 18:00). Endzeit kann vor Beginn der Startzeit sein (z.B. 17:00 Uhr, Ende 09:00), um die Funktion über Nacht zu aktivieren.",
@@ -298,8 +313,11 @@
298 "settings.app.sentryInfo": "Das Senden von Telemetriedaten ermöglicht es uns, Fehler in Ferdium zu finden - es werden keine persönlichen Informationen wie Ihre Nachrichtendaten gesendet!", 313 "settings.app.sentryInfo": "Das Senden von Telemetriedaten ermöglicht es uns, Fehler in Ferdium zu finden - es werden keine persönlichen Informationen wie Ihre Nachrichtendaten gesendet!",
299 "settings.app.serverHelp": "Verbunden mit dem Server unter {serverURL}", 314 "settings.app.serverHelp": "Verbunden mit dem Server unter {serverURL}",
300 "settings.app.servicesUpdateStatusUpToDate": "Ihre Dienste sind auf dem neuesten Stand", 315 "settings.app.servicesUpdateStatusUpToDate": "Ihre Dienste sind auf dem neuesten Stand",
316 "settings.app.spellCheckerLanguageInfo": "Ferdium verwendet die in Ihrem Mac eingebaute Rechtschreibprüfung, um auf Tippfehler zu prüfen. Wenn Sie die Sprachen, die die Rechtschreibprüfung prüft, ändern möchten, können Sie dies in den Systemeinstellungen Ihres Macs tun.",
301 "settings.app.subheadlineCache": "Cache", 317 "settings.app.subheadlineCache": "Cache",
318 "settings.app.subheadlineDownloads": "Downloads",
302 "settings.app.subheadlineFerdiumProfile": "Ferdium Profil", 319 "settings.app.subheadlineFerdiumProfile": "Ferdium Profil",
320 "settings.app.subheadlineUserAgent": "Browserkennung",
303 "settings.app.todoServerInfo": "Dieser Server wird für die \"Ferdium Todo\"-Funktion verwendet.", 321 "settings.app.todoServerInfo": "Dieser Server wird für die \"Ferdium Todo\"-Funktion verwendet.",
304 "settings.app.translationHelp": "Hilf uns, Ferdium in Deine Sprache zu übersetzen.", 322 "settings.app.translationHelp": "Hilf uns, Ferdium in Deine Sprache zu übersetzen.",
305 "settings.app.universalDarkModeInfo": "Universeller Dark Mode versucht dynamisch Dienste abzudunkeln, die vom normalen Dark Mode noch nicht unterstützt werden.", 323 "settings.app.universalDarkModeInfo": "Universeller Dark Mode versucht dynamisch Dienste abzudunkeln, die vom normalen Dark Mode noch nicht unterstützt werden.",
@@ -329,6 +347,8 @@
329 "settings.recipes.missingService": "Fehlt ein Dienst?", 347 "settings.recipes.missingService": "Fehlt ein Dienst?",
330 "settings.recipes.nothingFound": "Leider hat kein Dienst zu deinem Suchbegriff gepasst - aber du kannst ihn wahrscheinlich trotzdem über die Option \"Eigene Website\" hinzufügen. Bitte beachte, dass die Website möglicherweise weitere Dienste anzeigt, die seit deiner aktuellen Version zu Ferdium hinzugefügt wurden. Um diese neuen Dienste zu erhalten, erwäge bitte ein Upgrade auf eine neuere Version von Ferdium.", 348 "settings.recipes.nothingFound": "Leider hat kein Dienst zu deinem Suchbegriff gepasst - aber du kannst ihn wahrscheinlich trotzdem über die Option \"Eigene Website\" hinzufügen. Bitte beachte, dass die Website möglicherweise weitere Dienste anzeigt, die seit deiner aktuellen Version zu Ferdium hinzugefügt wurden. Um diese neuen Dienste zu erhalten, erwäge bitte ein Upgrade auf eine neuere Version von Ferdium.",
331 "settings.recipes.servicesSuccessfulAddedInfo": "Dienst erfolgreich hinzugefügt", 349 "settings.recipes.servicesSuccessfulAddedInfo": "Dienst erfolgreich hinzugefügt",
350 "settings.releasenotes.connectionError": "Bei der Verbindung zu Github ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.",
351 "settings.releasenotes.connectionErrorPageMissing": "Bei der Verbindung zu Github ist ein Fehler aufgetreten, die gesuchte Seite ist nicht vorhanden.",
332 "settings.releasenotes.headline": "Neuigkeiten", 352 "settings.releasenotes.headline": "Neuigkeiten",
333 "settings.searchService": "Dienst suchen", 353 "settings.searchService": "Dienst suchen",
334 "settings.service.error.goBack": "Zurück zu den Diensten", 354 "settings.service.error.goBack": "Zurück zu den Diensten",
@@ -385,6 +405,7 @@
385 "settings.service.form.tabOnPremise": "Selbst gehostet â­ï¸", 405 "settings.service.form.tabOnPremise": "Selbst gehostet â­ï¸",
386 "settings.service.form.team": "Team", 406 "settings.service.form.team": "Team",
387 "settings.service.form.trapLinkClicks": "URLs in Ferdium öffnen", 407 "settings.service.form.trapLinkClicks": "URLs in Ferdium öffnen",
408 "settings.service.form.useFavicon": "Favicon des Dienstes anstelle des Standard- oder benutzerdefinierten Symbols verwenden",
388 "settings.service.form.useHostedService": "Hosted {name} verwenden.", 409 "settings.service.form.useHostedService": "Hosted {name} verwenden.",
389 "settings.service.form.yourServices": "Deine Dienste", 410 "settings.service.form.yourServices": "Deine Dienste",
390 "settings.service.reloadRequired": "Änderungen werden erst nach einem Neuladen des Dienstes wirksam", 411 "settings.service.reloadRequired": "Änderungen werden erst nach einem Neuladen des Dienstes wirksam",
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 1941ecd86..cb0f4369c 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -164,6 +164,8 @@
164 "service.errorHandler.headline": "Oh no!", 164 "service.errorHandler.headline": "Oh no!",
165 "service.errorHandler.message": "Error", 165 "service.errorHandler.message": "Error",
166 "service.errorHandler.text": "{name} has failed to load.", 166 "service.errorHandler.text": "{name} has failed to load.",
167 "service.screenshare.cancel": "Cancel",
168 "service.screenshare.loading": "Loading screens and windows",
167 "service.webviewLoader.loading": "Loading {service}", 169 "service.webviewLoader.loading": "Loading {service}",
168 "services.getStarted": "Get started", 170 "services.getStarted": "Get started",
169 "services.login": "Please login to use Ferdium.", 171 "services.login": "Please login to use Ferdium.",
@@ -192,6 +194,7 @@
192 "settings.app.buttonOpenFerdiumCertsFolder": "Open certificates folder", 194 "settings.app.buttonOpenFerdiumCertsFolder": "Open certificates folder",
193 "settings.app.buttonOpenFerdiumProfileFolder": "Open Profile folder", 195 "settings.app.buttonOpenFerdiumProfileFolder": "Open Profile folder",
194 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Open Service Recipes folder", 196 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Open Service Recipes folder",
197 "settings.app.buttonOpenFolderSelector": "Open folder selector",
195 "settings.app.buttonOpenImportExport": "Import / Export", 198 "settings.app.buttonOpenImportExport": "Import / Export",
196 "settings.app.buttonSearchForUpdate": "Check for updates", 199 "settings.app.buttonSearchForUpdate": "Check for updates",
197 "settings.app.buttonShowChangelog": "Show changelog", 200 "settings.app.buttonShowChangelog": "Show changelog",
@@ -211,6 +214,7 @@
211 "settings.app.form.confirmOnQuit": "Confirm when quitting Ferdium", 214 "settings.app.form.confirmOnQuit": "Confirm when quitting Ferdium",
212 "settings.app.form.customTodoServer": "Custom Todo Server", 215 "settings.app.form.customTodoServer": "Custom Todo Server",
213 "settings.app.form.darkMode": "Enable Dark Mode", 216 "settings.app.form.darkMode": "Enable Dark Mode",
217 "settings.app.form.downloadFolderPath": "Default download folder (leave blank to be prompted for each download)",
214 "settings.app.form.enableGPUAcceleration": "Enable GPU Acceleration", 218 "settings.app.form.enableGPUAcceleration": "Enable GPU Acceleration",
215 "settings.app.form.enableGlobalHideShortcut": "Enable Global shortcut to hide Ferdium", 219 "settings.app.form.enableGlobalHideShortcut": "Enable Global shortcut to hide Ferdium",
216 "settings.app.form.enableLock": "Enable Password Lock", 220 "settings.app.form.enableLock": "Enable Password Lock",
@@ -288,6 +292,11 @@
288 "settings.app.lockedPasswordInfo": "Please make sure to set a password you'll remember.\nIf you loose this password, you will have to reinstall Ferdium.", 292 "settings.app.lockedPasswordInfo": "Please make sure to set a password you'll remember.\nIf you loose this password, you will have to reinstall Ferdium.",
289 "settings.app.overallTheme": "Overall Theme", 293 "settings.app.overallTheme": "Overall Theme",
290 "settings.app.progressbarTheme": "Progressbar Theme", 294 "settings.app.progressbarTheme": "Progressbar Theme",
295 "settings.app.restart.restartDialogDetail": "You made a change that requires a restart. This will close Ferdium and restart it.",
296 "settings.app.restart.restartDialogMessage": "Do you want to relaunch Ferdium?",
297 "settings.app.restart.restartDialogTitle": "Ferdium - Relaunch Application",
298 "settings.app.restart.restartLater": "Restart later",
299 "settings.app.restart.restartNow": "Restart now",
291 "settings.app.restartRequired": "Changes require restart", 300 "settings.app.restartRequired": "Changes require restart",
292 "settings.app.scheduledDNDInfo": "Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdium.", 301 "settings.app.scheduledDNDInfo": "Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdium.",
293 "settings.app.scheduledDNDTimeInfo": "Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.", 302 "settings.app.scheduledDNDTimeInfo": "Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.",
@@ -306,7 +315,9 @@
306 "settings.app.servicesUpdateStatusUpToDate": "Your services are up-to-date", 315 "settings.app.servicesUpdateStatusUpToDate": "Your services are up-to-date",
307 "settings.app.spellCheckerLanguageInfo": "Ferdium uses your Mac's built-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.", 316 "settings.app.spellCheckerLanguageInfo": "Ferdium uses your Mac's built-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.",
308 "settings.app.subheadlineCache": "Cache", 317 "settings.app.subheadlineCache": "Cache",
318 "settings.app.subheadlineDownloads": "Downloads",
309 "settings.app.subheadlineFerdiumProfile": "Ferdium Profile", 319 "settings.app.subheadlineFerdiumProfile": "Ferdium Profile",
320 "settings.app.subheadlineUserAgent": "User Agent",
310 "settings.app.todoServerInfo": "This server will be used for the \"Ferdium Todo\" feature.", 321 "settings.app.todoServerInfo": "This server will be used for the \"Ferdium Todo\" feature.",
311 "settings.app.translationHelp": "Help us to translate Ferdium into your language.", 322 "settings.app.translationHelp": "Help us to translate Ferdium into your language.",
312 "settings.app.universalDarkModeInfo": "Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.", 323 "settings.app.universalDarkModeInfo": "Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.",
@@ -394,6 +405,7 @@
394 "settings.service.form.tabOnPremise": "Self hosted â­ï¸", 405 "settings.service.form.tabOnPremise": "Self hosted â­ï¸",
395 "settings.service.form.team": "Team", 406 "settings.service.form.team": "Team",
396 "settings.service.form.trapLinkClicks": "Open URLs within Ferdium", 407 "settings.service.form.trapLinkClicks": "Open URLs within Ferdium",
408 "settings.service.form.useFavicon": "Use service favicon instead of default or custom icon",
397 "settings.service.form.useHostedService": "Use the hosted {name} service.", 409 "settings.service.form.useHostedService": "Use the hosted {name} service.",
398 "settings.service.form.yourServices": "Your services", 410 "settings.service.form.yourServices": "Your services",
399 "settings.service.reloadRequired": "Changes require reload of the service", 411 "settings.service.reloadRequired": "Changes require reload of the service",
diff --git a/src/i18n/locales/he.json b/src/i18n/locales/he.json
index 2247db0b0..0712a15d4 100644
--- a/src/i18n/locales/he.json
+++ b/src/i18n/locales/he.json
@@ -16,6 +16,7 @@
16 "feature.publishDebugInfo.info": "×¤×¨×¡×•× ×¤×™×¨×•×˜ השגי×ות שלך מסייע לנו ל×תר תקלות ושגי×ות ב־Ferdium. ×¤×¨×¡×•× ×¤×™×¨×•×˜ השגי×ות שלך מהווה ×ת הסכמתך למדיניות הפרטיות לניפוי שגי×ות ותנ××™ השירות של Ferdium", 16 "feature.publishDebugInfo.info": "×¤×¨×¡×•× ×¤×™×¨×•×˜ השגי×ות שלך מסייע לנו ל×תר תקלות ושגי×ות ב־Ferdium. ×¤×¨×¡×•× ×¤×™×¨×•×˜ השגי×ות שלך מהווה ×ת הסכמתך למדיניות הפרטיות לניפוי שגי×ות ותנ××™ השירות של Ferdium",
17 "feature.publishDebugInfo.privacy": "מדיניות פרטיות", 17 "feature.publishDebugInfo.privacy": "מדיניות פרטיות",
18 "feature.publishDebugInfo.publish": "מוסכ×, לפרס×", 18 "feature.publishDebugInfo.publish": "מוסכ×, לפרס×",
19 "feature.publishDebugInfo.published": "יומן ניפוי השגי×ות שלך ×¤×•×¨×¡× ×•×”×•× ×–×ž×™×Ÿ כעת דרך",
19 "feature.publishDebugInfo.terms": "תנ××™ שירות", 20 "feature.publishDebugInfo.terms": "תנ××™ שירות",
20 "feature.publishDebugInfo.title": "×¤×¨×¡×•× ×¤×¨×˜×™ ניפוי שגי×ות", 21 "feature.publishDebugInfo.title": "×¤×¨×¡×•× ×¤×¨×˜×™ ניפוי שגי×ות",
21 "feature.quickSwitch.info": "בחירת ×©×™×¨×•×ª×™× ×ž×ª×‘×¦×¢×ª ×¢× TAB, ↑ ו־↓. פתיחת ×©×™×¨×•×ª×™× ×¢× ENTER.", 22 "feature.quickSwitch.info": "בחירת ×©×™×¨×•×ª×™× ×ž×ª×‘×¦×¢×ª ×¢× TAB, ↑ ו־↓. פתיחת ×©×™×¨×•×ª×™× ×¢× ENTER.",
@@ -187,6 +188,7 @@
187 "settings.account.yourLicense": "רישיון ה־Ferdium שלך:", 188 "settings.account.yourLicense": "רישיון ה־Ferdium שלך:",
188 "settings.app.accentColorInfo": "יש לציין ×ת הצבע שלך ב×ופן שתו×× ×œÖ¾CSS (ברירת המחדל: {defaultAccentColor} ×ו להש×יר ×ת שדה הקלט פנוי)", 189 "settings.app.accentColorInfo": "יש לציין ×ת הצבע שלך ב×ופן שתו×× ×œÖ¾CSS (ברירת המחדל: {defaultAccentColor} ×ו להש×יר ×ת שדה הקלט פנוי)",
189 "settings.app.buttonInstallUpdate": "להפעיל מחדש ולהתקין עדכוני×", 190 "settings.app.buttonInstallUpdate": "להפעיל מחדש ולהתקין עדכוני×",
191 "settings.app.buttonOpenFerdiumCertsFolder": "פתיחת תיקיית ×”×ישורי×",
190 "settings.app.buttonOpenFerdiumProfileFolder": "פתיחת תיקיית הפרופיל", 192 "settings.app.buttonOpenFerdiumProfileFolder": "פתיחת תיקיית הפרופיל",
191 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "פתיחת תיקיית מתכוני השירותי×", 193 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "פתיחת תיקיית מתכוני השירותי×",
192 "settings.app.buttonOpenImportExport": "×™×™×‘×•× / ייצו×", 194 "settings.app.buttonOpenImportExport": "×™×™×‘×•× / ייצו×",
@@ -229,6 +231,7 @@
229 "settings.app.form.hideWorkspacesButton": "הסתרת כפתור סביבות עבודה", 231 "settings.app.form.hideWorkspacesButton": "הסתרת כפתור סביבות עבודה",
230 "settings.app.form.iconSize": "גודל סמל שירות", 232 "settings.app.form.iconSize": "גודל סמל שירות",
231 "settings.app.form.inactivityLock": "נעילה ל×חר ××™ פעילות", 233 "settings.app.form.inactivityLock": "נעילה ל×חר ××™ פעילות",
234 "settings.app.form.isTwoFactorAutoCatcherEnabled": "ללכוד ×§×•×“×™× ×œ×ימות דו־שלבי מההתר×ות ×וטומטית (למשל: הודעות ב־Android) ולהעתיק ללוח הגזירי×",
232 "settings.app.form.keepAllWorkspacesLoaded": "לשמור על כל סביבות העבודה פעילות ברקע", 235 "settings.app.form.keepAllWorkspacesLoaded": "לשמור על כל סביבות העבודה פעילות ברקע",
233 "settings.app.form.language": "שפה", 236 "settings.app.form.language": "שפה",
234 "settings.app.form.lockPassword": "סיסמה", 237 "settings.app.form.lockPassword": "סיסמה",
@@ -258,9 +261,11 @@
258 "settings.app.form.startMinimized": "להתחיל ממוזער", 261 "settings.app.form.startMinimized": "להתחיל ממוזער",
259 "settings.app.form.translatorEngine": "מנוע תרגו×", 262 "settings.app.form.translatorEngine": "מנוע תרגו×",
260 "settings.app.form.translatorLanguage": "שפת ×”×ž×ª×¨×’× ×›×‘×¨×™×¨×ª מחדל", 263 "settings.app.form.translatorLanguage": "שפת ×”×ž×ª×¨×’× ×›×‘×¨×™×¨×ª מחדל",
264 "settings.app.form.twoFactorAutoCatcherMatcher": "רשימה מופרדת ×‘×¤×¡×™×§×™× ×©×œ מילי×/×‘×™×˜×•×™×™× ×ª×œ×•×™×™ רישיות (מבדיל בין ×ותיות קטנות לגדולות ב×נגלית) ללכידת ×§×•×“×™× ×œ×ימות דו־שלבי. למשל: token,†code,†sms,†verify, ×ימות, קוד, חד־פעמי",
261 "settings.app.form.universalDarkMode": "הפעלת מצב לילה כולל", 265 "settings.app.form.universalDarkMode": "הפעלת מצב לילה כולל",
262 "settings.app.form.useGrayscaleServices": "להשתמש בשירותי גווני ×פור", 266 "settings.app.form.useGrayscaleServices": "להשתמש בשירותי גווני ×פור",
263 "settings.app.form.useHorizontalStyle": "להשתמש בסגנון ×ופקי", 267 "settings.app.form.useHorizontalStyle": "להשתמש בסגנון ×ופקי",
268 "settings.app.form.useSelfSignedCertificates": "הפעלת ××™×©×•×¨×™× ×—×ª×•×ž×™× ×¢×¦×ž×ית",
264 "settings.app.form.useTouchIdToUnlock": "ל×פשר שימוש ב־TouchID כדי לשחרר ×ת Ferdium", 269 "settings.app.form.useTouchIdToUnlock": "ל×פשר שימוש ב־TouchID כדי לשחרר ×ת Ferdium",
265 "settings.app.form.wakeUpHibernationSplay": "להרחיב ×ת מחזורי הרדמה/השכמה כדי להפחית בעומס", 270 "settings.app.form.wakeUpHibernationSplay": "להרחיב ×ת מחזורי הרדמה/השכמה כדי להפחית בעומס",
266 "settings.app.form.wakeUpHibernationStrategy": "×סטרטגיית הרדמה ל×חר השכמה ×וטומטית", 271 "settings.app.form.wakeUpHibernationStrategy": "×סטרטגיית הרדמה ל×חר השכמה ×וטומטית",
@@ -275,6 +280,8 @@
275 "settings.app.headlineUpdates": "עדכוני×", 280 "settings.app.headlineUpdates": "עדכוני×",
276 "settings.app.hibernateInfo": "כברירת מחדל, ×”×©×™×¨×•×ª×™× ×©×œ×š ייש×רו ×¤×ª×•×—×™× ×•×˜×¢×•× ×™× ×‘×¨×§×¢ על ידי Ferdium כדי שיהיו ×ž×•×›× ×™× ×œ×©×™×ž×•×©. הרדמת ×©×™×¨×•×ª×™× ×ª×¤×¨×•×§ ×ת ×”×©×™×¨×•×ª×™× ×©×œ×š ל×חר זמן מסוי×. עשוי לחסוך בזיכרון ×ו למנוע ×ž×©×™×¨×•×ª×™× ×œ×”×ט ×ת המחשב שלך.", 281 "settings.app.hibernateInfo": "כברירת מחדל, ×”×©×™×¨×•×ª×™× ×©×œ×š ייש×רו ×¤×ª×•×—×™× ×•×˜×¢×•× ×™× ×‘×¨×§×¢ על ידי Ferdium כדי שיהיו ×ž×•×›× ×™× ×œ×©×™×ž×•×©. הרדמת ×©×™×¨×•×ª×™× ×ª×¤×¨×•×§ ×ת ×”×©×™×¨×•×ª×™× ×©×œ×š ל×חר זמן מסוי×. עשוי לחסוך בזיכרון ×ו למנוע ×ž×©×™×¨×•×ª×™× ×œ×”×ט ×ת המחשב שלך.",
277 "settings.app.inactivityLockInfo": "דקות חוסר פעילות של×חריהן Ferdium ×מור להינעל ×וטומטית. 0 כדי להשבית", 282 "settings.app.inactivityLockInfo": "דקות חוסר פעילות של×חריהן Ferdium ×מור להינעל ×וטומטית. 0 כדי להשבית",
283 "settings.app.infoOpenCertificatesFolder": "כדי להתקין ×ישור, יש ללחוץ על הכפתור שלהלן כדי לפתוח ×ת תיקיית ×”××™×©×•×¨×™× ×•×œ×”×¢×ª×™×§ ×ותו לתיקייה. ל×חר מכן ×פשר לרענן ×ת השירות (CTRL/CMD + R). כדי להסיר (לחלוטין), ×פשר פשוט למחוק ×ת קובץ ×”×ישור ולהפעיל ×ת Ferdium מחדש.",
284 "settings.app.lockInfo": "נעילה בסיסמה מ×פשרת לך להגן על ההתכתבויות שלך.\nהשימוש בנעילה בסיסמה ×™×לץ ×ותך להקליד ×ת הסיסמה שלך ×¢× ×›×œ הפעלה ×ו נעילה של Ferdium ב×מצעות סמל המנעול בפינה הימנית התחתונה ×ו בעזרת קיצור המקלדת {lockShortcut}.",
278 "settings.app.lockedPassword": "סיסמה", 285 "settings.app.lockedPassword": "סיסמה",
279 "settings.app.lockedPasswordInfo": "× × ×œ×•×•×“× ×©×”×’×“×¨×ª סיסמה שקל לך לזכור.\n×יבוד הסיסמה ×”×–×ת ×™×לץ ×ותך להתקין ×ת Ferdium מחדש.", 286 "settings.app.lockedPasswordInfo": "× × ×œ×•×•×“× ×©×”×’×“×¨×ª סיסמה שקל לך לזכור.\n×יבוד הסיסמה ×”×–×ת ×™×לץ ×ותך להתקין ×ת Ferdium מחדש.",
280 "settings.app.overallTheme": "ערכת עיצוב כללית", 287 "settings.app.overallTheme": "ערכת עיצוב כללית",
@@ -294,6 +301,8 @@
294 "settings.app.sectionUpdates": "הגדרות עדכוני יישו×", 301 "settings.app.sectionUpdates": "הגדרות עדכוני יישו×",
295 "settings.app.sentryInfo": "שליחת × ×ª×•× ×™× ×˜×œ×ž×˜×¨×™×™× ×ž×פשרות לנו ×œ×ž×¦×•× ×©×’×™×ות ב־Ferdium - ×נו ×œ× × ×©×œ×— מידע פרטי כגון תוכן ההודעות שלך!", 302 "settings.app.sentryInfo": "שליחת × ×ª×•× ×™× ×˜×œ×ž×˜×¨×™×™× ×ž×פשרות לנו ×œ×ž×¦×•× ×©×’×™×ות ב־Ferdium - ×נו ×œ× × ×©×œ×— מידע פרטי כגון תוכן ההודעות שלך!",
296 "settings.app.serverHelp": "יש חיבור לשרת ב־{serverURL}", 303 "settings.app.serverHelp": "יש חיבור לשרת ב־{serverURL}",
304 "settings.app.servicesUpdateStatusUpToDate": "×”×©×™×¨×•×ª×™× ×©×œ×š עדכניי×",
305 "settings.app.spellCheckerLanguageInfo": "ב־Ferdium נעשה שימוש בבודק ×”×יות המובנה של Mac כדי לבדוק שגי×ות הקלדה. כדי לשנות ×ת שפות הבדיקה של בודק ×”×יות ניתן לעשות ×–×ת בהגדרות המערכת של ×”Ö¾Mac.",
297 "settings.app.subheadlineCache": "זיכרון מטמון", 306 "settings.app.subheadlineCache": "זיכרון מטמון",
298 "settings.app.subheadlineFerdiumProfile": "פרופיל Ferdium", 307 "settings.app.subheadlineFerdiumProfile": "פרופיל Ferdium",
299 "settings.app.todoServerInfo": "השרת ×”×–×” ישמש לספק ×ת היכולת „משימות Ferdiumâ€.", 308 "settings.app.todoServerInfo": "השרת ×”×–×” ישמש לספק ×ת היכולת „משימות Ferdiumâ€.",
@@ -302,6 +311,7 @@
302 "settings.app.updateStatusAvailable": "יש עדכון, מתבצעת הורדה…", 311 "settings.app.updateStatusAvailable": "יש עדכון, מתבצעת הורדה…",
303 "settings.app.updateStatusSearching": "מתבצע חיפוש ×חר עדכוני×…", 312 "settings.app.updateStatusSearching": "מתבצע חיפוש ×חר עדכוני×…",
304 "settings.app.updateStatusUpToDate": "זו הגרסה העדכנית ביותר של Ferdium", 313 "settings.app.updateStatusUpToDate": "זו הגרסה העדכנית ביותר של Ferdium",
314 "settings.app.warningSelfSignedCertificates": "×זהרה: יש להפעיל ×ת היכולת ×”×–×ת רק ×× ×‘×¨×•×¨ לך לחלוטין מה ×”×™× ×¢×•×©×”. הפעלתה מהווה סיכון ×בטחת מידע ×•×”×™× ×ž×™×•×¢×“×ª למטרות בדיקה בלבד.",
305 "settings.invite.headline": "להזמין חברי×", 315 "settings.invite.headline": "להזמין חברי×",
306 "settings.navigation.account": "חשבון", 316 "settings.navigation.account": "חשבון",
307 "settings.navigation.availableServices": "×©×™×¨×•×ª×™× ×–×ž×™× ×™×", 317 "settings.navigation.availableServices": "×©×™×¨×•×ª×™× ×–×ž×™× ×™×",
@@ -324,6 +334,8 @@
324 "settings.recipes.missingService": "חסר שירות?", 334 "settings.recipes.missingService": "חסר שירות?",
325 "settings.recipes.nothingFound": "××£ שירות ×ינו עונה על ביטוי החיפוש שלך, מחילה, ×¢× ×–×ת, עדיין ניתן להוסיף ×ותו ב×מצעות ×”×פשרות „×תר משלךâ€. × × ×œ×©×™× ×œ×‘ שה×תר עשוי להציג יותר ×©×™×¨×•×ª×™× ×ž×לו ×©×ž×•×¤×™×¢×™× ×‘Ö¾Ferdium עקב הגרסה שמותקנת ×צלך. כדי לקבל ×ת ×”×©×™×¨×•×ª×™× ×”×—×“×©×™× ×”×לו, כד××™ לשקול לשדרג לגרסה חדשה יותר של Ferdium.", 335 "settings.recipes.nothingFound": "××£ שירות ×ינו עונה על ביטוי החיפוש שלך, מחילה, ×¢× ×–×ת, עדיין ניתן להוסיף ×ותו ב×מצעות ×”×פשרות „×תר משלךâ€. × × ×œ×©×™× ×œ×‘ שה×תר עשוי להציג יותר ×©×™×¨×•×ª×™× ×ž×לו ×©×ž×•×¤×™×¢×™× ×‘Ö¾Ferdium עקב הגרסה שמותקנת ×צלך. כדי לקבל ×ת ×”×©×™×¨×•×ª×™× ×”×—×“×©×™× ×”×לו, כד××™ לשקול לשדרג לגרסה חדשה יותר של Ferdium.",
326 "settings.recipes.servicesSuccessfulAddedInfo": "השירות נוסף בהצלחה", 336 "settings.recipes.servicesSuccessfulAddedInfo": "השירות נוסף בהצלחה",
337 "settings.releasenotes.connectionError": "×ירעה שגי××” בעת ניסיון ההתחברות ל־GitHub. × × ×œ× ×¡×•×ª שוב מ×וחר יותר.",
338 "settings.releasenotes.connectionErrorPageMissing": "×ירעה שגי××” בעת ניסיון ההתחברות ל־GitHub. העמוד המבוקש חסר.",
327 "settings.releasenotes.headline": "הערות מוצר", 339 "settings.releasenotes.headline": "הערות מוצר",
328 "settings.searchService": "חיפוש שירות", 340 "settings.searchService": "חיפוש שירות",
329 "settings.service.error.goBack": "חזרה לשירותי×", 341 "settings.service.error.goBack": "חזרה לשירותי×",
diff --git a/src/i18n/locales/lv.json b/src/i18n/locales/lv.json
index 9713bbb0f..9c041b4f8 100644
--- a/src/i18n/locales/lv.json
+++ b/src/i18n/locales/lv.json
@@ -16,6 +16,7 @@
16 "feature.publishDebugInfo.info": "DalÄ«Å¡anÄs ar atkļūdoÅ¡anas informÄciju palÄ«dz mums atrast Ferdium nepilnÄ«bas un kļūmes. Daloties ar atkļūdoÅ¡anas informÄciju tiek pieņemti Ferdium atkļūdotÄja privÄtuma nosacÄ«jumi un pakalpojuma noteikumi", 16 "feature.publishDebugInfo.info": "DalÄ«Å¡anÄs ar atkļūdoÅ¡anas informÄciju palÄ«dz mums atrast Ferdium nepilnÄ«bas un kļūmes. Daloties ar atkļūdoÅ¡anas informÄciju tiek pieņemti Ferdium atkļūdotÄja privÄtuma nosacÄ«jumi un pakalpojuma noteikumi",
17 "feature.publishDebugInfo.privacy": "PrivÄtuma nosacÄ«jumi", 17 "feature.publishDebugInfo.privacy": "PrivÄtuma nosacÄ«jumi",
18 "feature.publishDebugInfo.publish": "ApstiprinÄt un publicÄ“t", 18 "feature.publishDebugInfo.publish": "ApstiprinÄt un publicÄ“t",
19 "feature.publishDebugInfo.published": "AtkļūdoÅ¡anas žurnÄls tika padarÄ«ts pieejams un tagad ir atrodams",
19 "feature.publishDebugInfo.terms": "Pakalpojuma izmantošanas noteikumi", 20 "feature.publishDebugInfo.terms": "Pakalpojuma izmantošanas noteikumi",
20 "feature.publishDebugInfo.title": "DalÄ«ties ar atkļūdoÅ¡anas informÄciju", 21 "feature.publishDebugInfo.title": "DalÄ«ties ar atkļūdoÅ¡anas informÄciju",
21 "feature.quickSwitch.info": "Pakalpojumu var izvēlēties ar TAB, ↑ un ↓. Pakalpojumu var atvērt ar ENTER.", 22 "feature.quickSwitch.info": "Pakalpojumu var izvēlēties ar TAB, ↑ un ↓. Pakalpojumu var atvērt ar ENTER.",
@@ -62,10 +63,10 @@
62 "locked.submit.label": "AtbloÄ·Ä“t", 63 "locked.submit.label": "AtbloÄ·Ä“t",
63 "locked.touchId": "AtbloÄ·Ä“t ar Touch ID", 64 "locked.touchId": "AtbloÄ·Ä“t ar Touch ID",
64 "locked.unlockWithPassword": "AtbloÄ·Ä“t ar Paroli", 65 "locked.unlockWithPassword": "AtbloÄ·Ä“t ar Paroli",
65 "login.changeServer": "Maini šeit!", 66 "login.changeServer": "Nomainīt",
66 "login.changeServerMessage": "Tu izmanto {serverNameParse} serveri. Vai vÄlies to mainÄ«t?", 67 "login.changeServerMessage": "Tiek izmantots {serverNameParse} serveris. Vai pÄrslÄ“gties uz citu?",
67 "login.customServerQuestion": "Izmanto pielÄgotu Ferdium serveri?", 68 "login.customServerQuestion": "Izmanto pielÄgotu Ferdium serveri?",
68 "login.customServerSuggestion": "MÄ“Ä£ini ievietot savu Franz kontu!", 69 "login.customServerSuggestion": "MÄ“Ä£ini ievietot savu Franz kontu",
69 "login.email.label": "E-pasta adrese", 70 "login.email.label": "E-pasta adrese",
70 "login.headline": "Pieteikties", 71 "login.headline": "Pieteikties",
71 "login.invalidCredentials": "E-pasta adrese vai parole ir nederīga", 72 "login.invalidCredentials": "E-pasta adrese vai parole ir nederīga",
@@ -123,6 +124,7 @@
123 "menu.view.back": "Atpakaļ", 124 "menu.view.back": "Atpakaļ",
124 "menu.view.forward": "Uz priekšu", 125 "menu.view.forward": "Uz priekšu",
125 "menu.view.lockFerdium": "Aizslēgt Ferdium", 126 "menu.view.lockFerdium": "Aizslēgt Ferdium",
127 "menu.view.openProcessManager": "AtvÄ“rt procesu pÄrvaldnieku",
126 "menu.view.openQuickSwitch": "AtvÄ“rt Ätro pÄrslÄ“gÅ¡anos", 128 "menu.view.openQuickSwitch": "AtvÄ“rt Ätro pÄrslÄ“gÅ¡anos",
127 "menu.view.reloadFerdium": "PÄrlÄdÄ“t Ferdium", 129 "menu.view.reloadFerdium": "PÄrlÄdÄ“t Ferdium",
128 "menu.view.reloadService": "PÄrlÄdÄ“t Pakalpojumu", 130 "menu.view.reloadService": "PÄrlÄdÄ“t Pakalpojumu",
@@ -166,7 +168,7 @@
166 "services.getStarted": "SÄksim", 168 "services.getStarted": "SÄksim",
167 "services.login": "LÅ«gums pieteikties, lai izmantotu Ferdium.", 169 "services.login": "LÅ«gums pieteikties, lai izmantotu Ferdium.",
168 "services.serverInfo": "PÄ“c izvÄ“les ir iespÄ“jams mainÄ«t Ferdium serveri, klikÅ¡Ä·inot uz zobrata apakÅ¡Ä“jÄ kreisajÄ stÅ«rÄ«. Ja pÄrej (no kÄda no izvietotajiem serveriem) uz Ferdium bez konta, lÅ«gums ņemt vÄ“rÄ, ka ir iespÄ“jams izgÅ«t savus datus no tÄ servera un pÄ“c tam tos ievietot paÅ¡reizÄ“jÄ, izmantojot palÄ«dzÄ«bas izvÄ“lni, lai atjaunotu visas savas darbvietas un pievienotos pakalpojumus.", 170 "services.serverInfo": "PÄ“c izvÄ“les ir iespÄ“jams mainÄ«t Ferdium serveri, klikÅ¡Ä·inot uz zobrata apakÅ¡Ä“jÄ kreisajÄ stÅ«rÄ«. Ja pÄrej (no kÄda no izvietotajiem serveriem) uz Ferdium bez konta, lÅ«gums ņemt vÄ“rÄ, ka ir iespÄ“jams izgÅ«t savus datus no tÄ servera un pÄ“c tam tos ievietot paÅ¡reizÄ“jÄ, izmantojot palÄ«dzÄ«bas izvÄ“lni, lai atjaunotu visas savas darbvietas un pievienotos pakalpojumus.",
169 "services.serverless": "Izmantot Ferdium bez Konta", 171 "services.serverless": "Izmantot Ferdium bez konta",
170 "settings.account.account.editButton": "Labot kontu", 172 "settings.account.account.editButton": "Labot kontu",
171 "settings.account.accountUnavailable": "Konts nav pieejams", 173 "settings.account.accountUnavailable": "Konts nav pieejams",
172 "settings.account.accountUnavailableInfo": "Ferdium tiek izmantots bez konta. Ja ir vÄ“lÄ“Å¡anÄs izmantot Ferdium ar kontu un sinhronizÄ“t savus pakalpojumus dažÄdÄs iekÄrtÄs, lÅ«gums atlasÄ«t serveri iestatÄ«jumu cilnÄ“ un tad pieteikties.", 174 "settings.account.accountUnavailableInfo": "Ferdium tiek izmantots bez konta. Ja ir vÄ“lÄ“Å¡anÄs izmantot Ferdium ar kontu un sinhronizÄ“t savus pakalpojumus dažÄdÄs iekÄrtÄs, lÅ«gums atlasÄ«t serveri iestatÄ«jumu cilnÄ“ un tad pieteikties.",
@@ -187,6 +189,7 @@
187 "settings.account.yourLicense": "Tava Ferdium licence:", 189 "settings.account.yourLicense": "Tava Ferdium licence:",
188 "settings.app.accentColorInfo": "Sava krÄsas izvÄ“le ir jÄieraksta CSS saderÄ«gÄ veidÄ. (NoklusÄ“jums: {defaultAccentColor} vai notÄ«rÄ«ts ievades lauks)", 190 "settings.app.accentColorInfo": "Sava krÄsas izvÄ“le ir jÄieraksta CSS saderÄ«gÄ veidÄ. (NoklusÄ“jums: {defaultAccentColor} vai notÄ«rÄ«ts ievades lauks)",
189 "settings.app.buttonInstallUpdate": "PÄrsÄknÄ“t un uzstÄdÄ«t atjauninÄjumu", 191 "settings.app.buttonInstallUpdate": "PÄrsÄknÄ“t un uzstÄdÄ«t atjauninÄjumu",
192 "settings.app.buttonOpenFerdiumCertsFolder": "AtvÄ“rt sertifikÄtu mapi",
190 "settings.app.buttonOpenFerdiumProfileFolder": "ParÄdÄ«t profila mapi", 193 "settings.app.buttonOpenFerdiumProfileFolder": "ParÄdÄ«t profila mapi",
191 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Atvērt Pakalpojumu Recepšu mapi", 194 "settings.app.buttonOpenFerdiumServiceRecipesFolder": "Atvērt Pakalpojumu Recepšu mapi",
192 "settings.app.buttonOpenImportExport": "Ievietot / Izgūt", 195 "settings.app.buttonOpenImportExport": "Ievietot / Izgūt",
@@ -220,6 +223,7 @@
220 "settings.app.form.grayscaleServicesDim": "Pelēktoņu aptumšošanas līmenis", 223 "settings.app.form.grayscaleServicesDim": "Pelēktoņu aptumšošanas līmenis",
221 "settings.app.form.hibernateOnStartup": "PaturÄ“t pakalpojumus hibernÄcijas režīmÄ, startÄ“jot", 224 "settings.app.form.hibernateOnStartup": "PaturÄ“t pakalpojumus hibernÄcijas režīmÄ, startÄ“jot",
222 "settings.app.form.hibernationStrategy": "HibernÄcijas stratÄ“Ä£ija", 225 "settings.app.form.hibernationStrategy": "HibernÄcijas stratÄ“Ä£ija",
226 "settings.app.form.hideAllServicesWorkspace": "Paslēpt \"Visi pakalpojumi\" darbvietu",
223 "settings.app.form.hideCollapseButton": "Paslēpt sakļaušanas pogu", 227 "settings.app.form.hideCollapseButton": "Paslēpt sakļaušanas pogu",
224 "settings.app.form.hideDownloadButton": "Paslēpt lejupielĞu pogu", 228 "settings.app.form.hideDownloadButton": "Paslēpt lejupielĞu pogu",
225 "settings.app.form.hideNotificationsButton": "Paslēpt paziņojumu un skaņu pogu", 229 "settings.app.form.hideNotificationsButton": "Paslēpt paziņojumu un skaņu pogu",
@@ -261,6 +265,7 @@
261 "settings.app.form.universalDarkMode": "IespÄ“jot vispÄrÄ“jo tumÅ¡o izskatu", 265 "settings.app.form.universalDarkMode": "IespÄ“jot vispÄrÄ“jo tumÅ¡o izskatu",
262 "settings.app.form.useGrayscaleServices": "Izmantot pelēktoņu pakalpojumu ikonas", 266 "settings.app.form.useGrayscaleServices": "Izmantot pelēktoņu pakalpojumu ikonas",
263 "settings.app.form.useHorizontalStyle": "Lietot horizontÄlu stilu", 267 "settings.app.form.useHorizontalStyle": "Lietot horizontÄlu stilu",
268 "settings.app.form.useSelfSignedCertificates": "IespÄ“jot paÅ¡parakstÄ«tus sertifikÄtus",
264 "settings.app.form.useTouchIdToUnlock": "AtļÄut izmantot Touch ID, lai atbloÄ·Ä“tu Ferdium", 269 "settings.app.form.useTouchIdToUnlock": "AtļÄut izmantot Touch ID, lai atbloÄ·Ä“tu Ferdium",
265 "settings.app.form.wakeUpHibernationSplay": "UzrÄdÄ«t hibernÄcija/moÅ¡anÄs ciklus, lai samazinÄtu noslogojumu", 270 "settings.app.form.wakeUpHibernationSplay": "UzrÄdÄ«t hibernÄcija/moÅ¡anÄs ciklus, lai samazinÄtu noslogojumu",
266 "settings.app.form.wakeUpHibernationStrategy": "HibernÄcijas stratÄ“Ä£ija pÄ“c automÄtiskas pamoÅ¡anÄs", 271 "settings.app.form.wakeUpHibernationStrategy": "HibernÄcijas stratÄ“Ä£ija pÄ“c automÄtiskas pamoÅ¡anÄs",
@@ -275,6 +280,7 @@
275 "settings.app.headlineUpdates": "AtjauninÄjumi", 280 "settings.app.headlineUpdates": "AtjauninÄjumi",
276 "settings.app.hibernateInfo": "PÄ“c noklusÄ“juma Ferdium paturÄ“s visus pakalpojumus atvÄ“rtus un ielÄdÄ“tus fonÄ, lai tie bÅ«tu gatavi, kad ir vajadzÄ«ba tos izmantot. Pakalpojuma hibernÄcija izslÄ“gs pakalpojumus pÄ“c noteikta laika. Tas ir noderÄ«gi, lai ietaupÄ«tu RAM vai atturÄ“tu pakalpojumus no datora pelÄ“ninÄÅ¡anas.", 281 "settings.app.hibernateInfo": "PÄ“c noklusÄ“juma Ferdium paturÄ“s visus pakalpojumus atvÄ“rtus un ielÄdÄ“tus fonÄ, lai tie bÅ«tu gatavi, kad ir vajadzÄ«ba tos izmantot. Pakalpojuma hibernÄcija izslÄ“gs pakalpojumus pÄ“c noteikta laika. Tas ir noderÄ«gi, lai ietaupÄ«tu RAM vai atturÄ“tu pakalpojumus no datora pelÄ“ninÄÅ¡anas.",
277 "settings.app.inactivityLockInfo": "NeaktÄ«vo minÅ«Å¡u skaits, pÄ“c kurÄm Ferdium automÄtiski aizslÄ“gsies. Izmanto 0, lai atslÄ“gtu", 282 "settings.app.inactivityLockInfo": "NeaktÄ«vo minÅ«Å¡u skaits, pÄ“c kurÄm Ferdium automÄtiski aizslÄ“gsies. Izmanto 0, lai atslÄ“gtu",
283 "settings.app.lockInfo": "AizslÄ“gÅ¡ana ar paroli ļauj aizsargÄt ziņojumus.\nAr aizslÄ“gÅ¡anu ar paroli tiks vaicÄts ievadÄ«t paroli katru reizi, kad Ferdium tiek sÄknÄ“ts vai paÅ¡rocÄ«gi aizslÄ“gts ar slÄ“dzenes zÄ«mi apakÅ¡Ä“jÄ kreisajÄ stÅ«rÄ« vai saÄ«sni {lockShortcut}.",
278 "settings.app.lockedPassword": "Parole", 284 "settings.app.lockedPassword": "Parole",
279 "settings.app.lockedPasswordInfo": "LÅ«dzu, pÄrliecinies, ka ievadi paroli, kuru atcerÄ“sies.\nJa tu pazaudÄ“si Å¡o paroli, tev vajadzÄ“s pÄrinstalÄ“t Ferdium.", 285 "settings.app.lockedPasswordInfo": "LÅ«dzu, pÄrliecinies, ka ievadi paroli, kuru atcerÄ“sies.\nJa tu pazaudÄ“si Å¡o paroli, tev vajadzÄ“s pÄrinstalÄ“t Ferdium.",
280 "settings.app.overallTheme": "VispÄrÄ“jÄ TÄ“ma", 286 "settings.app.overallTheme": "VispÄrÄ“jÄ TÄ“ma",
@@ -294,6 +300,8 @@
294 "settings.app.sectionUpdates": "AplikÄcijas AtjauninÄjumu IestatÄ«jumi", 300 "settings.app.sectionUpdates": "AplikÄcijas AtjauninÄjumu IestatÄ«jumi",
295 "settings.app.sentryInfo": "IzmantoÅ¡anas pÄrskata nosÅ«tÄ«Å¡ana ļauj atrast kļūdas Ferdium. Netiks sÅ«tÄ«ta nekÄda personÄ«ga informÄcija, piemÄ“ram, ziņojumu saturs.", 301 "settings.app.sentryInfo": "IzmantoÅ¡anas pÄrskata nosÅ«tÄ«Å¡ana ļauj atrast kļūdas Ferdium. Netiks sÅ«tÄ«ta nekÄda personÄ«ga informÄcija, piemÄ“ram, ziņojumu saturs.",
296 "settings.app.serverHelp": "Savienojies ar serveri {serverURL}", 302 "settings.app.serverHelp": "Savienojies ar serveri {serverURL}",
303 "settings.app.servicesUpdateStatusUpToDate": "Pakalpojumi ir atjauninÄti",
304 "settings.app.spellCheckerLanguageInfo": "Ferdium izmanto Mac iebÅ«vÄ“to pareizrakstÄ«bas pÄrbaudÄ«tÄju, lai meklÄ“tu drukas kļūmes. Ja ir vÄ“lme mainÄ«t pareizrakstÄ«bas pÄrbaudÄ«tÄja izmantoto valodu, to var izdarÄ«t Mac sistÄ“mas uzstÄdÄ«jumos.",
297 "settings.app.subheadlineCache": "Kešatmiņa", 305 "settings.app.subheadlineCache": "Kešatmiņa",
298 "settings.app.subheadlineFerdiumProfile": "Ferdium Profils", 306 "settings.app.subheadlineFerdiumProfile": "Ferdium Profils",
299 "settings.app.todoServerInfo": "Å is serveris tiks izmantots \"Ferdium darÄmais\" iespÄ“jai.", 307 "settings.app.todoServerInfo": "Å is serveris tiks izmantots \"Ferdium darÄmais\" iespÄ“jai.",
@@ -314,9 +322,9 @@
314 "settings.recipes.all": "Visi pakalpojumi", 322 "settings.recipes.all": "Visi pakalpojumi",
315 "settings.recipes.custom": "PielÄgotie Pakalpojumi", 323 "settings.recipes.custom": "PielÄgotie Pakalpojumi",
316 "settings.recipes.customService.headline.communityRecipes": "Kopienas Trešo Pušu Receptes", 324 "settings.recipes.customService.headline.communityRecipes": "Kopienas Trešo Pušu Receptes",
317 "settings.recipes.customService.headline.customRecipes": "PielÄgotÄs TreÅ¡o PuÅ¡u Receptes", 325 "settings.recipes.customService.headline.customRecipes": "PielÄgotas treÅ¡o puÅ¡u receptes",
318 "settings.recipes.customService.headline.devRecipes": "Manas izstrÄdes pakalpojumu receptes", 326 "settings.recipes.customService.headline.devRecipes": "Manas izstrÄdes pakalpojumu receptes",
319 "settings.recipes.customService.intro": "Lai pievienotu pielÄgotu pakalpojumu, kopÄ“ pakalpojuma receptes mapi iekÅ¡Ä:", 327 "settings.recipes.customService.intro": "Lai pievienotu pielÄgotu pakalpojumu, pakalpojuma receptes mape iekopÄ:",
320 "settings.recipes.customService.openDevDocs": "IzstrÄdÄtÄja rokasgrÄmata", 328 "settings.recipes.customService.openDevDocs": "IzstrÄdÄtÄja rokasgrÄmata",
321 "settings.recipes.customService.openFolder": "Atvērt mapi", 329 "settings.recipes.customService.openFolder": "Atvērt mapi",
322 "settings.recipes.ferdiumPicks": "Ferdium Izvēle", 330 "settings.recipes.ferdiumPicks": "Ferdium Izvēle",
@@ -324,6 +332,8 @@
324 "settings.recipes.missingService": "Trūkst pakalpojuma?", 332 "settings.recipes.missingService": "Trūkst pakalpojuma?",
325 "settings.recipes.nothingFound": "Atvaino, bet neviens pakalpojums neatbilst meklÄ“Å¡anas nosacÄ«jumiem, bet to joprojÄm ir iespÄ“jams pievienot ar \"PielÄgota tÄ«mekļa vietne\" iespÄ“ju. LÅ«gums ņemt vÄ“rÄ, ka tÄ«mekļa vietne var uzrÄdÄ«t vairÄk pakalpojumu, kas tika pievienoti Ferdium kopÅ¡ paÅ¡reiz izmantotÄs versijas. Lai iegÅ«tu jaunos pakalpojumus, lÅ«gums apsvÄ“rt atjaunot uz jaunÄku Ferdium versiju.", 333 "settings.recipes.nothingFound": "Atvaino, bet neviens pakalpojums neatbilst meklÄ“Å¡anas nosacÄ«jumiem, bet to joprojÄm ir iespÄ“jams pievienot ar \"PielÄgota tÄ«mekļa vietne\" iespÄ“ju. LÅ«gums ņemt vÄ“rÄ, ka tÄ«mekļa vietne var uzrÄdÄ«t vairÄk pakalpojumu, kas tika pievienoti Ferdium kopÅ¡ paÅ¡reiz izmantotÄs versijas. Lai iegÅ«tu jaunos pakalpojumus, lÅ«gums apsvÄ“rt atjaunot uz jaunÄku Ferdium versiju.",
326 "settings.recipes.servicesSuccessfulAddedInfo": "Pakalpojums veiksmīgi pievienots", 334 "settings.recipes.servicesSuccessfulAddedInfo": "Pakalpojums veiksmīgi pievienots",
335 "settings.releasenotes.connectionError": "AtgadÄ«jÄs kļūme savienojoties ar Github, lÅ«gums vÄ“lÄk mÄ“Ä£inÄt vÄ“lreiz.",
336 "settings.releasenotes.connectionErrorPageMissing": "AtgadÄ«jÄs kļūme savienojoties ar Github. MeklÄ“tÄ lapa nav atrodama.",
327 "settings.releasenotes.headline": "InformÄcija par laidienu", 337 "settings.releasenotes.headline": "InformÄcija par laidienu",
328 "settings.searchService": "Meklēt pakalpojumu", 338 "settings.searchService": "Meklēt pakalpojumu",
329 "settings.service.error.goBack": "Atpakaļ uz pakalpojumiem", 339 "settings.service.error.goBack": "Atpakaļ uz pakalpojumiem",
@@ -373,7 +383,7 @@
373 "settings.service.form.proxy.password": "Parole (neobligÄta)", 383 "settings.service.form.proxy.password": "Parole (neobligÄta)",
374 "settings.service.form.proxy.port": "Ports", 384 "settings.service.form.proxy.port": "Ports",
375 "settings.service.form.proxy.restartInfo": "LÅ«gums pÄrsÄknÄ“t Ferdium pÄ“c starpniekservera iestatÄ«jumu izmainÄ«Å¡anas.", 385 "settings.service.form.proxy.restartInfo": "LÅ«gums pÄrsÄknÄ“t Ferdium pÄ“c starpniekservera iestatÄ«jumu izmainÄ«Å¡anas.",
376 "settings.service.form.proxy.user": "LietotÄjs (neobligÄts)", 386 "settings.service.form.proxy.user": "LietotÄjs (izvÄ“les)",
377 "settings.service.form.recipeFileInfo": "LietotÄja datnes tiks ievietotas tÄ«mekļa lapÄ, lai varÄ“tu pielÄgot pakalpojumus pÄ“c saviem ieskatiem. LietotÄja datnes tiek glabÄtas tikai vietÄ“ji un netiek nosÅ«tÄ«tas uz citiem datoriem, kuros tiek izmantots tas pats konts.", 387 "settings.service.form.recipeFileInfo": "LietotÄja datnes tiks ievietotas tÄ«mekļa lapÄ, lai varÄ“tu pielÄgot pakalpojumus pÄ“c saviem ieskatiem. LietotÄja datnes tiek glabÄtas tikai vietÄ“ji un netiek nosÅ«tÄ«tas uz citiem datoriem, kuros tiek izmantots tas pats konts.",
378 "settings.service.form.saveButton": "SaglabÄt pakalpojumu", 388 "settings.service.form.saveButton": "SaglabÄt pakalpojumu",
379 "settings.service.form.tabHosted": "Izvietots", 389 "settings.service.form.tabHosted": "Izvietots",
@@ -471,8 +481,8 @@
471 "tabs.item.wakeUpService": "PamodinÄt pakalpojumu", 481 "tabs.item.wakeUpService": "PamodinÄt pakalpojumu",
472 "validation.email": "{field} nav derīgs", 482 "validation.email": "{field} nav derīgs",
473 "validation.minLength": "{field} ir jÄbÅ«t vismaz {length} simboliem garam", 483 "validation.minLength": "{field} ir jÄbÅ«t vismaz {length} simboliem garam",
474 "validation.oneRequired": "Vismaz viens ir obligÄts", 484 "validation.oneRequired": "Ir nepiecieÅ¡ams vismaz viens",
475 "validation.required": "{field} ir obligÄts", 485 "validation.required": "{field} ir jÄnorÄda",
476 "validation.url": "{field} nav derīgs URL", 486 "validation.url": "{field} nav derīgs URL",
477 "webControls.back": "Atpakaļ", 487 "webControls.back": "Atpakaļ",
478 "webControls.forward": "Uz priekšu", 488 "webControls.forward": "Uz priekšu",
diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json
index 9976af270..24ca894ee 100644
--- a/src/i18n/locales/ru.json
+++ b/src/i18n/locales/ru.json
@@ -159,6 +159,7 @@
159 "service.errorHandler.headline": "О, нет!", 159 "service.errorHandler.headline": "О, нет!",
160 "service.errorHandler.message": "Ошибка", 160 "service.errorHandler.message": "Ошибка",
161 "service.errorHandler.text": "{name} не Ñмог загрузитьÑÑ.", 161 "service.errorHandler.text": "{name} не Ñмог загрузитьÑÑ.",
162 "service.screenshare.cancel": "Отмена",
162 "service.webviewLoader.loading": "Загрузка {service}", 163 "service.webviewLoader.loading": "Загрузка {service}",
163 "services.getStarted": "Ðачать работу", 164 "services.getStarted": "Ðачать работу",
164 "services.login": "ПожалуйÑта, войдите, чтобы иÑпользовать Ferdium.", 165 "services.login": "ПожалуйÑта, войдите, чтобы иÑпользовать Ferdium.",
@@ -214,6 +215,7 @@
214 "settings.app.form.enableTodos": "Включить Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ferdium", 215 "settings.app.form.enableTodos": "Включить Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ferdium",
215 "settings.app.form.hibernateOnStartup": "ОÑтавлÑÑ‚ÑŒ ÑервиÑÑ‹ в ÑоÑтоÑнии глубокого Ñна во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка", 216 "settings.app.form.hibernateOnStartup": "ОÑтавлÑÑ‚ÑŒ ÑервиÑÑ‹ в ÑоÑтоÑнии глубокого Ñна во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка",
216 "settings.app.form.hibernationStrategy": "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð³Ð¸Ð±ÐµÑ€Ð½Ð°Ñ†Ð¸Ð¸", 217 "settings.app.form.hibernationStrategy": "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð³Ð¸Ð±ÐµÑ€Ð½Ð°Ñ†Ð¸Ð¸",
218 "settings.app.form.hideCollapseButton": "Скрыть кнопку \"Свернуть\"",
217 "settings.app.form.hideDownloadButton": "Скрыть кнопку \"Загрузки\"", 219 "settings.app.form.hideDownloadButton": "Скрыть кнопку \"Загрузки\"",
218 "settings.app.form.hideNotificationsButton": "Скрыть кнопки уведомлений и звука", 220 "settings.app.form.hideNotificationsButton": "Скрыть кнопки уведомлений и звука",
219 "settings.app.form.hideSettingsButton": "Скрыть кнопку наÑтроек", 221 "settings.app.form.hideSettingsButton": "Скрыть кнопку наÑтроек",
@@ -244,6 +246,7 @@
244 "settings.app.form.splitMode": "Включить раздельный режим проÑмотра", 246 "settings.app.form.splitMode": "Включить раздельный режим проÑмотра",
245 "settings.app.form.startMinimized": "ЗапуÑкать Ñвернутым", 247 "settings.app.form.startMinimized": "ЗапуÑкать Ñвернутым",
246 "settings.app.form.universalDarkMode": "Включить универÑальный темный режим", 248 "settings.app.form.universalDarkMode": "Включить универÑальный темный режим",
249 "settings.app.form.useSelfSignedCertificates": "Принимать ÑамоподпиÑные Ñертификаты",
247 "settings.app.form.useTouchIdToUnlock": "Разрешить разблокировку Ferdium по TouchID", 250 "settings.app.form.useTouchIdToUnlock": "Разрешить разблокировку Ferdium по TouchID",
248 "settings.app.form.wakeUpHibernationSplay": "Чередование циклов cна/Ð¿Ñ€Ð¾Ð±ÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ð½Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", 251 "settings.app.form.wakeUpHibernationSplay": "Чередование циклов cна/Ð¿Ñ€Ð¾Ð±ÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑƒÐ¼ÐµÐ½ÑŒÑˆÐµÐ½Ð¸Ñ Ð½Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸",
249 "settings.app.form.wakeUpHibernationStrategy": "ДейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ñле автоматичеÑкого проÑыпаниÑ", 252 "settings.app.form.wakeUpHibernationStrategy": "ДейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ñле автоматичеÑкого проÑыпаниÑ",
@@ -256,17 +259,30 @@
256 "settings.app.headlineUpdates": "ОбновлениÑ", 259 "settings.app.headlineUpdates": "ОбновлениÑ",
257 "settings.app.hibernateInfo": "По умолчанию, Ferdium оÑтавит вÑе ваши ÑервиÑÑ‹ открытыми и загружеными в фоновом режиме, чтобы они были готовы к иÑпользованию. СпÑщий режим выгрузит ваши ÑервиÑÑ‹ поÑле заданного количеÑтва минут. Это полезно Ð´Ð»Ñ Ñкономии памÑти и/или оптимальной работы компьютера.", 260 "settings.app.hibernateInfo": "По умолчанию, Ferdium оÑтавит вÑе ваши ÑервиÑÑ‹ открытыми и загружеными в фоновом режиме, чтобы они были готовы к иÑпользованию. СпÑщий режим выгрузит ваши ÑервиÑÑ‹ поÑле заданного количеÑтва минут. Это полезно Ð´Ð»Ñ Ñкономии памÑти и/или оптимальной работы компьютера.",
258 "settings.app.inactivityLockInfo": "минут бездейÑтвиÑ, поÑле чего Ferdium будет автоматичеÑки блокироватьÑÑ. ИÑпользуйте 0, чтобы отключить", 261 "settings.app.inactivityLockInfo": "минут бездейÑтвиÑ, поÑле чего Ferdium будет автоматичеÑки блокироватьÑÑ. ИÑпользуйте 0, чтобы отключить",
262 "settings.app.infoOpenCertificatesFolder": "Чтобы уÑтановить Ñертификат, нажмите на кнопку ниже, чтобы открыть папку Ñертификатов и Ñкопировать его в папку. ПоÑле Ñтого вы можете обновить Ñлужбу (CTRL/CMD + R). Ð”Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñто удалите файл Ñертификата и перезапуÑтите Ferdium.",
259 "settings.app.lockedPassword": "Пароль", 263 "settings.app.lockedPassword": "Пароль",
260 "settings.app.lockedPasswordInfo": "ПожалуйÑта, убедитеÑÑŒ, что уÑтановлен пароль, который вы запомните.\nЕÑли вы потерÑете Ñтот пароль, вам придетÑÑ Ð¿ÐµÑ€ÐµÑƒÑтановить Ferdium.", 264 "settings.app.lockedPasswordInfo": "ПожалуйÑта, убедитеÑÑŒ, что уÑтановлен пароль, который вы запомните.\nЕÑли вы потерÑете Ñтот пароль, вам придетÑÑ Ð¿ÐµÑ€ÐµÑƒÑтановить Ferdium.",
265 "settings.app.overallTheme": "ÐžÐ±Ñ‰Ð°Ñ Ñ‚ÐµÐ¼Ð°",
266 "settings.app.progressbarTheme": "Тема ПрогреÑÑа",
267 "settings.app.restart.restartDialogMessage": "Ð’Ñ‹ хотите перезапуÑтить Ferdium?",
268 "settings.app.restart.restartDialogTitle": "Ferdium - ПерезапуÑк приложениÑ",
269 "settings.app.restart.restartLater": "ПерезапуÑтить позже",
270 "settings.app.restart.restartNow": "ПерезапуÑтить ÑейчаÑ",
261 "settings.app.restartRequired": "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ перезагрузки приложениÑ", 271 "settings.app.restartRequired": "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ перезагрузки приложениÑ",
262 "settings.app.scheduledDNDInfo": "Запланированный режим \"не беÑпокоить\" позволÑет вам определить период времени, в течение которого вы не желаете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ Ferdium.", 272 "settings.app.scheduledDNDInfo": "Запланированный режим \"не беÑпокоить\" позволÑет вам определить период времени, в течение которого вы не желаете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ Ferdium.",
263 "settings.app.scheduledDNDTimeInfo": "Ð’Ñ€ÐµÐ¼Ñ Ð² 24-чаÑовом формате. Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть раньше времени начала (например, начало 17:00, конец 09:00), чтобы включить ночной режим \"не беÑпокоить\".", 273 "settings.app.scheduledDNDTimeInfo": "Ð’Ñ€ÐµÐ¼Ñ Ð² 24-чаÑовом формате. Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть раньше времени начала (например, начало 17:00, конец 09:00), чтобы включить ночной режим \"не беÑпокоить\".",
274 "settings.app.sectionAdvanced": "РаÑширенные наÑтройки",
275 "settings.app.sectionHibernation": "ГибернациÑ",
264 "settings.app.sectionLanguage": "ÐаÑтройка Ñзыка", 276 "settings.app.sectionLanguage": "ÐаÑтройка Ñзыка",
277 "settings.app.sectionPrivacy": "ÐаÑтройки конфиденциальноÑти",
278 "settings.app.sectionServiceIconsSettings": "ÐаÑтройки значков ÑервиÑа",
279 "settings.app.sectionSidebarSettings": "ÐаÑтройки боковой панели",
265 "settings.app.sectionUpdates": "ÐаÑтройки Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ", 280 "settings.app.sectionUpdates": "ÐаÑтройки Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ",
266 "settings.app.sentryInfo": "Отправка телеметрии позволÑет нам находить ошибки в Ferdium - мы не будем отправлÑÑ‚ÑŒ никакой личной информации, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð²Ð°ÑˆÐ¸ ÑообщениÑ!", 281 "settings.app.sentryInfo": "Отправка телеметрии позволÑет нам находить ошибки в Ferdium - мы не будем отправлÑÑ‚ÑŒ никакой личной информации, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð²Ð°ÑˆÐ¸ ÑообщениÑ!",
267 "settings.app.serverHelp": "Подключен к Ñерверу {serverURL}", 282 "settings.app.serverHelp": "Подключен к Ñерверу {serverURL}",
268 "settings.app.servicesUpdateStatusUpToDate": "Ваши ÑервиÑÑ‹ актуальны", 283 "settings.app.servicesUpdateStatusUpToDate": "Ваши ÑервиÑÑ‹ актуальны",
269 "settings.app.subheadlineCache": "КÑш", 284 "settings.app.subheadlineCache": "КÑш",
285 "settings.app.subheadlineDownloads": "Загрузки",
270 "settings.app.subheadlineFerdiumProfile": "Профиль Ferdium", 286 "settings.app.subheadlineFerdiumProfile": "Профиль Ferdium",
271 "settings.app.todoServerInfo": "Этот Ñервер будет иÑпользоватьÑÑ Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ задач \"Ferdium Todo\".", 287 "settings.app.todoServerInfo": "Этот Ñервер будет иÑпользоватьÑÑ Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ задач \"Ferdium Todo\".",
272 "settings.app.translationHelp": "Помогите нам перевеÑти Ferdium на ваш Ñзык.", 288 "settings.app.translationHelp": "Помогите нам перевеÑти Ferdium на ваш Ñзык.",
@@ -296,6 +312,8 @@
296 "settings.recipes.missingService": "Ðе можете найти ÑервиÑ?", 312 "settings.recipes.missingService": "Ðе можете найти ÑервиÑ?",
297 "settings.recipes.nothingFound": "Извините, но ÑÐµÑ€Ð²Ð¸Ñ Ð½Ðµ ÑоответÑтвует вашему поиÑковому запроÑу - но вы вÑе еще можете добавить его, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¾Ð¿Ñ†Ð¸ÑŽ \"ПользовательÑкий Ñайт\". ПожалуйÑта, обратите внимание, что на Ñайте может отображатьÑÑ Ð±Ð¾Ð»ÑŒÑˆÐµ Ñлужб, которые могли быть добавлены в новых верÑиÑÑ… Ferdium. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ñ… ÑервиÑов, пожалуйÑта, обновите Ferdium до актуальной верÑии.", 313 "settings.recipes.nothingFound": "Извините, но ÑÐµÑ€Ð²Ð¸Ñ Ð½Ðµ ÑоответÑтвует вашему поиÑковому запроÑу - но вы вÑе еще можете добавить его, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¾Ð¿Ñ†Ð¸ÑŽ \"ПользовательÑкий Ñайт\". ПожалуйÑта, обратите внимание, что на Ñайте может отображатьÑÑ Ð±Ð¾Ð»ÑŒÑˆÐµ Ñлужб, которые могли быть добавлены в новых верÑиÑÑ… Ferdium. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ñ… ÑервиÑов, пожалуйÑта, обновите Ferdium до актуальной верÑии.",
298 "settings.recipes.servicesSuccessfulAddedInfo": "Ð¡ÐµÑ€Ð²Ð¸Ñ ÑƒÑпешно добавлен", 314 "settings.recipes.servicesSuccessfulAddedInfo": "Ð¡ÐµÑ€Ð²Ð¸Ñ ÑƒÑпешно добавлен",
315 "settings.releasenotes.connectionError": "Произошла ошибка при подключении к Github, пожалуйÑта, повторите попытку позже.",
316 "settings.releasenotes.connectionErrorPageMissing": "Произошла ошибка при подключении к Github, Ñтраница, которую вы ищете, отÑутÑтвует.",
299 "settings.releasenotes.headline": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ выпуÑке", 317 "settings.releasenotes.headline": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ выпуÑке",
300 "settings.searchService": "Ðайти ÑервиÑ", 318 "settings.searchService": "Ðайти ÑервиÑ",
301 "settings.service.error.goBack": "ВернутьÑÑ Ðº ÑервиÑам", 319 "settings.service.error.goBack": "ВернутьÑÑ Ðº ÑервиÑам",
@@ -317,6 +335,7 @@
317 "settings.service.form.enableNotification": "Включить уведомлениÑ", 335 "settings.service.form.enableNotification": "Включить уведомлениÑ",
318 "settings.service.form.enableService": "Включить ÑервиÑ", 336 "settings.service.form.enableService": "Включить ÑервиÑ",
319 "settings.service.form.enableWakeUp": "Включить пробуждение", 337 "settings.service.form.enableWakeUp": "Включить пробуждение",
338 "settings.service.form.headlineAppearance": "Оформление",
320 "settings.service.form.headlineBadges": "Значки непрочитанных Ñообщений", 339 "settings.service.form.headlineBadges": "Значки непрочитанных Ñообщений",
321 "settings.service.form.headlineDarkReaderSettings": "ÐаÑтройки тёмного чтениÑ", 340 "settings.service.form.headlineDarkReaderSettings": "ÐаÑтройки тёмного чтениÑ",
322 "settings.service.form.headlineGeneral": "Общие", 341 "settings.service.form.headlineGeneral": "Общие",
@@ -401,6 +420,7 @@
401 "setupAssistant.headline": "Давайте начнем", 420 "setupAssistant.headline": "Давайте начнем",
402 "setupAssistant.subheadline": "Сделайте Ñвой выбор Ñреди наиболее популÑрных ÑервиÑов и возвращайтеÑÑŒ к Ñвоим ÑообщениÑм прÑмо ÑейчаÑ.", 421 "setupAssistant.subheadline": "Сделайте Ñвой выбор Ñреди наиболее популÑрных ÑервиÑов и возвращайтеÑÑŒ к Ñвоим ÑообщениÑм прÑмо ÑейчаÑ.",
403 "setupAssistant.submit.label": "Ðачнем", 422 "setupAssistant.submit.label": "Ðачнем",
423 "sidebar.addNewService": "Добавить новый ÑервиÑ",
404 "sidebar.muteApp": "Отключить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¸ звук", 424 "sidebar.muteApp": "Отключить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¸ звук",
405 "sidebar.unmuteApp": "Включить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¸ звук", 425 "sidebar.unmuteApp": "Включить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¸ звук",
406 "signup.company.label": "КомпаниÑ", 426 "signup.company.label": "КомпаниÑ",
@@ -432,6 +452,7 @@
432 "validation.minLength": "КількіÑÑ‚ÑŒ Ñимволів в {field} повина бути не меньше {length}", 452 "validation.minLength": "КількіÑÑ‚ÑŒ Ñимволів в {field} повина бути не меньше {length}",
433 "validation.oneRequired": "Ðеобходим как минимум один", 453 "validation.oneRequired": "Ðеобходим как минимум один",
434 "validation.required": "{field} необходимо", 454 "validation.required": "{field} необходимо",
455 "validation.url": "{field} не ÑвлÑетÑÑ Ð´ÐµÐ¹Ñтвительным URL",
435 "webControls.back": "Ðазад", 456 "webControls.back": "Ðазад",
436 "webControls.forward": "ПереÑлать", 457 "webControls.forward": "ПереÑлать",
437 "webControls.goHome": "Домой", 458 "webControls.goHome": "Домой",
diff --git a/src/index.ts b/src/index.ts
index 999d84348..a602c994e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -245,6 +245,18 @@ const createWindow = () => {
245 openExternalUrl(url); 245 openExternalUrl(url);
246 return { action: 'deny' }; 246 return { action: 'deny' };
247 }); 247 });
248
249 // Handle will download event from main process (prevent download dialog)
250 contents.session.on('will-download', (_e, item) => {
251 const downloadFolderPath = retrieveSettingValue(
252 'downloadFolderPath',
253 DEFAULT_APP_SETTINGS.downloadFolderPath,
254 ) as string;
255
256 if (downloadFolderPath !== '') {
257 item.setSavePath(join(downloadFolderPath, item.getFilename()));
258 }
259 });
248 } 260 }
249 }); 261 });
250 262
@@ -393,7 +405,8 @@ const createWindow = () => {
393 }); 405 });
394 406
395 if (isMac) { 407 if (isMac) {
396 import('./electron/macOSPermissions').then(macOSPermissions => { 408 // Note: Do not remove the extension. See https://github.com/ferdium/ferdium-app/issues/1755 for explanation
409 import('./electron/macOSPermissions.js').then(macOSPermissions => {
397 const { askFormacOSPermissions } = macOSPermissions; 410 const { askFormacOSPermissions } = macOSPermissions;
398 411
399 setTimeout(() => askFormacOSPermissions(mainWindow!), ms('30s')); 412 setTimeout(() => askFormacOSPermissions(mainWindow!), ms('30s'));
@@ -610,6 +623,11 @@ ipcMain.on('feature-basic-auth-cancel', () => {
610 authCallback = noop; 623 authCallback = noop;
611}); 624});
612 625
626ipcMain.on('load-available-displays', (_e, data) => {
627 debug('MAIN PROCESS: Received load-desktop-capturer-sources');
628 mainWindow?.webContents.send(`select-capture-device:${data.serviceId}`, data);
629});
630
613// Handle synchronous messages from service webviews. 631// Handle synchronous messages from service webviews.
614 632
615ipcMain.on('find-in-page', (e, text, options) => { 633ipcMain.on('find-in-page', (e, text, options) => {
@@ -777,3 +795,15 @@ app.on(
777 callback(checkIfCertIsPresent(certificate.data)); 795 callback(checkIfCertIsPresent(certificate.data));
778 }, 796 },
779); 797);
798
799ipcMain.on('relaunch-app', async (_, options) => {
800 // Ask user to confirm
801 const result = await dialog.showMessageBox(mainWindow!, options);
802
803 if (result.response === options.cancelId) {
804 return;
805 }
806
807 app.relaunch();
808 app.quit();
809});
diff --git a/src/internal-server/app/Controllers/Http/ServiceController.js b/src/internal-server/app/Controllers/Http/ServiceController.js
index 8e8aa97a8..4e3c63515 100644
--- a/src/internal-server/app/Controllers/Http/ServiceController.js
+++ b/src/internal-server/app/Controllers/Http/ServiceController.js
@@ -53,6 +53,7 @@ class ServiceController {
53 isNotificationEnabled: DEFAULT_SERVICE_SETTINGS.isNotificationEnabled, 53 isNotificationEnabled: DEFAULT_SERVICE_SETTINGS.isNotificationEnabled,
54 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled, 54 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled,
55 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks, 55 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
56 useFavicon: DEFAULT_SERVICE_SETTINGS.useFavicon,
56 isMuted: DEFAULT_SERVICE_SETTINGS.isMuted, 57 isMuted: DEFAULT_SERVICE_SETTINGS.isMuted,
57 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work. 58 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work.
58 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled, 59 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled,
@@ -83,6 +84,7 @@ class ServiceController {
83 hasCustomIcon: DEFAULT_SERVICE_SETTINGS.hasCustomIcon, 84 hasCustomIcon: DEFAULT_SERVICE_SETTINGS.hasCustomIcon,
84 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled, 85 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled,
85 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks, 86 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
87 useFavicon: DEFAULT_SERVICE_SETTINGS.useFavicon,
86 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work. 88 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work.
87 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled, 89 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled,
88 isEnabled: DEFAULT_SERVICE_SETTINGS.isEnabled, 90 isEnabled: DEFAULT_SERVICE_SETTINGS.isEnabled,
@@ -232,6 +234,7 @@ class ServiceController {
232 hasCustomIcon: DEFAULT_SERVICE_SETTINGS.customIcon, 234 hasCustomIcon: DEFAULT_SERVICE_SETTINGS.customIcon,
233 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled, 235 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled,
234 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks, 236 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
237 useFavicon: DEFAULT_SERVICE_SETTINGS.useFavicon,
235 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work. 238 isDarkModeEnabled: '', // TODO: This should ideally be a boolean (false). But, changing it caused the sidebar toggle to not work.
236 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled, 239 isProgressbarEnabled: DEFAULT_SERVICE_SETTINGS.isProgressbarEnabled,
237 isEnabled: DEFAULT_SERVICE_SETTINGS.isEnabled, 240 isEnabled: DEFAULT_SERVICE_SETTINGS.isEnabled,
diff --git a/src/jsUtils.ts b/src/jsUtils.ts
index 0befb8d56..145875cfa 100644
--- a/src/jsUtils.ts
+++ b/src/jsUtils.ts
@@ -28,12 +28,21 @@ export const safeParseInt = (text?: string | number | null) => {
28 return Math.max(adjustedNumber, 0); 28 return Math.max(adjustedNumber, 0);
29}; 29};
30 30
31export const acceleratorString = ( 31interface IAcceleratorString {
32 index: number, 32 keyCombo: string;
33 keyCombo: string, 33 index: number;
34 prefix: string = '(', 34 prefix?: string;
35 suffix: string = ')', 35 suffix?: string;
36) => (index <= 10 ? `${prefix}${keyCombo}+${index % 10}${suffix}` : ''); 36 maxIndex?: number;
37}
38export const acceleratorString = ({
39 index,
40 keyCombo,
41 prefix = '(',
42 suffix = ')',
43 maxIndex = 9,
44}: IAcceleratorString) =>
45 index <= maxIndex ? `${prefix}${keyCombo}+${index % 10}${suffix}` : '';
37 46
38export const removeNewLines = (input: string): string => 47export const removeNewLines = (input: string): string =>
39 input.replaceAll(/\r?\n|\r/g, ''); 48 input.replaceAll(/\r?\n|\r/g, '');
diff --git a/src/lib/Menu.ts b/src/lib/Menu.ts
index bb8eead3a..8fd8e0a73 100644
--- a/src/lib/Menu.ts
+++ b/src/lib/Menu.ts
@@ -496,7 +496,15 @@ function titleBarTemplateFactory(
496 }, 496 },
497 { 497 {
498 label: intl.formatMessage(menuItems.zoomIn), 498 label: intl.formatMessage(menuItems.zoomIn),
499 accelerator: `${cmdOrCtrlShortcutKey()}+plus`, 499 // TODO: Modify this logic once https://github.com/electron/electron/issues/40674 is fixed
500 // This is a workaround for the issue where the zoom in shortcut is not working
501 // This makes sure the accelerator is not registered
502 accelerator: isWindows
503 ? `${cmdOrCtrlShortcutKey()}++`
504 : `${cmdOrCtrlShortcutKey()}+Plus`,
505 registerAccelerator: !!isMac,
506 acceleratorWorksWhenHidden: !!isMac,
507 // ---------------------------
500 click() { 508 click() {
501 const activeService = getActiveService(); 509 const activeService = getActiveService();
502 if (!activeService) { 510 if (!activeService) {
@@ -1134,7 +1142,12 @@ class FranzMenu implements StoresProps {
1134 for (const [i, service] of services.allDisplayed.entries()) { 1142 for (const [i, service] of services.allDisplayed.entries()) {
1135 menu.push({ 1143 menu.push({
1136 label: this._getServiceName(service), 1144 label: this._getServiceName(service),
1137 accelerator: acceleratorString(i + 1, cmdOrCtrlShortcutKey(), '', ''), 1145 accelerator: acceleratorString({
1146 index: i + 1,
1147 keyCombo: cmdOrCtrlShortcutKey(),
1148 prefix: '',
1149 suffix: '',
1150 }),
1138 type: 'radio', 1151 type: 'radio',
1139 checked: service.isActive, 1152 checked: service.isActive,
1140 click: () => { 1153 click: () => {
diff --git a/src/models/Recipe.ts b/src/models/Recipe.ts
index 11eb1884f..87043693b 100644
--- a/src/models/Recipe.ts
+++ b/src/models/Recipe.ts
@@ -26,6 +26,7 @@ interface RecipeData {
26 message?: string; 26 message?: string;
27 allowFavoritesDelineationInUnreadCount?: boolean; 27 allowFavoritesDelineationInUnreadCount?: boolean;
28 }; 28 };
29 defaultIcon: string;
29} 30}
30 31
31export interface IRecipe { 32export interface IRecipe {
@@ -49,6 +50,7 @@ export interface IRecipe {
49 path: string; 50 path: string;
50 partition: string; 51 partition: string;
51 local: boolean; 52 local: boolean;
53 defaultIcon: string;
52 54
53 readonly overrideUserAgent?: () => string; 55 readonly overrideUserAgent?: () => string;
54 56
@@ -76,6 +78,8 @@ export default class Recipe implements IRecipe {
76 78
77 version = ''; 79 version = '';
78 80
81 defaultIcon = '';
82
79 // Removing this specific type will cause a typescript error 83 // Removing this specific type will cause a typescript error
80 // even while it's the exact same as the interface 84 // even while it's the exact same as the interface
81 aliases: string[] = []; 85 aliases: string[] = [];
@@ -135,6 +139,7 @@ export default class Recipe implements IRecipe {
135 // from the recipe 139 // from the recipe
136 this.id = ifUndefined<string>(data.id, this.id); 140 this.id = ifUndefined<string>(data.id, this.id);
137 this.name = ifUndefined<string>(data.name, this.name); 141 this.name = ifUndefined<string>(data.name, this.name);
142 this.defaultIcon = ifUndefined<string>(data.defaultIcon, this.defaultIcon);
138 this.version = ifUndefined<string>(data.version, this.version); 143 this.version = ifUndefined<string>(data.version, this.version);
139 this.aliases = ifUndefined<string[]>(data.aliases, this.aliases); 144 this.aliases = ifUndefined<string[]>(data.aliases, this.aliases);
140 this.serviceURL = ifUndefined<string>( 145 this.serviceURL = ifUndefined<string>(
diff --git a/src/models/Service.ts b/src/models/Service.ts
index 6d19f44f1..ae04b8b58 100644
--- a/src/models/Service.ts
+++ b/src/models/Service.ts
@@ -7,7 +7,9 @@ import type ElectronWebView from 'react-electron-web-view';
7import { v4 as uuidV4 } from 'uuid'; 7import { v4 as uuidV4 } from 'uuid';
8import { needsToken } from '../api/apiBase'; 8import { needsToken } from '../api/apiBase';
9import { DEFAULT_SERVICE_ORDER, DEFAULT_SERVICE_SETTINGS } from '../config'; 9import { DEFAULT_SERVICE_ORDER, DEFAULT_SERVICE_SETTINGS } from '../config';
10import { isMac } from '../environment';
10import { todosStore } from '../features/todos'; 11import { todosStore } from '../features/todos';
12import { getFaviconUrl } from '../helpers/favicon-helpers';
11import { isValidExternalURL, normalizedUrl } from '../helpers/url-helpers'; 13import { isValidExternalURL, normalizedUrl } from '../helpers/url-helpers';
12import { ifUndefined } from '../jsUtils'; 14import { ifUndefined } from '../jsUtils';
13import type { IRecipe } from './Recipe'; 15import type { IRecipe } from './Recipe';
@@ -134,6 +136,8 @@ export default class Service {
134 136
135 @observable isMediaPlaying: boolean = false; 137 @observable isMediaPlaying: boolean = false;
136 138
139 @observable useFavicon: boolean = DEFAULT_SERVICE_SETTINGS.useFavicon;
140
137 @action _setAutoRun() { 141 @action _setAutoRun() {
138 if (!this.isEnabled) { 142 if (!this.isEnabled) {
139 this.webview = null; 143 this.webview = null;
@@ -167,6 +171,7 @@ export default class Service {
167 this.team = ifUndefined<string>(data.team, this.team); 171 this.team = ifUndefined<string>(data.team, this.team);
168 this.customUrl = ifUndefined<string>(data.customUrl, this.customUrl); 172 this.customUrl = ifUndefined<string>(data.customUrl, this.customUrl);
169 this.iconUrl = ifUndefined<string>(data.iconUrl, this.iconUrl); 173 this.iconUrl = ifUndefined<string>(data.iconUrl, this.iconUrl);
174 this.useFavicon = ifUndefined<boolean>(data.useFavicon, this.useFavicon);
170 this.order = ifUndefined<number>(data.order, this.order); 175 this.order = ifUndefined<number>(data.order, this.order);
171 this.isEnabled = ifUndefined<boolean>(data.isEnabled, this.isEnabled); 176 this.isEnabled = ifUndefined<boolean>(data.isEnabled, this.isEnabled);
172 this.isNotificationEnabled = ifUndefined<boolean>( 177 this.isNotificationEnabled = ifUndefined<boolean>(
@@ -350,6 +355,10 @@ export default class Service {
350 } 355 }
351 356
352 @computed get icon(): string { 357 @computed get icon(): string {
358 if (this.useFavicon) {
359 return getFaviconUrl(this.url);
360 }
361
353 if (this.iconUrl) { 362 if (this.iconUrl) {
354 if (needsToken()) { 363 if (needsToken()) {
355 let url: URL; 364 let url: URL;
@@ -369,6 +378,10 @@ export default class Service {
369 return this.iconUrl; 378 return this.iconUrl;
370 } 379 }
371 380
381 if (this.recipe.defaultIcon) {
382 return this.recipe.defaultIcon;
383 }
384
372 return join(this.recipe.path, 'icon.svg'); 385 return join(this.recipe.path, 'icon.svg');
373 } 386 }
374 387
@@ -524,6 +537,18 @@ export default class Service {
524 }); 537 });
525 538
526 if (webviewWebContents) { 539 if (webviewWebContents) {
540 // TODO: Modify this logic once https://github.com/electron/electron/issues/40674 is fixed
541 // This is a workaround for the issue where the zoom in shortcut is not working
542 if (!isMac) {
543 webviewWebContents.on('before-input-event', (event, input) => {
544 if (input.control && input.key === '+' && input.type === 'keyDown') {
545 event.preventDefault();
546 const currentZoom = this.webview?.getZoomLevel();
547 this.webview?.setZoomLevel(currentZoom + 0.5);
548 }
549 });
550 }
551
527 webviewWebContents.session.on('will-download', (event, item) => { 552 webviewWebContents.session.on('will-download', (event, item) => {
528 event.preventDefault(); 553 event.preventDefault();
529 554
diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts
index 6c7a55d6b..58c6b2a87 100644
--- a/src/stores/ServicesStore.ts
+++ b/src/stores/ServicesStore.ts
@@ -472,6 +472,7 @@ export default class ServicesStore extends TypedStore {
472 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled, 472 isBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isBadgeEnabled,
473 isMediaBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isMediaBadgeEnabled, 473 isMediaBadgeEnabled: DEFAULT_SERVICE_SETTINGS.isMediaBadgeEnabled,
474 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks, 474 trapLinkClicks: DEFAULT_SERVICE_SETTINGS.trapLinkClicks,
475 useFavicon: DEFAULT_SERVICE_SETTINGS.useFavicon,
475 isMuted: DEFAULT_SERVICE_SETTINGS.isMuted, 476 isMuted: DEFAULT_SERVICE_SETTINGS.isMuted,
476 customIcon: DEFAULT_SERVICE_SETTINGS.customIcon, 477 customIcon: DEFAULT_SERVICE_SETTINGS.customIcon,
477 isDarkModeEnabled: DEFAULT_SERVICE_SETTINGS.isDarkModeEnabled, 478 isDarkModeEnabled: DEFAULT_SERVICE_SETTINGS.isDarkModeEnabled,
@@ -838,6 +839,16 @@ export default class ServicesStore extends TypedStore {
838 839
839 break; 840 break;
840 } 841 }
842
843 case 'load-available-displays': {
844 debug('Received request for capture devices from', serviceId);
845 ipcRenderer.send('load-available-displays', {
846 serviceId,
847 ...args[0],
848 });
849 break;
850 }
851
841 case 'notification': { 852 case 'notification': {
842 const { notificationId, options } = args[0]; 853 const { notificationId, options } = args[0];
843 854
@@ -906,12 +917,13 @@ export default class ServicesStore extends TypedStore {
906 917
907 if (service.isNotificationEnabled) { 918 if (service.isNotificationEnabled) {
908 let title: string; 919 let title: string;
909 options.icon = service.iconUrl;
910 if (this.stores.settings.all.app.privateNotifications === true) { 920 if (this.stores.settings.all.app.privateNotifications === true) {
911 // Remove message data from notification in private mode 921 // Remove message data from notification in private mode
912 options.body = ''; 922 options.body = '';
923 options.icon = service.icon;
913 title = `Notification from ${service.name}`; 924 title = `Notification from ${service.name}`;
914 } else { 925 } else {
926 options.icon = options.icon || service.icon;
915 options.body = typeof options.body === 'string' ? options.body : ''; 927 options.body = typeof options.body === 'string' ? options.body : '';
916 title = 928 title =
917 typeof args[0].title === 'string' ? args[0].title : service.name; 929 typeof args[0].title === 'string' ? args[0].title : service.name;
diff --git a/src/styles/capture-sources.scss b/src/styles/capture-sources.scss
new file mode 100644
index 000000000..c9ee69f09
--- /dev/null
+++ b/src/styles/capture-sources.scss
@@ -0,0 +1,75 @@
1.desktop-capturer-selection {
2 position: absolute;
3 top: 0;
4 left: 0;
5 width: 100%;
6 height: 100vh;
7 background: rgba(30, 30, 30, 0.75);
8 color: #fff;
9 z-index: 10000000;
10 display: flex;
11 align-items: center;
12 justify-content: center;
13}
14
15.desktop-capturer-selection__scroller {
16 width: 100%;
17 max-height: 100vh;
18 overflow-y: auto;
19}
20.desktop-capturer-selection__list {
21 max-width: calc(100% - 100px);
22 margin: 50px;
23 padding: 0;
24 display: flex;
25 flex-wrap: wrap;
26 list-style: none;
27 overflow: hidden;
28 justify-content: center;
29}
30.desktop-capturer-selection__item {
31 display: flex;
32 margin: 4px;
33}
34.desktop-capturer-selection__btn {
35 display: flex;
36 flex-direction: column;
37 align-items: stretch;
38 width: 145px;
39 margin: 0;
40 border: 0;
41 border-radius: 3px;
42 padding: 4px;
43 background: #252626;
44 text-align: left;
45 @media (prefers-reduced-motion: no-preference) {
46 transition:
47 background-color 0.15s,
48 box-shadow 0.15s,
49 color 0.15s;
50 }
51 color: #dedede;
52}
53.desktop-capturer-selection__btn:hover,
54.desktop-capturer-selection__btn:focus {
55 background: rgba(98, 100, 167, 0.8);
56 box-shadow:
57 0 0 4px rgba(0, 0, 0, 0.45),
58 0 0 2px rgba(0, 0, 0, 0.25);
59 color: #fff;
60}
61.desktop-capturer-selection__thumbnail {
62 width: 100%;
63 height: 81px;
64 object-fit: cover;
65}
66.desktop-capturer-selection__name {
67 margin: 6px 0;
68 white-space: nowrap;
69 text-overflow: ellipsis;
70 text-align: center;
71 overflow: hidden;
72}
73.desktop-capturer-selection__name--cancel {
74 margin: auto 0;
75}
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 8369c9298..fafb1b861 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -23,6 +23,7 @@
23@import './invite.scss'; 23@import './invite.scss';
24@import './title-bar.scss'; 24@import './title-bar.scss';
25@import './features.scss'; 25@import './features.scss';
26@import './capture-sources.scss';
26 27
27// form 28// form
28@import './input.scss'; 29@import './input.scss';
diff --git a/src/webview/contextMenuBuilder.ts b/src/webview/contextMenuBuilder.ts
index 0a8b9c57b..d92e18aa9 100644
--- a/src/webview/contextMenuBuilder.ts
+++ b/src/webview/contextMenuBuilder.ts
@@ -130,8 +130,8 @@ interface ContextMenuStringTable {
130 translatorLanguage: string; 130 translatorLanguage: string;
131 }) => string; 131 }) => string;
132 openLinkUrl: () => string; 132 openLinkUrl: () => string;
133 openLinkInFerdiumUrl: () => string;
134 openInBrowser: () => string; 133 openInBrowser: () => string;
134 openInFerdium: () => string;
135 copyLinkUrl: () => string; 135 copyLinkUrl: () => string;
136 copyImageUrl: () => string; 136 copyImageUrl: () => string;
137 copyImage: () => string; 137 copyImage: () => string;
@@ -158,8 +158,8 @@ const contextMenuStringTable: ContextMenuStringTable = {
158 `Translate to ${translatorLanguage}`, 158 `Translate to ${translatorLanguage}`,
159 translateLanguage: ({ translatorLanguage }) => `${translatorLanguage}`, 159 translateLanguage: ({ translatorLanguage }) => `${translatorLanguage}`,
160 openLinkUrl: () => 'Open Link', 160 openLinkUrl: () => 'Open Link',
161 openLinkInFerdiumUrl: () => 'Open Link in Ferdium',
162 openInBrowser: () => 'Open in Browser', 161 openInBrowser: () => 'Open in Browser',
162 openInFerdium: () => 'Open in Ferdium',
163 copyLinkUrl: () => 'Copy Link', 163 copyLinkUrl: () => 'Copy Link',
164 copyImageUrl: () => 'Copy Image Address', 164 copyImageUrl: () => 'Copy Image Address',
165 copyImage: () => 'Copy Image', 165 copyImage: () => 'Copy Image',
@@ -299,10 +299,13 @@ export class ContextMenuBuilder {
299 this.addPastePlain(menu, menuInfo); 299 this.addPastePlain(menu, menuInfo);
300 this.addInspectElement(menu, menuInfo); 300 this.addInspectElement(menu, menuInfo);
301 this.processMenu(menu); 301 this.processMenu(menu);
302 302 this.addSeparator(menu);
303 this.copyPageUrl(menu, menuInfo); 303 this.copyPageUrl(menu, menuInfo);
304 this.goToHomePage(menu, menuInfo); 304 this.addSeparator(menu);
305 this.openInBrowser(menu, menuInfo); 305 this.openInBrowser(menu, menuInfo);
306 this.openInFerdium(menu, menuInfo);
307 this.addSeparator(menu);
308 this.goToHomePage(menu, menuInfo);
306 309
307 return menu; 310 return menu;
308 } 311 }
@@ -340,16 +343,8 @@ export class ContextMenuBuilder {
340 }, 343 },
341 }); 344 });
342 345
343 const openInFerdiumLink = new MenuItem({
344 label: this.stringTable.openLinkInFerdiumUrl(),
345 click: () => {
346 window.location.href = menuInfo.linkURL;
347 },
348 });
349
350 menu.append(copyLink); 346 menu.append(copyLink);
351 menu.append(openLink); 347 menu.append(openLink);
352 menu.append(openInFerdiumLink);
353 348
354 if (this.isSrcUrlValid(menuInfo)) { 349 if (this.isSrcUrlValid(menuInfo)) {
355 this.addSeparator(menu); 350 this.addSeparator(menu);
@@ -358,13 +353,15 @@ export class ContextMenuBuilder {
358 353
359 this.addInspectElement(menu, menuInfo); 354 this.addInspectElement(menu, menuInfo);
360 this.processMenu(menu); 355 this.processMenu(menu);
361
362 this.addSeparator(menu); 356 this.addSeparator(menu);
363 this.goBack(menu); 357 this.goBack(menu);
364 this.goForward(menu); 358 this.goForward(menu);
365 this.copyPageUrl(menu, menuInfo); 359 this.copyPageUrl(menu, menuInfo);
366 this.goToHomePage(menu, menuInfo); 360 this.addSeparator(menu);
367 this.openInBrowser(menu, menuInfo); 361 this.openInBrowser(menu, menuInfo);
362 this.openInFerdium(menu, menuInfo);
363 this.addSeparator(menu);
364 this.goToHomePage(menu, menuInfo);
368 365
369 return menu; 366 return menu;
370 } 367 }
@@ -384,16 +381,19 @@ export class ContextMenuBuilder {
384 this.addTranslateItems(menu, menuInfo); 381 this.addTranslateItems(menu, menuInfo);
385 } 382 }
386 this.addCopy(menu, menuInfo); 383 this.addCopy(menu, menuInfo);
387 this.addInspectElement(menu, menuInfo);
388 // @ts-expect-error Expected 1 arguments, but got 2.
389 this.processMenu(menu, menuInfo);
390 384
385 this.addInspectElement(menu, menuInfo);
386 this.processMenu(menu);
391 this.addSeparator(menu); 387 this.addSeparator(menu);
392 this.goBack(menu); 388 this.goBack(menu);
393 this.goForward(menu); 389 this.goForward(menu);
390 this.addSeparator(menu);
394 this.copyPageUrl(menu, menuInfo); 391 this.copyPageUrl(menu, menuInfo);
395 this.goToHomePage(menu, menuInfo); 392 this.addSeparator(menu);
396 this.openInBrowser(menu, menuInfo); 393 this.openInBrowser(menu, menuInfo);
394 this.openInFerdium(menu, menuInfo);
395 this.addSeparator(menu);
396 this.goToHomePage(menu, menuInfo);
397 397
398 return menu; 398 return menu;
399 } 399 }
@@ -412,8 +412,7 @@ export class ContextMenuBuilder {
412 this.addImageItems(menu, menuInfo); 412 this.addImageItems(menu, menuInfo);
413 } 413 }
414 this.addInspectElement(menu, menuInfo); 414 this.addInspectElement(menu, menuInfo);
415 // @ts-expect-error Expected 1 arguments, but got 2. 415 this.processMenu(menu);
416 this.processMenu(menu, menuInfo);
417 416
418 return menu; 417 return menu;
419 } 418 }
@@ -970,6 +969,26 @@ export class ContextMenuBuilder {
970 return menu; 969 return menu;
971 } 970 }
972 971
972 /**
973 * Adds the 'open in ferdium' menu item.
974 */
975 openInFerdium(
976 menu: Electron.CrossProcessExports.Menu,
977 menuInfo: IContextMenuParams,
978 ) {
979 menu.append(
980 new MenuItem({
981 label: this.stringTable.openInFerdium(),
982 enabled: true,
983 click: () => {
984 window.location.href = menuInfo.linkURL;
985 },
986 }),
987 );
988
989 return menu;
990 }
991
973 _sendNotificationOnClipboardEvent( 992 _sendNotificationOnClipboardEvent(
974 isDisabled: boolean, 993 isDisabled: boolean,
975 notificationText: () => string, 994 notificationText: () => string,
diff --git a/src/webview/notifications.ts b/src/webview/notifications.ts
index e4401ab6e..0da86fbba 100644
--- a/src/webview/notifications.ts
+++ b/src/webview/notifications.ts
@@ -31,52 +31,51 @@ export class NotificationsHandler {
31} 31}
32 32
33export const notificationsClassDefinition = `(() => { 33export const notificationsClassDefinition = `(() => {
34 class Notification { 34class Notification {
35 static permission = 'granted'; 35 static permission = 'granted';
36
37 constructor(title = '', options = {}) {
38 this.title = title;
39 this.options = options;
40 try {
41 window.ferdium.displayNotification(title, options)
42 .then(() => {
43 if (typeof (this.onClick) === 'function') {
44 this.onClick();
45 }
46 });
47 } catch(error) {
48 this.options.onClick = null;
49 window.ferdium.displayNotification(title, options)
50 .then(() => {
51 if (typeof (this.onClick) === 'function') {
52 this.onClick();
53 }
54 });
55 }
56 }
57 36
58 static requestPermission(cb = null) { 37 static _notification;
59 if (!cb) {
60 return new Promise((resolve) => {
61 resolve(Notification.permission);
62 });
63 }
64 38
65 if (typeof (cb) === 'function') { 39 constructor(title = '', options = {}) {
66 return cb(Notification.permission); 40 Notification._displayNotification(title, options);
67 } 41 }
68 42
69 return Notification.permission; 43 static _displayNotification(title, options) {
70 } 44 Notification._notification = window.ferdium
45 .displayNotification(title, options)
46 .then(() => {
47 // TODO: After several tries, we couldn't find a way to trigger the native notification onclick event.
48 // This was needed so that user could go to the specific context when clicking on the notification (it only goes to the service now).
49 // For now, we don't do anything here
50 });
51 }
71 52
72 onNotify(data) { 53 static requestPermission(cb) {
73 return data; 54 if (typeof cb === 'function') {
55 cb(Notification.permission);
74 } 56 }
75 57
76 onClick() {} 58 return Promise.resolve(Notification.permission);
59 }
60
61 onNotify(data) {
62 return data;
63 }
77 64
78 close() {} 65 close() {
66 if (Notification._notification) {
67 Notification._notification = null;
68 }
79 } 69 }
80 70
71 onclick() {}
72
73 onclose() {}
74
75 onerror() {}
76
77 onshow() {}
78}
79
81 window.Notification = Notification; 80 window.Notification = Notification;
82})();`; 81})();`;
diff --git a/src/webview/recipe.ts b/src/webview/recipe.ts
index d6db39779..ad2215ffd 100644
--- a/src/webview/recipe.ts
+++ b/src/webview/recipe.ts
@@ -32,11 +32,7 @@ import {
32 NotificationsHandler, 32 NotificationsHandler,
33 notificationsClassDefinition, 33 notificationsClassDefinition,
34} from './notifications'; 34} from './notifications';
35import { 35import { getDisplayMediaSelector, screenShareJs } from './screenshare';
36 getDisplayMediaSelector,
37 screenShareCss,
38 screenShareJs,
39} from './screenshare';
40import SessionHandler from './sessionHandler'; 36import SessionHandler from './sessionHandler';
41import { 37import {
42 getSpellcheckerLocaleByFuzzyIdentifier, 38 getSpellcheckerLocaleByFuzzyIdentifier,
@@ -125,7 +121,7 @@ contextBridge.exposeInMainWorld('ferdium', {
125 setDialogTitle: (title: string | null | undefined) => 121 setDialogTitle: (title: string | null | undefined) =>
126 dialogTitleHandler.setDialogTitle(title), 122 dialogTitleHandler.setDialogTitle(title),
127 displayNotification: (title: string, options: any) => { 123 displayNotification: (title: string, options: any) => {
128 notificationsHandler.displayNotification( 124 return notificationsHandler.displayNotification(
129 title, 125 title,
130 // The following line is needed so that a proper clone of the "options" object is made. 126 // The following line is needed so that a proper clone of the "options" object is made.
131 // This line was causing issues with some services. 127 // This line was causing issues with some services.
@@ -267,7 +263,6 @@ class RecipeController {
267 263
268 async loadUserFiles(recipe, config) { 264 async loadUserFiles(recipe, config) {
269 const styles = document.createElement('style'); 265 const styles = document.createElement('style');
270 styles.innerHTML = screenShareCss;
271 266
272 const userCss = join(recipe.path, 'user.css'); 267 const userCss = join(recipe.path, 'user.css');
273 if (pathExistsSync(userCss)) { 268 if (pathExistsSync(userCss)) {
diff --git a/src/webview/screenshare.ts b/src/webview/screenshare.ts
index e631ce52f..521f95bd6 100644
--- a/src/webview/screenshare.ts
+++ b/src/webview/screenshare.ts
@@ -1,139 +1,41 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer } from 'electron';
2import { v4 as uuidV4 } from 'uuid';
2 3
3const CANCEL_ID = 'desktop-capturer-selection__cancel'; 4const debug = require('../preload-safe-debug')('Ferdium:Screenshare');
4 5
5export async function getDisplayMediaSelector() { 6export async function getDisplayMediaSelector() {
6 const sources = await ipcRenderer.invoke('get-desktop-capturer-sources'); 7 return new Promise((resolve, reject) => {
7 return `<div class="desktop-capturer-selection__scroller"> 8 const trackerId = uuidV4();
8 <ul class="desktop-capturer-selection__list"> 9 debug('New screenshare request', trackerId);
9 ${sources
10 .map(
11 ({ id, name, thumbnail }) => `
12 <li class="desktop-capturer-selection__item">
13 <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
14 <img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
15 <span class="desktop-capturer-selection__name">${name}</span>
16 </button>
17 </li>
18 `,
19 )
20 .join('')}
21 <li class="desktop-capturer-selection__item">
22 <button class="desktop-capturer-selection__btn" data-id="${CANCEL_ID}" title="Cancel">
23 <span class="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">Cancel</span>
24 </button>
25 </li>
26 </ul>
27</div>`;
28}
29 10
30export const screenShareCss = ` 11 ipcRenderer.sendToHost('load-available-displays', {
31.desktop-capturer-selection { 12 trackerId,
32 position: fixed; 13 });
33 top: 0; 14
34 left: 0; 15 ipcRenderer.once(`selected-media-source:${trackerId}`, (_e, data) => {
35 width: 100%; 16 if (data.mediaSourceId === 'desktop-capturer-selection__cancel') {
36 height: 100vh; 17 return reject(new Error('Cancelled by user'));
37 background: rgba(30,30,30,.75); 18 }
38 color: #fff; 19
39 z-index: 10000000; 20 return resolve(data.mediaSourceId);
40 display: flex; 21 });
41 align-items: center; 22 });
42 justify-content: center;
43}
44.desktop-capturer-selection__scroller {
45 width: 100%;
46 max-height: 100vh;
47 overflow-y: auto;
48}
49.desktop-capturer-selection__list {
50 max-width: calc(100% - 100px);
51 margin: 50px;
52 padding: 0;
53 display: flex;
54 flex-wrap: wrap;
55 list-style: none;
56 overflow: hidden;
57 justify-content: center;
58}
59.desktop-capturer-selection__item {
60 display: flex;
61 margin: 4px;
62}
63.desktop-capturer-selection__btn {
64 display: flex;
65 flex-direction: column;
66 align-items: stretch;
67 width: 145px;
68 margin: 0;
69 border: 0;
70 border-radius: 3px;
71 padding: 4px;
72 background: #252626;
73 text-align: left;
74 @media (prefers-reduced-motion: no-preference) {
75 transition: background-color .15s, box-shadow .15s, color .15s;
76 }
77 color: #dedede;
78}
79.desktop-capturer-selection__btn:hover,
80.desktop-capturer-selection__btn:focus {
81 background: rgba(98,100,167,.8);
82 box-shadow: 0 0 4px rgba(0,0,0,0.45), 0 0 2px rgba(0,0,0,0.25);
83 color: #fff;
84}
85.desktop-capturer-selection__thumbnail {
86 width: 100%;
87 height: 81px;
88 object-fit: cover;
89}
90.desktop-capturer-selection__name {
91 margin: 6px 0;
92 white-space: nowrap;
93 text-overflow: ellipsis;
94 text-align: center;
95 overflow: hidden;
96}
97.desktop-capturer-selection__name--cancel {
98 margin: auto 0;
99} 23}
100`;
101 24
102export const screenShareJs = ` 25export const screenShareJs = `
103window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => { 26window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => {
104 try { 27 try {
105 const selectionElem = document.createElement('div'); 28 const displayId = await window.ferdium.getDisplayMediaSelector();
106 selectionElem.classList = ['desktop-capturer-selection']; 29 const stream = await window.navigator.mediaDevices.getUserMedia({
107 selectionElem.innerHTML = await window.ferdium.getDisplayMediaSelector(); 30 audio: false,
108 document.body.appendChild(selectionElem); 31 video: {
109 32 mandatory: {
110 document 33 chromeMediaSource: 'desktop',
111 .querySelectorAll('.desktop-capturer-selection__btn') 34 chromeMediaSourceId: displayId,
112 .forEach((button) => { 35 },
113 button.addEventListener('click', async () => { 36 },
114 try { 37 });
115 const id = button.getAttribute('data-id'); 38 resolve(stream);
116 if (id === '${CANCEL_ID}') {
117 reject(new Error('Cancelled by user'));
118 } else {
119 const stream = await window.navigator.mediaDevices.getUserMedia({
120 audio: false,
121 video: {
122 mandatory: {
123 chromeMediaSource: 'desktop',
124 chromeMediaSourceId: id,
125 },
126 },
127 });
128 resolve(stream);
129 }
130 } catch (err) {
131 reject(err);
132 } finally {
133 selectionElem.remove();
134 }
135 });
136 });
137 } catch (err) { 39 } catch (err) {
138 reject(err); 40 reject(err);
139 } 41 }
diff --git a/test/jsUtils.test.ts b/test/jsUtils.test.ts
index 508f5dd9f..b40b21b2a 100644
--- a/test/jsUtils.test.ts
+++ b/test/jsUtils.test.ts
@@ -180,23 +180,59 @@ describe('jsUtils', () => {
180 180
181 describe('acceleratorString', () => { 181 describe('acceleratorString', () => {
182 it('handles without prefix and suffix', () => { 182 it('handles without prefix and suffix', () => {
183 expect(jsUtils.acceleratorString(5, 'abc')).toEqual('(abc+5)'); 183 expect(
184 jsUtils.acceleratorString({
185 index: 5,
186 keyCombo: 'abc',
187 }),
188 ).toEqual('(abc+5)');
184 }); 189 });
185 190
186 it('handles index = 0', () => { 191 it('handles index = 0', () => {
187 expect(jsUtils.acceleratorString(0, 'abc')).toEqual('(abc+0)'); 192 expect(
193 jsUtils.acceleratorString({
194 index: 0,
195 keyCombo: 'abc',
196 }),
197 ).toEqual('(abc+0)');
188 }); 198 });
189 199
190 it('handles index = 1', () => { 200 it('handles index = 1', () => {
191 expect(jsUtils.acceleratorString(1, 'abc')).toEqual('(abc+1)'); 201 expect(
202 jsUtils.acceleratorString({
203 index: 1,
204 keyCombo: 'abc',
205 }),
206 ).toEqual('(abc+1)');
192 }); 207 });
193 208
194 it('handles index = 10', () => { 209 it('handles index = 10', () => {
195 expect(jsUtils.acceleratorString(10, 'abc')).toEqual('(abc+0)'); 210 expect(
211 jsUtils.acceleratorString({
212 index: 10,
213 keyCombo: 'abc',
214 maxIndex: 10,
215 }),
216 ).toEqual('(abc+0)');
196 }); 217 });
197 218
198 it('handles index = 11', () => { 219 it('handles index = 11', () => {
199 expect(jsUtils.acceleratorString(11, 'abc')).toEqual(''); 220 expect(
221 jsUtils.acceleratorString({
222 index: 11,
223 keyCombo: 'abc',
224 }),
225 ).toEqual('');
226 });
227
228 it('handles index = 9, maxIndex = 8', () => {
229 expect(
230 jsUtils.acceleratorString({
231 index: 9,
232 maxIndex: 8,
233 keyCombo: 'abc',
234 }),
235 ).toEqual('');
200 }); 236 });
201 }); 237 });
202 238