diff options
author | Stefan Malzner <stefan@adlk.io> | 2017-12-15 14:44:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-15 14:44:46 +0100 |
commit | dc1dd2e857114fac2462f18ea774ddacb287fa81 (patch) | |
tree | 32b701de50c505abc95ceddc7c429df85c81f041 | |
parent | Remove IME handlers (diff) | |
parent | Merge pull request #475 from meetfranz/feature/service-improvements (diff) | |
download | ferdium-app-dc1dd2e857114fac2462f18ea774ddacb287fa81.tar.gz ferdium-app-dc1dd2e857114fac2462f18ea774ddacb287fa81.tar.zst ferdium-app-dc1dd2e857114fac2462f18ea774ddacb287fa81.zip |
Merge branch 'develop' into feature/macOS-copy-paste
-rw-r--r-- | gulpfile.babel.js | 6 | ||||
-rw-r--r-- | src/api/server/LocalApi.js | 4 | ||||
-rw-r--r-- | src/components/services/tabs/TabItem.js | 2 | ||||
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 77 | ||||
-rw-r--r-- | src/components/settings/settings/EditSettingsForm.js | 5 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 20 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 5 | ||||
-rw-r--r-- | src/lib/Menu.js | 2 | ||||
-rw-r--r-- | src/models/Recipe.js | 9 | ||||
-rw-r--r-- | src/models/Service.js | 4 | ||||
-rw-r--r-- | src/models/Settings.js | 6 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 10 | ||||
-rw-r--r-- | src/stores/SettingsStore.js | 2 | ||||
-rw-r--r-- | src/styles/content-tabs.scss | 12 | ||||
-rw-r--r-- | src/styles/input.scss | 4 | ||||
-rw-r--r-- | src/styles/settings.scss | 23 | ||||
-rw-r--r-- | src/webview/plugin.js | 28 | ||||
-rw-r--r-- | src/webview/spellchecker.js | 65 |
18 files changed, 208 insertions, 76 deletions
diff --git a/gulpfile.babel.js b/gulpfile.babel.js index d947974b3..b50001b2d 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js | |||
@@ -110,7 +110,11 @@ export function watch() { | |||
110 | } | 110 | } |
111 | 111 | ||
112 | export function webserver() { | 112 | export function webserver() { |
113 | gulp.src(paths.dest) | 113 | gulp.src([ |
114 | paths.dest, | ||
115 | `!${paths.dest}/electron/**`, | ||
116 | `!${paths.dest}/webview/**`, | ||
117 | ]) | ||
114 | .pipe(server({ | 118 | .pipe(server({ |
115 | livereload: true, | 119 | livereload: true, |
116 | })); | 120 | })); |
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index eba236f16..79ac6e12f 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js | |||
@@ -1,5 +1,3 @@ | |||
1 | import SettingsModel from '../../models/Settings'; | ||
2 | |||
3 | export default class LocalApi { | 1 | export default class LocalApi { |
4 | // App | 2 | // App |
5 | async updateAppSettings(data) { | 3 | async updateAppSettings(data) { |
@@ -15,7 +13,7 @@ export default class LocalApi { | |||
15 | async getAppSettings() { | 13 | async getAppSettings() { |
16 | const settingsString = localStorage.getItem('app'); | 14 | const settingsString = localStorage.getItem('app'); |
17 | try { | 15 | try { |
18 | const settings = new SettingsModel(JSON.parse(settingsString) || {}); | 16 | const settings = JSON.parse(settingsString) || {}; |
19 | console.debug('LocalApi::getAppSettings resolves', settings); | 17 | console.debug('LocalApi::getAppSettings resolves', settings); |
20 | 18 | ||
21 | return settings; | 19 | return settings; |
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js index 8403d9462..7aed8fda7 100644 --- a/src/components/services/tabs/TabItem.js +++ b/src/components/services/tabs/TabItem.js | |||
@@ -126,7 +126,7 @@ class TabItem extends Component { | |||
126 | const menu = Menu.buildFromTemplate(menuTemplate); | 126 | const menu = Menu.buildFromTemplate(menuTemplate); |
127 | 127 | ||
128 | let notificationBadge = null; | 128 | let notificationBadge = null; |
129 | if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted) { | 129 | if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && service.isBadgeEnabled) { |
130 | notificationBadge = ( | 130 | notificationBadge = ( |
131 | <span> | 131 | <span> |
132 | {service.unreadDirectMessageCount > 0 && ( | 132 | {service.unreadDirectMessageCount > 0 && ( |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 36cefe87c..4458c4c5a 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -47,6 +47,10 @@ const messages = defineMessages({ | |||
47 | id: 'settings.service.form.tabOnPremise', | 47 | id: 'settings.service.form.tabOnPremise', |
48 | defaultMessage: '!!!Self hosted ⭐️', | 48 | defaultMessage: '!!!Self hosted ⭐️', |
49 | }, | 49 | }, |
50 | useHostedService: { | ||
51 | id: 'settings.service.form.useHostedService', | ||
52 | defaultMessage: '!!!Use the hosted {name} service.', | ||
53 | }, | ||
50 | customUrlValidationError: { | 54 | customUrlValidationError: { |
51 | id: 'settings.service.form.customUrlValidationError', | 55 | id: 'settings.service.form.customUrlValidationError', |
52 | defaultMessage: '!!!Could not validate custom {name} server.', | 56 | defaultMessage: '!!!Could not validate custom {name} server.', |
@@ -67,6 +71,18 @@ const messages = defineMessages({ | |||
67 | id: 'settings.service.form.isMutedInfo', | 71 | id: 'settings.service.form.isMutedInfo', |
68 | defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', | 72 | defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', |
69 | }, | 73 | }, |
74 | headlineNotifications: { | ||
75 | id: 'settings.service.form.headlineNotifications', | ||
76 | defaultMessage: '!!!Notifications', | ||
77 | }, | ||
78 | headlineBadges: { | ||
79 | id: 'settings.service.form.headlineBadges', | ||
80 | defaultMessage: '!!!Unread message dadges', | ||
81 | }, | ||
82 | headlineGeneral: { | ||
83 | id: 'settings.service.form.headlineGeneral', | ||
84 | defaultMessage: '!!!General', | ||
85 | }, | ||
70 | }); | 86 | }); |
71 | 87 | ||
72 | @observer | 88 | @observer |
@@ -108,7 +124,6 @@ export default class EditServiceForm extends Component { | |||
108 | this.props.form.submit({ | 124 | this.props.form.submit({ |
109 | onSuccess: async (form) => { | 125 | onSuccess: async (form) => { |
110 | const values = form.values(); | 126 | const values = form.values(); |
111 | |||
112 | let isValid = true; | 127 | let isValid = true; |
113 | 128 | ||
114 | if (recipe.validateUrl && values.customUrl) { | 129 | if (recipe.validateUrl && values.customUrl) { |
@@ -166,6 +181,13 @@ export default class EditServiceForm extends Component { | |||
166 | /> | 181 | /> |
167 | ); | 182 | ); |
168 | 183 | ||
184 | let activeTabIndex = 0; | ||
185 | if (recipe.hasHostedOption && service.team) { | ||
186 | activeTabIndex = 1; | ||
187 | } else if (recipe.hasHostedOption && service.customUrl) { | ||
188 | activeTabIndex = 2; | ||
189 | } | ||
190 | |||
169 | return ( | 191 | return ( |
170 | <div className="settings__main"> | 192 | <div className="settings__main"> |
171 | <div className="settings__header"> | 193 | <div className="settings__header"> |
@@ -198,11 +220,20 @@ export default class EditServiceForm extends Component { | |||
198 | <Input field={form.$('name')} focus /> | 220 | <Input field={form.$('name')} focus /> |
199 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | 221 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( |
200 | <Tabs | 222 | <Tabs |
201 | active={service.customUrl ? 1 : 0} | 223 | active={activeTabIndex} |
202 | > | 224 | > |
225 | {recipe.hasHostedOption && ( | ||
226 | <TabItem title={recipe.name}> | ||
227 | {intl.formatMessage(messages.useHostedService, { name: recipe.name })} | ||
228 | </TabItem> | ||
229 | )} | ||
203 | {recipe.hasTeamId && ( | 230 | {recipe.hasTeamId && ( |
204 | <TabItem title={intl.formatMessage(messages.tabHosted)}> | 231 | <TabItem title={intl.formatMessage(messages.tabHosted)}> |
205 | <Input field={form.$('team')} suffix={recipe.urlInputSuffix} /> | 232 | <Input |
233 | field={form.$('team')} | ||
234 | prefix={recipe.urlInputPrefix} | ||
235 | suffix={recipe.urlInputSuffix} | ||
236 | /> | ||
206 | </TabItem> | 237 | </TabItem> |
207 | )} | 238 | )} |
208 | {recipe.hasCustomUrl && ( | 239 | {recipe.hasCustomUrl && ( |
@@ -231,20 +262,32 @@ export default class EditServiceForm extends Component { | |||
231 | </Tabs> | 262 | </Tabs> |
232 | )} | 263 | )} |
233 | <div className="settings__options"> | 264 | <div className="settings__options"> |
234 | <Toggle field={form.$('isNotificationEnabled')} /> | 265 | <div className="settings__settings-group"> |
235 | {recipe.hasIndirectMessages && ( | 266 | <h3>{intl.formatMessage(messages.headlineNotifications)}</h3> |
236 | <div> | 267 | <Toggle field={form.$('isNotificationEnabled')} /> |
237 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | 268 | <Toggle field={form.$('isMuted')} /> |
238 | <p className="settings__help"> | 269 | <p className="settings__help"> |
239 | {intl.formatMessage(messages.indirectMessageInfo)} | 270 | {intl.formatMessage(messages.isMutedInfo)} |
240 | </p> | 271 | </p> |
241 | </div> | 272 | </div> |
242 | )} | 273 | |
243 | <Toggle field={form.$('isMuted')} /> | 274 | <div className="settings__settings-group"> |
244 | <p className="settings__help"> | 275 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> |
245 | {intl.formatMessage(messages.isMutedInfo)} | 276 | <Toggle field={form.$('isBadgeEnabled')} /> |
246 | </p> | 277 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( |
247 | <Toggle field={form.$('isEnabled')} /> | 278 | <div> |
279 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | ||
280 | <p className="settings__help"> | ||
281 | {intl.formatMessage(messages.indirectMessageInfo)} | ||
282 | </p> | ||
283 | </div> | ||
284 | )} | ||
285 | </div> | ||
286 | |||
287 | <div className="settings__settings-group"> | ||
288 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> | ||
289 | <Toggle field={form.$('isEnabled')} /> | ||
290 | </div> | ||
248 | </div> | 291 | </div> |
249 | {recipe.message && ( | 292 | {recipe.message && ( |
250 | <p className="settings__message"> | 293 | <p className="settings__message"> |
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 878e46d6d..ff398aa33 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js | |||
@@ -64,10 +64,6 @@ const messages = defineMessages({ | |||
64 | id: 'settings.app.currentVersion', | 64 | id: 'settings.app.currentVersion', |
65 | defaultMessage: '!!!Current version:', | 65 | defaultMessage: '!!!Current version:', |
66 | }, | 66 | }, |
67 | restartRequired: { | ||
68 | id: 'settings.app.restartRequired', | ||
69 | defaultMessage: '!!!Changes require restart', | ||
70 | }, | ||
71 | }); | 67 | }); |
72 | 68 | ||
73 | @observer | 69 | @observer |
@@ -158,7 +154,6 @@ export default class EditSettingsForm extends Component { | |||
158 | {/* Advanced */} | 154 | {/* Advanced */} |
159 | <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2> | 155 | <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2> |
160 | <Toggle field={form.$('enableSpellchecking')} /> | 156 | <Toggle field={form.$('enableSpellchecking')} /> |
161 | <p className="settings__help">{intl.formatMessage(messages.restartRequired)}</p> | ||
162 | {/* <Select field={form.$('spellcheckingLanguage')} /> */} | 157 | {/* <Select field={form.$('spellcheckingLanguage')} /> */} |
163 | 158 | ||
164 | {/* Updates */} | 159 | {/* Updates */} |
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 191ef447b..3c52152b1 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -26,6 +26,10 @@ const messages = defineMessages({ | |||
26 | id: 'settings.service.form.enableNotification', | 26 | id: 'settings.service.form.enableNotification', |
27 | defaultMessage: '!!!Enable Notifications', | 27 | defaultMessage: '!!!Enable Notifications', |
28 | }, | 28 | }, |
29 | enableBadge: { | ||
30 | id: 'settings.service.form.enableBadge', | ||
31 | defaultMessage: '!!!Show unread message badges', | ||
32 | }, | ||
29 | enableAudio: { | 33 | enableAudio: { |
30 | id: 'settings.service.form.enableAudio', | 34 | id: 'settings.service.form.enableAudio', |
31 | defaultMessage: '!!!Enable audio', | 35 | defaultMessage: '!!!Enable audio', |
@@ -88,6 +92,11 @@ export default class EditServiceScreen extends Component { | |||
88 | value: service.isNotificationEnabled, | 92 | value: service.isNotificationEnabled, |
89 | default: true, | 93 | default: true, |
90 | }, | 94 | }, |
95 | isBadgeEnabled: { | ||
96 | label: intl.formatMessage(messages.enableBadge), | ||
97 | value: service.isBadgeEnabled, | ||
98 | default: true, | ||
99 | }, | ||
91 | isMuted: { | 100 | isMuted: { |
92 | label: intl.formatMessage(messages.enableAudio), | 101 | label: intl.formatMessage(messages.enableAudio), |
93 | value: !service.isMuted, | 102 | value: !service.isMuted, |
@@ -118,11 +127,22 @@ export default class EditServiceScreen extends Component { | |||
118 | }); | 127 | }); |
119 | } | 128 | } |
120 | 129 | ||
130 | // More fine grained and use case specific validation rules | ||
121 | if (recipe.hasTeamId && recipe.hasCustomUrl) { | 131 | if (recipe.hasTeamId && recipe.hasCustomUrl) { |
122 | config.fields.team.validate = [oneRequired(['team', 'customUrl'])]; | 132 | config.fields.team.validate = [oneRequired(['team', 'customUrl'])]; |
123 | config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])]; | 133 | config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])]; |
124 | } | 134 | } |
125 | 135 | ||
136 | // If a service can be hosted and has a teamId or customUrl | ||
137 | if (recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl)) { | ||
138 | if (config.fields.team) { | ||
139 | config.fields.team.validate = []; | ||
140 | } | ||
141 | if (config.fields.customUrl) { | ||
142 | config.fields.customUrl.validate = [url]; | ||
143 | } | ||
144 | } | ||
145 | |||
126 | if (recipe.hasIndirectMessages) { | 146 | if (recipe.hasIndirectMessages) { |
127 | Object.assign(config.fields, { | 147 | Object.assign(config.fields, { |
128 | isIndirectMessageBadgeEnabled: { | 148 | isIndirectMessageBadgeEnabled: { |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 48b408e59..567537d75 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -110,6 +110,7 @@ | |||
110 | "settings.service.form.editServiceHeadline": "Edit {name}", | 110 | "settings.service.form.editServiceHeadline": "Edit {name}", |
111 | "settings.service.form.tabHosted": "Hosted", | 111 | "settings.service.form.tabHosted": "Hosted", |
112 | "settings.service.form.tabOnPremise": "Self hosted ⭐️", | 112 | "settings.service.form.tabOnPremise": "Self hosted ⭐️", |
113 | "settings.service.form.useHostedService": "Use the hosted {name} service.", | ||
113 | "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.", | 114 | "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.", |
114 | "settings.service.form.customUrlPremiumInfo": "To add self hosted services, you need a Franz Premium Supporter Account.", | 115 | "settings.service.form.customUrlPremiumInfo": "To add self hosted services, you need a Franz Premium Supporter Account.", |
115 | "settings.service.form.customUrlUpgradeAccount": "Upgrade your account", | 116 | "settings.service.form.customUrlUpgradeAccount": "Upgrade your account", |
@@ -117,11 +118,15 @@ | |||
117 | "settings.service.form.name": "Name", | 118 | "settings.service.form.name": "Name", |
118 | "settings.service.form.enableService": "Enable service", | 119 | "settings.service.form.enableService": "Enable service", |
119 | "settings.service.form.enableNotification": "Enable notifications", | 120 | "settings.service.form.enableNotification": "Enable notifications", |
121 | "settings.service.form.enableBadge": "Show unread message badges", | ||
120 | "settings.service.form.team": "Team", | 122 | "settings.service.form.team": "Team", |
121 | "settings.service.form.customUrl": "Custom server", | 123 | "settings.service.form.customUrl": "Custom server", |
122 | "settings.service.form.indirectMessages": "Show message badge for all new messages", | 124 | "settings.service.form.indirectMessages": "Show message badge for all new messages", |
123 | "settings.service.form.enableAudio": "Enable audio", | 125 | "settings.service.form.enableAudio": "Enable audio", |
124 | "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", | 126 | "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", |
127 | "settings.service.form.headlineNotifications": "Notifications", | ||
128 | "settings.service.form.headlineBadges": "Unread message badges", | ||
129 | "settings.service.form.headlineGeneral": "General", | ||
125 | "settings.service.error.headline": "Error", | 130 | "settings.service.error.headline": "Error", |
126 | "settings.service.error.goBack": "Back to services", | 131 | "settings.service.error.goBack": "Back to services", |
127 | "settings.service.error.message": "Could not load service recipe.", | 132 | "settings.service.error.message": "Could not load service recipe.", |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index d9c30466b..d01666d49 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -167,7 +167,7 @@ export default class FranzMenu { | |||
167 | label: 'Settings', | 167 | label: 'Settings', |
168 | accelerator: 'CmdOrCtrl+,', | 168 | accelerator: 'CmdOrCtrl+,', |
169 | click: () => { | 169 | click: () => { |
170 | this.actions.ui.openSettings({ path: '' }); | 170 | this.actions.ui.openSettings({ path: 'app' }); |
171 | }, | 171 | }, |
172 | }, | 172 | }, |
173 | { | 173 | { |
diff --git a/src/models/Recipe.js b/src/models/Recipe.js index 9971df77c..1fc23ac89 100644 --- a/src/models/Recipe.js +++ b/src/models/Recipe.js | |||
@@ -1,10 +1,11 @@ | |||
1 | import emailParser from 'address-rfc2822'; | 1 | import emailParser from 'address-rfc2822'; |
2 | import semver from 'semver'; | ||
2 | 3 | ||
3 | export default class Recipe { | 4 | export default class Recipe { |
4 | id = ''; | 5 | id = ''; |
5 | name = ''; | 6 | name = ''; |
6 | description = ''; | 7 | description = ''; |
7 | version = '1.0'; | 8 | version = ''; |
8 | path = ''; | 9 | path = ''; |
9 | 10 | ||
10 | serviceURL = ''; | 11 | serviceURL = ''; |
@@ -15,6 +16,7 @@ export default class Recipe { | |||
15 | hasTeamId = false; | 16 | hasTeamId = false; |
16 | hasPredefinedUrl = false; | 17 | hasPredefinedUrl = false; |
17 | hasCustomUrl = false; | 18 | hasCustomUrl = false; |
19 | hasHostedOption = false; | ||
18 | urlInputPrefix = ''; | 20 | urlInputPrefix = ''; |
19 | urlInputSuffix = ''; | 21 | urlInputSuffix = ''; |
20 | 22 | ||
@@ -30,6 +32,10 @@ export default class Recipe { | |||
30 | throw Error(`Recipe '${data.name}' requires Id`); | 32 | throw Error(`Recipe '${data.name}' requires Id`); |
31 | } | 33 | } |
32 | 34 | ||
35 | if (!semver.valid(data.version)) { | ||
36 | throw Error(`Version ${data.version} of recipe '${data.name}' is not a valid semver version`); | ||
37 | } | ||
38 | |||
33 | this.id = data.id || this.id; | 39 | this.id = data.id || this.id; |
34 | this.name = data.name || this.name; | 40 | this.name = data.name || this.name; |
35 | this.rawAuthor = data.author || this.author; | 41 | this.rawAuthor = data.author || this.author; |
@@ -45,6 +51,7 @@ export default class Recipe { | |||
45 | this.hasTeamId = data.config.hasTeamId || this.hasTeamId; | 51 | this.hasTeamId = data.config.hasTeamId || this.hasTeamId; |
46 | this.hasPredefinedUrl = data.config.hasPredefinedUrl || this.hasPredefinedUrl; | 52 | this.hasPredefinedUrl = data.config.hasPredefinedUrl || this.hasPredefinedUrl; |
47 | this.hasCustomUrl = data.config.hasCustomUrl || this.hasCustomUrl; | 53 | this.hasCustomUrl = data.config.hasCustomUrl || this.hasCustomUrl; |
54 | this.hasHostedOption = data.config.hasHostedOption || this.hasHostedOption; | ||
48 | 55 | ||
49 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; | 56 | this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix; |
50 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; | 57 | this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix; |
diff --git a/src/models/Service.js b/src/models/Service.js index 958e4b11e..0b19440e7 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -22,6 +22,7 @@ export default class Service { | |||
22 | @observable team = ''; | 22 | @observable team = ''; |
23 | @observable customUrl = ''; | 23 | @observable customUrl = ''; |
24 | @observable isNotificationEnabled = true; | 24 | @observable isNotificationEnabled = true; |
25 | @observable isBadgeEnabled = true; | ||
25 | @observable isIndirectMessageBadgeEnabled = true; | 26 | @observable isIndirectMessageBadgeEnabled = true; |
26 | @observable customIconUrl = ''; | 27 | @observable customIconUrl = ''; |
27 | @observable hasCrashed = false; | 28 | @observable hasCrashed = false; |
@@ -52,6 +53,9 @@ export default class Service { | |||
52 | this.isNotificationEnabled = data.isNotificationEnabled !== undefined | 53 | this.isNotificationEnabled = data.isNotificationEnabled !== undefined |
53 | ? data.isNotificationEnabled : this.isNotificationEnabled; | 54 | ? data.isNotificationEnabled : this.isNotificationEnabled; |
54 | 55 | ||
56 | this.isBadgeEnabled = data.isBadgeEnabled !== undefined | ||
57 | ? data.isBadgeEnabled : this.isBadgeEnabled; | ||
58 | |||
55 | this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined | 59 | this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined |
56 | ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled; | 60 | ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled; |
57 | 61 | ||
diff --git a/src/models/Settings.js b/src/models/Settings.js index 35bfe0d05..ca44da258 100644 --- a/src/models/Settings.js +++ b/src/models/Settings.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import { observable } from 'mobx'; | 1 | import { observable, extendObservable } from 'mobx'; |
2 | import { DEFAULT_APP_SETTINGS } from '../config'; | 2 | import { DEFAULT_APP_SETTINGS } from '../config'; |
3 | 3 | ||
4 | export default class Settings { | 4 | export default class Settings { |
@@ -17,4 +17,8 @@ export default class Settings { | |||
17 | constructor(data) { | 17 | constructor(data) { |
18 | Object.assign(this, data); | 18 | Object.assign(this, data); |
19 | } | 19 | } |
20 | |||
21 | update(data) { | ||
22 | extendObservable(this, data); | ||
23 | } | ||
20 | } | 24 | } |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index b04aafd78..66f37af26 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -297,7 +297,7 @@ export default class ServicesStore extends Store { | |||
297 | }); | 297 | }); |
298 | } else if (channel === 'notification') { | 298 | } else if (channel === 'notification') { |
299 | const options = args[0].options; | 299 | const options = args[0].options; |
300 | if (service.recipe.hasNotificationSound || service.isMuted) { | 300 | if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.isAppMuted) { |
301 | Object.assign(options, { | 301 | Object.assign(options, { |
302 | silent: true, | 302 | silent: true, |
303 | }); | 303 | }); |
@@ -491,13 +491,13 @@ export default class ServicesStore extends Store { | |||
491 | const showMessageBadgeWhenMuted = this.stores.settings.all.showMessageBadgeWhenMuted; | 491 | const showMessageBadgeWhenMuted = this.stores.settings.all.showMessageBadgeWhenMuted; |
492 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; | 492 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; |
493 | 493 | ||
494 | const unreadDirectMessageCount = this.enabled | 494 | const unreadDirectMessageCount = this.allDisplayed |
495 | .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted) | 495 | .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) |
496 | .map(s => s.unreadDirectMessageCount) | 496 | .map(s => s.unreadDirectMessageCount) |
497 | .reduce((a, b) => a + b, 0); | 497 | .reduce((a, b) => a + b, 0); |
498 | 498 | ||
499 | const unreadIndirectMessageCount = this.enabled | 499 | const unreadIndirectMessageCount = this.allDisplayed |
500 | .filter(s => (showMessageBadgeWhenMuted || s.isIndirectMessageBadgeEnabled) && showMessageBadgesEvenWhenMuted) | 500 | .filter(s => (showMessageBadgeWhenMuted || s.isIndirectMessageBadgeEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) |
501 | .map(s => s.unreadIndirectMessageCount) | 501 | .map(s => s.unreadIndirectMessageCount) |
502 | .reduce((a, b) => a + b, 0); | 502 | .reduce((a, b) => a + b, 0); |
503 | 503 | ||
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index 33473f16d..da99a720f 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -26,7 +26,7 @@ export default class SettingsStore extends Store { | |||
26 | } | 26 | } |
27 | 27 | ||
28 | @computed get all() { | 28 | @computed get all() { |
29 | return this.allSettingsRequest.result || new SettingsModel(); | 29 | return new SettingsModel(this.allSettingsRequest.result); |
30 | } | 30 | } |
31 | 31 | ||
32 | @action async _update({ settings }) { | 32 | @action async _update({ settings }) { |
diff --git a/src/styles/content-tabs.scss b/src/styles/content-tabs.scss index aa3c8594b..47dfea2c4 100644 --- a/src/styles/content-tabs.scss +++ b/src/styles/content-tabs.scss | |||
@@ -12,15 +12,17 @@ | |||
12 | flex: 1; | 12 | flex: 1; |
13 | // border: 1px solid $theme-gray-lightest; | 13 | // border: 1px solid $theme-gray-lightest; |
14 | color: $theme-gray-dark; | 14 | color: $theme-gray-dark; |
15 | background: $theme-gray-lightest; | 15 | background: linear-gradient($theme-gray-lightest 80%, darken($theme-gray-lightest, 3%)); |
16 | border-bottom: 1px solid $theme-gray-lighter; | 16 | border-right: 1px solid $theme-gray-lighter; |
17 | box-shadow: inset 0px -3px 10px rgba(black, 0.05); | 17 | transition: background $theme-transition-time; |
18 | transition: all $theme-transition-time; | 18 | |
19 | &:last-of-type { | ||
20 | border-right: 0; | ||
21 | } | ||
19 | 22 | ||
20 | &.is-active { | 23 | &.is-active { |
21 | background: $theme-brand-primary; | 24 | background: $theme-brand-primary; |
22 | color: #FFF; | 25 | color: #FFF; |
23 | border-bottom: 1px solid $theme-brand-primary; | ||
24 | box-shadow: none; | 26 | box-shadow: none; |
25 | } | 27 | } |
26 | } | 28 | } |
diff --git a/src/styles/input.scss b/src/styles/input.scss index 814dce5f8..7042f56e8 100644 --- a/src/styles/input.scss +++ b/src/styles/input.scss | |||
@@ -47,6 +47,10 @@ | |||
47 | padding: 8px; | 47 | padding: 8px; |
48 | // font-size: 18px; | 48 | // font-size: 18px; |
49 | color: $theme-gray; | 49 | color: $theme-gray; |
50 | |||
51 | &::placeholder { | ||
52 | color: lighten($theme-gray-light, 10%); | ||
53 | } | ||
50 | } | 54 | } |
51 | 55 | ||
52 | .franz-form__input-prefix, | 56 | .franz-form__input-prefix, |
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 73cef0813..b29ed5468 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -151,8 +151,23 @@ | |||
151 | } | 151 | } |
152 | } | 152 | } |
153 | 153 | ||
154 | .settings__options { | 154 | &__options { |
155 | margin-top: 30px; | 155 | margin-top: 20px; |
156 | } | ||
157 | |||
158 | &__settings-group { | ||
159 | margin-top: 10px; | ||
160 | |||
161 | h3 { | ||
162 | font-weight: bold; | ||
163 | margin: 25px 0 15px; | ||
164 | color: $theme-gray-light; | ||
165 | letter-spacing: -0.1px; | ||
166 | |||
167 | &:first-of-type { | ||
168 | margin-top: 0; | ||
169 | } | ||
170 | } | ||
156 | } | 171 | } |
157 | 172 | ||
158 | .settings__message { | 173 | .settings__message { |
@@ -173,10 +188,6 @@ | |||
173 | margin: -10px 0 20px 55px;; | 188 | margin: -10px 0 20px 55px;; |
174 | font-size: 12px; | 189 | font-size: 12px; |
175 | color: $theme-gray-light; | 190 | color: $theme-gray-light; |
176 | |||
177 | &:last-of-type { | ||
178 | margin-bottom: 30px; | ||
179 | } | ||
180 | } | 191 | } |
181 | 192 | ||
182 | .settings__controls { | 193 | .settings__controls { |
diff --git a/src/webview/plugin.js b/src/webview/plugin.js index e2daf09dd..9903ee07a 100644 --- a/src/webview/plugin.js +++ b/src/webview/plugin.js | |||
@@ -1,13 +1,13 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker'; | ||
2 | import path from 'path'; | 3 | import path from 'path'; |
3 | 4 | ||
5 | import { isDevMode } from '../environment'; | ||
4 | import RecipeWebview from './lib/RecipeWebview'; | 6 | import RecipeWebview from './lib/RecipeWebview'; |
5 | 7 | ||
6 | import Spellchecker from './spellchecker.js'; | 8 | import Spellchecker from './spellchecker.js'; |
7 | import './notifications.js'; | 9 | import './notifications.js'; |
8 | 10 | ||
9 | const spellchecker = new Spellchecker(); | ||
10 | |||
11 | ipcRenderer.on('initializeRecipe', (e, data) => { | 11 | ipcRenderer.on('initializeRecipe', (e, data) => { |
12 | const modulePath = path.join(data.recipe.path, 'webview.js'); | 12 | const modulePath = path.join(data.recipe.path, 'webview.js'); |
13 | // Delete module from cache | 13 | // Delete module from cache |
@@ -20,20 +20,22 @@ ipcRenderer.on('initializeRecipe', (e, data) => { | |||
20 | } | 20 | } |
21 | }); | 21 | }); |
22 | 22 | ||
23 | const spellchecker = new Spellchecker(); | ||
24 | spellchecker.initialize(); | ||
25 | |||
26 | const contextMenuBuilder = new ContextMenuBuilder(spellchecker.handler, null, isDevMode); | ||
27 | |||
28 | new ContextMenuListener((info) => { // eslint-disable-line | ||
29 | contextMenuBuilder.showPopupMenu(info); | ||
30 | }); | ||
31 | |||
23 | ipcRenderer.on('settings-update', (e, data) => { | 32 | ipcRenderer.on('settings-update', (e, data) => { |
24 | if (data.enableSpellchecking) { | 33 | console.log('settings-update', data); |
25 | if (!spellchecker.isEnabled) { | 34 | spellchecker.toggleSpellchecker(data.enableSpellchecking); |
26 | spellchecker.enable(); | ||
27 | |||
28 | // TODO: this does not work yet, needs more testing | ||
29 | // if (data.spellcheckingLanguage !== 'auto') { | ||
30 | // console.log('set spellchecking language to', data.spellcheckingLanguage); | ||
31 | // spellchecker.switchLanguage(data.spellcheckingLanguage); | ||
32 | // } | ||
33 | } | ||
34 | } | ||
35 | }); | 35 | }); |
36 | 36 | ||
37 | // initSpellche | ||
38 | |||
37 | document.addEventListener('DOMContentLoaded', () => { | 39 | document.addEventListener('DOMContentLoaded', () => { |
38 | ipcRenderer.sendToHost('hello'); | 40 | ipcRenderer.sendToHost('hello'); |
39 | }, false); | 41 | }, false); |
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 5beb77e03..a504a4039 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js | |||
@@ -1,30 +1,63 @@ | |||
1 | import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker'; | 1 | import { SpellCheckHandler } from 'electron-spellchecker'; |
2 | 2 | ||
3 | import { isMac } from '../environment'; | 3 | import { isMac } from '../environment'; |
4 | 4 | ||
5 | export default class Spellchecker { | 5 | export default class Spellchecker { |
6 | isEnabled = false; | 6 | isInitialized = false; |
7 | spellchecker = null; | 7 | handler = null; |
8 | initRetries = 0; | ||
9 | DOMCheckInterval = null; | ||
10 | |||
11 | get inputs() { | ||
12 | return document.querySelectorAll('input[type="text"], [contenteditable="true"], textarea'); | ||
13 | } | ||
14 | |||
15 | initialize() { | ||
16 | this.handler = new SpellCheckHandler(); | ||
8 | 17 | ||
9 | enable() { | ||
10 | this.spellchecker = new SpellCheckHandler(); | ||
11 | if (!isMac) { | 18 | if (!isMac) { |
12 | this.spellchecker.attachToInput(); | 19 | this.attach(); |
13 | this.spellchecker.switchLanguage(navigator.language); | 20 | } else { |
21 | this.isInitialized = true; | ||
22 | } | ||
23 | } | ||
24 | |||
25 | attach() { | ||
26 | let initFailed = false; | ||
27 | |||
28 | if (this.initRetries > 3) { | ||
29 | console.error('Could not initialize spellchecker'); | ||
30 | return; | ||
14 | } | 31 | } |
15 | 32 | ||
16 | const contextMenuBuilder = new ContextMenuBuilder(this.spellchecker); | 33 | try { |
34 | this.handler.attachToInput(); | ||
35 | this.handler.switchLanguage(navigator.language); | ||
36 | } catch (err) { | ||
37 | initFailed = true; | ||
38 | this.initRetries = +1; | ||
39 | setTimeout(() => { this.attach(); console.warn('Spellchecker init failed, trying again in 5s'); }, 5000); | ||
40 | } | ||
17 | 41 | ||
18 | new ContextMenuListener((info) => { // eslint-disable-line | 42 | if (!initFailed) { |
19 | contextMenuBuilder.showPopupMenu(info); | 43 | this.isInitialized = true; |
44 | } | ||
45 | } | ||
46 | |||
47 | toggleSpellchecker(enable = false) { | ||
48 | this.inputs.forEach((input) => { | ||
49 | input.setAttribute('spellcheck', enable); | ||
20 | }); | 50 | }); |
51 | |||
52 | this.intervalHandler(enable); | ||
21 | } | 53 | } |
22 | 54 | ||
23 | // TODO: this does not work yet, needs more testing | 55 | intervalHandler(enable) { |
24 | // switchLanguage(language) { | 56 | clearInterval(this.DOMCheckInterval); |
25 | // if (language !== 'auto') { | 57 | |
26 | // this.spellchecker.switchLanguage(language); | 58 | if (enable) { |
27 | // } | 59 | this.DOMCheckInterval = setInterval(() => this.toggleSpellchecker(enable), 30000); |
28 | // } | 60 | } |
61 | } | ||
29 | } | 62 | } |
30 | 63 | ||