diff options
-rw-r--r-- | .travis.yml | 23 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | src/api/server/LocalApi.js | 4 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 6 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 1 | ||||
-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/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 | 3 | ||||
-rw-r--r-- | src/stores/AppStore.js | 3 | ||||
-rw-r--r-- | src/webview/contextMenu.js | 9 | ||||
-rw-r--r-- | src/webview/spellchecker.js | 48 |
16 files changed, 396 insertions, 45 deletions
diff --git a/.travis.yml b/.travis.yml index dc97cc119..124a6fc8a 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,16 +1,17 @@ | |||
1 | matrix: | 1 | matrix: |
2 | include: | 2 | include: |
3 | - os: linux | 3 | - os: linux |
4 | dist: xenial | 4 | dist: xenial |
5 | addons: | 5 | addons: |
6 | apt: | 6 | apt: |
7 | packages: | 7 | packages: |
8 | - libx11-dev | 8 | - libx11-dev |
9 | - libxext-dev | 9 | - libxext-dev |
10 | - libxss-dev | 10 | - libxss-dev |
11 | - libxkbfile-dev | 11 | - libxkbfile-dev |
12 | - os: osx | 12 | - os: osx |
13 | osx_image: xcode10.3 | 13 | osx_image: xcode11 |
14 | |||
14 | language: node_js | 15 | language: node_js |
15 | install: | 16 | install: |
16 | - echo do nothing | 17 | - echo do nothing |
diff --git a/package.json b/package.json index 467ae3099..9f014a8e4 100644 --- a/package.json +++ b/package.json | |||
@@ -51,8 +51,8 @@ | |||
51 | "du": "^0.1.0", | 51 | "du": "^0.1.0", |
52 | "electron-dl": "1.14.0", | 52 | "electron-dl": "1.14.0", |
53 | "electron-fetch": "1.3.0", | 53 | "electron-fetch": "1.3.0", |
54 | "electron-hunspell": "0.1.1", | 54 | "electron-hunspell": "1.0.0", |
55 | "electron-is-dev": "1.1.0", | 55 | "electron-is-dev": "1.0.1", |
56 | "electron-react-titlebar": "0.8.1", | 56 | "electron-react-titlebar": "0.8.1", |
57 | "electron-updater": "4.1.2", | 57 | "electron-updater": "4.1.2", |
58 | "electron-window-state": "5.0.3", | 58 | "electron-window-state": "5.0.3", |
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index c4abc00e9..2d9af416f 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js | |||
@@ -45,13 +45,13 @@ export default class LocalApi { | |||
45 | const s = session.fromPartition(`persist:service-${serviceId}`); | 45 | const s = session.fromPartition(`persist:service-${serviceId}`); |
46 | 46 | ||
47 | debug('LocalApi::clearCache resolves', serviceId); | 47 | debug('LocalApi::clearCache resolves', serviceId); |
48 | return new Promise(resolve => s.clearCache(resolve)); | 48 | return s.clearCache(); |
49 | } | 49 | } |
50 | 50 | ||
51 | async clearAppCache() { | 51 | async clearAppCache() { |
52 | const s = session.defaultSession; | 52 | const s = session.defaultSession; |
53 | 53 | ||
54 | debug('LocalApi::clearCache clearAppCache'); | 54 | debug('LocalApi::clearCache clearAppCache'); |
55 | return new Promise(resolve => s.clearCache(resolve)); | 55 | return s.clearCache(); |
56 | } | 56 | } |
57 | } | 57 | } |
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index c45acc961..0cfefc92b 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js | |||
@@ -13,6 +13,7 @@ import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | |||
13 | import ServiceDisabled from './ServiceDisabled'; | 13 | import ServiceDisabled from './ServiceDisabled'; |
14 | import ServiceWebview from './ServiceWebview'; | 14 | import ServiceWebview from './ServiceWebview'; |
15 | import SettingsStore from '../../../stores/SettingsStore'; | 15 | import SettingsStore from '../../../stores/SettingsStore'; |
16 | import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; | ||
16 | 17 | ||
17 | export default @observer @inject('stores') class ServiceView extends Component { | 18 | export default @observer @inject('stores') class ServiceView extends Component { |
18 | static propTypes = { | 19 | static propTypes = { |
@@ -177,6 +178,9 @@ export default @observer @inject('stores') class ServiceView extends Component { | |||
177 | </Fragment> | 178 | </Fragment> |
178 | ) : ( | 179 | ) : ( |
179 | <> | 180 | <> |
181 | {service.recipe.id === 'franz-custom-website' && ( | ||
182 | <WebControlsScreen service={service} /> | ||
183 | )} | ||
180 | {!this.state.hibernate ? ( | 184 | {!this.state.hibernate ? ( |
181 | <ServiceWebview | 185 | <ServiceWebview |
182 | service={service} | 186 | service={service} |
@@ -187,7 +191,7 @@ export default @observer @inject('stores') class ServiceView extends Component { | |||
187 | <div> | 191 | <div> |
188 | <span role="img" aria-label="Sleeping Emoji">😴</span> | 192 | <span role="img" aria-label="Sleeping Emoji">😴</span> |
189 | {' '} | 193 | {' '} |
190 | This service is currently hibernating. If this page doesn't close soon, please try reloading Ferdi. | 194 | This service is currently hibernating. If this page doesn't close soon, please try reloading Ferdi. |
191 | </div> | 195 | </div> |
192 | )} | 196 | )} |
193 | </> | 197 | </> |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 647e31d52..03d6d5bcc 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -59,6 +59,7 @@ class ServiceWebview extends Component { | |||
59 | }} | 59 | }} |
60 | onUpdateTargetUrl={this.updateTargetUrl} | 60 | onUpdateTargetUrl={this.updateTargetUrl} |
61 | useragent={service.userAgent} | 61 | useragent={service.userAgent} |
62 | disablewebsecurity={service.recipe.disablewebsecurity} | ||
62 | allowpopups | 63 | allowpopups |
63 | /> | 64 | /> |
64 | ); | 65 | ); |
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index c252aff90..35c102220 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -37,9 +37,6 @@ const styles = theme => ({ | |||
37 | 37 | ||
38 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, | 38 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, |
39 | 39 | ||
40 | '&:hover $closeTodosButton': { | ||
41 | opacity: 1, | ||
42 | }, | ||
43 | '& webview': { | 40 | '& webview': { |
44 | height: '100%', | 41 | height: '100%', |
45 | }, | 42 | }, |
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index d507237d1..a05203a04 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js | |||
@@ -162,6 +162,10 @@ export default class TodoStore extends FeatureStore { | |||
162 | theme: isDarkThemeActive ? ThemeType.dark : ThemeType.default, | 162 | theme: isDarkThemeActive ? ThemeType.dark : ThemeType.default, |
163 | }, | 163 | }, |
164 | }); | 164 | }); |
165 | |||
166 | this.webview.addEventListener('new-window', ({ url }) => { | ||
167 | this.actions.app.openExternalUrl({ url }); | ||
168 | }); | ||
165 | }; | 169 | }; |
166 | 170 | ||
167 | _goToService = ({ url, serviceId }) => { | 171 | _goToService = ({ url, serviceId }) => { |
diff --git a/src/features/webControls/components/WebControls.js b/src/features/webControls/components/WebControls.js new file mode 100644 index 000000000..03f601a17 --- /dev/null +++ b/src/features/webControls/components/WebControls.js | |||
@@ -0,0 +1,190 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { Icon } from '@meetfranz/ui'; | ||
6 | |||
7 | import { | ||
8 | mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, | ||
9 | } from '@mdi/js'; | ||
10 | |||
11 | const styles = theme => ({ | ||
12 | root: { | ||
13 | background: theme.colorBackground, | ||
14 | position: 'relative', | ||
15 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
16 | zIndex: 300, | ||
17 | height: 50, | ||
18 | display: 'flex', | ||
19 | flexDirection: 'row', | ||
20 | alignItems: 'center', | ||
21 | padding: [0, 20], | ||
22 | |||
23 | '& + div': { | ||
24 | height: 'calc(100% - 50px)', | ||
25 | }, | ||
26 | }, | ||
27 | button: { | ||
28 | width: 30, | ||
29 | height: 50, | ||
30 | transition: 'opacity 0.25s', | ||
31 | |||
32 | '&:hover': { | ||
33 | opacity: 0.8, | ||
34 | }, | ||
35 | |||
36 | '&:disabled': { | ||
37 | opacity: 0.5, | ||
38 | }, | ||
39 | }, | ||
40 | icon: { | ||
41 | width: '20px !important', | ||
42 | height: 20, | ||
43 | marginTop: 5, | ||
44 | }, | ||
45 | input: { | ||
46 | marginBottom: 0, | ||
47 | height: 'auto', | ||
48 | marginLeft: 10, | ||
49 | flex: 1, | ||
50 | border: 0, | ||
51 | padding: [4, 10], | ||
52 | borderRadius: theme.borderRadius, | ||
53 | background: theme.inputBackground, | ||
54 | color: theme.inputColor, | ||
55 | }, | ||
56 | inputButton: { | ||
57 | color: theme.colorText, | ||
58 | }, | ||
59 | }); | ||
60 | |||
61 | @injectSheet(styles) @observer | ||
62 | class WebControls extends Component { | ||
63 | static propTypes = { | ||
64 | classes: PropTypes.object.isRequired, | ||
65 | goHome: PropTypes.func.isRequired, | ||
66 | canGoBack: PropTypes.bool.isRequired, | ||
67 | goBack: PropTypes.func.isRequired, | ||
68 | canGoForward: PropTypes.bool.isRequired, | ||
69 | goForward: PropTypes.func.isRequired, | ||
70 | reload: PropTypes.func.isRequired, | ||
71 | url: PropTypes.string.isRequired, | ||
72 | navigate: PropTypes.func.isRequired, | ||
73 | } | ||
74 | |||
75 | static getDerivedStateFromProps(props, state) { | ||
76 | const { url } = props; | ||
77 | const { editUrl } = state; | ||
78 | |||
79 | if (!editUrl) { | ||
80 | return { | ||
81 | inputUrl: url, | ||
82 | editUrl: state.editUrl, | ||
83 | }; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | inputRef = React.createRef(); | ||
88 | |||
89 | state = { | ||
90 | inputUrl: '', | ||
91 | editUrl: false, | ||
92 | } | ||
93 | |||
94 | render() { | ||
95 | const { | ||
96 | classes, | ||
97 | goHome, | ||
98 | canGoBack, | ||
99 | goBack, | ||
100 | canGoForward, | ||
101 | goForward, | ||
102 | reload, | ||
103 | url, | ||
104 | navigate, | ||
105 | } = this.props; | ||
106 | |||
107 | const { | ||
108 | inputUrl, | ||
109 | editUrl, | ||
110 | } = this.state; | ||
111 | |||
112 | return ( | ||
113 | <div className={classes.root}> | ||
114 | <button | ||
115 | onClick={goHome} | ||
116 | type="button" | ||
117 | className={classes.button} | ||
118 | > | ||
119 | <Icon | ||
120 | icon={mdiHomeOutline} | ||
121 | className={classes.icon} | ||
122 | /> | ||
123 | </button> | ||
124 | <button | ||
125 | onClick={goBack} | ||
126 | type="button" | ||
127 | className={classes.button} | ||
128 | disabled={!canGoBack} | ||
129 | > | ||
130 | <Icon | ||
131 | icon={mdiArrowLeft} | ||
132 | className={classes.icon} | ||
133 | /> | ||
134 | </button> | ||
135 | <button | ||
136 | onClick={goForward} | ||
137 | type="button" | ||
138 | className={classes.button} | ||
139 | disabled={!canGoForward} | ||
140 | > | ||
141 | <Icon | ||
142 | icon={mdiArrowRight} | ||
143 | className={classes.icon} | ||
144 | /> | ||
145 | </button> | ||
146 | <button | ||
147 | onClick={reload} | ||
148 | type="button" | ||
149 | className={classes.button} | ||
150 | > | ||
151 | <Icon | ||
152 | icon={mdiReload} | ||
153 | className={classes.icon} | ||
154 | /> | ||
155 | </button> | ||
156 | <input | ||
157 | value={editUrl ? inputUrl : url} | ||
158 | className={classes.input} | ||
159 | onChange={event => this.setState({ | ||
160 | inputUrl: event.target.value, | ||
161 | })} | ||
162 | onFocus={(event) => { | ||
163 | event.target.select(); | ||
164 | this.setState({ | ||
165 | editUrl: true, | ||
166 | }); | ||
167 | }} | ||
168 | onKeyDown={(event) => { | ||
169 | if (event.key === 'Enter') { | ||
170 | this.setState({ | ||
171 | editUrl: false, | ||
172 | }); | ||
173 | navigate(inputUrl); | ||
174 | this.inputRef.current.blur(); | ||
175 | } else if (event.key === 'Escape') { | ||
176 | this.setState({ | ||
177 | editUrl: false, | ||
178 | inputUrl: url, | ||
179 | }); | ||
180 | event.target.blur(); | ||
181 | } | ||
182 | }} | ||
183 | ref={this.inputRef} | ||
184 | /> | ||
185 | </div> | ||
186 | ); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | export default WebControls; | ||
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js new file mode 100644 index 000000000..1452d5a3d --- /dev/null +++ b/src/features/webControls/containers/WebControlsScreen.js | |||
@@ -0,0 +1,128 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | |||
5 | import { autorun, observable } from 'mobx'; | ||
6 | import WebControls from '../components/WebControls'; | ||
7 | import ServicesStore from '../../../stores/ServicesStore'; | ||
8 | import Service from '../../../models/Service'; | ||
9 | |||
10 | const URL_EVENTS = [ | ||
11 | 'load-commit', | ||
12 | // 'dom-ready', | ||
13 | 'will-navigate', | ||
14 | 'did-navigate', | ||
15 | 'did-navigate-in-page', | ||
16 | ]; | ||
17 | |||
18 | @inject('stores', 'actions') @observer | ||
19 | class WebControlsScreen extends Component { | ||
20 | @observable url = ''; | ||
21 | |||
22 | @observable canGoBack = false; | ||
23 | |||
24 | @observable canGoForward = false; | ||
25 | |||
26 | webview = null; | ||
27 | |||
28 | autorunDisposer = null; | ||
29 | |||
30 | componentDidMount() { | ||
31 | const { service } = this.props; | ||
32 | |||
33 | this.autorunDisposer = autorun(() => { | ||
34 | if (service.isAttached) { | ||
35 | this.webview = service.webview; | ||
36 | |||
37 | URL_EVENTS.forEach((event) => { | ||
38 | this.webview.addEventListener(event, (e) => { | ||
39 | if (!e.isMainFrame) return; | ||
40 | |||
41 | this.url = e.url; | ||
42 | this.canGoBack = this.webview.canGoBack(); | ||
43 | this.canGoForward = this.webview.canGoForward(); | ||
44 | }); | ||
45 | }); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
49 | |||
50 | componentWillUnmount() { | ||
51 | this.autorunDisposer(); | ||
52 | } | ||
53 | |||
54 | goHome() { | ||
55 | const { reloadActive } = this.props.actions.service; | ||
56 | |||
57 | if (!this.webview) return; | ||
58 | |||
59 | reloadActive(); | ||
60 | } | ||
61 | |||
62 | reload() { | ||
63 | if (!this.webview) return; | ||
64 | |||
65 | this.webview.reload(); | ||
66 | } | ||
67 | |||
68 | goBack() { | ||
69 | if (!this.webview) return; | ||
70 | |||
71 | this.webview.goBack(); | ||
72 | } | ||
73 | |||
74 | goForward() { | ||
75 | if (!this.webview) return; | ||
76 | |||
77 | this.webview.goForward(); | ||
78 | } | ||
79 | |||
80 | navigate(newUrl) { | ||
81 | if (!this.webview) return; | ||
82 | |||
83 | let url = newUrl; | ||
84 | |||
85 | try { | ||
86 | url = new URL(url).toString(); | ||
87 | } catch (err) { | ||
88 | // eslint-disable-next-line no-useless-escape | ||
89 | if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) { | ||
90 | url = `http://${url}`; | ||
91 | } else { | ||
92 | url = `https://www.google.com/search?query=${url}`; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | this.webview.loadURL(url); | ||
97 | this.url = url; | ||
98 | } | ||
99 | |||
100 | render() { | ||
101 | return ( | ||
102 | <WebControls | ||
103 | goHome={() => this.goHome()} | ||
104 | reload={() => this.reload()} | ||
105 | canGoBack={this.canGoBack} | ||
106 | goBack={() => this.goBack()} | ||
107 | canGoForward={this.canGoForward} | ||
108 | goForward={() => this.goForward()} | ||
109 | navigate={url => this.navigate(url)} | ||
110 | url={this.url} | ||
111 | /> | ||
112 | ); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | export default WebControlsScreen; | ||
117 | |||
118 | WebControlsScreen.wrappedComponent.propTypes = { | ||
119 | service: PropTypes.instanceOf(Service).isRequired, | ||
120 | stores: PropTypes.shape({ | ||
121 | services: PropTypes.instanceOf(ServicesStore).isRequired, | ||
122 | }).isRequired, | ||
123 | actions: PropTypes.shape({ | ||
124 | service: PropTypes.shape({ | ||
125 | reloadActive: PropTypes.func.isRequired, | ||
126 | }).isRequired, | ||
127 | }).isRequired, | ||
128 | }; | ||
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index beaafb4d1..7e336c994 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -325,6 +325,9 @@ const _templateFactory = intl => [ | |||
325 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), | 325 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), |
326 | accelerator: 'Cmd+Shift+V', | 326 | accelerator: 'Cmd+Shift+V', |
327 | selector: 'pasteAndMatchStyle:', | 327 | selector: 'pasteAndMatchStyle:', |
328 | click() { | ||
329 | getActiveWebview().pasteAndMatchStyle(); | ||
330 | }, | ||
328 | }, | 331 | }, |
329 | { | 332 | { |
330 | label: intl.formatMessage(menuItems.delete), | 333 | label: intl.formatMessage(menuItems.delete), |
@@ -985,6 +988,10 @@ export default class FranzMenu { | |||
985 | checked: service.isActive, | 988 | checked: service.isActive, |
986 | click: () => { | 989 | click: () => { |
987 | this.actions.service.setActive({ serviceId: service.id }); | 990 | this.actions.service.setActive({ serviceId: service.id }); |
991 | |||
992 | if (isMac && i === 0) { | ||
993 | app.mainWindow.restore(); | ||
994 | } | ||
988 | }, | 995 | }, |
989 | }))); | 996 | }))); |
990 | 997 | ||
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js index 97c02d194..1de46d2a3 100644 --- a/src/lib/TouchBar.js +++ b/src/lib/TouchBar.js | |||
@@ -29,7 +29,7 @@ export default class FranzTouchBar { | |||
29 | const { TouchBarButton, TouchBarSpacer } = TouchBar; | 29 | const { TouchBarButton, TouchBarSpacer } = TouchBar; |
30 | 30 | ||
31 | const buttons = []; | 31 | const buttons = []; |
32 | this.stores.services.enabled.forEach(((service) => { | 32 | this.stores.services.allDisplayed.forEach(((service) => { |
33 | buttons.push(new TouchBarButton({ | 33 | buttons.push(new TouchBarButton({ |
34 | label: `${service.name}${service.unreadDirectMessageCount > 0 | 34 | label: `${service.name}${service.unreadDirectMessageCount > 0 |
35 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 | 35 | ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0 |
@@ -42,7 +42,7 @@ export default class FranzTouchBar { | |||
42 | }), new TouchBarSpacer({ size: 'small' })); | 42 | }), new TouchBarSpacer({ size: 'small' })); |
43 | })); | 43 | })); |
44 | 44 | ||
45 | const touchBar = new TouchBar(buttons); | 45 | const touchBar = new TouchBar({ items: buttons }); |
46 | currentWindow.setTouchBar(touchBar); | 46 | currentWindow.setTouchBar(touchBar); |
47 | } else { | 47 | } else { |
48 | currentWindow.setTouchBar(null); | 48 | currentWindow.setTouchBar(null); |
diff --git a/src/models/Recipe.js b/src/models/Recipe.js index 3f7299e34..6655f8310 100644 --- a/src/models/Recipe.js +++ b/src/models/Recipe.js | |||
@@ -36,6 +36,8 @@ export default class Recipe { | |||
36 | 36 | ||
37 | message = ''; | 37 | message = ''; |
38 | 38 | ||
39 | disablewebsecurity = false; | ||
40 | |||
39 | constructor(data) { | 41 | constructor(data) { |
40 | if (!data) { | 42 | if (!data) { |
41 | throw Error('Recipe config not valid'); | 43 | throw Error('Recipe config not valid'); |
@@ -74,6 +76,8 @@ export default class Recipe { | |||
74 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; | 76 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; |
75 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; | 77 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; |
76 | 78 | ||
79 | this.disablewebsecurity = data.config.disablewebsecurity || this.disablewebsecurity; | ||
80 | |||
77 | this.message = data.config.message || this.message; | 81 | this.message = data.config.message || this.message; |
78 | } | 82 | } |
79 | 83 | ||
diff --git a/src/models/Service.js b/src/models/Service.js index b48233237..3ab6e2603 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -134,6 +134,9 @@ export default class Service { | |||
134 | id: this.id, | 134 | id: this.id, |
135 | spellcheckerLanguage: this.spellcheckerLanguage, | 135 | spellcheckerLanguage: this.spellcheckerLanguage, |
136 | isDarkModeEnabled: this.isDarkModeEnabled, | 136 | isDarkModeEnabled: this.isDarkModeEnabled, |
137 | team: this.team, | ||
138 | url: this.url, | ||
139 | hasCustomIcon: this.hasCustomIcon, | ||
137 | }; | 140 | }; |
138 | } | 141 | } |
139 | 142 | ||
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 5bae6e8d4..40d98cf42 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -22,6 +22,7 @@ import { getLocale } from '../helpers/i18n-helpers'; | |||
22 | 22 | ||
23 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 23 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
24 | import { isValidExternalURL } from '../helpers/url-helpers'; | 24 | import { isValidExternalURL } from '../helpers/url-helpers'; |
25 | import { sleep } from '../helpers/async-helpers'; | ||
25 | 26 | ||
26 | const debug = require('debug')('Ferdi:AppStore'); | 27 | const debug = require('debug')('Ferdi:AppStore'); |
27 | 28 | ||
@@ -317,6 +318,8 @@ export default class AppStore extends Store { | |||
317 | 318 | ||
318 | await clearAppCache._promise; | 319 | await clearAppCache._promise; |
319 | 320 | ||
321 | await sleep(ms('1s')); | ||
322 | |||
320 | this.getAppCacheSizeRequest.execute(); | 323 | this.getAppCacheSizeRequest.execute(); |
321 | 324 | ||
322 | this.isClearingAllCache = false; | 325 | this.isClearingAllCache = false; |
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index ec5833848..acd62d675 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -255,9 +255,9 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
255 | }, | 255 | }, |
256 | { | 256 | { |
257 | id: 'resetToDefault', | 257 | id: 'resetToDefault', |
258 | label: `Reset to system default (${SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, | 258 | label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, |
259 | type: 'radio', | 259 | type: 'radio', |
260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, | 260 | visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'), |
261 | click() { | 261 | click() { |
262 | debug('Resetting service spellchecker to system default'); | 262 | debug('Resetting service spellchecker to system default'); |
263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); | 263 | ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); |
@@ -297,12 +297,13 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
297 | }; | 297 | }; |
298 | 298 | ||
299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { | 299 | export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { |
300 | webContents.on('context-menu', (e, props) => { | 300 | webContents.on('context-menu', async (e, props) => { |
301 | e.preventDefault(); | 301 | e.preventDefault(); |
302 | 302 | ||
303 | let suggestions = []; | 303 | let suggestions = []; |
304 | if (spellcheckProvider && props.misspelledWord) { | 304 | if (spellcheckProvider && props.misspelledWord) { |
305 | suggestions = spellcheckProvider.getSuggestion(props.misspelledWord); | 305 | debug('Mispelled word', props.misspelledWord); |
306 | suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord); | ||
306 | 307 | ||
307 | debug('Suggestions', suggestions); | 308 | debug('Suggestions', suggestions); |
308 | } | 309 | } |
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 1b2d60faf..27380676d 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -1,6 +1,7 @@ | |||
1 | import { webFrame } from 'electron'; | 1 | import { webFrame } from 'electron'; |
2 | import { SpellCheckerProvider } from 'electron-hunspell'; | 2 | import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell'; |
3 | import path from 'path'; | 3 | import path from 'path'; |
4 | import { readFileSync } from 'fs'; | ||
4 | 5 | ||
5 | import { DICTIONARY_PATH } from '../config'; | 6 | import { DICTIONARY_PATH } from '../config'; |
6 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 7 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
@@ -10,18 +11,21 @@ const debug = require('debug')('Ferdi:spellchecker'); | |||
10 | let provider; | 11 | let provider; |
11 | let currentDict; | 12 | let currentDict; |
12 | let _isEnabled = false; | 13 | let _isEnabled = false; |
14 | let attached; | ||
15 | |||
16 | const DEFAULT_LOCALE = 'en-us'; | ||
13 | 17 | ||
14 | async function loadDictionary(locale) { | 18 | async function loadDictionary(locale) { |
15 | try { | 19 | try { |
16 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); | 20 | const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); |
17 | await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`); | ||
18 | debug('Loaded dictionary', locale, 'from', fileLocation); | 21 | debug('Loaded dictionary', locale, 'from', fileLocation); |
22 | return provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`)); | ||
19 | } catch (err) { | 23 | } catch (err) { |
20 | console.error('Could not load dictionary', err); | 24 | console.error('Could not load dictionary', err); |
21 | } | 25 | } |
22 | } | 26 | } |
23 | 27 | ||
24 | export async function switchDict(locale) { | 28 | export async function switchDict(locale = DEFAULT_LOCALE) { |
25 | try { | 29 | try { |
26 | debug('Trying to load dictionary', locale); | 30 | debug('Trying to load dictionary', locale); |
27 | 31 | ||
@@ -40,8 +44,8 @@ export async function switchDict(locale) { | |||
40 | if (currentDict) { | 44 | if (currentDict) { |
41 | provider.unloadDictionary(locale); | 45 | provider.unloadDictionary(locale); |
42 | } | 46 | } |
43 | loadDictionary(locale); | 47 | await loadDictionary(locale); |
44 | provider.switchDictionary(locale); | 48 | await attached.switchLanguage(locale); |
45 | 49 | ||
46 | debug('Switched dictionary to', locale); | 50 | debug('Switched dictionary to', locale); |
47 | 51 | ||
@@ -52,18 +56,32 @@ export async function switchDict(locale) { | |||
52 | } | 56 | } |
53 | } | 57 | } |
54 | 58 | ||
55 | export default async function initialize(languageCode = 'en-us') { | 59 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { |
60 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
61 | |||
62 | if (locales.length >= 1) { | ||
63 | return locales[0]; | ||
64 | } | ||
65 | |||
66 | return null; | ||
67 | } | ||
68 | |||
69 | export default async function initialize(languageCode = DEFAULT_LOCALE) { | ||
56 | try { | 70 | try { |
57 | provider = new SpellCheckerProvider(); | 71 | provider = new SpellCheckerProvider(); |
58 | const locale = languageCode.toLowerCase(); | 72 | const locale = getSpellcheckerLocaleByFuzzyIdentifier(languageCode); |
59 | 73 | ||
60 | debug('Init spellchecker'); | 74 | debug('Init spellchecker'); |
61 | await provider.initialize(); | 75 | await provider.initialize(); |
62 | // await loadDictionaries(); | ||
63 | 76 | ||
64 | debug('Available spellchecker dictionaries', provider.availableDictionaries); | 77 | debug('Attaching spellcheck provider'); |
78 | attached = await attachSpellCheckProvider(provider); | ||
79 | |||
80 | const availableDictionaries = await provider.getAvailableDictionaries(); | ||
65 | 81 | ||
66 | switchDict(locale); | 82 | debug('Available spellchecker dictionaries', availableDictionaries); |
83 | |||
84 | await switchDict(locale); | ||
67 | 85 | ||
68 | return provider; | 86 | return provider; |
69 | } catch (err) { | 87 | } catch (err) { |
@@ -83,13 +101,3 @@ export function disable() { | |||
83 | currentDict = null; | 101 | currentDict = null; |
84 | } | 102 | } |
85 | } | 103 | } |
86 | |||
87 | export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) { | ||
88 | const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase()); | ||
89 | |||
90 | if (locales.length >= 1) { | ||
91 | return locales[0]; | ||
92 | } | ||
93 | |||
94 | return null; | ||
95 | } | ||