mirror of https://github.com/keeweb/keeweb.git
fix #86: context menu
This commit is contained in:
parent
f3e1ccdb3c
commit
389101a70f
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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('{}', '→');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ html {
|
|||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
position: fixed;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue