edit and remove tags

This commit is contained in:
antelle 2016-04-17 23:02:39 +03:00
parent 7e205e6958
commit 111182458d
14 changed files with 195 additions and 5 deletions

View File

@ -91,7 +91,7 @@ var AppModel = Backbone.Model.extend({
if (this.tags.length) {
this.menu.tagsSection.set('scrollable', true);
this.menu.tagsSection.setItems(this.tags.map(function (tag) {
return {title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag};
return {title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag, editable: true};
}));
} else {
this.menu.tagsSection.set('scrollable', false);
@ -110,6 +110,13 @@ var AppModel = Backbone.Model.extend({
}
},
renameTag: function(from, to) {
this.files.forEach(function(file) {
file.renameTag(from, to);
});
this.updateTags();
},
closeAllFiles: function() {
var that = this;
this.files.each(function(file) {

View File

@ -267,6 +267,19 @@ var EntryModel = Backbone.Model.extend({
this._fillByEntry();
},
renameTag: function(from, to) {
var ix = _.findIndex(this.entry.tags, function(tag) { return tag.toLowerCase() === from.toLowerCase(); });
if (ix < 0) {
return;
}
this._entryModified();
this.entry.tags.splice(ix, 1);
if (to) {
this.entry.tags.push(to);
}
this._fillByEntry();
},
setField: function(field, val) {
var hasValue = val && (typeof val === 'string' || val.isProtected && val.byteLength);
if (hasValue || this.builtInFields.indexOf(field) >= 0) {

View File

@ -458,6 +458,12 @@ var FileModel = Backbone.Model.extend({
var id = kdbxweb.KdbxUuid.random();
this.db.meta.customIcons[id] = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(iconData));
return id.toString();
},
renameTag: function(from, to) {
this.forEachEntry({}, function(entry) {
entry.renameTag(from, to);
});
}
});

View File

@ -75,6 +75,17 @@ var Locale = {
grpAutoType: 'Enable auto-type',
grpAutoTypeSeq: 'Auto-type sequence',
grpAutoTypeSeqDefault: 'Use default auto-type sequence',
grpTrash: 'Delete group with all entries',
tagTitle: 'Tag',
tagTrash: 'Remove tag from all entries',
tagRename: 'Rename',
tagTrashQuestion: 'Remove tag from all entries?',
tagTrashQuestionBody: 'This tag will be removed from all entries. There will be no easy way to put it back.',
tagExists: 'Tag already exists',
tagExistsBody: 'Tag with this name already exists. Please choose another name.',
tagBadName: 'Bad name',
tagBadNameBody: 'Tag name can not contain characters `,`, `;`, `:`. Please remove them.',
keyChangeTitle: 'Master Key Changed',
keyChangeMessage: 'Master key was changed for this database. Please enter a new key',

View File

@ -8,6 +8,7 @@ var Backbone = require('backbone'),
ListWrapView = require('../views/list-wrap-view'),
DetailsView = require('../views/details/details-view'),
GrpView = require('../views/grp-view'),
TagView = require('../views/tag-view'),
OpenView = require('../views/open-view'),
SettingsView = require('../views/settings/settings-view'),
KeyChangeView = require('../views/key-change-view'),
@ -48,6 +49,7 @@ var AppView = Backbone.View.extend({
this.views.details = new DetailsView();
this.views.details.appModel = this.model;
this.views.grp = new GrpView();
this.views.tag = new TagView({ model: this.model });
this.views.menu.listenDrag(this.views.menuDrag);
this.views.list.listenDrag(this.views.listDrag);
@ -66,6 +68,7 @@ var AppView = Backbone.View.extend({
this.listenTo(Backbone, 'toggle-menu', this.toggleMenu);
this.listenTo(Backbone, 'toggle-details', this.toggleDetails);
this.listenTo(Backbone, 'edit-group', this.editGroup);
this.listenTo(Backbone, 'edit-tag', this.editTag);
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
this.listenTo(Backbone, 'user-idle', this.userIdle);
this.listenTo(Backbone, 'app-minimized', this.appMinimized);
@ -94,6 +97,7 @@ var AppView = Backbone.View.extend({
this.views.listDrag.setElement(this.$el.find('.app__list-drag')).render();
this.views.details.setElement(this.$el.find('.app__details')).render();
this.views.grp.setElement(this.$el.find('.app__grp')).render().hide();
this.views.tag.setElement(this.$el.find('.app__tag')).render().hide();
this.showLastOpenFile();
return this;
},
@ -106,6 +110,7 @@ var AppView = Backbone.View.extend({
this.views.listDrag.hide();
this.views.details.hide();
this.views.grp.hide();
this.views.tag.hide();
this.views.footer.toggle(this.model.files.hasOpenFiles());
this.hideSettings();
this.hideOpenFile();
@ -145,6 +150,7 @@ var AppView = Backbone.View.extend({
this.views.listDrag.show();
this.views.details.show();
this.views.grp.hide();
this.views.tag.hide();
this.views.footer.show();
this.hideOpenFile();
this.hideSettings();
@ -182,6 +188,7 @@ var AppView = Backbone.View.extend({
this.views.listDrag.hide();
this.views.details.hide();
this.views.grp.hide();
this.views.tag.hide();
this.hideOpenFile();
this.hideKeyChange();
this.views.settings = new SettingsView({ model: this.model });
@ -198,9 +205,19 @@ var AppView = Backbone.View.extend({
this.views.list.hide();
this.views.listDrag.hide();
this.views.details.hide();
this.views.tag.hide();
this.views.grp.show();
},
showEditTag: function() {
this.views.listWrap.hide();
this.views.list.hide();
this.views.listDrag.hide();
this.views.details.hide();
this.views.grp.hide();
this.views.tag.show();
},
showKeyChange: function(file) {
if (this.views.keyChange || Alerts.alertDisplayed) {
return;
@ -212,6 +229,7 @@ var AppView = Backbone.View.extend({
this.views.listDrag.hide();
this.views.details.hide();
this.views.grp.hide();
this.views.tag.hide();
this.views.keyChange = new KeyChangeView({ model: file });
this.views.keyChange.setElement(this.$el.find('.app__body')).render();
this.views.keyChange.on('accept', this.keyChangeAccept.bind(this));
@ -322,7 +340,7 @@ var AppView = Backbone.View.extend({
menuSelect: function(opt) {
this.model.menu.select(opt);
if (!this.views.grp.isHidden()) {
if (!this.views.grp.isHidden() || !this.views.tag.isHidden()) {
this.showEntries();
}
},
@ -503,6 +521,15 @@ var AppView = Backbone.View.extend({
}
},
editTag: function(tag) {
if (tag && this.views.tag.isHidden()) {
this.showEditTag();
this.views.tag.showTag(tag);
} else {
this.showEntries();
}
},
contextmenu: function(e) {
if (['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0) {
e.preventDefault();

View File

@ -156,7 +156,14 @@ var MenuItemView = Backbone.View.extend({
editItem: function(e) {
if (this.model.get('active') && this.model.get('editable')) {
e.stopPropagation();
Backbone.trigger('edit-group', this.model);
switch (this.model.get('filterKey')) {
case 'tag':
Backbone.trigger('edit-tag', this.model);
break;
case 'group':
Backbone.trigger('edit-group', this.model);
break;
}
}
},

View File

@ -0,0 +1,69 @@
'use strict';
var Backbone = require('backbone'),
Locale = require('../util/locale'),
Alerts = require('../comp/alerts');
var TagView = Backbone.View.extend({
template: require('templates/tag.hbs'),
events: {
'click .tag__buttons-trash': 'moveToTrash',
'click .tag__back-button': 'returnToApp',
'click .tag__btn-rename': 'renameTag'
},
initialize: function() {
this.appModel = this.model;
},
render: function() {
if (this.model) {
this.renderTemplate({
title: this.model.get('title')
}, { plain: true });
}
return this;
},
showTag: function(tag) {
this.model = tag;
this.render();
},
renameTag: function() {
var title = $.trim(this.$el.find('#tag__field-title').val());
if (!title || title === this.model.get('title')) {
return;
}
if (/[;,:]/.test(title)) {
Alerts.error({ header: Locale.tagBadName, body: Locale.tagBadNameBody });
return;
}
if (this.appModel.tags.some(function(t) { return t.toLowerCase() === title.toLowerCase(); })) {
Alerts.error({ header: Locale.tagExists, body: Locale.tagExistsBody });
return;
}
this.appModel.renameTag(this.model.get('title'), title);
Backbone.trigger('select-all');
},
moveToTrash: function() {
this.title = null;
var that = this;
Alerts.yesno({
header: Locale.tagTrashQuestion,
body: Locale.tagTrashQuestionBody,
success: function() {
that.appModel.renameTag(that.model.get('title'), undefined);
Backbone.trigger('select-all');
}
});
},
returnToApp: function() {
Backbone.trigger('edit-tag');
}
});
module.exports = TagView;

View File

@ -87,7 +87,7 @@
}
}
&__grp {
&__grp, &__tag {
@include flex(1);
@include display(flex);
overflow: hidden;

View File

@ -0,0 +1,32 @@
.tag {
@include flex(1);
@include display(flex);
@include align-items(stretch);
@include flex-direction(column);
@include justify-content(flex-start);
width: 100%;
user-select: none;
&__back-button {
cursor: pointer;
position: absolute;
top: 0;
right: $base-padding-h;
padding: $base-padding-v * 2 0 1px 0;
z-index: 1;
}
&__space {
@include flex(1);
}
&__buttons {
@include display(flex);
@include flex-direction(row);
margin-top: $base-padding-v;
&-trash {
@include icon-btn($error:true);
}
}
}

View File

@ -24,6 +24,7 @@
@import "areas/details";
@import "areas/footer";
@import "areas/grp";
@import "areas/tag";
@import "areas/generator";
@import "areas/key-change";
@import "areas/list";

View File

@ -9,6 +9,7 @@
<div class="app__details"></div>
</div>
<div class="app__grp"></div>
<div class="app__tag"></div>
</div>
<div class="app__footer"></div>
</div>

View File

@ -39,7 +39,7 @@
<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>
{{#unless readonly}}
<div class="grp__buttons">
<i class="grp__buttons-trash fa fa-trash-o"></i>
<i class="grp__buttons-trash fa fa-trash-o" title="{{res 'grpTrash'}}" tip-placement="right"></i>
</div>
{{/unless}}
</div>

15
app/templates/tag.hbs Normal file
View File

@ -0,0 +1,15 @@
<div class="tag">
<div class="tag__back-button">
{{res 'retToApp'}} <i class="fa fa-external-link-square"></i>
</div>
<h1>{{res 'tagTitle'}}</h1>
<div class="tag__field">
<label for="tag__field-title">{{Res 'name'}}:</label>
<input type="text" class="input-base" id="tag__field-title" value="{{title}}" size="50" maxlength="128" required />
<button class="tag__btn-rename">{{res 'tagRename'}}</button>
</div>
<div class="tag__space"></div>
<div class="tag__buttons">
<i class="tag__buttons-trash fa fa-trash-o" title="{{res 'tagTrash'}}" tip-placement="right"></i>
</div>
</div>

View File

@ -4,6 +4,7 @@ Release notes
##### v1.2.0 (TBD)
`+` allow selecting attachments with click
`+` save groups collapsed/expanded state
`+` edit and remove tags
`+` register file associations
`-` prevent second app instance on windows