diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index 1f182f96..0386a10a 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -40,6 +40,7 @@ var AppModel = Backbone.Model.extend({ this.listenTo(Backbone, 'add-filter', this.addFilter); this.listenTo(Backbone, 'set-sort', this.setSort); this.listenTo(Backbone, 'empty-trash', this.emptyTrash); + this.listenTo(Backbone, 'select-entry', this.selectEntry); this.appLogger = new Logger('app'); @@ -199,13 +200,18 @@ var AppModel = Backbone.Model.extend({ this.activeEntryId = firstEntry ? firstEntry.id : null; } Backbone.trigger('filter', { filter: this.filter, sort: this.sort, entries: entries }); - Backbone.trigger('select-entry', entries.get(this.activeEntryId)); + Backbone.trigger('entry-selected', entries.get(this.activeEntryId)); }, refresh: function() { this.setFilter(this.filter); }, + selectEntry: function(entry) { + this.activeEntryId = entry.id; + this.refresh(); + }, + addFilter: function(filter) { this.setFilter(_.extend(this.filter, filter)); }, diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index bd5bed59..c6b53b64 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -608,6 +608,17 @@ var EntryModel = Backbone.Model.extend({ group = group.parentGroup; } return groupPath; + }, + + cloneEntry: function(nameSuffix) { + let newEntry = EntryModel.newEntry(this.group, this.file); + newEntry.entry.copyFrom(this.entry); + newEntry.entry.uuid = kdbxweb.KdbxUuid.random(); + newEntry.entry.times.update(); + newEntry.entry.fields.Title = this.title + nameSuffix; + newEntry._fillByEntry(); + this.file.reload(); + return newEntry; } }); diff --git a/app/scripts/util/locale.js b/app/scripts/util/locale.js index 4a004d10..f89a0460 100644 --- a/app/scripts/util/locale.js +++ b/app/scripts/util/locale.js @@ -243,6 +243,8 @@ var Locale = { detMenuCopyPassword: 'Copy password', detMenuCopyUser: 'Copy user', detSetupOtp: 'One-time passwords', + detClone: 'Make a copy', + detClonedName: 'Copy', detAutoType: 'Auto-type', detAutoTypeEnabled: 'Enable auto-type for this entry', detAutoTypeSequence: 'Keystrokes', diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index d0d042f6..5987acc5 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -66,7 +66,7 @@ var DetailsView = Backbone.View.extend({ this.fieldViews = []; this.views = {}; this.initScroll(); - this.listenTo(Backbone, 'select-entry', this.showEntry); + this.listenTo(Backbone, 'entry-selected', this.showEntry); this.listenTo(Backbone, 'copy-password', this.copyPassword); this.listenTo(Backbone, 'copy-user', this.copyUserName); this.listenTo(Backbone, 'copy-url', this.copyUrl); @@ -252,6 +252,7 @@ var DetailsView = Backbone.View.extend({ if (AutoType.enabled) { moreOptions.push({value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoType}); } + moreOptions.push({value: 'clone', icon: 'clone', text: Locale.detClone}); var rect = this.moreView.labelEl[0].getBoundingClientRect(); dropdownView.render({ position: {top: rect.bottom, left: rect.left}, @@ -280,6 +281,9 @@ var DetailsView = Backbone.View.extend({ case 'auto-type': this.toggleAutoType(); break; + case 'clone': + this.clone(); + break; default: if (e.item.lastIndexOf('add:', 0) === 0) { var fieldName = e.item.substr(4); @@ -486,7 +490,7 @@ var DetailsView = Backbone.View.extend({ this.model.moveToFile(newFile); this.appModel.activeEntryId = this.model.id; this.entryUpdated(); - Backbone.trigger('select-entry', this.model); + Backbone.trigger('entry-selected', this.model); return; } else if (fieldName) { this.model.setField(fieldName, e.val); @@ -737,6 +741,11 @@ var DetailsView = Backbone.View.extend({ Backbone.trigger('refresh'); }, + clone: function() { + let newEntry = this.model.cloneEntry(' ' + Locale.detClonedName); + Backbone.trigger('select-entry', newEntry); + }, + deleteFromTrash: function() { Alerts.yesno({ header: Locale.detDelFromTrash, @@ -761,6 +770,7 @@ var DetailsView = Backbone.View.extend({ options.push({ value: 'det-copy-user', icon: 'clipboard', text: Locale.detMenuCopyUser }); } options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField }); + options.push({ value: 'det-clone', icon: 'clone', text: Locale.detClone }); Backbone.trigger('show-context-menu', _.extend(e, { options })); }, @@ -775,6 +785,9 @@ var DetailsView = Backbone.View.extend({ case 'det-add-new': this.addNewField(); break; + case 'det-clone': + this.clone(); + break; } }, diff --git a/app/scripts/views/list-view.js b/app/scripts/views/list-view.js index 0ddc1e35..5f1530e4 100644 --- a/app/scripts/views/list-view.js +++ b/app/scripts/views/list-view.js @@ -162,7 +162,7 @@ var ListView = Backbone.View.extend({ selectItem: function(item) { this.model.activeEntryId = item.id; - Backbone.trigger('select-entry', item); + Backbone.trigger('entry-selected', item); this.itemsEl.find('.list__item--active').removeClass('list__item--active'); var itemEl = document.getElementById(item.id); itemEl.classList.add('list__item--active'); diff --git a/release-notes.md b/release-notes.md index c09fb5b5..c3c00fba 100644 --- a/release-notes.md +++ b/release-notes.md @@ -15,6 +15,7 @@ Audit, generator presets, auto-type and ui improvements `+` confirmation in password change dialog `+` inline generator keyboard management `+` field references decoding +`+` copy entries `-` fix app redraw in background `-` fix idle timer on computer sleep `-` fix storage popup when no action is required