mirror of https://github.com/keeweb/keeweb.git
Merge branch 'master' into develop
This commit is contained in:
commit
7e0565df3b
|
@ -20,7 +20,7 @@ The app is quite stable now. Basic stuff, as well as more advanced operations, s
|
||||||
|
|
||||||
# Self-hosting
|
# Self-hosting
|
||||||
|
|
||||||
Everything you need to host this app on your server is any static file server. The app is a single HTML file + cache manifest (optionally; for offline access).
|
Everything you need to host this app on your server is any static file server. The app is a single HTML file + a service worker (optionally; for offline access).
|
||||||
You can download the latest distribution files from [gh-pages](https://github.com/keeweb/keeweb/archive/gh-pages.zip) branch.
|
You can download the latest distribution files from [gh-pages](https://github.com/keeweb/keeweb/archive/gh-pages.zip) branch.
|
||||||
|
|
||||||
If you are using Docker:
|
If you are using Docker:
|
||||||
|
|
|
@ -9,7 +9,7 @@ AutoTypeHelper.prototype.getActiveWindowInfo = function(callback) {
|
||||||
complete(err, out) {
|
complete(err, out) {
|
||||||
let windowInfo;
|
let windowInfo;
|
||||||
if (out) {
|
if (out) {
|
||||||
const [id, title] = out.trim().split('\n');
|
const [title, id] = out.trim().split('\n');
|
||||||
windowInfo = {
|
windowInfo = {
|
||||||
id,
|
id,
|
||||||
title
|
title
|
||||||
|
|
|
@ -28,6 +28,7 @@ const AutoType = {
|
||||||
Events.on('auto-type', e => this.handleEvent(e));
|
Events.on('auto-type', e => this.handleEvent(e));
|
||||||
Events.on('main-window-blur', e => this.resetPendingEvent(e));
|
Events.on('main-window-blur', e => this.resetPendingEvent(e));
|
||||||
Events.on('main-window-will-close', e => this.resetPendingEvent(e));
|
Events.on('main-window-will-close', e => this.resetPendingEvent(e));
|
||||||
|
appModel.files.on('change', () => this.processPendingEvent());
|
||||||
},
|
},
|
||||||
|
|
||||||
handleEvent(e) {
|
handleEvent(e) {
|
||||||
|
@ -209,7 +210,6 @@ const AutoType = {
|
||||||
const evt = { filter, windowInfo };
|
const evt = { filter, windowInfo };
|
||||||
if (!this.appModel.files.hasOpenFiles()) {
|
if (!this.appModel.files.hasOpenFiles()) {
|
||||||
this.pendingEvent = evt;
|
this.pendingEvent = evt;
|
||||||
this.appModel.files.once('change', this.processPendingEvent, this);
|
|
||||||
logger.debug('auto-type event delayed');
|
logger.debug('auto-type event delayed');
|
||||||
this.focusMainWindow();
|
this.focusMainWindow();
|
||||||
} else {
|
} else {
|
||||||
|
@ -262,7 +262,6 @@ const AutoType = {
|
||||||
resetPendingEvent() {
|
resetPendingEvent() {
|
||||||
if (this.pendingEvent) {
|
if (this.pendingEvent) {
|
||||||
this.pendingEvent = null;
|
this.pendingEvent = null;
|
||||||
this.appModel.files.off('change', this.processPendingEvent, this);
|
|
||||||
logger.debug('auto-type event cancelled');
|
logger.debug('auto-type event cancelled');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -273,7 +272,6 @@ const AutoType = {
|
||||||
}
|
}
|
||||||
logger.debug('processing pending auto-type event');
|
logger.debug('processing pending auto-type event');
|
||||||
const evt = this.pendingEvent;
|
const evt = this.pendingEvent;
|
||||||
this.appModel.files.off('change', this.processPendingEvent, this);
|
|
||||||
this.pendingEvent = null;
|
this.pendingEvent = null;
|
||||||
this.processEventWithFilter(evt);
|
this.processEventWithFilter(evt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Storage } from 'storage';
|
|
||||||
import { Features } from 'util/features';
|
import { Features } from 'util/features';
|
||||||
|
|
||||||
const AuthReceiver = {
|
const AuthReceiver = {
|
||||||
receive() {
|
receive() {
|
||||||
if (!Features.isPopup && !Features.isStandalone) {
|
if (!Features.isPopup) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const opener = window.opener || window.parent;
|
const opener = window.opener || window.parent;
|
||||||
|
@ -12,16 +11,9 @@ const AuthReceiver = {
|
||||||
if (!hasKeys) {
|
if (!hasKeys) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (Features.isStandalone) {
|
opener.postMessage(message, window.location.origin);
|
||||||
if (sessionStorage.authStorage) {
|
window.close();
|
||||||
Storage[sessionStorage.authStorage].handleOAuthReturnMessage(message);
|
return true;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
opener.postMessage(message, window.location.origin);
|
|
||||||
window.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
urlArgsToMessage(url) {
|
urlArgsToMessage(url) {
|
||||||
|
|
|
@ -4,9 +4,11 @@ import { Tip } from 'util/ui/tip';
|
||||||
import { KeyHandler } from 'comp/browser/key-handler';
|
import { KeyHandler } from 'comp/browser/key-handler';
|
||||||
import { Logger } from 'util/logger';
|
import { Logger } from 'util/logger';
|
||||||
|
|
||||||
const OnlyDirectEvents = {
|
const DoesNotBubble = {
|
||||||
mouseenter: true,
|
mouseenter: true,
|
||||||
mouseleave: true
|
mouseleave: true,
|
||||||
|
blur: true,
|
||||||
|
focus: true
|
||||||
};
|
};
|
||||||
|
|
||||||
class View extends EventEmitter {
|
class View extends EventEmitter {
|
||||||
|
@ -19,6 +21,7 @@ class View extends EventEmitter {
|
||||||
hidden = false;
|
hidden = false;
|
||||||
removed = false;
|
removed = false;
|
||||||
eventListeners = {};
|
eventListeners = {};
|
||||||
|
elementEventListeners = [];
|
||||||
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
|
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
|
||||||
|
|
||||||
constructor(model = undefined, options = {}) {
|
constructor(model = undefined, options = {}) {
|
||||||
|
@ -59,6 +62,7 @@ class View extends EventEmitter {
|
||||||
if (this.el) {
|
if (this.el) {
|
||||||
const mountRoot = this.options.ownParent ? this.el.firstChild : this.el;
|
const mountRoot = this.options.ownParent ? this.el.firstChild : this.el;
|
||||||
morphdom(mountRoot, html);
|
morphdom(mountRoot, html);
|
||||||
|
this.bindElementEvents();
|
||||||
} else {
|
} else {
|
||||||
let parent = this.options.parent || this.parent;
|
let parent = this.options.parent || this.parent;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
@ -102,11 +106,9 @@ class View extends EventEmitter {
|
||||||
if (spaceIx > 0) {
|
if (spaceIx > 0) {
|
||||||
event = eventDef.substr(0, spaceIx);
|
event = eventDef.substr(0, spaceIx);
|
||||||
selector = eventDef.substr(spaceIx + 1);
|
selector = eventDef.substr(spaceIx + 1);
|
||||||
if (OnlyDirectEvents[event]) {
|
if (DoesNotBubble[event]) {
|
||||||
throw new Error(
|
this.elementEventListeners.push({ event, selector, method, els: [] });
|
||||||
`Event listener ${eventDef} defined in ${this.constructor.name} ` +
|
continue;
|
||||||
`can be installed only on the view itself`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
event = eventDef;
|
event = eventDef;
|
||||||
|
@ -117,17 +119,48 @@ class View extends EventEmitter {
|
||||||
eventsMap[event].push({ selector, method });
|
eventsMap[event].push({ selector, method });
|
||||||
}
|
}
|
||||||
for (const [event, handlers] of Object.entries(eventsMap)) {
|
for (const [event, handlers] of Object.entries(eventsMap)) {
|
||||||
this.debugLogger && this.debugLogger.debug('Bind', event, handlers);
|
this.debugLogger && this.debugLogger.debug('Bind', 'view', event, handlers);
|
||||||
const listener = e => this.eventListener(e, handlers);
|
const listener = e => this.eventListener(e, handlers);
|
||||||
this.eventListeners[event] = listener;
|
this.eventListeners[event] = listener;
|
||||||
this.el.addEventListener(event, listener);
|
this.el.addEventListener(event, listener);
|
||||||
}
|
}
|
||||||
|
this.bindElementEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
unbindEvents() {
|
unbindEvents() {
|
||||||
for (const [event, listener] of Object.entries(this.eventListeners)) {
|
for (const [event, listener] of Object.entries(this.eventListeners)) {
|
||||||
this.el.removeEventListener(event, listener);
|
this.el.removeEventListener(event, listener);
|
||||||
}
|
}
|
||||||
|
this.unbindElementEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindElementEvents() {
|
||||||
|
if (!this.elementEventListeners.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.unbindElementEvents();
|
||||||
|
for (const cfg of this.elementEventListeners) {
|
||||||
|
const els = this.el.querySelectorAll(cfg.selector);
|
||||||
|
this.debugLogger &&
|
||||||
|
this.debugLogger.debug('Bind', 'element', cfg.event, cfg.selector, els.length);
|
||||||
|
cfg.listener = e => this.eventListener(e, [cfg]);
|
||||||
|
for (const el of els) {
|
||||||
|
el.addEventListener(cfg.event, cfg.listener);
|
||||||
|
cfg.els.push(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindElementEvents() {
|
||||||
|
if (!this.elementEventListeners.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const cfg of this.elementEventListeners) {
|
||||||
|
for (const el of cfg.els) {
|
||||||
|
el.removeEventListener(cfg.event, cfg.listener);
|
||||||
|
}
|
||||||
|
cfg.els = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventListener(e, handlers) {
|
eventListener(e, handlers) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ FileInfoModel.defineModelProperties({
|
||||||
openDate: null,
|
openDate: null,
|
||||||
keyFileName: null,
|
keyFileName: null,
|
||||||
keyFileHash: null,
|
keyFileHash: null,
|
||||||
|
keyFilePath: null,
|
||||||
opts: null,
|
opts: null,
|
||||||
backup: null,
|
backup: null,
|
||||||
fingerprint: null
|
fingerprint: null
|
||||||
|
|
|
@ -33,22 +33,33 @@ const PluginGallery = {
|
||||||
this.logger.error('Network error loading plugins');
|
this.logger.error('Network error loading plugins');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}).then(data => {
|
})
|
||||||
return this.verifySignature(data).then(gallery => {
|
.then(data => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.loadError = !gallery;
|
if (!data) {
|
||||||
if (gallery) {
|
this.loadError = true;
|
||||||
this.logger.debug(
|
Events.emit('plugin-gallery-load-complete');
|
||||||
`Loaded ${gallery.plugins.length} plugins`,
|
return;
|
||||||
this.logger.ts(ts)
|
|
||||||
);
|
|
||||||
this.gallery = gallery;
|
|
||||||
this.saveGallery(gallery);
|
|
||||||
}
|
}
|
||||||
|
return this.verifySignature(data).then(gallery => {
|
||||||
|
this.loadError = !gallery;
|
||||||
|
if (gallery) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Loaded ${gallery.plugins.length} plugins`,
|
||||||
|
this.logger.ts(ts)
|
||||||
|
);
|
||||||
|
this.gallery = gallery;
|
||||||
|
this.saveGallery(gallery);
|
||||||
|
}
|
||||||
|
Events.emit('plugin-gallery-load-complete');
|
||||||
|
return gallery;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.loadError = true;
|
||||||
|
this.logger.error('Error loading plugin gallery', e);
|
||||||
Events.emit('plugin-gallery-load-complete');
|
Events.emit('plugin-gallery-load-complete');
|
||||||
return gallery;
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
verifySignature(gallery) {
|
verifySignature(gallery) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ class PluginManager extends Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
installIfNew(url, expectedManifest, skipSignatureValidation) {
|
installIfNew(url, expectedManifest, skipSignatureValidation) {
|
||||||
const plugin = this.plugins.find({ url });
|
const plugin = this.plugins.find(p => p.url === url);
|
||||||
if (plugin && plugin.status !== 'invalid') {
|
if (plugin && plugin.status !== 'invalid') {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Events } from 'framework/events';
|
||||||
import { Links } from 'const/links';
|
import { Links } from 'const/links';
|
||||||
import { AppSettingsModel } from 'models/app-settings-model';
|
import { AppSettingsModel } from 'models/app-settings-model';
|
||||||
import { RuntimeDataModel } from 'models/runtime-data-model';
|
import { RuntimeDataModel } from 'models/runtime-data-model';
|
||||||
import { Features } from 'util/features';
|
|
||||||
import { Logger } from 'util/logger';
|
import { Logger } from 'util/logger';
|
||||||
|
|
||||||
const MaxRequestRetries = 3;
|
const MaxRequestRetries = 3;
|
||||||
|
@ -30,18 +29,6 @@ class StorageBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger = new Logger('storage-' + this.name);
|
this.logger = new Logger('storage-' + this.name);
|
||||||
if (this._oauthReturnMessage) {
|
|
||||||
this.logger.debug('OAuth return message', this._oauthReturnMessage);
|
|
||||||
this._oauthProcessReturn(this._oauthReturnMessage);
|
|
||||||
delete this._oauthReturnMessage;
|
|
||||||
delete sessionStorage.authStorage;
|
|
||||||
if (Features.isStandalone) {
|
|
||||||
const [url, urlParams] = location.href.split(/[?#]/);
|
|
||||||
if (urlParams) {
|
|
||||||
location.href = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +36,6 @@ class StorageBase {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOAuthReturnMessage(message) {
|
|
||||||
this._oauthReturnMessage = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
_xhr(config) {
|
_xhr(config) {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
if (config.responseType) {
|
if (config.responseType) {
|
||||||
|
@ -94,8 +77,10 @@ class StorageBase {
|
||||||
if (this._oauthToken && !config.skipAuth) {
|
if (this._oauthToken && !config.skipAuth) {
|
||||||
xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken);
|
xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken);
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(config.headers)) {
|
if (config.headers) {
|
||||||
xhr.setRequestHeader(key, value);
|
for (const [key, value] of Object.entries(config.headers)) {
|
||||||
|
xhr.setRequestHeader(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let data = config.data;
|
let data = config.data;
|
||||||
if (data instanceof ArrayBuffer) {
|
if (data instanceof ArrayBuffer) {
|
||||||
|
@ -135,9 +120,6 @@ class StorageBase {
|
||||||
settings = Object.keys(settings)
|
settings = Object.keys(settings)
|
||||||
.map(key => key + '=' + settings[key])
|
.map(key => key + '=' + settings[key])
|
||||||
.join(',');
|
.join(',');
|
||||||
if (Features.isStandalone) {
|
|
||||||
sessionStorage.authStorage = this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.open(url, title, settings);
|
return window.open(url, title, settings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,8 @@ const KdbxwebInit = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
workerPostRun() {
|
// eslint-disable-next-line object-shorthand
|
||||||
|
workerPostRun: function() {
|
||||||
self.postMessage({ op: 'postRun' });
|
self.postMessage({ op: 'postRun' });
|
||||||
self.onmessage = e => {
|
self.onmessage = e => {
|
||||||
try {
|
try {
|
||||||
|
@ -128,7 +129,8 @@ const KdbxwebInit = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
calcHash(Module, args) {
|
// eslint-disable-next-line object-shorthand
|
||||||
|
calcHash: function(Module, args) {
|
||||||
let { password, salt } = args;
|
let { password, salt } = args;
|
||||||
const { memory, iterations, length, parallelism, type, version } = args;
|
const { memory, iterations, length, parallelism, type, version } = args;
|
||||||
const passwordLen = password.byteLength;
|
const passwordLen = password.byteLength;
|
||||||
|
|
|
@ -235,7 +235,7 @@ class SettingsPluginsView extends View {
|
||||||
pluginMatchesFilter(plugin) {
|
pluginMatchesFilter(plugin) {
|
||||||
const searchStr = this.searchStr;
|
const searchStr = this.searchStr;
|
||||||
const manifest = plugin.manifest;
|
const manifest = plugin.manifest;
|
||||||
return (
|
return !!(
|
||||||
!searchStr ||
|
!searchStr ||
|
||||||
manifest.name.toLowerCase().indexOf(searchStr) >= 0 ||
|
manifest.name.toLowerCase().indexOf(searchStr) >= 0 ||
|
||||||
(manifest.description && manifest.description.toLowerCase().indexOf(searchStr) >= 0) ||
|
(manifest.description && manifest.description.toLowerCase().indexOf(searchStr) >= 0) ||
|
||||||
|
|
|
@ -50,6 +50,8 @@ app.setPath('userData', path.join(tempUserDataPath, tempUserDataPathRand));
|
||||||
setEnv();
|
setEnv();
|
||||||
restorePreferences();
|
restorePreferences();
|
||||||
|
|
||||||
|
const appSettings = readAppSettings() || {};
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (restartPending) {
|
if (restartPending) {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
|
@ -62,11 +64,10 @@ app.on('window-all-closed', () => {
|
||||||
});
|
});
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
appReady = true;
|
appReady = true;
|
||||||
const appSettings = readAppSettings() || {};
|
|
||||||
setAppOptions();
|
setAppOptions();
|
||||||
setSystemAppearance();
|
setSystemAppearance();
|
||||||
createMainWindow(appSettings);
|
createMainWindow();
|
||||||
setGlobalShortcuts(appSettings);
|
setGlobalShortcuts();
|
||||||
subscribePowerEvents();
|
subscribePowerEvents();
|
||||||
deleteOldTempFiles();
|
deleteOldTempFiles();
|
||||||
hookRequestHeaders();
|
hookRequestHeaders();
|
||||||
|
@ -154,7 +155,7 @@ function setSystemAppearance() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMainWindow(appSettings) {
|
function createMainWindow() {
|
||||||
const windowOptions = {
|
const windowOptions = {
|
||||||
show: false,
|
show: false,
|
||||||
width: 1000,
|
width: 1000,
|
||||||
|
@ -394,7 +395,7 @@ function notifyOpenFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGlobalShortcuts(appSettings) {
|
function setGlobalShortcuts() {
|
||||||
const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
|
const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
|
||||||
const defaultShortcuts = {
|
const defaultShortcuts = {
|
||||||
CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },
|
CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "KeeWeb",
|
"name": "KeeWeb",
|
||||||
"version": "1.11.0",
|
"version": "1.11.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "KeeWeb",
|
"name": "KeeWeb",
|
||||||
"version": "1.11.0",
|
"version": "1.11.5",
|
||||||
"description": "Free cross-platform password manager compatible with KeePass",
|
"description": "Free cross-platform password manager compatible with KeePass",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"homepage": "https://keeweb.info",
|
"homepage": "https://keeweb.info",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "keeweb",
|
"name": "keeweb",
|
||||||
"version": "1.11.0",
|
"version": "1.11.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "keeweb",
|
"name": "keeweb",
|
||||||
"version": "1.11.0",
|
"version": "1.11.5",
|
||||||
"description": "Free cross-platform password manager compatible with KeePass",
|
"description": "Free cross-platform password manager compatible with KeePass",
|
||||||
"main": "Gruntfile.js",
|
"main": "Gruntfile.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -1,5 +1,26 @@
|
||||||
Release notes
|
Release notes
|
||||||
-------------
|
-------------
|
||||||
|
##### v1.11.5 (2019-09-29)
|
||||||
|
`-` fix #1279: error opening files with saved keyfiles
|
||||||
|
|
||||||
|
##### v1.11.4 (2019-09-29)
|
||||||
|
`-` fix #1277: auto-type window matching on Linux
|
||||||
|
`-` fix #1278: entry selection auto-type window issues
|
||||||
|
`-` fixed displaying errors on the plugins page
|
||||||
|
|
||||||
|
##### v1.11.3 (2019-09-29)
|
||||||
|
`-` fix #1275: starting the app after closing on macOS
|
||||||
|
`-` fix #1276 GDrive connection issues
|
||||||
|
|
||||||
|
##### v1.11.2 (2019-09-29)
|
||||||
|
`-` fix #1272: Argon2 error
|
||||||
|
`-` fixed Dropbox connection on iOS 13 homescreen
|
||||||
|
`-` fixed plugin search filtering
|
||||||
|
|
||||||
|
##### v1.11.1 (2019-09-28)
|
||||||
|
`-` fix #1270: password change control focus
|
||||||
|
`-` fix #1271: loading custom plugins from config
|
||||||
|
|
||||||
##### v1.11.0 (2019-09-28)
|
##### v1.11.0 (2019-09-28)
|
||||||
`+` #1125: field actions: copy, reveal, auto-type
|
`+` #1125: field actions: copy, reveal, auto-type
|
||||||
`+` #107: multiline custom fields support
|
`+` #107: multiline custom fields support
|
||||||
|
|
Loading…
Reference in New Issue