mirror of https://github.com/keeweb/keeweb
dropbox
parent
8bc020c5b0
commit
b90b31297e
1
TODO.md
1
TODO.md
|
@ -4,7 +4,6 @@
|
|||
|
||||
# FUTURE
|
||||
|
||||
- [ ] dropbox
|
||||
- [ ] trash: groups/empty/untrash
|
||||
- [ ] move groups/entries
|
||||
- [ ] help/tips
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
button ~ button {
|
||||
margin-left: $small-spacing;
|
||||
}
|
||||
>button {
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
}
|
||||
&__body, &__buttons {
|
||||
@include align-self(center);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue