diff --git a/app/scripts/storage/index.js b/app/scripts/storage/index.js index 0b43c9f5..ca3a0c11 100644 --- a/app/scripts/storage/index.js +++ b/app/scripts/storage/index.js @@ -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') }; diff --git a/app/scripts/storage/storage-dropbox.js b/app/scripts/storage/storage-dropbox.js index 98127c10..7cf97990 100644 --- a/app/scripts/storage/storage-dropbox.js +++ b/app/scripts/storage/storage-dropbox.js @@ -7,6 +7,7 @@ var logger = new Logger('storage-dropbox'); var StorageDropbox = { name: 'dropbox', + icon: 'dropbox', enabled: true, _convertError: function(err) { diff --git a/app/scripts/storage/storage-file.js b/app/scripts/storage/storage-file.js index cbd9a7dc..e519ea8d 100644 --- a/app/scripts/storage/storage-file.js +++ b/app/scripts/storage/storage-file.js @@ -9,6 +9,7 @@ var fileWatchers = {}; var StorageFile = { name: 'file', + icon: 'hdd-o', enabled: !!Launcher, load: function(path, callback) { diff --git a/app/scripts/storage/storage-webdav.js b/app/scripts/storage/storage-webdav.js new file mode 100644 index 00000000..ed95ac77 --- /dev/null +++ b/app/scripts/storage/storage-webdav.js @@ -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; diff --git a/app/scripts/util/locale.js b/app/scripts/util/locale.js index dafd9d40..505630cc 100644 --- a/app/scripts/util/locale.js +++ b/app/scripts/util/locale.js @@ -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', diff --git a/app/scripts/views/open-config-view.js b/app/scripts/views/open-config-view.js new file mode 100644 index 00000000..0acdeb77 --- /dev/null +++ b/app/scripts/views/open-config-view.js @@ -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; diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index fdf2bd7c..707864ec 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -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); + } } }); diff --git a/app/styles/areas/_open.scss b/app/styles/areas/_open.scss index 82ca8e66..46139146 100644 --- a/app/styles/areas/_open.scss +++ b/app/styles/areas/_open.scss @@ -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; diff --git a/app/templates/open-config.hbs b/app/templates/open-config.hbs new file mode 100644 index 00000000..40938e94 --- /dev/null +++ b/app/templates/open-config.hbs @@ -0,0 +1,23 @@ +
+

{{#res 'openConfigHeader'}}{{name}}{{/res}}

+
+ {{#each fields as |field ix|}} + + {{#if desc}}
{{res desc}}
{{/if}} + + {{/each}} +
+
+
+ + +
+
diff --git a/app/templates/open.hbs b/app/templates/open.hbs index d0eab308..c255e4df 100644 --- a/app/templates/open.hbs +++ b/app/templates/open.hbs @@ -67,6 +67,8 @@ {{/each}} +
+

{{res 'openDropHere'}}