diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-10-03 17:15:46 +0200 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-10-03 17:15:46 +0200 |
commit | 6e5bf64ef3ef858a5cc40025f61f3b1cf550d4fe (patch) | |
tree | 0429f9b7af2ff312dfbdb4b184a6cbeec28969ca /src | |
parent | Automatic i18n update (i18n.meetfranz.com) (diff) | |
parent | update strings (diff) | |
download | ferdium-app-6e5bf64ef3ef858a5cc40025f61f3b1cf550d4fe.tar.gz ferdium-app-6e5bf64ef3ef858a5cc40025f61f3b1cf550d4fe.tar.zst ferdium-app-6e5bf64ef3ef858a5cc40025f61f3b1cf550d4fe.zip |
Merge branch 'release/5.4.0' into i18n
Diffstat (limited to 'src')
28 files changed, 711 insertions, 111 deletions
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index ab1604a27..383f38b16 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 bac57d4dc..918298011 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -112,7 +112,7 @@ export default @observer class Sidebar extends Component { | |||
112 | this.updateToolTip(); | 112 | this.updateToolTip(); |
113 | gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'sidebar'); | 113 | gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'sidebar'); |
114 | }} | 114 | }} |
115 | className="sidebar__button sidebar__button--workspaces" | 115 | className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} |
116 | data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`} | 116 | data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`} |
117 | > | 117 | > |
118 | <i className="mdi mdi-check-all" /> | 118 | <i className="mdi mdi-check-all" /> |
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index f65f51346..e8df58a1e 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js | |||
@@ -12,6 +12,7 @@ import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | |||
12 | import ServiceDisabled from './ServiceDisabled'; | 12 | import ServiceDisabled from './ServiceDisabled'; |
13 | import ServiceRestricted from './ServiceRestricted'; | 13 | import ServiceRestricted from './ServiceRestricted'; |
14 | import ServiceWebview from './ServiceWebview'; | 14 | import ServiceWebview from './ServiceWebview'; |
15 | import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; | ||
15 | 16 | ||
16 | export default @observer class ServiceView extends Component { | 17 | export default @observer class ServiceView extends Component { |
17 | static propTypes = { | 18 | static propTypes = { |
@@ -137,11 +138,16 @@ export default @observer class ServiceView extends Component { | |||
137 | type={service.restrictionType} | 138 | type={service.restrictionType} |
138 | /> | 139 | /> |
139 | ) : ( | 140 | ) : ( |
140 | <ServiceWebview | 141 | <> |
141 | service={service} | 142 | {service.recipe.id === 'franz-custom-website' && ( |
142 | setWebviewReference={setWebviewReference} | 143 | <WebControlsScreen service={service} /> |
143 | detachService={detachService} | 144 | )} |
144 | /> | 145 | <ServiceWebview |
146 | service={service} | ||
147 | setWebviewReference={setWebviewReference} | ||
148 | detachService={detachService} | ||
149 | /> | ||
150 | </> | ||
145 | )} | 151 | )} |
146 | </> | 152 | </> |
147 | )} | 153 | )} |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 7252c695f..b3198d36a 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -20,6 +20,13 @@ class ServiceWebview extends Component { | |||
20 | detachService({ service }); | 20 | detachService({ service }); |
21 | } | 21 | } |
22 | 22 | ||
23 | refocusWebview = () => { | ||
24 | const { webview } = this; | ||
25 | if (!webview) return; | ||
26 | webview.view.blur(); | ||
27 | webview.view.focus(); | ||
28 | }; | ||
29 | |||
23 | render() { | 30 | render() { |
24 | const { | 31 | const { |
25 | service, | 32 | service, |
@@ -28,7 +35,10 @@ class ServiceWebview extends Component { | |||
28 | 35 | ||
29 | return ( | 36 | return ( |
30 | <ElectronWebView | 37 | <ElectronWebView |
31 | ref={(webview) => { this.webview = webview; }} | 38 | ref={(webview) => { |
39 | this.webview = webview; | ||
40 | webview.view.addEventListener('did-stop-loading', this.refocusWebview); | ||
41 | }} | ||
32 | autosize | 42 | autosize |
33 | src={service.url} | 43 | src={service.url} |
34 | preload="./webview/recipe.js" | 44 | preload="./webview/recipe.js" |
@@ -41,6 +51,7 @@ class ServiceWebview extends Component { | |||
41 | }} | 51 | }} |
42 | onUpdateTargetUrl={this.updateTargetUrl} | 52 | onUpdateTargetUrl={this.updateTargetUrl} |
43 | useragent={service.userAgent} | 53 | useragent={service.userAgent} |
54 | disablewebsecurity={service.recipe.disablewebsecurity} | ||
44 | allowpopups | 55 | allowpopups |
45 | /> | 56 | /> |
46 | ); | 57 | ); |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 73c27bfb6..b6291666b 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -56,16 +56,24 @@ export default @observer @injectSheet(styles) class Services extends Component { | |||
56 | 56 | ||
57 | state = { | 57 | state = { |
58 | showConfetti: true, | 58 | showConfetti: true, |
59 | } | 59 | }; |
60 | |||
61 | _confettiTimeout = null; | ||
60 | 62 | ||
61 | componentDidMount() { | 63 | componentDidMount() { |
62 | window.setTimeout(() => { | 64 | this._confettiTimeout = window.setTimeout(() => { |
63 | this.setState({ | 65 | this.setState({ |
64 | showConfetti: false, | 66 | showConfetti: false, |
65 | }); | 67 | }); |
66 | }, ms('8s')); | 68 | }, ms('8s')); |
67 | } | 69 | } |
68 | 70 | ||
71 | componentWillUnmount() { | ||
72 | if (this._confettiTimeout) { | ||
73 | clearTimeout(this._confettiTimeout); | ||
74 | } | ||
75 | } | ||
76 | |||
69 | render() { | 77 | render() { |
70 | const { | 78 | const { |
71 | services, | 79 | services, |
diff --git a/src/components/ui/Modal/styles.js b/src/components/ui/Modal/styles.js index 49b970c97..c2bebf9bb 100644 --- a/src/components/ui/Modal/styles.js +++ b/src/components/ui/Modal/styles.js | |||
@@ -13,7 +13,7 @@ export default theme => ({ | |||
13 | display: 'flex', | 13 | display: 'flex', |
14 | }, | 14 | }, |
15 | modal: { | 15 | modal: { |
16 | background: '#FFF', | 16 | background: theme.colorModalBackground, |
17 | maxWidth: '90%', | 17 | maxWidth: '90%', |
18 | height: 'auto', | 18 | height: 'auto', |
19 | margin: 'auto auto', | 19 | margin: 'auto auto', |
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js index a07f9f63a..fd8bc738a 100644 --- a/src/features/spellchecker/index.js +++ b/src/features/spellchecker/index.js | |||
@@ -16,7 +16,7 @@ export default function init(stores) { | |||
16 | 16 | ||
17 | config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan; | 17 | config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan; |
18 | 18 | ||
19 | if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) { | 19 | if (!stores.user.data.isPremium && !config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) { |
20 | debug('Override settings.spellcheckerEnabled flag to false'); | 20 | debug('Override settings.spellcheckerEnabled flag to false'); |
21 | 21 | ||
22 | Object.assign(stores.settings.app, { | 22 | Object.assign(stores.settings.app, { |
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index d052da6f2..f24c0b044 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -35,9 +35,6 @@ const styles = theme => ({ | |||
35 | 35 | ||
36 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, | 36 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, |
37 | 37 | ||
38 | '&:hover $closeTodosButton': { | ||
39 | opacity: 1, | ||
40 | }, | ||
41 | '& webview': { | 38 | '& webview': { |
42 | height: '100%', | 39 | height: '100%', |
43 | }, | 40 | }, |
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index abf176604..4480b2545 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..a39fcfe0e --- /dev/null +++ b/src/features/webControls/components/WebControls.js | |||
@@ -0,0 +1,239 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { Icon } from '@meetfranz/ui'; | ||
6 | import { defineMessages, intlShape } from 'react-intl'; | ||
7 | |||
8 | import { | ||
9 | mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, mdiEarth, | ||
10 | } from '@mdi/js'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | goHome: { | ||
14 | id: 'webControls.goHome', | ||
15 | defaultMessage: '!!!Home', | ||
16 | }, | ||
17 | openInBrowser: { | ||
18 | id: 'webControls.openInBrowser', | ||
19 | defaultMessage: '!!!Open in Browser', | ||
20 | }, | ||
21 | back: { | ||
22 | id: 'webControls.back', | ||
23 | defaultMessage: '!!!Back', | ||
24 | }, | ||
25 | forward: { | ||
26 | id: 'webControls.forward', | ||
27 | defaultMessage: '!!!Forward', | ||
28 | }, | ||
29 | reload: { | ||
30 | id: 'webControls.reload', | ||
31 | defaultMessage: '!!!Reload', | ||
32 | }, | ||
33 | }); | ||
34 | |||
35 | const styles = theme => ({ | ||
36 | root: { | ||
37 | background: theme.colorBackground, | ||
38 | position: 'relative', | ||
39 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
40 | zIndex: 300, | ||
41 | height: 50, | ||
42 | display: 'flex', | ||
43 | flexDirection: 'row', | ||
44 | alignItems: 'center', | ||
45 | padding: [0, 10], | ||
46 | |||
47 | '& + div': { | ||
48 | height: 'calc(100% - 50px)', | ||
49 | }, | ||
50 | }, | ||
51 | button: { | ||
52 | width: 30, | ||
53 | height: 50, | ||
54 | transition: 'opacity 0.25s', | ||
55 | |||
56 | '&:hover': { | ||
57 | opacity: 0.8, | ||
58 | }, | ||
59 | |||
60 | '&:disabled': { | ||
61 | opacity: 0.5, | ||
62 | }, | ||
63 | }, | ||
64 | icon: { | ||
65 | width: '20px !important', | ||
66 | height: 20, | ||
67 | marginTop: 5, | ||
68 | }, | ||
69 | input: { | ||
70 | marginBottom: 0, | ||
71 | height: 'auto', | ||
72 | margin: [0, 10], | ||
73 | flex: 1, | ||
74 | border: 0, | ||
75 | padding: [4, 10], | ||
76 | borderRadius: theme.borderRadius, | ||
77 | background: theme.inputBackground, | ||
78 | color: theme.inputColor, | ||
79 | }, | ||
80 | inputButton: { | ||
81 | color: theme.colorText, | ||
82 | }, | ||
83 | }); | ||
84 | |||
85 | @injectSheet(styles) @observer | ||
86 | class WebControls extends Component { | ||
87 | static propTypes = { | ||
88 | classes: PropTypes.object.isRequired, | ||
89 | goHome: PropTypes.func.isRequired, | ||
90 | canGoBack: PropTypes.bool.isRequired, | ||
91 | goBack: PropTypes.func.isRequired, | ||
92 | canGoForward: PropTypes.bool.isRequired, | ||
93 | goForward: PropTypes.func.isRequired, | ||
94 | reload: PropTypes.func.isRequired, | ||
95 | openInBrowser: PropTypes.func.isRequired, | ||
96 | url: PropTypes.string.isRequired, | ||
97 | navigate: PropTypes.func.isRequired, | ||
98 | } | ||
99 | |||
100 | static contextTypes = { | ||
101 | intl: intlShape, | ||
102 | }; | ||
103 | |||
104 | static getDerivedStateFromProps(props, state) { | ||
105 | const { url } = props; | ||
106 | const { editUrl } = state; | ||
107 | |||
108 | if (!editUrl) { | ||
109 | return { | ||
110 | inputUrl: url, | ||
111 | editUrl: state.editUrl, | ||
112 | }; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | inputRef = React.createRef(); | ||
117 | |||
118 | state = { | ||
119 | inputUrl: '', | ||
120 | editUrl: false, | ||
121 | } | ||
122 | |||
123 | render() { | ||
124 | const { | ||
125 | classes, | ||
126 | goHome, | ||
127 | canGoBack, | ||
128 | goBack, | ||
129 | canGoForward, | ||
130 | goForward, | ||
131 | reload, | ||
132 | openInBrowser, | ||
133 | url, | ||
134 | navigate, | ||
135 | } = this.props; | ||
136 | |||
137 | const { | ||
138 | inputUrl, | ||
139 | editUrl, | ||
140 | } = this.state; | ||
141 | |||
142 | const { intl } = this.context; | ||
143 | |||
144 | return ( | ||
145 | <div className={classes.root}> | ||
146 | <button | ||
147 | onClick={goHome} | ||
148 | type="button" | ||
149 | className={classes.button} | ||
150 | data-tip={intl.formatMessage(messages.goHome)} | ||
151 | > | ||
152 | <Icon | ||
153 | icon={mdiHomeOutline} | ||
154 | className={classes.icon} | ||
155 | /> | ||
156 | </button> | ||
157 | <button | ||
158 | onClick={goBack} | ||
159 | type="button" | ||
160 | className={classes.button} | ||
161 | disabled={!canGoBack} | ||
162 | data-tip={intl.formatMessage(messages.back)} | ||
163 | > | ||
164 | <Icon | ||
165 | icon={mdiArrowLeft} | ||
166 | className={classes.icon} | ||
167 | /> | ||
168 | </button> | ||
169 | <button | ||
170 | onClick={goForward} | ||
171 | type="button" | ||
172 | className={classes.button} | ||
173 | disabled={!canGoForward} | ||
174 | data-tip={intl.formatMessage(messages.forward)} | ||
175 | > | ||
176 | <Icon | ||
177 | icon={mdiArrowRight} | ||
178 | className={classes.icon} | ||
179 | /> | ||
180 | </button> | ||
181 | <button | ||
182 | onClick={reload} | ||
183 | type="button" | ||
184 | className={classes.button} | ||
185 | data-tip={intl.formatMessage(messages.reload)} | ||
186 | > | ||
187 | <Icon | ||
188 | icon={mdiReload} | ||
189 | className={classes.icon} | ||
190 | /> | ||
191 | </button> | ||
192 | <input | ||
193 | value={editUrl ? inputUrl : url} | ||
194 | className={classes.input} | ||
195 | onChange={event => this.setState({ | ||
196 | inputUrl: event.target.value, | ||
197 | })} | ||
198 | onFocus={(event) => { | ||
199 | event.target.select(); | ||
200 | this.setState({ | ||
201 | editUrl: true, | ||
202 | }); | ||
203 | }} | ||
204 | onKeyDown={(event) => { | ||
205 | if (event.key === 'Enter') { | ||
206 | this.setState({ | ||
207 | editUrl: false, | ||
208 | }); | ||
209 | navigate(inputUrl); | ||
210 | this.inputRef.current.blur(); | ||
211 | } else if (event.key === 'Escape') { | ||
212 | this.setState({ | ||
213 | editUrl: false, | ||
214 | inputUrl: url, | ||
215 | }); | ||
216 | event.target.blur(); | ||
217 | } | ||
218 | }} | ||
219 | ref={this.inputRef} | ||
220 | /> | ||
221 | <button | ||
222 | onClick={openInBrowser} | ||
223 | type="button" | ||
224 | className={classes.button} | ||
225 | data-tip={intl.formatMessage(messages.openInBrowser)} | ||
226 | data-place="left" | ||
227 | > | ||
228 | <Icon | ||
229 | icon={mdiEarth} | ||
230 | className={classes.icon} | ||
231 | /> | ||
232 | </button> | ||
233 | {/* <ReactTooltip place="bottom" type="dark" effect="solid" /> */} | ||
234 | </div> | ||
235 | ); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | export default WebControls; | ||
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js new file mode 100644 index 000000000..cada01a6f --- /dev/null +++ b/src/features/webControls/containers/WebControlsScreen.js | |||
@@ -0,0 +1,139 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | |||
5 | import { autorun, observable } from 'mobx'; | ||
6 | import WebControls from '../components/WebControls'; | ||
7 | import ServicesStore from '../../../stores/ServicesStore'; | ||
8 | import Service from '../../../models/Service'; | ||
9 | |||
10 | const URL_EVENTS = [ | ||
11 | 'load-commit', | ||
12 | 'will-navigate', | ||
13 | 'did-navigate', | ||
14 | 'did-navigate-in-page', | ||
15 | ]; | ||
16 | |||
17 | @inject('stores', 'actions') @observer | ||
18 | class WebControlsScreen extends Component { | ||
19 | @observable url = ''; | ||
20 | |||
21 | @observable canGoBack = false; | ||
22 | |||
23 | @observable canGoForward = false; | ||
24 | |||
25 | webview = null; | ||
26 | |||
27 | autorunDisposer = null; | ||
28 | |||
29 | componentDidMount() { | ||
30 | const { service } = this.props; | ||
31 | |||
32 | this.autorunDisposer = autorun(() => { | ||
33 | if (service.isAttached) { | ||
34 | this.webview = service.webview; | ||
35 | |||
36 | URL_EVENTS.forEach((event) => { | ||
37 | this.webview.addEventListener(event, (e) => { | ||
38 | if (!e.isMainFrame) return; | ||
39 | |||
40 | this.url = e.url; | ||
41 | this.canGoBack = this.webview.canGoBack(); | ||
42 | this.canGoForward = this.webview.canGoForward(); | ||
43 | }); | ||
44 | }); | ||
45 | } | ||
46 | }); | ||
47 | } | ||
48 | |||
49 | componentWillUnmount() { | ||
50 | this.autorunDisposer(); | ||
51 | } | ||
52 | |||
53 | goHome() { | ||
54 | const { reloadActive } = this.props.actions.service; | ||
55 | |||
56 | if (!this.webview) return; | ||
57 | |||
58 | reloadActive(); | ||
59 | } | ||
60 | |||
61 | reload() { | ||
62 | if (!this.webview) return; | ||
63 | |||
64 | this.webview.reload(); | ||
65 | } | ||
66 | |||
67 | goBack() { | ||
68 | if (!this.webview) return; | ||
69 | |||
70 | this.webview.goBack(); | ||
71 | } | ||
72 | |||
73 | goForward() { | ||
74 | if (!this.webview) return; | ||
75 | |||
76 | this.webview.goForward(); | ||
77 | } | ||
78 | |||
79 | navigate(newUrl) { | ||
80 | if (!this.webview) return; | ||
81 | |||
82 | let url = newUrl; | ||
83 | |||
84 | try { | ||
85 | url = new URL(url).toString(); | ||
86 | } catch (err) { | ||
87 | // eslint-disable-next-line no-useless-escape | ||
88 | 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,})$/)) { | ||
89 | url = `http://${url}`; | ||
90 | } else { | ||
91 | url = `https://www.google.com/search?query=${url}`; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | this.webview.loadURL(url); | ||
96 | this.url = url; | ||
97 | } | ||
98 | |||
99 | openInBrowser() { | ||
100 | const { openExternalUrl } = this.props.actions.app; | ||
101 | |||
102 | if (!this.webview) return; | ||
103 | |||
104 | openExternalUrl({ url: this.url }); | ||
105 | } | ||
106 | |||
107 | render() { | ||
108 | return ( | ||
109 | <WebControls | ||
110 | goHome={() => this.goHome()} | ||
111 | reload={() => this.reload()} | ||
112 | openInBrowser={() => this.openInBrowser()} | ||
113 | canGoBack={this.canGoBack} | ||
114 | goBack={() => this.goBack()} | ||
115 | canGoForward={this.canGoForward} | ||
116 | goForward={() => this.goForward()} | ||
117 | navigate={url => this.navigate(url)} | ||
118 | url={this.url} | ||
119 | /> | ||
120 | ); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | export default WebControlsScreen; | ||
125 | |||
126 | WebControlsScreen.wrappedComponent.propTypes = { | ||
127 | service: PropTypes.instanceOf(Service).isRequired, | ||
128 | stores: PropTypes.shape({ | ||
129 | services: PropTypes.instanceOf(ServicesStore).isRequired, | ||
130 | }).isRequired, | ||
131 | actions: PropTypes.shape({ | ||
132 | app: PropTypes.shape({ | ||
133 | openExternalUrl: PropTypes.func.isRequired, | ||
134 | }).isRequired, | ||
135 | service: PropTypes.shape({ | ||
136 | reloadActive: PropTypes.func.isRequired, | ||
137 | }).isRequired, | ||
138 | }).isRequired, | ||
139 | }; | ||
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index e7bc0b157..ee6f8416c 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js | |||
@@ -204,8 +204,9 @@ class WorkspaceDrawer extends Component { | |||
204 | }} | 204 | }} |
205 | services={getServicesForWorkspace(null)} | 205 | services={getServicesForWorkspace(null)} |
206 | isActive={actualWorkspace == null} | 206 | isActive={actualWorkspace == null} |
207 | shortcutIndex={0} | ||
207 | /> | 208 | /> |
208 | {workspaces.map(workspace => ( | 209 | {workspaces.map((workspace, index) => ( |
209 | <WorkspaceDrawerItem | 210 | <WorkspaceDrawerItem |
210 | key={workspace.id} | 211 | key={workspace.id} |
211 | name={workspace.name} | 212 | name={workspace.name} |
@@ -218,6 +219,7 @@ class WorkspaceDrawer extends Component { | |||
218 | }} | 219 | }} |
219 | onContextMenuEditClick={() => workspaceActions.edit({ workspace })} | 220 | onContextMenuEditClick={() => workspaceActions.edit({ workspace })} |
220 | services={getServicesForWorkspace(workspace)} | 221 | services={getServicesForWorkspace(workspace)} |
222 | shortcutIndex={index + 1} | ||
221 | /> | 223 | /> |
222 | ))} | 224 | ))} |
223 | <div | 225 | <div |
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.js b/src/features/workspaces/components/WorkspaceDrawerItem.js index 59a2144d3..18f424d8a 100644 --- a/src/features/workspaces/components/WorkspaceDrawerItem.js +++ b/src/features/workspaces/components/WorkspaceDrawerItem.js | |||
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'; | |||
5 | import injectSheet from 'react-jss'; | 5 | import injectSheet from 'react-jss'; |
6 | import classnames from 'classnames'; | 6 | import classnames from 'classnames'; |
7 | import { defineMessages, intlShape } from 'react-intl'; | 7 | import { defineMessages, intlShape } from 'react-intl'; |
8 | import { ctrlKey } from '../../../environment'; | ||
8 | 9 | ||
9 | const { Menu } = remote; | 10 | const { Menu } = remote; |
10 | 11 | ||
@@ -69,6 +70,7 @@ class WorkspaceDrawerItem extends Component { | |||
69 | onClick: PropTypes.func.isRequired, | 70 | onClick: PropTypes.func.isRequired, |
70 | services: PropTypes.arrayOf(PropTypes.string).isRequired, | 71 | services: PropTypes.arrayOf(PropTypes.string).isRequired, |
71 | onContextMenuEditClick: PropTypes.func, | 72 | onContextMenuEditClick: PropTypes.func, |
73 | shortcutIndex: PropTypes.number.isRequired, | ||
72 | }; | 74 | }; |
73 | 75 | ||
74 | static defaultProps = { | 76 | static defaultProps = { |
@@ -87,6 +89,7 @@ class WorkspaceDrawerItem extends Component { | |||
87 | onClick, | 89 | onClick, |
88 | onContextMenuEditClick, | 90 | onContextMenuEditClick, |
89 | services, | 91 | services, |
92 | shortcutIndex, | ||
90 | } = this.props; | 93 | } = this.props; |
91 | const { intl } = this.context; | 94 | const { intl } = this.context; |
92 | 95 | ||
@@ -112,6 +115,7 @@ class WorkspaceDrawerItem extends Component { | |||
112 | onContextMenu={() => ( | 115 | onContextMenu={() => ( |
113 | onContextMenuEditClick && contextMenu.popup(remote.getCurrentWindow()) | 116 | onContextMenuEditClick && contextMenu.popup(remote.getCurrentWindow()) |
114 | )} | 117 | )} |
118 | data-tip={`${shortcutIndex <= 9 ? `(${ctrlKey}+Alt+${shortcutIndex})` : ''}`} | ||
115 | > | 119 | > |
116 | <span | 120 | <span |
117 | className={classnames([ | 121 | className={classnames([ |
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 0dfe2055a..e12b6b49b 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -4023,6 +4023,76 @@ | |||
4023 | { | 4023 | { |
4024 | "descriptors": [ | 4024 | "descriptors": [ |
4025 | { | 4025 | { |
4026 | "defaultMessage": "!!!Home", | ||
4027 | "end": { | ||
4028 | "column": 3, | ||
4029 | "line": 16 | ||
4030 | }, | ||
4031 | "file": "src/features/webControls/components/WebControls.js", | ||
4032 | "id": "webControls.goHome", | ||
4033 | "start": { | ||
4034 | "column": 10, | ||
4035 | "line": 13 | ||
4036 | } | ||
4037 | }, | ||
4038 | { | ||
4039 | "defaultMessage": "!!!Open in Browser", | ||
4040 | "end": { | ||
4041 | "column": 3, | ||
4042 | "line": 20 | ||
4043 | }, | ||
4044 | "file": "src/features/webControls/components/WebControls.js", | ||
4045 | "id": "webControls.openInBrowser", | ||
4046 | "start": { | ||
4047 | "column": 17, | ||
4048 | "line": 17 | ||
4049 | } | ||
4050 | }, | ||
4051 | { | ||
4052 | "defaultMessage": "!!!Back", | ||
4053 | "end": { | ||
4054 | "column": 3, | ||
4055 | "line": 24 | ||
4056 | }, | ||
4057 | "file": "src/features/webControls/components/WebControls.js", | ||
4058 | "id": "webControls.back", | ||
4059 | "start": { | ||
4060 | "column": 8, | ||
4061 | "line": 21 | ||
4062 | } | ||
4063 | }, | ||
4064 | { | ||
4065 | "defaultMessage": "!!!Forward", | ||
4066 | "end": { | ||
4067 | "column": 3, | ||
4068 | "line": 28 | ||
4069 | }, | ||
4070 | "file": "src/features/webControls/components/WebControls.js", | ||
4071 | "id": "webControls.forward", | ||
4072 | "start": { | ||
4073 | "column": 11, | ||
4074 | "line": 25 | ||
4075 | } | ||
4076 | }, | ||
4077 | { | ||
4078 | "defaultMessage": "!!!Reload", | ||
4079 | "end": { | ||
4080 | "column": 3, | ||
4081 | "line": 32 | ||
4082 | }, | ||
4083 | "file": "src/features/webControls/components/WebControls.js", | ||
4084 | "id": "webControls.reload", | ||
4085 | "start": { | ||
4086 | "column": 10, | ||
4087 | "line": 29 | ||
4088 | } | ||
4089 | } | ||
4090 | ], | ||
4091 | "path": "src/features/webControls/components/WebControls.json" | ||
4092 | }, | ||
4093 | { | ||
4094 | "descriptors": [ | ||
4095 | { | ||
4026 | "defaultMessage": "!!!Create workspace", | 4096 | "defaultMessage": "!!!Create workspace", |
4027 | "end": { | 4097 | "end": { |
4028 | "column": 3, | 4098 | "column": 3, |
@@ -4262,26 +4332,26 @@ | |||
4262 | "defaultMessage": "!!!No services added yet", | 4332 | "defaultMessage": "!!!No services added yet", |
4263 | "end": { | 4333 | "end": { |
4264 | "column": 3, | 4334 | "column": 3, |
4265 | "line": 15 | 4335 | "line": 16 |
4266 | }, | 4336 | }, |
4267 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", | 4337 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", |
4268 | "id": "workspaceDrawer.item.noServicesAddedYet", | 4338 | "id": "workspaceDrawer.item.noServicesAddedYet", |
4269 | "start": { | 4339 | "start": { |
4270 | "column": 22, | 4340 | "column": 22, |
4271 | "line": 12 | 4341 | "line": 13 |
4272 | } | 4342 | } |
4273 | }, | 4343 | }, |
4274 | { | 4344 | { |
4275 | "defaultMessage": "!!!edit", | 4345 | "defaultMessage": "!!!edit", |
4276 | "end": { | 4346 | "end": { |
4277 | "column": 3, | 4347 | "column": 3, |
4278 | "line": 19 | 4348 | "line": 20 |
4279 | }, | 4349 | }, |
4280 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", | 4350 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", |
4281 | "id": "workspaceDrawer.item.contextMenuEdit", | 4351 | "id": "workspaceDrawer.item.contextMenuEdit", |
4282 | "start": { | 4352 | "start": { |
4283 | "column": 19, | 4353 | "column": 19, |
4284 | "line": 16 | 4354 | "line": 17 |
4285 | } | 4355 | } |
4286 | } | 4356 | } |
4287 | ], | 4357 | ], |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index dcb9d92a2..c2c6a9863 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -370,6 +370,11 @@ | |||
370 | "validation.oneRequired": "At least one is required", | 370 | "validation.oneRequired": "At least one is required", |
371 | "validation.required": "{field} is required", | 371 | "validation.required": "{field} is required", |
372 | "validation.url": "{field} is not a valid URL", | 372 | "validation.url": "{field} is not a valid URL", |
373 | "webControls.back": "Back", | ||
374 | "webControls.forward": "Forward", | ||
375 | "webControls.goHome": "Home", | ||
376 | "webControls.openInBrowser": "Open in Browser", | ||
377 | "webControls.reload": "Reload", | ||
373 | "welcome.loginButton": "Login to your account", | 378 | "welcome.loginButton": "Login to your account", |
374 | "welcome.signupButton": "Create a free account", | 379 | "welcome.signupButton": "Create a free account", |
375 | "workspaceDrawer.addNewWorkspaceLabel": "Add new workspace", | 380 | "workspaceDrawer.addNewWorkspaceLabel": "Add new workspace", |
diff --git a/src/i18n/messages/src/features/webControls/components/WebControls.json b/src/i18n/messages/src/features/webControls/components/WebControls.json new file mode 100644 index 000000000..969437e98 --- /dev/null +++ b/src/i18n/messages/src/features/webControls/components/WebControls.json | |||
@@ -0,0 +1,67 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "webControls.goHome", | ||
4 | "defaultMessage": "!!!Home", | ||
5 | "file": "src/features/webControls/components/WebControls.js", | ||
6 | "start": { | ||
7 | "line": 13, | ||
8 | "column": 10 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 16, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "webControls.openInBrowser", | ||
17 | "defaultMessage": "!!!Open in Browser", | ||
18 | "file": "src/features/webControls/components/WebControls.js", | ||
19 | "start": { | ||
20 | "line": 17, | ||
21 | "column": 17 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 20, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "webControls.back", | ||
30 | "defaultMessage": "!!!Back", | ||
31 | "file": "src/features/webControls/components/WebControls.js", | ||
32 | "start": { | ||
33 | "line": 21, | ||
34 | "column": 8 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 24, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "webControls.forward", | ||
43 | "defaultMessage": "!!!Forward", | ||
44 | "file": "src/features/webControls/components/WebControls.js", | ||
45 | "start": { | ||
46 | "line": 25, | ||
47 | "column": 11 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 28, | ||
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | "id": "webControls.reload", | ||
56 | "defaultMessage": "!!!Reload", | ||
57 | "file": "src/features/webControls/components/WebControls.js", | ||
58 | "start": { | ||
59 | "line": 29, | ||
60 | "column": 10 | ||
61 | }, | ||
62 | "end": { | ||
63 | "line": 32, | ||
64 | "column": 3 | ||
65 | } | ||
66 | } | ||
67 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json index 4ff190606..1b6664787 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!No services added yet", | 4 | "defaultMessage": "!!!No services added yet", |
5 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", | 5 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 12, | 7 | "line": 13, |
8 | "column": 22 | 8 | "column": 22 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 15, | 11 | "line": 16, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!edit", | 17 | "defaultMessage": "!!!edit", |
18 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", | 18 | "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 16, | 20 | "line": 17, |
21 | "column": 19 | 21 | "column": 19 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 19, | 24 | "line": 20, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | } | 27 | } |
diff --git a/src/index.js b/src/index.js index d9d51fd5b..7de7a5e1c 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -331,22 +331,7 @@ app.on('login', (event, webContents, request, authInfo, callback) => { | |||
331 | debug('browser login event', authInfo); | 331 | debug('browser login event', authInfo); |
332 | event.preventDefault(); | 332 | event.preventDefault(); |
333 | 333 | ||
334 | if (authInfo.isProxy && authInfo.scheme === 'basic') { | 334 | if (!authInfo.isProxy && authInfo.scheme === 'basic') { |
335 | debug('Sending service echo ping'); | ||
336 | webContents.send('get-service-id'); | ||
337 | |||
338 | ipcMain.once('service-id', (e, id) => { | ||
339 | debug('Received service id', id); | ||
340 | |||
341 | const ps = proxySettings.get(id); | ||
342 | if (ps) { | ||
343 | debug('Sending proxy auth callback for service', id); | ||
344 | callback(ps.user, ps.password); | ||
345 | } else { | ||
346 | debug('No proxy auth config found for', id); | ||
347 | } | ||
348 | }); | ||
349 | } else if (authInfo.scheme === 'basic') { | ||
350 | debug('basic auth handler', authInfo); | 335 | debug('basic auth handler', authInfo); |
351 | basicAuthHandler(mainWindow, authInfo); | 336 | basicAuthHandler(mainWindow, authInfo); |
352 | } | 337 | } |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 4aa2edaba..32bd1644b 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -302,6 +302,9 @@ const _templateFactory = intl => [ | |||
302 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), | 302 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), |
303 | accelerator: 'Cmd+Shift+V', | 303 | accelerator: 'Cmd+Shift+V', |
304 | selector: 'pasteAndMatchStyle:', | 304 | selector: 'pasteAndMatchStyle:', |
305 | click() { | ||
306 | getActiveWebview().pasteAndMatchStyle(); | ||
307 | }, | ||
305 | }, | 308 | }, |
306 | { | 309 | { |
307 | label: intl.formatMessage(menuItems.delete), | 310 | label: intl.formatMessage(menuItems.delete), |
@@ -548,6 +551,11 @@ const _titleBarTemplateFactory = intl => [ | |||
548 | visible: workspaceStore.isFeatureEnabled, | 551 | visible: workspaceStore.isFeatureEnabled, |
549 | }, | 552 | }, |
550 | { | 553 | { |
554 | label: intl.formatMessage(menuItems.todos), | ||
555 | submenu: [], | ||
556 | visible: todosStore.isFeatureEnabled, | ||
557 | }, | ||
558 | { | ||
551 | label: intl.formatMessage(menuItems.window), | 559 | label: intl.formatMessage(menuItems.window), |
552 | submenu: [ | 560 | submenu: [ |
553 | { | 561 | { |
@@ -862,6 +870,10 @@ export default class FranzMenu { | |||
862 | checked: service.isActive, | 870 | checked: service.isActive, |
863 | click: () => { | 871 | click: () => { |
864 | this.actions.service.setActive({ serviceId: service.id }); | 872 | this.actions.service.setActive({ serviceId: service.id }); |
873 | |||
874 | if (isMac && i === 0) { | ||
875 | app.mainWindow.restore(); | ||
876 | } | ||
865 | }, | 877 | }, |
866 | }))); | 878 | }))); |
867 | 879 | ||
@@ -943,12 +955,12 @@ export default class FranzMenu { | |||
943 | gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'menu'); | 955 | gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'menu'); |
944 | }, | 956 | }, |
945 | enabled: this.stores.user.isLoggedIn && isFeatureEnabledByUser, | 957 | enabled: this.stores.user.isLoggedIn && isFeatureEnabledByUser, |
946 | }, { | ||
947 | type: 'separator', | ||
948 | }); | 958 | }); |
949 | 959 | ||
950 | if (!isFeatureEnabledByUser) { | 960 | if (!isFeatureEnabledByUser) { |
951 | menu.push({ | 961 | menu.push({ |
962 | type: 'separator', | ||
963 | }, { | ||
952 | label: intl.formatMessage(menuItems.enableTodos), | 964 | label: intl.formatMessage(menuItems.enableTodos), |
953 | click: () => { | 965 | click: () => { |
954 | todoActions.toggleTodosFeatureVisibility(); | 966 | todoActions.toggleTodosFeatureVisibility(); |
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 b0d60e75e..00c0f699f 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 848a84aa2..e45c39564 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 | ||
@@ -185,19 +188,24 @@ export default class Service { | |||
185 | return userAgent; | 188 | return userAgent; |
186 | } | 189 | } |
187 | 190 | ||
188 | initializeWebViewEvents({ handleIPCMessage, openWindow }) { | 191 | initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { |
192 | const webContents = this.webview.getWebContents(); | ||
193 | |||
189 | this.webview.addEventListener('ipc-message', e => handleIPCMessage({ | 194 | this.webview.addEventListener('ipc-message', e => handleIPCMessage({ |
190 | serviceId: this.id, | 195 | serviceId: this.id, |
191 | channel: e.channel, | 196 | channel: e.channel, |
192 | args: e.args, | 197 | args: e.args, |
193 | })); | 198 | })); |
194 | 199 | ||
195 | this.webview.addEventListener('new-window', (event, url, frameName, options) => openWindow({ | 200 | this.webview.addEventListener('new-window', (event, url, frameName, options) => { |
196 | event, | 201 | console.log('open window', event, url, frameName, options); |
197 | url, | 202 | openWindow({ |
198 | frameName, | 203 | event, |
199 | options, | 204 | url, |
200 | })); | 205 | frameName, |
206 | options, | ||
207 | }); | ||
208 | }); | ||
201 | 209 | ||
202 | this.webview.addEventListener('did-start-loading', (event) => { | 210 | this.webview.addEventListener('did-start-loading', (event) => { |
203 | debug('Did start load', this.name, event); | 211 | debug('Did start load', this.name, event); |
@@ -231,6 +239,28 @@ export default class Service { | |||
231 | debug('Service crashed', this.name); | 239 | debug('Service crashed', this.name); |
232 | this.hasCrashed = true; | 240 | this.hasCrashed = true; |
233 | }); | 241 | }); |
242 | |||
243 | webContents.on('login', (event, request, authInfo, callback) => { | ||
244 | // const authCallback = callback; | ||
245 | debug('browser login event', authInfo); | ||
246 | event.preventDefault(); | ||
247 | |||
248 | if (authInfo.isProxy && authInfo.scheme === 'basic') { | ||
249 | debug('Sending service echo ping'); | ||
250 | webContents.send('get-service-id'); | ||
251 | |||
252 | debug('Received service id', this.id); | ||
253 | |||
254 | const ps = stores.settings.proxy[this.id]; | ||
255 | |||
256 | if (ps) { | ||
257 | debug('Sending proxy auth callback for service', this.id); | ||
258 | callback(ps.user, ps.password); | ||
259 | } else { | ||
260 | debug('No proxy auth config found for', this.id); | ||
261 | } | ||
262 | } | ||
263 | }); | ||
234 | } | 264 | } |
235 | 265 | ||
236 | initializeWebViewListener() { | 266 | initializeWebViewListener() { |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 315235ba4..0398b7533 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -23,6 +23,7 @@ import { getLocale } from '../helpers/i18n-helpers'; | |||
23 | 23 | ||
24 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 24 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
25 | import { isValidExternalURL } from '../helpers/url-helpers'; | 25 | import { isValidExternalURL } from '../helpers/url-helpers'; |
26 | import { sleep } from '../helpers/async-helpers'; | ||
26 | 27 | ||
27 | const debug = require('debug')('Franz:AppStore'); | 28 | const debug = require('debug')('Franz:AppStore'); |
28 | 29 | ||
@@ -327,6 +328,8 @@ export default class AppStore extends Store { | |||
327 | 328 | ||
328 | await clearAppCache._promise; | 329 | await clearAppCache._promise; |
329 | 330 | ||
331 | await sleep(ms('1s')); | ||
332 | |||
330 | this.getAppCacheSizeRequest.execute(); | 333 | this.getAppCacheSizeRequest.execute(); |
331 | 334 | ||
332 | this.isClearingAllCache = false; | 335 | this.isClearingAllCache = false; |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index d1fd2be3d..70b775503 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -345,6 +345,7 @@ export default class ServicesStore extends Store { | |||
345 | service.initializeWebViewEvents({ | 345 | service.initializeWebViewEvents({ |
346 | handleIPCMessage: this.actions.service.handleIPCMessage, | 346 | handleIPCMessage: this.actions.service.handleIPCMessage, |
347 | openWindow: this.actions.service.openWindow, | 347 | openWindow: this.actions.service.openWindow, |
348 | stores: this.stores, | ||
348 | }); | 349 | }); |
349 | service.initializeWebViewListener(); | 350 | service.initializeWebViewListener(); |
350 | } | 351 | } |
@@ -683,6 +684,8 @@ export default class ServicesStore extends Store { | |||
683 | const serviceData = data; | 684 | const serviceData = data; |
684 | const recipe = this.stores.recipes.one(recipeId); | 685 | const recipe = this.stores.recipes.one(recipeId); |
685 | 686 | ||
687 | if (!recipe) return; | ||
688 | |||
686 | if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { | 689 | if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { |
687 | delete serviceData.team; | 690 | delete serviceData.team; |
688 | } | 691 | } |
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index a456195bf..75bb38fe0 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { | 2 | import { |
3 | action, computed, observable, set, | 3 | action, computed, observable, |
4 | } from 'mobx'; | 4 | } from 'mobx'; |
5 | import localStorage from 'mobx-localstorage'; | 5 | import localStorage from 'mobx-localstorage'; |
6 | 6 | ||
7 | import Store from './lib/Store'; | 7 | import Store from './lib/Store'; |
8 | import Request from './lib/Request'; | 8 | import Request from './lib/Request'; |
9 | import CachedRequest from './lib/CachedRequest'; | ||
10 | import { getLocale } from '../helpers/i18n-helpers'; | 9 | import { getLocale } from '../helpers/i18n-helpers'; |
11 | 10 | ||
12 | import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; | 11 | import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; |
@@ -15,12 +14,8 @@ import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | |||
15 | const debug = require('debug')('Franz:SettingsStore'); | 14 | const debug = require('debug')('Franz:SettingsStore'); |
16 | 15 | ||
17 | export default class SettingsStore extends Store { | 16 | export default class SettingsStore extends Store { |
18 | @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings'); | ||
19 | |||
20 | @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); | 17 | @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); |
21 | 18 | ||
22 | fileSystemSettingsRequests = []; | ||
23 | |||
24 | fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES; | 19 | fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES; |
25 | 20 | ||
26 | @observable _fileSystemSettingsCache = { | 21 | @observable _fileSystemSettingsCache = { |
@@ -35,14 +30,10 @@ export default class SettingsStore extends Store { | |||
35 | this.actions.settings.update.listen(this._update.bind(this)); | 30 | this.actions.settings.update.listen(this._update.bind(this)); |
36 | this.actions.settings.remove.listen(this._remove.bind(this)); | 31 | this.actions.settings.remove.listen(this._remove.bind(this)); |
37 | 32 | ||
38 | this.fileSystemSettingsTypes.forEach((type) => { | ||
39 | this.fileSystemSettingsRequests[type] = new CachedRequest(this.api.local, 'getAppSettings'); | ||
40 | }); | ||
41 | |||
42 | ipcRenderer.on('appSettings', (event, resp) => { | 33 | ipcRenderer.on('appSettings', (event, resp) => { |
43 | debug('Get appSettings resolves', resp.type, resp.data); | 34 | debug('Get appSettings resolves', resp.type, resp.data); |
44 | 35 | ||
45 | this._fileSystemSettingsCache[resp.type] = resp.data; | 36 | Object.assign(this._fileSystemSettingsCache[resp.type], resp.data); |
46 | }); | 37 | }); |
47 | 38 | ||
48 | this.fileSystemSettingsTypes.forEach((type) => { | 39 | this.fileSystemSettingsTypes.forEach((type) => { |
@@ -51,8 +42,6 @@ export default class SettingsStore extends Store { | |||
51 | } | 42 | } |
52 | 43 | ||
53 | async setup() { | 44 | async setup() { |
54 | // We need to wait until `appSettingsRequest` has been executed once, otherwise we can't patch the result. If we don't wait we'd run into an issue with mobx not reacting to changes of previously not existing keys | ||
55 | await this.appSettingsRequest._promise; | ||
56 | await this._migrate(); | 45 | await this._migrate(); |
57 | } | 46 | } |
58 | 47 | ||
@@ -61,21 +50,6 @@ export default class SettingsStore extends Store { | |||
61 | } | 50 | } |
62 | 51 | ||
63 | @computed get proxy() { | 52 | @computed get proxy() { |
64 | // // We need to provide the final data structure as mobx autoruns won't work | ||
65 | // const proxySettings = observable({}); | ||
66 | // this.stores.services.all.forEach((service) => { | ||
67 | // proxySettings[service.id] = { | ||
68 | // isEnabled: false, | ||
69 | // host: null, | ||
70 | // user: null, | ||
71 | // password: null, | ||
72 | // }; | ||
73 | // }); | ||
74 | |||
75 | // debug('this._fileSystemSettingsCache.proxy', this._fileSystemSettingsCache.proxy, proxySettings); | ||
76 | |||
77 | // return Object.assign(proxySettings, this._fileSystemSettingsCache.proxy); | ||
78 | |||
79 | return this._fileSystemSettingsCache.proxy || {}; | 53 | return this._fileSystemSettingsCache.proxy || {}; |
80 | } | 54 | } |
81 | 55 | ||
@@ -117,7 +91,7 @@ export default class SettingsStore extends Store { | |||
117 | data, | 91 | data, |
118 | }); | 92 | }); |
119 | 93 | ||
120 | set(this._fileSystemSettingsCache[type], data); | 94 | Object.assign(this._fileSystemSettingsCache[type], data); |
121 | } | 95 | } |
122 | } | 96 | } |
123 | 97 | ||
@@ -197,8 +171,4 @@ export default class SettingsStore extends Store { | |||
197 | }); | 171 | }); |
198 | } | 172 | } |
199 | } | 173 | } |
200 | |||
201 | _getFileBasedSettings(type) { | ||
202 | ipcRenderer.send('getAppSettings', type); | ||
203 | } | ||
204 | } | 174 | } |
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index 83914f581..d3b976554 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -255,9 +255,9 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
255 | }, | 255 | }, |
256 | { | 256 | { |
257 | id: 'resetToDefault', | 257 | id: 'resetToDefault', |
258 | label: `Reset to system default (${SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, | 258 | label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, |
259 | type: 'radio', | 259 | type: 'radio', |
260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, | 260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'), |
261 | click() { | 261 | click() { |
262 | debug('Resetting service spellchecker to system default'); | 262 | debug('Resetting service spellchecker to system default'); |
263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); | 263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); |
@@ -297,12 +297,13 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
297 | }; | 297 | }; |
298 | 298 | ||
299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { | 299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { |
300 | webContents.on('context-menu', (e, props) => { | 300 | webContents.on('context-menu', async (e, props) => { |
301 | e.preventDefault(); | 301 | e.preventDefault(); |
302 | 302 | ||
303 | let suggestions = []; | 303 | let suggestions = []; |
304 | if (spellcheckProvider && props.misspelledWord) { | 304 | if (spellcheckProvider && props.misspelledWord) { |
305 | suggestions = spellcheckProvider.getSuggestion(props.misspelledWord); | 305 | debug('Mispelled word', props.misspelledWord); |
306 | suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord); | ||
306 | 307 | ||
307 | debug('Suggestions', suggestions); | 308 | debug('Suggestions', suggestions); |
308 | } | 309 | } |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index c223b73de..e3e13b726 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -12,6 +12,7 @@ import contextMenu from './contextMenu'; | |||
12 | import './notifications'; | 12 | import './notifications'; |
13 | 13 | ||
14 | import { DEFAULT_APP_SETTINGS } from '../config'; | 14 | import { DEFAULT_APP_SETTINGS } from '../config'; |
15 | import { isDevMode } from '../environment'; | ||
15 | 16 | ||
16 | const debug = require('debug')('Franz:Plugin'); | 17 | const debug = require('debug')('Franz:Plugin'); |
17 | 18 | ||
@@ -32,7 +33,7 @@ class RecipeController { | |||
32 | 'settings-update': 'updateAppSettings', | 33 | 'settings-update': 'updateAppSettings', |
33 | 'service-settings-update': 'updateServiceSettings', | 34 | 'service-settings-update': 'updateServiceSettings', |
34 | 'get-service-id': 'serviceIdEcho', | 35 | 'get-service-id': 'serviceIdEcho', |
35 | } | 36 | }; |
36 | 37 | ||
37 | constructor() { | 38 | constructor() { |
38 | this.initialize(); | 39 | this.initialize(); |
@@ -173,11 +174,42 @@ new RecipeController(); | |||
173 | // Patching window.open | 174 | // Patching window.open |
174 | const originalWindowOpen = window.open; | 175 | const originalWindowOpen = window.open; |
175 | 176 | ||
177 | |||
176 | window.open = (url, frameName, features) => { | 178 | window.open = (url, frameName, features) => { |
179 | if (!url && !frameName && !features) { | ||
180 | // The service hasn't yet supplied a URL (as used in Skype). | ||
181 | // Return a new dummy window object and wait for the service to change the properties | ||
182 | const newWindow = { | ||
183 | location: { | ||
184 | href: '', | ||
185 | }, | ||
186 | }; | ||
187 | |||
188 | const checkInterval = setInterval(() => { | ||
189 | // Has the service changed the URL yet? | ||
190 | if (newWindow.location.href !== '') { | ||
191 | // Open the new URL | ||
192 | ipcRenderer.sendToHost('new-window', newWindow.location.href); | ||
193 | clearInterval(checkInterval); | ||
194 | } | ||
195 | }, 0); | ||
196 | |||
197 | setTimeout(() => { | ||
198 | // Stop checking for location changes after 1 second | ||
199 | clearInterval(checkInterval); | ||
200 | }, 1000); | ||
201 | |||
202 | return newWindow; | ||
203 | } | ||
204 | |||
177 | // We need to differentiate if the link should be opened in a popup or in the systems default browser | 205 | // We need to differentiate if the link should be opened in a popup or in the systems default browser |
178 | if (!frameName && !features) { | 206 | if (!frameName && !features && typeof features !== 'string') { |
179 | return ipcRenderer.sendToHost('new-window', url); | 207 | return ipcRenderer.sendToHost('new-window', url); |
180 | } | 208 | } |
181 | 209 | ||
182 | return originalWindowOpen(url, frameName, features); | 210 | return originalWindowOpen(url, frameName, features); |
183 | }; | 211 | }; |
212 | |||
213 | if (isDevMode) { | ||
214 | window.log = console.log; | ||
215 | } | ||
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 9158b3b94..06cbd283a 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -1,6 +1,7 @@ | |||
1 | import { webFrame } from 'electron'; | 1 | import { webFrame } from 'electron'; |
2 | import { SpellCheckerProvider } from 'electron-hunspell'; | 2 | import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell'; |
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | import { readFileSync } from 'fs'; | ||
4 | 5 | ||
5 | import { DICTIONARY_PATH } from '../config'; | 6 | import { DICTIONARY_PATH } from '../config'; |
6 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 7 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
@@ -10,18 +11,21 @@ const debug = require('debug')('Franz:spellchecker'); | |||
10 | let provider; | 11 | let provider; |
11 | let currentDict; | 12 | let currentDict; |
12 | let _isEnabled = false; | 13 | let _isEnabled = false; |
14 | let attached; | ||
15 | |||
16 | const DEFAULT_LOCALE = 'en-us'; | ||
13 | 17 | ||
14 | async function loadDictionary(locale) { | 18 | async function loadDictionary(locale) { |
15 | try { | 19 | try { |
16 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); | 20 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); |
17 | await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`); | ||
18 | debug('Loaded dictionary', locale, 'from', fileLocation); | 21 | debug('Loaded dictionary', locale, 'from', fileLocation); |
22 | return provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`)); | ||
19 | } catch (err) { | 23 | } catch (err) { |
20 | console.error('Could not load dictionary', err); | 24 | console.error('Could not load dictionary', err); |
21 | } | 25 | } |
22 | } | 26 | } |
23 | 27 | ||
24 | export async function switchDict(locale) { | 28 | export async function switchDict(locale = DEFAULT_LOCALE) { |
25 | try { | 29 | try { |
26 | debug('Trying to load dictionary', locale); | 30 | debug('Trying to load dictionary', locale); |
27 | 31 | ||
@@ -40,8 +44,8 @@ export async function switchDict(locale) { | |||
40 | if (currentDict) { | 44 | if (currentDict) { |
41 | provider.unloadDictionary(locale); | 45 | provider.unloadDictionary(locale); |
42 | } | 46 | } |
43 | loadDictionary(locale); | 47 | await loadDictionary(locale); |
44 | provider.switchDictionary(locale); | 48 | await attached.switchLanguage(locale); |
45 | 49 | ||
46 | debug('Switched dictionary to', locale); | 50 | debug('Switched dictionary to', locale); |
47 | 51 | ||
@@ -52,18 +56,32 @@ export async function switchDict(locale) { | |||
52 | } | 56 | } |
53 | } | 57 | } |
54 | 58 | ||
55 | export default async function initialize(languageCode = 'en-us') { | 59 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { |
60 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
61 | |||
62 | if (locales.length >= 1) { | ||
63 | return locales[0]; | ||
64 | } | ||
65 | |||
66 | return null; | ||
67 | } | ||
68 | |||
69 | export default async function initialize(languageCode = DEFAULT_LOCALE) { | ||
56 | try { | 70 | try { |
57 | provider = new SpellCheckerProvider(); | 71 | provider = new SpellCheckerProvider(); |
58 | const locale = languageCode.toLowerCase(); | 72 | const locale = getSpellcheckerLocaleByFuzzyIdentifier(languageCode); |
59 | 73 | ||
60 | debug('Init spellchecker'); | 74 | debug('Init spellchecker'); |
61 | await provider.initialize(); | 75 | await provider.initialize(); |
62 | // await loadDictionaries(); | ||
63 | 76 | ||
64 | debug('Available spellchecker dictionaries', provider.availableDictionaries); | 77 | debug('Attaching spellcheck provider'); |
78 | attached = await attachSpellCheckProvider(provider); | ||
79 | |||
80 | const availableDictionaries = await provider.getAvailableDictionaries(); | ||
65 | 81 | ||
66 | switchDict(locale); | 82 | debug('Available spellchecker dictionaries', availableDictionaries); |
83 | |||
84 | await switchDict(locale); | ||
67 | 85 | ||
68 | return provider; | 86 | return provider; |
69 | } catch (err) { | 87 | } catch (err) { |
@@ -83,13 +101,3 @@ export function disable() { | |||
83 | currentDict = null; | 101 | currentDict = null; |
84 | } | 102 | } |
85 | } | 103 | } |
86 | |||
87 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { | ||
88 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
89 | |||
90 | if (locales.length >= 1) { | ||
91 | return locales[0]; | ||
92 | } | ||
93 | |||
94 | return null; | ||
95 | } | ||