diff --git a/TODO.md b/TODO.md index 9b7a1afe..0250b2c1 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,7 @@ - [x] improve open page UX - [x] provide engineer error details on file open - [ ] trash: groups/empty/untrash -- [ ] move groups/entries +- [x] move groups/entries - [ ] help/tips - [ ] switch view - [ ] optional auto-update diff --git a/app/scripts/comp/drag-drop-info.js b/app/scripts/comp/drag-drop-info.js new file mode 100644 index 00000000..d536db16 --- /dev/null +++ b/app/scripts/comp/drag-drop-info.js @@ -0,0 +1,7 @@ +'use strict'; + +var DragDropInfo = { + dragObject: null +}; + +module.exports = DragDropInfo; diff --git a/app/scripts/models/group-model.js b/app/scripts/models/group-model.js index 2946911d..5e16c89f 100644 --- a/app/scripts/models/group-model.js +++ b/app/scripts/models/group-model.js @@ -12,7 +12,9 @@ var GroupModel = MenuItemModel.extend({ entries: null, filterKey: 'group', editable: true, - top: false + top: false, + drag: true, + drop: true }), initialize: function() { @@ -134,6 +136,28 @@ var GroupModel = MenuItemModel.extend({ } this.parentGroup.removeGroup(this); this.trigger('delete'); + }, + + moveHere: function(object) { + if (!object || object.id === this.id) { + return; + } + if (object instanceof GroupModel) { + if (this.group.groups.indexOf(object.group) >= 0) { + return; + } + this.file.db.move(object.group, this.group); + object.parentGroup.removeGroup(object); + object.trigger('delete'); + this.addGroup(object); + } else if (object instanceof EntryModel) { + if (this.group.entries.indexOf(object.entry) >= 0) { + return; + } + this.file.db.move(object.entry, this.group); + object.group.removeEntry(object); + this.addEntry(object); + } } }); @@ -143,7 +167,7 @@ GroupModel.fromGroup = function(group, file, parentGroup) { if (parentGroup) { model.parentGroup = parentGroup; } else { - model.set({ top: true }, { silent: true }); + model.set({ top: true, drag: false }, { silent: true }); } return model; }; diff --git a/app/scripts/models/menu/menu-item-model.js b/app/scripts/models/menu/menu-item-model.js index b1ef3981..9157159e 100644 --- a/app/scripts/models/menu/menu-item-model.js +++ b/app/scripts/models/menu/menu-item-model.js @@ -16,6 +16,8 @@ var MenuItemModel = Backbone.Model.extend({ cls: null, disabled: false, visible: true, + drag: false, + drop: false, filterKey: null, filterValue: null }, diff --git a/app/scripts/views/list-view.js b/app/scripts/views/list-view.js index 0176a965..d56b603e 100644 --- a/app/scripts/views/list-view.js +++ b/app/scripts/views/list-view.js @@ -5,6 +5,7 @@ var Backbone = require('backbone'), Scrollable = require('../mixins/scrollable'), ListSearchView = require('./list-search-view'), EntryPresenter = require('../presenters/entry-presenter'), + DragDropInfo = require('../comp/drag-drop-info'), baron = require('baron'); var ListView = Backbone.View.extend({ @@ -13,7 +14,8 @@ var ListView = Backbone.View.extend({ emptyTemplate: require('templates/list-empty.html'), events: { - 'click .list__item': 'itemClick' + 'click .list__item': 'itemClick', + 'dragstart .list__item': 'itemDragStart' }, views: null, @@ -144,6 +146,14 @@ var ListView = Backbone.View.extend({ var scrollTop = this.itemsEl[0].scrollTop; this.render(); this.itemsEl[0].scrollTop = scrollTop; + }, + + itemDragStart: function(e) { + e.stopPropagation(); + var id = $(e.target).closest('.list__item').attr('id'); + e.dataTransfer.setData('text/entry', id); + e.dataTransfer.effectAllowed = 'move'; + DragDropInfo.dragObject = this.items.get(id); } }); diff --git a/app/scripts/views/menu/menu-item-view.js b/app/scripts/views/menu/menu-item-view.js index e28349e8..83c2f15c 100644 --- a/app/scripts/views/menu/menu-item-view.js +++ b/app/scripts/views/menu/menu-item-view.js @@ -3,7 +3,8 @@ var Backbone = require('backbone'), KeyHandler = require('../../comp/key-handler'), Keys = require('../../const/keys'), - Alerts = require('../../comp/alerts'); + Alerts = require('../../comp/alerts'), + DragDropInfo = require('../../comp/drag-drop-info'); var MenuItemView = Backbone.View.extend({ template: require('templates/menu/menu-item.html'), @@ -14,7 +15,11 @@ var MenuItemView = Backbone.View.extend({ 'click .menu__item-option': 'selectOption', 'click': 'selectItem', 'dblclick': 'expandItem', - 'click .menu__item-edit': 'editItem' + 'click .menu__item-edit': 'editItem', + 'dragstart': 'dragstart', + 'dragover': 'dragover', + 'dragleave': 'dragleave', + 'drop' : 'drop' }, iconEl: null, @@ -99,8 +104,10 @@ var MenuItemView = Backbone.View.extend({ }, mouseover: function(e) { - this.$el.addClass('menu__item--hover'); - e.stopPropagation(); + if (!e.button) { + this.$el.addClass('menu__item--hover'); + e.stopPropagation(); + } }, mouseout: function(e) { @@ -146,6 +153,43 @@ var MenuItemView = Backbone.View.extend({ e.stopPropagation(); Backbone.trigger('edit-group', this.model); } + }, + + dropAllowed: function(e) { + return ['text/group', 'text/entry'].indexOf(e.dataTransfer.types[0]) >= 0; + }, + + dragstart: function(e) { + e.stopPropagation(); + if (this.model.get('drag')) { + e.dataTransfer.setData('text/group', this.model.id); + e.dataTransfer.effectAllowed = 'move'; + DragDropInfo.dragObject = this.model; + } + }, + + dragover: function(e) { + e.stopPropagation(); + if (this.model.get('drop') && this.dropAllowed(e)) { + e.preventDefault(); + this.$el.addClass('menu__item--drag'); + } + }, + + dragleave: function(e) { + e.stopPropagation(); + if (this.model.get('drop') && this.dropAllowed(e)) { + this.$el.removeClass('menu__item--drag'); + } + }, + + drop: function(e) { + e.stopPropagation(); + if (this.model.get('drop') && this.dropAllowed(e)) { + this.$el.removeClass('menu__item--drag'); + this.model.moveHere(DragDropInfo.dragObject); + Backbone.trigger('refresh'); + } } }); diff --git a/app/styles/areas/_menu.scss b/app/styles/areas/_menu.scss index 81735bd2..c6188d34 100644 --- a/app/styles/areas/_menu.scss +++ b/app/styles/areas/_menu.scss @@ -145,5 +145,11 @@ .fa { margin-right: $base-padding-h / 2; } + + &--drag { + >.menu__item-body { + @include th { color: action-color(); } + } + } } } diff --git a/app/templates/list-item-short.html b/app/templates/list-item-short.html index 66585bc1..1c1e6f55 100644 --- a/app/templates/list-item-short.html +++ b/app/templates/list-item-short.html @@ -1,4 +1,4 @@ -
+
<%- title || '(no title)' %><%- description %> -
\ No newline at end of file +
diff --git a/app/templates/menu/menu-item.html b/app/templates/menu/menu-item.html index 81bc8d77..ab0f1859 100644 --- a/app/templates/menu/menu-item.html +++ b/app/templates/menu/menu-item.html @@ -3,7 +3,7 @@ disabled ? 'menu__item--disabled' : '' %> <%= options && options.length ? 'menu__item--with-options' : '' %> <%= cls ? cls : '' %>"> - diff --git a/release-notes.md b/release-notes.md index 05a3b26b..d6babaf5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,11 @@ Release notes ------------- ##### v0.2.0 (not released yet) -Bugfixes and new features +UX improvements, new features, bugfixes `+` improved open page ux `+` keyfiles from Dropbox -`+` #17: option to hide entries from subgroups +`+` #17: option to hide entries from subgroups +`+` #5: groups and entries arranging `+` #13: increase max generated password length `+` #20: default http:// for urls without protocol `-` #12: cannot edit entries without title