diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 43797090a..130043a14 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -14,10 +14,8 @@ ### Other Changes - Added types to state/ui/actions [#1849](https://github.com/Automattic/simplenote-electron/pull/1849) - -### Other Changes - - Updated Dependencies [#1848](https://github.com/Automattic/simplenote-electron/pull/1848) +- Refactored selected note state [1851](https://github.com/Automattic/simplenote-electron/pull/1851) ## [v1.14.0] diff --git a/babel.config.js b/babel.config.js index 1e0984cf1..a11f53d45 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,6 +15,7 @@ module.exports = function(api) { const plugins = [ '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-optional-chaining', '@babel/plugin-syntax-dynamic-import', ]; const env = { diff --git a/lib/app-layout/index.tsx b/lib/app-layout/index.tsx index 1327636bf..0982476b0 100644 --- a/lib/app-layout/index.tsx +++ b/lib/app-layout/index.tsx @@ -1,6 +1,5 @@ import React, { FunctionComponent, Suspense } from 'react'; import classNames from 'classnames'; -import { get } from 'lodash'; import NoteToolbarContainer from '../note-toolbar-container'; import NoteToolbar from '../note-toolbar'; @@ -40,7 +39,6 @@ export const AppLayout: FunctionComponent = ({ isNoteInfoOpen, isNoteOpen, isSmallScreen, - note, noteBucket, revisions, onNoteClosed, @@ -76,25 +74,20 @@ export const AppLayout: FunctionComponent = ({
} + toolbar={} />
diff --git a/lib/app.tsx b/lib/app.tsx index 8f11629b7..c19c61834 100644 --- a/lib/app.tsx +++ b/lib/app.tsx @@ -34,15 +34,32 @@ import { toggleSimperiumConnectionStatus } from './state/ui/actions'; import * as settingsActions from './state/settings/actions'; +import actions from './state/actions'; +import * as S from './state'; +import * as T from './types'; + const ipc = getIpcRenderer(); +export type OwnProps = { + noteBucket: object; +}; + +export type DispatchProps = { + selectNote: (note: T.NoteEntity) => any; +}; + +export type Props = DispatchProps; + const mapStateToProps = state => ({ ...state, authIsPending: selectors.auth.authIsPending(state), isAuthorized: selectors.auth.isAuthorized(state), }); -function mapDispatchToProps(dispatch, { noteBucket }) { +const mapDispatchToProps: S.MapDispatch< + DispatchProps, + OwnProps +> = function mapDispatchToProps(dispatch, { noteBucket }) { const actionCreators = Object.assign({}, appState.actionCreators); const thenReloadNotes = action => a => { @@ -85,8 +102,9 @@ function mapDispatchToProps(dispatch, { noteBucket }) { dispatch(actionCreators.setSearchFocus({ searchFocus: true })), setSimperiumConnectionStatus: connected => dispatch(toggleSimperiumConnectionStatus(connected)), + selectNote: note => dispatch(actions.ui.selectNote(note)), }; -} +}; const isElectron = (() => { // https://github.com/atom/electron/issues/2288 @@ -102,7 +120,7 @@ export const App = connect( mapStateToProps, mapDispatchToProps )( - class extends Component { + class extends Component { static displayName = 'App'; static propTypes = { @@ -120,7 +138,6 @@ export const App = connect( openTagList: PropTypes.func.isRequired, onSignOut: PropTypes.func.isRequired, settings: PropTypes.object.isRequired, - noteBucket: PropTypes.object.isRequired, preferencesBucket: PropTypes.object.isRequired, resetAuth: PropTypes.func.isRequired, setAuthorized: PropTypes.func.isRequired, @@ -296,12 +313,14 @@ export const App = connect( onNoteRemoved = () => this.onNotesIndex(); onNoteUpdate = (noteId, data, remoteUpdateInfo = {}) => { - if (remoteUpdateInfo.patch) { - this.props.actions.noteUpdatedRemotely({ - noteBucket: this.props.noteBucket, - noteId, - data, - remoteUpdateInfo, + const { + noteBucket, + selectNote, + ui: { note }, + } = this.props; + if (remoteUpdateInfo.patch && note && noteId === note.id) { + noteBucket.get(noteId, (e, updatedNote) => { + selectNote({ ...updatedNote, hasRemoteUpdate: true }); }); } }; @@ -439,7 +458,6 @@ export const App = connect( isNoteOpen={this.state.isNoteOpen} isNoteInfoOpen={state.showNoteInfo} isSmallScreen={isSmallScreen} - note={state.note} noteBucket={noteBucket} revisions={state.revisions} onNoteClosed={() => this.setState({ isNoteOpen: false })} diff --git a/lib/dialogs/share/index.tsx b/lib/dialogs/share/index.tsx index 603903bcd..5fa2b9d1c 100644 --- a/lib/dialogs/share/index.tsx +++ b/lib/dialogs/share/index.tsx @@ -13,16 +13,25 @@ import TabPanels from '../../components/tab-panels'; import PanelTitle from '../../components/panel-title'; import ToggleControl from '../../controls/toggle'; +import * as S from '../../state'; +import * as T from '../../types'; + const shareTabs = ['collaborate', 'publish']; -export class ShareDialog extends Component { +type StateProps = { + settings: S.State['settings']; + note: T.NoteEntity | null; +}; + +type Props = StateProps; + +export class ShareDialog extends Component { static propTypes = { actions: PropTypes.object.isRequired, dialog: PropTypes.object.isRequired, noteBucket: PropTypes.object.isRequired, appState: PropTypes.object.isRequired, requestClose: PropTypes.func.isRequired, - settings: PropTypes.object.isRequired, tagBucket: PropTypes.object.isRequired, updateNoteTags: PropTypes.func.isRequired, }; @@ -30,7 +39,7 @@ export class ShareDialog extends Component { onTogglePublished = event => { this.props.actions.publishNote({ noteBucket: this.props.noteBucket, - note: this.props.appState.note, + note: this.props.note, publish: event.currentTarget.checked, }); }; @@ -38,7 +47,7 @@ export class ShareDialog extends Component { getPublishURL = url => (isEmpty(url) ? undefined : `http://simp.ly/p/${url}`); onAddCollaborator = event => { - const { note } = this.props.appState; + const { note } = this.props; const tags = (note.data && note.data.tags) || []; const collaborator = this.collaboratorElement.value.trim(); @@ -57,7 +66,7 @@ export class ShareDialog extends Component { }; onRemoveCollaborator = collaborator => { - const { note } = this.props.appState; + const { note } = this.props; let tags = (note.data && note.data.tags) || []; tags = tags.filter(tag => tag !== collaborator); @@ -67,7 +76,7 @@ export class ShareDialog extends Component { }; collaborators = () => { - const { note } = this.props.appState; + const { note } = this.props; const tags = (note.data && note.data.tags) || []; const collaborators = tags.filter(isEmailTag); @@ -86,9 +95,7 @@ export class ShareDialog extends Component { }; render() { - const { dialog, requestClose } = this.props; - const { note } = this.props.appState; - + const { dialog, note, requestClose } = this.props; const data = (note && note.data) || {}; const isPublished = includes(data.systemTags, 'published'); const publishURL = this.getPublishURL(data.publishURL); @@ -207,9 +214,12 @@ export class ShareDialog extends Component { } } -export default connect( - state => ({ - settings: state.settings, - }), - { updateNoteTags } -)(ShareDialog); +const mapStateToProps: S.MapState = ({ + settings, + ui: { note }, +}) => ({ + settings, + note, +}); + +export default connect(mapStateToProps, { updateNoteTags })(ShareDialog); diff --git a/lib/flux/app-state.ts b/lib/flux/app-state.ts index 97653b19f..2a6225823 100644 --- a/lib/flux/app-state.ts +++ b/lib/flux/app-state.ts @@ -2,7 +2,6 @@ import { get, partition, some } from 'lodash'; import update from 'react-addons-update'; import Debug from 'debug'; import ActionMap from './action-map'; -import filterNotes from '../utils/filter-notes'; import analytics from '../analytics'; import { AppState, State } from '../state'; @@ -36,7 +35,6 @@ const toggleSystemTag = ( const initialState: AppState = { editorMode: 'edit', filter: '', - selectedNoteId: null, previousIndex: -1, notes: null, tags: [], @@ -100,8 +98,6 @@ export const actionMap = new ActionMap({ showTrash: { $set: false }, listTitle: { $set: 'All Notes' }, tag: { $set: null }, - note: { $set: null }, - selectedNoteId: { $set: null }, previousIndex: { $set: -1 }, }); }, @@ -113,8 +109,6 @@ export const actionMap = new ActionMap({ showTrash: { $set: true }, listTitle: { $set: 'Trash' }, tag: { $set: null }, - note: { $set: null }, - selectedNoteId: { $set: null }, previousIndex: { $set: -1 }, }); }, @@ -139,8 +133,6 @@ export const actionMap = new ActionMap({ showTrash: { $set: false }, listTitle: { $set: tag.data.name }, tag: { $set: tag }, - note: { $set: null }, - selectedNoteId: { $set: null }, previousIndex: { $set: -1 }, }); }, @@ -297,25 +289,8 @@ export const actionMap = new ActionMap({ note.data.systemTags.includes('pinned') ); const pinSortedNotes = [...pinned, ...notPinned]; - - let selectedNote = null; - - if (state.note) { - // Load the newest version of the selected note - selectedNote = notes.find(note => note.id === state.note.id); - } else { - // If no note is selected, select either the first note - // or the previous note in the list - const filteredNotes = filterNotes(state, pinSortedNotes); - if (filteredNotes.length > 0) { - selectedNote = filteredNotes[Math.max(state.previousIndex, 0)]; - } - } - return update(state, { notes: { $set: pinSortedNotes }, - note: { $set: selectedNote }, - selectedNoteId: { $set: get(selectedNote, 'id', null) }, }); }, @@ -395,11 +370,9 @@ export const actionMap = new ActionMap({ }, }, - selectNote(state: AppState, { note, hasRemoteUpdate }) { + selectNote(state: AppState) { return update(state, { editingTags: { $set: false }, - note: { $set: { ...note, hasRemoteUpdate } }, - selectedNoteId: { $set: note.id }, revision: { $set: null }, revisions: { $set: null }, }); @@ -407,35 +380,10 @@ export const actionMap = new ActionMap({ closeNote(state: AppState, { previousIndex = -1 }) { return update(state, { - note: { $set: null }, - selectedNoteId: { $set: null }, previousIndex: { $set: previousIndex }, }); }, - noteUpdatedRemotely: { - creator({ noteBucket, noteId, data, remoteUpdateInfo = {} }) { - return (dispatch, getState: () => State) => { - const state = getState().appState; - const { patch } = remoteUpdateInfo; - - debug('noteUpdatedRemotely: %O', data); - - if (state.selectedNoteId !== noteId || !patch) { - return; - } - - dispatch( - this.action('loadAndSelectNote', { - noteBucket, - noteId, - hasRemoteUpdate: true, - }) - ); - }; - }, - }, - /** * A note is being changed from somewhere else! If the same * note is also open and being edited, we need to make sure diff --git a/lib/flux/test.ts b/lib/flux/test.ts index 9203526a9..a595cd91a 100644 --- a/lib/flux/test.ts +++ b/lib/flux/test.ts @@ -42,69 +42,3 @@ describe('appState action creators', () => { }); }); }); - -describe('appState action reducers', () => { - describe('notesLoaded', () => { - const notesLoaded = appState.actionReducers['App.notesLoaded']; - - describe('when a note is currently selected', () => { - it('should load the newest version of the selected note', () => { - const oldState = { - notes: [{}], - note: { id: 'foo', data: { content: 'old', systemTags: [] } }, - }; - const newNote = { id: 'foo', data: { content: 'new', systemTags: [] } }; - const newNoteArray = [newNote]; - const newState = notesLoaded(oldState, { - notes: newNoteArray, - }); - expect(newState.notes).toEqual(newNoteArray); - expect(newState.note).toEqual(newNote); - }); - }); - - describe('when no note is currently selected', () => { - it('should load the first filtered note if there is no valid previousIndex', () => { - const oldState = { - notes: [], - note: null, - previousIndex: -1, - }; - const firstNote = { - id: 'foo', - data: { content: 'first', systemTags: [] }, - }; - const newNoteArray = [ - firstNote, - { id: 'bar', data: { content: 'boo', systemTags: [] } }, - ]; - const newState = notesLoaded(oldState, { - notes: newNoteArray, - }); - expect(newState.notes).toEqual(newNoteArray); - expect(newState.note).toEqual(firstNote); - }); - - it('should load the previousIndex note if there is a previousIndex', () => { - const oldState = { - notes: [], - note: null, - previousIndex: 1, - }; - const previousIndexNote = { - id: 'foo', - data: { content: 'previous', systemTags: [] }, - }; - const newNoteArray = [ - { id: 'bar', data: { content: 'boo', systemTags: [] } }, - previousIndexNote, - ]; - const newState = notesLoaded(oldState, { - notes: newNoteArray, - }); - expect(newState.notes).toEqual(newNoteArray); - expect(newState.note).toEqual(previousIndexNote); - }); - }); - }); -}); diff --git a/lib/note-editor/index.tsx b/lib/note-editor/index.tsx index a1579a578..509e9de3c 100644 --- a/lib/note-editor/index.tsx +++ b/lib/note-editor/index.tsx @@ -6,7 +6,16 @@ import TagField from '../tag-field'; import { property } from 'lodash'; import NoteDetail from '../note-detail'; -export class NoteEditor extends Component { +import * as S from '../state'; +import * as T from '../types'; + +type StateProps = { + note: T.NoteEntity | null; +}; + +type Props = StateProps; + +export class NoteEditor extends Component { static displayName = 'NoteEditor'; static propTypes = { @@ -16,7 +25,6 @@ export class NoteEditor extends Component { isEditorActive: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired, filter: PropTypes.string.isRequired, - note: PropTypes.object, noteBucket: PropTypes.object.isRequired, fontSize: PropTypes.number, onNoteClosed: PropTypes.func.isRequired, @@ -161,12 +169,17 @@ export class NoteEditor extends Component { } } -const mapStateToProps = ({ appState: state, settings }) => ({ +const mapStateToProps: S.MapState = ({ + appState: state, + settings, + ui: { note }, +}) => ({ allTags: state.tags, filter: state.filter, fontSize: settings.fontSize, editorMode: state.editorMode, isEditorActive: !state.showNavigation, + note, revision: state.revision, }); diff --git a/lib/note-info/index.tsx b/lib/note-info/index.tsx index 263eded50..22de994f0 100644 --- a/lib/note-info/index.tsx +++ b/lib/note-info/index.tsx @@ -10,11 +10,19 @@ import { connect } from 'react-redux'; import appState from '../flux/app-state'; import { setMarkdown } from '../state/settings/actions'; -export class NoteInfo extends Component { +import * as S from '../state'; +import * as T from '../types'; + +type StateProps = { + isMarkdown: boolean; + isPinned: boolean; + note: T.NoteEntity | null; +}; + +type Props = StateProps; + +export class NoteInfo extends Component { static propTypes = { - isMarkdown: PropTypes.bool.isRequired, - isPinned: PropTypes.bool.isRequired, - note: PropTypes.object, markdownEnabled: PropTypes.bool, onPinNote: PropTypes.func.isRequired, onMarkdownNote: PropTypes.func.isRequired, @@ -188,15 +196,11 @@ function characterCount(content) { const { markdownNote, pinNote, toggleNoteInfo } = appState.actionCreators; -const mapStateToProps = ({ appState: state, ui: { filteredNotes } }) => { - const noteIndex = Math.max(state.previousIndex, 0); - const note = state.note ? state.note : filteredNotes[noteIndex]; - return { - note, - isMarkdown: note.data.systemTags.includes('markdown'), - isPinned: note.data.systemTags.includes('pinned'), - }; -}; +const mapStateToProps: S.MapState = ({ ui: { note } }) => ({ + note, + isMarkdown: !!note && note.data.systemTags.includes('markdown'), + isPinned: !!note && note.data.systemTags.includes('pinned'), +}); const mapDispatchToProps = (dispatch, { noteBucket }) => ({ onMarkdownNote: (note, markdown = true) => { diff --git a/lib/note-list/index.tsx b/lib/note-list/index.tsx index 581e98ab8..63a213602 100644 --- a/lib/note-list/index.tsx +++ b/lib/note-list/index.tsx @@ -29,6 +29,15 @@ import { } from './decorators'; import TagSuggestions, { getMatchingTags } from '../tag-suggestions'; +import * as S from '../state'; +import * as T from '../types'; + +type StateProps = { + selectedNoteId?: T.EntityId; +}; + +type Props = StateProps; + AutoSizer.displayName = 'AutoSizer'; List.displayName = 'List'; @@ -295,7 +304,7 @@ const createCompositeNoteList = (notes, filter, tagResultsFound) => { ]; }; -export class NoteList extends Component { +export class NoteList extends Component { static displayName = 'NoteList'; list = createRef(); @@ -306,7 +315,6 @@ export class NoteList extends Component { tagResultsFound: PropTypes.number.isRequired, isSmallScreen: PropTypes.bool.isRequired, notes: PropTypes.array.isRequired, - selectedNoteId: PropTypes.any, onNoteOpened: PropTypes.func.isRequired, onSelectNote: PropTypes.func.isRequired, onPinNote: PropTypes.func.isRequired, @@ -348,11 +356,6 @@ export class NoteList extends Component { this.recomputeHeights(); } - // Ensure that the note selected here is also selected in the editor - if (selectedNoteId !== prevProps.selectedNoteId) { - onSelectNote(selectedNoteId); - } - // Deselect the currently selected note if it doesn't match the search query if (filter !== prevProps.filter) { const selectedNotePassesFilter = notes.some( @@ -494,16 +497,14 @@ const { } = appState.actionCreators; const { recordEvent } = tracks; -const mapStateToProps = ({ +const mapStateToProps: S.MapState = ({ appState: state, - ui: { filteredNotes }, + ui: { filteredNotes, note }, settings: { noteDisplay }, }) => { const tagResultsFound = getMatchingTags(state.tags, state.filter).length; - - const noteIndex = Math.max(state.previousIndex, 0); - const selectedNote = state.note ? state.note : filteredNotes[noteIndex]; - const selectedNoteId = get(selectedNote, 'id', state.selectedNoteId); + const selectedNote = note; + const selectedNoteId = selectedNote?.id; const selectedNoteIndex = filteredNotes.findIndex( ({ id }) => id === selectedNoteId ); diff --git a/lib/note-toolbar/index.tsx b/lib/note-toolbar/index.tsx index 125d65a69..0c757bf29 100644 --- a/lib/note-toolbar/index.tsx +++ b/lib/note-toolbar/index.tsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { noop } from 'lodash'; @@ -12,11 +13,19 @@ import TrashIcon from '../icons/trash'; import ShareIcon from '../icons/share'; import SidebarIcon from '../icons/sidebar'; -export class NoteToolbar extends Component { +import * as S from '../state'; +import * as T from '../types'; + +type StateProps = { + note: T.NoteEntity | null; +}; + +type Props = StateProps; + +export class NoteToolbar extends Component { static displayName = 'NoteToolbar'; static propTypes = { - note: PropTypes.object, onRestoreNote: PropTypes.func, onTrashNote: PropTypes.func, onDeleteNoteForever: PropTypes.func, @@ -171,4 +180,8 @@ export class NoteToolbar extends Component { }; } -export default NoteToolbar; +const mapStateToProps: S.MapState = ({ ui: { note } }) => ({ + note, +}); + +export default connect(mapStateToProps)(NoteToolbar); diff --git a/lib/revision-selector/index.tsx b/lib/revision-selector/index.tsx index 857324bbd..feed1ea03 100644 --- a/lib/revision-selector/index.tsx +++ b/lib/revision-selector/index.tsx @@ -8,15 +8,14 @@ import Slider from '../components/slider'; import appState from '../flux/app-state'; import { updateNoteTags } from '../state/domain/notes'; -import { NoteEntity } from '../types'; +import * as S from '../state'; +import * as T from '../types'; -const sortedRevisions = (revisions: NoteEntity[]) => +const sortedRevisions = (revisions: T.NoteEntity[]) => orderBy(revisions, 'version', 'asc'); -type Props = { - isViewingRevisions: boolean; - note: NoteEntity; - revisions: NoteEntity[]; +type OwnProps = { + revisions: T.NoteEntity[]; onUpdateContent: Function; setRevision: Function; resetIsViewingRevisions: Function; @@ -24,12 +23,19 @@ type Props = { updateNoteTags: Function; }; -type State = { - revisions: NoteEntity[]; +type StateProps = { + isViewingRevisions: boolean; + note: T.NoteEntity | null; +}; + +type ComponentState = { + revisions: T.NoteEntity[]; selection: number; }; -export class RevisionSelector extends Component { +type Props = OwnProps & StateProps; + +export class RevisionSelector extends Component { constructor(props: Props, ...args: unknown[]) { super(props, ...args); @@ -167,8 +173,12 @@ export class RevisionSelector extends Component { } } -const mapStateToProps = ({ appState: state }) => ({ +const mapStateToProps: S.MapState = ({ + appState: state, + ui: { note }, +}) => ({ isViewingRevisions: state.isViewingRevisions, + note: note, }); const { setRevision, setIsViewingRevisions } = appState.actionCreators; diff --git a/lib/state/action-types.ts b/lib/state/action-types.ts index 649a18109..33c911f56 100644 --- a/lib/state/action-types.ts +++ b/lib/state/action-types.ts @@ -58,9 +58,11 @@ export type ToggleSimperiumConnectionStatus = Action< { simperiumConnected: boolean } >; export type ToggleTagDrawer = Action<'TAG_DRAWER_TOGGLE', { show: boolean }>; +export type SelectNote = Action<'SELECT_NOTE', { note: T.NoteEntity }>; export type ActionType = | FilterNotes + | SelectNote | SetAccountName | SetAuth | SetAutoHideMenuBar diff --git a/lib/state/index.ts b/lib/state/index.ts index 4c5ed5b09..dbb942311 100644 --- a/lib/state/index.ts +++ b/lib/state/index.ts @@ -34,13 +34,11 @@ export type AppState = { isViewingRevisions: boolean; listTitle: T.TranslatableString; nextDialogKey: number; - note?: T.NoteEntity; notes: T.NoteEntity[] | null; preferences?: T.Preferences; previousIndex: number; revision: T.NoteEntity | null; searchFocus: boolean; - selectedNoteId: T.EntityId | null; shouldPrint: boolean; showNavigation: boolean; showNoteInfo: boolean; diff --git a/lib/state/ui/actions.ts b/lib/state/ui/actions.ts index 62a30ac26..b7c3f43e8 100644 --- a/lib/state/ui/actions.ts +++ b/lib/state/ui/actions.ts @@ -15,6 +15,10 @@ export const toggleSimperiumConnectionStatus: A.ActionCreator = ( + note: T.NoteEntity +) => ({ type: 'SELECT_NOTE', note }); + export const toggleTagDrawer: A.ActionCreator = ( show: boolean ) => ({ diff --git a/lib/state/ui/middleware.ts b/lib/state/ui/middleware.ts index 4cb1506c1..12e9bf90b 100644 --- a/lib/state/ui/middleware.ts +++ b/lib/state/ui/middleware.ts @@ -30,7 +30,6 @@ export const middleware: Middleware<{}, S.State, Dispatch> = store => { // on updating the search field we should delay the update // so we don't waste our CPU time and lose responsiveness - case 'App.noteUpdatedRemotely': case 'App.search': clearTimeout(searchTimeout); if ('App.search' === action.type && !action.filter) { diff --git a/lib/state/ui/reducer.ts b/lib/state/ui/reducer.ts index 816956e9f..2920daf60 100644 --- a/lib/state/ui/reducer.ts +++ b/lib/state/ui/reducer.ts @@ -1,6 +1,5 @@ import { difference, union } from 'lodash'; -import { combineReducers } from 'redux'; - +import { AnyAction, combineReducers } from 'redux'; import * as A from '../action-types'; import * as T from '../../types'; @@ -30,8 +29,30 @@ const visiblePanes: A.Reducer = ( return state; }; +const note: A.Reducer = (state = null, action) => { + switch (action.type) { + case 'App.selectNote': + return { ...action.note, hasRemoteUpdate: action.hasRemoteUpdate }; + case 'App.closeNote': + case 'App.showAllNotes': + case 'App.selectTrash': + case 'App.selectTag': + return null; + case 'SELECT_NOTE': + return action.note; + case 'FILTER_NOTES': + // keep note if still in new filtered list otherwise try to choose first note in list + return state && action.notes.some(({ id }) => id === state.id) + ? state + : action.notes[0] || null; + default: + return state; + } +}; + export default combineReducers({ filteredNotes, + note, simperiumConnected, visiblePanes, }); diff --git a/lib/types.ts b/lib/types.ts index b6bc68ac8..eed453da1 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -25,7 +25,7 @@ export type Note = { tags: TagName[]; }; -export type NoteEntity = Entity; +export type NoteEntity = Entity & { hasRemoteUpdate?: boolean }; export type Tag = { index?: number; diff --git a/package-lock.json b/package-lock.json index 90bba59ca..7e0557947 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1856,14 +1856,6 @@ "requires": { "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - } } }, "@babel/plugin-proposal-optional-chaining": { @@ -1969,14 +1961,6 @@ "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - } } }, "@babel/plugin-syntax-nullish-coalescing-operator": {