mirror of https://github.com/keeweb/keeweb.git
edit and remove tags
This commit is contained in:
parent
7e205e6958
commit
111182458d
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -87,7 +87,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__grp {
|
||||
&__grp, &__tag {
|
||||
@include flex(1);
|
||||
@include display(flex);
|
||||
overflow: hidden;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue