aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gulpfile.babel.js2
-rw-r--r--package.json6
-rw-r--r--src/actions/app.js1
-rw-r--r--src/actions/service.js3
-rw-r--r--src/api/LocalApi.js8
-rw-r--r--src/api/ServicesApi.js7
-rw-r--r--src/api/server/LocalApi.js34
-rw-r--r--src/api/server/ServerApi.js6
-rw-r--r--src/app.js2
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js20
-rw-r--r--src/components/settings/services/EditServiceForm.js2
-rw-r--r--src/components/settings/services/ServicesDashboard.js42
-rw-r--r--src/components/settings/settings/EditSettingsForm.js38
-rw-r--r--src/components/ui/SearchInput.js48
-rw-r--r--src/containers/settings/EditSettingsScreen.js17
-rw-r--r--src/containers/settings/ServicesScreen.js1
-rw-r--r--src/electron/ipc-api/autoUpdate.js3
-rw-r--r--src/helpers/service-helpers.js20
-rw-r--r--src/i18n/locales/en-US.json4
-rw-r--r--src/index.html18
-rw-r--r--src/index.js1
-rw-r--r--src/stores/AppStore.js60
-rw-r--r--src/stores/ServicesStore.js18
-rw-r--r--src/styles/button.scss12
-rw-r--r--src/styles/searchInput.scss16
-rw-r--r--src/styles/settings.scss22
-rw-r--r--src/webview/notifications.js4
-rw-r--r--src/webview/plugin.js16
-rw-r--r--yarn.lock133
29 files changed, 471 insertions, 93 deletions
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index b50001b2d..95b026f66 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -112,8 +112,6 @@ export function watch() {
112export function webserver() { 112export function webserver() {
113 gulp.src([ 113 gulp.src([
114 paths.dest, 114 paths.dest,
115 `!${paths.dest}/electron/**`,
116 `!${paths.dest}/webview/**`,
117 ]) 115 ])
118 .pipe(server({ 116 .pipe(server({
119 livereload: true, 117 livereload: true,
diff --git a/package.json b/package.json
index 5dae5bc7c..5f5dd4182 100644
--- a/package.json
+++ b/package.json
@@ -34,8 +34,9 @@
34 "babel-polyfill": "^6.23.0", 34 "babel-polyfill": "^6.23.0",
35 "babel-runtime": "^6.23.0", 35 "babel-runtime": "^6.23.0",
36 "classnames": "^2.2.5", 36 "classnames": "^2.2.5",
37 "du": "^0.1.0",
37 "electron-fetch": "^1.1.0", 38 "electron-fetch": "^1.1.0",
38 "electron-spellchecker": "^1.2.0", 39 "electron-spellchecker": "^1.1.2",
39 "electron-updater": "^2.4.3", 40 "electron-updater": "^2.4.3",
40 "electron-window-state": "^4.1.0", 41 "electron-window-state": "^4.1.0",
41 "fs-extra": "^3.0.1", 42 "fs-extra": "^3.0.1",
@@ -54,6 +55,7 @@
54 "mobx-react-router": "^3.1.2", 55 "mobx-react-router": "^3.1.2",
55 "moment": "^2.17.1", 56 "moment": "^2.17.1",
56 "normalize-url": "^1.9.1", 57 "normalize-url": "^1.9.1",
58 "pretty-bytes": "^4.0.2",
57 "prop-types": "^15.5.10", 59 "prop-types": "^15.5.10",
58 "prop-types-extended": "^0.2.1", 60 "prop-types-extended": "^0.2.1",
59 "react": "^15.4.1", 61 "react": "^15.4.1",
@@ -105,7 +107,7 @@
105 "gulp-sass": "^3.1.0", 107 "gulp-sass": "^3.1.0",
106 "gulp-sass-variables": "^1.1.1", 108 "gulp-sass-variables": "^1.1.1",
107 "gulp-server-livereload": "^1.9.2", 109 "gulp-server-livereload": "^1.9.2",
108 "node-sass": "^4.5.3" 110 "node-sass": "^4.7.2"
109 }, 111 },
110 "config": { 112 "config": {
111 "commitizen": { 113 "commitizen": {
diff --git a/src/actions/app.js b/src/actions/app.js
index e4f648fc9..e6f7f22ba 100644
--- a/src/actions/app.js
+++ b/src/actions/app.js
@@ -25,4 +25,5 @@ export default {
25 overrideSystemMute: PropTypes.bool, 25 overrideSystemMute: PropTypes.bool,
26 }, 26 },
27 toggleMuteApp: {}, 27 toggleMuteApp: {},
28 clearAllCache: {},
28}; 29};
diff --git a/src/actions/service.js b/src/actions/service.js
index e3100e986..5d483b12a 100644
--- a/src/actions/service.js
+++ b/src/actions/service.js
@@ -25,6 +25,9 @@ export default {
25 serviceId: PropTypes.string.isRequired, 25 serviceId: PropTypes.string.isRequired,
26 redirect: PropTypes.string, 26 redirect: PropTypes.string,
27 }, 27 },
28 clearCache: {
29 serviceId: PropTypes.string.isRequired,
30 },
28 setUnreadMessageCount: { 31 setUnreadMessageCount: {
29 serviceId: PropTypes.string.isRequired, 32 serviceId: PropTypes.string.isRequired,
30 count: PropTypes.object.isRequired, 33 count: PropTypes.object.isRequired,
diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js
index 6f2b049d6..3f84f8a0b 100644
--- a/src/api/LocalApi.js
+++ b/src/api/LocalApi.js
@@ -15,4 +15,12 @@ export default class LocalApi {
15 removeKey(key) { 15 removeKey(key) {
16 return this.local.removeKey(key); 16 return this.local.removeKey(key);
17 } 17 }
18
19 getAppCacheSize() {
20 return this.local.getAppCacheSize();
21 }
22
23 clearAppCache() {
24 return this.local.clearAppCache();
25 }
18} 26}
diff --git a/src/api/ServicesApi.js b/src/api/ServicesApi.js
index 3cb40ba0d..36ed9482f 100644
--- a/src/api/ServicesApi.js
+++ b/src/api/ServicesApi.js
@@ -1,5 +1,6 @@
1export default class ServicesApi { 1export default class ServicesApi {
2 constructor(server) { 2 constructor(server, local) {
3 this.local = local;
3 this.server = server; 4 this.server = server;
4 } 5 }
5 6
@@ -30,4 +31,8 @@ export default class ServicesApi {
30 reorder(data) { 31 reorder(data) {
31 return this.server.reorderService(data); 32 return this.server.reorderService(data);
32 } 33 }
34
35 clearCache(serviceId) {
36 return this.local.clearCache(serviceId);
37 }
33} 38}
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js
index 79ac6e12f..e95d750ac 100644
--- a/src/api/server/LocalApi.js
+++ b/src/api/server/LocalApi.js
@@ -1,3 +1,10 @@
1import { remote } from 'electron';
2import du from 'du';
3
4import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js';
5
6const { session } = remote;
7
1export default class LocalApi { 8export default class LocalApi {
2 // App 9 // App
3 async updateAppSettings(data) { 10 async updateAppSettings(data) {
@@ -30,4 +37,31 @@ export default class LocalApi {
30 localStorage.setItem('app', JSON.stringify(settings)); 37 localStorage.setItem('app', JSON.stringify(settings));
31 } 38 }
32 } 39 }
40
41 // Services
42 async getAppCacheSize() {
43 const partitionsDir = getServicePartitionsDirectory();
44 return new Promise((resolve, reject) => {
45 du(partitionsDir, (err, size) => {
46 if (err) reject(err);
47
48 console.debug('LocalApi::getAppCacheSize resolves', size);
49 resolve(size);
50 });
51 });
52 }
53
54 async clearCache(serviceId) {
55 const s = session.fromPartition(`persist:service-${serviceId}`);
56
57 console.debug('LocalApi::clearCache resolves', serviceId);
58 return new Promise(resolve => s.clearCache(resolve));
59 }
60
61 async clearAppCache() {
62 const s = session.defaultSession;
63
64 console.debug('LocalApi::clearCache clearAppCache');
65 return new Promise(resolve => s.clearCache(resolve));
66 }
33} 67}
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index 6b96f709e..a684ff98b 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -22,6 +22,10 @@ import {
22 loadRecipeConfig, 22 loadRecipeConfig,
23} from '../../helpers/recipe-helpers'; 23} from '../../helpers/recipe-helpers';
24 24
25import {
26 removeServicePartitionDirectory,
27} from '../../helpers/service-helpers.js';
28
25module.paths.unshift( 29module.paths.unshift(
26 getDevRecipeDirectory(), 30 getDevRecipeDirectory(),
27 getRecipeDirectory(), 31 getRecipeDirectory(),
@@ -248,6 +252,8 @@ export default class ServerApi {
248 } 252 }
249 const data = await request.json(); 253 const data = await request.json();
250 254
255 removeServicePartitionDirectory(id, true);
256
251 console.debug('ServerApi::deleteService resolves', data); 257 console.debug('ServerApi::deleteService resolves', data);
252 return data; 258 return data;
253 } 259 }
diff --git a/src/app.js b/src/app.js
index a0b88611c..8e62776d2 100644
--- a/src/app.js
+++ b/src/app.js
@@ -105,3 +105,5 @@ window.addEventListener('load', () => {
105// Prevent drag and drop into window from redirecting 105// Prevent drag and drop into window from redirecting
106window.addEventListener('dragover', event => event.preventDefault()); 106window.addEventListener('dragover', event => event.preventDefault());
107window.addEventListener('drop', event => event.preventDefault()); 107window.addEventListener('drop', event => event.preventDefault());
108window.addEventListener('dragover', event => event.stopPropagation());
109window.addEventListener('drop', event => event.stopPropagation());
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index b6ade5da4..4610c69a5 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -16,6 +16,10 @@ const messages = defineMessages({
16 id: 'settings.recipes.headline', 16 id: 'settings.recipes.headline',
17 defaultMessage: '!!!Available Services', 17 defaultMessage: '!!!Available Services',
18 }, 18 },
19 searchService: {
20 id: 'settings.searchService',
21 defaultMessage: '!!!Search service',
22 },
19 mostPopularRecipes: { 23 mostPopularRecipes: {
20 id: 'settings.recipes.mostPopular', 24 id: 'settings.recipes.mostPopular',
21 defaultMessage: '!!!Most popular', 25 defaultMessage: '!!!Most popular',
@@ -81,13 +85,7 @@ export default class RecipesDashboard extends Component {
81 return ( 85 return (
82 <div className="settings__main"> 86 <div className="settings__main">
83 <div className="settings__header"> 87 <div className="settings__header">
84 <SearchInput 88 <h1>{intl.formatMessage(messages.headline)}</h1>
85 className="settings__search-header"
86 defaultValue={intl.formatMessage(messages.headline)}
87 onChange={e => searchRecipes(e)}
88 onReset={() => resetSearch()}
89 throttle
90 />
91 </div> 89 </div>
92 <div className="settings__body recipes"> 90 <div className="settings__body recipes">
93 {serviceStatus.length > 0 && serviceStatus.includes('created') && ( 91 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
@@ -101,7 +99,13 @@ export default class RecipesDashboard extends Component {
101 </Infobox> 99 </Infobox>
102 </Appear> 100 </Appear>
103 )} 101 )}
104 {/* {!searchNeedle && ( */} 102 <SearchInput
103 placeholder={intl.formatMessage(messages.searchService)}
104 onChange={e => searchRecipes(e)}
105 onReset={() => resetSearch()}
106 autoFocus
107 throttle
108 />
105 <div className="recipes__navigation"> 109 <div className="recipes__navigation">
106 <Link 110 <Link
107 to="/settings/recipes" 111 to="/settings/recipes"
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index c0a993736..f6f2df2f3 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -78,7 +78,7 @@ const messages = defineMessages({
78 }, 78 },
79 headlineBadges: { 79 headlineBadges: {
80 id: 'settings.service.form.headlineBadges', 80 id: 'settings.service.form.headlineBadges',
81 defaultMessage: '!!!Unread message dadges', 81 defaultMessage: '!!!Unread message badges',
82 }, 82 },
83 headlineGeneral: { 83 headlineGeneral: {
84 id: 'settings.service.form.headlineGeneral', 84 id: 'settings.service.form.headlineGeneral',
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index 5f146b5f3..20e451f01 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -15,10 +15,18 @@ const messages = defineMessages({
15 id: 'settings.services.headline', 15 id: 'settings.services.headline',
16 defaultMessage: '!!!Your services', 16 defaultMessage: '!!!Your services',
17 }, 17 },
18 searchService: {
19 id: 'settings.searchService',
20 defaultMessage: '!!!Search service',
21 },
18 noServicesAdded: { 22 noServicesAdded: {
19 id: 'settings.services.noServicesAdded', 23 id: 'settings.services.noServicesAdded',
20 defaultMessage: '!!!You haven\'t added any services yet.', 24 defaultMessage: '!!!You haven\'t added any services yet.',
21 }, 25 },
26 noServiceFound: {
27 id: 'settings.recipes.nothingFound',
28 defaultMessage: '!!!Sorry, but no service matched your search term.',
29 },
22 discoverServices: { 30 discoverServices: {
23 id: 'settings.services.discoverServices', 31 id: 'settings.services.discoverServices',
24 defaultMessage: '!!!Discover services', 32 defaultMessage: '!!!Discover services',
@@ -53,7 +61,13 @@ export default class ServicesDashboard extends Component {
53 servicesRequestFailed: PropTypes.bool.isRequired, 61 servicesRequestFailed: PropTypes.bool.isRequired,
54 retryServicesRequest: PropTypes.func.isRequired, 62 retryServicesRequest: PropTypes.func.isRequired,
55 status: MobxPropTypes.arrayOrObservableArray.isRequired, 63 status: MobxPropTypes.arrayOrObservableArray.isRequired,
64 searchNeedle: PropTypes.string,
56 }; 65 };
66
67 static defaultProps = {
68 searchNeedle: '',
69 }
70
57 static contextTypes = { 71 static contextTypes = {
58 intl: intlShape, 72 intl: intlShape,
59 }; 73 };
@@ -69,20 +83,24 @@ export default class ServicesDashboard extends Component {
69 servicesRequestFailed, 83 servicesRequestFailed,
70 retryServicesRequest, 84 retryServicesRequest,
71 status, 85 status,
86 searchNeedle,
72 } = this.props; 87 } = this.props;
73 const { intl } = this.context; 88 const { intl } = this.context;
74 89
75 return ( 90 return (
76 <div className="settings__main"> 91 <div className="settings__main">
77 <div className="settings__header"> 92 <div className="settings__header">
78 <SearchInput 93 <h1>{intl.formatMessage(messages.headline)}</h1>
79 className="settings__search-header"
80 defaultValue={intl.formatMessage(messages.headline)}
81 onChange={needle => filterServices({ needle })}
82 onReset={() => resetFilter()}
83 />
84 </div> 94 </div>
85 <div className="settings__body"> 95 <div className="settings__body">
96 {!isLoading && (
97 <SearchInput
98 placeholder={intl.formatMessage(messages.searchService)}
99 onChange={needle => filterServices({ needle })}
100 onReset={() => resetFilter()}
101 autoFocus
102 />
103 )}
86 {!isLoading && servicesRequestFailed && ( 104 {!isLoading && servicesRequestFailed && (
87 <div> 105 <div>
88 <Infobox 106 <Infobox
@@ -121,7 +139,7 @@ export default class ServicesDashboard extends Component {
121 </Appear> 139 </Appear>
122 )} 140 )}
123 141
124 {!isLoading && services.length === 0 && ( 142 {!isLoading && services.length === 0 && !searchNeedle && (
125 <div className="align-middle settings__empty-state"> 143 <div className="align-middle settings__empty-state">
126 <p className="settings__empty-text"> 144 <p className="settings__empty-text">
127 <span className="emoji"> 145 <span className="emoji">
@@ -132,6 +150,16 @@ export default class ServicesDashboard extends Component {
132 <Link to="/settings/recipes" className="button">{intl.formatMessage(messages.discoverServices)}</Link> 150 <Link to="/settings/recipes" className="button">{intl.formatMessage(messages.discoverServices)}</Link>
133 </div> 151 </div>
134 )} 152 )}
153 {!isLoading && services.length === 0 && searchNeedle && (
154 <div className="align-middle settings__empty-state">
155 <p className="settings__empty-text">
156 <span className="emoji">
157 <img src="./assets/images/emoji/dontknow.png" alt="" />
158 </span>
159 {intl.formatMessage(messages.noServiceFound)}
160 </p>
161 </div>
162 )}
135 {isLoading ? ( 163 {isLoading ? (
136 <Loader /> 164 <Loader />
137 ) : ( 165 ) : (
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index ff398aa33..72aa5a8af 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -40,6 +40,18 @@ const messages = defineMessages({
40 id: 'settings.app.translationHelp', 40 id: 'settings.app.translationHelp',
41 defaultMessage: '!!!Help us to translate Franz into your language.', 41 defaultMessage: '!!!Help us to translate Franz into your language.',
42 }, 42 },
43 subheadlineCache: {
44 id: 'settings.app.subheadlineCache',
45 defaultMessage: '!!!Cache',
46 },
47 cacheInfo: {
48 id: 'settings.app.cacheInfo',
49 defaultMessage: '!!!Franz cache is currently using {size} of disk space.',
50 },
51 buttonClearAllCache: {
52 id: 'settings.app.buttonClearAllCache',
53 defaultMessage: '!!!Clear cache',
54 },
43 buttonSearchForUpdate: { 55 buttonSearchForUpdate: {
44 id: 'settings.app.buttonSearchForUpdate', 56 id: 'settings.app.buttonSearchForUpdate',
45 defaultMessage: '!!!Check for updates', 57 defaultMessage: '!!!Check for updates',
@@ -77,6 +89,9 @@ export default class EditSettingsForm extends Component {
77 isUpdateAvailable: PropTypes.bool.isRequired, 89 isUpdateAvailable: PropTypes.bool.isRequired,
78 noUpdateAvailable: PropTypes.bool.isRequired, 90 noUpdateAvailable: PropTypes.bool.isRequired,
79 updateIsReadyToInstall: PropTypes.bool.isRequired, 91 updateIsReadyToInstall: PropTypes.bool.isRequired,
92 isClearingAllCache: PropTypes.bool.isRequired,
93 onClearAllCache: PropTypes.func.isRequired,
94 cacheSize: PropTypes.string.isRequired,
80 }; 95 };
81 96
82 static contextTypes = { 97 static contextTypes = {
@@ -103,6 +118,9 @@ export default class EditSettingsForm extends Component {
103 isUpdateAvailable, 118 isUpdateAvailable,
104 noUpdateAvailable, 119 noUpdateAvailable,
105 updateIsReadyToInstall, 120 updateIsReadyToInstall,
121 isClearingAllCache,
122 onClearAllCache,
123 cacheSize,
106 } = this.props; 124 } = this.props;
107 const { intl } = this.context; 125 const { intl } = this.context;
108 126
@@ -155,6 +173,25 @@ export default class EditSettingsForm extends Component {
155 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2> 173 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2>
156 <Toggle field={form.$('enableSpellchecking')} /> 174 <Toggle field={form.$('enableSpellchecking')} />
157 {/* <Select field={form.$('spellcheckingLanguage')} /> */} 175 {/* <Select field={form.$('spellcheckingLanguage')} /> */}
176 <div className="settings__settings-group">
177 <h3>
178 {intl.formatMessage(messages.subheadlineCache)}
179 </h3>
180 <p>
181 {intl.formatMessage(messages.cacheInfo, {
182 size: cacheSize,
183 })}
184 </p>
185 <p>
186 <Button
187 buttonType="secondary"
188 label={intl.formatMessage(messages.buttonClearAllCache)}
189 onClick={onClearAllCache}
190 disabled={isClearingAllCache}
191 loaded={!isClearingAllCache}
192 />
193 </p>
194 </div>
158 195
159 {/* Updates */} 196 {/* Updates */}
160 <h2 id="updates">{intl.formatMessage(messages.headlineUpdates)}</h2> 197 <h2 id="updates">{intl.formatMessage(messages.headlineUpdates)}</h2>
@@ -165,6 +202,7 @@ export default class EditSettingsForm extends Component {
165 /> 202 />
166 ) : ( 203 ) : (
167 <Button 204 <Button
205 buttonType="secondary"
168 label={intl.formatMessage(updateButtonLabelMessage)} 206 label={intl.formatMessage(updateButtonLabelMessage)}
169 onClick={checkForUpdates} 207 onClick={checkForUpdates}
170 disabled={isCheckingForUpdates || isUpdateAvailable} 208 disabled={isCheckingForUpdates || isUpdateAvailable}
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js
index bca412cef..a94cde201 100644
--- a/src/components/ui/SearchInput.js
+++ b/src/components/ui/SearchInput.js
@@ -9,36 +9,46 @@ import { debounce } from 'lodash';
9export default class SearchInput extends Component { 9export default class SearchInput extends Component {
10 static propTypes = { 10 static propTypes = {
11 value: PropTypes.string, 11 value: PropTypes.string,
12 defaultValue: PropTypes.string, 12 placeholder: PropTypes.string,
13 className: PropTypes.string, 13 className: PropTypes.string,
14 onChange: PropTypes.func, 14 onChange: PropTypes.func,
15 onReset: PropTypes.func, 15 onReset: PropTypes.func,
16 name: PropTypes.string, 16 name: PropTypes.string,
17 throttle: PropTypes.bool, 17 throttle: PropTypes.bool,
18 throttleDelay: PropTypes.number, 18 throttleDelay: PropTypes.number,
19 autoFocus: PropTypes.bool,
19 }; 20 };
20 21
21 static defaultProps = { 22 static defaultProps = {
22 value: '', 23 value: '',
23 defaultValue: '', 24 placeholder: '',
24 className: '', 25 className: '',
25 name: uuidv1(), 26 name: uuidv1(),
26 throttle: false, 27 throttle: false,
27 throttleDelay: 250, 28 throttleDelay: 250,
28 onChange: () => null, 29 onChange: () => null,
29 onReset: () => null, 30 onReset: () => null,
31 autoFocus: false,
30 } 32 }
31 33
32 constructor(props) { 34 constructor(props) {
33 super(props); 35 super(props);
34 36
35 this.state = { 37 this.state = {
36 value: props.value || props.defaultValue, 38 value: props.value,
37 }; 39 };
38 40
39 this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay); 41 this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay);
40 } 42 }
41 43
44 componentDidMount() {
45 const { autoFocus } = this.props;
46
47 if (autoFocus) {
48 this.input.focus();
49 }
50 }
51
42 onChange(e) { 52 onChange(e) {
43 const { throttle, onChange } = this.props; 53 const { throttle, onChange } = this.props;
44 const { value } = e.target; 54 const { value } = e.target;
@@ -52,26 +62,6 @@ export default class SearchInput extends Component {
52 } 62 }
53 } 63 }
54 64
55 onClick() {
56 const { defaultValue } = this.props;
57 const { value } = this.state;
58
59 if (value === defaultValue) {
60 this.setState({ value: '' });
61 }
62
63 this.input.focus();
64 }
65
66 onBlur() {
67 const { defaultValue } = this.props;
68 const { value } = this.state;
69
70 if (value === '') {
71 this.setState({ value: defaultValue });
72 }
73 }
74
75 throttledOnChange(e) { 65 throttledOnChange(e) {
76 const { onChange } = this.props; 66 const { onChange } = this.props;
77 67
@@ -79,8 +69,8 @@ export default class SearchInput extends Component {
79 } 69 }
80 70
81 reset() { 71 reset() {
82 const { defaultValue, onReset } = this.props; 72 const { onReset } = this.props;
83 this.setState({ value: defaultValue }); 73 this.setState({ value: '' });
84 74
85 onReset(); 75 onReset();
86 } 76 }
@@ -88,7 +78,7 @@ export default class SearchInput extends Component {
88 input = null; 78 input = null;
89 79
90 render() { 80 render() {
91 const { className, name, defaultValue } = this.props; 81 const { className, name, placeholder } = this.props;
92 const { value } = this.state; 82 const { value } = this.state;
93 83
94 return ( 84 return (
@@ -101,18 +91,16 @@ export default class SearchInput extends Component {
101 <label 91 <label
102 htmlFor={name} 92 htmlFor={name}
103 className="mdi mdi-magnify" 93 className="mdi mdi-magnify"
104 onClick={() => this.onClick()}
105 /> 94 />
106 <input 95 <input
107 name={name} 96 name={name}
108 type="text" 97 type="text"
98 placeholder={placeholder}
109 value={value} 99 value={value}
110 onChange={e => this.onChange(e)} 100 onChange={e => this.onChange(e)}
111 onClick={() => this.onClick()}
112 onBlur={() => this.onBlur()}
113 ref={(ref) => { this.input = ref; }} 101 ref={(ref) => { this.input = ref; }}
114 /> 102 />
115 {value !== defaultValue && value.length > 0 && ( 103 {value.length > 0 && (
116 <span 104 <span
117 className="mdi mdi-close-circle-outline" 105 className="mdi mdi-close-circle-outline"
118 onClick={() => this.reset()} 106 onClick={() => this.reset()}
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 45ded9e5c..9fa815a0a 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -193,8 +193,17 @@ export default class EditSettingsScreen extends Component {
193 } 193 }
194 194
195 render() { 195 render() {
196 const { updateStatus, updateStatusTypes } = this.props.stores.app; 196 const {
197 const { checkForUpdates, installUpdate } = this.props.actions.app; 197 updateStatus,
198 cacheSize,
199 updateStatusTypes,
200 isClearingAllCache,
201 } = this.props.stores.app;
202 const {
203 checkForUpdates,
204 installUpdate,
205 clearAllCache,
206 } = this.props.actions.app;
198 const form = this.prepareForm(); 207 const form = this.prepareForm();
199 208
200 return ( 209 return (
@@ -207,6 +216,9 @@ export default class EditSettingsScreen extends Component {
207 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE} 216 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
208 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED} 217 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
209 onSubmit={d => this.onSubmit(d)} 218 onSubmit={d => this.onSubmit(d)}
219 cacheSize={cacheSize}
220 isClearingAllCache={isClearingAllCache}
221 onClearAllCache={clearAllCache}
210 /> 222 />
211 ); 223 );
212 } 224 }
@@ -223,6 +235,7 @@ EditSettingsScreen.wrappedComponent.propTypes = {
223 launchOnStartup: PropTypes.func.isRequired, 235 launchOnStartup: PropTypes.func.isRequired,
224 checkForUpdates: PropTypes.func.isRequired, 236 checkForUpdates: PropTypes.func.isRequired,
225 installUpdate: PropTypes.func.isRequired, 237 installUpdate: PropTypes.func.isRequired,
238 clearAllCache: PropTypes.func.isRequired,
226 }).isRequired, 239 }).isRequired,
227 settings: PropTypes.shape({ 240 settings: PropTypes.shape({
228 update: PropTypes.func.isRequired, 241 update: PropTypes.func.isRequired,
diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js
index 8cfe5efbf..12db1bcd3 100644
--- a/src/containers/settings/ServicesScreen.js
+++ b/src/containers/settings/ServicesScreen.js
@@ -53,6 +53,7 @@ export default class ServicesScreen extends Component {
53 goTo={router.push} 53 goTo={router.push}
54 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError} 54 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
55 retryServicesRequest={() => services.allServicesRequest.reload()} 55 retryServicesRequest={() => services.allServicesRequest.reload()}
56 searchNeedle={services.filterNeedle}
56 /> 57 />
57 ); 58 );
58 } 59 }
diff --git a/src/electron/ipc-api/autoUpdate.js b/src/electron/ipc-api/autoUpdate.js
index 7bc193e2d..ba49a2f97 100644
--- a/src/electron/ipc-api/autoUpdate.js
+++ b/src/electron/ipc-api/autoUpdate.js
@@ -1,8 +1,9 @@
1import { app, ipcMain } from 'electron'; 1import { app, ipcMain } from 'electron';
2import { autoUpdater } from 'electron-updater'; 2import { autoUpdater } from 'electron-updater';
3import { isDevMode } from '../../environment.js';
3 4
4export default (params) => { 5export default (params) => {
5 if (process.platform === 'darwin' || process.platform === 'win32') { 6 if (!isDevMode && (process.platform === 'darwin' || process.platform === 'win32')) {
6 // autoUpdater.setFeedURL(updateUrl); 7 // autoUpdater.setFeedURL(updateUrl);
7 ipcMain.on('autoUpdate', (event, args) => { 8 ipcMain.on('autoUpdate', (event, args) => {
8 try { 9 try {
diff --git a/src/helpers/service-helpers.js b/src/helpers/service-helpers.js
new file mode 100644
index 000000000..5f63f6b7c
--- /dev/null
+++ b/src/helpers/service-helpers.js
@@ -0,0 +1,20 @@
1import path from 'path';
2import { remote } from 'electron';
3import fs from 'fs-extra';
4
5const app = remote.app;
6
7export function getServicePartitionsDirectory() {
8 return path.join(app.getPath('userData'), 'Partitions');
9}
10
11export function removeServicePartitionDirectory(id = '', addServicePrefix = false) {
12 const servicePartition = path.join(getServicePartitionsDirectory(), `${addServicePrefix ? 'service-' : ''}${id}`);
13
14 return fs.remove(servicePartition);
15}
16
17export async function getServiceIdsFromPartitions() {
18 const files = await fs.readdir(getServicePartitionsDirectory());
19 return files.filter(n => n !== '__chrome_extension');
20}
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index f3db20aea..7fc9eac1c 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -66,6 +66,7 @@
66 "sidebar.unmuteApp": "Enable notifications & audio", 66 "sidebar.unmuteApp": "Enable notifications & audio",
67 "services.welcome": "Welcome to Franz", 67 "services.welcome": "Welcome to Franz",
68 "services.getStarted": "Get started", 68 "services.getStarted": "Get started",
69 "settings.searchService": "Search service",
69 "settings.account.headline": "Account", 70 "settings.account.headline": "Account",
70 "settings.account.headlineSubscription": "Your subscription", 71 "settings.account.headlineSubscription": "Your subscription",
71 "settings.account.headlineUpgrade": "Upgrade your account & support Franz", 72 "settings.account.headlineUpgrade": "Upgrade your account & support Franz",
@@ -152,6 +153,9 @@
152 "settings.app.updateStatusSearching": "Is searching for update", 153 "settings.app.updateStatusSearching": "Is searching for update",
153 "settings.app.updateStatusAvailable": "Update available, downloading...", 154 "settings.app.updateStatusAvailable": "Update available, downloading...",
154 "settings.app.updateStatusUpToDate": "You are using the latest version of Franz", 155 "settings.app.updateStatusUpToDate": "You are using the latest version of Franz",
156 "settings.app.subheadlineCache": "Cache",
157 "settings.app.cacheInfo": "Franz cache is currently using {size} of disk space.",
158 "settings.app.buttonClearAllCache": "Clear cache",
155 "settings.app.form.autoLaunchOnStart": "Launch Franz on start", 159 "settings.app.form.autoLaunchOnStart": "Launch Franz on start",
156 "settings.app.form.autoLaunchInBackground": "Open in background", 160 "settings.app.form.autoLaunchInBackground": "Open in background",
157 "settings.app.form.enableSystemTray": "Show Franz in system tray", 161 "settings.app.form.enableSystemTray": "Show Franz in system tray",
diff --git a/src/index.html b/src/index.html
index 05a93e37b..9e5acd705 100644
--- a/src/index.html
+++ b/src/index.html
@@ -23,6 +23,24 @@
23 s.async = true; 23 s.async = true;
24 s.setAttribute('src', lrHost + '/livereload.js'); 24 s.setAttribute('src', lrHost + '/livereload.js');
25 document.body.appendChild(s); 25 document.body.appendChild(s);
26
27 s.onload = () => {
28 console.log('livereload loaded');
29 const originalReloadBehaviour = window._onLiveReloadFileChanged;
30
31 window._onLiveReloadFileChanged = (file) => {
32 if (!file.path.includes('/build/webview/') && !file.path.includes('/build/index.js') && !file.path.includes('/build/electron/')) {
33 originalReloadBehaviour(file);
34 } else {
35 if (file.path.includes('/build/webview/')) {
36 console.log('Livereload: Reloading all webvies');
37 const webviews = document.querySelectorAll('webview').forEach(webview => webview.reload());
38 } else {
39 console.log('Livereload: skip reload as only main process files have changed');
40 }
41 }
42 }
43 }
26 })(); 44 })();
27 } 45 }
28 </script> 46 </script>
diff --git a/src/index.js b/src/index.js
index a047e2bc1..f82bb3590 100644
--- a/src/index.js
+++ b/src/index.js
@@ -44,6 +44,7 @@ const isSecondInstance = app.makeSingleInstance((argv) => {
44}); 44});
45 45
46if (isSecondInstance) { 46if (isSecondInstance) {
47 console.log('An instance of Franz is already running. Exiting...');
47 app.exit(); 48 app.exit();
48} 49}
49 50
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 5a6c12ee1..e33f50f05 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -1,10 +1,11 @@
1import { remote, ipcRenderer, shell } from 'electron'; 1import { remote, ipcRenderer, shell } from 'electron';
2import { action, observable } from 'mobx'; 2import { action, computed, observable } from 'mobx';
3import moment from 'moment'; 3import moment from 'moment';
4import key from 'keymaster'; 4import key from 'keymaster';
5import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; 5import { getDoNotDisturb } from '@meetfranz/electron-notification-state';
6import idleTimer from '@paulcbetts/system-idle-time'; 6import idleTimer from '@paulcbetts/system-idle-time';
7import AutoLaunch from 'auto-launch'; 7import AutoLaunch from 'auto-launch';
8import prettyBytes from 'pretty-bytes';
8 9
9import Store from './lib/Store'; 10import Store from './lib/Store';
10import Request from './lib/Request'; 11import Request from './lib/Request';
@@ -14,7 +15,10 @@ import locales from '../i18n/translations';
14import { gaEvent } from '../lib/analytics'; 15import { gaEvent } from '../lib/analytics';
15import Miner from '../lib/Miner'; 16import Miner from '../lib/Miner';
16 17
17const { app, powerMonitor } = remote; 18import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js';
19
20const { app } = remote;
21
18const defaultLocale = DEFAULT_APP_SETTINGS.locale; 22const defaultLocale = DEFAULT_APP_SETTINGS.locale;
19const autoLauncher = new AutoLaunch({ 23const autoLauncher = new AutoLaunch({
20 name: 'Franz', 24 name: 'Franz',
@@ -30,6 +34,8 @@ export default class AppStore extends Store {
30 }; 34 };
31 35
32 @observable healthCheckRequest = new Request(this.api.app, 'health'); 36 @observable healthCheckRequest = new Request(this.api.app, 'health');
37 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize');
38 @observable clearAppCacheRequest = new Request(this.api.local, 'clearAppCache');
33 39
34 @observable autoLaunchOnStart = true; 40 @observable autoLaunchOnStart = true;
35 41
@@ -47,6 +53,8 @@ export default class AppStore extends Store {
47 53
48 @observable isSystemMuteOverridden = false; 54 @observable isSystemMuteOverridden = false;
49 55
56 @observable isClearingAllCache = false;
57
50 constructor(...args) { 58 constructor(...args) {
51 super(...args); 59 super(...args);
52 60
@@ -61,6 +69,7 @@ export default class AppStore extends Store {
61 this.actions.app.healthCheck.listen(this._healthCheck.bind(this)); 69 this.actions.app.healthCheck.listen(this._healthCheck.bind(this));
62 this.actions.app.muteApp.listen(this._muteApp.bind(this)); 70 this.actions.app.muteApp.listen(this._muteApp.bind(this));
63 this.actions.app.toggleMuteApp.listen(this._toggleMuteApp.bind(this)); 71 this.actions.app.toggleMuteApp.listen(this._toggleMuteApp.bind(this));
72 this.actions.app.clearAllCache.listen(this._clearAllCache.bind(this));
64 73
65 this.registerReactions([ 74 this.registerReactions([
66 this._offlineCheck.bind(this), 75 this._offlineCheck.bind(this),
@@ -124,15 +133,23 @@ export default class AppStore extends Store {
124 this.stores.router.push(data.url); 133 this.stores.router.push(data.url);
125 }); 134 });
126 135
136 const TIMEOUT = 5000;
127 // Check system idle time every minute 137 // Check system idle time every minute
128 setInterval(() => { 138 setInterval(() => {
129 this.idleTime = idleTimer.getIdleTime(); 139 this.idleTime = idleTimer.getIdleTime();
130 }, 60000); 140 }, TIMEOUT);
131 141
132 // Reload all services after a healthy nap 142 // Reload all services after a healthy nap
133 powerMonitor.on('resume', () => { 143 // Alternative solution for powerMonitor as the resume event is not fired
134 setTimeout(window.location.reload, 5000); 144 // More information: https://github.com/electron/electron/issues/1615
135 }); 145 let lastTime = (new Date()).getTime();
146 setInterval(() => {
147 const currentTime = (new Date()).getTime();
148 if (currentTime > (lastTime + TIMEOUT + 2000)) {
149 this._reactivateServices();
150 }
151 lastTime = currentTime;
152 }, TIMEOUT);
136 153
137 // Set active the next service 154 // Set active the next service
138 key( 155 key(
@@ -157,6 +174,10 @@ export default class AppStore extends Store {
157 this._healthCheck(); 174 this._healthCheck();
158 } 175 }
159 176
177 @computed get cacheSize() {
178 return prettyBytes(this.getAppCacheSizeRequest.execute().result || 0);
179 }
180
160 // Actions 181 // Actions
161 @action _notify({ title, options, notificationId, serviceId = null }) { 182 @action _notify({ title, options, notificationId, serviceId = null }) {
162 if (this.stores.settings.all.isAppMuted) return; 183 if (this.stores.settings.all.isAppMuted) return;
@@ -247,6 +268,23 @@ export default class AppStore extends Store {
247 this._muteApp({ isMuted: !this.stores.settings.all.isAppMuted }); 268 this._muteApp({ isMuted: !this.stores.settings.all.isAppMuted });
248 } 269 }
249 270
271 @action async _clearAllCache() {
272 this.isClearingAllCache = true;
273 const clearAppCache = this.clearAppCacheRequest.execute();
274 const allServiceIds = await getServiceIdsFromPartitions();
275 const allOrphanedServiceIds = allServiceIds.filter(id => !this.stores.services.all.find(s => id.replace('service-', '') === s.id));
276
277 await Promise.all(allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)));
278
279 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ serviceId: s.id })));
280
281 await clearAppCache._promise;
282
283 this.getAppCacheSizeRequest.execute();
284
285 this.isClearingAllCache = false;
286 }
287
250 // Reactions 288 // Reactions
251 _offlineCheck() { 289 _offlineCheck() {
252 if (!this.isOnline) { 290 if (!this.isOnline) {
@@ -357,6 +395,16 @@ export default class AppStore extends Store {
357 return autoLauncher.isEnabled() || false; 395 return autoLauncher.isEnabled() || false;
358 } 396 }
359 397
398 _reactivateServices(retryCount = 0) {
399 if (!this.isOnline) {
400 console.debug('reactivateServices: computer is offline, trying again in 5s, retries:', retryCount);
401 setTimeout(() => this._reactivateServices(retryCount + 1), 5000);
402 } else {
403 console.debug('reactivateServices: reload all services');
404 this.actions.service.reloadAll();
405 }
406 }
407
360 _systemDND() { 408 _systemDND() {
361 const dnd = getDoNotDisturb(); 409 const dnd = getDoNotDisturb();
362 if (dnd === this.stores.settings.all.isAppMuted || !this.isSystemMuteOverriden) { 410 if (dnd === this.stores.settings.all.isAppMuted || !this.isSystemMuteOverriden) {
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index f2a8683ba..7300a76c8 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -16,6 +16,7 @@ export default class ServicesStore extends Store {
16 @observable updateServiceRequest = new Request(this.api.services, 'update'); 16 @observable updateServiceRequest = new Request(this.api.services, 'update');
17 @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); 17 @observable reorderServicesRequest = new Request(this.api.services, 'reorder');
18 @observable deleteServiceRequest = new Request(this.api.services, 'delete'); 18 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
19 @observable clearCacheRequest = new Request(this.api.services, 'clearCache');
19 20
20 @observable filterNeedle = null; 21 @observable filterNeedle = null;
21 22
@@ -31,6 +32,7 @@ export default class ServicesStore extends Store {
31 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); 32 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this));
32 this.actions.service.updateService.listen(this._updateService.bind(this)); 33 this.actions.service.updateService.listen(this._updateService.bind(this));
33 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 34 this.actions.service.deleteService.listen(this._deleteService.bind(this));
35 this.actions.service.clearCache.listen(this._clearCache.bind(this));
34 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 36 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this));
35 this.actions.service.focusService.listen(this._focusService.bind(this)); 37 this.actions.service.focusService.listen(this._focusService.bind(this));
36 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 38 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this));
@@ -225,6 +227,13 @@ export default class ServicesStore extends Store {
225 gaEvent('Service', 'delete', service.recipe.id); 227 gaEvent('Service', 'delete', service.recipe.id);
226 } 228 }
227 229
230 @action async _clearCache({ serviceId }) {
231 this.clearCacheRequest.reset();
232 const request = this.clearCacheRequest.execute(serviceId);
233 await request._promise;
234 gaEvent('Service', 'clear cache');
235 }
236
228 @action _setActive({ serviceId }) { 237 @action _setActive({ serviceId }) {
229 const service = this.one(serviceId); 238 const service = this.one(serviceId);
230 239
@@ -347,6 +356,10 @@ export default class ServicesStore extends Store {
347 redirect: false, 356 redirect: false,
348 }); 357 });
349 } 358 }
359 } else if (channel === 'new-window') {
360 const url = args[0];
361
362 this.actions.app.openExternalUrl({ url });
350 } 363 }
351 } 364 }
352 365
@@ -388,7 +401,7 @@ export default class ServicesStore extends Store {
388 const service = this.one(serviceId); 401 const service = this.one(serviceId);
389 service.resetMessageCount(); 402 service.resetMessageCount();
390 403
391 service.webview.reload(); 404 service.webview.loadURL(service.url);
392 } 405 }
393 406
394 @action _reloadActive() { 407 @action _reloadActive() {
@@ -517,12 +530,13 @@ export default class ServicesStore extends Store {
517 .reduce((a, b) => a + b, 0); 530 .reduce((a, b) => a + b, 0);
518 531
519 const unreadIndirectMessageCount = this.allDisplayed 532 const unreadIndirectMessageCount = this.allDisplayed
520 .filter(s => (showMessageBadgeWhenMuted || s.isIndirectMessageBadgeEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) 533 .filter(s => (showMessageBadgeWhenMuted && showMessageBadgesEvenWhenMuted) && (s.isBadgeEnabled && s.isIndirectMessageBadgeEnabled))
521 .map(s => s.unreadIndirectMessageCount) 534 .map(s => s.unreadIndirectMessageCount)
522 .reduce((a, b) => a + b, 0); 535 .reduce((a, b) => a + b, 0);
523 536
524 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases 537 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases
525 if (showMessageBadgesEvenWhenMuted) { 538 if (showMessageBadgesEvenWhenMuted) {
539 console.log('set badge', unreadDirectMessageCount, unreadIndirectMessageCount);
526 this.actions.app.setBadge({ 540 this.actions.app.setBadge({
527 unreadDirectMessageCount, 541 unreadDirectMessageCount,
528 unreadIndirectMessageCount, 542 unreadIndirectMessageCount,
diff --git a/src/styles/button.scss b/src/styles/button.scss
index 75d2cb1d4..8d2adbbcc 100644
--- a/src/styles/button.scss
+++ b/src/styles/button.scss
@@ -48,6 +48,18 @@
48 } 48 }
49 } 49 }
50 50
51 &.franz-form__button--warning {
52 background: $theme-brand-warning;
53
54 &:hover {
55 background: darken($theme-brand-warning, 5%);
56 }
57
58 &:active {
59 background: lighten($theme-brand-warning, 5%);
60 }
61 }
62
51 &.franz-form__button--inverted { 63 &.franz-form__button--inverted {
52 background: none; 64 background: none;
53 padding: 10px 20px; 65 padding: 10px 20px;
diff --git a/src/styles/searchInput.scss b/src/styles/searchInput.scss
index 28ff09fc4..633a31e09 100644
--- a/src/styles/searchInput.scss
+++ b/src/styles/searchInput.scss
@@ -1,4 +1,20 @@
1.search-input { 1.search-input {
2 width: 100%; 2 width: 100%;
3 height: auto; 3 height: auto;
4 display: flex;
5 align-items: center;
6 padding: 0 10px;
7 border-radius: 30px;
8 background: $theme-gray-lightest;
9 padding: 5px 10px;
10 @extend %headline;
11 color: $theme-gray-light;
12
13 input {
14 padding-left: 10px;
15 background: none;
16 border: 0;
17 flex: 1;
18 color: $theme-gray-light;
19 }
4} 20}
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index cb39717ea..2182c9b5f 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -149,26 +149,8 @@
149 } 149 }
150 } 150 }
151 151
152 .settings__search-header { 152 .search-input {
153 display: flex; 153 margin-bottom: 30px;
154 align-items: center;
155 padding: 0 10px;
156 border-radius: $theme-border-radius;
157 transition: background $theme-transition-time;
158 @extend %headline;
159 font-size: 22px;
160
161 &:hover {
162 background: darken($theme-gray-lighter, 5%);
163 }
164
165 input {
166 padding-left: 10px;
167 background: none;
168 border: 0;
169 flex: 1;
170 @extend %headline;
171 }
172 } 154 }
173 155
174 &__options { 156 &__options {
diff --git a/src/webview/notifications.js b/src/webview/notifications.js
index 4f602bfdb..2020bbdc6 100644
--- a/src/webview/notifications.js
+++ b/src/webview/notifications.js
@@ -16,7 +16,9 @@ class Notification {
16 })); 16 }));
17 17
18 ipcRenderer.once(`notification-onclick:${this.notificationId}`, () => { 18 ipcRenderer.once(`notification-onclick:${this.notificationId}`, () => {
19 this.onclick(); 19 if (typeof this.onclick === 'function') {
20 this.onclick();
21 }
20 }); 22 });
21 } 23 }
22 24
diff --git a/src/webview/plugin.js b/src/webview/plugin.js
index 9903ee07a..d9e021e6d 100644
--- a/src/webview/plugin.js
+++ b/src/webview/plugin.js
@@ -5,8 +5,8 @@ import path from 'path';
5import { isDevMode } from '../environment'; 5import { isDevMode } from '../environment';
6import RecipeWebview from './lib/RecipeWebview'; 6import RecipeWebview from './lib/RecipeWebview';
7 7
8import Spellchecker from './spellchecker.js'; 8import Spellchecker from './spellchecker';
9import './notifications.js'; 9import './notifications';
10 10
11ipcRenderer.on('initializeRecipe', (e, data) => { 11ipcRenderer.on('initializeRecipe', (e, data) => {
12 const modulePath = path.join(data.recipe.path, 'webview.js'); 12 const modulePath = path.join(data.recipe.path, 'webview.js');
@@ -39,3 +39,15 @@ ipcRenderer.on('settings-update', (e, data) => {
39document.addEventListener('DOMContentLoaded', () => { 39document.addEventListener('DOMContentLoaded', () => {
40 ipcRenderer.sendToHost('hello'); 40 ipcRenderer.sendToHost('hello');
41}, false); 41}, false);
42
43// Patching window.open
44const originalWindowOpen = window.open;
45
46window.open = (url, frameName, features) => {
47 // We need to differentiate if the link should be opened in a popup or in the systems default browser
48 if (!frameName && !features) {
49 return ipcRenderer.sendToHost('new-window', url);
50 }
51
52 return originalWindowOpen(url, frameName, features);
53};
diff --git a/yarn.lock b/yarn.lock
index 353f346a0..58befebf1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -39,7 +39,7 @@
39 rimraf "^2.4.0" 39 rimraf "^2.4.0"
40 underscore "^1.6.0" 40 underscore "^1.6.0"
41 41
42"@paulcbetts/spellchecker@^4.0.5": 42"@paulcbetts/spellchecker@^4.0.6":
43 version "4.0.6" 43 version "4.0.6"
44 resolved "https://registry.yarnpkg.com/@paulcbetts/spellchecker/-/spellchecker-4.0.6.tgz#79ef1f9c19c5a3156921ccaa9ffdc3efbbee47e3" 44 resolved "https://registry.yarnpkg.com/@paulcbetts/spellchecker/-/spellchecker-4.0.6.tgz#79ef1f9c19c5a3156921ccaa9ffdc3efbbee47e3"
45 dependencies: 45 dependencies:
@@ -333,6 +333,10 @@ async@^0.9.0:
333 version "0.9.2" 333 version "0.9.2"
334 resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" 334 resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
335 335
336async@~0.1.22:
337 version "0.1.22"
338 resolved "https://registry.yarnpkg.com/async/-/async-0.1.22.tgz#0fc1aaa088a0e3ef0ebe2d8831bab0dcf8845061"
339
336asynckit@^0.4.0: 340asynckit@^0.4.0:
337 version "0.4.0" 341 version "0.4.0"
338 resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 342 resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -1307,6 +1311,10 @@ capture-stack-trace@^1.0.0:
1307 version "1.0.0" 1311 version "1.0.0"
1308 resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" 1312 resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
1309 1313
1314caseless@~0.11.0:
1315 version "0.11.0"
1316 resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
1317
1310caseless@~0.12.0: 1318caseless@~0.12.0:
1311 version "0.12.0" 1319 version "0.12.0"
1312 resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 1320 resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1783,6 +1791,12 @@ dotenv@^4.0.0:
1783 version "4.0.0" 1791 version "4.0.0"
1784 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" 1792 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
1785 1793
1794du@^0.1.0:
1795 version "0.1.0"
1796 resolved "https://registry.yarnpkg.com/du/-/du-0.1.0.tgz#f26e340a09c7bc5b6fd69af6dbadea60fa8c6f4d"
1797 dependencies:
1798 async "~0.1.22"
1799
1786duplexer2@0.0.2, duplexer2@~0.0.2: 1800duplexer2@0.0.2, duplexer2@~0.0.2:
1787 version "0.0.2" 1801 version "0.0.2"
1788 resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" 1802 resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"
@@ -2017,12 +2031,12 @@ electron-remote@^1.1.1:
2017 rxjs "^5.0.0-beta.12" 2031 rxjs "^5.0.0-beta.12"
2018 xmlhttprequest "^1.8.0" 2032 xmlhttprequest "^1.8.0"
2019 2033
2020electron-spellchecker@^1.2.0: 2034electron-spellchecker@^1.1.2:
2021 version "1.2.0" 2035 version "1.1.2"
2022 resolved "https://registry.yarnpkg.com/electron-spellchecker/-/electron-spellchecker-1.2.0.tgz#f6306afd4078244c1e6311370667d95b873fbcbb" 2036 resolved "https://registry.yarnpkg.com/electron-spellchecker/-/electron-spellchecker-1.1.2.tgz#5fbe1e65d246b77e6e7433ee2387d9d26010f7a8"
2023 dependencies: 2037 dependencies:
2024 "@paulcbetts/cld" "^2.4.6" 2038 "@paulcbetts/cld" "^2.4.6"
2025 "@paulcbetts/spellchecker" "^4.0.5" 2039 "@paulcbetts/spellchecker" "^4.0.6"
2026 bcp47 "^1.1.2" 2040 bcp47 "^1.1.2"
2027 debug "^2.6.3" 2041 debug "^2.6.3"
2028 electron-remote "^1.1.1" 2042 electron-remote "^1.1.1"
@@ -2728,6 +2742,16 @@ gaze@^1.0.0:
2728 dependencies: 2742 dependencies:
2729 globule "^1.0.0" 2743 globule "^1.0.0"
2730 2744
2745generate-function@^2.0.0:
2746 version "2.0.0"
2747 resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
2748
2749generate-object-property@^1.1.0:
2750 version "1.2.0"
2751 resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
2752 dependencies:
2753 is-property "^1.0.0"
2754
2731get-caller-file@^1.0.1: 2755get-caller-file@^1.0.1:
2732 version "1.0.2" 2756 version "1.0.2"
2733 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" 2757 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
@@ -3122,6 +3146,15 @@ har-schema@^1.0.5:
3122 version "1.0.5" 3146 version "1.0.5"
3123 resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 3147 resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
3124 3148
3149har-validator@~2.0.6:
3150 version "2.0.6"
3151 resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
3152 dependencies:
3153 chalk "^1.1.1"
3154 commander "^2.9.0"
3155 is-my-json-valid "^2.12.4"
3156 pinkie-promise "^2.0.0"
3157
3125har-validator@~4.2.1: 3158har-validator@~4.2.1:
3126 version "4.2.1" 3159 version "4.2.1"
3127 resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 3160 resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
@@ -3437,6 +3470,15 @@ is-glob@^3.1.0:
3437 dependencies: 3470 dependencies:
3438 is-extglob "^2.1.0" 3471 is-extglob "^2.1.0"
3439 3472
3473is-my-json-valid@^2.12.4:
3474 version "2.17.1"
3475 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
3476 dependencies:
3477 generate-function "^2.0.0"
3478 generate-object-property "^1.1.0"
3479 jsonpointer "^4.0.0"
3480 xtend "^4.0.0"
3481
3440is-npm@^1.0.0: 3482is-npm@^1.0.0:
3441 version "1.0.0" 3483 version "1.0.0"
3442 resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" 3484 resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
@@ -3495,6 +3537,10 @@ is-promise@^2.1.0:
3495 version "2.1.0" 3537 version "2.1.0"
3496 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 3538 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
3497 3539
3540is-property@^1.0.0:
3541 version "1.0.2"
3542 resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
3543
3498is-redirect@^1.0.0: 3544is-redirect@^1.0.0:
3499 version "1.0.0" 3545 version "1.0.0"
3500 resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" 3546 resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
@@ -3690,6 +3736,10 @@ jsonify@~0.0.0:
3690 version "0.0.0" 3736 version "0.0.0"
3691 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 3737 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
3692 3738
3739jsonpointer@^4.0.0:
3740 version "4.0.1"
3741 resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
3742
3693jsonwebtoken@^7.4.1: 3743jsonwebtoken@^7.4.1:
3694 version "7.4.3" 3744 version "7.4.3"
3695 resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638" 3745 resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638"
@@ -4358,7 +4408,7 @@ node-pre-gyp@^0.6.36:
4358 tar "^2.2.1" 4408 tar "^2.2.1"
4359 tar-pack "^3.4.0" 4409 tar-pack "^3.4.0"
4360 4410
4361node-sass@^4.2.0, node-sass@^4.5.3: 4411node-sass@^4.2.0:
4362 version "4.5.3" 4412 version "4.5.3"
4363 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568" 4413 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568"
4364 dependencies: 4414 dependencies:
@@ -4381,6 +4431,30 @@ node-sass@^4.2.0, node-sass@^4.5.3:
4381 sass-graph "^2.1.1" 4431 sass-graph "^2.1.1"
4382 stdout-stream "^1.4.0" 4432 stdout-stream "^1.4.0"
4383 4433
4434node-sass@^4.7.2:
4435 version "4.7.2"
4436 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
4437 dependencies:
4438 async-foreach "^0.1.3"
4439 chalk "^1.1.1"
4440 cross-spawn "^3.0.0"
4441 gaze "^1.0.0"
4442 get-stdin "^4.0.1"
4443 glob "^7.0.3"
4444 in-publish "^2.0.0"
4445 lodash.assign "^4.2.0"
4446 lodash.clonedeep "^4.3.2"
4447 lodash.mergewith "^4.6.0"
4448 meow "^3.7.0"
4449 mkdirp "^0.5.1"
4450 nan "^2.3.2"
4451 node-gyp "^3.3.1"
4452 npmlog "^4.0.0"
4453 request "~2.79.0"
4454 sass-graph "^2.2.4"
4455 stdout-stream "^1.4.0"
4456 "true-case-path" "^1.0.2"
4457
4384node-watch@^0.3.4: 4458node-watch@^0.3.4:
4385 version "0.3.5" 4459 version "0.3.5"
4386 resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.3.5.tgz#a07f253a4f538de9d4ca522dd7f1996eeec0d97e" 4460 resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.3.5.tgz#a07f253a4f538de9d4ca522dd7f1996eeec0d97e"
@@ -4835,6 +4909,10 @@ pretty-bytes@^1.0.2, pretty-bytes@^1.0.4:
4835 get-stdin "^4.0.1" 4909 get-stdin "^4.0.1"
4836 meow "^3.1.0" 4910 meow "^3.1.0"
4837 4911
4912pretty-bytes@^4.0.2:
4913 version "4.0.2"
4914 resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
4915
4838pretty-hrtime@^1.0.0: 4916pretty-hrtime@^1.0.0:
4839 version "1.0.3" 4917 version "1.0.3"
4840 resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" 4918 resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
@@ -4911,6 +4989,10 @@ q@^1.1.2:
4911 version "1.5.0" 4989 version "1.5.0"
4912 resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" 4990 resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
4913 4991
4992qs@~6.3.0:
4993 version "6.3.2"
4994 resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
4995
4914qs@~6.4.0: 4996qs@~6.4.0:
4915 version "6.4.0" 4997 version "6.4.0"
4916 resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 4998 resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@@ -5262,6 +5344,31 @@ request@2, request@^2.45.0, request@^2.54.0, request@^2.79.0, request@^2.81.0:
5262 tunnel-agent "^0.6.0" 5344 tunnel-agent "^0.6.0"
5263 uuid "^3.0.0" 5345 uuid "^3.0.0"
5264 5346
5347request@~2.79.0:
5348 version "2.79.0"
5349 resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
5350 dependencies:
5351 aws-sign2 "~0.6.0"
5352 aws4 "^1.2.1"
5353 caseless "~0.11.0"
5354 combined-stream "~1.0.5"
5355 extend "~3.0.0"
5356 forever-agent "~0.6.1"
5357 form-data "~2.1.1"
5358 har-validator "~2.0.6"
5359 hawk "~3.1.3"
5360 http-signature "~1.1.0"
5361 is-typedarray "~1.0.0"
5362 isstream "~0.1.2"
5363 json-stringify-safe "~5.0.1"
5364 mime-types "~2.1.7"
5365 oauth-sign "~0.8.1"
5366 qs "~6.3.0"
5367 stringstream "~0.0.4"
5368 tough-cookie "~2.3.0"
5369 tunnel-agent "~0.4.1"
5370 uuid "^3.0.0"
5371
5265require-directory@^2.1.1: 5372require-directory@^2.1.1:
5266 version "2.1.1" 5373 version "2.1.1"
5267 resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 5374 resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -5365,7 +5472,7 @@ sanitize-filename@^1.6.0, sanitize-filename@^1.6.1:
5365 dependencies: 5472 dependencies:
5366 truncate-utf8-bytes "^1.0.0" 5473 truncate-utf8-bytes "^1.0.0"
5367 5474
5368sass-graph@^2.1.1: 5475sass-graph@^2.1.1, sass-graph@^2.2.4:
5369 version "2.2.4" 5476 version "2.2.4"
5370 resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" 5477 resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
5371 dependencies: 5478 dependencies:
@@ -5935,6 +6042,12 @@ trim-right@^1.0.1:
5935 version "1.0.1" 6042 version "1.0.1"
5936 resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" 6043 resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
5937 6044
6045"true-case-path@^1.0.2":
6046 version "1.0.2"
6047 resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
6048 dependencies:
6049 glob "^6.0.4"
6050
5938truncate-utf8-bytes@^1.0.0: 6051truncate-utf8-bytes@^1.0.0:
5939 version "1.0.2" 6052 version "1.0.2"
5940 resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" 6053 resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
@@ -5951,6 +6064,10 @@ tunnel-agent@^0.6.0:
5951 dependencies: 6064 dependencies:
5952 safe-buffer "^5.0.1" 6065 safe-buffer "^5.0.1"
5953 6066
6067tunnel-agent@~0.4.1:
6068 version "0.4.3"
6069 resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
6070
5954tweetnacl@^0.14.3, tweetnacl@~0.14.0: 6071tweetnacl@^0.14.3, tweetnacl@~0.14.0:
5955 version "0.14.5" 6072 version "0.14.5"
5956 resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 6073 resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -6310,7 +6427,7 @@ xmlhttprequest@^1.8.0:
6310 version "1.8.0" 6427 version "1.8.0"
6311 resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" 6428 resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
6312 6429
6313"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: 6430"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
6314 version "4.0.1" 6431 version "4.0.1"
6315 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 6432 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
6316 6433