diff --git a/.eslintignore b/.eslintignore index d22c4db9174..43d5398466c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -140,6 +140,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map packages/app-desktop/commands/replaceMisspelling.d.ts packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/replaceMisspelling.js.map +packages/app-desktop/commands/restoreNoteRevision.d.ts +packages/app-desktop/commands/restoreNoteRevision.js +packages/app-desktop/commands/restoreNoteRevision.js.map packages/app-desktop/commands/startExternalEditing.d.ts packages/app-desktop/commands/startExternalEditing.js packages/app-desktop/commands/startExternalEditing.js.map diff --git a/.gitignore b/.gitignore index 997fc70da0d..0ae10962935 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,9 @@ packages/app-desktop/commands/openProfileDirectory.js.map packages/app-desktop/commands/replaceMisspelling.d.ts packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/replaceMisspelling.js.map +packages/app-desktop/commands/restoreNoteRevision.d.ts +packages/app-desktop/commands/restoreNoteRevision.js +packages/app-desktop/commands/restoreNoteRevision.js.map packages/app-desktop/commands/startExternalEditing.d.ts packages/app-desktop/commands/startExternalEditing.js packages/app-desktop/commands/startExternalEditing.js.map diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index 5c875364989..ae6aee24b17 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -96,6 +96,7 @@ const globalCommands = [ require('./commands/stopExternalEditing'), require('./commands/toggleExternalEditing'), require('./commands/toggleSafeMode'), + require('./commands/restoreNoteRevision'), require('@joplin/lib/commands/historyBackward'), require('@joplin/lib/commands/historyForward'), require('@joplin/lib/commands/synchronize'), diff --git a/packages/app-desktop/commands/restoreNoteRevision.ts b/packages/app-desktop/commands/restoreNoteRevision.ts new file mode 100644 index 00000000000..45681188de4 --- /dev/null +++ b/packages/app-desktop/commands/restoreNoteRevision.ts @@ -0,0 +1,20 @@ +import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; +import RevisionService from '@joplin/lib/services/RevisionService'; + +export const declaration: CommandDeclaration = { + name: 'restoreNoteRevision', + label: 'Restore a note from history', +}; + +export const runtime = (): CommandRuntime => { + return { + execute: async (_context: CommandContext, noteId: string, reverseRevIndex: number = 0) => { + try { + const note = await RevisionService.instance().restoreNoteById(noteId, reverseRevIndex); + alert(RevisionService.instance().restoreSuccessMessage(note)); + } catch (error) { + alert(error.message); + } + }, + }; +}; diff --git a/packages/app-desktop/gui/NoteRevisionViewer.jsx b/packages/app-desktop/gui/NoteRevisionViewer.jsx index 350faa125ec..7aad41ed3af 100644 --- a/packages/app-desktop/gui/NoteRevisionViewer.jsx +++ b/packages/app-desktop/gui/NoteRevisionViewer.jsx @@ -13,7 +13,7 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js'); const { MarkupToHtml } = require('@joplin/renderer'); const time = require('@joplin/lib/time').default; const ReactTooltip = require('react-tooltip'); -const { urlDecode, substrWithEllipsis } = require('@joplin/lib/string-utils'); +const { urlDecode } = require('@joplin/lib/string-utils'); const bridge = require('electron').remote.require('./bridge').default; const markupLanguageUtils = require('../utils/markupLanguageUtils').default; @@ -75,7 +75,7 @@ class NoteRevisionViewerComponent extends React.PureComponent { this.setState({ restoring: true }); await RevisionService.instance().importRevisionNote(this.state.note); this.setState({ restoring: false }); - alert(_('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(this.state.note.title, 0, 32), RevisionService.instance().restoreFolderTitle())); + alert(RevisionService.instance().restoreSuccessMessage(this.state.note)); } backButton_click() { diff --git a/packages/lib/services/RevisionService.ts b/packages/lib/services/RevisionService.ts index 97181632db9..b13dd58d47d 100644 --- a/packages/lib/services/RevisionService.ts +++ b/packages/lib/services/RevisionService.ts @@ -9,6 +9,7 @@ import shim from '../shim'; import BaseService from './BaseService'; import { _ } from '../locale'; import { ItemChangeEntity, NoteEntity, RevisionEntity } from './database/types'; +const { substrWithEllipsis } = require('../string-utils'); const { sprintf } = require('sprintf-js'); const { wrapError } = require('../errorUtils'); @@ -230,7 +231,23 @@ export default class RevisionService extends BaseService { return folder; } - async importRevisionNote(note: NoteEntity) { + // reverseRevIndex = 0 means restoring the latest version. reverseRevIndex = + // 1 means the version before that, etc. + public async restoreNoteById(noteId: string, reverseRevIndex: number): Promise { + const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId); + if (!revisions.length) throw new Error(`No revision for note "${noteId}"`); + + const revIndex = revisions.length - 1 - reverseRevIndex; + + const note = await this.revisionNote(revisions, revIndex); + return this.importRevisionNote(note); + } + + public restoreSuccessMessage(note: NoteEntity): string { + return _('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(note.title, 0, 32), this.restoreFolderTitle()); + } + + async importRevisionNote(note: NoteEntity): Promise { const toImport = Object.assign({}, note); delete toImport.id; delete toImport.updated_time; @@ -242,7 +259,7 @@ export default class RevisionService extends BaseService { toImport.parent_id = folder.id; - await Note.save(toImport); + return Note.save(toImport); } async maintenance() {