fix #86: context menu

This commit is contained in:
antelle 2016-07-30 12:25:22 +03:00
parent f3e1ccdb3c
commit 389101a70f
8 changed files with 112 additions and 30 deletions

View File

@ -111,10 +111,10 @@ var Locale = {
searchCreated: 'Created',
searchUpdated: 'Updated',
searchAttachments: 'Attachments',
searchAZ: 'A → Z',
searchZA: 'Z → A',
searchON: 'Old → New',
searchNO: 'New → Old',
searchAZ: 'A {} Z',
searchZA: 'Z {} A',
searchON: 'Old {} New',
searchNO: 'New {} Old',
searchShiftClickOr: 'shift-click or',
searchAdvTitle: 'Toggle advanced search',
searchSearchIn: 'Search in',
@ -221,6 +221,8 @@ var Locale = {
detMenuShowEmpty: 'Show empty fields',
detMenuHideEmpty: 'Hide empty fields',
detMenuAddField: 'Add {}',
detMenuCopyPassword: 'Copy password',
detMenuCopyUser: 'Copy user',
detSetupOtp: 'One-time passwords',
detAutoType: 'Auto-type',
detAutoTypeEnabled: 'Enable auto-type for this entry',

View File

@ -12,6 +12,7 @@ var Backbone = require('backbone'),
OpenView = require('../views/open-view'),
SettingsView = require('../views/settings/settings-view'),
KeyChangeView = require('../views/key-change-view'),
DropdownView = require('../views/dropdown-view'),
Alerts = require('../comp/alerts'),
Keys = require('../const/keys'),
Timeouts = require('../const/timeouts'),
@ -28,7 +29,7 @@ var AppView = Backbone.View.extend({
template: require('templates/app.hbs'),
events: {
'contextmenu': 'contextmenu',
'contextmenu': 'contextMenu',
'drop': 'drop',
'dragover': 'dragover',
'click a[target=_blank]': 'extLinkClick',
@ -74,6 +75,7 @@ var AppView = Backbone.View.extend({
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
this.listenTo(Backbone, 'user-idle', this.userIdle);
this.listenTo(Backbone, 'app-minimized', this.appMinimized);
this.listenTo(Backbone, 'show-context-menu', this.showContextMenu);
this.listenTo(UpdateModel.instance, 'change:updateReady', this.updateApp);
@ -105,6 +107,7 @@ var AppView = Backbone.View.extend({
},
showOpenFile: function() {
this.hideContextMenu();
this.views.menu.hide();
this.views.menuDrag.hide();
this.views.listWrap.hide();
@ -546,12 +549,46 @@ var AppView = Backbone.View.extend({
}
},
contextmenu: function(e) {
if (['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0) {
isContextMenuAllowed(e) {
return ['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0;
},
contextMenu: function(e) {
if (this.isContextMenuAllowed(e)) {
e.preventDefault();
}
},
showContextMenu: function(e) {
if (e.options && this.isContextMenuAllowed(e)) {
e.stopImmediatePropagation();
e.preventDefault();
if (this.views.contextMenu) {
this.views.contextMenu.remove();
}
let menu = new DropdownView({ model: e });
menu.render({
position: { left: e.pageX, top: e.pageY },
options: e.options
});
menu.on('cancel', e => this.hideContextMenu());
menu.on('select', e => this.contextMenuSelect(e));
this.views.contextMenu = menu;
}
},
hideContextMenu: function() {
if (this.views.contextMenu) {
this.views.contextMenu.remove();
delete this.views.contextMenu;
}
},
contextMenuSelect: function(e) {
this.hideContextMenu();
Backbone.trigger('context-menu-select', e);
},
dragover: function(e) {
e.preventDefault();
},

View File

@ -58,7 +58,8 @@ var DetailsView = Backbone.View.extend({
'change .details__attachment-input-file': 'attachmentFileChange',
'dragover .details': 'dragover',
'dragleave .details': 'dragleave',
'drop .details': 'drop'
'drop .details': 'drop',
'contextmenu .details': 'contextMenu'
},
initialize: function () {
@ -70,6 +71,7 @@ var DetailsView = Backbone.View.extend({
this.listenTo(Backbone, 'copy-user', this.copyUserName);
this.listenTo(Backbone, 'copy-url', this.copyUrl);
this.listenTo(Backbone, 'toggle-settings', this.settingsToggled);
this.listenTo(Backbone, 'context-menu-select', this.contextMenuSelect);
this.listenTo(OtpQrReqder, 'qr-read', this.otpCodeRead);
this.listenTo(OtpQrReqder, 'enter-manually', this.otpEnterManually);
KeyHandler.onKey(Keys.DOM_VK_C, this.copyPassword, this, KeyHandler.SHORTCUT_ACTION, false, true);
@ -751,6 +753,31 @@ var DetailsView = Backbone.View.extend({
Backbone.trigger('toggle-details', false);
},
contextMenu(e) {
var canCopy = document.queryCommandSupported('copy');
let options = [];
if (canCopy) {
options.push({ value: 'det-copy-password', icon: 'clipboard', text: Locale.detMenuCopyPassword });
options.push({ value: 'det-copy-user', icon: 'clipboard', text: Locale.detMenuCopyUser });
}
options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField });
Backbone.trigger('show-context-menu', _.extend(e, { options }));
},
contextMenuSelect(e) {
switch (e.item) {
case 'det-copy-password':
this.copyPassword();
break;
case 'det-copy-user':
this.copyUserName();
break;
case 'det-add-new':
this.addNewField();
break;
}
},
setupOtp: function() {
OtpQrReqder.read();
},

View File

@ -1,8 +1,8 @@
'use strict';
var Backbone = require('backbone');
const Backbone = require('backbone');
var DropdownView = Backbone.View.extend({
let DropdownView = Backbone.View.extend({
template: require('templates/dropdown.hbs'),
events: {
@ -11,26 +11,38 @@ var DropdownView = Backbone.View.extend({
initialize: function () {
this.bodyClick = this.bodyClick.bind(this);
$('body').on('click', this.bodyClick);
this.listenTo(Backbone, 'show-context-menu', this.bodyClick);
$('body').on('click contextmenu keyup', this.bodyClick);
},
render: function (config) {
this.options = config.options;
this.renderTemplate(config);
this.$el.appendTo(document.body);
var ownRect = this.$el[0].getBoundingClientRect();
var left = config.position.left || (config.position.right - ownRect.right + ownRect.left);
this.$el.css({ top: config.position.top, left: left });
let ownRect = this.$el[0].getBoundingClientRect();
let bodyRect = document.body.getBoundingClientRect();
let left = config.position.left || (config.position.right - ownRect.right + ownRect.left);
let top = config.position.top;
if (left + ownRect.width > bodyRect.right) {
left = Math.max(0, bodyRect.right - ownRect.width);
}
if (top + ownRect.height > bodyRect.bottom) {
top = Math.max(0, bodyRect.bottom - ownRect.height);
}
this.$el.css({ top: top, left: left });
return this;
},
remove: function() {
$('body').off('click', this.bodyClick);
this.viewRemoved = true;
$('body').off('click contextmenu keyup', this.bodyClick);
Backbone.View.prototype.remove.apply(this, arguments);
},
bodyClick: function() {
this.trigger('cancel');
if (!this.viewRemoved) {
this.trigger('cancel');
}
},
itemClick: function(e) {

View File

@ -32,16 +32,16 @@ var ListSearchView = Backbone.View.extend({
initialize: function () {
this.sortOptions = [
{ value: 'title', icon: 'sort-alpha-asc', text: Locale.searchTitle + ' ' + Locale.searchAZ },
{ value: '-title', icon: 'sort-alpha-desc', text: Locale.searchTitle + ' ' + Locale.searchZA },
{ value: 'website', icon: 'sort-alpha-asc', text: Locale.searchWebsite + ' ' + Locale.searchAZ },
{ value: '-website', icon: 'sort-alpha-desc', text: Locale.searchWebsite + ' ' + Locale.searchZA },
{ value: 'user', icon: 'sort-alpha-asc', text: Locale.searchUser + ' ' + Locale.searchAZ },
{ value: '-user', icon: 'sort-alpha-desc', text: Locale.searchUser + ' ' + Locale.searchZA },
{ value: 'created', icon: 'sort-numeric-asc', text: Locale.searchCreated + ' ' + Locale.searchON },
{ value: '-created', icon: 'sort-numeric-desc', text: Locale.searchCreated + ' ' + Locale.searchNO },
{ value: 'updated', icon: 'sort-numeric-asc', text: Locale.searchUpdated + ' ' + Locale.searchON },
{ value: '-updated', icon: 'sort-numeric-desc', text: Locale.searchUpdated + ' ' + Locale.searchNO },
{ value: 'title', icon: 'sort-alpha-asc', text: Locale.searchTitle + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-title', icon: 'sort-alpha-desc', text: Locale.searchTitle + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'website', icon: 'sort-alpha-asc', text: Locale.searchWebsite + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-website', icon: 'sort-alpha-desc', text: Locale.searchWebsite + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'user', icon: 'sort-alpha-asc', text: Locale.searchUser + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-user', icon: 'sort-alpha-desc', text: Locale.searchUser + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'created', icon: 'sort-numeric-asc', text: Locale.searchCreated + ' ' + this.addArrow(Locale.searchON) },
{ value: '-created', icon: 'sort-numeric-desc', text: Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) },
{ value: 'updated', icon: 'sort-numeric-asc', text: Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) },
{ value: '-updated', icon: 'sort-numeric-desc', text: Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) },
{ value: '-attachments', icon: 'sort-amount-desc', text: Locale.searchAttachments }
];
this.sortIcons = {};
@ -282,6 +282,10 @@ var ListSearchView = Backbone.View.extend({
this.trigger('create-group');
break;
}
},
addArrow(str) {
return str.replace('{}', '&rarr;');
}
});

View File

@ -5,7 +5,7 @@ html {
}
body {
overflow: auto;
overflow: hidden;
cursor: default;
position: fixed;
-webkit-overflow-scrolling: touch;

View File

@ -4,13 +4,12 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/bower_components" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/npm-shrinkwrap.json" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

View File

@ -2,6 +2,7 @@ Release notes
-------------
##### v1.3.0 (TBD)
`+` context menu
`+` solarized themes
`+` select field contents on search hotkey
`+` option to preload default config