aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2019-03-12 14:04:13 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-03-12 14:04:13 +0100
commit9af01849f895230ae60a635c9756a5e206aa0fa1 (patch)
treeb0b99f324c1a2e8c52e1fadf1152efac6d1028d2
parentBump version to 5.0.1-beta.1 (diff)
parentMerge branch 'sergiughf-hotfix/update-electron' into develop (diff)
downloadferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.tar.gz
ferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.tar.zst
ferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.zip
Merge branch 'develop' into release/5.0.1-beta.1
-rw-r--r--.eslintrc1
-rw-r--r--README.md2
-rw-r--r--package-lock.json65
-rw-r--r--package.json3
-rw-r--r--src/actions/service.js4
-rw-r--r--src/components/services/content/ServiceView.js136
-rw-r--r--src/components/services/content/ServiceWebview.js145
-rw-r--r--src/components/services/content/Services.js7
-rw-r--r--src/containers/layout/AppLayoutContainer.js3
-rw-r--r--src/i18n/locales/defaultMessages.json52
-rw-r--r--src/i18n/locales/en-US.json2
-rw-r--r--src/i18n/messages/src/lib/Menu.json52
-rw-r--r--src/index.js5
-rw-r--r--src/lib/Menu.js91
-rw-r--r--src/lib/Tray.js8
-rw-r--r--src/stores/AppStore.js56
-rw-r--r--src/stores/ServicesStore.js6
-rw-r--r--src/webview/contextMenu.js21
18 files changed, 443 insertions, 216 deletions
diff --git a/.eslintrc b/.eslintrc
index e15148e96..743946d35 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,6 +2,7 @@
2 "parser": "babel-eslint", 2 "parser": "babel-eslint",
3 "extends": "eslint-config-airbnb", 3 "extends": "eslint-config-airbnb",
4 "rules": { 4 "rules": {
5 "no-param-reassign": 0,
5 "import/extensions": 0, 6 "import/extensions": 0,
6 "import/no-extraneous-dependencies": 0, 7 "import/no-extraneous-dependencies": 0,
7 "import/no-unresolved": [2, { 8 "import/no-unresolved": [2, {
diff --git a/README.md b/README.md
index c7e1d8034..d44cfaa6c 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many mor
16 16
17`$ brew cask install franz` 17`$ brew cask install franz`
18 18
19(Don't know homebrew? [brew.sh](https://brew.sh/) 19(Don't know homebrew? [brew.sh](https://brew.sh/))
20 20
21## Development 21## Development
22 22
diff --git a/package-lock.json b/package-lock.json
index 82e9c9997..a9724bb82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2358,7 +2358,8 @@
2358 "abbrev": { 2358 "abbrev": {
2359 "version": "1.1.1", 2359 "version": "1.1.1",
2360 "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 2360 "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
2361 "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 2361 "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
2362 "dev": true
2362 }, 2363 },
2363 "accepts": { 2364 "accepts": {
2364 "version": "1.0.7", 2365 "version": "1.0.7",
@@ -2628,7 +2629,8 @@
2628 "aproba": { 2629 "aproba": {
2629 "version": "1.2.0", 2630 "version": "1.2.0",
2630 "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 2631 "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
2631 "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 2632 "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
2633 "dev": true
2632 }, 2634 },
2633 "archy": { 2635 "archy": {
2634 "version": "1.0.0", 2636 "version": "1.0.0",
@@ -2640,6 +2642,7 @@
2640 "version": "1.1.5", 2642 "version": "1.1.5",
2641 "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 2643 "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
2642 "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 2644 "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
2645 "dev": true,
2643 "requires": { 2646 "requires": {
2644 "delegates": "^1.0.0", 2647 "delegates": "^1.0.0",
2645 "readable-stream": "^2.0.6" 2648 "readable-stream": "^2.0.6"
@@ -2649,6 +2652,7 @@
2649 "version": "2.3.6", 2652 "version": "2.3.6",
2650 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 2653 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
2651 "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 2654 "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
2655 "dev": true,
2652 "requires": { 2656 "requires": {
2653 "core-util-is": "~1.0.0", 2657 "core-util-is": "~1.0.0",
2654 "inherits": "~2.0.3", 2658 "inherits": "~2.0.3",
@@ -2663,6 +2667,7 @@
2663 "version": "1.1.1", 2667 "version": "1.1.1",
2664 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 2668 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
2665 "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 2669 "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
2670 "dev": true,
2666 "requires": { 2671 "requires": {
2667 "safe-buffer": "~5.1.0" 2672 "safe-buffer": "~5.1.0"
2668 } 2673 }
@@ -5428,7 +5433,8 @@
5428 "delegates": { 5433 "delegates": {
5429 "version": "1.0.0", 5434 "version": "1.0.0",
5430 "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 5435 "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
5431 "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 5436 "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
5437 "dev": true
5432 }, 5438 },
5433 "depd": { 5439 "depd": {
5434 "version": "0.4.4", 5440 "version": "0.4.4",
@@ -5668,6 +5674,29 @@
5668 "readable-stream": "~1.1.9" 5674 "readable-stream": "~1.1.9"
5669 }, 5675 },
5670 "dependencies": { 5676 "dependencies": {
5677 "abbrev": {
5678 "version": "1.1.1",
5679 "resolved": false,
5680 "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
5681 },
5682 "ansi-regex": {
5683 "version": "2.1.1",
5684 "resolved": false,
5685 "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
5686 },
5687 "aproba": {
5688 "version": "1.2.0",
5689 "resolved": false,
5690 "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
5691 },
5692 "are-we-there-yet": {
5693 "version": "1.1.4",
5694 "resolved": false,
5695 "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
5696 "requires": {
5697 "delegates": "^1.0.0"
5698 }
5699 },
5671 "balanced-match": { 5700 "balanced-match": {
5672 "version": "1.0.0", 5701 "version": "1.0.0",
5673 "resolved": false, 5702 "resolved": false,
@@ -5984,6 +6013,11 @@
5984 "glob": "^7.0.5" 6013 "glob": "^7.0.5"
5985 } 6014 }
5986 }, 6015 },
6016 "safe-buffer": {
6017 "version": "5.1.1",
6018 "resolved": false,
6019 "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
6020 },
5987 "safer-buffer": { 6021 "safer-buffer": {
5988 "version": "2.1.2", 6022 "version": "2.1.2",
5989 "resolved": false, 6023 "resolved": false,
@@ -6028,7 +6062,10 @@
6028 "strip-ansi": { 6062 "strip-ansi": {
6029 "version": "3.0.1", 6063 "version": "3.0.1",
6030 "resolved": false, 6064 "resolved": false,
6031 "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" 6065 "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
6066 "requires": {
6067 "ansi-regex": "^2.0.0"
6068 }
6032 }, 6069 },
6033 "strip-json-comments": { 6070 "strip-json-comments": {
6034 "version": "2.0.1", 6071 "version": "2.0.1",
@@ -6154,9 +6191,9 @@
6154 "dev": true 6191 "dev": true
6155 }, 6192 },
6156 "electron": { 6193 "electron": {
6157 "version": "4.0.7", 6194 "version": "4.0.8",
6158 "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.7.tgz", 6195 "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.8.tgz",
6159 "integrity": "sha512-KYQ9SJZFWNKqoq6XjKW1bLFHjmAGeSC3XNuhHK/Sd2MK5H5sO3iKjvZU/YhiBUtkB/cBSkOdQTVEaLcMwU8l3A==", 6196 "integrity": "sha512-FOBJIHkuv8wc15N+ZyqwDzPavYVu5CHMBEf14jHDWv7QW2vkEIpJjVK+PIT31kfZfvjsIP0j2wvA/FBsiqB7pw==",
6160 "dev": true, 6197 "dev": true,
6161 "requires": { 6198 "requires": {
6162 "@types/node": "^10.12.18", 6199 "@types/node": "^10.12.18",
@@ -6165,9 +6202,9 @@
6165 }, 6202 },
6166 "dependencies": { 6203 "dependencies": {
6167 "@types/node": { 6204 "@types/node": {
6168 "version": "10.12.29", 6205 "version": "10.12.30",
6169 "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.29.tgz", 6206 "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz",
6170 "integrity": "sha512-J/tnbnj8HcsBgCe2apZbdUpQ7hs4d7oZNTYA5bekWdP0sr2NGsOpI/HRdDroEi209tEvTcTtxhD0FfED3DhEcw==", 6207 "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==",
6171 "dev": true 6208 "dev": true
6172 } 6209 }
6173 } 6210 }
@@ -11454,11 +11491,6 @@
11454 "integrity": "sha1-P55JkK3K0MaGwOcB92RYaPdfkes=", 11491 "integrity": "sha1-P55JkK3K0MaGwOcB92RYaPdfkes=",
11455 "dev": true 11492 "dev": true
11456 }, 11493 },
11457 "keymaster": {
11458 "version": "1.6.2",
11459 "resolved": "https://registry.npmjs.org/keymaster/-/keymaster-1.6.2.tgz",
11460 "integrity": "sha1-4a5U0OqUiPn2C2a2aPAumhlGxus="
11461 },
11462 "killable": { 11494 "killable": {
11463 "version": "1.0.1", 11495 "version": "1.0.1",
11464 "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", 11496 "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -14571,7 +14603,8 @@
14571 "process-nextick-args": { 14603 "process-nextick-args": {
14572 "version": "2.0.0", 14604 "version": "2.0.0",
14573 "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 14605 "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
14574 "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 14606 "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
14607 "dev": true
14575 }, 14608 },
14576 "progress": { 14609 "progress": {
14577 "version": "2.0.3", 14610 "version": "2.0.3",
diff --git a/package.json b/package.json
index d3cd8ea7b..ec135ca77 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,6 @@
53 "fs-extra": "7.0.1", 53 "fs-extra": "7.0.1",
54 "hex-to-rgba": "1.0.2", 54 "hex-to-rgba": "1.0.2",
55 "jsonwebtoken": "^7.4.1", 55 "jsonwebtoken": "^7.4.1",
56 "keymaster": "^1.6.2",
57 "lodash": "^4.17.4", 56 "lodash": "^4.17.4",
58 "mdi": "^1.9.33", 57 "mdi": "^1.9.33",
59 "mime-types": "2.1.21", 58 "mime-types": "2.1.21",
@@ -114,7 +113,7 @@
114 "cross-env": "^5.0.5", 113 "cross-env": "^5.0.5",
115 "cz-conventional-changelog": "2.1.0", 114 "cz-conventional-changelog": "2.1.0",
116 "dotenv": "^4.0.0", 115 "dotenv": "^4.0.0",
117 "electron": "4.0.7", 116 "electron": "4.0.8",
118 "electron-builder": "20.38.4", 117 "electron-builder": "20.38.4",
119 "electron-rebuild": "1.8.4", 118 "electron-rebuild": "1.8.4",
120 "eslint": "5.10.0", 119 "eslint": "5.10.0",
diff --git a/src/actions/service.js b/src/actions/service.js
index 5d483b12a..ceaabc31e 100644
--- a/src/actions/service.js
+++ b/src/actions/service.js
@@ -1,4 +1,5 @@
1import PropTypes from 'prop-types'; 1import PropTypes from 'prop-types';
2import ServiceModel from '../models/Service';
2 3
3export default { 4export default {
4 setActive: { 5 setActive: {
@@ -36,6 +37,9 @@ export default {
36 serviceId: PropTypes.string.isRequired, 37 serviceId: PropTypes.string.isRequired,
37 webview: PropTypes.object.isRequired, 38 webview: PropTypes.object.isRequired,
38 }, 39 },
40 detachService: {
41 service: PropTypes.instanceOf(ServiceModel).isRequired,
42 },
39 focusService: { 43 focusService: {
40 serviceId: PropTypes.string.isRequired, 44 serviceId: PropTypes.string.isRequired,
41 }, 45 },
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
new file mode 100644
index 000000000..5afc54f9d
--- /dev/null
+++ b/src/components/services/content/ServiceView.js
@@ -0,0 +1,136 @@
1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react';
5import classnames from 'classnames';
6
7import ServiceModel from '../../../models/Service';
8import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
9import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler';
11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
12import ServiceDisabled from './ServiceDisabled';
13import ServiceWebview from './ServiceWebview';
14
15export default @observer class ServiceView extends Component {
16 static propTypes = {
17 service: PropTypes.instanceOf(ServiceModel).isRequired,
18 setWebviewReference: PropTypes.func.isRequired,
19 detachService: PropTypes.func.isRequired,
20 reload: PropTypes.func.isRequired,
21 edit: PropTypes.func.isRequired,
22 enable: PropTypes.func.isRequired,
23 isActive: PropTypes.bool,
24 };
25
26 static defaultProps = {
27 isActive: false,
28 };
29
30 state = {
31 forceRepaint: false,
32 targetUrl: '',
33 statusBarVisible: false,
34 };
35
36 autorunDisposer = null;
37
38 componentDidMount() {
39 this.autorunDisposer = autorun(() => {
40 if (this.props.service.isActive) {
41 this.setState({ forceRepaint: true });
42 setTimeout(() => {
43 this.setState({ forceRepaint: false });
44 }, 100);
45 }
46 });
47 }
48
49 componentWillUnmount() {
50 this.autorunDisposer();
51 }
52
53 updateTargetUrl = (event) => {
54 let visible = true;
55 if (event.url === '' || event.url === '#') {
56 visible = false;
57 }
58 this.setState({
59 targetUrl: event.url,
60 statusBarVisible: visible,
61 });
62 };
63
64 render() {
65 const {
66 detachService,
67 service,
68 setWebviewReference,
69 reload,
70 edit,
71 enable,
72 } = this.props;
73
74 const webviewClasses = classnames({
75 services__webview: true,
76 'services__webview-wrapper': true,
77 'is-active': service.isActive,
78 'services__webview--force-repaint': this.state.forceRepaint,
79 });
80
81 let statusBar = null;
82 if (this.state.statusBarVisible) {
83 statusBar = (
84 <StatusBarTargetUrl text={this.state.targetUrl} />
85 );
86 }
87
88 return (
89 <div className={webviewClasses}>
90 {service.isActive && service.isEnabled && (
91 <Fragment>
92 {service.hasCrashed && (
93 <WebviewCrashHandler
94 name={service.recipe.name}
95 webview={service.webview}
96 reload={reload}
97 />
98 )}
99 {service.isEnabled && service.isLoading && service.isFirstLoad && (
100 <WebviewLoader
101 loaded={false}
102 name={service.name}
103 />
104 )}
105 {service.isError && (
106 <WebviewErrorHandler
107 name={service.recipe.name}
108 errorMessage={service.errorMessage}
109 reload={reload}
110 edit={edit}
111 />
112 )}
113 </Fragment>
114 )}
115 {!service.isEnabled ? (
116 <Fragment>
117 {service.isActive && (
118 <ServiceDisabled
119 name={service.recipe.name}
120 webview={service.webview}
121 enable={enable}
122 />
123 )}
124 </Fragment>
125 ) : (
126 <ServiceWebview
127 service={service}
128 setWebviewReference={setWebviewReference}
129 detachService={detachService}
130 />
131 )}
132 {statusBar}
133 </div>
134 );
135 }
136}
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index bb577e4cc..7252c695f 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -1,145 +1,50 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
5import Webview from 'react-electron-web-view'; 4import ElectronWebView from 'react-electron-web-view';
6import classnames from 'classnames';
7 5
8import ServiceModel from '../../../models/Service'; 6import ServiceModel from '../../../models/Service';
9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
10import WebviewLoader from '../../ui/WebviewLoader';
11import WebviewCrashHandler from './WebviewCrashHandler';
12import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
13import ServiceDisabled from './ServiceDisabled';
14 7
15export default @observer class ServiceWebview extends Component { 8@observer
9class ServiceWebview extends Component {
16 static propTypes = { 10 static propTypes = {
17 service: PropTypes.instanceOf(ServiceModel).isRequired, 11 service: PropTypes.instanceOf(ServiceModel).isRequired,
18 setWebviewReference: PropTypes.func.isRequired, 12 setWebviewReference: PropTypes.func.isRequired,
19 reload: PropTypes.func.isRequired, 13 detachService: PropTypes.func.isRequired,
20 edit: PropTypes.func.isRequired,
21 enable: PropTypes.func.isRequired,
22 isActive: PropTypes.bool,
23 }; 14 };
24 15
25 static defaultProps = {
26 isActive: false,
27 };
28
29 state = {
30 forceRepaint: false,
31 targetUrl: '',
32 statusBarVisible: false,
33 };
34
35 autorunDisposer = null;
36
37 webview = null; 16 webview = null;
38 17
39 componentDidMount() {
40 this.autorunDisposer = autorun(() => {
41 if (this.props.service.isActive) {
42 this.setState({ forceRepaint: true });
43 setTimeout(() => {
44 this.setState({ forceRepaint: false });
45 }, 100);
46 }
47 });
48 }
49
50 componentWillUnmount() { 18 componentWillUnmount() {
51 this.autorunDisposer(); 19 const { service, detachService } = this.props;
52 } 20 detachService({ service });
53
54 updateTargetUrl = (event) => {
55 let visible = true;
56 if (event.url === '' || event.url === '#') {
57 visible = false;
58 }
59 this.setState({
60 targetUrl: event.url,
61 statusBarVisible: visible,
62 });
63 } 21 }
64 22
65 render() { 23 render() {
66 const { 24 const {
67 service, 25 service,
68 setWebviewReference, 26 setWebviewReference,
69 reload,
70 edit,
71 enable,
72 } = this.props; 27 } = this.props;
73 28
74 const webviewClasses = classnames({
75 services__webview: true,
76 'services__webview-wrapper': true,
77 'is-active': service.isActive,
78 'services__webview--force-repaint': this.state.forceRepaint,
79 });
80
81 let statusBar = null;
82 if (this.state.statusBarVisible) {
83 statusBar = (
84 <StatusBarTargetUrl text={this.state.targetUrl} />
85 );
86 }
87
88 return ( 29 return (
89 <div className={webviewClasses}> 30 <ElectronWebView
90 {service.isActive && service.isEnabled && ( 31 ref={(webview) => { this.webview = webview; }}
91 <Fragment> 32 autosize
92 {service.hasCrashed && ( 33 src={service.url}
93 <WebviewCrashHandler 34 preload="./webview/recipe.js"
94 name={service.recipe.name} 35 partition={`persist:service-${service.id}`}
95 webview={service.webview} 36 onDidAttach={() => {
96 reload={reload} 37 setWebviewReference({
97 /> 38 serviceId: service.id,
98 )} 39 webview: this.webview.view,
99 {service.isEnabled && service.isLoading && service.isFirstLoad && ( 40 });
100 <WebviewLoader 41 }}
101 loaded={false} 42 onUpdateTargetUrl={this.updateTargetUrl}
102 name={service.name} 43 useragent={service.userAgent}
103 /> 44 allowpopups
104 )} 45 />
105 {service.isError && (
106 <WebviewErrorHandler
107 name={service.recipe.name}
108 errorMessage={service.errorMessage}
109 reload={reload}
110 edit={edit}
111 />
112 )}
113 </Fragment>
114 )}
115 {!service.isEnabled ? (
116 <Fragment>
117 {service.isActive && (
118 <ServiceDisabled
119 name={service.recipe.name}
120 webview={service.webview}
121 enable={enable}
122 />
123 )}
124 </Fragment>
125 ) : (
126 <Webview
127 ref={(element) => { this.webview = element; }}
128 autosize
129 src={service.url}
130 preload="./webview/recipe.js"
131 partition={`persist:service-${service.id}`}
132 onDidAttach={() => setWebviewReference({
133 serviceId: service.id,
134 webview: this.webview.view,
135 })}
136 onUpdateTargetUrl={this.updateTargetUrl}
137 useragent={service.userAgent}
138 allowpopups
139 />
140 )}
141 {statusBar}
142 </div>
143 ); 46 );
144 } 47 }
145} 48}
49
50export default ServiceWebview;
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 54f16ba12..8f8c38a11 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -4,7 +4,7 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, intlShape } from 'react-intl';
6 6
7import Webview from './ServiceWebview'; 7import ServiceView from './ServiceView';
8import Appear from '../../ui/effects/Appear'; 8import Appear from '../../ui/effects/Appear';
9 9
10const messages = defineMessages({ 10const messages = defineMessages({
@@ -22,6 +22,7 @@ export default @observer class Services extends Component {
22 static propTypes = { 22 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray, 23 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 24 setWebviewReference: PropTypes.func.isRequired,
25 detachService: PropTypes.func.isRequired,
25 handleIPCMessage: PropTypes.func.isRequired, 26 handleIPCMessage: PropTypes.func.isRequired,
26 openWindow: PropTypes.func.isRequired, 27 openWindow: PropTypes.func.isRequired,
27 reload: PropTypes.func.isRequired, 28 reload: PropTypes.func.isRequired,
@@ -42,6 +43,7 @@ export default @observer class Services extends Component {
42 services, 43 services,
43 handleIPCMessage, 44 handleIPCMessage,
44 setWebviewReference, 45 setWebviewReference,
46 detachService,
45 openWindow, 47 openWindow,
46 reload, 48 reload,
47 openSettings, 49 openSettings,
@@ -71,11 +73,12 @@ export default @observer class Services extends Component {
71 </Appear> 73 </Appear>
72 )} 74 )}
73 {services.map(service => ( 75 {services.map(service => (
74 <Webview 76 <ServiceView
75 key={service.id} 77 key={service.id}
76 service={service} 78 service={service}
77 handleIPCMessage={handleIPCMessage} 79 handleIPCMessage={handleIPCMessage}
78 setWebviewReference={setWebviewReference} 80 setWebviewReference={setWebviewReference}
81 detachService={detachService}
79 openWindow={openWindow} 82 openWindow={openWindow}
80 reload={() => reload({ serviceId: service.id })} 83 reload={() => reload({ serviceId: service.id })}
81 edit={() => openSettings({ path: `services/edit/${service.id}` })} 84 edit={() => openSettings({ path: `services/edit/${service.id}` })}
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 749912c59..5a05ce431 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
42 setActive, 42 setActive,
43 handleIPCMessage, 43 handleIPCMessage,
44 setWebviewReference, 44 setWebviewReference,
45 detachService,
45 openWindow, 46 openWindow,
46 reorder, 47 reorder,
47 reload, 48 reload,
@@ -105,6 +106,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
105 services={services.allDisplayedUnordered} 106 services={services.allDisplayedUnordered}
106 handleIPCMessage={handleIPCMessage} 107 handleIPCMessage={handleIPCMessage}
107 setWebviewReference={setWebviewReference} 108 setWebviewReference={setWebviewReference}
109 detachService={detachService}
108 openWindow={openWindow} 110 openWindow={openWindow}
109 reload={reload} 111 reload={reload}
110 openSettings={openSettings} 112 openSettings={openSettings}
@@ -160,6 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
160 toggleAudio: PropTypes.func.isRequired, 162 toggleAudio: PropTypes.func.isRequired,
161 handleIPCMessage: PropTypes.func.isRequired, 163 handleIPCMessage: PropTypes.func.isRequired,
162 setWebviewReference: PropTypes.func.isRequired, 164 setWebviewReference: PropTypes.func.isRequired,
165 detachService: PropTypes.func.isRequired,
163 openWindow: PropTypes.func.isRequired, 166 openWindow: PropTypes.func.isRequired,
164 reloadUpdatedServices: PropTypes.func.isRequired, 167 reloadUpdatedServices: PropTypes.func.isRequired,
165 updateService: PropTypes.func.isRequired, 168 updateService: PropTypes.func.isRequired,
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 065398dc6..0641c510c 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -3875,6 +3875,58 @@
3875 "column": 17, 3875 "column": 17,
3876 "line": 178 3876 "line": 178
3877 } 3877 }
3878 },
3879 {
3880 "defaultMessage": "!!!Activate next service...",
3881 "end": {
3882 "column": 3,
3883 "line": 185
3884 },
3885 "file": "src/lib/Menu.js",
3886 "id": "menu.services.setNextServiceActive",
3887 "start": {
3888 "column": 23,
3889 "line": 182
3890 }
3891 },
3892 {
3893 "defaultMessage": "!!!Activate previous service...",
3894 "end": {
3895 "column": 3,
3896 "line": 189
3897 },
3898 "file": "src/lib/Menu.js",
3899 "id": "menu.services.activatePreviousService",
3900 "start": {
3901 "column": 27,
3902 "line": 186
3903 }
3904 },
3905 {
3906 "defaultMessage": "!!!Disable notifications & audio",
3907 "end": {
3908 "column": 3,
3909 "line": 193
3910 },
3911 "file": "src/lib/Menu.js",
3912 "id": "sidebar.muteApp",
3913 "start": {
3914 "column": 11,
3915 "line": 190
3916 }
3917 },
3918 {
3919 "defaultMessage": "!!!Enable notifications & audio",
3920 "end": {
3921 "column": 3,
3922 "line": 197
3923 },
3924 "file": "src/lib/Menu.js",
3925 "id": "sidebar.unmuteApp",
3926 "start": {
3927 "column": 13,
3928 "line": 194
3929 }
3878 } 3930 }
3879 ], 3931 ],
3880 "path": "src/lib/Menu.json" 3932 "path": "src/lib/Menu.json"
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index af6edd2df..7543d38bd 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -70,7 +70,9 @@
70 "menu.help.support": "Support", 70 "menu.help.support": "Support",
71 "menu.help.tos": "Terms of Service", 71 "menu.help.tos": "Terms of Service",
72 "menu.services": "Services", 72 "menu.services": "Services",
73 "menu.services.activatePreviousService": "Activate previous service",
73 "menu.services.addNewService": "Add New Service...", 74 "menu.services.addNewService": "Add New Service...",
75 "menu.services.setNextServiceActive": "Activate next service",
74 "menu.view": "View", 76 "menu.view": "View",
75 "menu.view.enterFullScreen": "Enter Full Screen", 77 "menu.view.enterFullScreen": "Enter Full Screen",
76 "menu.view.exitFullScreen": "Exit Full Screen", 78 "menu.view.exitFullScreen": "Exit Full Screen",
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index 6958f0219..9314f5cce 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -557,5 +557,57 @@
557 "line": 181, 557 "line": 181,
558 "column": 3 558 "column": 3
559 } 559 }
560 },
561 {
562 "id": "menu.services.setNextServiceActive",
563 "defaultMessage": "!!!Activate next service...",
564 "file": "src/lib/Menu.js",
565 "start": {
566 "line": 182,
567 "column": 23
568 },
569 "end": {
570 "line": 185,
571 "column": 3
572 }
573 },
574 {
575 "id": "menu.services.activatePreviousService",
576 "defaultMessage": "!!!Activate previous service...",
577 "file": "src/lib/Menu.js",
578 "start": {
579 "line": 186,
580 "column": 27
581 },
582 "end": {
583 "line": 189,
584 "column": 3
585 }
586 },
587 {
588 "id": "sidebar.muteApp",
589 "defaultMessage": "!!!Disable notifications & audio",
590 "file": "src/lib/Menu.js",
591 "start": {
592 "line": 190,
593 "column": 11
594 },
595 "end": {
596 "line": 193,
597 "column": 3
598 }
599 },
600 {
601 "id": "sidebar.unmuteApp",
602 "defaultMessage": "!!!Enable notifications & audio",
603 "file": "src/lib/Menu.js",
604 "start": {
605 "line": 194,
606 "column": 13
607 },
608 "end": {
609 "line": 197,
610 "column": 3
611 }
560 } 612 }
561] \ No newline at end of file 613] \ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 0e222c3d6..05c793d98 100644
--- a/src/index.js
+++ b/src/index.js
@@ -72,7 +72,10 @@ if (!gotTheLock) {
72 app.on('second-instance', (event, argv) => { 72 app.on('second-instance', (event, argv) => {
73 // Someone tried to run a second instance, we should focus our window. 73 // Someone tried to run a second instance, we should focus our window.
74 if (mainWindow) { 74 if (mainWindow) {
75 if (mainWindow.isMinimized()) mainWindow.restore(); 75 mainWindow.show();
76 if (mainWindow.isMinimized()) {
77 mainWindow.restore();
78 }
76 mainWindow.focus(); 79 mainWindow.focus();
77 80
78 if (isWindows) { 81 if (isWindows) {
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index dce8ab969..7a60c448f 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -1,5 +1,5 @@
1import { remote, shell } from 'electron'; 1import { remote, shell } from 'electron';
2import { observable, autorun, computed } from 'mobx'; 2import { observable, autorun } from 'mobx';
3import { defineMessages } from 'react-intl'; 3import { defineMessages } from 'react-intl';
4 4
5import { isMac, ctrlKey, cmdKey } from '../environment'; 5import { isMac, ctrlKey, cmdKey } from '../environment';
@@ -179,6 +179,22 @@ const menuItems = defineMessages({
179 id: 'menu.services.addNewService', 179 id: 'menu.services.addNewService',
180 defaultMessage: '!!!Add New Service...', 180 defaultMessage: '!!!Add New Service...',
181 }, 181 },
182 activateNextService: {
183 id: 'menu.services.setNextServiceActive',
184 defaultMessage: '!!!Activate next service...',
185 },
186 activatePreviousService: {
187 id: 'menu.services.activatePreviousService',
188 defaultMessage: '!!!Activate previous service...',
189 },
190 muteApp: {
191 id: 'sidebar.muteApp',
192 defaultMessage: '!!!Disable notifications & audio',
193 },
194 unmuteApp: {
195 id: 'sidebar.unmuteApp',
196 defaultMessage: '!!!Enable notifications & audio',
197 },
182}); 198});
183 199
184function getActiveWebview() { 200function getActiveWebview() {
@@ -408,7 +424,7 @@ const _titleBarTemplateFactory = intl => [
408 }, 424 },
409 { 425 {
410 label: intl.formatMessage(menuItems.zoomIn), 426 label: intl.formatMessage(menuItems.zoomIn),
411 accelerator: `${ctrlKey}+Plus`, 427 accelerator: `${ctrlKey}+=`,
412 click() { 428 click() {
413 const activeService = getActiveWebview(); 429 const activeService = getActiveWebview();
414 activeService.getZoomLevel((level) => { 430 activeService.getZoomLevel((level) => {
@@ -519,13 +535,14 @@ export default class FranzMenu {
519 } 535 }
520 536
521 _build() { 537 _build() {
522 const serviceTpl = Object.assign([], this.serviceTpl); // need to clone object so we don't modify computed (cached) object 538 // need to clone object so we don't modify computed (cached) object
539 const serviceTpl = Object.assign([], this.serviceTpl());
523 540
524 if (window.franz === undefined) { 541 if (window.franz === undefined) {
525 return; 542 return;
526 } 543 }
527 544
528 const intl = window.franz.intl; 545 const { intl } = window.franz;
529 const tpl = isMac ? _templateFactory(intl) : _titleBarTemplateFactory(intl); 546 const tpl = isMac ? _templateFactory(intl) : _titleBarTemplateFactory(intl);
530 547
531 tpl[1].submenu.push({ 548 tpl[1].submenu.push({
@@ -683,17 +700,6 @@ export default class FranzMenu {
683 }, about); 700 }, about);
684 } 701 }
685 702
686 serviceTpl.unshift({
687 label: intl.formatMessage(menuItems.addNewService),
688 accelerator: `${cmdKey}+N`,
689 click: () => {
690 this.actions.ui.openSettings({ path: 'recipes' });
691 },
692 enabled: this.stores.user.isLoggedIn,
693 }, {
694 type: 'separator',
695 });
696
697 if (serviceTpl.length > 0) { 703 if (serviceTpl.length > 0) {
698 tpl[3].submenu = serviceTpl; 704 tpl[3].submenu = serviceTpl;
699 } 705 }
@@ -703,22 +709,49 @@ export default class FranzMenu {
703 Menu.setApplicationMenu(menu); 709 Menu.setApplicationMenu(menu);
704 } 710 }
705 711
706 @computed get serviceTpl() { 712 serviceTpl() {
707 const services = this.stores.services.allDisplayed; 713 const { intl } = window.franz;
714 const { user, services, settings } = this.stores;
715 if (!user.isLoggedIn) return [];
716 const menu = [];
708 717
709 if (this.stores.user.isLoggedIn) { 718 menu.push({
710 return services.map((service, i) => ({ 719 label: intl.formatMessage(menuItems.addNewService),
711 label: this._getServiceName(service), 720 accelerator: `${cmdKey}+N`,
712 accelerator: i < 9 ? `${cmdKey}+${i + 1}` : null, 721 click: () => {
713 type: 'radio', 722 this.actions.ui.openSettings({ path: 'recipes' });
714 checked: service.isActive, 723 },
715 click: () => { 724 }, {
716 this.actions.service.setActive({ serviceId: service.id }); 725 type: 'separator',
717 }, 726 }, {
718 })); 727 label: intl.formatMessage(menuItems.activateNextService),
719 } 728 accelerator: `${cmdKey}+alt+right`,
729 click: () => this.actions.service.setActiveNext(),
730 }, {
731 label: intl.formatMessage(menuItems.activatePreviousService),
732 accelerator: `${cmdKey}+alt+left`,
733 click: () => this.actions.service.setActivePrev(),
734 }, {
735 label: intl.formatMessage(
736 settings.all.app.isAppMuted ? menuItems.unmuteApp : menuItems.muteApp,
737 ).replace('&', '&&'),
738 accelerator: `${cmdKey}+shift+m`,
739 click: () => this.actions.app.toggleMuteApp(),
740 }, {
741 type: 'separator',
742 });
743
744 services.allDisplayed.forEach((service, i) => (menu.push({
745 label: this._getServiceName(service),
746 accelerator: i < 9 ? `${cmdKey}+${i + 1}` : null,
747 type: 'radio',
748 checked: service.isActive,
749 click: () => {
750 this.actions.service.setActive({ serviceId: service.id });
751 },
752 })));
720 753
721 return []; 754 return menu;
722 } 755 }
723 756
724 _getServiceName(service) { 757 _getServiceName(service) {
diff --git a/src/lib/Tray.js b/src/lib/Tray.js
index 669b02709..192e24796 100644
--- a/src/lib/Tray.js
+++ b/src/lib/Tray.js
@@ -22,7 +22,11 @@ export default class TrayIcon {
22 { 22 {
23 label: 'Show Franz', 23 label: 'Show Franz',
24 click() { 24 click() {
25 if (app.mainWindow.isMinimized()) {
26 app.mainWindow.restore();
27 }
25 app.mainWindow.show(); 28 app.mainWindow.show();
29 app.mainWindow.focus();
26 }, 30 },
27 }, { 31 }, {
28 label: 'Quit Franz', 32 label: 'Quit Franz',
@@ -36,7 +40,11 @@ export default class TrayIcon {
36 this.trayIcon.setContextMenu(trayMenu); 40 this.trayIcon.setContextMenu(trayMenu);
37 41
38 this.trayIcon.on('click', () => { 42 this.trayIcon.on('click', () => {
43 if (app.mainWindow.isMinimized()) {
44 app.mainWindow.restore();
45 }
39 app.mainWindow.show(); 46 app.mainWindow.show();
47 app.mainWindow.focus();
40 }); 48 });
41 49
42 if (process.platform === 'darwin') { 50 if (process.platform === 'darwin') {
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index d933ca407..351ad6422 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -3,7 +3,6 @@ import {
3 action, computed, observable, reaction, 3 action, computed, observable, reaction,
4} from 'mobx'; 4} from 'mobx';
5import moment from 'moment'; 5import moment from 'moment';
6import key from 'keymaster';
7import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; 6import { getDoNotDisturb } from '@meetfranz/electron-notification-state';
8import AutoLaunch from 'auto-launch'; 7import AutoLaunch from 'auto-launch';
9import prettyBytes from 'pretty-bytes'; 8import prettyBytes from 'pretty-bytes';
@@ -13,7 +12,7 @@ import { URL } from 'url';
13import Store from './lib/Store'; 12import Store from './lib/Store';
14import Request from './lib/Request'; 13import Request from './lib/Request';
15import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; 14import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config';
16import { isMac, isLinux, isWindows } from '../environment'; 15import { isMac } from '../environment';
17import locales from '../i18n/translations'; 16import locales from '../i18n/translations';
18import { gaEvent, gaPage } from '../lib/analytics'; 17import { gaEvent, gaPage } from '../lib/analytics';
19import { onVisibilityChange } from '../helpers/visibility-helper'; 18import { onVisibilityChange } from '../helpers/visibility-helper';
@@ -157,41 +156,6 @@ export default class AppStore extends Store {
157 this.stores.router.push(url); 156 this.stores.router.push(url);
158 }); 157 });
159 158
160 // Set active the next service
161 key(
162 '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => {
163 this.actions.service.setActiveNext();
164 },
165 );
166
167 // Set active the prev service
168 key(
169 '⌘+pageup, ctrl+pageup, ⌘+alt+left, ctrl+shift+tab', () => {
170 this.actions.service.setActivePrev();
171 },
172 );
173
174 // Global Mute
175 key(
176 '⌘+shift+m ctrl+shift+m', () => {
177 this.actions.app.toggleMuteApp();
178 },
179 );
180
181 // We need to add an additional key listener for ctrl+ on windows. Otherwise only ctrl+shift+ would work
182 if (isWindows) {
183 key(
184 'ctrl+=', () => {
185 debug('Windows: zoom in via ctrl+');
186 const { webview } = this.stores.services.active;
187 webview.getZoomLevel((level) => {
188 // level 9 =~ +300% and setZoomLevel wouldnt zoom in further
189 if (level < 9) webview.setZoomLevel(level + 1);
190 });
191 },
192 );
193 }
194
195 this.locale = this._getDefaultLocale(); 159 this.locale = this._getDefaultLocale();
196 160
197 this._healthCheck(); 161 this._healthCheck();
@@ -221,7 +185,15 @@ export default class AppStore extends Store {
221 }) { 185 }) {
222 if (this.stores.settings.all.app.isAppMuted) return; 186 if (this.stores.settings.all.app.isAppMuted) return;
223 187
188 // TODO: is there a simple way to use blobs for notifications without storing them on disk?
189 if (options.icon.startsWith('blob:')) {
190 delete options.icon;
191 }
192
224 const notification = new window.Notification(title, options); 193 const notification = new window.Notification(title, options);
194
195 debug('New notification', title, options);
196
225 notification.onclick = (e) => { 197 notification.onclick = (e) => {
226 if (serviceId) { 198 if (serviceId) {
227 this.actions.service.sendIPCMessage({ 199 this.actions.service.sendIPCMessage({
@@ -231,12 +203,13 @@ export default class AppStore extends Store {
231 }); 203 });
232 204
233 this.actions.service.setActive({ serviceId }); 205 this.actions.service.setActive({ serviceId });
234 206 mainWindow.show();
235 if (isWindows) { 207 if (app.mainWindow.isMinimized()) {
236 mainWindow.restore(); 208 mainWindow.restore();
237 } else if (isLinux) {
238 mainWindow.show();
239 } 209 }
210 mainWindow.focus();
211
212 debug('Notification click handler');
240 } 213 }
241 }; 214 };
242 } 215 }
@@ -303,7 +276,6 @@ export default class AppStore extends Store {
303 276
304 @action _muteApp({ isMuted, overrideSystemMute = true }) { 277 @action _muteApp({ isMuted, overrideSystemMute = true }) {
305 this.isSystemMuteOverridden = overrideSystemMute; 278 this.isSystemMuteOverridden = overrideSystemMute;
306
307 this.actions.settings.update({ 279 this.actions.settings.update({
308 type: 'app', 280 type: 'app',
309 data: { 281 data: {
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index c63bef196..69e616f0c 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -44,6 +44,7 @@ export default class ServicesStore extends Store {
44 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 44 this.actions.service.deleteService.listen(this._deleteService.bind(this));
45 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 45 this.actions.service.clearCache.listen(this._clearCache.bind(this));
46 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 46 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this));
47 this.actions.service.detachService.listen(this._detachService.bind(this));
47 this.actions.service.focusService.listen(this._focusService.bind(this)); 48 this.actions.service.focusService.listen(this._focusService.bind(this));
48 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 49 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this));
49 this.actions.service.toggleService.listen(this._toggleService.bind(this)); 50 this.actions.service.toggleService.listen(this._toggleService.bind(this));
@@ -341,6 +342,11 @@ export default class ServicesStore extends Store {
341 service.isAttached = true; 342 service.isAttached = true;
342 } 343 }
343 344
345 @action _detachService({ service }) {
346 service.webview = null;
347 service.isAttached = false;
348 }
349
344 @action _focusService({ serviceId }) { 350 @action _focusService({ serviceId }) {
345 const service = this.one(serviceId); 351 const service = this.one(serviceId);
346 352
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index afb1d8912..a4a6ab899 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -33,6 +33,8 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
33 const canGoBack = webContents.canGoBack(); 33 const canGoBack = webContents.canGoBack();
34 const canGoForward = webContents.canGoForward(); 34 const canGoForward = webContents.canGoForward();
35 35
36 // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one
37 // Github issue creation is pending
36 let menuTpl = [ 38 let menuTpl = [
37 { 39 {
38 type: 'separator', 40 type: 'separator',
@@ -48,19 +50,32 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
48 type: 'separator', 50 type: 'separator',
49 }, { 51 }, {
50 id: 'cut', 52 id: 'cut',
51 role: can('Cut') ? 'cut' : '', 53 label: 'Cut',
54 click() {
55 if (can('Cut')) {
56 webContents.cut();
57 }
58 },
52 enabled: can('Cut'), 59 enabled: can('Cut'),
53 visible: hasText && props.isEditable, 60 visible: hasText && props.isEditable,
54 }, { 61 }, {
55 id: 'copy', 62 id: 'copy',
56 label: 'Copy', 63 label: 'Copy',
57 role: can('Copy') ? 'copy' : '', 64 click() {
65 if (can('Copy')) {
66 webContents.copy();
67 }
68 },
58 enabled: can('Copy'), 69 enabled: can('Copy'),
59 visible: props.isEditable || hasText, 70 visible: props.isEditable || hasText,
60 }, { 71 }, {
61 id: 'paste', 72 id: 'paste',
62 label: 'Paste', 73 label: 'Paste',
63 role: editFlags.canPaste ? 'paste' : '', 74 click() {
75 if (editFlags.canPaste) {
76 webContents.paste();
77 }
78 },
64 enabled: editFlags.canPaste, 79 enabled: editFlags.canPaste,
65 visible: props.isEditable, 80 visible: props.isEditable,
66 }, { 81 }, {