diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/server/LocalApi.js | 4 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 16 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 1 | ||||
-rw-r--r-- | src/components/ui/Modal/styles.js | 2 | ||||
-rw-r--r-- | src/features/todos/components/TodosWebview.js | 3 | ||||
-rw-r--r-- | src/features/todos/store.js | 4 | ||||
-rw-r--r-- | src/features/webControls/components/WebControls.js | 190 | ||||
-rw-r--r-- | src/features/webControls/containers/WebControlsScreen.js | 128 | ||||
-rw-r--r-- | src/index.js | 17 | ||||
-rw-r--r-- | src/lib/Menu.js | 7 | ||||
-rw-r--r-- | src/lib/TouchBar.js | 4 | ||||
-rw-r--r-- | src/models/Recipe.js | 4 | ||||
-rw-r--r-- | src/models/Service.js | 44 | ||||
-rw-r--r-- | src/stores/AppStore.js | 3 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 1 | ||||
-rw-r--r-- | src/webview/contextMenu.js | 9 | ||||
-rw-r--r-- | src/webview/recipe.js | 9 | ||||
-rw-r--r-- | src/webview/spellchecker.js | 48 |
18 files changed, 433 insertions, 61 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/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..07bd17d9c 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -41,6 +41,7 @@ class ServiceWebview extends Component { | |||
41 | }} | 41 | }} |
42 | onUpdateTargetUrl={this.updateTargetUrl} | 42 | onUpdateTargetUrl={this.updateTargetUrl} |
43 | useragent={service.userAgent} | 43 | useragent={service.userAgent} |
44 | disablewebsecurity={service.recipe.disablewebsecurity} | ||
44 | allowpopups | 45 | allowpopups |
45 | /> | 46 | /> |
46 | ); | 47 | ); |
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/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..03f601a17 --- /dev/null +++ b/src/features/webControls/components/WebControls.js | |||
@@ -0,0 +1,190 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { Icon } from '@meetfranz/ui'; | ||
6 | |||
7 | import { | ||
8 | mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, | ||
9 | } from '@mdi/js'; | ||
10 | |||
11 | const styles = theme => ({ | ||
12 | root: { | ||
13 | background: theme.colorBackground, | ||
14 | position: 'relative', | ||
15 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
16 | zIndex: 300, | ||
17 | height: 50, | ||
18 | display: 'flex', | ||
19 | flexDirection: 'row', | ||
20 | alignItems: 'center', | ||
21 | padding: [0, 20], | ||
22 | |||
23 | '& + div': { | ||
24 | height: 'calc(100% - 50px)', | ||
25 | }, | ||
26 | }, | ||
27 | button: { | ||
28 | width: 30, | ||
29 | height: 50, | ||
30 | transition: 'opacity 0.25s', | ||
31 | |||
32 | '&:hover': { | ||
33 | opacity: 0.8, | ||
34 | }, | ||
35 | |||
36 | '&:disabled': { | ||
37 | opacity: 0.5, | ||
38 | }, | ||
39 | }, | ||
40 | icon: { | ||
41 | width: '20px !important', | ||
42 | height: 20, | ||
43 | marginTop: 5, | ||
44 | }, | ||
45 | input: { | ||
46 | marginBottom: 0, | ||
47 | height: 'auto', | ||
48 | marginLeft: 10, | ||
49 | flex: 1, | ||
50 | border: 0, | ||
51 | padding: [4, 10], | ||
52 | borderRadius: theme.borderRadius, | ||
53 | background: theme.inputBackground, | ||
54 | color: theme.inputColor, | ||
55 | }, | ||
56 | inputButton: { | ||
57 | color: theme.colorText, | ||
58 | }, | ||
59 | }); | ||
60 | |||
61 | @injectSheet(styles) @observer | ||
62 | class WebControls extends Component { | ||
63 | static propTypes = { | ||
64 | classes: PropTypes.object.isRequired, | ||
65 | goHome: PropTypes.func.isRequired, | ||
66 | canGoBack: PropTypes.bool.isRequired, | ||
67 | goBack: PropTypes.func.isRequired, | ||
68 | canGoForward: PropTypes.bool.isRequired, | ||
69 | goForward: PropTypes.func.isRequired, | ||
70 | reload: PropTypes.func.isRequired, | ||
71 | url: PropTypes.string.isRequired, | ||
72 | navigate: PropTypes.func.isRequired, | ||
73 | } | ||
74 | |||
75 | static getDerivedStateFromProps(props, state) { | ||
76 | const { url } = props; | ||
77 | const { editUrl } = state; | ||
78 | |||
79 | if (!editUrl) { | ||
80 | return { | ||
81 | inputUrl: url, | ||
82 | editUrl: state.editUrl, | ||
83 | }; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | inputRef = React.createRef(); | ||
88 | |||
89 | state = { | ||
90 | inputUrl: '', | ||
91 | editUrl: false, | ||
92 | } | ||
93 | |||
94 | render() { | ||
95 | const { | ||
96 | classes, | ||
97 | goHome, | ||
98 | canGoBack, | ||
99 | goBack, | ||
100 | canGoForward, | ||
101 | goForward, | ||
102 | reload, | ||
103 | url, | ||
104 | navigate, | ||
105 | } = this.props; | ||
106 | |||
107 | const { | ||
108 | inputUrl, | ||
109 | editUrl, | ||
110 | } = this.state; | ||
111 | |||
112 | return ( | ||
113 | <div className={classes.root}> | ||
114 | <button | ||
115 | onClick={goHome} | ||
116 | type="button" | ||
117 | className={classes.button} | ||
118 | > | ||
119 | <Icon | ||
120 | icon={mdiHomeOutline} | ||
121 | className={classes.icon} | ||
122 | /> | ||
123 | </button> | ||
124 | <button | ||
125 | onClick={goBack} | ||
126 | type="button" | ||
127 | className={classes.button} | ||
128 | disabled={!canGoBack} | ||
129 | > | ||
130 | <Icon | ||
131 | icon={mdiArrowLeft} | ||
132 | className={classes.icon} | ||
133 | /> | ||
134 | </button> | ||
135 | <button | ||
136 | onClick={goForward} | ||
137 | type="button" | ||
138 | className={classes.button} | ||
139 | disabled={!canGoForward} | ||
140 | > | ||
141 | <Icon | ||
142 | icon={mdiArrowRight} | ||
143 | className={classes.icon} | ||
144 | /> | ||
145 | </button> | ||
146 | <button | ||
147 | onClick={reload} | ||
148 | type="button" | ||
149 | className={classes.button} | ||
150 | > | ||
151 | <Icon | ||
152 | icon={mdiReload} | ||
153 | className={classes.icon} | ||
154 | /> | ||
155 | </button> | ||
156 | <input | ||
157 | value={editUrl ? inputUrl : url} | ||
158 | className={classes.input} | ||
159 | onChange={event => this.setState({ | ||
160 | inputUrl: event.target.value, | ||
161 | })} | ||
162 | onFocus={(event) => { | ||
163 | event.target.select(); | ||
164 | this.setState({ | ||
165 | editUrl: true, | ||
166 | }); | ||
167 | }} | ||
168 | onKeyDown={(event) => { | ||
169 | if (event.key === 'Enter') { | ||
170 | this.setState({ | ||
171 | editUrl: false, | ||
172 | }); | ||
173 | navigate(inputUrl); | ||
174 | this.inputRef.current.blur(); | ||
175 | } else if (event.key === 'Escape') { | ||
176 | this.setState({ | ||
177 | editUrl: false, | ||
178 | inputUrl: url, | ||
179 | }); | ||
180 | event.target.blur(); | ||
181 | } | ||
182 | }} | ||
183 | ref={this.inputRef} | ||
184 | /> | ||
185 | </div> | ||
186 | ); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | export default WebControls; | ||
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js new file mode 100644 index 000000000..1452d5a3d --- /dev/null +++ b/src/features/webControls/containers/WebControlsScreen.js | |||
@@ -0,0 +1,128 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | |||
5 | import { autorun, observable } from 'mobx'; | ||
6 | import WebControls from '../components/WebControls'; | ||
7 | import ServicesStore from '../../../stores/ServicesStore'; | ||
8 | import Service from '../../../models/Service'; | ||
9 | |||
10 | const URL_EVENTS = [ | ||
11 | 'load-commit', | ||
12 | // 'dom-ready', | ||
13 | 'will-navigate', | ||
14 | 'did-navigate', | ||
15 | 'did-navigate-in-page', | ||
16 | ]; | ||
17 | |||
18 | @inject('stores', 'actions') @observer | ||
19 | class WebControlsScreen extends Component { | ||
20 | @observable url = ''; | ||
21 | |||
22 | @observable canGoBack = false; | ||
23 | |||
24 | @observable canGoForward = false; | ||
25 | |||
26 | webview = null; | ||
27 | |||
28 | autorunDisposer = null; | ||
29 | |||
30 | componentDidMount() { | ||
31 | const { service } = this.props; | ||
32 | |||
33 | this.autorunDisposer = autorun(() => { | ||
34 | if (service.isAttached) { | ||
35 | this.webview = service.webview; | ||
36 | |||
37 | URL_EVENTS.forEach((event) => { | ||
38 | this.webview.addEventListener(event, (e) => { | ||
39 | if (!e.isMainFrame) return; | ||
40 | |||
41 | this.url = e.url; | ||
42 | this.canGoBack = this.webview.canGoBack(); | ||
43 | this.canGoForward = this.webview.canGoForward(); | ||
44 | }); | ||
45 | }); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
49 | |||
50 | componentWillUnmount() { | ||
51 | this.autorunDisposer(); | ||
52 | } | ||
53 | |||
54 | goHome() { | ||
55 | const { reloadActive } = this.props.actions.service; | ||
56 | |||
57 | if (!this.webview) return; | ||
58 | |||
59 | reloadActive(); | ||
60 | } | ||
61 | |||
62 | reload() { | ||
63 | if (!this.webview) return; | ||
64 | |||
65 | this.webview.reload(); | ||
66 | } | ||
67 | |||
68 | goBack() { | ||
69 | if (!this.webview) return; | ||
70 | |||
71 | this.webview.goBack(); | ||
72 | } | ||
73 | |||
74 | goForward() { | ||
75 | if (!this.webview) return; | ||
76 | |||
77 | this.webview.goForward(); | ||
78 | } | ||
79 | |||
80 | navigate(newUrl) { | ||
81 | if (!this.webview) return; | ||
82 | |||
83 | let url = newUrl; | ||
84 | |||
85 | try { | ||
86 | url = new URL(url).toString(); | ||
87 | } catch (err) { | ||
88 | // eslint-disable-next-line no-useless-escape | ||
89 | if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) { | ||
90 | url = `http://${url}`; | ||
91 | } else { | ||
92 | url = `https://www.google.com/search?query=${url}`; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | this.webview.loadURL(url); | ||
97 | this.url = url; | ||
98 | } | ||
99 | |||
100 | render() { | ||
101 | return ( | ||
102 | <WebControls | ||
103 | goHome={() => this.goHome()} | ||
104 | reload={() => this.reload()} | ||
105 | canGoBack={this.canGoBack} | ||
106 | goBack={() => this.goBack()} | ||
107 | canGoForward={this.canGoForward} | ||
108 | goForward={() => this.goForward()} | ||
109 | navigate={url => this.navigate(url)} | ||
110 | url={this.url} | ||
111 | /> | ||
112 | ); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | export default WebControlsScreen; | ||
117 | |||
118 | WebControlsScreen.wrappedComponent.propTypes = { | ||
119 | service: PropTypes.instanceOf(Service).isRequired, | ||
120 | stores: PropTypes.shape({ | ||
121 | services: PropTypes.instanceOf(ServicesStore).isRequired, | ||
122 | }).isRequired, | ||
123 | actions: PropTypes.shape({ | ||
124 | service: PropTypes.shape({ | ||
125 | reloadActive: PropTypes.func.isRequired, | ||
126 | }).isRequired, | ||
127 | }).isRequired, | ||
128 | }; | ||
diff --git a/src/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 b72f0df96..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), |
@@ -867,6 +870,10 @@ export default class FranzMenu { | |||
867 | checked: service.isActive, | 870 | checked: service.isActive, |
868 | click: () => { | 871 | click: () => { |
869 | 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 | } | ||
870 | }, | 877 | }, |
871 | }))); | 878 | }))); |
872 | 879 | ||
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..08befe4eb 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 | } |
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..353eb31fd 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 | ||
@@ -173,11 +174,17 @@ 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 | debug('window.open', url, frameName, features); | ||
177 | // We need to differentiate if the link should be opened in a popup or in the systems default browser | 180 | // We need to differentiate if the link should be opened in a popup or in the systems default browser |
178 | if (!frameName && !features) { | 181 | if (!frameName && !features && typeof features !== 'string') { |
179 | return ipcRenderer.sendToHost('new-window', url); | 182 | return ipcRenderer.sendToHost('new-window', url); |
180 | } | 183 | } |
181 | 184 | ||
182 | return originalWindowOpen(url, frameName, features); | 185 | return originalWindowOpen(url, frameName, features); |
183 | }; | 186 | }; |
187 | |||
188 | if (isDevMode) { | ||
189 | window.log = console.log; | ||
190 | } | ||
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 | } | ||