aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.all-contributorsrc12
-rw-r--r--.travis.yml24
-rw-r--r--README.md3
-rw-r--r--appveyor.yml1
-rw-r--r--branding/social-preview.psdbin0 -> 2137096 bytes
-rw-r--r--electron-builder.yml8
-rw-r--r--package-lock.json50
-rw-r--r--package.json4
-rw-r--r--src/api/server/LocalApi.js4
-rw-r--r--src/components/layout/Sidebar.js11
-rw-r--r--src/components/services/content/ServiceView.js6
-rw-r--r--src/components/services/content/ServiceWebview.js3
-rw-r--r--src/components/settings/settings/EditSettingsForm.js5
-rw-r--r--src/components/ui/AppLoader/index.js15
-rw-r--r--src/config.js1
-rw-r--r--src/containers/settings/EditSettingsScreen.js12
-rw-r--r--src/electron/ipc-api/autoUpdate.js53
-rw-r--r--src/features/todos/components/TodosWebview.js3
-rw-r--r--src/features/todos/store.js4
-rw-r--r--src/features/webControls/components/WebControls.js190
-rw-r--r--src/features/webControls/containers/WebControlsScreen.js128
-rw-r--r--src/i18n/apply-branding.js1
-rw-r--r--src/i18n/locales/defaultMessages.json21
-rw-r--r--src/i18n/locales/en-US.json1
-rw-r--r--src/i18n/messages/src/containers/settings/EditSettingsScreen.json21
-rw-r--r--src/index.js6
-rw-r--r--src/lib/Menu.js22
-rw-r--r--src/lib/TouchBar.js4
-rw-r--r--src/models/Recipe.js4
-rw-r--r--src/models/Service.js3
-rw-r--r--src/stores/AppStore.js3
-rw-r--r--src/stores/SettingsStore.js11
-rw-r--r--src/webview/contextMenu.js9
-rw-r--r--src/webview/spellchecker.js48
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 @@
1matrix: 1matrix:
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
14language: node_js 15language: node_js
15install: 16install:
16 - echo do nothing 17 - echo do nothing
@@ -25,3 +26,4 @@ cache: npm
25branches: 26branches:
26 except: 27 except:
27 - i18n 28 - i18n
29 - l10n_master
diff --git a/README.md b/README.md
index eb69de370..d007155a1 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ $ git tag v5.3.4-beta.4
103$ git push --tags 103$ git push --tags
104``` 104```
105 105
106When pushing a new tag, the CI build will create a draft GitHub release and upload the deliverables in the release assets. 106When 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:
25branches: 25branches:
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:
27win: 27win:
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
35linux: 35linux:
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';
13import ServiceDisabled from './ServiceDisabled'; 13import ServiceDisabled from './ServiceDisabled';
14import ServiceWebview from './ServiceWebview'; 14import ServiceWebview from './ServiceWebview';
15import SettingsStore from '../../../stores/SettingsStore'; 15import SettingsStore from '../../../stores/SettingsStore';
16import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
16 17
17export default @observer @inject('stores') class ServiceView extends Component { 18export 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 {' '}
190This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi. 194 This service is currently hibernating. If this page doesn&#x27;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';
9import styles from './styles'; 9import styles from './styles';
10 10
11const textList = shuffleArray([ 11const 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
22export default @injectSheet(styles) @withTheme class AppLoader extends Component { 21export 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
68export const DEFAULT_FEATURES_CONFIG = { 69export 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';
4const debug = require('debug')('Ferdi:ipcApi:autoUpdate'); 4const debug = require('debug')('Ferdi:ipcApi:autoUpdate');
5 5
6export default (params) => { 6export 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 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui';
6
7import {
8 mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline,
9} from '@mdi/js';
10
11const 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
62class 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
190export 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 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import { autorun, observable } from 'mobx';
6import WebControls from '../components/WebControls';
7import ServicesStore from '../../../stores/ServicesStore';
8import Service from '../../../models/Service';
9
10const 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
19class 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
116export default WebControlsScreen;
117
118WebControlsScreen.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...');
10const ignore = [ 10const 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
13if (isDevMode) { 13if (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
23import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; 23import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js';
24import { isValidExternalURL } from '../helpers/url-helpers'; 24import { isValidExternalURL } from '../helpers/url-helpers';
25import { sleep } from '../helpers/async-helpers';
25 26
26const debug = require('debug')('Ferdi:AppStore'); 27const 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
299export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { 299export 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 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import { SpellCheckerProvider } from 'electron-hunspell'; 2import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell';
3import path from 'path'; 3import path from 'path';
4import { readFileSync } from 'fs';
4 5
5import { DICTIONARY_PATH } from '../config'; 6import { DICTIONARY_PATH } from '../config';
6import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 7import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -10,18 +11,21 @@ const debug = require('debug')('Ferdi:spellchecker');
10let provider; 11let provider;
11let currentDict; 12let currentDict;
12let _isEnabled = false; 13let _isEnabled = false;
14let attached;
15
16const DEFAULT_LOCALE = 'en-us';
13 17
14async function loadDictionary(locale) { 18async 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
24export async function switchDict(locale) { 28export 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
55export default async function initialize(languageCode = 'en-us') { 59export 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
69export 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
87export 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}