aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml23
-rw-r--r--package.json4
-rw-r--r--src/api/server/LocalApi.js4
-rw-r--r--src/components/services/content/ServiceView.js6
-rw-r--r--src/components/services/content/ServiceWebview.js1
-rw-r--r--src/features/todos/components/TodosWebview.js3
-rw-r--r--src/features/todos/store.js4
-rw-r--r--src/features/webControls/components/WebControls.js190
-rw-r--r--src/features/webControls/containers/WebControlsScreen.js128
-rw-r--r--src/lib/Menu.js7
-rw-r--r--src/lib/TouchBar.js4
-rw-r--r--src/models/Recipe.js4
-rw-r--r--src/models/Service.js3
-rw-r--r--src/stores/AppStore.js3
-rw-r--r--src/webview/contextMenu.js9
-rw-r--r--src/webview/spellchecker.js48
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 @@
1matrix: 1matrix:
2 include: 2 include:
3 - os: linux 3 - os: linux
4 dist: xenial 4 dist: xenial
5 addons: 5 addons:
6 apt: 6 apt:
7 packages: 7 packages:
8 - libx11-dev 8 - libx11-dev
9 - libxext-dev 9 - libxext-dev
10 - libxss-dev 10 - libxss-dev
11 - libxkbfile-dev 11 - libxkbfile-dev
12 - os: osx 12 - os: osx
13 osx_image: xcode10.3 13 osx_image: xcode11
14
14language: node_js 15language: node_js
15install: 16install:
16 - echo do nothing 17 - echo do nothing
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';
13import ServiceDisabled from './ServiceDisabled'; 13import ServiceDisabled from './ServiceDisabled';
14import ServiceWebview from './ServiceWebview'; 14import ServiceWebview from './ServiceWebview';
15import SettingsStore from '../../../stores/SettingsStore'; 15import SettingsStore from '../../../stores/SettingsStore';
16import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
16 17
17export default @observer @inject('stores') class ServiceView extends Component { 18export default @observer @inject('stores') class ServiceView extends Component {
18 static propTypes = { 19 static propTypes = {
@@ -177,6 +178,9 @@ export default @observer @inject('stores') class ServiceView extends Component {
177 </Fragment> 178 </Fragment>
178 ) : ( 179 ) : (
179 <> 180 <>
181 {service.recipe.id === 'franz-custom-website' && (
182 <WebControlsScreen service={service} />
183 )}
180 {!this.state.hibernate ? ( 184 {!this.state.hibernate ? (
181 <ServiceWebview 185 <ServiceWebview
182 service={service} 186 service={service}
@@ -187,7 +191,7 @@ export default @observer @inject('stores') class ServiceView extends Component {
187 <div> 191 <div>
188 <span role="img" aria-label="Sleeping Emoji">😴</span> 192 <span role="img" aria-label="Sleeping Emoji">😴</span>
189 {' '} 193 {' '}
190This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi. 194 This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi.
191 </div> 195 </div>
192 )} 196 )}
193 </> 197 </>
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 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 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui';
6
7import {
8 mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline,
9} from '@mdi/js';
10
11const styles = theme => ({
12 root: {
13 background: theme.colorBackground,
14 position: 'relative',
15 borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
16 zIndex: 300,
17 height: 50,
18 display: 'flex',
19 flexDirection: 'row',
20 alignItems: 'center',
21 padding: [0, 20],
22
23 '& + div': {
24 height: 'calc(100% - 50px)',
25 },
26 },
27 button: {
28 width: 30,
29 height: 50,
30 transition: 'opacity 0.25s',
31
32 '&:hover': {
33 opacity: 0.8,
34 },
35
36 '&:disabled': {
37 opacity: 0.5,
38 },
39 },
40 icon: {
41 width: '20px !important',
42 height: 20,
43 marginTop: 5,
44 },
45 input: {
46 marginBottom: 0,
47 height: 'auto',
48 marginLeft: 10,
49 flex: 1,
50 border: 0,
51 padding: [4, 10],
52 borderRadius: theme.borderRadius,
53 background: theme.inputBackground,
54 color: theme.inputColor,
55 },
56 inputButton: {
57 color: theme.colorText,
58 },
59});
60
61@injectSheet(styles) @observer
62class WebControls extends Component {
63 static propTypes = {
64 classes: PropTypes.object.isRequired,
65 goHome: PropTypes.func.isRequired,
66 canGoBack: PropTypes.bool.isRequired,
67 goBack: PropTypes.func.isRequired,
68 canGoForward: PropTypes.bool.isRequired,
69 goForward: PropTypes.func.isRequired,
70 reload: PropTypes.func.isRequired,
71 url: PropTypes.string.isRequired,
72 navigate: PropTypes.func.isRequired,
73 }
74
75 static getDerivedStateFromProps(props, state) {
76 const { url } = props;
77 const { editUrl } = state;
78
79 if (!editUrl) {
80 return {
81 inputUrl: url,
82 editUrl: state.editUrl,
83 };
84 }
85 }
86
87 inputRef = React.createRef();
88
89 state = {
90 inputUrl: '',
91 editUrl: false,
92 }
93
94 render() {
95 const {
96 classes,
97 goHome,
98 canGoBack,
99 goBack,
100 canGoForward,
101 goForward,
102 reload,
103 url,
104 navigate,
105 } = this.props;
106
107 const {
108 inputUrl,
109 editUrl,
110 } = this.state;
111
112 return (
113 <div className={classes.root}>
114 <button
115 onClick={goHome}
116 type="button"
117 className={classes.button}
118 >
119 <Icon
120 icon={mdiHomeOutline}
121 className={classes.icon}
122 />
123 </button>
124 <button
125 onClick={goBack}
126 type="button"
127 className={classes.button}
128 disabled={!canGoBack}
129 >
130 <Icon
131 icon={mdiArrowLeft}
132 className={classes.icon}
133 />
134 </button>
135 <button
136 onClick={goForward}
137 type="button"
138 className={classes.button}
139 disabled={!canGoForward}
140 >
141 <Icon
142 icon={mdiArrowRight}
143 className={classes.icon}
144 />
145 </button>
146 <button
147 onClick={reload}
148 type="button"
149 className={classes.button}
150 >
151 <Icon
152 icon={mdiReload}
153 className={classes.icon}
154 />
155 </button>
156 <input
157 value={editUrl ? inputUrl : url}
158 className={classes.input}
159 onChange={event => this.setState({
160 inputUrl: event.target.value,
161 })}
162 onFocus={(event) => {
163 event.target.select();
164 this.setState({
165 editUrl: true,
166 });
167 }}
168 onKeyDown={(event) => {
169 if (event.key === 'Enter') {
170 this.setState({
171 editUrl: false,
172 });
173 navigate(inputUrl);
174 this.inputRef.current.blur();
175 } else if (event.key === 'Escape') {
176 this.setState({
177 editUrl: false,
178 inputUrl: url,
179 });
180 event.target.blur();
181 }
182 }}
183 ref={this.inputRef}
184 />
185 </div>
186 );
187 }
188}
189
190export default WebControls;
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js
new file mode 100644
index 000000000..1452d5a3d
--- /dev/null
+++ b/src/features/webControls/containers/WebControlsScreen.js
@@ -0,0 +1,128 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import { autorun, observable } from 'mobx';
6import WebControls from '../components/WebControls';
7import ServicesStore from '../../../stores/ServicesStore';
8import Service from '../../../models/Service';
9
10const URL_EVENTS = [
11 'load-commit',
12 // 'dom-ready',
13 'will-navigate',
14 'did-navigate',
15 'did-navigate-in-page',
16];
17
18@inject('stores', 'actions') @observer
19class WebControlsScreen extends Component {
20 @observable url = '';
21
22 @observable canGoBack = false;
23
24 @observable canGoForward = false;
25
26 webview = null;
27
28 autorunDisposer = null;
29
30 componentDidMount() {
31 const { service } = this.props;
32
33 this.autorunDisposer = autorun(() => {
34 if (service.isAttached) {
35 this.webview = service.webview;
36
37 URL_EVENTS.forEach((event) => {
38 this.webview.addEventListener(event, (e) => {
39 if (!e.isMainFrame) return;
40
41 this.url = e.url;
42 this.canGoBack = this.webview.canGoBack();
43 this.canGoForward = this.webview.canGoForward();
44 });
45 });
46 }
47 });
48 }
49
50 componentWillUnmount() {
51 this.autorunDisposer();
52 }
53
54 goHome() {
55 const { reloadActive } = this.props.actions.service;
56
57 if (!this.webview) return;
58
59 reloadActive();
60 }
61
62 reload() {
63 if (!this.webview) return;
64
65 this.webview.reload();
66 }
67
68 goBack() {
69 if (!this.webview) return;
70
71 this.webview.goBack();
72 }
73
74 goForward() {
75 if (!this.webview) return;
76
77 this.webview.goForward();
78 }
79
80 navigate(newUrl) {
81 if (!this.webview) return;
82
83 let url = newUrl;
84
85 try {
86 url = new URL(url).toString();
87 } catch (err) {
88 // eslint-disable-next-line no-useless-escape
89 if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) {
90 url = `http://${url}`;
91 } else {
92 url = `https://www.google.com/search?query=${url}`;
93 }
94 }
95
96 this.webview.loadURL(url);
97 this.url = url;
98 }
99
100 render() {
101 return (
102 <WebControls
103 goHome={() => this.goHome()}
104 reload={() => this.reload()}
105 canGoBack={this.canGoBack}
106 goBack={() => this.goBack()}
107 canGoForward={this.canGoForward}
108 goForward={() => this.goForward()}
109 navigate={url => this.navigate(url)}
110 url={this.url}
111 />
112 );
113 }
114}
115
116export default WebControlsScreen;
117
118WebControlsScreen.wrappedComponent.propTypes = {
119 service: PropTypes.instanceOf(Service).isRequired,
120 stores: PropTypes.shape({
121 services: PropTypes.instanceOf(ServicesStore).isRequired,
122 }).isRequired,
123 actions: PropTypes.shape({
124 service: PropTypes.shape({
125 reloadActive: PropTypes.func.isRequired,
126 }).isRequired,
127 }).isRequired,
128};
diff --git a/src/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
23import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; 23import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js';
24import { isValidExternalURL } from '../helpers/url-helpers'; 24import { isValidExternalURL } from '../helpers/url-helpers';
25import { sleep } from '../helpers/async-helpers';
25 26
26const debug = require('debug')('Ferdi:AppStore'); 27const debug = require('debug')('Ferdi:AppStore');
27 28
@@ -317,6 +318,8 @@ export default class AppStore extends Store {
317 318
318 await clearAppCache._promise; 319 await clearAppCache._promise;
319 320
321 await sleep(ms('1s'));
322
320 this.getAppCacheSizeRequest.execute(); 323 this.getAppCacheSizeRequest.execute();
321 324
322 this.isClearingAllCache = false; 325 this.isClearingAllCache = false;
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index ec5833848..acd62d675 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -255,9 +255,9 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
255 }, 255 },
256 { 256 {
257 id: 'resetToDefault', 257 id: 'resetToDefault',
258 label: `Reset to system default (${SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, 258 label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`,
259 type: 'radio', 259 type: 'radio',
260 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, 260 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'),
261 click() { 261 click() {
262 debug('Resetting service spellchecker to system default'); 262 debug('Resetting service spellchecker to system default');
263 ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); 263 ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset');
@@ -297,12 +297,13 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
297}; 297};
298 298
299export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { 299export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
300 webContents.on('context-menu', (e, props) => { 300 webContents.on('context-menu', async (e, props) => {
301 e.preventDefault(); 301 e.preventDefault();
302 302
303 let suggestions = []; 303 let suggestions = [];
304 if (spellcheckProvider && props.misspelledWord) { 304 if (spellcheckProvider && props.misspelledWord) {
305 suggestions = spellcheckProvider.getSuggestion(props.misspelledWord); 305 debug('Mispelled word', props.misspelledWord);
306 suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);
306 307
307 debug('Suggestions', suggestions); 308 debug('Suggestions', suggestions);
308 } 309 }
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
index 1b2d60faf..27380676d 100644
--- a/src/webview/spellchecker.js
+++ b/src/webview/spellchecker.js
@@ -1,6 +1,7 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import { SpellCheckerProvider } from 'electron-hunspell'; 2import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell';
3import path from 'path'; 3import path from 'path';
4import { readFileSync } from 'fs';
4 5
5import { DICTIONARY_PATH } from '../config'; 6import { DICTIONARY_PATH } from '../config';
6import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 7import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -10,18 +11,21 @@ const debug = require('debug')('Ferdi:spellchecker');
10let provider; 11let provider;
11let currentDict; 12let currentDict;
12let _isEnabled = false; 13let _isEnabled = false;
14let attached;
15
16const DEFAULT_LOCALE = 'en-us';
13 17
14async function loadDictionary(locale) { 18async function loadDictionary(locale) {
15 try { 19 try {
16 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); 20 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`);
17 await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`);
18 debug('Loaded dictionary', locale, 'from', fileLocation); 21 debug('Loaded dictionary', locale, 'from', fileLocation);
22 return provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`));
19 } catch (err) { 23 } catch (err) {
20 console.error('Could not load dictionary', err); 24 console.error('Could not load dictionary', err);
21 } 25 }
22} 26}
23 27
24export async function switchDict(locale) { 28export async function switchDict(locale = DEFAULT_LOCALE) {
25 try { 29 try {
26 debug('Trying to load dictionary', locale); 30 debug('Trying to load dictionary', locale);
27 31
@@ -40,8 +44,8 @@ export async function switchDict(locale) {
40 if (currentDict) { 44 if (currentDict) {
41 provider.unloadDictionary(locale); 45 provider.unloadDictionary(locale);
42 } 46 }
43 loadDictionary(locale); 47 await loadDictionary(locale);
44 provider.switchDictionary(locale); 48 await attached.switchLanguage(locale);
45 49
46 debug('Switched dictionary to', locale); 50 debug('Switched dictionary to', locale);
47 51
@@ -52,18 +56,32 @@ export async function switchDict(locale) {
52 } 56 }
53} 57}
54 58
55export default async function initialize(languageCode = 'en-us') { 59export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) {
60 const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase());
61
62 if (locales.length >= 1) {
63 return locales[0];
64 }
65
66 return null;
67}
68
69export default async function initialize(languageCode = DEFAULT_LOCALE) {
56 try { 70 try {
57 provider = new SpellCheckerProvider(); 71 provider = new SpellCheckerProvider();
58 const locale = languageCode.toLowerCase(); 72 const locale = getSpellcheckerLocaleByFuzzyIdentifier(languageCode);
59 73
60 debug('Init spellchecker'); 74 debug('Init spellchecker');
61 await provider.initialize(); 75 await provider.initialize();
62 // await loadDictionaries();
63 76
64 debug('Available spellchecker dictionaries', provider.availableDictionaries); 77 debug('Attaching spellcheck provider');
78 attached = await attachSpellCheckProvider(provider);
79
80 const availableDictionaries = await provider.getAvailableDictionaries();
65 81
66 switchDict(locale); 82 debug('Available spellchecker dictionaries', availableDictionaries);
83
84 await switchDict(locale);
67 85
68 return provider; 86 return provider;
69 } catch (err) { 87 } catch (err) {
@@ -83,13 +101,3 @@ export function disable() {
83 currentDict = null; 101 currentDict = null;
84 } 102 }
85} 103}
86
87export function getSpellcheckerLocaleByFuzzyIdentifier(identifier) {
88 const locales = Object.keys(SPELLCHECKER_LOCALES).filter(key => key === identifier.toLowerCase() || key.split('-')[0] === identifier.toLowerCase());
89
90 if (locales.length >= 1) {
91 return locales[0];
92 }
93
94 return null;
95}