diff --git a/app/scripts/collections/file-collection.js b/app/scripts/collections/file-collection.js index 0e53a4ab..a80562da 100644 --- a/app/scripts/collections/file-collection.js +++ b/app/scripts/collections/file-collection.js @@ -20,10 +20,6 @@ var FileCollection = Backbone.Collection.extend({ getByName: function(name) { return this.find(function(file) { return file.get('name').toLowerCase() === name.toLowerCase(); }); - }, - - getById: function(id) { - return this.find(function(file) { return file.id === id; }); } }); diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index 149355f6..326d7623 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -42,7 +42,7 @@ var AppModel = Backbone.Model.extend({ }, addFile: function(file) { - if (this.files.getById(file.id)) { + if (this.files.get(file.id)) { return false; } this.files.add(file); @@ -63,10 +63,7 @@ var AppModel = Backbone.Model.extend({ }, reloadFile: function(file) { - this.menu.groupsSection.removeByFile(file, true); - file.get('groups').forEach(function (group) { - this.menu.groupsSection.addItem(group); - }, this); + this.menu.groupsSection.replaceByFile(file, file.get('groups').first()); this.updateTags(); }, @@ -140,7 +137,7 @@ var AppModel = Backbone.Model.extend({ this.updateTags(); this.menu.groupsSection.removeByFile(file); this.menu.filesSection.removeByFile(file); - this.refresh(); + this.menu.select({ item: this.menu.allItemsSection.get('items').first() }); }, emptyTrash: function() { diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index 63312a5f..ca203ae9 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -60,6 +60,9 @@ var EntryModel = Backbone.Model.extend({ if (this.isJustCreated) { this.isJustCreated = false; } + if (this.canBeDeleted) { + this.canBeDeleted = false; + } if (this.unsaved && +this.updated !== +this.entry.times.lastModTime) { this.unsaved = false; } @@ -153,6 +156,15 @@ var EntryModel = Backbone.Model.extend({ this.entry.times.update(); }, + setSaved: function() { + if (this.unsaved) { + this.unsaved = false; + } + if (this.canBeDeleted) { + this.canBeDeleted = false; + } + }, + matches: function(filter) { return !filter || (!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) && @@ -389,11 +401,28 @@ var EntryModel = Backbone.Model.extend({ }, removeWithoutHistory: function() { - var ix = this.group.group.entries.indexOf(this.entry); - if (ix >= 0) { - this.group.group.entries.splice(ix, 1); + if (this.canBeDeleted) { + var ix = this.group.group.entries.indexOf(this.entry); + if (ix >= 0) { + this.group.group.entries.splice(ix, 1); + } + this.file.reload(); + } + }, + + moveToFile: function(file) { + if (this.canBeDeleted) { + this.removeWithoutHistory(); + this.group = file.get('groups').first(); + this.file = file; + this._fillByEntry(); + this.entry.times.update(); + this.group.group.entries.push(this.entry); + this.group.addEntry(this); + this.isJustCreated = true; + this.unsaved = true; + this.file.setModified(); } - this.file.reload(); }, initOtpGenerator: function() { @@ -523,6 +552,7 @@ EntryModel.newEntry = function(group, file) { model.entry.times.update(); model.unsaved = true; model.isJustCreated = true; + model.canBeDeleted = true; group.addEntry(model); file.setModified(); return model; diff --git a/app/scripts/models/file-model.js b/app/scripts/models/file-model.js index 62b7c948..7fd3d0c9 100644 --- a/app/scripts/models/file-model.js +++ b/app/scripts/models/file-model.js @@ -343,7 +343,7 @@ var FileModel = Backbone.Model.extend({ } this.setOpenFile({ passwordLength: this.get('passwordLength') }); this.forEachEntry({}, function(entry) { - entry.unsaved = false; + entry.setSaved(); }); }, diff --git a/app/scripts/models/menu/menu-section-model.js b/app/scripts/models/menu/menu-section-model.js index 7c599587..af677f9c 100644 --- a/app/scripts/models/menu/menu-section-model.js +++ b/app/scripts/models/menu/menu-section-model.js @@ -27,20 +27,27 @@ var MenuItemModel = Backbone.Model.extend({ this.trigger('change-items'); }, - removeByFile: function(file, skipEvent) { + removeByFile: function(file) { var items = this.get('items'); - var toRemove; - items.each(function(item) { + items.find(function(item) { if (item.file === file || item.get('file') === file) { - toRemove = item; + items.remove(item); + return true; } }); - if (toRemove) { - items.remove(toRemove); - } - if (!skipEvent) { - this.trigger('change-items'); - } + this.trigger('change-items'); + }, + + replaceByFile: function(file, newItem) { + var items = this.get('items'); + items.find(function(item, ix) { + if (item.file === file || item.get('file') === file) { + items.remove(item); + items.add(newItem, { at: ix }); + return true; + } + }); + this.trigger('change-items'); }, setItems: function(items) { diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index 35b5877c..2506fcd8 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -6,6 +6,7 @@ var Backbone = require('backbone'), AppSettingsModel = require('../../models/app-settings-model'), Scrollable = require('../../mixins/scrollable'), FieldViewText = require('../fields/field-view-text'), + FieldViewSelect = require('../fields/field-view-select'), FieldViewAutocomplete = require('../fields/field-view-autocomplete'), FieldViewDate = require('../fields/field-view-date'), FieldViewTags = require('../fields/field-view-tags'), @@ -134,6 +135,17 @@ var DetailsView = Backbone.View.extend({ addFieldViews: function() { var model = this.model; + if (model.isJustCreated && this.appModel.files.length > 1) { + var fileNames = this.appModel.files.map(function(file) { + return { id: file.id, value: file.get('name'), selected: file === this.model.file }; + }, this); + this.fileEditView = new FieldViewSelect({ model: { name: '$File', title: Locale.detFile, + value: function() { return fileNames; } } }); + this.fieldViews.push(this.fileEditView); + } else { + this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'File', title: Locale.detFile, + value: function() { return model.fileName; } } })); + } this.userEditView = new FieldViewAutocomplete({ model: { name: '$UserName', title: Locale.detUser, value: function() { return model.user; }, getCompletions: this.getUserNameCompletions.bind(this) } }); this.fieldViews.push(this.userEditView); @@ -149,8 +161,6 @@ var DetailsView = Backbone.View.extend({ value: function() { return model.tags; } } })); this.fieldViews.push(new FieldViewDate({ model: { name: 'Expires', title: Locale.detExpires, lessThanNow: '(' + Locale.detExpired + ')', value: function() { return model.expires; } } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'File', title: Locale.detFile, - value: function() { return model.fileName; } } })); this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Group', title: Locale.detGroup, value: function() { return model.groupName; }, tip: function() { return model.getGroupPath().join(' / '); } } })); this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Created', title: Locale.detCreated, @@ -461,6 +471,13 @@ var DetailsView = Backbone.View.extend({ this.model.setField(fieldName, e.val); this.entryUpdated(); return; + } else if (fieldName === 'File') { + var newFile = this.appModel.files.get(e.val); + this.model.moveToFile(newFile); + this.appModel.activeEntryId = this.model.id; + this.entryUpdated(); + Backbone.trigger('select-entry', this.model); + return; } else if (fieldName) { this.model.setField(fieldName, e.val); } diff --git a/app/scripts/views/fields/field-view-select.js b/app/scripts/views/fields/field-view-select.js new file mode 100644 index 00000000..29fc1142 --- /dev/null +++ b/app/scripts/views/fields/field-view-select.js @@ -0,0 +1,44 @@ +'use strict'; + +var FieldView = require('./field-view'); + +var FieldViewSelect = FieldView.extend({ + readonly: true, + + renderValue: function(value) { + return ''; + }, + + render: function() { + var that = this; + FieldView.prototype.render.call(this); + this.valueEl.addClass('details__field-value--select'); + this.valueEl.find('select:first').change(function(e) { + that.triggerChange({ val: e.target.value, field: that.model.name }); + }); + }, + + fieldLabelClick: function() {}, + + fieldValueClick: function() {}, + + edit: function() {}, + + startEdit: function() {}, + + endEdit: function(newVal, extra) { + if (!this.editing) { + return; + } + delete this.input; + FieldView.prototype.endEdit.call(this, newVal, extra); + } +}); + +module.exports = FieldViewSelect; diff --git a/app/scripts/views/fields/field-view.js b/app/scripts/views/fields/field-view.js index 988287b0..5e51fabd 100644 --- a/app/scripts/views/fields/field-view.js +++ b/app/scripts/views/fields/field-view.js @@ -136,11 +136,15 @@ var FieldView = Backbone.View.extend({ arg = extra; } if (arg) { - arg.sender = this; - this.trigger('change', arg); + this.triggerChange(arg); } this.valueEl.html(this.renderValue(this.value)); this.$el.removeClass('details__field--edit'); + }, + + triggerChange: function(arg) { + arg.sender = this; + this.trigger('change', arg); } }); diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index b984200a..2a785c65 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -449,7 +449,7 @@ var OpenView = Backbone.View.extend({ }, openDb: function() { - if (this.params.id && this.model.files.getById(this.params.id)) { + if (this.params.id && this.model.files.get(this.params.id)) { this.trigger('close'); return; } diff --git a/app/styles/areas/_details.scss b/app/styles/areas/_details.scss index 5d607146..c41bf845 100644 --- a/app/styles/areas/_details.scss +++ b/app/styles/areas/_details.scss @@ -208,8 +208,9 @@ border-radius: $base-border-radius; &:hover { transition: border-color $base-duration $base-timing; + border: 1px solid; @include th { - border: 1px solid light-border-color(); + border-color: light-border-color(); box-shadow: 0 0 3px form-box-shadow-color(); } .details__field-value-add-label { @@ -276,6 +277,16 @@ &:before { content: $fa-var-unlock; } .details__field--protected & { &:before { content: $fa-var-lock; } } } + &--select { + border-width: 0; + padding: 0; + .details__field--editable:hover & { border-width: 0; } + } + >select { + margin: 0; + width: 100%; + padding: 0 $base-padding-h; + } } &--no-select { diff --git a/release-notes.md b/release-notes.md index 725613d9..3aafda57 100644 --- a/release-notes.md +++ b/release-notes.md @@ -14,6 +14,7 @@ Auto-type, ui improvements `+` option to show app logs `+` group info in entry details `+` logout from remote storages on disable +`+` select file for new records `*` don't check updates at startup `*` repos moved to github organization account `*` allow opening same file twice