mirror of https://github.com/keeweb/keeweb.git
webdav settings ui
This commit is contained in:
parent
1c17bcc8c5
commit
4a13d4f130
|
@ -5,6 +5,7 @@ var Launcher = require('../comp/launcher');
|
|||
var Storage = {
|
||||
file: require('./storage-file'),
|
||||
dropbox: require('./storage-dropbox'),
|
||||
webdav: require('./storage-webdav'),
|
||||
cache: Launcher ? require('./storage-file-cache') : require('./storage-cache')
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ var logger = new Logger('storage-dropbox');
|
|||
|
||||
var StorageDropbox = {
|
||||
name: 'dropbox',
|
||||
icon: 'dropbox',
|
||||
enabled: true,
|
||||
|
||||
_convertError: function(err) {
|
||||
|
|
|
@ -9,6 +9,7 @@ var fileWatchers = {};
|
|||
|
||||
var StorageFile = {
|
||||
name: 'file',
|
||||
icon: 'hdd-o',
|
||||
enabled: !!Launcher,
|
||||
|
||||
load: function(path, callback) {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
'use strict';
|
||||
|
||||
var Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('storage-webdav');
|
||||
|
||||
var StorageDropbox = {
|
||||
name: 'webdav',
|
||||
icon: 'server',
|
||||
enabled: true,
|
||||
|
||||
openFields: [
|
||||
{ id: 'url', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
|
||||
{ id: 'user', title: 'openUser', placeholder: 'openUserPlaceholder', type: 'text' },
|
||||
{ id: 'password', title: 'openPass', placeholder: 'openPassPlaceholder', type: 'password' }
|
||||
],
|
||||
|
||||
load: function(path, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
var stat = {};
|
||||
logger.debug('Loaded', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
},
|
||||
|
||||
stat: function(path, callback) {
|
||||
logger.debug('Stat', path);
|
||||
var ts = logger.ts();
|
||||
var stat = {};
|
||||
logger.debug('Stated', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
},
|
||||
|
||||
save: function(path, data, callback, rev) {
|
||||
logger.debug('Save', path, rev);
|
||||
var ts = logger.ts();
|
||||
logger.debug('Saved', path, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = StorageDropbox;
|
|
@ -19,6 +19,11 @@ var Locale = {
|
|||
or: 'or',
|
||||
notImplemented: 'Not Implemented',
|
||||
|
||||
cache: 'cache',
|
||||
file: 'file',
|
||||
webdav: 'WebDAV',
|
||||
dropbox: 'Dropbox',
|
||||
|
||||
menuAllItems: 'All Items',
|
||||
menuColors: 'Colors',
|
||||
menuTags: 'Tags',
|
||||
|
@ -126,6 +131,14 @@ var Locale = {
|
|||
openWrongFileBody: 'This file format is not supported. This app works with KeePass database format files (KDBX).',
|
||||
openKdbFileBody: 'You are opening an old version format file (KDB). This app supports only new format (KDBX), ' +
|
||||
'please use KeePass v2 to convert between them.',
|
||||
openConfigHeader: '{} Settings',
|
||||
openUrl: 'URL',
|
||||
openUrlDesc: 'https://server/path/file.kdbx, or just file.kdbx',
|
||||
openUser: 'Username',
|
||||
openUserPlaceholder: 'no username',
|
||||
openPass: 'Password',
|
||||
openPassPlaceholder: 'no password',
|
||||
openConfigError: 'Error: {}',
|
||||
|
||||
detAttDownload: 'Shift-click attachment button to download or ',
|
||||
detAttDelToRemove: 'Delete to remove',
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
var Backbone = require('backbone'),
|
||||
Locale = require('../util/locale'),
|
||||
Keys = require('../const/keys');
|
||||
|
||||
var OpenConfigView = Backbone.View.extend({
|
||||
template: require('templates/open-config.hbs'),
|
||||
|
||||
events: {
|
||||
'click .open__config-btn-cancel': 'cancel',
|
||||
'click .open__config-btn-ok': 'apply',
|
||||
'input input': 'changeInput',
|
||||
'keyup input': 'keyup'
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.renderTemplate(this.model);
|
||||
this.$el.find(':input:first').focus();
|
||||
this.checkValidity();
|
||||
return this;
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this.trigger('cancel');
|
||||
},
|
||||
|
||||
apply: function() {
|
||||
var data = this.getData();
|
||||
if (data) {
|
||||
this.trigger('apply', data);
|
||||
}
|
||||
},
|
||||
|
||||
changeInput: function() {
|
||||
this.checkValidity();
|
||||
},
|
||||
|
||||
keyup: function(e) {
|
||||
if (e.which === Keys.DOM_VK_RETURN) {
|
||||
this.apply();
|
||||
}
|
||||
},
|
||||
|
||||
checkValidity: function() {
|
||||
var isValid = this.getData();
|
||||
this.$el.find('.open__config-btn-ok').prop('disabled', !isValid);
|
||||
},
|
||||
|
||||
getData: function() {
|
||||
var data = { storage: this.model.id };
|
||||
this.model.fields.every(function(field) {
|
||||
var input = this.$el.find('#open__config-field-' + field.id)[0];
|
||||
if (data && input.checkValidity()) {
|
||||
data[field.id] = input.value;
|
||||
} else {
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, this);
|
||||
return data;
|
||||
},
|
||||
|
||||
setDisabled: function(disabled) {
|
||||
disabled = !!disabled;
|
||||
this.$el.find(':input:not(.open__config-btn-cancel)').prop('disabled', disabled);
|
||||
this.$el.toggleClass('open__config--disabled', disabled);
|
||||
if (disabled) {
|
||||
this.$el.find('.open__config-error').text('');
|
||||
}
|
||||
},
|
||||
|
||||
setError: function(err) {
|
||||
this.$el.find('.open__config-error').text(Locale.openConfigError.replace('{}', err));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = OpenConfigView;
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
var Backbone = require('backbone'),
|
||||
kdbxweb = require('kdbxweb'),
|
||||
OpenConfigView = require('./open-config-view'),
|
||||
Keys = require('../const/keys'),
|
||||
Alerts = require('../comp/alerts'),
|
||||
SecureInput = require('../comp/secure-input'),
|
||||
DropboxLink = require('../comp/dropbox-link'),
|
||||
Logger = require('../util/logger'),
|
||||
Locale = require('../util/locale');
|
||||
Locale = require('../util/locale'),
|
||||
Storage = require('../storage');
|
||||
|
||||
var logger = new Logger('open-view');
|
||||
|
||||
|
@ -22,6 +24,7 @@ var OpenView = Backbone.View.extend({
|
|||
'click .open__icon-import-xml': 'importFromXml',
|
||||
'click .open__icon-demo': 'createDemo',
|
||||
'click .open__icon-more': 'toggleMore',
|
||||
'click .open__icon-webdav': 'toggleWebDav',
|
||||
'click .open__pass-input[readonly]': 'openFile',
|
||||
'input .open__pass-input': 'inputInput',
|
||||
'keydown .open__pass-input': 'inputKeydown',
|
||||
|
@ -35,11 +38,13 @@ var OpenView = Backbone.View.extend({
|
|||
'drop': 'drop'
|
||||
},
|
||||
|
||||
views: null,
|
||||
params: null,
|
||||
passwordInput: null,
|
||||
busy: false,
|
||||
|
||||
initialize: function () {
|
||||
this.views = {};
|
||||
this.params = {
|
||||
id: null,
|
||||
name: '',
|
||||
|
@ -65,17 +70,10 @@ var OpenView = Backbone.View.extend({
|
|||
|
||||
getLastOpenFiles: function() {
|
||||
return this.model.fileInfos.map(function(f) {
|
||||
var icon;
|
||||
switch (f.get('storage')) {
|
||||
case 'dropbox':
|
||||
icon = 'dropbox';
|
||||
break;
|
||||
case 'file':
|
||||
icon = 'hdd-o';
|
||||
break;
|
||||
default:
|
||||
icon = 'file-text';
|
||||
break;
|
||||
var icon = 'file-text';
|
||||
var storage = f.get('storage');
|
||||
if (Storage[storage] && Storage[storage].icon) {
|
||||
icon = Storage[storage].icon;
|
||||
}
|
||||
return {
|
||||
id: f.get('id'),
|
||||
|
@ -223,12 +221,14 @@ var OpenView = Backbone.View.extend({
|
|||
|
||||
openFile: function() {
|
||||
if (!this.busy) {
|
||||
this.closeConfig();
|
||||
this.openAny('fileData');
|
||||
}
|
||||
},
|
||||
|
||||
importFromXml: function() {
|
||||
if (!this.busy) {
|
||||
this.closeConfig();
|
||||
this.openAny('fileXml', 'xml');
|
||||
}
|
||||
},
|
||||
|
@ -355,9 +355,13 @@ var OpenView = Backbone.View.extend({
|
|||
|
||||
drop: function(e) {
|
||||
e.preventDefault();
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
if (this.dragTimeout) {
|
||||
clearTimeout(this.dragTimeout);
|
||||
}
|
||||
this.closeConfig();
|
||||
this.$el.removeClass('open--drag');
|
||||
var files = e.target.files || e.originalEvent.dataTransfer.files;
|
||||
var dataFile = _.find(files, function(file) { return file.name.split('.').pop().toLowerCase() === 'kdbx'; });
|
||||
|
@ -376,6 +380,7 @@ var OpenView = Backbone.View.extend({
|
|||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
this.closeConfig();
|
||||
var that = this;
|
||||
DropboxLink.authenticate(function(err) {
|
||||
if (err) {
|
||||
|
@ -467,6 +472,7 @@ var OpenView = Backbone.View.extend({
|
|||
|
||||
createDemo: function() {
|
||||
if (!this.busy) {
|
||||
this.closeConfig();
|
||||
if (!this.model.createDemoFile()) {
|
||||
this.trigger('close');
|
||||
}
|
||||
|
@ -515,7 +521,75 @@ var OpenView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
toggleMore: function() {
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
this.closeConfig();
|
||||
this.$el.find('.open__icons--lower').toggleClass('hide');
|
||||
},
|
||||
|
||||
toggleWebDav: function() {
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
this.$el.find('.open__icons--lower').addClass('hide');
|
||||
this.$el.find('.open__pass-area').addClass('hide');
|
||||
this.showConfig(Storage.webdav);
|
||||
},
|
||||
|
||||
showConfig: function(storage) {
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
if (this.views.openConfig) {
|
||||
this.views.openConfig.remove();
|
||||
}
|
||||
var config = {
|
||||
id: storage.name,
|
||||
name: Locale[storage.name] || storage.name,
|
||||
icon: storage.icon,
|
||||
fields: storage.openFields
|
||||
};
|
||||
this.views.openConfig = new OpenConfigView({ el: this.$el.find('.open__config-wrap'), model: config }).render();
|
||||
this.views.openConfig.on('cancel', this.closeConfig.bind(this));
|
||||
this.views.openConfig.on('apply', this.applyConfig.bind(this));
|
||||
},
|
||||
|
||||
closeConfig: function() {
|
||||
if (this.busy) {
|
||||
this.storageWaitId = null;
|
||||
this.busy = false;
|
||||
}
|
||||
if (this.views.openConfig) {
|
||||
this.views.openConfig.remove();
|
||||
delete this.views.openConfig;
|
||||
}
|
||||
this.$el.find('.open__pass-area').removeClass('hide');
|
||||
this.$el.find('.open__config').addClass('hide');
|
||||
this.inputEl.focus();
|
||||
},
|
||||
|
||||
applyConfig: function(config) {
|
||||
if (this.busy || !config) {
|
||||
return;
|
||||
}
|
||||
this.busy = true;
|
||||
this.views.openConfig.setDisabled(true);
|
||||
var storage = Storage[config.storage];
|
||||
this.storageWaitId = Math.random();
|
||||
storage.stat(config, this.storageStatComplete.bind(this, this.storageWaitId));
|
||||
},
|
||||
|
||||
storageStatComplete: function(waitId, err) {
|
||||
if (this.storageWaitId !== waitId) {
|
||||
return;
|
||||
}
|
||||
this.storageWaitId = null;
|
||||
this.busy = false;
|
||||
this.views.openConfig.setDisabled(false);
|
||||
if (err) {
|
||||
this.views.openConfig.setError(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -73,6 +73,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__config {
|
||||
@include display(flex);
|
||||
@include align-items(stretch);
|
||||
@include flex-direction(column);
|
||||
@include justify-content(flex-start);
|
||||
position: relative;
|
||||
width: 30em;
|
||||
.open--drag & {
|
||||
display: none;
|
||||
}
|
||||
&-buttons {
|
||||
@include align-self(flex-end);
|
||||
}
|
||||
&-header {
|
||||
>i { margin-right: .3em; }
|
||||
}
|
||||
&-field {
|
||||
width: 100%;
|
||||
}
|
||||
&:not(.open__config--disabled) {
|
||||
.open__config-btn-ok-text { display: inline; }
|
||||
.open__config-btn-ok-spinner { display: none; }
|
||||
}
|
||||
&.open__config--disabled {
|
||||
.open__config-btn-ok-text { display: none; }
|
||||
.open__config-btn-ok-spinner { display: block; }
|
||||
}
|
||||
}
|
||||
|
||||
input[type=password].open__pass-input {
|
||||
font-size: $large-pass-font-size;
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<div class="open__config">
|
||||
<h2 class="open__config-header"><i class="fa fa-{{icon}}"></i> {{#res 'openConfigHeader'}}{{name}}{{/res}}</h2>
|
||||
<div class="open__config-fields">
|
||||
{{#each fields as |field ix|}}
|
||||
<label for="open__config-field-{{id}}">{{res title}}:</label>
|
||||
{{#if desc}}<div class="open__config-field-desc muted-color">{{res desc}}</div>{{/if}}
|
||||
<input type="{{type}}" class="open__config-field input-base" id="open__config-field-{{id}}"
|
||||
autocomplete="off"
|
||||
{{#if placeholder}}placeholder="{{res placeholder}}"{{/if}}
|
||||
{{#if required}}required{{/if}}
|
||||
{{#if pattern}}pattern="{{pattern}}"{{/if}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="open__config-error error-color"></div>
|
||||
<div class="open__config-buttons">
|
||||
<button class="btn-silent open__config-btn-cancel">{{res 'alertCancel'}}</button>
|
||||
<button class="open__config-btn-ok">
|
||||
<span class="open__config-btn-ok-text">{{res 'alertOk'}}</span>
|
||||
<i class="open__config-btn-ok-spinner fa fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
|
@ -67,6 +67,8 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="open__config-wrap">
|
||||
</div>
|
||||
<div class="open__dropzone">
|
||||
<i class="fa fa-lock muted-color open__dropzone-icon"></i>
|
||||
<h1 class="muted-color open__dropzone-header">{{res 'openDropHere'}}</h1>
|
||||
|
|
Loading…
Reference in New Issue