Merge branch 'master' into no-auto-update

This commit is contained in:
Antelle 2015-10-30 00:15:25 +03:00
commit 308ff8505c
35 changed files with 333 additions and 105 deletions

View File

@ -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

View File

@ -4,3 +4,6 @@ CACHE MANIFEST
CACHE:
index.html
NETWORK:
*

View File

@ -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);
});
}
};

View File

@ -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'
};

View File

@ -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);
}
});

View 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;
};

View File

@ -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);
}
});

View File

@ -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(); }
});
}

View File

@ -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));
}
};

View File

@ -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();

View File

@ -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);
}
});

View File

@ -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) {

View File

@ -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) {

View File

@ -79,6 +79,7 @@ var ListView = Backbone.View.extend({
if (!item.active) {
this.selectItem(item);
}
Backbone.trigger('toggle-details', true);
},
selectPrev: function() {

View File

@ -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);
},

View File

@ -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);
}
});

View File

@ -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;

View File

@ -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({

View File

@ -12,7 +12,7 @@ var SettingsView = Backbone.View.extend({
views: null,
events: {
'click .settings__return-link': 'returnToApp'
'click .settings__back-button': 'returnToApp'
},
initialize: function () {

View File

@ -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 {

View File

@ -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;

View File

@ -82,7 +82,9 @@
height: 32px;
&--active, &--active:hover {
@include area-selected(right);
@include nomobile {
@include area-selected(right);
}
}
&--expired {

View File

@ -111,6 +111,7 @@
}
&-title {
padding-left: .4em;
.menu__item-colors & {
display: inline-block;
@include th { color: text-color(); }

View File

@ -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; }
}
}

View File

@ -55,7 +55,7 @@
}
&:disabled {
cursor: not-allowed;
cursor: default;
opacity: 0.5;
&:hover {
border-color: action-color();

View File

@ -9,3 +9,9 @@
@content;
}
}
@mixin nomobile {
@media (min-width: #{$mobile-width + 1}) {
@content;
}
}

View File

@ -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; }
}

View File

@ -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>

View File

@ -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>

View File

@ -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">&nbsp;<%- 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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",