From 310afb0ad601c776f2a68affe23fbcf531835161 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 27 Jun 2018 21:45:31 +0100 Subject: [PATCH] Electron: Resolves #612: Allow duplicating a note --- ElectronClient/app/gui/ImportScreen.jsx | 2 +- ElectronClient/app/gui/NoteList.jsx | 9 +++++++++ ReactNativeClient/lib/models/BaseItem.js | 13 +++++++++++++ ReactNativeClient/lib/models/Folder.js | 13 ------------- ReactNativeClient/lib/models/Note.js | 6 ++++++ .../lib/services/InteropService_Importer_Enex.js | 2 +- .../lib/services/InteropService_Importer_Md.js | 2 +- .../lib/services/InteropService_Importer_Raw.js | 4 ++-- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/ElectronClient/app/gui/ImportScreen.jsx b/ElectronClient/app/gui/ImportScreen.jsx index 7d2c264b1..9fe902e0a 100644 --- a/ElectronClient/app/gui/ImportScreen.jsx +++ b/ElectronClient/app/gui/ImportScreen.jsx @@ -59,7 +59,7 @@ class ImportScreenComponent extends React.Component { async doImport() { const filePath = this.props.filePath; - const folderTitle = await Folder.findUniqueFolderTitle(filename(filePath)); + const folderTitle = await Folder.findUniqueItemTitle(filename(filePath)); const messages = this.state.messages.slice(); this.addMessage('start', _('New notebook "%s" will be created and file "%s" will be imported into it', folderTitle, basename(filePath))); diff --git a/ElectronClient/app/gui/NoteList.jsx b/ElectronClient/app/gui/NoteList.jsx index d873c1f89..31c946dfe 100644 --- a/ElectronClient/app/gui/NoteList.jsx +++ b/ElectronClient/app/gui/NoteList.jsx @@ -88,6 +88,15 @@ class NoteListComponent extends React.Component { }); }})); + menu.append(new MenuItem({label: _('Duplicate'), click: async () => { + for (let i = 0; i < noteIds.length; i++) { + const note = await Note.load(noteIds[i]); + await Note.duplicate(noteIds[i], { + uniqueTitle: _('%s - Copy', note.title), + }); + } + }})); + menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => { for (let i = 0; i < noteIds.length; i++) { const note = await Note.load(noteIds[i]); diff --git a/ReactNativeClient/lib/models/BaseItem.js b/ReactNativeClient/lib/models/BaseItem.js index 618568d84..d0c2843f7 100644 --- a/ReactNativeClient/lib/models/BaseItem.js +++ b/ReactNativeClient/lib/models/BaseItem.js @@ -29,6 +29,19 @@ class BaseItem extends BaseModel { throw new Error('Invalid class name: ' + className); } + static async findUniqueItemTitle(title) { + let counter = 1; + let titleToTry = title; + while (true) { + const item = await this.loadByField('title', titleToTry); + if (!item) return titleToTry; + titleToTry = title + ' (' + counter + ')'; + counter++; + if (counter >= 100) titleToTry = title + ' (' + ((new Date()).getTime()) + ')'; + if (counter >= 1000) throw new Error('Cannot find unique title'); + } + } + // Need to dynamically load the classes like this to avoid circular dependencies static getClass(name) { for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) { diff --git a/ReactNativeClient/lib/models/Folder.js b/ReactNativeClient/lib/models/Folder.js index 75cfee2c6..70baaf2aa 100644 --- a/ReactNativeClient/lib/models/Folder.js +++ b/ReactNativeClient/lib/models/Folder.js @@ -33,19 +33,6 @@ class Folder extends BaseItem { } } - static async findUniqueFolderTitle(title) { - let counter = 1; - let titleToTry = title; - while (true) { - const folder = await this.loadByField('title', titleToTry); - if (!folder) return titleToTry; - titleToTry = title + ' (' + counter + ')'; - counter++; - if (counter >= 100) titleToTry = title + ' (' + ((new Date()).getTime()) + ')'; - if (counter >= 1000) throw new Error('Cannot find unique title'); - } - } - static noteIds(parentId) { return this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]).then((rows) => { let output = []; diff --git a/ReactNativeClient/lib/models/Note.js b/ReactNativeClient/lib/models/Note.js index 05301455c..15584a8d7 100644 --- a/ReactNativeClient/lib/models/Note.js +++ b/ReactNativeClient/lib/models/Note.js @@ -420,6 +420,7 @@ class Note extends BaseItem { static async duplicate(noteId, options = null) { const changes = options && options.changes; + const uniqueTitle = options && options.uniqueTitle; const originalNote = await Note.load(noteId); if (!originalNote) throw new Error('Unknown note: ' + noteId); @@ -432,6 +433,11 @@ class Note extends BaseItem { newNote[n] = changes[n]; } + if (uniqueTitle) { + const title = await Note.findUniqueItemTitle(uniqueTitle); + newNote.title = title; + } + return this.save(newNote); } diff --git a/ReactNativeClient/lib/services/InteropService_Importer_Enex.js b/ReactNativeClient/lib/services/InteropService_Importer_Enex.js index 31fa8faa3..791812e45 100644 --- a/ReactNativeClient/lib/services/InteropService_Importer_Enex.js +++ b/ReactNativeClient/lib/services/InteropService_Importer_Enex.js @@ -23,7 +23,7 @@ class InteropService_Importer_Enex extends InteropService_Importer_Base { let folder = this.options_.destinationFolder; if (!folder) { - const folderTitle = await Folder.findUniqueFolderTitle(filename(this.sourcePath_)); + const folderTitle = await Folder.findUniqueItemTitle(filename(this.sourcePath_)); folder = await Folder.save({ title: folderTitle }); } diff --git a/ReactNativeClient/lib/services/InteropService_Importer_Md.js b/ReactNativeClient/lib/services/InteropService_Importer_Md.js index 49ab0a342..5897f3be6 100644 --- a/ReactNativeClient/lib/services/InteropService_Importer_Md.js +++ b/ReactNativeClient/lib/services/InteropService_Importer_Md.js @@ -34,7 +34,7 @@ class InteropService_Importer_Md extends InteropService_Importer_Base { } if (!this.options_.destinationFolder) { - const folderTitle = await Folder.findUniqueFolderTitle(basename(rtrimSlashes(this.sourcePath_))); + const folderTitle = await Folder.findUniqueItemTitle(basename(rtrimSlashes(this.sourcePath_))); const folder = await Folder.save({ title: folderTitle }); parentFolderId = folder.id; } else { diff --git a/ReactNativeClient/lib/services/InteropService_Importer_Raw.js b/ReactNativeClient/lib/services/InteropService_Importer_Raw.js index 6884f73f4..56eb6b19f 100644 --- a/ReactNativeClient/lib/services/InteropService_Importer_Raw.js +++ b/ReactNativeClient/lib/services/InteropService_Importer_Raw.js @@ -51,7 +51,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base { let defaultFolder_ = null; const defaultFolder = async () => { if (defaultFolder_) return defaultFolder_; - const folderTitle = await Folder.findUniqueFolderTitle(this.options_.defaultFolderTitle ? this.options_.defaultFolderTitle : 'Imported'); + const folderTitle = await Folder.findUniqueItemTitle(this.options_.defaultFolderTitle ? this.options_.defaultFolderTitle : 'Imported'); defaultFolder_ = await Folder.save({ title: folderTitle }); return defaultFolder_; } @@ -100,7 +100,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base { if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create(); item.id = itemIdMap[item.id]; - item.title = await Folder.findUniqueFolderTitle(item.title); + item.title = await Folder.findUniqueItemTitle(item.title); if (item.parent_id) { await setFolderToImportTo(item.parent_id);