1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-26 18:58:21 +02:00

All: Added concept of provisional note to simplify creation and handling of newly created notes

This commit is contained in:
Laurent Cozic 2020-02-29 12:39:15 +00:00
parent 6542a60d61
commit 6ca0e6adcc
6 changed files with 63 additions and 65 deletions

View File

@ -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,
});
};

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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]);
}

View File

@ -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) {

View File

@ -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);