mirror of https://github.com/keeweb/keeweb
Merge branch 'master' into no-auto-update
commit
308ff8505c
4
TODO.md
4
TODO.md
|
@ -8,10 +8,12 @@
|
|||
- [ ] move groups/entries
|
||||
- [ ] help/tips
|
||||
- [ ] switch view
|
||||
- [ ] optional auto-update
|
||||
- [ ] lock without closing
|
||||
- [ ] merge
|
||||
- [ ] show sync state
|
||||
- [ ] show sync date
|
||||
- [ ] dropbox keyfiles
|
||||
- [ ] save to localstorage
|
||||
- [ ] generation templates
|
||||
- [ ] advanced search
|
||||
- [ ] mobile
|
||||
|
|
|
@ -4,3 +4,6 @@ CACHE MANIFEST
|
|||
|
||||
CACHE:
|
||||
index.html
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var Dropbox = require('dropbox');
|
||||
var Dropbox = require('dropbox'),
|
||||
Alerts = require('./alerts');
|
||||
|
||||
var DropboxKeys = {
|
||||
AppFolder: 'qp7ctun6qt5n9d6'
|
||||
|
@ -8,7 +9,7 @@ var DropboxKeys = {
|
|||
|
||||
var DropboxLink = {
|
||||
_getClient: function(complete) {
|
||||
if (this._dropboxClient) {
|
||||
if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
|
||||
complete(null, this._dropboxClient);
|
||||
return;
|
||||
}
|
||||
|
@ -22,41 +23,115 @@ var DropboxLink = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
_handleUiError: function(err, callback) {
|
||||
switch (err.status) {
|
||||
case Dropbox.ApiError.INVALID_TOKEN:
|
||||
Alerts.yesno({
|
||||
icon: 'dropbox',
|
||||
header: 'Dropbox Login',
|
||||
body: 'To continue, you have to sign in to Dropbox.',
|
||||
buttons: [{result: 'yes', title: 'Sign In'}, {result: '', title: 'Cancel'}],
|
||||
success: (function() {
|
||||
this.authenticate(function(err) { callback(!err); });
|
||||
}).bind(this),
|
||||
cancel: function() { callback(false); }
|
||||
});
|
||||
return;
|
||||
case Dropbox.ApiError.NOT_FOUND:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Sync Error',
|
||||
body: 'The file was not found. Has it been removed from another computer?'
|
||||
});
|
||||
break;
|
||||
case Dropbox.ApiError.OVER_QUOTA:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Full',
|
||||
body: 'Your Dropbox is full, there\'s no space left anymore.'
|
||||
});
|
||||
break;
|
||||
case Dropbox.ApiError.RATE_LIMITED:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Sync Error',
|
||||
body: 'Too many requests to Dropbox have been made by this app. Please, try again later.'
|
||||
});
|
||||
break;
|
||||
case Dropbox.ApiError.NETWORK_ERROR:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Sync Network Error',
|
||||
body: 'Network error occured during Dropbox sync. Please, check your connection and try again.'
|
||||
});
|
||||
break;
|
||||
case Dropbox.ApiError.INVALID_PARAM:
|
||||
case Dropbox.ApiError.OAUTH_ERROR:
|
||||
case Dropbox.ApiError.INVALID_METHOD:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Sync Error',
|
||||
body: 'Something went wrong during Dropbox sync. Please, try again later. Error code: ' + err.status
|
||||
});
|
||||
break;
|
||||
default:
|
||||
Alerts.error({
|
||||
header: 'Dropbox Sync Error',
|
||||
body: 'Something went wrong during Dropbox sync. Please, try again later. Error: ' + err
|
||||
});
|
||||
break;
|
||||
}
|
||||
callback(false);
|
||||
},
|
||||
|
||||
_callAndHandleError: function(callName, args, callback) {
|
||||
var that = this;
|
||||
this._getClient(function(err, client) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
client[callName].apply(client, args.concat(function(err, res) {
|
||||
if (err) {
|
||||
that._handleUiError(err, function(repeat) {
|
||||
if (repeat) {
|
||||
that._callAndHandleError(callName, args, callback);
|
||||
} else {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(err, res);
|
||||
}
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
authenticate: function(copmlete) {
|
||||
this._getClient(function(err) { copmlete(err); });
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
if (overwrite) {
|
||||
this._callAndHandleError('writeFile', [fileName, data], complete);
|
||||
} else {
|
||||
this.getFileList((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 }); }
|
||||
this._callAndHandleError('writeFile', [fileName, data], complete);
|
||||
}).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
openFile: function(fileName, complete) {
|
||||
this._getClient(function(err, client) {
|
||||
if (err) { return complete(err); }
|
||||
client.readFile(fileName, { blob: true }, complete);
|
||||
});
|
||||
this._callAndHandleError('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); }
|
||||
this._callAndHandleError('readdir', [''], function(err, files) {
|
||||
if (files) {
|
||||
files = files.filter(function(f) { return /\.kdbx$/i.test(f); });
|
||||
complete(null, files);
|
||||
});
|
||||
}
|
||||
complete(err, files);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
var Links = {
|
||||
Repo: 'https://github.com/antelle/keeweb',
|
||||
Desktop: 'https://github.com/antelle/keeweb/releases',
|
||||
Desktop: 'https://github.com/antelle/keeweb/releases/latest',
|
||||
WebApp: 'https://antelle.github.io/keeweb/',
|
||||
License: 'https://github.com/antelle/keeweb/blob/master/MIT-LICENSE.txt'
|
||||
};
|
||||
|
|
|
@ -155,10 +155,7 @@ var AppModel = Backbone.Model.extend({
|
|||
file = this.files.first();
|
||||
group = file.get('groups').first();
|
||||
}
|
||||
var entry = EntryModel.newEntry(group, file);
|
||||
group.addEntry(entry);
|
||||
entry.isNew = true;
|
||||
return entry;
|
||||
return EntryModel.newEntry(group, file);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -110,6 +110,9 @@ var EntryModel = Backbone.Model.extend({
|
|||
this.entry.pushHistory();
|
||||
this.file.setModified();
|
||||
}
|
||||
if (this.isJustCreated) {
|
||||
this.isJustCreated = false;
|
||||
}
|
||||
this.entry.times.update();
|
||||
},
|
||||
|
||||
|
@ -222,6 +225,14 @@ var EntryModel = Backbone.Model.extend({
|
|||
this.group = trashGroup;
|
||||
this.deleted = true;
|
||||
}
|
||||
},
|
||||
|
||||
removeWithoutHistory: function() {
|
||||
var ix = this.group.group.entries.indexOf(this.entry);
|
||||
if (ix >= 0) {
|
||||
this.group.group.entries.splice(ix, 1);
|
||||
}
|
||||
this.group.removeEntry(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -237,6 +248,8 @@ EntryModel.newEntry = function(group, file) {
|
|||
model.setEntry(entry, group, file);
|
||||
model.entry.times.update();
|
||||
model.unsaved = true;
|
||||
model.isJustCreated = true;
|
||||
group.addEntry(model);
|
||||
file.setModified();
|
||||
return model;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,10 @@ var MenuItemModel = Backbone.Model.extend({
|
|||
filterValue: null
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
initialize: function(model) {
|
||||
if (model && model.file) {
|
||||
this.listenTo(model.file, 'change:name', this.changeTitle, this);
|
||||
}
|
||||
},
|
||||
|
||||
_loadItemCollectionType: function() {
|
||||
|
@ -55,6 +58,10 @@ var MenuItemModel = Backbone.Model.extend({
|
|||
expanded = true;
|
||||
}
|
||||
this.set('expanded', expanded);
|
||||
},
|
||||
|
||||
changeTitle: function(model, newTitle) {
|
||||
this.set('title', newTitle);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var FeatureDetector = require('./feature-detector');
|
||||
|
||||
var CopyPaste = {
|
||||
tryCopy: function() {
|
||||
try {
|
||||
|
@ -9,15 +11,24 @@ var CopyPaste = {
|
|||
}
|
||||
},
|
||||
|
||||
createHiddenInput: function(text) {
|
||||
createHiddenInput: function(text, pos) {
|
||||
var hiddenInput = $('<input/>')
|
||||
.attr({ type: 'text', readonly: true, 'class': 'hide-by-pos' })
|
||||
.val(text)
|
||||
.appendTo(document.body)
|
||||
.focus();
|
||||
hiddenInput[0].select();
|
||||
.attr({ type: 'text', 'class': pos ? '' : 'hide-by-pos' })
|
||||
.appendTo(document.body);
|
||||
if (FeatureDetector.canCopyReadonlyInput()) {
|
||||
hiddenInput.attr('readonly', true);
|
||||
}
|
||||
if (pos) {
|
||||
hiddenInput.css({ position: 'absolute', zIndex: 100, padding: '0 .6em',
|
||||
border: 'none', background: 'transparent', color: 'transparent',
|
||||
left: pos.left, top: pos.top, width: pos.width, height: pos.height });
|
||||
}
|
||||
hiddenInput[0].selectionStart = 0;
|
||||
hiddenInput[0].selectionEnd = text.length;
|
||||
hiddenInput.focus();
|
||||
hiddenInput.on({
|
||||
'copy': function() { setTimeout(function() { hiddenInput.blur(); }, 0); },
|
||||
'copy cut paste': function() { setTimeout(function() { hiddenInput.blur(); }, 0); },
|
||||
blur: function() { hiddenInput.remove(); }
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,12 @@ var FeatureDetector = {
|
|||
},
|
||||
altShortcutSymbol: function(formatting) {
|
||||
return this.isMac() ? '⌥' : formatting ? '<span class="thin">alt + </span>' : 'alt-';
|
||||
},
|
||||
shouldMoveHiddenInputToCopySource: function() {
|
||||
return /(iPad|iPhone)/i.test(navigator.userAgent);
|
||||
},
|
||||
canCopyReadonlyInput: function() {
|
||||
return !(/CriOS/i.test(navigator.userAgent));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ var AppView = Backbone.View.extend({
|
|||
this.listenTo(Backbone, 'switch-view', this.switchView);
|
||||
this.listenTo(Backbone, 'toggle-settings', this.toggleSettings);
|
||||
this.listenTo(Backbone, 'toggle-menu', this.toggleMenu);
|
||||
this.listenTo(Backbone, 'toggle-details', this.toggleDetails);
|
||||
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
|
||||
|
||||
window.onbeforeunload = this.beforeUnload.bind(this);
|
||||
|
@ -135,6 +136,7 @@ var AppView = Backbone.View.extend({
|
|||
selectedMenuItem = this.model.menu.generalSection.get('items').first();
|
||||
}
|
||||
this.model.menu.select({ item: selectedMenuItem });
|
||||
this.views.menu.switchVisibility(false);
|
||||
},
|
||||
|
||||
fileListUpdated: function() {
|
||||
|
@ -253,6 +255,11 @@ var AppView = Backbone.View.extend({
|
|||
Alerts.notImplemented();
|
||||
},
|
||||
|
||||
toggleDetails: function(visible) {
|
||||
this.$el.find('.app__list').toggleClass('app__list--details-visible', visible);
|
||||
this.views.menu.switchVisibility(false);
|
||||
},
|
||||
|
||||
contextmenu: function(e) {
|
||||
if (['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -34,6 +34,7 @@ var DetailsView = Backbone.View.extend({
|
|||
'click .details__header-title': 'editTitle',
|
||||
'click .details__history-link': 'showHistory',
|
||||
'click .details__buttons-trash': 'moveToTrash',
|
||||
'click .details__back-button': 'backClick',
|
||||
'dragover .details': 'dragover',
|
||||
'dragleave .details': 'dragleave',
|
||||
'drop .details': 'drop'
|
||||
|
@ -236,7 +237,7 @@ var DetailsView = Backbone.View.extend({
|
|||
showEntry: function(entry) {
|
||||
this.model = entry;
|
||||
this.render();
|
||||
if (entry && !entry.title && entry.isNew) {
|
||||
if (entry && !entry.title && entry.isJustCreated) {
|
||||
this.editTitle();
|
||||
}
|
||||
},
|
||||
|
@ -380,6 +381,11 @@ var DetailsView = Backbone.View.extend({
|
|||
this.setTitle(e.target.value);
|
||||
} else if (code === Keys.DOM_VK_ESCAPE) {
|
||||
$(e.target).unbind('blur');
|
||||
if (this.model.isJustCreated) {
|
||||
this.model.removeWithoutHistory();
|
||||
Backbone.trigger('refresh');
|
||||
return;
|
||||
}
|
||||
this.render();
|
||||
} else if (code === Keys.DOM_VK_TAB) {
|
||||
e.preventDefault();
|
||||
|
@ -392,6 +398,11 @@ var DetailsView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
setTitle: function(title) {
|
||||
if (!title && this.model.isJustCreated) {
|
||||
this.model.removeWithoutHistory();
|
||||
Backbone.trigger('refresh');
|
||||
return;
|
||||
}
|
||||
if (this.model.title instanceof kdbxweb.ProtectedValue) {
|
||||
title = kdbxweb.ProtectedValue.fromString(title);
|
||||
}
|
||||
|
@ -452,6 +463,10 @@ var DetailsView = Backbone.View.extend({
|
|||
moveToTrash: function() {
|
||||
this.model.moveToTrash();
|
||||
Backbone.trigger('refresh');
|
||||
},
|
||||
|
||||
backClick: function() {
|
||||
Backbone.trigger('toggle-details', false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ var FieldView = require('./field-view'),
|
|||
|
||||
var FieldViewText = FieldView.extend({
|
||||
renderValue: function(value) {
|
||||
return typeof value.byteLength === 'number' ? PasswordGenerator.present(value.byteLength) :
|
||||
_.escape(value).replace(/\n/g, '<br/>');
|
||||
return value && typeof value.byteLength === 'number' ? PasswordGenerator.present(value.byteLength) :
|
||||
_.escape(value || '').replace(/\n/g, '<br/>');
|
||||
},
|
||||
|
||||
getEditValue: function(value) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var Backbone = require('backbone'),
|
||||
FeatureDetector = require('../../util/feature-detector'),
|
||||
CopyPaste = require('../../util/copy-paste');
|
||||
|
||||
var FieldView = Backbone.View.extend({
|
||||
|
@ -36,6 +37,16 @@ var FieldView = Backbone.View.extend({
|
|||
fieldLabelClick: function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
var field = this.model.name;
|
||||
if (FeatureDetector.shouldMoveHiddenInputToCopySource()) {
|
||||
var box = this.valueEl[0].getBoundingClientRect();
|
||||
var textValue = this.value && this.value.getText ? this.value.getText() : this.getEditValue(this.value);
|
||||
if (!textValue) {
|
||||
return;
|
||||
}
|
||||
CopyPaste.createHiddenInput(textValue, box);
|
||||
//CopyPaste.tryCopy(); // maybe Apple will ever support this?
|
||||
return;
|
||||
}
|
||||
if (field) {
|
||||
var value = this.value || '';
|
||||
if (value && value.getText) {
|
||||
|
|
|
@ -79,6 +79,7 @@ var ListView = Backbone.View.extend({
|
|||
if (!item.active) {
|
||||
this.selectItem(item);
|
||||
}
|
||||
Backbone.trigger('toggle-details', true);
|
||||
},
|
||||
|
||||
selectPrev: function() {
|
||||
|
|
|
@ -21,6 +21,7 @@ var MenuItemView = Backbone.View.extend({
|
|||
|
||||
initialize: function () {
|
||||
this.itemViews = [];
|
||||
this.listenTo(this.model, 'change:title', this.changeTitle);
|
||||
this.listenTo(this.model, 'change:active', this.changeActive);
|
||||
this.listenTo(this.model, 'change:expanded', this.changeExpanded);
|
||||
this.listenTo(this.model, 'change:cls', this.changeCls);
|
||||
|
@ -67,6 +68,10 @@ var MenuItemView = Backbone.View.extend({
|
|||
this.itemViews = [];
|
||||
},
|
||||
|
||||
changeTitle: function(model, title) {
|
||||
this.$el.find('.menu__item-title').text(title);
|
||||
},
|
||||
|
||||
changeActive: function(model, active) {
|
||||
this.$el.toggleClass('menu__item--active', active);
|
||||
},
|
||||
|
|
|
@ -47,8 +47,8 @@ var MenuView = Backbone.View.extend({
|
|||
this.render();
|
||||
},
|
||||
|
||||
switchVisibility: function() {
|
||||
this.$el.toggleClass('menu-visible');
|
||||
switchVisibility: function(visible) {
|
||||
this.$el.toggleClass('menu-visible', visible);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -181,41 +181,52 @@ var OpenFileView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
openFromDropbox: function() {
|
||||
this.dropboxLoading = 'opening';
|
||||
this.render();
|
||||
DropboxLink.getFileList((function(err, files) {
|
||||
this.dropboxLoading = null;
|
||||
if (err) { return; }
|
||||
var buttons = [];
|
||||
files.forEach(function(file) {
|
||||
buttons.push({ result: file, title: file.replace(/\.kdbx/i, '') });
|
||||
});
|
||||
if (!buttons.length) {
|
||||
this.dropboxLoading = null;
|
||||
this.render();
|
||||
Alerts.error({
|
||||
header: 'Nothing found',
|
||||
body: 'You have no files in your Dropbox which could opened. Files are searched in your Dropbox app folder: Apps/KeeWeb'
|
||||
});
|
||||
if (this.dropboxLoading) {
|
||||
return;
|
||||
}
|
||||
DropboxLink.authenticate((function(err) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
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)
|
||||
});
|
||||
this.dropboxLoading = 'file list';
|
||||
this.render();
|
||||
DropboxLink.getFileList((function(err, files) {
|
||||
this.dropboxLoading = null;
|
||||
if (err) {
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
var buttons = [];
|
||||
files.forEach(function(file) {
|
||||
buttons.push({ result: file, title: file.replace(/\.kdbx/i, '') });
|
||||
});
|
||||
if (!buttons.length) {
|
||||
this.dropboxLoading = null;
|
||||
this.render();
|
||||
Alerts.error({
|
||||
header: 'Nothing found',
|
||||
body: 'You have no files in your Dropbox which could be opened. Files are searched in your Dropbox app folder: Apps/KeeWeb'
|
||||
});
|
||||
return;
|
||||
}
|
||||
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));
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
openDropboxFile: function(file) {
|
||||
var fileName = file.replace(/\.kdbx/i, '');
|
||||
this.dropboxLoading = 'opening ' + fileName;
|
||||
this.dropboxLoading = fileName;
|
||||
this.render();
|
||||
DropboxLink.openFile(file, (function(err, data) {
|
||||
this.dropboxLoading = null;
|
||||
|
|
|
@ -44,6 +44,7 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
name: this.model.get('name'),
|
||||
path: this.model.get('path'),
|
||||
storage: this.model.get('storage'),
|
||||
syncing: this.model.get('syncing'),
|
||||
password: PasswordGenerator.present(this.model.get('passwordLength')),
|
||||
defaultUser: this.model.get('defaultUser'),
|
||||
recycleBinEnabled: this.model.get('recycleBinEnabled'),
|
||||
|
@ -86,9 +87,7 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
Alerts.error({
|
||||
header: 'Empty password',
|
||||
body: 'Please, enter the password. You will use it the next time you open this file.',
|
||||
complete: (function() {
|
||||
this.$el.find('#settings__file-master-pass').focus();
|
||||
}).bind(this)
|
||||
complete: (function() { this.$el.find('#settings__file-master-pass').focus(); }).bind(this)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
@ -115,7 +114,9 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
var blob = new Blob([data], {type: 'application/octet-stream'});
|
||||
FileSaver.saveAs(blob, fileName);
|
||||
this.passwordChanged = false;
|
||||
this.model.saved();
|
||||
if (this.model.get('storage') !== 'dropbox') {
|
||||
this.model.saved();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -151,12 +152,13 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
saveToDropbox: function(overwrite) {
|
||||
if (!this.validate()) {
|
||||
if (this.model.get('syncing') || !this.validate()) {
|
||||
return;
|
||||
}
|
||||
var data = this.model.getData();
|
||||
var fileName = this.model.get('name') + '.kdbx';
|
||||
this.model.set('syncing', true);
|
||||
this.render();
|
||||
DropboxLink.saveFile(fileName, data, overwrite, (function(err) {
|
||||
if (err) {
|
||||
this.model.set('syncing', false);
|
||||
|
@ -169,7 +171,8 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
esc: '',
|
||||
click: '',
|
||||
enter: 'yes',
|
||||
success: this.saveToDropbox.bind(this, true)
|
||||
success: this.saveToDropbox.bind(this, true),
|
||||
cancel: (function() { this.$el.find('#settings__file-name').focus(); }).bind(this)
|
||||
});
|
||||
} else {
|
||||
Alerts.error({
|
||||
|
|
|
@ -12,7 +12,7 @@ var SettingsView = Backbone.View.extend({
|
|||
views: null,
|
||||
|
||||
events: {
|
||||
'click .settings__return-link': 'returnToApp'
|
||||
'click .settings__back-button': 'returnToApp'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
|
|
|
@ -30,6 +30,13 @@
|
|||
@include flex(0 0 auto);
|
||||
width: 1px;
|
||||
cursor: col-resize;
|
||||
@include mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-visible + &__menu-drag {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__list {
|
||||
|
@ -39,6 +46,12 @@
|
|||
@include flex-direction(column);
|
||||
width: 250px;
|
||||
overflow-y: auto;
|
||||
@include mobile {
|
||||
width: 100vw;
|
||||
&.app__list--details-visible {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
|
@ -47,6 +60,10 @@
|
|||
overflow: hidden;
|
||||
padding: $base-spacing;
|
||||
position: relative;
|
||||
@include mobile {
|
||||
width: 100vw;
|
||||
padding: $base-padding;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
|
|
|
@ -6,6 +6,16 @@
|
|||
@include user-select(text);
|
||||
width: 100%;
|
||||
|
||||
&__back-button {
|
||||
display: none;
|
||||
@include mobile {
|
||||
display: block;
|
||||
padding-bottom: $base-padding-v;
|
||||
cursor: pointer;
|
||||
>i { margin-right: $base-padding-h; }
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
@include display(flex);
|
||||
padding-bottom: $small-spacing;
|
||||
|
@ -14,6 +24,7 @@
|
|||
&-title {
|
||||
@include user-select(text);
|
||||
@include flex(1);
|
||||
@include align-self(flex-start);
|
||||
cursor: text;
|
||||
margin: 0 6px;
|
||||
padding: 3px 6px 1px;
|
||||
|
@ -272,6 +283,8 @@
|
|||
text-align: center;
|
||||
overflow: hidden;
|
||||
transition: color $base-duration $base-timing;
|
||||
display: none;
|
||||
@include nomobile { display: block; }
|
||||
&:hover {
|
||||
@include th { color: medium-color(); }
|
||||
}
|
||||
|
@ -306,6 +319,8 @@
|
|||
right: $base-padding-h;
|
||||
white-space: nowrap;
|
||||
opacity: .15;
|
||||
display: none;
|
||||
@include nomobile { display: block; }
|
||||
}
|
||||
&-icon {
|
||||
display: none;
|
||||
|
|
|
@ -82,7 +82,9 @@
|
|||
height: 32px;
|
||||
|
||||
&--active, &--active:hover {
|
||||
@include area-selected(right);
|
||||
@include nomobile {
|
||||
@include area-selected(right);
|
||||
}
|
||||
}
|
||||
|
||||
&--expired {
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
}
|
||||
|
||||
&-title {
|
||||
padding-left: .4em;
|
||||
.menu__item-colors & {
|
||||
display: inline-block;
|
||||
@include th { color: text-color(); }
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
position: relative;
|
||||
|
||||
>.scroller {
|
||||
@include flex(1);
|
||||
@include flex(1 0 0);
|
||||
}
|
||||
|
||||
h2,h3 {
|
||||
|
@ -31,16 +31,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__return-link {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: $base-padding-h;
|
||||
padding: $base-padding-v * 2 0 1px 0;
|
||||
z-index: 1;
|
||||
@include th {
|
||||
background: background-color();
|
||||
box-shadow: 0 0 5px 5px background-color();
|
||||
&__back-button {
|
||||
&-pre, &-post { display: none; }
|
||||
cursor: pointer;
|
||||
@include mobile {
|
||||
padding-bottom: $base-padding-v;
|
||||
>i { margin-right: $base-padding-h; }
|
||||
&-pre { display: inline; }
|
||||
}
|
||||
@include nomobile {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: $base-padding-h;
|
||||
padding: $base-padding-v * 2 0 1px 0;
|
||||
z-index: 1;
|
||||
@include th {
|
||||
background: background-color();
|
||||
box-shadow: 0 0 5px 5px background-color();
|
||||
}
|
||||
&-post { display: inline; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
border-color: action-color();
|
||||
|
|
|
@ -9,3 +9,9 @@
|
|||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin nomobile {
|
||||
@media (min-width: #{$mobile-width + 1}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
transition: background-color $slow-transition-out;
|
||||
@include th { background: light-border-color(); }
|
||||
&:hover, &.dragging {
|
||||
transition: background-color $slow-transition-in;
|
||||
@include th { background: accent-border-color(); }
|
||||
@include nomobile {
|
||||
transition: background-color $slow-transition-in;
|
||||
@include th {
|
||||
background: accent-border-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,4 +23,5 @@
|
|||
left: -2px;
|
||||
width: calc(100% + 5px);
|
||||
height: calc(100% + 5px);
|
||||
@include mobile { display: none; }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<div class="details">
|
||||
<div class="details__back-button">
|
||||
<i class="fa fa-chevron-left"></i> back to list
|
||||
</div>
|
||||
<div class="details__header">
|
||||
<i class="details__header-color fa fa-bookmark-o">
|
||||
<span class="details__colors-popup">
|
||||
|
@ -39,4 +42,4 @@
|
|||
<i class="fa fa-paperclip muted-color details__dropzone-icon"></i>
|
||||
<h1 class="muted-color details__dropzone-header">drop attachments here</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<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') && !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>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
cls ? cls : '' %>">
|
||||
<div class="menu__item-body">
|
||||
<i class="menu__item-icon fa <%= icon ? 'fa-' + icon : 'menu__item-icon--no-icon' %>"></i><span
|
||||
class="menu__item-title"> <%- title %></span>
|
||||
class="menu__item-title"><%- title %></span>
|
||||
<% if (options) { %>
|
||||
<div class="menu__item-options">
|
||||
<% options.forEach(function(option) { %>
|
||||
|
@ -15,4 +15,4 @@
|
|||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
<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><% if (supportsDropbox) { %> / <a
|
||||
class="open__file-link-new muted-color" <%= opening ? 'disabled' : '' %>>New</a> / <a
|
||||
class="open__file-link-demo muted-color" <%= opening ? 'disabled' : '' %>>Demo</a><% if (supportsDropbox) { %> / <a
|
||||
class="open__file-link-dropbox muted-color" <%= (opening || dropboxLoading) ? 'disabled' : '' %>
|
||||
>Dropbox<%= dropboxLoading ? ' (' + dropboxLoading + '...)' : '' %></a><% } %> / <a
|
||||
class="open__file-link-demo muted-color" <%= opening ? 'disabled' : '' %>>Demo</a>
|
||||
>Dropbox<%= dropboxLoading ? ' (loading ' + dropboxLoading + '...)' : '' %></a><% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="open__file-warning muted-color hide"><i class="fa fa-exclamation-triangle"></i> Caps Lock is on</div>
|
||||
|
|
|
@ -15,12 +15,11 @@
|
|||
<% } %>
|
||||
|
||||
<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>
|
||||
<button class="settings__file-button-save-dropbox btn-silent" <%= syncing ? 'disabled' : '' %>>
|
||||
Sync with Dropbox <%= syncing ? '(working...)' : '' %></button>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<i class="fa fa-chrome"></i>
|
||||
<i class="fa fa-firefox"></i>
|
||||
<i class="fa fa-opera"></i>
|
||||
<i class="fa fa-compass"></i>
|
||||
<i class="fa fa-internet-explorer"></i>
|
||||
<a href="<%= webAppLink %>" target="_blank">Web app</a>
|
||||
</li>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<div class="settings">
|
||||
<a class="settings__return-link">return to app <i class="fa fa-external-link-square"></i></a>
|
||||
<div class="settings__back-button">
|
||||
<i class="fa fa-chevron-left settings__back-button-pre"></i> return to app <i class="fa fa-external-link-square settings__back-button-post"></i>
|
||||
</div>
|
||||
<div class="scroller">
|
||||
</div>
|
||||
<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "keeweb",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.6",
|
||||
"description": "KeePass web app",
|
||||
"main": "Gulpfile.js",
|
||||
"repository": "https://github.com/antelle/keeweb",
|
||||
|
|
Loading…
Reference in New Issue