diff options
author | kytwb <412895+kytwb@users.noreply.github.com> | 2021-06-12 19:51:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-12 19:51:28 +0200 |
commit | b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb (patch) | |
tree | aed0253b61ca035f3388a7d6a369ffe75f195a58 | |
parent | Bypassed code signing since that is also incorrect in GH settings. (diff) | |
download | ferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.tar.gz ferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.tar.zst ferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.zip |
Fix active Todos service behaviour (#1481)
* Return false instead of null in isTodosServiceAdded
* Resolve from TODOS_RECIPES_ID instead of hardcoded TODOS_RECIPE_ID
* Fix TodosWebview width toggling when isTodosServiceActive
* Add more todo service recipe IDs
* Refactor todos state management
* Moved todos service URL and recipe ID computation logic to todos/store
* Simplified TodosWebview by delegating to the store for the URL and
removing the (unused) payment logic
* Made the todos service computation logic in the Service model depend
on the logic in todos/store
* Made ServicesStore depend on the todos service logic from the Service
model
* Todos appearance fixes
* Hide double horizontal rules if todo settings are hidden due to an
added todo service
* Hide todos panel border when the panel is hidden or expanded
* Make expanded todos panel obey sidebar width and vertical style
settings
* Make todos/store use isValidExternalURL
* Harden isValidExternalURL against malformed URLs
* Reduce todo URL string duplication in config.js
Co-authored-by: Kristóf Marussy <kristof@marussy.com>
-rw-r--r-- | src/components/services/content/Services.js | 3 | ||||
-rw-r--r-- | src/components/settings/settings/EditSettingsForm.js | 4 | ||||
-rw-r--r-- | src/config.js | 47 | ||||
-rw-r--r-- | src/containers/auth/SetupAssistantScreen.js | 8 | ||||
-rw-r--r-- | src/features/appearance/index.js | 11 | ||||
-rw-r--r-- | src/features/todos/components/TodosWebview.js | 96 | ||||
-rw-r--r-- | src/features/todos/containers/TodosScreen.js | 3 | ||||
-rw-r--r-- | src/features/todos/index.js | 1 | ||||
-rw-r--r-- | src/features/todos/store.js | 29 | ||||
-rw-r--r-- | src/helpers/url-helpers.js | 7 | ||||
-rw-r--r-- | src/i18n/locales/defaultMessages.json | 20 | ||||
-rw-r--r-- | src/i18n/messages/src/components/services/content/Services.json | 20 | ||||
-rw-r--r-- | src/models/Service.js | 9 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 11 |
14 files changed, 136 insertions, 133 deletions
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index f679eeed0..7cf02c237 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -10,7 +10,6 @@ import injectSheet from 'react-jss'; | |||
10 | import ServiceView from './ServiceView'; | 10 | import ServiceView from './ServiceView'; |
11 | import Appear from '../../ui/effects/Appear'; | 11 | import Appear from '../../ui/effects/Appear'; |
12 | import serverlessLogin from '../../../helpers/serverless-helpers'; | 12 | import serverlessLogin from '../../../helpers/serverless-helpers'; |
13 | import { TODOS_RECIPE_ID } from '../../../features/todos'; | ||
14 | 13 | ||
15 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
16 | welcome: { | 15 | welcome: { |
@@ -171,7 +170,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
171 | </div> | 170 | </div> |
172 | </Appear> | 171 | </Appear> |
173 | )} | 172 | )} |
174 | {services.filter(service => service.recipe.id !== TODOS_RECIPE_ID).map(service => ( | 173 | {services.filter(service => !service.isTodosService).map(service => ( |
175 | <ServiceView | 174 | <ServiceView |
176 | key={service.id} | 175 | key={service.id} |
177 | service={service} | 176 | service={service} |
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 8a362d3c9..b7e007186 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js | |||
@@ -390,12 +390,10 @@ export default @observer class EditSettingsForm extends Component { | |||
390 | )} | 390 | )} |
391 | </div> | 391 | </div> |
392 | )} | 392 | )} |
393 | <Hr /> | ||
393 | </> | 394 | </> |
394 | )} | 395 | )} |
395 | 396 | ||
396 | |||
397 | <Hr /> | ||
398 | |||
399 | <Toggle field={form.$('scheduledDNDEnabled')} /> | 397 | <Toggle field={form.$('scheduledDNDEnabled')} /> |
400 | {scheduledDNDEnabled && ( | 398 | {scheduledDNDEnabled && ( |
401 | <> | 399 | <> |
diff --git a/src/config.js b/src/config.js index 8930c755f..3a728b55e 100644 --- a/src/config.js +++ b/src/config.js | |||
@@ -62,19 +62,44 @@ export const SEARCH_ENGINE_URLS = { | |||
62 | [SEARCH_ENGINE_DDG]: ({ searchTerm }) => `https://duckduckgo.com/?q=${searchTerm}`, | 62 | [SEARCH_ENGINE_DDG]: ({ searchTerm }) => `https://duckduckgo.com/?q=${searchTerm}`, |
63 | }; | 63 | }; |
64 | 64 | ||
65 | export const CUSTOM_TODO_SERVICE = 'isUsingCustomTodoService'; | ||
66 | |||
67 | const TODO_TODOIST_URL = 'https://todoist.com/app'; | ||
68 | const TODO_FRANZ_TODOS_URL = 'https://app.franztodos.com'; | ||
69 | const TODO_TICKTICK_URL = 'https://ticktick.com/signin'; | ||
70 | const TODO_MSTODO_URL = 'https://todo.microsoft.com/?app#'; | ||
71 | const TODO_HABITICA_URL = 'https://habitica.com/login'; | ||
72 | const TODO_NOZBE_URL = 'https://app.nozbe.com/#login'; | ||
73 | const TODO_RTM_URL = 'https://www.rememberthemilk.com/login/'; | ||
74 | const TODO_ANYDO_URL = 'https://desktop.any.do/'; | ||
75 | const TODO_GOOGLETASKS_URL = 'https://tasks.google.com/embed/?origin=https%3A%2F%2Fcalendar.google.com&fullWidth=1'; | ||
76 | |||
77 | export const TODO_SERVICE_RECIPE_IDS = { | ||
78 | [TODO_TODOIST_URL]: 'todoist', | ||
79 | [TODO_FRANZ_TODOS_URL]: 'franz-todos', | ||
80 | [TODO_TICKTICK_URL]: 'TickTick', | ||
81 | [TODO_MSTODO_URL]: 'mstodo', | ||
82 | [TODO_HABITICA_URL]: 'habitica', | ||
83 | [TODO_ANYDO_URL]: 'anydo', | ||
84 | }; | ||
85 | |||
65 | export const TODO_APPS = { | 86 | export const TODO_APPS = { |
66 | 'https://todoist.com/app': 'Todoist', | 87 | [TODO_TODOIST_URL]: 'Todoist', |
67 | 'https://app.franztodos.com': 'Franz Todo', | 88 | [TODO_FRANZ_TODOS_URL]: 'Franz Todo', |
68 | 'https://ticktick.com/signin': 'TickTick', | 89 | [TODO_TICKTICK_URL]: 'TickTick', |
69 | 'https://todo.microsoft.com/?app#': 'Microsoft To Do', | 90 | [TODO_MSTODO_URL]: 'Microsoft To Do', |
70 | 'https://habitica.com/login': 'Habitica', | 91 | [TODO_HABITICA_URL]: 'Habitica', |
71 | 'https://app.nozbe.com/#login': 'Nozbe', | 92 | [TODO_NOZBE_URL]: 'Nozbe', |
72 | 'https://www.rememberthemilk.com/login/': 'Remember The Milk', | 93 | [TODO_RTM_URL]: 'Remember The Milk', |
73 | 'https://desktop.any.do/': 'Any.do', | 94 | [TODO_ANYDO_URL]: 'Any.do', |
74 | 'https://tasks.google.com/embed/?origin=https%3A%2F%2Fcalendar.google.com&fullWidth=1': 'Google Tasks', | 95 | [TODO_GOOGLETASKS_URL]: 'Google Tasks', |
75 | isUsingCustomTodoService: 'Other service', | 96 | [CUSTOM_TODO_SERVICE]: 'Other service', |
76 | }; | 97 | }; |
77 | 98 | ||
99 | export const DEFAULT_TODO_SERVICE = TODO_FRANZ_TODOS_URL; | ||
100 | export const DEFAULT_TODO_RECIPE_ID = TODO_SERVICE_RECIPE_IDS[DEFAULT_TODO_SERVICE]; | ||
101 | export const DEFAULT_TODO_SERVICE_NAME = TODO_APPS[DEFAULT_TODO_SERVICE]; | ||
102 | |||
78 | export const SIDEBAR_WIDTH = { | 103 | export const SIDEBAR_WIDTH = { |
79 | 35: 'Extremely slim sidebar', | 104 | 35: 'Extremely slim sidebar', |
80 | 45: 'Very slim sidebar', | 105 | 45: 'Very slim sidebar', |
@@ -123,7 +148,7 @@ export const DEFAULT_APP_SETTINGS = { | |||
123 | 148 | ||
124 | // Ferdi specific options | 149 | // Ferdi specific options |
125 | server: LIVE_FERDI_API, | 150 | server: LIVE_FERDI_API, |
126 | predefinedTodoServer: 'https://app.franztodos.com', | 151 | predefinedTodoServer: DEFAULT_TODO_SERVICE, |
127 | autohideMenuBar: false, | 152 | autohideMenuBar: false, |
128 | lockingFeatureEnabled: false, | 153 | lockingFeatureEnabled: false, |
129 | locked: false, | 154 | locked: false, |
diff --git a/src/containers/auth/SetupAssistantScreen.js b/src/containers/auth/SetupAssistantScreen.js index 960ed7e33..1c4fa6c22 100644 --- a/src/containers/auth/SetupAssistantScreen.js +++ b/src/containers/auth/SetupAssistantScreen.js | |||
@@ -3,11 +3,11 @@ import React, { Component } from 'react'; | |||
3 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
4 | import { inject, observer } from 'mobx-react'; | 4 | import { inject, observer } from 'mobx-react'; |
5 | 5 | ||
6 | import { DEFAULT_TODO_RECIPE_ID, DEFAULT_TODO_SERVICE_NAME } from '../../config'; | ||
6 | import { sleep } from '../../helpers/async-helpers'; | 7 | import { sleep } from '../../helpers/async-helpers'; |
7 | import SetupAssistant from '../../components/auth/SetupAssistant'; | 8 | import SetupAssistant from '../../components/auth/SetupAssistant'; |
8 | import ServicesStore from '../../stores/ServicesStore'; | 9 | import ServicesStore from '../../stores/ServicesStore'; |
9 | import RecipesStore from '../../stores/RecipesStore'; | 10 | import RecipesStore from '../../stores/RecipesStore'; |
10 | import { TODOS_RECIPE_ID } from '../../features/todos'; | ||
11 | import UserStore from '../../stores/UserStore'; | 11 | import UserStore from '../../stores/UserStore'; |
12 | 12 | ||
13 | export default @inject('stores', 'actions') @observer class SetupAssistantScreen extends Component { | 13 | export default @inject('stores', 'actions') @observer class SetupAssistantScreen extends Component { |
@@ -82,11 +82,11 @@ export default @inject('stores', 'actions') @observer class SetupAssistantScreen | |||
82 | await sleep(100); | 82 | await sleep(100); |
83 | } | 83 | } |
84 | 84 | ||
85 | // Add Franz ToDos | 85 | // Add todo service |
86 | await services._createService({ | 86 | await services._createService({ |
87 | recipeId: TODOS_RECIPE_ID, | 87 | recipeId: DEFAULT_TODO_RECIPE_ID, |
88 | serviceData: { | 88 | serviceData: { |
89 | name: 'Franz ToDos', | 89 | name: DEFAULT_TODO_SERVICE_NAME, |
90 | }, | 90 | }, |
91 | redirect: false, | 91 | redirect: false, |
92 | skipCleanup: true, | 92 | skipCleanup: true, |
diff --git a/src/features/appearance/index.js b/src/features/appearance/index.js index 82a0971eb..b5648250e 100644 --- a/src/features/appearance/index.js +++ b/src/features/appearance/index.js | |||
@@ -93,10 +93,10 @@ function generateServiceRibbonWidthStyle(widthStr, iconSizeStr, vertical) { | |||
93 | } | 93 | } |
94 | ` : ` | 94 | ` : ` |
95 | .sidebar { | 95 | .sidebar { |
96 | width: ${width - 2}px !important; | 96 | width: ${width}px !important; |
97 | } | 97 | } |
98 | .tab-item { | 98 | .tab-item { |
99 | width: ${width - 2}px !important; | 99 | width: ${width}px !important; |
100 | height: ${width - 5 + iconSize}px !important; | 100 | height: ${width - 5 + iconSize}px !important; |
101 | } | 101 | } |
102 | .tab-item .tab-item__icon { | 102 | .tab-item .tab-item__icon { |
@@ -105,6 +105,9 @@ function generateServiceRibbonWidthStyle(widthStr, iconSizeStr, vertical) { | |||
105 | .sidebar__button { | 105 | .sidebar__button { |
106 | font-size: ${width / 3}px !important; | 106 | font-size: ${width / 3}px !important; |
107 | } | 107 | } |
108 | .todos__todos-panel--expanded { | ||
109 | width: calc(100% - ${300 + width}px) !important; | ||
110 | } | ||
108 | `; | 111 | `; |
109 | } | 112 | } |
110 | 113 | ||
@@ -156,6 +159,10 @@ function generateVerticalStyle(widthStr, alwaysShowWorkspaces) { | |||
156 | .workspaces-drawer { | 159 | .workspaces-drawer { |
157 | maring-top: -${sidebarWidthStr} !important; | 160 | maring-top: -${sidebarWidthStr} !important; |
158 | } | 161 | } |
162 | |||
163 | .todos__todos-panel--expanded { | ||
164 | width: calc(100% - 300px) !important; | ||
165 | } | ||
159 | `; | 166 | `; |
160 | } | 167 | } |
161 | 168 | ||
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index 634ec4caa..03bb5efe8 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -1,47 +1,12 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, inject } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import injectSheet from 'react-jss'; | 4 | import injectSheet from 'react-jss'; |
5 | import Webview from 'react-electron-web-view'; | 5 | import Webview from 'react-electron-web-view'; |
6 | import { Icon } from '@meetfranz/ui'; | ||
7 | import { defineMessages, intlShape } from 'react-intl'; | ||
8 | import classnames from 'classnames'; | 6 | import classnames from 'classnames'; |
9 | 7 | ||
10 | import { mdiCheckAll } from '@mdi/js'; | ||
11 | import SettingsStore from '../../../stores/SettingsStore'; | ||
12 | |||
13 | import Appear from '../../../components/ui/effects/Appear'; | ||
14 | import UpgradeButton from '../../../components/ui/UpgradeButton'; | ||
15 | import { TODOS_PARTITION_ID } from '..'; | 8 | import { TODOS_PARTITION_ID } from '..'; |
16 | 9 | ||
17 | // NOTE: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url | ||
18 | function validURL(str) { | ||
19 | let url; | ||
20 | |||
21 | try { | ||
22 | url = new URL(str); | ||
23 | } catch (_) { | ||
24 | return false; | ||
25 | } | ||
26 | |||
27 | return url.protocol === 'http:' || url.protocol === 'https:'; | ||
28 | } | ||
29 | |||
30 | const messages = defineMessages({ | ||
31 | premiumInfo: { | ||
32 | id: 'feature.todos.premium.info', | ||
33 | defaultMessage: '!!!Franz Todos are available to premium users now!', | ||
34 | }, | ||
35 | upgradeCTA: { | ||
36 | id: 'feature.todos.premium.upgrade', | ||
37 | defaultMessage: '!!!Upgrade Account', | ||
38 | }, | ||
39 | rolloutInfo: { | ||
40 | id: 'feature.todos.premium.rollout', | ||
41 | defaultMessage: '!!!Everyone else will have to wait a little longer.', | ||
42 | }, | ||
43 | }); | ||
44 | |||
45 | const styles = theme => ({ | 10 | const styles = theme => ({ |
46 | root: { | 11 | root: { |
47 | background: theme.colorBackground, | 12 | background: theme.colorBackground, |
@@ -96,10 +61,14 @@ const styles = theme => ({ | |||
96 | position: 'absolute', | 61 | position: 'absolute', |
97 | right: 0, | 62 | right: 0, |
98 | zIndex: 0, | 63 | zIndex: 0, |
64 | borderLeftWidth: 0, | ||
65 | }, | ||
66 | hidden: { | ||
67 | borderLeftWidth: 0, | ||
99 | }, | 68 | }, |
100 | }); | 69 | }); |
101 | 70 | ||
102 | @injectSheet(styles) @inject('stores') @observer | 71 | @injectSheet(styles) @observer |
103 | class TodosWebview extends Component { | 72 | class TodosWebview extends Component { |
104 | static propTypes = { | 73 | static propTypes = { |
105 | classes: PropTypes.object.isRequired, | 74 | classes: PropTypes.object.isRequired, |
@@ -111,10 +80,8 @@ class TodosWebview extends Component { | |||
111 | width: PropTypes.number.isRequired, | 80 | width: PropTypes.number.isRequired, |
112 | minWidth: PropTypes.number.isRequired, | 81 | minWidth: PropTypes.number.isRequired, |
113 | userAgent: PropTypes.string.isRequired, | 82 | userAgent: PropTypes.string.isRequired, |
114 | isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired, | 83 | todoUrl: PropTypes.string.isRequired, |
115 | stores: PropTypes.shape({ | 84 | isTodoUrlValid: PropTypes.bool.isRequired, |
116 | settings: PropTypes.instanceOf(SettingsStore).isRequired, | ||
117 | }).isRequired, | ||
118 | }; | 85 | }; |
119 | 86 | ||
120 | state = { | 87 | state = { |
@@ -122,10 +89,6 @@ class TodosWebview extends Component { | |||
122 | width: 300, | 89 | width: 300, |
123 | }; | 90 | }; |
124 | 91 | ||
125 | static contextTypes = { | ||
126 | intl: intlShape, | ||
127 | }; | ||
128 | |||
129 | componentWillMount() { | 92 | componentWillMount() { |
130 | const { width } = this.props; | 93 | const { width } = this.props; |
131 | 94 | ||
@@ -209,8 +172,8 @@ class TodosWebview extends Component { | |||
209 | isTodosServiceActive, | 172 | isTodosServiceActive, |
210 | isVisible, | 173 | isVisible, |
211 | userAgent, | 174 | userAgent, |
212 | isTodosIncludedInCurrentPlan, | 175 | todoUrl, |
213 | stores, | 176 | isTodoUrlValid, |
214 | } = this.props; | 177 | } = this.props; |
215 | 178 | ||
216 | const { | 179 | const { |
@@ -219,29 +182,20 @@ class TodosWebview extends Component { | |||
219 | isDragging, | 182 | isDragging, |
220 | } = this.state; | 183 | } = this.state; |
221 | 184 | ||
222 | const { intl } = this.context; | 185 | let displayedWidth = isVisible ? width : 0; |
223 | 186 | if (isTodosServiceActive) { | |
224 | const isUsingPredefinedTodoServer = stores.settings.all.app.predefinedTodoServer !== 'isUsingCustomTodoService'; | 187 | displayedWidth = null; |
225 | const todoUrl = isUsingPredefinedTodoServer | ||
226 | ? stores.settings.all.app.predefinedTodoServer | ||
227 | : stores.settings.all.app.customTodoServer; | ||
228 | let isTodoUrlValid = true; | ||
229 | if (isUsingPredefinedTodoServer === false) { | ||
230 | isTodoUrlValid = validURL(todoUrl); | ||
231 | } | 188 | } |
232 | 189 | ||
233 | const todosPanelStyle = { | ||
234 | width: isVisible ? width : 0, | ||
235 | borderLeftWidth: isVisible ? '1px' : 0, | ||
236 | }; | ||
237 | |||
238 | return ( | 190 | return ( |
239 | <div | 191 | <div |
240 | className={classnames({ | 192 | className={classnames({ |
241 | [classes.root]: true, | 193 | [classes.root]: true, |
242 | [classes.isTodosServiceActive]: isTodosServiceActive, | 194 | [classes.isTodosServiceActive]: isTodosServiceActive, |
195 | 'todos__todos-panel--expanded': isTodosServiceActive, | ||
196 | [classes.hidden]: !isVisible, | ||
243 | })} | 197 | })} |
244 | style={todosPanelStyle} | 198 | style={{ width: displayedWidth }} |
245 | onMouseUp={() => this.stopResize()} | 199 | onMouseUp={() => this.stopResize()} |
246 | ref={(node) => { this.node = node; }} | 200 | ref={(node) => { this.node = node; }} |
247 | id="todos-panel" | 201 | id="todos-panel" |
@@ -257,9 +211,7 @@ class TodosWebview extends Component { | |||
257 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad | 211 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad |
258 | /> | 212 | /> |
259 | )} | 213 | )} |
260 | {isTodosIncludedInCurrentPlan ? ( | 214 | {isTodoUrlValid && ( |
261 | isTodoUrlValid | ||
262 | && ( | ||
263 | <Webview | 215 | <Webview |
264 | className={classes.webview} | 216 | className={classes.webview} |
265 | onDidAttach={() => { | 217 | onDidAttach={() => { |
@@ -273,20 +225,6 @@ class TodosWebview extends Component { | |||
273 | useragent={userAgent} | 225 | useragent={userAgent} |
274 | src={todoUrl} | 226 | src={todoUrl} |
275 | /> | 227 | /> |
276 | ) | ||
277 | ) : ( | ||
278 | <Appear> | ||
279 | <div className={classes.premiumContainer}> | ||
280 | <Icon icon={mdiCheckAll} className={classes.premiumIcon} size={4} /> | ||
281 | <p>{intl.formatMessage(messages.premiumInfo)}</p> | ||
282 | <p>{intl.formatMessage(messages.rolloutInfo)}</p> | ||
283 | <UpgradeButton | ||
284 | className={classes.premiumCTA} | ||
285 | gaEventInfo={{ category: 'Todos', event: 'upgrade' }} | ||
286 | short | ||
287 | /> | ||
288 | </div> | ||
289 | </Appear> | ||
290 | )} | 228 | )} |
291 | </div> | 229 | </div> |
292 | ); | 230 | ); |
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js index 631893f93..96147d5ab 100644 --- a/src/features/todos/containers/TodosScreen.js +++ b/src/features/todos/containers/TodosScreen.js | |||
@@ -28,7 +28,8 @@ class TodosScreen extends Component { | |||
28 | minWidth={TODOS_MIN_WIDTH} | 28 | minWidth={TODOS_MIN_WIDTH} |
29 | resize={width => todoActions.resize({ width })} | 29 | resize={width => todoActions.resize({ width })} |
30 | userAgent={todosStore.userAgent} | 30 | userAgent={todosStore.userAgent} |
31 | isTodosIncludedInCurrentPlan | 31 | todoUrl={todosStore.todoUrl} |
32 | isTodoUrlValid={todosStore.isTodoUrlValid} | ||
32 | /> | 33 | /> |
33 | </ErrorBoundary> | 34 | </ErrorBoundary> |
34 | ); | 35 | ); |
diff --git a/src/features/todos/index.js b/src/features/todos/index.js index ebd2c60ef..b6d13e5e1 100644 --- a/src/features/todos/index.js +++ b/src/features/todos/index.js | |||
@@ -9,7 +9,6 @@ export const DEFAULT_TODOS_WIDTH = 300; | |||
9 | export const TODOS_MIN_WIDTH = 200; | 9 | export const TODOS_MIN_WIDTH = 200; |
10 | export const DEFAULT_TODOS_VISIBLE = true; | 10 | export const DEFAULT_TODOS_VISIBLE = true; |
11 | export const DEFAULT_IS_FEATURE_ENABLED_BY_USER = true; | 11 | export const DEFAULT_IS_FEATURE_ENABLED_BY_USER = true; |
12 | export const TODOS_RECIPE_ID = 'franz-todos'; | ||
13 | export const TODOS_PARTITION_ID = 'persist:todos'; | 12 | export const TODOS_PARTITION_ID = 'persist:todos'; |
14 | 13 | ||
15 | export const TODOS_ROUTES = { | 14 | export const TODOS_ROUTES = { |
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index af8519d9c..4febd7bb1 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js | |||
@@ -7,6 +7,8 @@ import { | |||
7 | import localStorage from 'mobx-localstorage'; | 7 | import localStorage from 'mobx-localstorage'; |
8 | 8 | ||
9 | import { todoActions } from './actions'; | 9 | import { todoActions } from './actions'; |
10 | import { CUSTOM_TODO_SERVICE, TODO_SERVICE_RECIPE_IDS } from '../../config'; | ||
11 | import { isValidExternalURL } from '../../helpers/url-helpers'; | ||
10 | import { FeatureStore } from '../utils/FeatureStore'; | 12 | import { FeatureStore } from '../utils/FeatureStore'; |
11 | import { createReactions } from '../../stores/lib/Reaction'; | 13 | import { createReactions } from '../../stores/lib/Reaction'; |
12 | import { createActionBindings } from '../utils/ActionBinding'; | 14 | import { createActionBindings } from '../utils/ActionBinding'; |
@@ -21,6 +23,8 @@ import UserAgent from '../../models/UserAgent'; | |||
21 | const debug = require('debug')('Ferdi:feature:todos:store'); | 23 | const debug = require('debug')('Ferdi:feature:todos:store'); |
22 | 24 | ||
23 | export default class TodoStore extends FeatureStore { | 25 | export default class TodoStore extends FeatureStore { |
26 | @observable stores = null; | ||
27 | |||
24 | @observable isFeatureEnabled = false; | 28 | @observable isFeatureEnabled = false; |
25 | 29 | ||
26 | @observable isFeatureActive = false; | 30 | @observable isFeatureActive = false; |
@@ -59,6 +63,31 @@ export default class TodoStore extends FeatureStore { | |||
59 | return this.userAgentModel.userAgent; | 63 | return this.userAgentModel.userAgent; |
60 | } | 64 | } |
61 | 65 | ||
66 | @computed get isUsingPredefinedTodoServer() { | ||
67 | return this.stores && this.stores.settings.app.predefinedTodoServer !== CUSTOM_TODO_SERVICE; | ||
68 | } | ||
69 | |||
70 | @computed get todoUrl() { | ||
71 | if (!this.stores) { | ||
72 | return null; | ||
73 | } | ||
74 | return this.isUsingPredefinedTodoServer | ||
75 | ? this.stores.settings.app.predefinedTodoServer | ||
76 | : this.stores.settings.app.customTodoServer; | ||
77 | } | ||
78 | |||
79 | @computed get isTodoUrlValid() { | ||
80 | return !this.isUsingPredefinedTodoServer || isValidExternalURL(this.todoUrl); | ||
81 | } | ||
82 | |||
83 | @computed get todoRecipeId() { | ||
84 | if (this.isFeatureEnabledByUser && this.isUsingPredefinedTodoServer | ||
85 | && this.todoUrl in TODO_SERVICE_RECIPE_IDS) { | ||
86 | return TODO_SERVICE_RECIPE_IDS[this.todoUrl]; | ||
87 | } | ||
88 | return null; | ||
89 | } | ||
90 | |||
62 | // ========== PUBLIC API ========= // | 91 | // ========== PUBLIC API ========= // |
63 | 92 | ||
64 | @action start(stores, actions) { | 93 | @action start(stores, actions) { |
diff --git a/src/helpers/url-helpers.js b/src/helpers/url-helpers.js index 2f429a25c..972f9b79a 100644 --- a/src/helpers/url-helpers.js +++ b/src/helpers/url-helpers.js | |||
@@ -5,7 +5,12 @@ import { ALLOWED_PROTOCOLS } from '../config'; | |||
5 | const debug = require('debug')('Ferdi:Helpers:url'); | 5 | const debug = require('debug')('Ferdi:Helpers:url'); |
6 | 6 | ||
7 | export function isValidExternalURL(url) { | 7 | export function isValidExternalURL(url) { |
8 | const parsedUrl = new URL(url); | 8 | let parsedUrl; |
9 | try { | ||
10 | parsedUrl = new URL(url); | ||
11 | } catch (_) { | ||
12 | return false; | ||
13 | } | ||
9 | 14 | ||
10 | const isAllowed = ALLOWED_PROTOCOLS.includes(parsedUrl.protocol); | 15 | const isAllowed = ALLOWED_PROTOCOLS.includes(parsedUrl.protocol); |
11 | 16 | ||
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 2f998a2c2..b8ea986ce 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -1743,65 +1743,65 @@ | |||
1743 | "defaultMessage": "!!!Welcome to Ferdi", | 1743 | "defaultMessage": "!!!Welcome to Ferdi", |
1744 | "end": { | 1744 | "end": { |
1745 | "column": 3, | 1745 | "column": 3, |
1746 | "line": 19 | 1746 | "line": 18 |
1747 | }, | 1747 | }, |
1748 | "file": "src/components/services/content/Services.js", | 1748 | "file": "src/components/services/content/Services.js", |
1749 | "id": "services.welcome", | 1749 | "id": "services.welcome", |
1750 | "start": { | 1750 | "start": { |
1751 | "column": 11, | 1751 | "column": 11, |
1752 | "line": 16 | 1752 | "line": 15 |
1753 | } | 1753 | } |
1754 | }, | 1754 | }, |
1755 | { | 1755 | { |
1756 | "defaultMessage": "!!!Get started", | 1756 | "defaultMessage": "!!!Get started", |
1757 | "end": { | 1757 | "end": { |
1758 | "column": 3, | 1758 | "column": 3, |
1759 | "line": 23 | 1759 | "line": 22 |
1760 | }, | 1760 | }, |
1761 | "file": "src/components/services/content/Services.js", | 1761 | "file": "src/components/services/content/Services.js", |
1762 | "id": "services.getStarted", | 1762 | "id": "services.getStarted", |
1763 | "start": { | 1763 | "start": { |
1764 | "column": 14, | 1764 | "column": 14, |
1765 | "line": 20 | 1765 | "line": 19 |
1766 | } | 1766 | } |
1767 | }, | 1767 | }, |
1768 | { | 1768 | { |
1769 | "defaultMessage": "!!!Please login to use Ferdi.", | 1769 | "defaultMessage": "!!!Please login to use Ferdi.", |
1770 | "end": { | 1770 | "end": { |
1771 | "column": 3, | 1771 | "column": 3, |
1772 | "line": 27 | 1772 | "line": 26 |
1773 | }, | 1773 | }, |
1774 | "file": "src/components/services/content/Services.js", | 1774 | "file": "src/components/services/content/Services.js", |
1775 | "id": "services.login", | 1775 | "id": "services.login", |
1776 | "start": { | 1776 | "start": { |
1777 | "column": 9, | 1777 | "column": 9, |
1778 | "line": 24 | 1778 | "line": 23 |
1779 | } | 1779 | } |
1780 | }, | 1780 | }, |
1781 | { | 1781 | { |
1782 | "defaultMessage": "!!!Use Ferdi without an Account", | 1782 | "defaultMessage": "!!!Use Ferdi without an Account", |
1783 | "end": { | 1783 | "end": { |
1784 | "column": 3, | 1784 | "column": 3, |
1785 | "line": 31 | 1785 | "line": 30 |
1786 | }, | 1786 | }, |
1787 | "file": "src/components/services/content/Services.js", | 1787 | "file": "src/components/services/content/Services.js", |
1788 | "id": "services.serverless", | 1788 | "id": "services.serverless", |
1789 | "start": { | 1789 | "start": { |
1790 | "column": 14, | 1790 | "column": 14, |
1791 | "line": 28 | 1791 | "line": 27 |
1792 | } | 1792 | } |
1793 | }, | 1793 | }, |
1794 | { | 1794 | { |
1795 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | 1795 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", |
1796 | "end": { | 1796 | "end": { |
1797 | "column": 3, | 1797 | "column": 3, |
1798 | "line": 35 | 1798 | "line": 34 |
1799 | }, | 1799 | }, |
1800 | "file": "src/components/services/content/Services.js", | 1800 | "file": "src/components/services/content/Services.js", |
1801 | "id": "services.serverInfo", | 1801 | "id": "services.serverInfo", |
1802 | "start": { | 1802 | "start": { |
1803 | "column": 14, | 1803 | "column": 14, |
1804 | "line": 32 | 1804 | "line": 31 |
1805 | } | 1805 | } |
1806 | } | 1806 | } |
1807 | ], | 1807 | ], |
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json index 3c40b11ab..6a5eb052e 100644 --- a/src/i18n/messages/src/components/services/content/Services.json +++ b/src/i18n/messages/src/components/services/content/Services.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Welcome to Ferdi", | 4 | "defaultMessage": "!!!Welcome to Ferdi", |
5 | "file": "src/components/services/content/Services.js", | 5 | "file": "src/components/services/content/Services.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 16, | 7 | "line": 15, |
8 | "column": 11 | 8 | "column": 11 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 19, | 11 | "line": 18, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Get started", | 17 | "defaultMessage": "!!!Get started", |
18 | "file": "src/components/services/content/Services.js", | 18 | "file": "src/components/services/content/Services.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 20, | 20 | "line": 19, |
21 | "column": 14 | 21 | "column": 14 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 23, | 24 | "line": 22, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,11 @@ | |||
30 | "defaultMessage": "!!!Please login to use Ferdi.", | 30 | "defaultMessage": "!!!Please login to use Ferdi.", |
31 | "file": "src/components/services/content/Services.js", | 31 | "file": "src/components/services/content/Services.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 24, | 33 | "line": 23, |
34 | "column": 9 | 34 | "column": 9 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 27, | 37 | "line": 26, |
38 | "column": 3 | 38 | "column": 3 |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,11 +43,11 @@ | |||
43 | "defaultMessage": "!!!Use Ferdi without an Account", | 43 | "defaultMessage": "!!!Use Ferdi without an Account", |
44 | "file": "src/components/services/content/Services.js", | 44 | "file": "src/components/services/content/Services.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 28, | 46 | "line": 27, |
47 | "column": 14 | 47 | "column": 14 |
48 | }, | 48 | }, |
49 | "end": { | 49 | "end": { |
50 | "line": 31, | 50 | "line": 30, |
51 | "column": 3 | 51 | "column": 3 |
52 | } | 52 | } |
53 | }, | 53 | }, |
@@ -56,11 +56,11 @@ | |||
56 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | 56 | "defaultMessage": "!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", |
57 | "file": "src/components/services/content/Services.js", | 57 | "file": "src/components/services/content/Services.js", |
58 | "start": { | 58 | "start": { |
59 | "line": 32, | 59 | "line": 31, |
60 | "column": 14 | 60 | "column": 14 |
61 | }, | 61 | }, |
62 | "end": { | 62 | "end": { |
63 | "line": 35, | 63 | "line": 34, |
64 | "column": 3 | 64 | "column": 3 |
65 | } | 65 | } |
66 | } | 66 | } |
diff --git a/src/models/Service.js b/src/models/Service.js index 0d1dff431..d0c6a7103 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -4,7 +4,7 @@ import { webContents } from '@electron/remote'; | |||
4 | import normalizeUrl from 'normalize-url'; | 4 | import normalizeUrl from 'normalize-url'; |
5 | import path from 'path'; | 5 | import path from 'path'; |
6 | 6 | ||
7 | import { TODOS_RECIPE_ID, todosStore } from '../features/todos'; | 7 | import { todosStore } from '../features/todos'; |
8 | import { isValidExternalURL } from '../helpers/url-helpers'; | 8 | import { isValidExternalURL } from '../helpers/url-helpers'; |
9 | import UserAgent from './UserAgent'; | 9 | import UserAgent from './UserAgent'; |
10 | 10 | ||
@@ -184,8 +184,12 @@ export default class Service { | |||
184 | }; | 184 | }; |
185 | } | 185 | } |
186 | 186 | ||
187 | @computed get isTodosService() { | ||
188 | return this.recipe.id === todosStore.todoRecipeId; | ||
189 | } | ||
190 | |||
187 | get webview() { | 191 | get webview() { |
188 | if (this.recipe.id === TODOS_RECIPE_ID) { | 192 | if (this.isTodosService) { |
189 | return todosStore.webview; | 193 | return todosStore.webview; |
190 | } | 194 | } |
191 | 195 | ||
@@ -243,7 +247,6 @@ export default class Service { | |||
243 | return this.recipe.partition || `persist:service-${this.id}`; | 247 | return this.recipe.partition || `persist:service-${this.id}`; |
244 | } | 248 | } |
245 | 249 | ||
246 | |||
247 | initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { | 250 | initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { |
248 | const webviewWebContents = webContents.fromId(this.webview.getWebContentsId()); | 251 | const webviewWebContents = webContents.fromId(this.webview.getWebContentsId()); |
249 | 252 | ||
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 48b128966..752f287e8 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -21,7 +21,6 @@ import { workspaceStore } from '../features/workspaces'; | |||
21 | import { serviceLimitStore } from '../features/serviceLimit'; | 21 | import { serviceLimitStore } from '../features/serviceLimit'; |
22 | import { RESTRICTION_TYPES } from '../models/Service'; | 22 | import { RESTRICTION_TYPES } from '../models/Service'; |
23 | import { KEEP_WS_LOADED_USID } from '../config'; | 23 | import { KEEP_WS_LOADED_USID } from '../config'; |
24 | import { TODOS_RECIPE_ID } from '../features/todos'; | ||
25 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 24 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; |
26 | 25 | ||
27 | const debug = require('debug')('Ferdi:ServiceStore'); | 26 | const debug = require('debug')('Ferdi:ServiceStore'); |
@@ -279,11 +278,11 @@ export default class ServicesStore extends Store { | |||
279 | } | 278 | } |
280 | 279 | ||
281 | @computed get isTodosServiceAdded() { | 280 | @computed get isTodosServiceAdded() { |
282 | return this.allDisplayed.find(service => service.recipe.id === TODOS_RECIPE_ID && service.isEnabled) || null; | 281 | return this.allDisplayed.find(service => service.isTodosService && service.isEnabled) || false; |
283 | } | 282 | } |
284 | 283 | ||
285 | @computed get isTodosServiceActive() { | 284 | @computed get isTodosServiceActive() { |
286 | return this.active && this.active.recipe.id === TODOS_RECIPE_ID; | 285 | return this.active && this.active.isTodosService; |
287 | } | 286 | } |
288 | 287 | ||
289 | one(id) { | 288 | one(id) { |
@@ -484,7 +483,7 @@ export default class ServicesStore extends Store { | |||
484 | this._awake({ serviceId: service.id }); | 483 | this._awake({ serviceId: service.id }); |
485 | service.lastUsed = Date.now(); | 484 | service.lastUsed = Date.now(); |
486 | 485 | ||
487 | if (this.active.recipe.id === TODOS_RECIPE_ID && !this.stores.todos.settings.isFeatureEnabledByUser) { | 486 | if (this.isTodosServiceActive && !this.stores.todos.settings.isFeatureEnabledByUser) { |
488 | this.actions.todos.toggleTodosFeatureVisibility(); | 487 | this.actions.todos.toggleTodosFeatureVisibility(); |
489 | } | 488 | } |
490 | 489 | ||
@@ -718,7 +717,7 @@ export default class ServicesStore extends Store { | |||
718 | service.resetMessageCount(); | 717 | service.resetMessageCount(); |
719 | service.lostRecipeConnection = false; | 718 | service.lostRecipeConnection = false; |
720 | 719 | ||
721 | if (service.recipe.id === TODOS_RECIPE_ID) { | 720 | if (service.isTodosService) { |
722 | return this.actions.todos.reload(); | 721 | return this.actions.todos.reload(); |
723 | } | 722 | } |
724 | 723 | ||
@@ -805,7 +804,7 @@ export default class ServicesStore extends Store { | |||
805 | 804 | ||
806 | @action _openDevTools({ serviceId }) { | 805 | @action _openDevTools({ serviceId }) { |
807 | const service = this.one(serviceId); | 806 | const service = this.one(serviceId); |
808 | if (service.recipe.id === TODOS_RECIPE_ID) { | 807 | if (service.isTodosService) { |
809 | this.actions.todos.openDevTools(); | 808 | this.actions.todos.openDevTools(); |
810 | } else { | 809 | } else { |
811 | service.webview.openDevTools(); | 810 | service.webview.openDevTools(); |