This commit is contained in:
Antelle 2015-10-25 19:27:34 +03:00
parent 8bc020c5b0
commit b90b31297e
11 changed files with 203 additions and 23 deletions

View File

@ -4,7 +4,6 @@
# FUTURE
- [ ] dropbox
- [ ] trash: groups/empty/untrash
- [ ] move groups/entries
- [ ] help/tips

View File

@ -3,10 +3,16 @@
var AppModel = require('./models/app-model'),
AppView = require('./views/app-view'),
KeyHandler = require('./comp/key-handler'),
Alerts = require('./comp/alerts');
Alerts = require('./comp/alerts'),
DropboxLink = require('./comp/dropbox-link');
$(function() {
require('./mixins/view');
if (location.href.indexOf('state=') >= 0) {
DropboxLink.receive();
return;
}
KeyHandler.init();
if (['https:', 'file:', 'app:'].indexOf(location.protocol) < 0) {
Alerts.error({ header: 'Not Secure!', icon: 'user-secret', esc: false, enter: false, click: false,

View File

@ -0,0 +1,64 @@
'use strict';
var Dropbox = require('dropbox');
var DropboxKeys = {
AppFolder: 'qp7ctun6qt5n9d6'
};
var DropboxLink = {
_getClient: function(complete) {
if (this._dropboxClient) {
complete(null, this._dropboxClient);
return;
}
var client = new Dropbox.Client({ key: DropboxKeys.AppFolder });
client.authDriver(new Dropbox.AuthDriver.Popup({ receiverUrl: location.href }));
client.authenticate((function(error, client) {
if (!error) {
this._dropboxClient = client;
}
complete(error, client);
}).bind(this));
},
receive: function() {
Dropbox.AuthDriver.Popup.oauthReceiver();
},
saveFile: function(fileName, data, overwrite, complete) {
this._getClient(function(err, client) {
if (err) { return complete(err); }
if (!overwrite) {
client.readdir('', function(err, files) {
if (err) { return complete(err); }
var exists = files.some(function(file) { return file.toLowerCase() === fileName.toLowerCase(); });
if (exists) { return complete({ exists: true }); }
client.writeFile(fileName, data, complete);
});
} else {
client.writeFile(fileName, data, complete);
}
});
},
openFile: function(fileName, complete) {
this._getClient(function(err, client) {
if (err) { return complete(err); }
client.readFile(fileName, { blob: true }, complete);
});
},
getFileList: function(complete) {
this._getClient(function(err, client) {
if (err) { return complete(err); }
client.readdir('', function(err, files) {
if (err) { return complete(err); }
files = files.filter(function(f) { return /\.kdbx$/i.test(f); });
complete(null, files);
});
});
}
};
module.exports = DropboxLink;

View File

@ -13,6 +13,7 @@ var FileModel = Backbone.Model.extend({
keyFileName: '',
passwordLength: 0,
path: '',
storage: null,
modified: false,
open: false,
opening: false,
@ -23,7 +24,8 @@ var FileModel = Backbone.Model.extend({
oldPasswordLength: 0,
oldKeyFileName: '',
passwordChanged: false,
keyFileChanged: false
keyFileChanged: false,
syncing: false
},
db: null,
@ -169,8 +171,8 @@ var FileModel = Backbone.Model.extend({
return this.db.saveXml();
},
saved: function(path) {
this.set({ path: path || '', modified: false, created: false });
saved: function(path, storage) {
this.set({ path: path || '', storage: storage || null, modified: false, created: false, syncing: false });
this.setOpenFile({ passwordLength: this.get('passwordLength') });
this.forEachEntry({}, function(entry) {
entry.unsaved = false;

View File

@ -3,7 +3,9 @@
var Backbone = require('backbone'),
Keys = require('../const/keys'),
Alerts = require('../comp/alerts'),
SecureInput = require('../comp/secure-input');
SecureInput = require('../comp/secure-input'),
Launcher = require('../comp/launcher'),
DropboxLink = require('../comp/dropbox-link');
var OpenFileView = Backbone.View.extend({
template: require('templates/open-file.html'),
@ -12,6 +14,7 @@ var OpenFileView = Backbone.View.extend({
'click .open__file-btn-new': 'createNew',
'click .open__file-link-open': 'openFile',
'click .open__file-link-new': 'createNew',
'click .open__file-link-dropbox': 'openFromDropbox',
'click .open__file-link-demo': 'createDemo',
'click .open__file-link-name': 'resetFile',
'click .open__file-btn-key': 'openKeyFile',
@ -25,6 +28,7 @@ var OpenFileView = Backbone.View.extend({
fileData: null,
keyFileData: null,
passwordInput: null,
dropboxLoading: false,
initialize: function () {
this.fileData = null;
@ -34,7 +38,10 @@ var OpenFileView = Backbone.View.extend({
},
render: function () {
this.renderTemplate(this.model.attributes);
this.renderTemplate($.extend({
supportsDropbox: !Launcher,
dropboxLoading: this.dropboxLoading
}, this.model.attributes));
this.inputEl = this.$el.find('.open__file-input');
this.passwordInput.setElement(this.inputEl);
if (this.inputEl.attr('autofocus')) {
@ -97,7 +104,7 @@ var OpenFileView = Backbone.View.extend({
if (this.reading === 'fileData') {
this.model.set('name', file.name.replace(/\.\w+$/i, ''));
if (file.path) {
this.model.set('path', file.path);
this.model.set({ path: file.path, storage: file.storage || 'file' });
}
} else {
this.model.set('keyFileName', file.name);
@ -171,6 +178,53 @@ var OpenFileView = Backbone.View.extend({
if (!this.model.get('opening')) {
this.trigger('create-demo');
}
},
openFromDropbox: function() {
this.dropboxLoading = true;
this.render();
DropboxLink.getFileList((function(err, files) {
this.dropboxLoading = false;
if (err) { return; }
var buttons = [];
files.forEach(function(file) {
buttons.push({ result: file, title: file.replace(/\.kdbx/i, '') });
});
buttons.push({ result: '', title: 'cancel' });
Alerts.alert({
header: 'Select a file',
body: 'Select a file from your Dropbox which you would like to open',
icon: 'dropbox',
buttons: buttons,
esc: '',
click: '',
success: this.openDropboxFile.bind(this),
cancel: this.cancelOpenDropboxFile.bind(this)
});
}).bind(this));
},
openDropboxFile: function(file) {
this.dropboxLoading = true;
DropboxLink.openFile(file, (function(err, data) {
this.dropboxLoading = false;
if (err || !data || !data.size) {
this.render();
Alerts.error({ header: 'Failed to read file', body: 'Error reading Dropbox file: \n' + err });
return;
}
Object.defineProperties(data, {
storage: { value: 'dropbox' },
path: { value: file },
name: { value: file.replace(/\.kdbx/i, '') }
});
this.setFile(data);
}).bind(this));
},
cancelOpenDropboxFile: function() {
this.dropboxLoading = false;
this.render();
}
});

View File

@ -8,6 +8,7 @@ var Backbone = require('backbone'),
RuntimeInfo = require('../../comp/runtime-info'),
Launcher = require('../../comp/launcher'),
Links = require('../../const/links'),
DropboxLink = require('../../comp/dropbox-link'),
kdbxweb = require('kdbxweb'),
FileSaver = require('filesaver');
@ -17,7 +18,7 @@ var SettingsAboutView = Backbone.View.extend({
events: {
'click .settings__file-button-save-file': 'saveToFile',
'click .settings__file-button-export-xml': 'exportAsXml',
'click .settings__file-button-save-dropbox': 'saveToDropbox',
'click .settings__file-button-save-dropbox': 'saveToDropboxClick',
'change #settings__file-key-file': 'keyFileChange',
'mousedown #settings__file-file-select-link': 'triggerSelectFile',
'change #settings__file-file-select': 'fileSelected',
@ -37,11 +38,13 @@ var SettingsAboutView = Backbone.View.extend({
render: function() {
this.renderTemplate({
cmd: FeatureDetector.actionShortcutSymbol(true),
supportFiles: RuntimeInfo.launcher,
supportFiles: !!Launcher,
supportsDropbox: !Launcher,
desktopLink: Links.Desktop,
name: this.model.get('name'),
path: this.model.get('path'),
storage: this.model.get('storage'),
password: PasswordGenerator.present(this.model.get('passwordLength')),
defaultUser: this.model.get('defaultUser'),
recycleBinEnabled: this.model.get('recycleBinEnabled'),
@ -119,7 +122,7 @@ var SettingsAboutView = Backbone.View.extend({
saveToFileWithPath: function(path, data) {
try {
Launcher.writeFile(path, data);
this.model.saved(path);
this.model.saved(path, 'file');
if (!AppSettingsModel.instance.get('lastOpenFile')) {
AppSettingsModel.instance.set('lastOpenFile', path);
}
@ -140,11 +143,44 @@ var SettingsAboutView = Backbone.View.extend({
FileSaver.saveAs(blob, this.model.get('name') + '.xml');
},
saveToDropbox: function() {
saveToDropboxClick: function() {
var nameChanged = this.model.get('path') !== this.model.get('name') + '.kdbx',
canOverwrite = !nameChanged;
this.saveToDropbox(canOverwrite);
},
saveToDropbox: function(overwrite) {
if (!this.validate()) {
return;
}
Alerts.notImplemented();
var data = this.model.getData();
var fileName = this.model.get('name') + '.kdbx';
this.model.set('syncing', true);
DropboxLink.saveFile(fileName, data, overwrite, (function(err) {
if (err) {
this.model.set('syncing', false);
if (err.exists) {
Alerts.alert({
header: 'Already exists',
body: 'File ' + fileName + ' already exists in your Dropbox.',
icon: 'question',
buttons: [{result: 'yes', title: 'Overwrite it'}, {result: '', title: 'I\'ll choose another name'}],
esc: '',
click: '',
enter: 'yes',
success: this.saveToDropbox.bind(this, true)
});
} else {
Alerts.error({
header: 'Save error',
body: 'Error saving to Dropbox: \n' + err
});
}
} else {
this.model.saved(fileName, 'dropbox');
this.render();
}
}).bind(this));
},
keyFileChange: function(e) {

View File

@ -7,7 +7,9 @@
&__db {
@include flex(0 0 auto);
@include area-selectable(top);
position: relative;
padding: $medium-padding;
padding-right: 1.3em;
white-space: nowrap;
&.footer__db--dimmed {
@include th {
@ -22,10 +24,13 @@
@include flex(1);
}
&-mod-sign {
&-sign {
font-size: 6px;
vertical-align: top;
margin-left: $base-padding-h;
position: absolute;
right: 1em;
top: 1em;
@include th { color: action-color(); }
}
}

View File

@ -42,6 +42,9 @@
button ~ button {
margin-left: $small-spacing;
}
>button {
margin-bottom: $small-spacing;
}
}
&__body, &__buttons {
@include align-self(center);

View File

@ -2,7 +2,8 @@
<% files.forEach(function(file) { %>
<div class="footer__db footer__db-item <%= file.get('open') ? '' : 'footer__db--dimmed' %>" data-file-id="<%= file.cid %>">
<i class="fa fa-<%= file.get('open') ? 'unlock' : 'lock' %>"></i> <%- file.get('name') %>
<% if (file.get('modified')) { %><i class="fa fa-circle footer__db-mod-sign"></i><% } %>
<% if (file.get('modified') && !file.get('syncing')) { %><i class="fa fa-circle footer__db-sign"></i><% } %>
<% if (file.get('syncing')) { %><i class="fa fa-refresh fa-spin footer__db-sign"></i><% } %>
</div>
<% }); %>
<div class="footer__db footer__db--dimmed footer__db--expanded footer__db-open"><i class="fa fa-plus"></i> Open / New</div>

View File

@ -13,7 +13,9 @@
<a class="open__file-link-name muted-color" <%= opening ? 'disabled' : '' %>>Open another</a>
<% } else { %>
<a class="open__file-link-open muted-color" <%= opening ? 'disabled' : '' %>>Open</a> / <a
class="open__file-link-new muted-color" <%= opening ? 'disabled' : '' %>>New</a> / <a
class="open__file-link-new muted-color" <%= opening ? 'disabled' : '' %>>New</a><% if (supportsDropbox) { %> / <a
class="open__file-link-dropbox muted-color" <%= (opening || dropboxLoading) ? 'disabled' : '' %>
>Dropbox<%= dropboxLoading ? ' (Loading...)' : '' %></a><% } %> / <a
class="open__file-link-demo muted-color" <%= opening ? 'disabled' : '' %>>Demo</a>
<% } %>
</div>

View File

@ -1,19 +1,27 @@
<div>
<h1><i class="fa fa-lock"></i> <%- name %></h1>
<% if (path) { %>
<p>File path: <%- path %></p>
<% if (storage) { %>
<% if (storage === 'file') { %>
<p>File path: <%- path %></p>
<% } else if (storage === 'dropbox') { %>
<p>This file is opened from Dropbox.</p>
<% } %>
<% } else { %>
<p>This database is loaded in memory. To enable auto-save and saving with shortcut <%= cmd %>S,
please, save it to <%= supportFiles ? ' file or ' : '' %> Dropbox.</p>
<% if (!supportFiles) { %>
<p>Want to work seamlessly with local files? <a href="<%= desktopLink %>" target="_blank">Download a desktop app</a></p>
<% } %>
<p>This database is loaded in memory. To enable auto-save and saving with shortcut <%= cmd %>S,
please, save it to <%= supportFiles ? 'file' : 'Dropbox' %>.</p>
<% if (!supportFiles) { %>
<p>Want to work seamlessly with local files? <a href="<%= desktopLink %>" target="_blank">Download a desktop app</a></p>
<% } %>
<% } %>
<div class="settings__file-buttons">
<% if (storage !== 'dropbox') { %>
<button class="settings__file-button-save-file btn-silent">Save to file</button>
<% } %>
<button class="settings__file-button-export-xml btn-silent">Export to XML</button>
<% if (supportsDropbox) { %>
<button class="settings__file-button-save-dropbox btn-silent">Sync with Dropbox</button>
<% } %>
</div>
<h2>Settings</h2>