diff --git a/ElectronClient/gui/MainScreen.jsx b/ElectronClient/gui/MainScreen.jsx index 54607e881..dec7ea08f 100644 --- a/ElectronClient/gui/MainScreen.jsx +++ b/ElectronClient/gui/MainScreen.jsx @@ -96,15 +96,15 @@ class MainScreenComponent extends React.Component { const folderId = Setting.value('activeFolderId'); if (!folderId) return; - const newNote = { + const newNote = await Note.save({ parent_id: folderId, - template: template, is_todo: isTodo ? 1 : 0, - }; + template: template, + }, { provisional: true }); this.props.dispatch({ - type: 'NOTE_SET_NEW_ONE', - item: newNote, + type: 'NOTE_SELECT', + id: newNote.id, }); }; diff --git a/ElectronClient/gui/NoteList.jsx b/ElectronClient/gui/NoteList.jsx index 2ee9e9973..9375e80ef 100644 --- a/ElectronClient/gui/NoteList.jsx +++ b/ElectronClient/gui/NoteList.jsx @@ -159,7 +159,7 @@ class NoteListComponent extends React.Component { } } - let style = Object.assign({ width: width }, this.style().listItem); + let style = Object.assign({ width: width, opacity: this.props.provisionalNoteIds.includes(item.id) ? 0.5 : 1 }, this.style().listItem); if (this.props.selectedNoteIds.indexOf(item.id) >= 0) { style = Object.assign(style, this.style().listItemSelected); @@ -465,6 +465,7 @@ const mapStateToProps = state => { selectedSearchId: state.selectedSearchId, watchedNoteFiles: state.watchedNoteFiles, windowCommand: state.windowCommand, + provisionalNoteIds: state.provisionalNoteIds, }; }; diff --git a/ElectronClient/gui/NoteText.jsx b/ElectronClient/gui/NoteText.jsx index 143ffea8b..414d64500 100644 --- a/ElectronClient/gui/NoteText.jsx +++ b/ElectronClient/gui/NoteText.jsx @@ -101,7 +101,6 @@ class NoteTextComponent extends React.Component { webviewReady: false, scrollHeight: null, editorScrollTop: 0, - newNote: null, noteTags: [], showRevisions: false, loading: false, @@ -416,9 +415,8 @@ class NoteTextComponent extends React.Component { async UNSAFE_componentWillMount() { let note = null; let noteTags = []; - if (this.props.newNote) { - note = Object.assign({}, this.props.newNote); - } else if (this.props.noteId) { + + if (this.props.noteId) { note = await Note.load(this.props.noteId); noteTags = this.props.noteTags || []; } @@ -537,35 +535,24 @@ class NoteTextComponent extends React.Component { this.setState({ loading: true }); const stateNoteId = this.state.note ? this.state.note.id : null; - let noteId = null; - let note = null; - let loadingNewNote = true; + let noteId = props.noteId; let parentFolder = null; - let scrollPercent = 0; + const isProvisionalNote = this.props.provisionalNoteIds.includes(noteId); - if (props.newNote) { - // assign new note and prevent body from being null - note = Object.assign({}, props.newNote, { body: '' }); - this.lastLoadedNoteId_ = null; - if (note.template) note.body = TemplateUtils.render(note.template); - } else { - noteId = props.noteId; + let scrollPercent = this.props.lastEditorScrollPercents[noteId]; + if (!scrollPercent) scrollPercent = 0; - scrollPercent = this.props.lastEditorScrollPercents[noteId]; - if (!scrollPercent) scrollPercent = 0; + let loadingNewNote = stateNoteId !== noteId; + this.lastLoadedNoteId_ = noteId; + let note = noteId ? await Note.load(noteId) : null; + if (noteId !== this.lastLoadedNoteId_) return defer(); // Race condition - current note was changed while this one was loading + if (options.noReloadIfLocalChanges && this.isModified()) return defer(); - loadingNewNote = stateNoteId !== noteId; - this.lastLoadedNoteId_ = noteId; - note = noteId ? await Note.load(noteId) : null; - if (noteId !== this.lastLoadedNoteId_) return defer(); // Race condition - current note was changed while this one was loading - if (options.noReloadIfLocalChanges && this.isModified()) return defer(); - - // If the note hasn't been changed, exit now - if (this.state.note && note) { - let diff = Note.diffObjects(this.state.note, note); - delete diff.type_; - if (!Object.getOwnPropertyNames(diff).length) return defer(); - } + // If the note hasn't been changed, exit now + if (this.state.note && note) { + let diff = Note.diffObjects(this.state.note, note); + delete diff.type_; + if (!Object.getOwnPropertyNames(diff).length) return defer(); } this.markupToHtml_ = null; @@ -573,7 +560,7 @@ class NoteTextComponent extends React.Component { // If we are loading nothing (noteId == null), make sure to // set webviewReady to false too because the webview component // is going to be removed in render(). - const webviewReady = !!this.webviewRef_.current && this.state.webviewReady && (!!noteId || !!props.newNote); + const webviewReady = !!this.webviewRef_.current && this.state.webviewReady && !!noteId; // Scroll back to top when loading new note if (loadingNewNote) { @@ -589,7 +576,7 @@ class NoteTextComponent extends React.Component { this.restoreScrollTop_ = 0; // Only force focus on notes when creating a new note/todo - if (this.props.newNote) { + if (isProvisionalNote) { const focusSettingName = note.is_todo ? 'newTodoFocus' : 'newNoteFocus'; requestAnimationFrame(() => { @@ -661,9 +648,7 @@ class NoteTextComponent extends React.Component { } async UNSAFE_componentWillReceiveProps(nextProps) { - if (this.props.newNote !== nextProps.newNote && nextProps.newNote) { - await this.scheduleReloadNote(nextProps); - } else if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) { + if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) { await this.scheduleReloadNote(nextProps); } else if ('noteTags' in nextProps && this.areNoteTagsModified(nextProps.noteTags, this.state.noteTags)) { this.setState({ @@ -1941,7 +1926,6 @@ class NoteTextComponent extends React.Component { if (this.props.selectedNoteIds.length > 1) { return this.renderMultiNotes(rootStyle); } else if (!note || !!note.encryption_applied) { - // || (note && !this.props.newNote && this.props.noteId && note.id !== this.props.noteId)) { // note.id !== props.noteId is when the note has not been loaded yet, and the previous one is still in the state return this.renderNoNotes(rootStyle); } @@ -2101,7 +2085,7 @@ class NoteTextComponent extends React.Component { this.title_changeText(event); }} onKeyDown={this.titleField_keyDown} - placeholder={this.props.newNote ? _('Creating new %s...', isTodo ? _('to-do') : _('note')) : ''} + placeholder={this.props.provisionalNoteIds.includes(note.id) ? _('Creating new %s...', isTodo ? _('to-do') : _('note')) : ''} /> ); @@ -2229,7 +2213,6 @@ const mapStateToProps = state => { folders: state.folders, theme: state.settings.theme, syncStarted: state.syncStarted, - newNote: state.newNote, windowCommand: state.windowCommand, notesParentType: state.notesParentType, searches: state.searches, @@ -2239,6 +2222,7 @@ const mapStateToProps = state => { lastEditorScrollPercents: state.lastEditorScrollPercents, historyNotes: state.historyNotes, templates: state.templates, + provisionalNoteIds: state.provisionalNoteIds, }; }; diff --git a/ReactNativeClient/lib/components/shared/reduxSharedMiddleware.js b/ReactNativeClient/lib/components/shared/reduxSharedMiddleware.js index bfed27b68..6010ae74b 100644 --- a/ReactNativeClient/lib/components/shared/reduxSharedMiddleware.js +++ b/ReactNativeClient/lib/components/shared/reduxSharedMiddleware.js @@ -1,5 +1,6 @@ const Setting = require('lib/models/Setting'); const Tag = require('lib/models/Tag'); +const Note = require('lib/models/Note'); const { reg } = require('lib/registry.js'); const ResourceFetcher = require('lib/services/ResourceFetcher'); @@ -28,16 +29,22 @@ const reduxSharedMiddleware = async function(store, next, action) { refreshTags = true; } + if (action.type === 'NOTE_SELECT') { + const noteIds = newState.provisionalNoteIds.slice(); + for (const noteId of noteIds) { + if (action.id === noteId) continue; + await Note.delete(noteId); + } + } + if (action.type === 'NOTE_DELETE' || action.type === 'NOTE_SELECT' || - action.type === 'NOTE_SELECT_TOGGLE' || - action.type === 'NOTE_SET_NEW_ONE') { + action.type === 'NOTE_SELECT_TOGGLE') { let noteTags = []; // We don't need to show tags unless only one note is selected. // For new notes, the old note is still selected, but we don't want to show any tags. - if (action.type !== 'NOTE_SET_NEW_ONE' && - newState.selectedNoteIds && + if (newState.selectedNoteIds && newState.selectedNoteIds.length === 1) { noteTags = await Tag.tagsByNoteId(newState.selectedNoteIds[0]); } diff --git a/ReactNativeClient/lib/models/Note.js b/ReactNativeClient/lib/models/Note.js index de8a74da1..375f76568 100644 --- a/ReactNativeClient/lib/models/Note.js +++ b/ReactNativeClient/lib/models/Note.js @@ -561,6 +561,7 @@ class Note extends BaseItem { this.dispatch({ type: 'NOTE_UPDATE_ONE', note: note, + provisional: !!options.provisional, }); if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) { diff --git a/ReactNativeClient/lib/reducer.js b/ReactNativeClient/lib/reducer.js index 5c3036359..d35d9d8dc 100644 --- a/ReactNativeClient/lib/reducer.js +++ b/ReactNativeClient/lib/reducer.js @@ -32,7 +32,6 @@ const defaultState = { sharedData: null, appState: 'starting', hasDisabledSyncItems: false, - newNote: null, customCss: '', templates: [], collapsedFolderIds: [], @@ -51,6 +50,7 @@ const defaultState = { }, historyNotes: [], plugins: {}, + provisionalNoteIds: [], }; const stateUtils = {}; @@ -291,19 +291,15 @@ function changeSelectedNotes(state, action, options = null) { if (action.ids) noteIds = action.ids; if (action.noteId) noteIds = [action.noteId]; - // const noteIds = 'id' in action ? (action.id ? [action.id] : []) : action.ids; - let newState = Object.assign({}, state); if (action.type === 'NOTE_SELECT') { if (JSON.stringify(newState.selectedNoteIds) === JSON.stringify(noteIds)) return state; newState.selectedNoteIds = noteIds; - newState.newNote = null; newState.selectedNoteHash = action.hash ? action.hash : ''; } else if (action.type === 'NOTE_SELECT_ADD') { if (!noteIds.length) return state; newState.selectedNoteIds = ArrayUtils.unique(newState.selectedNoteIds.concat(noteIds)); - newState.newNote = null; } else if (action.type === 'NOTE_SELECT_REMOVE') { if (!noteIds.length) return state; // Nothing to unselect if (state.selectedNoteIds.length <= 1) return state; // Cannot unselect the last note @@ -315,7 +311,6 @@ function changeSelectedNotes(state, action, options = null) { newSelectedNoteIds.push(id); } newState.selectedNoteIds = newSelectedNoteIds; - newState.newNote = null; } else if (action.type === 'NOTE_SELECT_TOGGLE') { if (!noteIds.length) return state; @@ -324,8 +319,6 @@ function changeSelectedNotes(state, action, options = null) { } else { newState = changeSelectedNotes(state, { type: 'NOTE_SELECT_ADD', id: noteIds[0] }); } - - newState.newNote = null; } else { throw new Error('Unreachable'); } @@ -512,11 +505,32 @@ const reducer = (state = defaultState, action) => { if (!newNotes.length) newIndex = -1; newState.selectedNoteIds = newIndex >= 0 ? [newNotes[newIndex].id] : []; } + + if (action.provisional) { + newState.provisionalNoteIds.push(modNote.id); + } else { + const idx = newState.provisionalNoteIds.indexOf(modNote.id); + if (idx >= 0) { + const t = newState.provisionalNoteIds.slice(); + t.splice(idx, 1); + newState.provisionalNoteIds = t; + } + } } break; case 'NOTE_DELETE': - newState = handleItemDelete(state, action); + + { + newState = handleItemDelete(state, action); + + const idx = newState.provisionalNoteIds.indexOf(action.id); + if (idx >= 0) { + const t = newState.provisionalNoteIds.slice(); + t.splice(idx, 1); + newState.provisionalNoteIds = t; + } + } break; case 'TAG_DELETE': @@ -700,15 +714,6 @@ const reducer = (state = defaultState, action) => { newState.hasDisabledSyncItems = true; break; - case 'NOTE_SET_NEW_ONE': - newState = Object.assign({}, state); - newState.newNote = action.item; - if (newState.selectedNoteIds.length > 1) { - newState.selectedNoteIds = newState.selectedNoteIds.slice(); - newState.selectedNoteIds = [newState.selectedNoteIds[0]]; - } - break; - case 'CLIPPER_SERVER_SET': { newState = Object.assign({}, state);