diff options
34 files changed, 572 insertions, 119 deletions
diff --git a/.all-contributorsrc b/.all-contributorsrc index b897eabe2..6013e64ad 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc | |||
@@ -159,6 +159,18 @@ | |||
159 | "bug", | 159 | "bug", |
160 | "code" | 160 | "code" |
161 | ] | 161 | ] |
162 | }, | ||
163 | { | ||
164 | "login": "xthursdayx", | ||
165 | "name": "xthursdayx", | ||
166 | "avatar_url": "https://avatars0.githubusercontent.com/u/18044308?v=4", | ||
167 | "profile": "https://github.com/xthursdayx", | ||
168 | "contributions": [ | ||
169 | "code", | ||
170 | "doc", | ||
171 | "infra", | ||
172 | "platform" | ||
173 | ] | ||
162 | } | 174 | } |
163 | ], | 175 | ], |
164 | "contributorsPerLine": 6 | 176 | "contributorsPerLine": 6 |
diff --git a/.travis.yml b/.travis.yml index 199938fba..124a6fc8a 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,16 +1,17 @@ | |||
1 | matrix: | 1 | matrix: |
2 | include: | 2 | include: |
3 | - os: linux | 3 | - os: linux |
4 | dist: xenial | 4 | dist: xenial |
5 | addons: | 5 | addons: |
6 | apt: | 6 | apt: |
7 | packages: | 7 | packages: |
8 | - libx11-dev | 8 | - libx11-dev |
9 | - libxext-dev | 9 | - libxext-dev |
10 | - libxss-dev | 10 | - libxss-dev |
11 | - libxkbfile-dev | 11 | - libxkbfile-dev |
12 | - os: osx | 12 | - os: osx |
13 | osx_image: xcode10.3 | 13 | osx_image: xcode11 |
14 | |||
14 | language: node_js | 15 | language: node_js |
15 | install: | 16 | install: |
16 | - echo do nothing | 17 | - echo do nothing |
@@ -25,3 +26,4 @@ cache: npm | |||
25 | branches: | 26 | branches: |
26 | except: | 27 | except: |
27 | - i18n | 28 | - i18n |
29 | - l10n_master | ||
@@ -103,7 +103,7 @@ $ git tag v5.3.4-beta.4 | |||
103 | $ git push --tags | 103 | $ git push --tags |
104 | ``` | 104 | ``` |
105 | 105 | ||
106 | When pushing a new tag, the CI build will create a draft GitHub release and upload the deliverables in the release assets. | 106 | When pushing a new tag, the CI build will create a draft GitHub release and upload the deliverables in the draft release assets. Wait for all the assets to be uploaded before publishing the draft release. |
107 | 107 | ||
108 | ## Contributors ✨ | 108 | ## Contributors ✨ |
109 | 109 | ||
@@ -131,6 +131,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d | |||
131 | </tr> | 131 | </tr> |
132 | <tr> | 132 | <tr> |
133 | <td align="center"><a href="https://github.com/Makazzz"><img src="https://avatars2.githubusercontent.com/u/49844464?v=4" width="40px;" alt="Makazzz"/><br /><sub><b>Makazzz</b></sub></a><br /><a href="https://github.com/kytwb/ferdi/issues?q=author%3AMakazzz" title="Bug reports">🐛</a> <a href="https://github.com/kytwb/ferdi/commits?author=Makazzz" title="Code">💻</a></td> | 133 | <td align="center"><a href="https://github.com/Makazzz"><img src="https://avatars2.githubusercontent.com/u/49844464?v=4" width="40px;" alt="Makazzz"/><br /><sub><b>Makazzz</b></sub></a><br /><a href="https://github.com/kytwb/ferdi/issues?q=author%3AMakazzz" title="Bug reports">🐛</a> <a href="https://github.com/kytwb/ferdi/commits?author=Makazzz" title="Code">💻</a></td> |
134 | <td align="center"><a href="https://github.com/xthursdayx"><img src="https://avatars0.githubusercontent.com/u/18044308?v=4" width="40px;" alt="xthursdayx"/><br /><sub><b>xthursdayx</b></sub></a><br /><a href="https://github.com/kytwb/ferdi/commits?author=xthursdayx" title="Code">💻</a> <a href="https://github.com/kytwb/ferdi/commits?author=xthursdayx" title="Documentation">📖</a> <a href="#infra-xthursdayx" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#platform-xthursdayx" title="Packaging/porting to new platform">📦</a></td> | ||
134 | </tr> | 135 | </tr> |
135 | </table> | 136 | </table> |
136 | 137 | ||
diff --git a/appveyor.yml b/appveyor.yml index 157e35df3..39d7f793a 100644 --- a/appveyor.yml +++ b/appveyor.yml | |||
@@ -25,3 +25,4 @@ artifacts: | |||
25 | branches: | 25 | branches: |
26 | except: | 26 | except: |
27 | - i18n | 27 | - i18n |
28 | - l10n_master | ||
diff --git a/branding/social-preview.psd b/branding/social-preview.psd new file mode 100644 index 000000000..891ccdc39 --- /dev/null +++ b/branding/social-preview.psd | |||
Binary files differ | |||
diff --git a/electron-builder.yml b/electron-builder.yml index e95eb47b7..b2c243e49 100644 --- a/electron-builder.yml +++ b/electron-builder.yml | |||
@@ -27,10 +27,10 @@ dmg: | |||
27 | win: | 27 | win: |
28 | icon: ./build-helpers/images/icon.ico | 28 | icon: ./build-helpers/images/icon.ico |
29 | target: | 29 | target: |
30 | target: nsis | 30 | - target: nsis |
31 | arch: | 31 | arch: [x64, ia32] |
32 | - x64 | 32 | - target: portable |
33 | - ia32 | 33 | arch: [x64, ia32] |
34 | 34 | ||
35 | linux: | 35 | linux: |
36 | icon: ./build-helpers/images/icons | 36 | icon: ./build-helpers/images/icons |
diff --git a/package-lock.json b/package-lock.json index 70c791007..b2622361b 100644 --- a/package-lock.json +++ b/package-lock.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "name": "ferdi", | 2 | "name": "ferdi", |
3 | "version": "5.3.4-beta.3", | 3 | "version": "5.3.4-beta.7", |
4 | "lockfileVersion": 1, | 4 | "lockfileVersion": 1, |
5 | "requires": true, | 5 | "requires": true, |
6 | "dependencies": { | 6 | "dependencies": { |
@@ -7382,21 +7382,11 @@ | |||
7382 | } | 7382 | } |
7383 | }, | 7383 | }, |
7384 | "electron-hunspell": { | 7384 | "electron-hunspell": { |
7385 | "version": "0.1.1", | 7385 | "version": "1.0.0", |
7386 | "resolved": "https://registry.npmjs.org/electron-hunspell/-/electron-hunspell-0.1.1.tgz", | 7386 | "resolved": "https://registry.npmjs.org/electron-hunspell/-/electron-hunspell-1.0.0.tgz", |
7387 | "integrity": "sha512-B3nOQqHexIX+8bz72FZkNk+iFBrdqS9DpV2SaH+t7T9SLbONBVBRLJ2Jj2ytXFUzvw81q7vz2dfxPCddh/E3ww==", | 7387 | "integrity": "sha512-egyioCtGkkNMOYdKBrZsva63JxbPmNjmQVFCZCcmr+uIUs6et6OUvqd6ac9/ujuchiEPDrTfF9gdR9+lPbVyPA==", |
7388 | "requires": { | 7388 | "requires": { |
7389 | "hunspell-asm": "1.0.2", | 7389 | "hunspell-asm": "^4.0.0" |
7390 | "lodash": "^4.17.11", | ||
7391 | "tslib": "1.9.3", | ||
7392 | "unixify": "1.0.0" | ||
7393 | }, | ||
7394 | "dependencies": { | ||
7395 | "tslib": { | ||
7396 | "version": "1.9.3", | ||
7397 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", | ||
7398 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" | ||
7399 | } | ||
7400 | } | 7390 | } |
7401 | }, | 7391 | }, |
7402 | "electron-is-dev": { | 7392 | "electron-is-dev": { |
@@ -12653,14 +12643,24 @@ | |||
12653 | } | 12643 | } |
12654 | }, | 12644 | }, |
12655 | "hunspell-asm": { | 12645 | "hunspell-asm": { |
12656 | "version": "1.0.2", | 12646 | "version": "4.0.0", |
12657 | "resolved": "https://registry.npmjs.org/hunspell-asm/-/hunspell-asm-1.0.2.tgz", | 12647 | "resolved": "https://registry.npmjs.org/hunspell-asm/-/hunspell-asm-4.0.0.tgz", |
12658 | "integrity": "sha512-UTLBvc0yZiIcHl9qrgxnFTZbX3zF4CprzEY+u+N0iXlUKZnUJRIgvgppTdgiQTsucm5b0aN/rHsgXz2q/0kBRA==", | 12648 | "integrity": "sha512-EcNwMx0Byq1JHMZiuATmqpMk41bOo+NH4yD5xJ3H0X403MoDBory3zri2lJzYSGj+z/6Bqr3EZn24syCjZoY1w==", |
12659 | "requires": { | 12649 | "requires": { |
12660 | "emscripten-wasm-loader": "^1.0.0", | 12650 | "emscripten-wasm-loader": "^3.0.3", |
12661 | "nanoid": "^1.0.2", | 12651 | "nanoid": "^2.1.1" |
12662 | "tslib": "^1.9.0", | 12652 | }, |
12663 | "unixify": "^1.0.0" | 12653 | "dependencies": { |
12654 | "emscripten-wasm-loader": { | ||
12655 | "version": "3.0.3", | ||
12656 | "resolved": "https://registry.npmjs.org/emscripten-wasm-loader/-/emscripten-wasm-loader-3.0.3.tgz", | ||
12657 | "integrity": "sha512-fyq2maBt5LOou27LEBlL5H6G04BxgSamXkvmMsAuIT6rd8ioH4BxNQhuyl6jVPeODh6U8Wk1BoFZxzHpg3o8wA==", | ||
12658 | "requires": { | ||
12659 | "getroot": "^1.0.0", | ||
12660 | "nanoid": "^2.0.3", | ||
12661 | "unixify": "^1.0.0" | ||
12662 | } | ||
12663 | } | ||
12664 | } | 12664 | } |
12665 | }, | 12665 | }, |
12666 | "husky": { | 12666 | "husky": { |
@@ -15850,9 +15850,9 @@ | |||
15850 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" | 15850 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" |
15851 | }, | 15851 | }, |
15852 | "nanoid": { | 15852 | "nanoid": { |
15853 | "version": "1.3.4", | 15853 | "version": "2.1.2", |
15854 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.4.tgz", | 15854 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.2.tgz", |
15855 | "integrity": "sha512-4ug4BsuHxiVHoRUe1ud6rUFT3WUMmjXt1W0quL0CviZQANdan7D8kqN5/maw53hmAApY/jfzMRkC57BNNs60ZQ==" | 15855 | "integrity": "sha512-q0iKJHcLc9rZg/qtJ/ioG5s6/5357bqvkYCpqXJxpcyfK7L5us8+uJllZosqPWou7l6E1lY2Qqoq5ce+AMbFuQ==" |
15856 | }, | 15856 | }, |
15857 | "nanomatch": { | 15857 | "nanomatch": { |
15858 | "version": "1.2.13", | 15858 | "version": "1.2.13", |
diff --git a/package.json b/package.json index 1a24c54fb..5b0a0bcdb 100644 --- a/package.json +++ b/package.json | |||
@@ -2,7 +2,7 @@ | |||
2 | "name": "ferdi", | 2 | "name": "ferdi", |
3 | "productName": "Ferdi", | 3 | "productName": "Ferdi", |
4 | "appId": "com.kytwb.ferdi", | 4 | "appId": "com.kytwb.ferdi", |
5 | "version": "5.3.4-beta.5", | 5 | "version": "5.3.4-beta.7", |
6 | "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.", | 6 | "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.", |
7 | "copyright": "kytwb", | 7 | "copyright": "kytwb", |
8 | "main": "index.js", | 8 | "main": "index.js", |
@@ -51,7 +51,7 @@ | |||
51 | "du": "^0.1.0", | 51 | "du": "^0.1.0", |
52 | "electron-dl": "1.14.0", | 52 | "electron-dl": "1.14.0", |
53 | "electron-fetch": "1.3.0", | 53 | "electron-fetch": "1.3.0", |
54 | "electron-hunspell": "0.1.1", | 54 | "electron-hunspell": "1.0.0", |
55 | "electron-is-dev": "1.1.0", | 55 | "electron-is-dev": "1.1.0", |
56 | "electron-react-titlebar": "0.8.1", | 56 | "electron-react-titlebar": "0.8.1", |
57 | "electron-updater": "4.1.2", | 57 | "electron-updater": "4.1.2", |
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index c4abc00e9..2d9af416f 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js | |||
@@ -45,13 +45,13 @@ export default class LocalApi { | |||
45 | const s = session.fromPartition(`persist:service-${serviceId}`); | 45 | const s = session.fromPartition(`persist:service-${serviceId}`); |
46 | 46 | ||
47 | debug('LocalApi::clearCache resolves', serviceId); | 47 | debug('LocalApi::clearCache resolves', serviceId); |
48 | return new Promise(resolve => s.clearCache(resolve)); | 48 | return s.clearCache(); |
49 | } | 49 | } |
50 | 50 | ||
51 | async clearAppCache() { | 51 | async clearAppCache() { |
52 | const s = session.defaultSession; | 52 | const s = session.defaultSession; |
53 | 53 | ||
54 | debug('LocalApi::clearCache clearAppCache'); | 54 | debug('LocalApi::clearCache clearAppCache'); |
55 | return new Promise(resolve => s.clearCache(resolve)); | 55 | return s.clearCache(); |
56 | } | 56 | } |
57 | } | 57 | } |
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 1d6bfee33..d0cae3443 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -118,12 +118,21 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
118 | type="button" | 118 | type="button" |
119 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} | 119 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} |
120 | onClick={() => { | 120 | onClick={() => { |
121 | // Disable lock first - otherwise the application might not update correctly | ||
121 | actions.settings.update({ | 122 | actions.settings.update({ |
122 | type: 'app', | 123 | type: 'app', |
123 | data: { | 124 | data: { |
124 | locked: true, | 125 | locked: false, |
125 | }, | 126 | }, |
126 | }); | 127 | }); |
128 | setTimeout(() => { | ||
129 | actions.settings.update({ | ||
130 | type: 'app', | ||
131 | data: { | ||
132 | locked: true, | ||
133 | }, | ||
134 | }); | ||
135 | }, 0); | ||
127 | }} | 136 | }} |
128 | data-tip={`${intl.formatMessage(messages.lockFerdi)} (${ctrlKey}+Shift+L)`} | 137 | data-tip={`${intl.formatMessage(messages.lockFerdi)} (${ctrlKey}+Shift+L)`} |
129 | > | 138 | > |
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index c45acc961..0cfefc92b 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js | |||
@@ -13,6 +13,7 @@ import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | |||
13 | import ServiceDisabled from './ServiceDisabled'; | 13 | import ServiceDisabled from './ServiceDisabled'; |
14 | import ServiceWebview from './ServiceWebview'; | 14 | import ServiceWebview from './ServiceWebview'; |
15 | import SettingsStore from '../../../stores/SettingsStore'; | 15 | import SettingsStore from '../../../stores/SettingsStore'; |
16 | import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; | ||
16 | 17 | ||
17 | export default @observer @inject('stores') class ServiceView extends Component { | 18 | export default @observer @inject('stores') class ServiceView extends Component { |
18 | static propTypes = { | 19 | static propTypes = { |
@@ -177,6 +178,9 @@ export default @observer @inject('stores') class ServiceView extends Component { | |||
177 | </Fragment> | 178 | </Fragment> |
178 | ) : ( | 179 | ) : ( |
179 | <> | 180 | <> |
181 | {service.recipe.id === 'franz-custom-website' && ( | ||
182 | <WebControlsScreen service={service} /> | ||
183 | )} | ||
180 | {!this.state.hibernate ? ( | 184 | {!this.state.hibernate ? ( |
181 | <ServiceWebview | 185 | <ServiceWebview |
182 | service={service} | 186 | service={service} |
@@ -187,7 +191,7 @@ export default @observer @inject('stores') class ServiceView extends Component { | |||
187 | <div> | 191 | <div> |
188 | <span role="img" aria-label="Sleeping Emoji">😴</span> | 192 | <span role="img" aria-label="Sleeping Emoji">😴</span> |
189 | {' '} | 193 | {' '} |
190 | This service is currently hibernating. If this page doesn't close soon, please try reloading Ferdi. | 194 | This service is currently hibernating. If this page doesn't close soon, please try reloading Ferdi. |
191 | </div> | 195 | </div> |
192 | )} | 196 | )} |
193 | </> | 197 | </> |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 75b3d2cf0..03d6d5bcc 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -24,7 +24,7 @@ class ServiceWebview extends Component { | |||
24 | reaction( | 24 | reaction( |
25 | () => this.webview, | 25 | () => this.webview, |
26 | () => { | 26 | () => { |
27 | if (this.webview.view) { | 27 | if (this.webview && this.webview.view) { |
28 | this.webview.view.addEventListener('console-message', (e) => { | 28 | this.webview.view.addEventListener('console-message', (e) => { |
29 | debug('Service logged a message:', e.message); | 29 | debug('Service logged a message:', e.message); |
30 | }); | 30 | }); |
@@ -59,6 +59,7 @@ class ServiceWebview extends Component { | |||
59 | }} | 59 | }} |
60 | onUpdateTargetUrl={this.updateTargetUrl} | 60 | onUpdateTargetUrl={this.updateTargetUrl} |
61 | useragent={service.userAgent} | 61 | useragent={service.userAgent} |
62 | disablewebsecurity={service.recipe.disablewebsecurity} | ||
62 | allowpopups | 63 | allowpopups |
63 | /> | 64 | /> |
64 | ); | 65 | ); |
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 1d2383125..75f0d9d23 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js | |||
@@ -146,6 +146,7 @@ export default @observer class EditSettingsForm extends Component { | |||
146 | isTodosEnabled: PropTypes.bool.isRequired, | 146 | isTodosEnabled: PropTypes.bool.isRequired, |
147 | isWorkspaceEnabled: PropTypes.bool.isRequired, | 147 | isWorkspaceEnabled: PropTypes.bool.isRequired, |
148 | server: PropTypes.string.isRequired, | 148 | server: PropTypes.string.isRequired, |
149 | noUpdates: PropTypes.bool.isRequired, | ||
149 | }; | 150 | }; |
150 | 151 | ||
151 | static contextTypes = { | 152 | static contextTypes = { |
@@ -179,6 +180,7 @@ export default @observer class EditSettingsForm extends Component { | |||
179 | isTodosEnabled, | 180 | isTodosEnabled, |
180 | isWorkspaceEnabled, | 181 | isWorkspaceEnabled, |
181 | server, | 182 | server, |
183 | noUpdates, | ||
182 | } = this.props; | 184 | } = this.props; |
183 | const { intl } = this.context; | 185 | const { intl } = this.context; |
184 | 186 | ||
@@ -412,7 +414,7 @@ export default @observer class EditSettingsForm extends Component { | |||
412 | buttonType="secondary" | 414 | buttonType="secondary" |
413 | label={intl.formatMessage(updateButtonLabelMessage)} | 415 | label={intl.formatMessage(updateButtonLabelMessage)} |
414 | onClick={checkForUpdates} | 416 | onClick={checkForUpdates} |
415 | disabled={isCheckingForUpdates || isUpdateAvailable} | 417 | disabled={noUpdates || isCheckingForUpdates || isUpdateAvailable} |
416 | loaded={!isCheckingForUpdates || !isUpdateAvailable} | 418 | loaded={!isCheckingForUpdates || !isUpdateAvailable} |
417 | /> | 419 | /> |
418 | )} | 420 | )} |
@@ -421,6 +423,7 @@ export default @observer class EditSettingsForm extends Component { | |||
421 | )} | 423 | )} |
422 | <br /> | 424 | <br /> |
423 | <Toggle field={form.$('beta')} /> | 425 | <Toggle field={form.$('beta')} /> |
426 | <Toggle field={form.$('noUpdates')} /> | ||
424 | {intl.formatMessage(messages.currentVersion)} | 427 | {intl.formatMessage(messages.currentVersion)} |
425 | {' '} | 428 | {' '} |
426 | {remote.app.getVersion()} | 429 | {remote.app.getVersion()} |
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js index a9a87707b..1fd247d17 100644 --- a/src/components/ui/AppLoader/index.js +++ b/src/components/ui/AppLoader/index.js | |||
@@ -9,14 +9,13 @@ import { shuffleArray } from '../../../helpers/array-helpers'; | |||
9 | import styles from './styles'; | 9 | import styles from './styles'; |
10 | 10 | ||
11 | const textList = shuffleArray([ | 11 | const textList = shuffleArray([ |
12 | 'Looking for Sisi', | 12 | 'Adding free features', |
13 | 'Contacting the herald', | 13 | 'Making application usable', |
14 | 'Saddling the unicorn', | 14 | 'Removing unproductive paywalls', |
15 | 'Learning the Waltz', | 15 | 'Creating custom server software', |
16 | 'Visiting Horst & Grete', | 16 | 'Increasing productivity', |
17 | 'Twisting my moustache', | 17 | 'Listening to our userbase', |
18 | 'Playing the trumpet', | 18 | 'Fixing bugs', |
19 | 'Traveling through space & time', | ||
20 | ]); | 19 | ]); |
21 | 20 | ||
22 | export default @injectSheet(styles) @withTheme class AppLoader extends Component { | 21 | export default @injectSheet(styles) @withTheme class AppLoader extends Component { |
diff --git a/src/config.js b/src/config.js index ea36be1f9..c6c31ce23 100644 --- a/src/config.js +++ b/src/config.js | |||
@@ -63,6 +63,7 @@ export const DEFAULT_APP_SETTINGS = { | |||
63 | scheduledDNDStart: '17:00', | 63 | scheduledDNDStart: '17:00', |
64 | scheduledDNDEnd: '09:00', | 64 | scheduledDNDEnd: '09:00', |
65 | hibernate: false, | 65 | hibernate: false, |
66 | noUpdates: false, | ||
66 | }; | 67 | }; |
67 | 68 | ||
68 | export const DEFAULT_FEATURES_CONFIG = { | 69 | export const DEFAULT_FEATURES_CONFIG = { |
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js index 962dc1b65..463a290d2 100644 --- a/src/containers/settings/EditSettingsScreen.js +++ b/src/containers/settings/EditSettingsScreen.js | |||
@@ -109,6 +109,10 @@ const messages = defineMessages({ | |||
109 | id: 'settings.app.form.beta', | 109 | id: 'settings.app.form.beta', |
110 | defaultMessage: '!!!Include beta versions', | 110 | defaultMessage: '!!!Include beta versions', |
111 | }, | 111 | }, |
112 | noUpdates: { | ||
113 | id: 'settings.app.form.noUpdates', | ||
114 | defaultMessage: '!!!Disable updates', | ||
115 | }, | ||
112 | enableTodos: { | 116 | enableTodos: { |
113 | id: 'settings.app.form.enableTodos', | 117 | id: 'settings.app.form.enableTodos', |
114 | defaultMessage: '!!!Enable Franz Todos', | 118 | defaultMessage: '!!!Enable Franz Todos', |
@@ -161,12 +165,14 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e | |||
161 | enableSpellchecking: settingsData.enableSpellchecking, | 165 | enableSpellchecking: settingsData.enableSpellchecking, |
162 | spellcheckerLanguage: settingsData.spellcheckerLanguage, | 166 | spellcheckerLanguage: settingsData.spellcheckerLanguage, |
163 | beta: settingsData.beta, // we need this info in the main process as well | 167 | beta: settingsData.beta, // we need this info in the main process as well |
168 | noUpdates: settingsData.noUpdates, // we need this info in the main process as well | ||
164 | locale: settingsData.locale, // we need this info in the main process as well | 169 | locale: settingsData.locale, // we need this info in the main process as well |
165 | }, | 170 | }, |
166 | }); | 171 | }); |
167 | 172 | ||
168 | user.update({ | 173 | user.update({ |
169 | userData: { | 174 | userData: { |
175 | noUpdates: settingsData.noUpdates, | ||
170 | beta: settingsData.beta, | 176 | beta: settingsData.beta, |
171 | locale: settingsData.locale, | 177 | locale: settingsData.locale, |
172 | }, | 178 | }, |
@@ -319,6 +325,11 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e | |||
319 | value: user.data.beta, | 325 | value: user.data.beta, |
320 | default: DEFAULT_APP_SETTINGS.beta, | 326 | default: DEFAULT_APP_SETTINGS.beta, |
321 | }, | 327 | }, |
328 | noUpdates: { | ||
329 | label: intl.formatMessage(messages.noUpdates), | ||
330 | value: settings.app.noUpdates, | ||
331 | default: DEFAULT_APP_SETTINGS.noUpdates, | ||
332 | }, | ||
322 | }, | 333 | }, |
323 | }; | 334 | }; |
324 | 335 | ||
@@ -381,6 +392,7 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e | |||
381 | isWorkspaceEnabled={workspaces.isFeatureActive} | 392 | isWorkspaceEnabled={workspaces.isFeatureActive} |
382 | server={server || 'https://api.franzinfra.com'} | 393 | server={server || 'https://api.franzinfra.com'} |
383 | lockingFeatureEnabled={lockingFeatureEnabled} | 394 | lockingFeatureEnabled={lockingFeatureEnabled} |
395 | noUpdates={this.props.stores.settings.app.noUpdates} | ||
384 | /> | 396 | /> |
385 | </ErrorBoundary> | 397 | </ErrorBoundary> |
386 | ); | 398 | ); |
diff --git a/src/electron/ipc-api/autoUpdate.js b/src/electron/ipc-api/autoUpdate.js index 6a3314b2b..506aecdf7 100644 --- a/src/electron/ipc-api/autoUpdate.js +++ b/src/electron/ipc-api/autoUpdate.js | |||
@@ -4,24 +4,33 @@ import { autoUpdater } from 'electron-updater'; | |||
4 | const debug = require('debug')('Ferdi:ipcApi:autoUpdate'); | 4 | const debug = require('debug')('Ferdi:ipcApi:autoUpdate'); |
5 | 5 | ||
6 | export default (params) => { | 6 | export default (params) => { |
7 | if (process.platform === 'darwin' || process.platform === 'win32' || process.env.APPIMAGE) { | 7 | const disableUpdates = Boolean(params.settings.app.get('noUpdates')); |
8 | |||
9 | if (disableUpdates) { | ||
10 | autoUpdater.autoInstallOnAppQuit = false; | ||
11 | autoUpdater.autoDownload = false; | ||
12 | } else if (process.platform === 'darwin' || process.platform === 'win32' || process.env.APPIMAGE) { | ||
8 | ipcMain.on('autoUpdate', (event, args) => { | 13 | ipcMain.on('autoUpdate', (event, args) => { |
9 | try { | 14 | const enableUpdate = !params.settings.app.get('noUpdates'); |
10 | autoUpdater.autoInstallOnAppQuit = false; | 15 | |
11 | autoUpdater.allowPrerelease = Boolean(params.settings.app.get('beta')); | 16 | if (enableUpdate) { |
12 | if (args.action === 'check') { | 17 | try { |
13 | autoUpdater.checkForUpdates(); | 18 | autoUpdater.autoInstallOnAppQuit = false; |
14 | } else if (args.action === 'install') { | 19 | autoUpdater.allowPrerelease = Boolean(params.settings.app.get('beta')); |
15 | debug('install update'); | 20 | if (args.action === 'check') { |
16 | autoUpdater.quitAndInstall(); | 21 | autoUpdater.checkForUpdates(); |
17 | // we need to send a quit event | 22 | } else if (args.action === 'install') { |
18 | setTimeout(() => { | 23 | debug('install update'); |
19 | app.quit(); | 24 | autoUpdater.quitAndInstall(); |
20 | }, 20); | 25 | // we need to send a quit event |
26 | setTimeout(() => { | ||
27 | app.quit(); | ||
28 | }, 20); | ||
29 | } | ||
30 | } catch (e) { | ||
31 | console.error(e); | ||
32 | event.sender.send('autoUpdate', { error: true }); | ||
21 | } | 33 | } |
22 | } catch (e) { | ||
23 | console.error(e); | ||
24 | event.sender.send('autoUpdate', { error: true }); | ||
25 | } | 34 | } |
26 | }); | 35 | }); |
27 | 36 | ||
@@ -32,10 +41,14 @@ export default (params) => { | |||
32 | 41 | ||
33 | autoUpdater.on('update-available', (event) => { | 42 | autoUpdater.on('update-available', (event) => { |
34 | debug('update-available'); | 43 | debug('update-available'); |
35 | params.mainWindow.webContents.send('autoUpdate', { | 44 | |
36 | version: event.version, | 45 | const enableUpdate = !params.settings.app.get('noUpdates'); |
37 | available: true, | 46 | if (enableUpdate) { |
38 | }); | 47 | params.mainWindow.webContents.send('autoUpdate', { |
48 | version: event.version, | ||
49 | available: true, | ||
50 | }); | ||
51 | } | ||
39 | }); | 52 | }); |
40 | 53 | ||
41 | autoUpdater.on('download-progress', (progressObj) => { | 54 | autoUpdater.on('download-progress', (progressObj) => { |
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index c252aff90..35c102220 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -37,9 +37,6 @@ const styles = theme => ({ | |||
37 | 37 | ||
38 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, | 38 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, |
39 | 39 | ||
40 | '&:hover $closeTodosButton': { | ||
41 | opacity: 1, | ||
42 | }, | ||
43 | '& webview': { | 40 | '& webview': { |
44 | height: '100%', | 41 | height: '100%', |
45 | }, | 42 | }, |
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index d507237d1..a05203a04 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js | |||
@@ -162,6 +162,10 @@ export default class TodoStore extends FeatureStore { | |||
162 | theme: isDarkThemeActive ? ThemeType.dark : ThemeType.default, | 162 | theme: isDarkThemeActive ? ThemeType.dark : ThemeType.default, |
163 | }, | 163 | }, |
164 | }); | 164 | }); |
165 | |||
166 | this.webview.addEventListener('new-window', ({ url }) => { | ||
167 | this.actions.app.openExternalUrl({ url }); | ||
168 | }); | ||
165 | }; | 169 | }; |
166 | 170 | ||
167 | _goToService = ({ url, serviceId }) => { | 171 | _goToService = ({ url, serviceId }) => { |
diff --git a/src/features/webControls/components/WebControls.js b/src/features/webControls/components/WebControls.js new file mode 100644 index 000000000..03f601a17 --- /dev/null +++ b/src/features/webControls/components/WebControls.js | |||
@@ -0,0 +1,190 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { Icon } from '@meetfranz/ui'; | ||
6 | |||
7 | import { | ||
8 | mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, | ||
9 | } from '@mdi/js'; | ||
10 | |||
11 | const styles = theme => ({ | ||
12 | root: { | ||
13 | background: theme.colorBackground, | ||
14 | position: 'relative', | ||
15 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
16 | zIndex: 300, | ||
17 | height: 50, | ||
18 | display: 'flex', | ||
19 | flexDirection: 'row', | ||
20 | alignItems: 'center', | ||
21 | padding: [0, 20], | ||
22 | |||
23 | '& + div': { | ||
24 | height: 'calc(100% - 50px)', | ||
25 | }, | ||
26 | }, | ||
27 | button: { | ||
28 | width: 30, | ||
29 | height: 50, | ||
30 | transition: 'opacity 0.25s', | ||
31 | |||
32 | '&:hover': { | ||
33 | opacity: 0.8, | ||
34 | }, | ||
35 | |||
36 | '&:disabled': { | ||
37 | opacity: 0.5, | ||
38 | }, | ||
39 | }, | ||
40 | icon: { | ||
41 | width: '20px !important', | ||
42 | height: 20, | ||
43 | marginTop: 5, | ||
44 | }, | ||
45 | input: { | ||
46 | marginBottom: 0, | ||
47 | height: 'auto', | ||
48 | marginLeft: 10, | ||
49 | flex: 1, | ||
50 | border: 0, | ||
51 | padding: [4, 10], | ||
52 | borderRadius: theme.borderRadius, | ||
53 | background: theme.inputBackground, | ||
54 | color: theme.inputColor, | ||
55 | }, | ||
56 | inputButton: { | ||
57 | color: theme.colorText, | ||
58 | }, | ||
59 | }); | ||
60 | |||
61 | @injectSheet(styles) @observer | ||
62 | class WebControls extends Component { | ||
63 | static propTypes = { | ||
64 | classes: PropTypes.object.isRequired, | ||
65 | goHome: PropTypes.func.isRequired, | ||
66 | canGoBack: PropTypes.bool.isRequired, | ||
67 | goBack: PropTypes.func.isRequired, | ||
68 | canGoForward: PropTypes.bool.isRequired, | ||
69 | goForward: PropTypes.func.isRequired, | ||
70 | reload: PropTypes.func.isRequired, | ||
71 | url: PropTypes.string.isRequired, | ||
72 | navigate: PropTypes.func.isRequired, | ||
73 | } | ||
74 | |||
75 | static getDerivedStateFromProps(props, state) { | ||
76 | const { url } = props; | ||
77 | const { editUrl } = state; | ||
78 | |||
79 | if (!editUrl) { | ||
80 | return { | ||
81 | inputUrl: url, | ||
82 | editUrl: state.editUrl, | ||
83 | }; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | inputRef = React.createRef(); | ||
88 | |||
89 | state = { | ||
90 | inputUrl: '', | ||
91 | editUrl: false, | ||
92 | } | ||
93 | |||
94 | render() { | ||
95 | const { | ||
96 | classes, | ||
97 | goHome, | ||
98 | canGoBack, | ||
99 | goBack, | ||
100 | canGoForward, | ||
101 | goForward, | ||
102 | reload, | ||
103 | url, | ||
104 | navigate, | ||
105 | } = this.props; | ||
106 | |||
107 | const { | ||
108 | inputUrl, | ||
109 | editUrl, | ||
110 | } = this.state; | ||
111 | |||
112 | return ( | ||
113 | <div className={classes.root}> | ||
114 | <button | ||
115 | onClick={goHome} | ||
116 | type="button" | ||
117 | className={classes.button} | ||
118 | > | ||
119 | <Icon | ||
120 | icon={mdiHomeOutline} | ||
121 | className={classes.icon} | ||
122 | /> | ||
123 | </button> | ||
124 | <button | ||
125 | onClick={goBack} | ||
126 | type="button" | ||
127 | className={classes.button} | ||
128 | disabled={!canGoBack} | ||
129 | > | ||
130 | <Icon | ||
131 | icon={mdiArrowLeft} | ||
132 | className={classes.icon} | ||
133 | /> | ||
134 | </button> | ||
135 | <button | ||
136 | onClick={goForward} | ||
137 | type="button" | ||
138 | className={classes.button} | ||
139 | disabled={!canGoForward} | ||
140 | > | ||
141 | <Icon | ||
142 | icon={mdiArrowRight} | ||
143 | className={classes.icon} | ||
144 | /> | ||
145 | </button> | ||
146 | <button | ||
147 | onClick={reload} | ||
148 | type="button" | ||
149 | className={classes.button} | ||
150 | > | ||
151 | <Icon | ||
152 | icon={mdiReload} | ||
153 | className={classes.icon} | ||
154 | /> | ||
155 | </button> | ||
156 | <input | ||
157 | value={editUrl ? inputUrl : url} | ||
158 | className={classes.input} | ||
159 | onChange={event => this.setState({ | ||
160 | inputUrl: event.target.value, | ||
161 | })} | ||
162 | onFocus={(event) => { | ||
163 | event.target.select(); | ||
164 | this.setState({ | ||
165 | editUrl: true, | ||
166 | }); | ||
167 | }} | ||
168 | onKeyDown={(event) => { | ||
169 | if (event.key === 'Enter') { | ||
170 | this.setState({ | ||
171 | editUrl: false, | ||
172 | }); | ||
173 | navigate(inputUrl); | ||
174 | this.inputRef.current.blur(); | ||
175 | } else if (event.key === 'Escape') { | ||
176 | this.setState({ | ||
177 | editUrl: false, | ||
178 | inputUrl: url, | ||
179 | }); | ||
180 | event.target.blur(); | ||
181 | } | ||
182 | }} | ||
183 | ref={this.inputRef} | ||
184 | /> | ||
185 | </div> | ||
186 | ); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | export default WebControls; | ||
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js new file mode 100644 index 000000000..1452d5a3d --- /dev/null +++ b/src/features/webControls/containers/WebControlsScreen.js | |||
@@ -0,0 +1,128 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | |||
5 | import { autorun, observable } from 'mobx'; | ||
6 | import WebControls from '../components/WebControls'; | ||
7 | import ServicesStore from '../../../stores/ServicesStore'; | ||
8 | import Service from '../../../models/Service'; | ||
9 | |||
10 | const URL_EVENTS = [ | ||
11 | 'load-commit', | ||
12 | // 'dom-ready', | ||
13 | 'will-navigate', | ||
14 | 'did-navigate', | ||
15 | 'did-navigate-in-page', | ||
16 | ]; | ||
17 | |||
18 | @inject('stores', 'actions') @observer | ||
19 | class WebControlsScreen extends Component { | ||
20 | @observable url = ''; | ||
21 | |||
22 | @observable canGoBack = false; | ||
23 | |||
24 | @observable canGoForward = false; | ||
25 | |||
26 | webview = null; | ||
27 | |||
28 | autorunDisposer = null; | ||
29 | |||
30 | componentDidMount() { | ||
31 | const { service } = this.props; | ||
32 | |||
33 | this.autorunDisposer = autorun(() => { | ||
34 | if (service.isAttached) { | ||
35 | this.webview = service.webview; | ||
36 | |||
37 | URL_EVENTS.forEach((event) => { | ||
38 | this.webview.addEventListener(event, (e) => { | ||
39 | if (!e.isMainFrame) return; | ||
40 | |||
41 | this.url = e.url; | ||
42 | this.canGoBack = this.webview.canGoBack(); | ||
43 | this.canGoForward = this.webview.canGoForward(); | ||
44 | }); | ||
45 | }); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
49 | |||
50 | componentWillUnmount() { | ||
51 | this.autorunDisposer(); | ||
52 | } | ||
53 | |||
54 | goHome() { | ||
55 | const { reloadActive } = this.props.actions.service; | ||
56 | |||
57 | if (!this.webview) return; | ||
58 | |||
59 | reloadActive(); | ||
60 | } | ||
61 | |||
62 | reload() { | ||
63 | if (!this.webview) return; | ||
64 | |||
65 | this.webview.reload(); | ||
66 | } | ||
67 | |||
68 | goBack() { | ||
69 | if (!this.webview) return; | ||
70 | |||
71 | this.webview.goBack(); | ||
72 | } | ||
73 | |||
74 | goForward() { | ||
75 | if (!this.webview) return; | ||
76 | |||
77 | this.webview.goForward(); | ||
78 | } | ||
79 | |||
80 | navigate(newUrl) { | ||
81 | if (!this.webview) return; | ||
82 | |||
83 | let url = newUrl; | ||
84 | |||
85 | try { | ||
86 | url = new URL(url).toString(); | ||
87 | } catch (err) { | ||
88 | // eslint-disable-next-line no-useless-escape | ||
89 | if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) { | ||
90 | url = `http://${url}`; | ||
91 | } else { | ||
92 | url = `https://www.google.com/search?query=${url}`; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | this.webview.loadURL(url); | ||
97 | this.url = url; | ||
98 | } | ||
99 | |||
100 | render() { | ||
101 | return ( | ||
102 | <WebControls | ||
103 | goHome={() => this.goHome()} | ||
104 | reload={() => this.reload()} | ||
105 | canGoBack={this.canGoBack} | ||
106 | goBack={() => this.goBack()} | ||
107 | canGoForward={this.canGoForward} | ||
108 | goForward={() => this.goForward()} | ||
109 | navigate={url => this.navigate(url)} | ||
110 | url={this.url} | ||
111 | /> | ||
112 | ); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | export default WebControlsScreen; | ||
117 | |||
118 | WebControlsScreen.wrappedComponent.propTypes = { | ||
119 | service: PropTypes.instanceOf(Service).isRequired, | ||
120 | stores: PropTypes.shape({ | ||
121 | services: PropTypes.instanceOf(ServicesStore).isRequired, | ||
122 | }).isRequired, | ||
123 | actions: PropTypes.shape({ | ||
124 | service: PropTypes.shape({ | ||
125 | reloadActive: PropTypes.func.isRequired, | ||
126 | }).isRequired, | ||
127 | }).isRequired, | ||
128 | }; | ||
diff --git a/src/i18n/apply-branding.js b/src/i18n/apply-branding.js index 662799ab0..8e496c69d 100644 --- a/src/i18n/apply-branding.js +++ b/src/i18n/apply-branding.js | |||
@@ -10,6 +10,7 @@ console.log('Applying Ferdi branding to translations...'); | |||
10 | const ignore = [ | 10 | const ignore = [ |
11 | 'login.customServerSuggestion', | 11 | 'login.customServerSuggestion', |
12 | 'settings.app.todoServerInfo', | 12 | 'settings.app.todoServerInfo', |
13 | 'settings.app.serverMoneyInfo', | ||
13 | ]; | 14 | ]; |
14 | 15 | ||
15 | // Files to ignore when applying branding | 16 | // Files to ignore when applying branding |
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 4033407e3..2cb42d134 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -4032,29 +4032,42 @@ | |||
4032 | } | 4032 | } |
4033 | }, | 4033 | }, |
4034 | { | 4034 | { |
4035 | "defaultMessage": "!!!Enable Franz Todos", | 4035 | "defaultMessage": "!!!Disable updates", |
4036 | "end": { | 4036 | "end": { |
4037 | "column": 3, | 4037 | "column": 3, |
4038 | "line": 115 | 4038 | "line": 115 |
4039 | }, | 4039 | }, |
4040 | "file": "src/containers/settings/EditSettingsScreen.js", | 4040 | "file": "src/containers/settings/EditSettingsScreen.js", |
4041 | "id": "settings.app.form.noUpdates", | ||
4042 | "start": { | ||
4043 | "column": 13, | ||
4044 | "line": 112 | ||
4045 | } | ||
4046 | }, | ||
4047 | { | ||
4048 | "defaultMessage": "!!!Enable Franz Todos", | ||
4049 | "end": { | ||
4050 | "column": 3, | ||
4051 | "line": 119 | ||
4052 | }, | ||
4053 | "file": "src/containers/settings/EditSettingsScreen.js", | ||
4041 | "id": "settings.app.form.enableTodos", | 4054 | "id": "settings.app.form.enableTodos", |
4042 | "start": { | 4055 | "start": { |
4043 | "column": 15, | 4056 | "column": 15, |
4044 | "line": 112 | 4057 | "line": 116 |
4045 | } | 4058 | } |
4046 | }, | 4059 | }, |
4047 | { | 4060 | { |
4048 | "defaultMessage": "!!!Keep all workspaces loaded", | 4061 | "defaultMessage": "!!!Keep all workspaces loaded", |
4049 | "end": { | 4062 | "end": { |
4050 | "column": 3, | 4063 | "column": 3, |
4051 | "line": 119 | 4064 | "line": 123 |
4052 | }, | 4065 | }, |
4053 | "file": "src/containers/settings/EditSettingsScreen.js", | 4066 | "file": "src/containers/settings/EditSettingsScreen.js", |
4054 | "id": "settings.app.form.keepAllWorkspacesLoaded", | 4067 | "id": "settings.app.form.keepAllWorkspacesLoaded", |
4055 | "start": { | 4068 | "start": { |
4056 | "column": 27, | 4069 | "column": 27, |
4057 | "line": 116 | 4070 | "line": 120 |
4058 | } | 4071 | } |
4059 | } | 4072 | } |
4060 | ], | 4073 | ], |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index ad64bec14..c8cf09366 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -231,6 +231,7 @@ | |||
231 | "settings.app.form.language": "Language", | 231 | "settings.app.form.language": "Language", |
232 | "settings.app.form.lockPassword": "Ferdi Lock password", | 232 | "settings.app.form.lockPassword": "Ferdi Lock password", |
233 | "settings.app.form.minimizeToSystemTray": "Minimize Ferdi to system tray", | 233 | "settings.app.form.minimizeToSystemTray": "Minimize Ferdi to system tray", |
234 | "settings.app.form.noUpdates": "Disable updates", | ||
234 | "settings.app.form.privateNotifications": "Don't show message content in notifications", | 235 | "settings.app.form.privateNotifications": "Don't show message content in notifications", |
235 | "settings.app.form.runInBackground": "Keep Ferdi in background when closing the window", | 236 | "settings.app.form.runInBackground": "Keep Ferdi in background when closing the window", |
236 | "settings.app.form.scheduledDNDEnabled": "Enable scheduled Do-not-Disturb", | 237 | "settings.app.form.scheduledDNDEnabled": "Enable scheduled Do-not-Disturb", |
diff --git a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json index dccf8b992..110b7787b 100644 --- a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json +++ b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json | |||
@@ -273,15 +273,28 @@ | |||
273 | } | 273 | } |
274 | }, | 274 | }, |
275 | { | 275 | { |
276 | "id": "settings.app.form.noUpdates", | ||
277 | "defaultMessage": "!!!Disable updates", | ||
278 | "file": "src/containers/settings/EditSettingsScreen.js", | ||
279 | "start": { | ||
280 | "line": 112, | ||
281 | "column": 13 | ||
282 | }, | ||
283 | "end": { | ||
284 | "line": 115, | ||
285 | "column": 3 | ||
286 | } | ||
287 | }, | ||
288 | { | ||
276 | "id": "settings.app.form.enableTodos", | 289 | "id": "settings.app.form.enableTodos", |
277 | "defaultMessage": "!!!Enable Franz Todos", | 290 | "defaultMessage": "!!!Enable Franz Todos", |
278 | "file": "src/containers/settings/EditSettingsScreen.js", | 291 | "file": "src/containers/settings/EditSettingsScreen.js", |
279 | "start": { | 292 | "start": { |
280 | "line": 112, | 293 | "line": 116, |
281 | "column": 15 | 294 | "column": 15 |
282 | }, | 295 | }, |
283 | "end": { | 296 | "end": { |
284 | "line": 115, | 297 | "line": 119, |
285 | "column": 3 | 298 | "column": 3 |
286 | } | 299 | } |
287 | }, | 300 | }, |
@@ -290,11 +303,11 @@ | |||
290 | "defaultMessage": "!!!Keep all workspaces loaded", | 303 | "defaultMessage": "!!!Keep all workspaces loaded", |
291 | "file": "src/containers/settings/EditSettingsScreen.js", | 304 | "file": "src/containers/settings/EditSettingsScreen.js", |
292 | "start": { | 305 | "start": { |
293 | "line": 116, | 306 | "line": 120, |
294 | "column": 27 | 307 | "column": 27 |
295 | }, | 308 | }, |
296 | "end": { | 309 | "end": { |
297 | "line": 119, | 310 | "line": 123, |
298 | "column": 3 | 311 | "column": 3 |
299 | } | 312 | } |
300 | } | 313 | } |
diff --git a/src/index.js b/src/index.js index 73fc6e299..96f840533 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -11,7 +11,11 @@ import windowStateKeeper from 'electron-window-state'; | |||
11 | 11 | ||
12 | // Set app directory before loading user modules | 12 | // Set app directory before loading user modules |
13 | if (isDevMode) { | 13 | if (isDevMode) { |
14 | app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev')); | 14 | app.setPath('userData', path.join(app.getPath('appData'), `${app.getName()}Dev`)); |
15 | } else if (process.env.FERDI_USERDATA_DIR != null) { | ||
16 | app.setPath('userData', process.env.FERDI_USERDATA_DIR); | ||
17 | } else if (process.env.PORTABLE_EXECUTABLE_DIR != null) { | ||
18 | app.setPath('userData', path.join(process.env.PORTABLE_EXECUTABLE_DIR, app.getName())); | ||
15 | } | 19 | } |
16 | 20 | ||
17 | /* eslint-disable import/first */ | 21 | /* eslint-disable import/first */ |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 80fa0e463..7e336c994 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -325,6 +325,9 @@ const _templateFactory = intl => [ | |||
325 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), | 325 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), |
326 | accelerator: 'Cmd+Shift+V', | 326 | accelerator: 'Cmd+Shift+V', |
327 | selector: 'pasteAndMatchStyle:', | 327 | selector: 'pasteAndMatchStyle:', |
328 | click() { | ||
329 | getActiveWebview().pasteAndMatchStyle(); | ||
330 | }, | ||
328 | }, | 331 | }, |
329 | { | 332 | { |
330 | label: intl.formatMessage(menuItems.delete), | 333 | label: intl.formatMessage(menuItems.delete), |
@@ -345,7 +348,7 @@ const _templateFactory = intl => [ | |||
345 | }, | 348 | }, |
346 | { | 349 | { |
347 | label: intl.formatMessage(menuItems.openQuickSwitch), | 350 | label: intl.formatMessage(menuItems.openQuickSwitch), |
348 | accelerator: 'CmdOrCtrl+P', | 351 | accelerator: 'CmdOrCtrl+S', |
349 | click() { | 352 | click() { |
350 | window.ferdi.features.quickSwitch.state.isModalVisible = true; | 353 | window.ferdi.features.quickSwitch.state.isModalVisible = true; |
351 | }, | 354 | }, |
@@ -549,7 +552,7 @@ const _titleBarTemplateFactory = intl => [ | |||
549 | }, | 552 | }, |
550 | { | 553 | { |
551 | label: intl.formatMessage(menuItems.openQuickSwitch), | 554 | label: intl.formatMessage(menuItems.openQuickSwitch), |
552 | accelerator: 'CmdOrCtrl+P', | 555 | accelerator: 'CmdOrCtrl+S', |
553 | click() { | 556 | click() { |
554 | window.ferdi.features.quickSwitch.state.isModalVisible = true; | 557 | window.ferdi.features.quickSwitch.state.isModalVisible = true; |
555 | }, | 558 | }, |
@@ -783,12 +786,21 @@ export default class FranzMenu { | |||
783 | accelerator: 'CmdOrCtrl+Shift+L', | 786 | accelerator: 'CmdOrCtrl+Shift+L', |
784 | enabled: this.stores.settings.app.lockingFeatureEnabled, | 787 | enabled: this.stores.settings.app.lockingFeatureEnabled, |
785 | click() { | 788 | click() { |
789 | // Disable lock first - otherwise the application might not update correctly | ||
786 | actions.settings.update({ | 790 | actions.settings.update({ |
787 | type: 'app', | 791 | type: 'app', |
788 | data: { | 792 | data: { |
789 | locked: true, | 793 | locked: false, |
790 | }, | 794 | }, |
791 | }); | 795 | }); |
796 | setTimeout(() => { | ||
797 | actions.settings.update({ | ||
798 | type: 'app', | ||
799 | data: { | ||
800 | locked: true, | ||
801 | }, | ||
802 | }); | ||
803 | }, 0); | ||
792 | }, | 804 | }, |
793 | }); | 805 | }); |
794 | 806 | ||
@@ -976,6 +988,10 @@ export default class FranzMenu { | |||
976 | checked: service.isActive, | 988 | checked: service.isActive, |
977 | click: () => { | 989 | click: () => { |
978 | this.actions.service.setActive({ serviceId: service.id }); | 990 | this.actions.service.setActive({ serviceId: service.id }); |
991 | |||
992 | if (isMac && i === 0) { | ||
993 | app.mainWindow.restore(); | ||
994 | } | ||
979 | }, | 995 | }, |
980 | }))); | 996 | }))); |
981 | 997 | ||
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js index 97c02d194..1de46d2a3 100644 --- a/src/lib/TouchBar.js +++ b/src/lib/TouchBar.js | |||
@@ -29,7 +29,7 @@ export default class FranzTouchBar { | |||
29 | const { TouchBarButton, TouchBarSpacer } = TouchBar; | 29 | const { TouchBarButton, TouchBarSpacer } = TouchBar; |
30 | 30 | ||
31 | const buttons = []; | 31 | const buttons = []; |
32 | this.stores.services.enabled.forEach(((service) => { | 32 | this.stores.services.allDisplayed.forEach(((service) => { |
33 | buttons.push(new TouchBarButton({ | 33 | buttons.push(new TouchBarButton({ |
34 | label: `${service.name}${service.unreadDirectMessageCount > 0 | 34 | label: `${service.name}${service.unreadDirectMessageCount > 0 |
35 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 | 35 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 |
@@ -42,7 +42,7 @@ export default class FranzTouchBar { | |||
42 | }), new TouchBarSpacer({ size: 'small' })); | 42 | }), new TouchBarSpacer({ size: 'small' })); |
43 | })); | 43 | })); |
44 | 44 | ||
45 | const touchBar = new TouchBar(buttons); | 45 | const touchBar = new TouchBar({ items: buttons }); |
46 | currentWindow.setTouchBar(touchBar); | 46 | currentWindow.setTouchBar(touchBar); |
47 | } else { | 47 | } else { |
48 | currentWindow.setTouchBar(null); | 48 | currentWindow.setTouchBar(null); |
diff --git a/src/models/Recipe.js b/src/models/Recipe.js index 3f7299e34..6655f8310 100644 --- a/src/models/Recipe.js +++ b/src/models/Recipe.js | |||
@@ -36,6 +36,8 @@ export default class Recipe { | |||
36 | 36 | ||
37 | message = ''; | 37 | message = ''; |
38 | 38 | ||
39 | disablewebsecurity = false; | ||
40 | |||
39 | constructor(data) { | 41 | constructor(data) { |
40 | if (!data) { | 42 | if (!data) { |
41 | throw Error('Recipe config not valid'); | 43 | throw Error('Recipe config not valid'); |
@@ -74,6 +76,8 @@ export default class Recipe { | |||
74 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; | 76 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; |
75 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; | 77 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; |
76 | 78 | ||
79 | this.disablewebsecurity = data.config.disablewebsecurity || this.disablewebsecurity; | ||
80 | |||
77 | this.message = data.config.message || this.message; | 81 | this.message = data.config.message || this.message; |
78 | } | 82 | } |
79 | 83 | ||
diff --git a/src/models/Service.js b/src/models/Service.js index b48233237..3ab6e2603 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -134,6 +134,9 @@ export default class Service { | |||
134 | id: this.id, | 134 | id: this.id, |
135 | spellcheckerLanguage: this.spellcheckerLanguage, | 135 | spellcheckerLanguage: this.spellcheckerLanguage, |
136 | isDarkModeEnabled: this.isDarkModeEnabled, | 136 | isDarkModeEnabled: this.isDarkModeEnabled, |
137 | team: this.team, | ||
138 | url: this.url, | ||
139 | hasCustomIcon: this.hasCustomIcon, | ||
137 | }; | 140 | }; |
138 | } | 141 | } |
139 | 142 | ||
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 5bae6e8d4..40d98cf42 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -22,6 +22,7 @@ import { getLocale } from '../helpers/i18n-helpers'; | |||
22 | 22 | ||
23 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 23 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
24 | import { isValidExternalURL } from '../helpers/url-helpers'; | 24 | import { isValidExternalURL } from '../helpers/url-helpers'; |
25 | import { sleep } from '../helpers/async-helpers'; | ||
25 | 26 | ||
26 | const debug = require('debug')('Ferdi:AppStore'); | 27 | const debug = require('debug')('Ferdi:AppStore'); |
27 | 28 | ||
@@ -317,6 +318,8 @@ export default class AppStore extends Store { | |||
317 | 318 | ||
318 | await clearAppCache._promise; | 319 | await clearAppCache._promise; |
319 | 320 | ||
321 | await sleep(ms('1s')); | ||
322 | |||
320 | this.getAppCacheSizeRequest.execute(); | 323 | this.getAppCacheSizeRequest.execute(); |
321 | 324 | ||
322 | this.isClearingAllCache = false; | 325 | this.isClearingAllCache = false; |
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index c09f24af7..8c4cd47eb 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -71,12 +71,21 @@ export default class SettingsStore extends Store { | |||
71 | // Make sure to lock app on launch if locking feature is enabled | 71 | // Make sure to lock app on launch if locking feature is enabled |
72 | setTimeout(() => { | 72 | setTimeout(() => { |
73 | if (this.all.app.lockingFeatureEnabled) { | 73 | if (this.all.app.lockingFeatureEnabled) { |
74 | // Disable lock first - otherwise the lock might not get activated corrently | ||
74 | this.actions.settings.update({ | 75 | this.actions.settings.update({ |
75 | type: 'app', | 76 | type: 'app', |
76 | data: { | 77 | data: { |
77 | locked: true, | 78 | locked: false, |
78 | }, | 79 | }, |
79 | }); | 80 | }); |
81 | setTimeout(() => { | ||
82 | this.actions.settings.update({ | ||
83 | type: 'app', | ||
84 | data: { | ||
85 | locked: true, | ||
86 | }, | ||
87 | }); | ||
88 | }, 0); | ||
80 | } | 89 | } |
81 | }, 1000); | 90 | }, 1000); |
82 | } | 91 | } |
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index ec5833848..acd62d675 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -255,9 +255,9 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
255 | }, | 255 | }, |
256 | { | 256 | { |
257 | id: 'resetToDefault', | 257 | id: 'resetToDefault', |
258 | label: `Reset to system default (${SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, | 258 | label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, |
259 | type: 'radio', | 259 | type: 'radio', |
260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, | 260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'), |
261 | click() { | 261 | click() { |
262 | debug('Resetting service spellchecker to system default'); | 262 | debug('Resetting service spellchecker to system default'); |
263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); | 263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); |
@@ -297,12 +297,13 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
297 | }; | 297 | }; |
298 | 298 | ||
299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { | 299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { |
300 | webContents.on('context-menu', (e, props) => { | 300 | webContents.on('context-menu', async (e, props) => { |
301 | e.preventDefault(); | 301 | e.preventDefault(); |
302 | 302 | ||
303 | let suggestions = []; | 303 | let suggestions = []; |
304 | if (spellcheckProvider && props.misspelledWord) { | 304 | if (spellcheckProvider && props.misspelledWord) { |
305 | suggestions = spellcheckProvider.getSuggestion(props.misspelledWord); | 305 | debug('Mispelled word', props.misspelledWord); |
306 | suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord); | ||
306 | 307 | ||
307 | debug('Suggestions', suggestions); | 308 | debug('Suggestions', suggestions); |
308 | } | 309 | } |
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 1b2d60faf..27380676d 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -1,6 +1,7 @@ | |||
1 | import { webFrame } from 'electron'; | 1 | import { webFrame } from 'electron'; |
2 | import { SpellCheckerProvider } from 'electron-hunspell'; | 2 | import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell'; |
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | import { readFileSync } from 'fs'; | ||
4 | 5 | ||
5 | import { DICTIONARY_PATH } from '../config'; | 6 | import { DICTIONARY_PATH } from '../config'; |
6 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 7 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
@@ -10,18 +11,21 @@ const debug = require('debug')('Ferdi:spellchecker'); | |||
10 | let provider; | 11 | let provider; |
11 | let currentDict; | 12 | let currentDict; |
12 | let _isEnabled = false; | 13 | let _isEnabled = false; |
14 | let attached; | ||
15 | |||
16 | const DEFAULT_LOCALE = 'en-us'; | ||
13 | 17 | ||
14 | async function loadDictionary(locale) { | 18 | async function loadDictionary(locale) { |
15 | try { | 19 | try { |
16 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); | 20 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); |
17 | await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`); | ||
18 | debug('Loaded dictionary', locale, 'from', fileLocation); | 21 | debug('Loaded dictionary', locale, 'from', fileLocation); |
22 | return provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`)); | ||
19 | } catch (err) { | 23 | } catch (err) { |
20 | console.error('Could not load dictionary', err); | 24 | console.error('Could not load dictionary', err); |
21 | } | 25 | } |
22 | } | 26 | } |
23 | 27 | ||
24 | export async function switchDict(locale) { | 28 | export async function switchDict(locale = DEFAULT_LOCALE) { |
25 | try { | 29 | try { |
26 | debug('Trying to load dictionary', locale); | 30 | debug('Trying to load dictionary', locale); |
27 | 31 | ||
@@ -40,8 +44,8 @@ export async function switchDict(locale) { | |||
40 | if (currentDict) { | 44 | if (currentDict) { |
41 | provider.unloadDictionary(locale); | 45 | provider.unloadDictionary(locale); |
42 | } | 46 | } |
43 | loadDictionary(locale); | 47 | await loadDictionary(locale); |
44 | provider.switchDictionary(locale); | 48 | await attached.switchLanguage(locale); |
45 | 49 | ||
46 | debug('Switched dictionary to', locale); | 50 | debug('Switched dictionary to', locale); |
47 | 51 | ||
@@ -52,18 +56,32 @@ export async function switchDict(locale) { | |||
52 | } | 56 | } |
53 | } | 57 | } |
54 | 58 | ||
55 | export default async function initialize(languageCode = 'en-us') { | 59 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { |
60 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
61 | |||
62 | if (locales.length >= 1) { | ||
63 | return locales[0]; | ||
64 | } | ||
65 | |||
66 | return null; | ||
67 | } | ||
68 | |||
69 | export default async function initialize(languageCode = DEFAULT_LOCALE) { | ||
56 | try { | 70 | try { |
57 | provider = new SpellCheckerProvider(); | 71 | provider = new SpellCheckerProvider(); |
58 | const locale = languageCode.toLowerCase(); | 72 | const locale = getSpellcheckerLocaleByFuzzyIdentifier(languageCode); |
59 | 73 | ||
60 | debug('Init spellchecker'); | 74 | debug('Init spellchecker'); |
61 | await provider.initialize(); | 75 | await provider.initialize(); |
62 | // await loadDictionaries(); | ||
63 | 76 | ||
64 | debug('Available spellchecker dictionaries', provider.availableDictionaries); | 77 | debug('Attaching spellcheck provider'); |
78 | attached = await attachSpellCheckProvider(provider); | ||
79 | |||
80 | const availableDictionaries = await provider.getAvailableDictionaries(); | ||
65 | 81 | ||
66 | switchDict(locale); | 82 | debug('Available spellchecker dictionaries', availableDictionaries); |
83 | |||
84 | await switchDict(locale); | ||
67 | 85 | ||
68 | return provider; | 86 | return provider; |
69 | } catch (err) { | 87 | } catch (err) { |
@@ -83,13 +101,3 @@ export function disable() { | |||
83 | currentDict = null; | 101 | currentDict = null; |
84 | } | 102 | } |
85 | } | 103 | } |
86 | |||
87 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { | ||
88 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
89 | |||
90 | if (locales.length >= 1) { | ||
91 | return locales[0]; | ||
92 | } | ||
93 | |||
94 | return null; | ||
95 | } | ||