You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-16 00:14:34 +02:00
All: Added concept of provisional note to simplify creation and handling of newly created notes
This commit is contained in:
@ -96,15 +96,15 @@ class MainScreenComponent extends React.Component {
|
|||||||
const folderId = Setting.value('activeFolderId');
|
const folderId = Setting.value('activeFolderId');
|
||||||
if (!folderId) return;
|
if (!folderId) return;
|
||||||
|
|
||||||
const newNote = {
|
const newNote = await Note.save({
|
||||||
parent_id: folderId,
|
parent_id: folderId,
|
||||||
template: template,
|
|
||||||
is_todo: isTodo ? 1 : 0,
|
is_todo: isTodo ? 1 : 0,
|
||||||
};
|
template: template,
|
||||||
|
}, { provisional: true });
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'NOTE_SET_NEW_ONE',
|
type: 'NOTE_SELECT',
|
||||||
item: newNote,
|
id: newNote.id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
if (this.props.selectedNoteIds.indexOf(item.id) >= 0) {
|
||||||
style = Object.assign(style, this.style().listItemSelected);
|
style = Object.assign(style, this.style().listItemSelected);
|
||||||
@ -465,6 +465,7 @@ const mapStateToProps = state => {
|
|||||||
selectedSearchId: state.selectedSearchId,
|
selectedSearchId: state.selectedSearchId,
|
||||||
watchedNoteFiles: state.watchedNoteFiles,
|
watchedNoteFiles: state.watchedNoteFiles,
|
||||||
windowCommand: state.windowCommand,
|
windowCommand: state.windowCommand,
|
||||||
|
provisionalNoteIds: state.provisionalNoteIds,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ class NoteTextComponent extends React.Component {
|
|||||||
webviewReady: false,
|
webviewReady: false,
|
||||||
scrollHeight: null,
|
scrollHeight: null,
|
||||||
editorScrollTop: 0,
|
editorScrollTop: 0,
|
||||||
newNote: null,
|
|
||||||
noteTags: [],
|
noteTags: [],
|
||||||
showRevisions: false,
|
showRevisions: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -416,9 +415,8 @@ class NoteTextComponent extends React.Component {
|
|||||||
async UNSAFE_componentWillMount() {
|
async UNSAFE_componentWillMount() {
|
||||||
let note = null;
|
let note = null;
|
||||||
let noteTags = [];
|
let noteTags = [];
|
||||||
if (this.props.newNote) {
|
|
||||||
note = Object.assign({}, this.props.newNote);
|
if (this.props.noteId) {
|
||||||
} else if (this.props.noteId) {
|
|
||||||
note = await Note.load(this.props.noteId);
|
note = await Note.load(this.props.noteId);
|
||||||
noteTags = this.props.noteTags || [];
|
noteTags = this.props.noteTags || [];
|
||||||
}
|
}
|
||||||
@ -537,35 +535,24 @@ class NoteTextComponent extends React.Component {
|
|||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
const stateNoteId = this.state.note ? this.state.note.id : null;
|
const stateNoteId = this.state.note ? this.state.note.id : null;
|
||||||
let noteId = null;
|
let noteId = props.noteId;
|
||||||
let note = null;
|
|
||||||
let loadingNewNote = true;
|
|
||||||
let parentFolder = null;
|
let parentFolder = null;
|
||||||
let scrollPercent = 0;
|
const isProvisionalNote = this.props.provisionalNoteIds.includes(noteId);
|
||||||
|
|
||||||
if (props.newNote) {
|
let scrollPercent = this.props.lastEditorScrollPercents[noteId];
|
||||||
// assign new note and prevent body from being null
|
if (!scrollPercent) scrollPercent = 0;
|
||||||
note = Object.assign({}, props.newNote, { body: '' });
|
|
||||||
this.lastLoadedNoteId_ = null;
|
|
||||||
if (note.template) note.body = TemplateUtils.render(note.template);
|
|
||||||
} else {
|
|
||||||
noteId = props.noteId;
|
|
||||||
|
|
||||||
scrollPercent = this.props.lastEditorScrollPercents[noteId];
|
let loadingNewNote = stateNoteId !== noteId;
|
||||||
if (!scrollPercent) scrollPercent = 0;
|
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;
|
// If the note hasn't been changed, exit now
|
||||||
this.lastLoadedNoteId_ = noteId;
|
if (this.state.note && note) {
|
||||||
note = noteId ? await Note.load(noteId) : null;
|
let diff = Note.diffObjects(this.state.note, note);
|
||||||
if (noteId !== this.lastLoadedNoteId_) return defer(); // Race condition - current note was changed while this one was loading
|
delete diff.type_;
|
||||||
if (options.noReloadIfLocalChanges && this.isModified()) return defer();
|
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;
|
this.markupToHtml_ = null;
|
||||||
@ -573,7 +560,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
// If we are loading nothing (noteId == null), make sure to
|
// If we are loading nothing (noteId == null), make sure to
|
||||||
// set webviewReady to false too because the webview component
|
// set webviewReady to false too because the webview component
|
||||||
// is going to be removed in render().
|
// 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
|
// Scroll back to top when loading new note
|
||||||
if (loadingNewNote) {
|
if (loadingNewNote) {
|
||||||
@ -589,7 +576,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
this.restoreScrollTop_ = 0;
|
this.restoreScrollTop_ = 0;
|
||||||
|
|
||||||
// Only force focus on notes when creating a new note/todo
|
// Only force focus on notes when creating a new note/todo
|
||||||
if (this.props.newNote) {
|
if (isProvisionalNote) {
|
||||||
const focusSettingName = note.is_todo ? 'newTodoFocus' : 'newNoteFocus';
|
const focusSettingName = note.is_todo ? 'newTodoFocus' : 'newNoteFocus';
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -661,9 +648,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async UNSAFE_componentWillReceiveProps(nextProps) {
|
async UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.newNote !== nextProps.newNote && nextProps.newNote) {
|
if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) {
|
||||||
await this.scheduleReloadNote(nextProps);
|
|
||||||
} else if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) {
|
|
||||||
await this.scheduleReloadNote(nextProps);
|
await this.scheduleReloadNote(nextProps);
|
||||||
} else if ('noteTags' in nextProps && this.areNoteTagsModified(nextProps.noteTags, this.state.noteTags)) {
|
} else if ('noteTags' in nextProps && this.areNoteTagsModified(nextProps.noteTags, this.state.noteTags)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -1941,7 +1926,6 @@ class NoteTextComponent extends React.Component {
|
|||||||
if (this.props.selectedNoteIds.length > 1) {
|
if (this.props.selectedNoteIds.length > 1) {
|
||||||
return this.renderMultiNotes(rootStyle);
|
return this.renderMultiNotes(rootStyle);
|
||||||
} else if (!note || !!note.encryption_applied) {
|
} 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);
|
return this.renderNoNotes(rootStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2101,7 +2085,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
this.title_changeText(event);
|
this.title_changeText(event);
|
||||||
}}
|
}}
|
||||||
onKeyDown={this.titleField_keyDown}
|
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,
|
folders: state.folders,
|
||||||
theme: state.settings.theme,
|
theme: state.settings.theme,
|
||||||
syncStarted: state.syncStarted,
|
syncStarted: state.syncStarted,
|
||||||
newNote: state.newNote,
|
|
||||||
windowCommand: state.windowCommand,
|
windowCommand: state.windowCommand,
|
||||||
notesParentType: state.notesParentType,
|
notesParentType: state.notesParentType,
|
||||||
searches: state.searches,
|
searches: state.searches,
|
||||||
@ -2239,6 +2222,7 @@ const mapStateToProps = state => {
|
|||||||
lastEditorScrollPercents: state.lastEditorScrollPercents,
|
lastEditorScrollPercents: state.lastEditorScrollPercents,
|
||||||
historyNotes: state.historyNotes,
|
historyNotes: state.historyNotes,
|
||||||
templates: state.templates,
|
templates: state.templates,
|
||||||
|
provisionalNoteIds: state.provisionalNoteIds,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const Setting = require('lib/models/Setting');
|
const Setting = require('lib/models/Setting');
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
|
const Note = require('lib/models/Note');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||||
|
|
||||||
@ -28,16 +29,22 @@ const reduxSharedMiddleware = async function(store, next, action) {
|
|||||||
refreshTags = true;
|
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' ||
|
if (action.type === 'NOTE_DELETE' ||
|
||||||
action.type === 'NOTE_SELECT' ||
|
action.type === 'NOTE_SELECT' ||
|
||||||
action.type === 'NOTE_SELECT_TOGGLE' ||
|
action.type === 'NOTE_SELECT_TOGGLE') {
|
||||||
action.type === 'NOTE_SET_NEW_ONE') {
|
|
||||||
let noteTags = [];
|
let noteTags = [];
|
||||||
|
|
||||||
// We don't need to show tags unless only one note is selected.
|
// 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.
|
// 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' &&
|
if (newState.selectedNoteIds &&
|
||||||
newState.selectedNoteIds &&
|
|
||||||
newState.selectedNoteIds.length === 1) {
|
newState.selectedNoteIds.length === 1) {
|
||||||
noteTags = await Tag.tagsByNoteId(newState.selectedNoteIds[0]);
|
noteTags = await Tag.tagsByNoteId(newState.selectedNoteIds[0]);
|
||||||
}
|
}
|
||||||
|
@ -561,6 +561,7 @@ class Note extends BaseItem {
|
|||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'NOTE_UPDATE_ONE',
|
type: 'NOTE_UPDATE_ONE',
|
||||||
note: note,
|
note: note,
|
||||||
|
provisional: !!options.provisional,
|
||||||
});
|
});
|
||||||
|
|
||||||
if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) {
|
if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) {
|
||||||
|
@ -32,7 +32,6 @@ const defaultState = {
|
|||||||
sharedData: null,
|
sharedData: null,
|
||||||
appState: 'starting',
|
appState: 'starting',
|
||||||
hasDisabledSyncItems: false,
|
hasDisabledSyncItems: false,
|
||||||
newNote: null,
|
|
||||||
customCss: '',
|
customCss: '',
|
||||||
templates: [],
|
templates: [],
|
||||||
collapsedFolderIds: [],
|
collapsedFolderIds: [],
|
||||||
@ -51,6 +50,7 @@ const defaultState = {
|
|||||||
},
|
},
|
||||||
historyNotes: [],
|
historyNotes: [],
|
||||||
plugins: {},
|
plugins: {},
|
||||||
|
provisionalNoteIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateUtils = {};
|
const stateUtils = {};
|
||||||
@ -291,19 +291,15 @@ function changeSelectedNotes(state, action, options = null) {
|
|||||||
if (action.ids) noteIds = action.ids;
|
if (action.ids) noteIds = action.ids;
|
||||||
if (action.noteId) noteIds = [action.noteId];
|
if (action.noteId) noteIds = [action.noteId];
|
||||||
|
|
||||||
// const noteIds = 'id' in action ? (action.id ? [action.id] : []) : action.ids;
|
|
||||||
|
|
||||||
let newState = Object.assign({}, state);
|
let newState = Object.assign({}, state);
|
||||||
|
|
||||||
if (action.type === 'NOTE_SELECT') {
|
if (action.type === 'NOTE_SELECT') {
|
||||||
if (JSON.stringify(newState.selectedNoteIds) === JSON.stringify(noteIds)) return state;
|
if (JSON.stringify(newState.selectedNoteIds) === JSON.stringify(noteIds)) return state;
|
||||||
newState.selectedNoteIds = noteIds;
|
newState.selectedNoteIds = noteIds;
|
||||||
newState.newNote = null;
|
|
||||||
newState.selectedNoteHash = action.hash ? action.hash : '';
|
newState.selectedNoteHash = action.hash ? action.hash : '';
|
||||||
} else if (action.type === 'NOTE_SELECT_ADD') {
|
} else if (action.type === 'NOTE_SELECT_ADD') {
|
||||||
if (!noteIds.length) return state;
|
if (!noteIds.length) return state;
|
||||||
newState.selectedNoteIds = ArrayUtils.unique(newState.selectedNoteIds.concat(noteIds));
|
newState.selectedNoteIds = ArrayUtils.unique(newState.selectedNoteIds.concat(noteIds));
|
||||||
newState.newNote = null;
|
|
||||||
} else if (action.type === 'NOTE_SELECT_REMOVE') {
|
} else if (action.type === 'NOTE_SELECT_REMOVE') {
|
||||||
if (!noteIds.length) return state; // Nothing to unselect
|
if (!noteIds.length) return state; // Nothing to unselect
|
||||||
if (state.selectedNoteIds.length <= 1) return state; // Cannot unselect the last note
|
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);
|
newSelectedNoteIds.push(id);
|
||||||
}
|
}
|
||||||
newState.selectedNoteIds = newSelectedNoteIds;
|
newState.selectedNoteIds = newSelectedNoteIds;
|
||||||
newState.newNote = null;
|
|
||||||
} else if (action.type === 'NOTE_SELECT_TOGGLE') {
|
} else if (action.type === 'NOTE_SELECT_TOGGLE') {
|
||||||
if (!noteIds.length) return state;
|
if (!noteIds.length) return state;
|
||||||
|
|
||||||
@ -324,8 +319,6 @@ function changeSelectedNotes(state, action, options = null) {
|
|||||||
} else {
|
} else {
|
||||||
newState = changeSelectedNotes(state, { type: 'NOTE_SELECT_ADD', id: noteIds[0] });
|
newState = changeSelectedNotes(state, { type: 'NOTE_SELECT_ADD', id: noteIds[0] });
|
||||||
}
|
}
|
||||||
|
|
||||||
newState.newNote = null;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unreachable');
|
throw new Error('Unreachable');
|
||||||
}
|
}
|
||||||
@ -512,11 +505,32 @@ const reducer = (state = defaultState, action) => {
|
|||||||
if (!newNotes.length) newIndex = -1;
|
if (!newNotes.length) newIndex = -1;
|
||||||
newState.selectedNoteIds = newIndex >= 0 ? [newNotes[newIndex].id] : [];
|
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;
|
break;
|
||||||
|
|
||||||
case 'NOTE_DELETE':
|
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;
|
break;
|
||||||
|
|
||||||
case 'TAG_DELETE':
|
case 'TAG_DELETE':
|
||||||
@ -700,15 +714,6 @@ const reducer = (state = defaultState, action) => {
|
|||||||
newState.hasDisabledSyncItems = true;
|
newState.hasDisabledSyncItems = true;
|
||||||
break;
|
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':
|
case 'CLIPPER_SERVER_SET':
|
||||||
{
|
{
|
||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
|
Reference in New Issue
Block a user