mirror of https://github.com/keeweb/keeweb.git
improved open page ux
This commit is contained in:
parent
997b516ab8
commit
2c1da0dc04
5
TODO.md
5
TODO.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
## v1.0
|
||||
|
||||
- [ ] improve open page UX
|
||||
- [x] improve open page UX
|
||||
- [x] provide engineer error details on file open
|
||||
- [ ] trash: groups/empty/untrash
|
||||
- [ ] move groups/entries
|
||||
|
@ -13,9 +13,8 @@
|
|||
- [ ] secure fields
|
||||
- [ ] close files
|
||||
- [ ] show sync date
|
||||
- [ ] dropbox keyfiles
|
||||
- [ ] offline and local storage
|
||||
- [ ] use dropbox chooser for keyfile
|
||||
- [x] use dropbox chooser for keyfile
|
||||
- [ ] trim history by rules
|
||||
- [ ] advanced search
|
||||
|
||||
|
|
|
@ -7,6 +7,106 @@ var DropboxKeys = {
|
|||
AppFolder: 'qp7ctun6qt5n9d6'
|
||||
};
|
||||
|
||||
var DropboxChooser = function(callback) {
|
||||
this.cb = callback;
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.callback = function(err, res) {
|
||||
if (this.cb) {
|
||||
this.cb(err, res);
|
||||
}
|
||||
this.cb = null;
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.choose = function() {
|
||||
var windowFeatures = 'width=640,height=552,left=357,top=100,resizable=yes,location=yes';
|
||||
var url = this.buildUrl();
|
||||
this.popup = window.open(url, 'dropbox', windowFeatures);
|
||||
if (!this.popup) {
|
||||
return this.callback('Failed to open window');
|
||||
}
|
||||
window.addEventListener('message', this.onMessage);
|
||||
this.closeInt = setInterval(this.checkClose.bind(this), 200);
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.buildUrl = function() {
|
||||
var urlParams = {
|
||||
origin: encodeURIComponent(window.location.protocol + '//' + window.location.host),
|
||||
'app_key': DropboxKeys.AppFolder,
|
||||
'link_type': 'direct',
|
||||
trigger: 'js',
|
||||
multiselect: 'false',
|
||||
extensions: '',
|
||||
folderselect: 'false',
|
||||
iframe: 'false',
|
||||
version: 2
|
||||
};
|
||||
return 'https://www.dropbox.com/chooser?' + Object.keys(urlParams).map(function(key) {
|
||||
return key + '=' + urlParams[key];
|
||||
}).join('&');
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.onMessage = function(e) {
|
||||
if (e.source !== this.popup) {
|
||||
return;
|
||||
}
|
||||
var data = JSON.parse(e.data);
|
||||
switch (data.method) {
|
||||
case 'origin_request':
|
||||
e.source.postMessage(JSON.stringify({ method: 'origin' }), 'https://www.dropbox.com');
|
||||
break;
|
||||
case 'files_selected':
|
||||
this.popup.close();
|
||||
this.success(data.params);
|
||||
break;
|
||||
case 'close_dialog':
|
||||
this.popup.close();
|
||||
break;
|
||||
case 'web_session_error':
|
||||
case 'web_session_unlinked':
|
||||
this.callback(data.method);
|
||||
break;
|
||||
case 'resize':
|
||||
this.popup.resize(data.params);
|
||||
break;
|
||||
case 'error':
|
||||
this.callback(data.params);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.checkClose = function() {
|
||||
if (this.popup.closed) {
|
||||
clearInterval(this.closeInt);
|
||||
window.removeEventListener('message', this.onMessage);
|
||||
if (!this.result) {
|
||||
this.callback('closed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.success = function(params) {
|
||||
/* jshint camelcase:false */
|
||||
if (!params || !params[0] || !params[0].link || params[0].is_dir) {
|
||||
return this.callback('bad result');
|
||||
}
|
||||
this.result = params[0];
|
||||
this.readFile(this.result.link);
|
||||
};
|
||||
|
||||
DropboxChooser.prototype.readFile = function(url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('load', (function() {
|
||||
this.callback(null, { name: this.result.name, data: xhr.response });
|
||||
}).bind(this));
|
||||
xhr.addEventListener('error', this.callback.bind(this, 'download error'));
|
||||
xhr.addEventListener('abort', this.callback.bind(this, 'download abort'));
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
var DropboxLink = {
|
||||
_getClient: function(complete) {
|
||||
if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
|
||||
|
@ -134,6 +234,10 @@ var DropboxLink = {
|
|||
}
|
||||
complete(err, files);
|
||||
});
|
||||
},
|
||||
|
||||
chooseFile: function(callback) {
|
||||
new DropboxChooser(callback).choose();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,12 @@ SecureInput.prototype.reset = function() {
|
|||
this.el = null;
|
||||
this.length = 0;
|
||||
this.pseudoValue = '';
|
||||
|
||||
if (this.salt) {
|
||||
for (var i = 0; i < this.salt.length; i++) {
|
||||
this.salt[i] = 0;
|
||||
}
|
||||
}
|
||||
this.salt = new Uint32Array(0);
|
||||
};
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ var FileModel = Backbone.Model.extend({
|
|||
this.db = db;
|
||||
this.readModel(this.get('name'));
|
||||
this.setOpenFile({ passwordLength: len });
|
||||
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
|
||||
}
|
||||
}).bind(this));
|
||||
} catch (e) {
|
||||
|
|
|
@ -13,20 +13,18 @@ var OpenView = Backbone.View.extend({
|
|||
|
||||
events: {
|
||||
'change .open__file-ctrl': 'fileSelected',
|
||||
|
||||
'click .open__icon-open': 'openFile',
|
||||
'click .open__icon-new': 'createNew',
|
||||
'click .open__icon-dropbox': 'openFromDropbox',
|
||||
'click .open__icon-demo': 'createDemo',
|
||||
|
||||
'click .open__pass-input[readonly]': 'openFile',
|
||||
'input .open__pass-input': 'inputInput',
|
||||
'keydown .open__pass-input': 'inputKeydown',
|
||||
'keypress .open__pass-input': 'inputKeypress',
|
||||
|
||||
'click .open__pass-enter-btn': 'openDb',
|
||||
'click .open__settings-key-file': 'openKeyFile',
|
||||
'change .open__settings-check-offline': 'changeOffline',
|
||||
|
||||
'click .open__last-itemo': 'openLast',
|
||||
'dragover': 'dragover',
|
||||
'dragleave': 'dragleave',
|
||||
'drop': 'drop'
|
||||
|
@ -42,20 +40,331 @@ var OpenView = Backbone.View.extend({
|
|||
this.fileData = null;
|
||||
this.keyFileData = null;
|
||||
this.passwordInput = new SecureInput();
|
||||
this.listenTo(this.file, 'change:open', this.fileOpened);
|
||||
this.listenTo(this.file, 'change:open', this.fileOpenChanged);
|
||||
this.listenTo(this.file, 'change:opening', this.fileOpeningChanged);
|
||||
this.listenTo(this.file, 'change:error', this.fileErrorChanged);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.dragTimeout) {
|
||||
clearTimeout(this.dragTimeout);
|
||||
}
|
||||
this.renderTemplate({
|
||||
supportsDropbox: !Launcher,
|
||||
dropboxLoading: this.dropboxLoading
|
||||
});
|
||||
this.renderTemplate({ supportsDropbox: !Launcher });
|
||||
this.inputEl = this.$el.find('.open__pass-input');
|
||||
this.passwordInput.setElement(this.inputEl);
|
||||
return this;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
this.passwordInput.reset();
|
||||
Backbone.View.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
|
||||
fileOpenChanged: function() {
|
||||
this.model.addFile(this.file);
|
||||
},
|
||||
|
||||
fileOpeningChanged: function() {
|
||||
var opening = this.file.get('opening');
|
||||
this.$el.toggleClass('open--opening', opening);
|
||||
if (opening) {
|
||||
this.inputEl.attr('disabled', 'disabled');
|
||||
this.$el.find('#open__settings-check-offline').attr('disabled', 'disabled');
|
||||
} else {
|
||||
this.inputEl.removeAttr('disabled');
|
||||
this.$el.find('#open__settings-check-offline').removeAttr('disabled');
|
||||
}
|
||||
},
|
||||
|
||||
fileErrorChanged: function() {
|
||||
if (this.file.get('error')) {
|
||||
this.inputEl.addClass('input--error').focus();
|
||||
this.inputEl[0].selectionStart = 0;
|
||||
this.inputEl[0].selectionEnd = this.inputEl.val().length;
|
||||
}
|
||||
},
|
||||
|
||||
fileSelected: function(e) {
|
||||
var file = e.target.files[0];
|
||||
if (file) {
|
||||
this.processFile(file);
|
||||
}
|
||||
},
|
||||
|
||||
processFile: function(file, complete) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = (function(e) {
|
||||
this[this.reading] = e.target.result;
|
||||
if (this.reading === 'fileData') {
|
||||
this.file.set('name', file.name.replace(/\.\w+$/i, ''));
|
||||
if (file.path) {
|
||||
this.file.set({ path: file.path, storage: file.storage || 'file' });
|
||||
}
|
||||
this.displayOpenFile();
|
||||
} else {
|
||||
this.file.set('keyFileName', file.name);
|
||||
this.displayOpenKeyFile();
|
||||
}
|
||||
if (complete) {
|
||||
complete(true);
|
||||
}
|
||||
}).bind(this);
|
||||
reader.onerror = (function() {
|
||||
Alerts.error({ header: 'Failed to read file' });
|
||||
this.showReadyToOpen();
|
||||
if (complete) {
|
||||
complete(false);
|
||||
}
|
||||
}).bind(this);
|
||||
reader.readAsArrayBuffer(file);
|
||||
},
|
||||
|
||||
displayOpenFile: function() {
|
||||
this.$el.addClass('open--file');
|
||||
this.$el.find('#open__settings-check-offline')[0].removeAttribute('disabled');
|
||||
this.inputEl[0].removeAttribute('readonly');
|
||||
this.inputEl[0].setAttribute('placeholder', 'Password for ' + this.file.get('name'));
|
||||
this.inputEl.focus();
|
||||
},
|
||||
|
||||
displayOpenKeyFile: function() {
|
||||
this.$el.find('.open__settings-key-file-name').text(this.file.get('keyFileName'));
|
||||
this.$el.addClass('open--key-file');
|
||||
this.inputEl.focus();
|
||||
},
|
||||
|
||||
setFile: function(file, keyFile) {
|
||||
this.reading = 'fileData';
|
||||
this.processFile(file, (function(success) {
|
||||
if (success && keyFile) {
|
||||
this.reading = 'keyFileData';
|
||||
this.processFile(keyFile);
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
createDemo: function() {
|
||||
if (!this.file.get('opening')) {
|
||||
if (!this.model.files.getByName('Demo')) {
|
||||
this.file.createDemo();
|
||||
} else {
|
||||
this.trigger('cancel');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
createNew: function() {
|
||||
if (!this.file.get('opening')) {
|
||||
var name;
|
||||
for (var i = 0; ; i++) {
|
||||
name = 'New' + (i || '');
|
||||
if (!this.model.files.getByName(name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.file.create(name);
|
||||
}
|
||||
},
|
||||
|
||||
showOpenLocalFile: function(path) {
|
||||
if (path && Launcher) {
|
||||
try {
|
||||
var name = path.match(/[^/\\]*$/)[0];
|
||||
var data = Launcher.readFile(path);
|
||||
var file = new Blob([data]);
|
||||
Object.defineProperties(file, {
|
||||
path: { value: path },
|
||||
name: { value: name }
|
||||
});
|
||||
this.setFile(file);
|
||||
} catch (e) {
|
||||
console.log('Failed to show local file', e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
openFile: function() {
|
||||
if (!this.file.get('opening')) {
|
||||
this.openAny('fileData'/*, '.kdbx'*/);
|
||||
}
|
||||
},
|
||||
|
||||
openKeyFile: function(e) {
|
||||
if ($(e.target).hasClass('open__settings-key-file-dropbox')) {
|
||||
this.openKeyFileFromDropbox();
|
||||
} else if (!this.file.get('opening') && this.file.get('name')) {
|
||||
if (this.keyFileData) {
|
||||
this.keyFileData = null;
|
||||
this.file.set('keyFileName', '');
|
||||
this.$el.removeClass('open--key-file');
|
||||
this.$el.find('.open__settings-key-file-name').text('key file');
|
||||
} else {
|
||||
this.openAny('keyFileData');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
openKeyFileFromDropbox: function() {
|
||||
if (!this.file.get('opening')) {
|
||||
DropboxLink.chooseFile((function(err, res) {
|
||||
this.keyFileData = res.data;
|
||||
this.file.set('keyFileName', res.name);
|
||||
this.displayOpenKeyFile();
|
||||
}).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
openAny: function(reading, ext) {
|
||||
this.reading = reading;
|
||||
this[reading] = null;
|
||||
this.$el.find('.open__file-ctrl').attr('accept', ext || '').val(null).click();
|
||||
},
|
||||
|
||||
openDb: function() {
|
||||
if (!this.file.get('opening')) {
|
||||
var arg = {
|
||||
password: this.passwordInput.value,
|
||||
fileData: this.fileData,
|
||||
keyFileData: this.keyFileData
|
||||
};
|
||||
this.file.set({opening: true, error: false});
|
||||
this.afterPaint(function () {
|
||||
this.file.open(arg.password, arg.fileData, arg.keyFileData);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
changeOffline: function(e) {
|
||||
if (!this.file.get('opening')) {
|
||||
this.file.set('availOffline', !!e.target.checked);
|
||||
}
|
||||
},
|
||||
|
||||
inputKeydown: function(e) {
|
||||
var code = e.keyCode || e.which;
|
||||
if (code === Keys.DOM_VK_RETURN && this.passwordInput.length) {
|
||||
this.openDb();
|
||||
} else if (code === Keys.DOM_VK_CAPS_LOCK) {
|
||||
this.$el.find('.open__pass-warning').removeClass('invisible');
|
||||
}
|
||||
},
|
||||
|
||||
inputKeypress: function(e) {
|
||||
var charCode = e.keyCode || e.which;
|
||||
var ch = String.fromCharCode(charCode),
|
||||
lower = ch.toLowerCase(),
|
||||
upper = ch.toUpperCase();
|
||||
if (lower !== upper && !e.shiftKey) {
|
||||
this.toggleCapsLockWarning(ch !== lower);
|
||||
}
|
||||
},
|
||||
|
||||
toggleCapsLockWarning: function(on) {
|
||||
this.$el.find('.open__file-warning').toggleClass('invisible', on);
|
||||
},
|
||||
|
||||
dragover: function(e) {
|
||||
e.preventDefault();
|
||||
if (this.dragTimeout) {
|
||||
clearTimeout(this.dragTimeout);
|
||||
}
|
||||
if (!this.$el.hasClass('open--drag')) {
|
||||
this.$el.addClass('open--drag');
|
||||
}
|
||||
},
|
||||
|
||||
dragleave: function() {
|
||||
if (this.dragTimeout) {
|
||||
clearTimeout(this.dragTimeout);
|
||||
}
|
||||
this.dragTimeout = setTimeout((function() {
|
||||
this.$el.removeClass('open--drag');
|
||||
}).bind(this), 100);
|
||||
},
|
||||
|
||||
drop: function(e) {
|
||||
e.preventDefault();
|
||||
if (this.dragTimeout) {
|
||||
clearTimeout(this.dragTimeout);
|
||||
}
|
||||
this.$el.removeClass('open--drag');
|
||||
var files = e.target.files || e.dataTransfer.files;
|
||||
var dataFile = _.find(files, function(file) { return file.name.split('.').pop().toLowerCase() === 'kdbx'; });
|
||||
var keyFile = _.find(files, function(file) { return file.name.split('.').pop().toLowerCase() === 'key'; });
|
||||
if (dataFile) {
|
||||
this.setFile(dataFile, keyFile);
|
||||
}
|
||||
},
|
||||
|
||||
openFromDropbox: function() {
|
||||
if (this.dropboxLoading) {
|
||||
return;
|
||||
}
|
||||
var that = this;
|
||||
DropboxLink.authenticate(function(err) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
that.dropboxLoading = 'file list';
|
||||
that.displayDropboxLoading();
|
||||
DropboxLink.getFileList(function(err, files) {
|
||||
that.dropboxLoading = null;
|
||||
that.displayDropboxLoading();
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
var buttons = [];
|
||||
files.forEach(function(file) {
|
||||
buttons.push({ result: file, title: file.replace(/\.kdbx/i, '') });
|
||||
});
|
||||
if (!buttons.length) {
|
||||
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: that.openDropboxFile.bind(that),
|
||||
cancel: function() {
|
||||
that.dropboxLoading = null;
|
||||
that.displayDropboxLoading();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openDropboxFile: function(file) {
|
||||
var fileName = file.replace(/\.kdbx/i, '');
|
||||
this.dropboxLoading = fileName;
|
||||
this.displayDropboxLoading();
|
||||
DropboxLink.openFile(file, (function(err, data) {
|
||||
this.dropboxLoading = null;
|
||||
this.displayDropboxLoading();
|
||||
if (err || !data || !data.size) {
|
||||
// TODO: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: fileName }
|
||||
});
|
||||
this.setFile(data);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
displayDropboxLoading: function() {
|
||||
this.$el.find('.open__icon-dropbox .open__icon-i').toggleClass('flip3d', !!this.dropboxLoading);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
@include display(flex);
|
||||
@include align-items(stretch);
|
||||
@include flex-direction(row);
|
||||
.open--drag & { display: none; }
|
||||
}
|
||||
|
||||
&__icon {
|
||||
|
@ -34,6 +35,7 @@
|
|||
@include flex-direction(column);
|
||||
@include justify-content(flex-start);
|
||||
position: relative;
|
||||
.open--drag & { display: none; }
|
||||
}
|
||||
&-warn-wrap {
|
||||
@include display(flex);
|
||||
|
@ -47,12 +49,20 @@
|
|||
@include align-items(stretch);
|
||||
margin-bottom: $base-padding-v;
|
||||
}
|
||||
&-enter-btn, &-opening-icon {
|
||||
position: absolute;
|
||||
@include th { color: muted-color(); }
|
||||
>i { font-size: 3em; }
|
||||
}
|
||||
&-enter-btn {
|
||||
padding: .6em $base-spacing;
|
||||
position: absolute;
|
||||
@include th { color: muted-color(); };
|
||||
@include area-selectable;
|
||||
>i { font-size: 3em; }
|
||||
.open--file & { @include area-selectable; }
|
||||
.open--opening & { display: none; }
|
||||
}
|
||||
&-opening-icon {
|
||||
padding: .6em $base-padding-h;
|
||||
display: none;
|
||||
.open--opening & { display: block; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,32 +78,40 @@
|
|||
@include justify-content(space-between);
|
||||
@include align-items(stretch);
|
||||
&-key-file {
|
||||
cursor: pointer;
|
||||
.open--file:not(.open--opening) & { cursor: pointer; }
|
||||
.open--key-file & { @include th { color: medium-color(); } }
|
||||
&-dropbox {
|
||||
visibility: hidden;
|
||||
&:hover {
|
||||
visibility: visible;
|
||||
.open--file & { visibility: visible; }
|
||||
}
|
||||
}
|
||||
&-dropbox {
|
||||
.open--key-file, .open--opening & { display: none; }
|
||||
}
|
||||
&:hover .open__settings-key-file-dropbox {
|
||||
visibility: visible;
|
||||
.open--file & { visibility: visible; }
|
||||
}
|
||||
}
|
||||
|
||||
&-key-file, &-label-offline, &-label-offline:before, input[type=checkbox] + label.open__settings-label-offline:before,
|
||||
&-key-file-dropbox {
|
||||
@include th {
|
||||
color: muted-color();
|
||||
&:hover {
|
||||
color: medium-color();
|
||||
}
|
||||
&:hover {
|
||||
.open--file:not(.open--opening) & {
|
||||
@include th { color: medium-color(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
input[type=checkbox] + label.open__settings-label-offline:hover:before {
|
||||
@include th { color: medium-color(); }
|
||||
}
|
||||
&-label-offline { font-weight: normal; }
|
||||
}
|
||||
|
||||
&--file:not(.open--opening) input[type=checkbox] + label.open__settings-label-offline:hover:before {
|
||||
@include th { color: medium-color(); }
|
||||
}
|
||||
|
||||
&__last {
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
|
@ -101,7 +119,9 @@
|
|||
@include align-items(stretch);
|
||||
margin-top: $base-spacing;
|
||||
&-item {
|
||||
@include area-selectable;
|
||||
.open:not(.open--opening) & {
|
||||
@include area-selectable;
|
||||
}
|
||||
@include th { color: muted-color(); }
|
||||
padding: $base-padding-v 0;
|
||||
>i { width: 2em; }
|
||||
|
@ -110,7 +130,6 @@
|
|||
|
||||
&__dropzone {
|
||||
display: none;
|
||||
position: absolute;
|
||||
.open--drag & {
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="open">
|
||||
<input type="file" id="open__file-ctrl" class="hide-by-pos" />
|
||||
<input type="file" class="open__file-ctrl hide-by-pos" />
|
||||
<div class="open__icons">
|
||||
<div class="open__icon open__icon-open">
|
||||
<i class="fa fa-lock open__icon-i"></i>
|
||||
|
@ -22,31 +22,34 @@
|
|||
</div>
|
||||
<div class="open__pass-area">
|
||||
<div class="open__pass-warn-wrap">
|
||||
<div class="open__pass-warning muted-color _invisible"><i class="fa fa-exclamation-triangle"></i> Caps Lock is on</div>
|
||||
<div class="open__pass-warning muted-color invisible"><i class="fa fa-exclamation-triangle"></i> Caps Lock is on</div>
|
||||
</div>
|
||||
<div class="open__pass-field-wrap">
|
||||
<input class="open__pass-input" type="password" size="30" autocomplete="off" maxlength="128"
|
||||
placeholder="Click to open a file" readonly />
|
||||
<div class="open__pass-enter-btn"><i class="fa fa-level-down fa-rotate-90"></i></div>
|
||||
<div class="open__pass-opening-icon"><i class="fa fa-spinner fa-spin"></i></div>
|
||||
</div>
|
||||
<div class="open__settings">
|
||||
<div class="open__settings-key-file">
|
||||
<i class="fa fa-key"></i>
|
||||
keyfile
|
||||
<i class="fa fa-key open__settings-key-file-icon"></i>
|
||||
<span class="open__settings-key-file-name">key file</span>
|
||||
<% if (supportsDropbox) { %>
|
||||
<span class="open__settings-key-file-dropbox"> (from dropbox)</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="open__settings-offline">
|
||||
<input type="checkbox" id="open__settings-check-offline" checked />
|
||||
<input type="checkbox" id="open__settings-check-offline" class="open__settings-check-offline" checked disabled />
|
||||
<% if (supportsDropbox) { %>
|
||||
<label for="open__settings-check-offline" class="open__settings-label-offline">make available offline</label>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="open__last">
|
||||
<div class="open__last-item"><i class="fa fa-dropbox"></i>My passwords</div>
|
||||
<div class="open__last-item"><i class="fa fa-dropbox"></i>Work</div>
|
||||
<div class="open__last-item"><i class="fa fa-dropbox"></i>Hard with many rounds</div>
|
||||
<div class="open__last-item"><i class="fa fa-file-text"></i>Local</div>
|
||||
<!--<div class="open__last-item"><i class="fa fa-dropbox"></i>My passwords</div>-->
|
||||
<!--<div class="open__last-item"><i class="fa fa-dropbox"></i>Work</div>-->
|
||||
<!--<div class="open__last-item"><i class="fa fa-dropbox"></i>Hard with many rounds</div>-->
|
||||
<!--<div class="open__last-item"><i class="fa fa-file-text"></i>Local</div>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="open__dropzone">
|
||||
|
|
|
@ -2,6 +2,8 @@ Release notes
|
|||
-------------
|
||||
##### v0.2.0 (not released yet)
|
||||
Bugfixes and new features
|
||||
`+` improved open page ux
|
||||
`+` keyfiles from Dropbox
|
||||
`-` #12: cannot edit entries without title
|
||||
`+` #13: increase max generated password length
|
||||
|
||||
|
|
Loading…
Reference in New Issue