This commit is contained in:
antelle 2019-08-16 23:05:39 +02:00
parent 68245629e4
commit 84a23e4aea
179 changed files with 7485 additions and 5091 deletions

View File

@ -1,7 +1,6 @@
{
"extends": "standard",
"extends": ["standard", "plugin:prettier/recommended"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"semi": ["error", "always"],
"one-var": "off",
"space-before-function-paren": "off",

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"tabWidth": 4,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "none",
"quoteProps": "preserve"
}

View File

@ -144,7 +144,7 @@ module.exports = function(grunt) {
eslint: {
app: ['app/scripts/**/*.js'],
desktop: ['desktop/**/*.js', '!desktop/node_modules/**'],
grunt: ['Gruntfile.js', 'grunt/**/*.js']
grunt: ['Gruntfile.js', 'build/**/*.js']
},
inline: {
app: {
@ -182,12 +182,21 @@ module.exports = function(grunt) {
files: { 'tmp/desktop/app/index.html': 'dist/index.html' }
},
'desktop-public-key': {
options: { replacements: [{ pattern: '\'@@PUBLIC_KEY_CONTENT\'', replacement:
'`' + fs.readFileSync('app/resources/public-key.pem', {encoding: 'utf8'}).trim() + '`' }] },
options: {
replacements: [
{
pattern: "'@@PUBLIC_KEY_CONTENT'",
replacement:
'`' + fs.readFileSync('app/resources/public-key.pem', { encoding: 'utf8' }).trim() + '`'
}
]
},
files: { 'tmp/desktop/app/main.js': 'desktop/main.js' }
},
'cordova-html': {
options: { replacements: [{ pattern: '<script', replacement: '<script src="cordova.js"></script><script' }] },
options: {
replacements: [{ pattern: '<script', replacement: '<script src="cordova.js"></script><script' }]
},
files: { 'tmp/cordova/app/index.html': 'dist/index.html' }
}
},
@ -239,9 +248,9 @@ module.exports = function(grunt) {
electronVersion: electronVersion,
overwrite: true,
asar: true,
'appCopyright': `Copyright © ${year} Antelle`,
'appVersion': pkg.version,
'buildVersion': '<%= gitinfo.local.branch.current.shortSHA %>'
appCopyright: `Copyright © ${year} Antelle`,
appVersion: pkg.version,
buildVersion: '<%= gitinfo.local.branch.current.shortSHA %>'
},
linux: {
options: {
@ -255,9 +264,9 @@ module.exports = function(grunt) {
platform: 'darwin',
arch: ['x64'],
icon: 'graphics/icon.icns',
'appBundleId': 'net.antelle.keeweb',
'appCategoryType': 'public.app-category.productivity',
'extendInfo': 'package/osx/extend.plist'
appBundleId: 'net.antelle.keeweb',
appCategoryType: 'public.app-category.productivity',
extendInfo: 'package/osx/extend.plist'
}
},
win32: {
@ -265,13 +274,13 @@ module.exports = function(grunt) {
platform: 'win32',
arch: ['ia32', 'x64'],
icon: 'graphics/icon.ico',
'buildVersion': pkg.version,
buildVersion: pkg.version,
'version-string': {
'CompanyName': 'KeeWeb',
'FileDescription': pkg.description,
'OriginalFilename': 'KeeWeb.exe',
'ProductName': 'KeeWeb',
'InternalName': 'KeeWeb'
CompanyName: 'KeeWeb',
FileDescription: pkg.description,
OriginalFilename: 'KeeWeb.exe',
ProductName: 'KeeWeb',
InternalName: 'KeeWeb'
}
}
}
@ -297,9 +306,7 @@ module.exports = function(grunt) {
},
'desktop-update': {
options: { archive: 'dist/desktop/UpdateDesktop.zip', comment: zipCommentPlaceholder },
files: [
{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }
]
files: [{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }]
},
'win32-x64': {
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.win.x64.zip` },
@ -311,13 +318,17 @@ module.exports = function(grunt) {
},
'linux-x64': {
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.linux.x64.zip` },
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true },
{ cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }]
files: [
{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true },
{ cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }
]
},
'linux-ia32': {
options: { archive: `dist/desktop/KeeWeb-${pkg.version}.linux.ia32.zip` },
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true },
{ cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }]
files: [
{ cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true },
{ cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }
]
}
},
appdmg: {
@ -341,7 +352,9 @@ module.exports = function(grunt) {
options: {
vars: {
version: pkg.version,
rev: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); },
rev: function() {
return grunt.config.get('gitinfo.local.branch.current.shortSHA');
},
homepage: pkg.homepage
}
},
@ -383,7 +396,9 @@ module.exports = function(grunt) {
description: pkg.description,
author: pkg.author,
homepage: pkg.homepage,
rev: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); }
rev: function() {
return grunt.config.get('gitinfo.local.branch.current.shortSHA');
}
}
},
'linux-x64': {
@ -401,8 +416,18 @@ module.exports = function(grunt) {
},
files: [
{ cwd: 'package/deb/usr', src: '**', dest: '/usr', expand: true, nonull: true },
{ cwd: 'tmp/desktop/KeeWeb-linux-x64/', src: '**', dest: '/opt/keeweb-desktop', expand: true, nonull: true },
{ src: 'graphics/128x128.png', dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', nonull: true }
{
cwd: 'tmp/desktop/KeeWeb-linux-x64/',
src: '**',
dest: '/opt/keeweb-desktop',
expand: true,
nonull: true
},
{
src: 'graphics/128x128.png',
dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png',
nonull: true
}
]
},
'linux-ia32': {
@ -420,8 +445,18 @@ module.exports = function(grunt) {
},
files: [
{ cwd: 'package/deb/usr', src: '**', dest: '/usr', expand: true, nonull: true },
{ cwd: 'tmp/desktop/KeeWeb-linux-ia32/', src: '**', dest: '/opt/keeweb-desktop', expand: true, nonull: true },
{ src: 'graphics/128x128.png', dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', nonull: true }
{
cwd: 'tmp/desktop/KeeWeb-linux-ia32/',
src: '**',
dest: '/opt/keeweb-desktop',
expand: true,
nonull: true
},
{
src: 'graphics/128x128.png',
dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png',
nonull: true
}
]
}
},
@ -444,18 +479,14 @@ module.exports = function(grunt) {
desktop: {
options: {
file: 'dist/desktop/UpdateDesktop.zip',
expected: [
'app.asar',
'helper/darwin/KeeWebHelper',
'helper/win32/KeeWebHelper.exe'
],
expected: ['app.asar', 'helper/darwin/KeeWebHelper', 'helper/win32/KeeWebHelper.exe'],
expectedCount: 7,
publicKey: 'app/resources/public-key.pem'
}
}
},
'sign-html': {
'app': {
app: {
options: {
file: 'dist/index.html',
skip: grunt.option('skip-sign')
@ -521,7 +552,7 @@ module.exports = function(grunt) {
}
},
'sign-dist': {
'dist': {
dist: {
options: {
sign: 'dist/desktop/Verify.sign.sha256'
},

View File

@ -24,7 +24,7 @@ const FeatureDetector = require('./util/feature-detector');
const KdbxwebInit = require('./util/kdbxweb-init');
const Locale = require('./util/locale');
const ready = Launcher && Launcher.ready || $;
const ready = (Launcher && Launcher.ready) || $;
ready(() => {
if (AuthReceiver.receive() || FeatureDetector.isFrame) {
@ -51,16 +51,17 @@ ready(() => {
}
function ensureCanRun() {
return FeatureTester.test()
.catch(e => {
Alerts.error({
header: Locale.appSettingsError,
body: Locale.appNotSupportedError + '<br/><br/>' + e,
buttons: [],
esc: false, enter: false, click: false
});
throw 'Feature testing failed: ' + e;
return FeatureTester.test().catch(e => {
Alerts.error({
header: Locale.appSettingsError,
body: Locale.appNotSupportedError + '<br/><br/>' + e,
buttons: [],
esc: false,
enter: false,
click: false
});
throw 'Feature testing failed: ' + e;
});
}
function loadConfigs() {
@ -87,7 +88,9 @@ ready(() => {
header: Locale.appSettingsError,
body: Locale.appSettingsErrorBody,
buttons: [],
esc: false, enter: false, click: false
esc: false,
enter: false,
click: false
});
}
@ -96,42 +99,46 @@ ready(() => {
SettingsManager.setBySettings(appModel.settings);
const configParam = getConfigParam();
if (configParam) {
return appModel.loadConfig(configParam).then(() => {
SettingsManager.setBySettings(appModel.settings);
}).catch(e => {
if (!appModel.settings.get('cacheConfigSettings')) {
showSettingsLoadError();
throw e;
}
});
return appModel
.loadConfig(configParam)
.then(() => {
SettingsManager.setBySettings(appModel.settings);
})
.catch(e => {
if (!appModel.settings.get('cacheConfigSettings')) {
showSettingsLoadError();
throw e;
}
});
}
});
}
function showApp() {
return Promise.resolve()
.then(() => {
const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning');
const protocolIsInsecure = ['https:', 'file:', 'app:'].indexOf(location.protocol) < 0;
const hostIsInsecure = location.hostname !== 'localhost';
if (protocolIsInsecure && hostIsInsecure && !skipHttpsWarning) {
return new Promise(resolve => {
Alerts.error({
header: Locale.appSecWarn, icon: 'user-secret', esc: false, enter: false, click: false,
body: Locale.appSecWarnBody1 + '<br/><br/>' + Locale.appSecWarnBody2,
buttons: [
{result: '', title: Locale.appSecWarnBtn, error: true}
],
complete: () => {
showView();
resolve();
}
});
return Promise.resolve().then(() => {
const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning');
const protocolIsInsecure = ['https:', 'file:', 'app:'].indexOf(location.protocol) < 0;
const hostIsInsecure = location.hostname !== 'localhost';
if (protocolIsInsecure && hostIsInsecure && !skipHttpsWarning) {
return new Promise(resolve => {
Alerts.error({
header: Locale.appSecWarn,
icon: 'user-secret',
esc: false,
enter: false,
click: false,
body: Locale.appSecWarnBody1 + '<br/><br/>' + Locale.appSecWarnBody2,
buttons: [{ result: '', title: Locale.appSecWarnBtn, error: true }],
complete: () => {
showView();
resolve();
}
});
} else {
showView();
}
});
});
} else {
showView();
}
});
}
function postInit() {

View File

@ -17,14 +17,13 @@ AutoTypeFilter.prototype.getEntries = function() {
autoType: true
};
this.prepareFilter();
let entries = this.appModel.getEntriesByFilter(filter)
.map(e => [e, this.getEntryRank(e)]);
let entries = this.appModel.getEntriesByFilter(filter).map(e => [e, this.getEntryRank(e)]);
if (!this.ignoreWindowInfo) {
entries = entries.filter(e => e[1]);
}
entries = entries.sort((x, y) => x[1] === y[1] ? x[0].title.localeCompare(y[0].title) : y[1] - x[1]);
entries = entries.sort((x, y) => (x[1] === y[1] ? x[0].title.localeCompare(y[0].title) : y[1] - x[1]));
entries = entries.map(p => p[0]);
return new EntryCollection(entries, {comparator: 'none'});
return new EntryCollection(entries, { comparator: 'none' });
};
AutoTypeFilter.prototype.hasWindowInfo = function() {
@ -40,10 +39,7 @@ AutoTypeFilter.prototype.prepareFilter = function() {
AutoTypeFilter.prototype.getEntryRank = function(entry) {
let rank = 0;
if (this.titleLower && entry.title) {
rank += Ranking.getStringRank(
entry.title.toLowerCase(),
this.titleLower
);
rank += Ranking.getStringRank(entry.title.toLowerCase(), this.titleLower);
}
if (this.urlParts && entry.url) {
const entryUrlParts = urlPartsRegex.exec(entry.url.toLowerCase());

View File

@ -33,8 +33,12 @@ AutoTypeObfuscator.prototype.obfuscate = function() {
};
AutoTypeObfuscator.prototype.finished = function() {
return this.chars.length === this.inputChars.length &&
this.chars.every(function(ch, ix) { return this.inputChars[ix].ch === ch; }, this);
return (
this.chars.length === this.inputChars.length &&
this.chars.every(function(ch, ix) {
return this.inputChars[ix].ch === ch;
}, this)
);
};
AutoTypeObfuscator.prototype.step = function() {
@ -161,7 +165,7 @@ AutoTypeObfuscator.prototype.inputChar = function(ch) {
AutoTypeObfuscator.prototype.copyPaste = function(ch) {
logger.debug('copyPaste', ch);
this.ops.push({type: 'cmd', value: 'copyPaste', arg: ch});
this.ops.push({ type: 'cmd', value: 'copyPaste', arg: ch });
this.inputChars.splice(this.inputCursor, this.inputSel, { ch: ch });
this.inputCursor++;
this.inputSel = 0;
@ -174,10 +178,10 @@ AutoTypeObfuscator.prototype.selectText = function(backward, count) {
ops.push({ type: 'key', value: backward ? 'left' : 'right' });
}
if (ops.length === 1) {
ops[0].mod = {'+': true};
ops[0].mod = { '+': true };
this.ops.push(ops[0]);
} else {
this.ops.push({type: 'group', value: ops, mod: {'+': true}});
this.ops.push({ type: 'group', value: ops, mod: { '+': true } });
}
if (backward) {
this.inputCursor -= count;

View File

@ -16,39 +16,127 @@ const AutoTypeRunner = function(ops) {
AutoTypeRunner.PendingResolve = { pending: true };
AutoTypeRunner.Keys = {
tab: 'tab', enter: 'enter', space: 'space',
up: 'up', down: 'down', left: 'left', right: 'right', home: 'home', end: 'end', pgup: 'pgup', pgdn: 'pgdn',
insert: 'ins', ins: 'ins', delete: 'del', del: 'del', backspace: 'bs', bs: 'bs', bksp: 'bs', esc: 'esc',
win: 'win', lwin: 'win', rwin: 'rwin', f1: 'f1', f2: 'f2', f3: 'f3', f4: 'f4', f5: 'f5', f6: 'f6',
f7: 'f7', f8: 'f8', f9: 'f9', f10: 'f10', f11: 'f11', f12: 'f12', f13: 'f13', f14: 'f14', f15: 'f15', f16: 'f16',
add: 'add', subtract: 'subtract', multiply: 'multiply', divide: 'divide',
numpad0: 'n0', numpad1: 'n1', numpad2: 'n2', numpad3: 'n3', numpad4: 'n4',
numpad5: 'n5', numpad6: 'n6', numpad7: 'n7', numpad8: 'n8', numpad9: 'n9'
tab: 'tab',
enter: 'enter',
space: 'space',
up: 'up',
down: 'down',
left: 'left',
right: 'right',
home: 'home',
end: 'end',
pgup: 'pgup',
pgdn: 'pgdn',
insert: 'ins',
ins: 'ins',
delete: 'del',
del: 'del',
backspace: 'bs',
bs: 'bs',
bksp: 'bs',
esc: 'esc',
win: 'win',
lwin: 'win',
rwin: 'rwin',
f1: 'f1',
f2: 'f2',
f3: 'f3',
f4: 'f4',
f5: 'f5',
f6: 'f6',
f7: 'f7',
f8: 'f8',
f9: 'f9',
f10: 'f10',
f11: 'f11',
f12: 'f12',
f13: 'f13',
f14: 'f14',
f15: 'f15',
f16: 'f16',
add: 'add',
subtract: 'subtract',
multiply: 'multiply',
divide: 'divide',
numpad0: 'n0',
numpad1: 'n1',
numpad2: 'n2',
numpad3: 'n3',
numpad4: 'n4',
numpad5: 'n5',
numpad6: 'n6',
numpad7: 'n7',
numpad8: 'n8',
numpad9: 'n9'
};
AutoTypeRunner.Substitutions = {
title: function(runner, op) { return runner.getEntryFieldKeys('Title', op); },
username: function(runner, op) { return runner.getEntryFieldKeys('UserName', op); },
url: function(runner, op) { return runner.getEntryFieldKeys('URL', op); },
password: function(runner, op) { return runner.getEntryFieldKeys('Password', op); },
notes: function(runner, op) { return runner.getEntryFieldKeys('Notes', op); },
group: function(runner) { return runner.getEntryGroupName(); },
totp: function(runner, op) { return runner.getOtp(op); },
s: function(runner, op) { return runner.getEntryFieldKeys(op.arg, op); },
'dt_simple': function(runner) { return runner.dt('simple'); },
'dt_year': function(runner) { return runner.dt('Y'); },
'dt_month': function(runner) { return runner.dt('M'); },
'dt_day': function(runner) { return runner.dt('D'); },
'dt_hour': function(runner) { return runner.dt('h'); },
'dt_minute': function(runner) { return runner.dt('m'); },
'dt_second': function(runner) { return runner.dt('s'); },
'dt_utc_simple': function(runner) { return runner.udt('simple'); },
'dt_utc_year': function(runner) { return runner.udt('Y'); },
'dt_utc_month': function(runner) { return runner.udt('M'); },
'dt_utc_day': function(runner) { return runner.udt('D'); },
'dt_utc_hour': function(runner) { return runner.udt('h'); },
'dt_utc_minute': function(runner) { return runner.udt('m'); },
'dt_utc_second': function(runner) { return runner.udt('s'); }
title: function(runner, op) {
return runner.getEntryFieldKeys('Title', op);
},
username: function(runner, op) {
return runner.getEntryFieldKeys('UserName', op);
},
url: function(runner, op) {
return runner.getEntryFieldKeys('URL', op);
},
password: function(runner, op) {
return runner.getEntryFieldKeys('Password', op);
},
notes: function(runner, op) {
return runner.getEntryFieldKeys('Notes', op);
},
group: function(runner) {
return runner.getEntryGroupName();
},
totp: function(runner, op) {
return runner.getOtp(op);
},
s: function(runner, op) {
return runner.getEntryFieldKeys(op.arg, op);
},
'dt_simple': function(runner) {
return runner.dt('simple');
},
'dt_year': function(runner) {
return runner.dt('Y');
},
'dt_month': function(runner) {
return runner.dt('M');
},
'dt_day': function(runner) {
return runner.dt('D');
},
'dt_hour': function(runner) {
return runner.dt('h');
},
'dt_minute': function(runner) {
return runner.dt('m');
},
'dt_second': function(runner) {
return runner.dt('s');
},
'dt_utc_simple': function(runner) {
return runner.udt('simple');
},
'dt_utc_year': function(runner) {
return runner.udt('Y');
},
'dt_utc_month': function(runner) {
return runner.udt('M');
},
'dt_utc_day': function(runner) {
return runner.udt('D');
},
'dt_utc_hour': function(runner) {
return runner.udt('h');
},
'dt_utc_minute': function(runner) {
return runner.udt('m');
},
'dt_utc_second': function(runner) {
return runner.udt('s');
}
};
AutoTypeRunner.prototype.resolve = function(entry, callback) {
@ -103,7 +191,7 @@ AutoTypeRunner.prototype.resolveOp = function(op) {
op.value = [];
const count = +op.arg;
for (let i = 0; i < count; i++) {
op.value.push({type: 'key', value: key});
op.value.push({ type: 'key', value: key });
}
} else {
// {TAB}
@ -179,9 +267,9 @@ AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) {
const ops = [];
value.forEachChar(ch => {
if (ch === 10 || ch === 13) {
ops.push({type: 'key', value: 'enter'});
ops.push({ type: 'key', value: 'enter' });
} else {
ops.push({type: 'text', value: String.fromCharCode(ch)});
ops.push({ type: 'text', value: String.fromCharCode(ch) });
}
});
return ops;
@ -194,10 +282,10 @@ AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) {
const partsOps = [];
parts.forEach(part => {
if (partsOps.length) {
partsOps.push({type: 'key', value: 'enter'});
partsOps.push({ type: 'key', value: 'enter' });
}
if (part) {
partsOps.push({type: 'text', value: part});
partsOps.push({ type: 'text', value: part });
}
});
return partsOps;

View File

@ -3,15 +3,53 @@ const AutoTypeNativeHelper = require('../helper/auto-type-native-helper');
// http://eastmanreference.com/complete-list-of-applescript-key-codes/
const KeyMap = {
tab: 48, enter: 36, space: 49,
up: 126, down: 125, left: 123, right: 124, home: 115, end: 119, pgup: 116, pgdn: 121,
ins: 114, del: 117, bs: 51, esc: 53,
win: 55, rwin: 55,
f1: 122, f2: 120, f3: 99, f4: 118, f5: 96, f6: 97, f7: 98, f8: 100, f9: 101,
f10: 109, f11: 103, f12: 111, f13: 105, f14: 107, f15: 113, f16: 106,
add: 69, subtract: 78, multiply: 67, divide: 75,
n0: 82, n1: 83, n2: 84, n3: 85, n4: 86,
n5: 87, n6: 88, n7: 89, n8: 91, n9: 92
tab: 48,
enter: 36,
space: 49,
up: 126,
down: 125,
left: 123,
right: 124,
home: 115,
end: 119,
pgup: 116,
pgdn: 121,
ins: 114,
del: 117,
bs: 51,
esc: 53,
win: 55,
rwin: 55,
f1: 122,
f2: 120,
f3: 99,
f4: 118,
f5: 96,
f6: 97,
f7: 98,
f8: 100,
f9: 101,
f10: 109,
f11: 103,
f12: 111,
f13: 105,
f14: 107,
f15: 113,
f16: 106,
add: 69,
subtract: 78,
multiply: 67,
divide: 75,
n0: 82,
n1: 83,
n2: 84,
n3: 85,
n4: 86,
n5: 87,
n6: 88,
n7: 89,
n8: 91,
n9: 92
};
const ModMap = {

View File

@ -3,15 +3,53 @@ const Locale = require('../../util/locale');
// https://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
const KeyMap = {
tab: 'Tab', enter: 'KP_Enter', space: 'KP_Space',
up: 'Up', down: 'Down', left: 'Left', right: 'Right', home: 'Home', end: 'End', pgup: 'Page_Up', pgdn: 'Page_Down',
ins: 'Insert', del: 'Delete', bs: 'BackSpace', esc: 'Escape',
win: 'Meta_L', rwin: 'Meta_R',
f1: 'F1', f2: 'F2', f3: 'F3', f4: 'F4', f5: 'F5', f6: 'F6', f7: 'F7', f8: 'F8', f9: 'F9',
f10: 'F10', f11: 'F11', f12: 'F12', f13: 'F13', f14: 'F14', f15: 'F15', f16: 'F16',
add: 'KP_Add', subtract: 'KP_Subtract', multiply: 'KP_Multiply', divide: 'KP_Divide',
n0: 'KP_0', n1: 'KP_1', n2: 'KP_2', n3: 'KP_3', n4: 'KP_4',
n5: 'KP_5', n6: 'KP_6', n7: 'KP_7', n8: 'KP_8', n9: 'KP_9'
tab: 'Tab',
enter: 'KP_Enter',
space: 'KP_Space',
up: 'Up',
down: 'Down',
left: 'Left',
right: 'Right',
home: 'Home',
end: 'End',
pgup: 'Page_Up',
pgdn: 'Page_Down',
ins: 'Insert',
del: 'Delete',
bs: 'BackSpace',
esc: 'Escape',
win: 'Meta_L',
rwin: 'Meta_R',
f1: 'F1',
f2: 'F2',
f3: 'F3',
f4: 'F4',
f5: 'F5',
f6: 'F6',
f7: 'F7',
f8: 'F8',
f9: 'F9',
f10: 'F10',
f11: 'F11',
f12: 'F12',
f13: 'F13',
f14: 'F14',
f15: 'F15',
f16: 'F16',
add: 'KP_Add',
subtract: 'KP_Subtract',
multiply: 'KP_Multiply',
divide: 'KP_Divide',
n0: 'KP_0',
n1: 'KP_1',
n2: 'KP_2',
n3: 'KP_3',
n4: 'KP_4',
n5: 'KP_5',
n6: 'KP_6',
n7: 'KP_7',
n8: 'KP_8',
n9: 'KP_9'
};
const ModMap = {
@ -69,7 +107,7 @@ AutoTypeEmitter.prototype.copyPaste = function(text) {
};
AutoTypeEmitter.prototype.wait = function(time) {
this.pendingScript.push('sleep ' + (time / 1000));
this.pendingScript.push('sleep ' + time / 1000);
this.callback();
};

View File

@ -3,15 +3,53 @@ const AutoTypeNativeHelper = require('../helper/auto-type-native-helper');
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
const KeyMap = {
tab: 0x09, enter: 0x0D, space: 0x20,
up: 0x26, down: 0x28, left: 0x25, right: 0x27, home: 0x24, end: 0x23, pgup: 0x21, pgdn: 0x22,
ins: 0x2D, del: 0x2E, bs: 0x08, esc: 0x1B,
win: 0x5B, rwin: 0x5C,
f1: 0x70, f2: 0x71, f3: 0x72, f4: 0x73, f5: 0x74, f6: 0x75, f7: 0x76, f8: 0x77, f9: 0x78,
f10: 0x79, f11: 0x7A, f12: 0x7B, f13: 0x7C, f14: 0x7D, f15: 0x7E, f16: 0x7F,
add: 0x6B, subtract: 0x6D, multiply: 0x6A, divide: 0x6F,
n0: 0x30, n1: 0x31, n2: 0x32, n3: 0x33, n4: 0x34,
n5: 0x35, n6: 0x36, n7: 0x37, n8: 0x38, n9: 0x39
tab: 0x09,
enter: 0x0d,
space: 0x20,
up: 0x26,
down: 0x28,
left: 0x25,
right: 0x27,
home: 0x24,
end: 0x23,
pgup: 0x21,
pgdn: 0x22,
ins: 0x2d,
del: 0x2e,
bs: 0x08,
esc: 0x1b,
win: 0x5b,
rwin: 0x5c,
f1: 0x70,
f2: 0x71,
f3: 0x72,
f4: 0x73,
f5: 0x74,
f6: 0x75,
f7: 0x76,
f8: 0x77,
f9: 0x78,
f10: 0x79,
f11: 0x7a,
f12: 0x7b,
f13: 0x7c,
f14: 0x7d,
f15: 0x7e,
f16: 0x7f,
add: 0x6b,
subtract: 0x6d,
multiply: 0x6a,
divide: 0x6f,
n0: 0x30,
n1: 0x31,
n2: 0x32,
n3: 0x33,
n4: 0x34,
n5: 0x35,
n6: 0x36,
n7: 0x37,
n8: 0x38,
n9: 0x39
};
const ModMap = {

View File

@ -1,13 +1,17 @@
const Launcher = require('../../comp/launcher');
const ForeMostAppScript = 'tell application "System Events" to set frontApp to name of first process whose frontmost is true';
const ChromeScript = 'tell application "{}" to set appUrl to URL of active tab of front window\n' +
const ForeMostAppScript =
'tell application "System Events" to set frontApp to name of first process whose frontmost is true';
const ChromeScript =
'tell application "{}" to set appUrl to URL of active tab of front window\n' +
'tell application "{}" to set appTitle to title of active tab of front window\n' +
'return appUrl & "\n" & appTitle';
const SafariScript = 'tell application "{}" to set appUrl to URL of front document\n' +
const SafariScript =
'tell application "{}" to set appUrl to URL of front document\n' +
'tell application "{}" to set appTitle to name of front document\n' +
'return appUrl & "\n" & appTitle';
const OtherAppsScript = 'tell application "System Events"\n' +
const OtherAppsScript =
'tell application "System Events"\n' +
' tell process "{}"\n' +
' tell (1st window whose value of attribute "AXMain" is true)\n' +
' set windowTitle to value of attribute "AXTitle"\n' +
@ -15,12 +19,13 @@ const OtherAppsScript = 'tell application "System Events"\n' +
' end tell\n' +
'end tell';
const AutoTypeHelper = function() {
};
const AutoTypeHelper = function() {};
AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) {
AutoTypeHelper.exec(ForeMostAppScript, (err, out) => {
if (err) { return callback(err); }
if (err) {
return callback(err);
}
const appName = out.trim();
// getting urls and titles from Chrome or Safari:
// - will suit in 90% cases
@ -28,20 +33,26 @@ AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) {
// - allows to get url
if (['Google Chrome', 'Chromium', 'Google Chrome Canary'].indexOf(appName) >= 0) {
AutoTypeHelper.exec(ChromeScript.replace(/\{}/g, appName), (err, out) => {
if (err) { return callback(err); }
if (err) {
return callback(err);
}
const parts = out.split('\n');
return callback(null, (parts[1] || '').trim(), parts[0].trim());
});
} else if (['Safari', 'Webkit'].indexOf(appName) >= 0) {
AutoTypeHelper.exec(SafariScript.replace(/\{}/g, appName), (err, out) => {
if (err) { return callback(err); }
if (err) {
return callback(err);
}
const parts = out.split('\n');
return callback(null, (parts[1] || '').trim(), parts[0].trim());
});
} else {
// special cases are not available. this method may ask the user about assistive access
AutoTypeHelper.exec(OtherAppsScript.replace(/\{}/g, appName), (err, out) => {
if (err) { return callback(err); }
if (err) {
return callback(err);
}
return callback(null, out.trim());
});
}

View File

@ -1,7 +1,6 @@
const Launcher = require('../../comp/launcher');
const AutoTypeHelper = function() {
};
const AutoTypeHelper = function() {};
AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) {
Launcher.spawn({

View File

@ -1,18 +1,18 @@
const Launcher = require('../../comp/launcher');
const AutoTypeNativeHelper = require('./auto-type-native-helper');
const AutoTypeHelper = function() {
};
const AutoTypeHelper = function() {};
AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) {
Launcher.spawn({
cmd: AutoTypeNativeHelper.getHelperPath(),
args: ['--window-info'],
complete: function(err, out) {
if (err) { return callback(err); }
if (err) {
return callback(err);
}
const parts = out.split('\n');
return callback(null, (parts[0] || '').trim(),
parts[1] ? parts[1].trim() : undefined);
return callback(null, (parts[0] || '').trim(), parts[1] ? parts[1].trim() : undefined);
}
});
};

View File

@ -31,14 +31,16 @@ const AutoType = {
},
handleEvent(e) {
const entry = e && e.entry || null;
const entry = (e && e.entry) || null;
logger.debug('Auto type event', entry);
if (this.running) {
logger.debug('Already running, skipping event');
return;
}
if (entry) {
this.hideWindow(() => { this.runAndHandleResult({ entry }); });
this.hideWindow(() => {
this.runAndHandleResult({ entry });
});
} else {
if (this.selectEntryView) {
return;
@ -180,7 +182,7 @@ const AutoType = {
selectEntryAndRun() {
this.getActiveWindowTitle((e, title, url) => {
const filter = new AutoTypeFilter({title, url}, this.appModel);
const filter = new AutoTypeFilter({ title, url }, this.appModel);
const evt = { filter };
if (!this.appModel.files.hasOpenFiles()) {
this.pendingEvent = evt;
@ -224,10 +226,14 @@ const AutoType = {
this.selectEntryView.on('show-open-files', () => {
this.selectEntryView.hide();
Backbone.trigger('open-file');
Backbone.once('closed-open-view', () => {
this.selectEntryView.show();
this.selectEntryView.setupKeys();
}, this);
Backbone.once(
'closed-open-view',
() => {
this.selectEntryView.show();
this.selectEntryView.setupKeys();
},
this
);
});
},

View File

@ -19,7 +19,9 @@ const EntryCollection = Backbone.Collection.extend({
'-created': Comparators.dateComparator('created', false),
'updated': Comparators.dateComparator('updated', true),
'-updated': Comparators.dateComparator('updated', false),
'-attachments': function(x, y) { return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y)); },
'-attachments': function(x, y) {
return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y));
},
'-rank': Comparators.rankComparator()
},
@ -28,7 +30,7 @@ const EntryCollection = Backbone.Collection.extend({
filter: null,
initialize: function(models, options) {
const comparatorName = options && options.comparator || this.defaultComparator;
const comparatorName = (options && options.comparator) || this.defaultComparator;
this.comparator = this.comparators[comparatorName];
},

View File

@ -5,30 +5,31 @@ const SettingsStore = require('../comp/settings-store');
const FileInfoCollection = Backbone.Collection.extend({
model: FileInfoModel,
initialize: function () {
},
initialize: function() {},
load: function () {
load: function() {
return SettingsStore.load('file-info').then(data => {
if (data) {
this.reset(data, {silent: true});
this.reset(data, { silent: true });
}
});
},
save: function () {
save: function() {
SettingsStore.save('file-info', this.toJSON());
},
getLast: function () {
getLast: function() {
return this.first();
},
getMatch: function (storage, name, path) {
getMatch: function(storage, name, path) {
return this.find(fi => {
return (fi.get('storage') || '') === (storage || '') &&
return (
(fi.get('storage') || '') === (storage || '') &&
(fi.get('name') || '') === (name || '') &&
(fi.get('path') || '') === (path || '');
(fi.get('path') || '') === (path || '')
);
});
},

View File

@ -5,10 +5,30 @@ const Alerts = {
alertDisplayed: false,
buttons: {
ok: {result: 'yes', get title() { return Locale.alertOk; }},
yes: {result: 'yes', get title() { return Locale.alertYes; }},
no: {result: '', get title() { return Locale.alertNo; }},
cancel: {result: '', get title() { return Locale.alertCancel; }}
ok: {
result: 'yes',
get title() {
return Locale.alertOk;
}
},
yes: {
result: 'yes',
get title() {
return Locale.alertYes;
}
},
no: {
result: '',
get title() {
return Locale.alertNo;
}
},
cancel: {
result: '',
get title() {
return Locale.alertCancel;
}
}
},
alert: function(config) {
@ -46,39 +66,54 @@ const Alerts = {
},
info: function(config) {
this.alert(_.extend({
header: '',
body: '',
icon: 'info',
buttons: [this.buttons.ok],
esc: '',
click: '',
enter: ''
}, config));
this.alert(
_.extend(
{
header: '',
body: '',
icon: 'info',
buttons: [this.buttons.ok],
esc: '',
click: '',
enter: ''
},
config
)
);
},
error: function(config) {
this.alert(_.extend({
header: '',
body: '',
icon: 'exclamation-circle',
buttons: [this.buttons.ok],
esc: '',
click: '',
enter: ''
}, config));
this.alert(
_.extend(
{
header: '',
body: '',
icon: 'exclamation-circle',
buttons: [this.buttons.ok],
esc: '',
click: '',
enter: ''
},
config
)
);
},
yesno: function(config) {
this.alert(_.extend({
header: '',
body: '',
icon: 'question',
buttons: [this.buttons.yes, this.buttons.no],
esc: '',
click: '',
enter: 'yes'
}, config));
this.alert(
_.extend(
{
header: '',
body: '',
icon: 'question',
buttons: [this.buttons.yes, this.buttons.no],
esc: '',
click: '',
enter: 'yes'
},
config
)
);
}
};

View File

@ -37,13 +37,13 @@ const AppRightsChecker = {
this.alert = Alerts.alert({
icon: 'lock',
header: Locale.appRightsAlert,
body: Locale.appRightsAlertBody1.replace('{}', `<code>${this.AppPath}</code>`) +
'<br/>' + Locale.appRightsAlertBody2 + `: <pre>${command}</pre>`,
buttons: [
{result: 'skip', title: Locale.alertDoNotAsk, error: true},
Alerts.buttons.ok
],
success: (result) => {
body:
Locale.appRightsAlertBody1.replace('{}', `<code>${this.AppPath}</code>`) +
'<br/>' +
Locale.appRightsAlertBody2 +
`: <pre>${command}</pre>`,
buttons: [{ result: 'skip', title: Locale.alertDoNotAsk, error: true }, Alerts.buttons.ok],
success: result => {
if (result === 'skip') {
this.dontAskAnymore();
}
@ -54,7 +54,7 @@ const AppRightsChecker = {
runInstaller() {
Launcher.spawn({
cmd: this.AppPath + '/Contents/Installer/KeeWeb\ Installer.app/Contents/MacOS/applet',
cmd: this.AppPath + '/Contents/Installer/KeeWeb Installer.app/Contents/MacOS/applet',
complete: () => {
this.needRunInstaller(needRun => {
if (this.alert && !needRun) {

View File

@ -21,13 +21,13 @@ const CopyPaste = {
Backbone.off('main-window-will-close', clearClipboard);
}, clipboardSeconds * 1000);
}
return {success: true, seconds: clipboardSeconds};
return { success: true, seconds: clipboardSeconds };
} else {
try {
if (document.execCommand('copy')) {
return {success: true};
return { success: true };
}
} catch (e) { }
} catch (e) {}
return false;
}
},
@ -41,8 +41,12 @@ const CopyPaste = {
hiddenInput[0].selectionEnd = text.length;
hiddenInput.focus();
hiddenInput.on({
'copy cut paste': function() { setTimeout(() => hiddenInput.blur(), 0); },
blur: function() { hiddenInput.remove(); }
'copy cut paste': function() {
setTimeout(() => hiddenInput.blur(), 0);
},
blur: function() {
hiddenInput.remove();
}
});
}
};

View File

@ -37,7 +37,12 @@ DropboxChooser.prototype.buildUrl = function() {
iframe: 'false',
version: 2
};
return 'https://www.dropbox.com/chooser?' + Object.keys(urlParams).map(key => key + '=' + urlParams[key]).join('&');
return (
'https://www.dropbox.com/chooser?' +
Object.keys(urlParams)
.map(key => key + '=' + urlParams[key])
.join('&')
);
};
DropboxChooser.prototype.onMessage = function(e) {

View File

@ -2,9 +2,15 @@ const AppSettingsModel = require('../models/app-settings-model');
const ExportApi = {
settings: {
get: function(key) { return key ? AppSettingsModel.instance.get(key) : AppSettingsModel.instance.toJSON(); },
set: function(key, value) { AppSettingsModel.instance.set(key, value); },
del: function(key) { AppSettingsModel.instance.unset(key); }
get: function(key) {
return key ? AppSettingsModel.instance.get(key) : AppSettingsModel.instance.toJSON();
},
set: function(key, value) {
AppSettingsModel.instance.set(key, value);
},
del: function(key) {
AppSettingsModel.instance.unset(key);
}
}
};

View File

@ -25,16 +25,23 @@ const FeatureTester = {
const data = 'e567554429098a38d5f819115edffd39';
const iv = '4db46dff4add42cb813b98de98e627c4';
const exp = '46ab4c37d9ec594e5742971f76f7c1620bc29f2e0736b27832d6bcc5c1c39dc1';
return aesCbc.importKey(kdbxweb.ByteUtils.hexToBytes(key)).then(() => {
return aesCbc.encrypt(kdbxweb.ByteUtils.hexToBytes(data), kdbxweb.ByteUtils.hexToBytes(iv)).then(res => {
if (kdbxweb.ByteUtils.bytesToHex(res) !== exp) {
throw 'AES is not working properly';
}
if (kdbxweb.CryptoEngine.random(1).length !== 1) {
throw 'Random is not working';
}
return aesCbc
.importKey(kdbxweb.ByteUtils.hexToBytes(key))
.then(() => {
return aesCbc
.encrypt(kdbxweb.ByteUtils.hexToBytes(data), kdbxweb.ByteUtils.hexToBytes(iv))
.then(res => {
if (kdbxweb.ByteUtils.bytesToHex(res) !== exp) {
throw 'AES is not working properly';
}
if (kdbxweb.CryptoEngine.random(1).length !== 1) {
throw 'Random is not working';
}
});
})
.catch(e => {
throw 'WebCrypto is not supported: ' + e;
});
}).catch(e => { throw 'WebCrypto is not supported: ' + e; });
});
},

View File

@ -3,33 +3,37 @@ const Locale = require('../util/locale');
const GeneratorPresets = {
get defaultPreset() {
return { name: 'Default', title: Locale.genPresetDefault,
length: 16, upper: true, lower: true, digits: true };
return { name: 'Default', title: Locale.genPresetDefault, length: 16, upper: true, lower: true, digits: true };
},
get builtIn() {
return [
this.defaultPreset,
{ name: 'Pronounceable', title: Locale.genPresetPronounceable,
length: 10, lower: true, upper: true },
{ name: 'Med', title: Locale.genPresetMed,
length: 16, upper: true, lower: true, digits: true, special: true, brackets: true, ambiguous: true },
{ name: 'Long', title: Locale.genPresetLong,
length: 32, upper: true, lower: true, digits: true },
{ name: 'Pin4', title: Locale.genPresetPin4,
length: 4, digits: true },
{ name: 'Mac', title: Locale.genPresetMac,
length: 17, upper: true, digits: true, special: true },
{ name: 'Hash128', title: Locale.genPresetHash128,
length: 32, lower: true, digits: true },
{ name: 'Hash256', title: Locale.genPresetHash256,
length: 64, lower: true, digits: true }
{ name: 'Pronounceable', title: Locale.genPresetPronounceable, length: 10, lower: true, upper: true },
{
name: 'Med',
title: Locale.genPresetMed,
length: 16,
upper: true,
lower: true,
digits: true,
special: true,
brackets: true,
ambiguous: true
},
{ name: 'Long', title: Locale.genPresetLong, length: 32, upper: true, lower: true, digits: true },
{ name: 'Pin4', title: Locale.genPresetPin4, length: 4, digits: true },
{ name: 'Mac', title: Locale.genPresetMac, length: 17, upper: true, digits: true, special: true },
{ name: 'Hash128', title: Locale.genPresetHash128, length: 32, lower: true, digits: true },
{ name: 'Hash256', title: Locale.genPresetHash256, length: 64, lower: true, digits: true }
];
},
get all() {
let presets = this.builtIn;
presets.forEach(preset => { preset.builtIn = true; });
presets.forEach(preset => {
preset.builtIn = true;
});
const setting = AppSettingsModel.instance.get('generatorPresets');
if (setting) {
if (setting.user) {

View File

@ -15,15 +15,22 @@ const KeyHandler = {
$(document).bind('keypress', this.keypress.bind(this));
$(document).bind('keydown', this.keydown.bind(this));
this.shortcuts[Keys.DOM_VK_A] = [{ handler: this.handleAKey, thisArg: this, shortcut: this.SHORTCUT_ACTION,
modal: true, noPrevent: true }];
this.shortcuts[Keys.DOM_VK_A] = [
{ handler: this.handleAKey, thisArg: this, shortcut: this.SHORTCUT_ACTION, modal: true, noPrevent: true }
];
},
onKey: function(key, handler, thisArg, shortcut, modal, noPrevent) {
let keyShortcuts = this.shortcuts[key];
if (!keyShortcuts) {
this.shortcuts[key] = keyShortcuts = [];
}
keyShortcuts.push({ handler: handler, thisArg: thisArg, shortcut: shortcut, modal: modal, noPrevent: noPrevent });
keyShortcuts.push({
handler: handler,
thisArg: thisArg,
shortcut: shortcut,
modal: modal,
noPrevent: noPrevent
});
},
offKey: function(key, handler, thisArg) {
if (this.shortcuts[key]) {
@ -51,16 +58,24 @@ const KeyHandler = {
const isActionKey = this.isActionKey(e);
switch (sh.shortcut) {
case this.SHORTCUT_ACTION:
if (!isActionKey) { continue; }
if (!isActionKey) {
continue;
}
break;
case this.SHORTCUT_OPT:
if (!e.altKey) { continue; }
if (!e.altKey) {
continue;
}
break;
case this.SHORTCUT_ACTION + this.SHORTCUT_OPT:
if (!e.altKey || !isActionKey) { continue; }
if (!e.altKey || !isActionKey) {
continue;
}
break;
default:
if (e.metaKey || e.ctrlKey || e.altKey) { continue; }
if (e.metaKey || e.ctrlKey || e.altKey) {
continue;
}
break;
}
sh.handler.call(sh.thisArg, e, code);
@ -74,11 +89,15 @@ const KeyHandler = {
}
},
keypress: function(e) {
if (!this.modal &&
if (
!this.modal &&
e.charCode !== Keys.DOM_VK_RETURN &&
e.charCode !== Keys.DOM_VK_ESCAPE &&
e.charCode !== Keys.DOM_VK_TAB &&
!e.altKey && !e.ctrlKey && !e.metaKey) {
!e.altKey &&
!e.ctrlKey &&
!e.metaKey
) {
this.trigger('keypress', e);
} else if (this.modal) {
this.trigger('keypress:' + this.modal, e);

View File

@ -10,9 +10,13 @@ const Launcher = {
clipboardSupported: false,
ready: function(callback) {
document.addEventListener('deviceready', callback, false);
document.addEventListener('pause', () => {
Backbone.trigger('app-minimized');
}, false);
document.addEventListener(
'pause',
() => {
Backbone.trigger('app-minimized');
},
false
);
},
platform: function() {
return 'cordova';
@ -22,7 +26,9 @@ const Launcher = {
},
devTools: false,
// openDevTools: function() { },
getSaveFileName: function(defaultPath, callback) { /* skip in cordova */ },
getSaveFileName: function(defaultPath, callback) {
/* skip in cordova */
},
getDataPath: function() {
const storagePath = window.cordova.file.externalDataDirectory;
return [storagePath].concat(Array.from(arguments)).filter(s => !!s);
@ -47,9 +53,14 @@ const Launcher = {
},
writeFile: function(path, data, callback) {
const createFile = filePath => {
window.resolveLocalFileSystemURL(filePath.dir, dir => {
dir.getFile(filePath.file, {create: true}, writeFile);
}, callback, callback);
window.resolveLocalFileSystemURL(
filePath.dir,
dir => {
dir.getFile(filePath.file, { create: true }, writeFile);
},
callback,
callback
);
};
const writeFile = fileEntry => {
@ -60,9 +71,11 @@ const Launcher = {
}, callback);
};
if (path.startsWith('cdvfile://')) { // then file exists
if (path.startsWith('cdvfile://')) {
// then file exists
window.resolveLocalFileSystemURL(path, writeFile, callback, callback);
} else { // create file on sd card
} else {
// create file on sd card
const filePath = this.parsePath(path);
this.mkdir(filePath.dir, () => {
createFile(filePath);
@ -70,55 +83,86 @@ const Launcher = {
}
},
readFile: function(path, encoding, callback) {
window.resolveLocalFileSystemURL(path, fileEntry => {
fileEntry.file(file => {
const reader = new FileReader();
reader.onerror = callback;
reader.onloadend = () => {
const contents = new Uint8Array(reader.result);
callback(encoding ? String.fromCharCode.apply(null, contents) : contents);
};
reader.readAsArrayBuffer(file);
}, err => callback(undefined, err));
}, err => callback(undefined, err));
window.resolveLocalFileSystemURL(
path,
fileEntry => {
fileEntry.file(
file => {
const reader = new FileReader();
reader.onerror = callback;
reader.onloadend = () => {
const contents = new Uint8Array(reader.result);
callback(encoding ? String.fromCharCode.apply(null, contents) : contents);
};
reader.readAsArrayBuffer(file);
},
err => callback(undefined, err)
);
},
err => callback(undefined, err)
);
},
fileExists: function(path, callback) {
window.resolveLocalFileSystemURL(path, fileEntry => callback(true), () => callback(false));
},
deleteFile: function(path, callback) {
window.resolveLocalFileSystemURL(path, fileEntry => {
fileEntry.remove(callback, callback, callback);
}, callback);
window.resolveLocalFileSystemURL(
path,
fileEntry => {
fileEntry.remove(callback, callback, callback);
},
callback
);
},
statFile: function(path, callback) {
window.resolveLocalFileSystemURL(path, fileEntry => {
fileEntry.file(file => {
callback({
ctime: new Date(file.lastModified),
mtime: new Date(file.lastModified)
});
}, err => callback(undefined, err));
}, err => callback(undefined, err));
window.resolveLocalFileSystemURL(
path,
fileEntry => {
fileEntry.file(
file => {
callback({
ctime: new Date(file.lastModified),
mtime: new Date(file.lastModified)
});
},
err => callback(undefined, err)
);
},
err => callback(undefined, err)
);
},
mkdir: function(dir, callback) {
const basePath = this.getDataPath().join('/');
const createDir = (dirEntry, path, callback) => {
const name = path.shift();
dirEntry.getDirectory(name, { create: true }, dirEntry => {
if (path.length) { // there is more to create
createDir(dirEntry, path, callback);
} else {
callback();
}
}, callback);
dirEntry.getDirectory(
name,
{ create: true },
dirEntry => {
if (path.length) {
// there is more to create
createDir(dirEntry, path, callback);
} else {
callback();
}
},
callback
);
};
const localPath = dir.replace(basePath, '').split('/').filter(s => !!s);
const localPath = dir
.replace(basePath, '')
.split('/')
.filter(s => !!s);
if (localPath.length) {
window.resolveLocalFileSystemURL(basePath, dirEntry => {
createDir(dirEntry, localPath, callback);
}, callback);
window.resolveLocalFileSystemURL(
basePath,
dirEntry => {
createDir(dirEntry, localPath, callback);
},
callback
);
} else {
callback();
}
@ -145,11 +189,15 @@ const Launcher = {
this.hideApp();
},
requestExit: function() { /* skip in cordova */ },
requestExit: function() {
/* skip in cordova */
},
requestRestart: function() {
window.location.reload();
},
cancelRestart: function() { /* skip in cordova */ },
cancelRestart: function() {
/* skip in cordova */
},
setClipboardText: function(text) {},
getClipboardText: function() {},
@ -169,13 +217,21 @@ const Launcher = {
},
// getMainWindow: function() { },
resolveProxy: function(url, callback) { /* skip in cordova */ },
openWindow: function(opts) { /* skip in cordova */ },
hideApp: function() { /* skip in cordova */ },
resolveProxy: function(url, callback) {
/* skip in cordova */
},
openWindow: function(opts) {
/* skip in cordova */
},
hideApp: function() {
/* skip in cordova */
},
isAppFocused: function() {
return false; /* skip in cordova */
},
showMainWindow: function() { /* skip in cordova */ },
showMainWindow: function() {
/* skip in cordova */
},
// spawn: function(config) { },
openFileChooser: function(callback) {
const onFileSelected = function(selected) {

View File

@ -28,18 +28,23 @@ const Launcher = {
},
devTools: true,
openDevTools: function() {
this.electron().remote.getCurrentWindow().openDevTools({ mode: 'bottom' });
this.electron()
.remote.getCurrentWindow()
.openDevTools({ mode: 'bottom' });
},
getSaveFileName: function(defaultPath, callback) {
if (defaultPath) {
const homePath = this.remReq('electron').app.getPath('userDesktop');
defaultPath = this.joinPath(homePath, defaultPath);
}
this.remReq('electron').dialog.showSaveDialog({
title: Locale.launcherSave,
defaultPath: defaultPath,
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
}, callback);
this.remReq('electron').dialog.showSaveDialog(
{
title: Locale.launcherSave,
defaultPath: defaultPath,
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
},
callback
);
},
getUserDataPath: function(fileName) {
if (!this.userDataPath) {
@ -110,9 +115,7 @@ const Launcher = {
return callback();
}
fs.mkdir(stack.shift(), err =>
err ? callback(err) : create(stack, callback)
);
fs.mkdir(stack.shift(), err => (err ? callback(err) : create(stack, callback)));
};
collect(dir, stack, () => create(stack, callback));
@ -222,8 +225,12 @@ const Launcher = {
[ps.stdin, ps.stdout, ps.stderr].forEach(s => s.setEncoding('utf-8'));
let stderr = '';
let stdout = '';
ps.stderr.on('data', d => { stderr += d.toString('utf-8'); });
ps.stdout.on('data', d => { stdout += d.toString('utf-8'); });
ps.stderr.on('data', d => {
stderr += d.toString('utf-8');
});
ps.stdout.on('data', d => {
stdout += d.toString('utf-8');
});
ps.on('close', code => {
stdout = stdout.trim();
stderr = stderr.trim();

View File

@ -18,21 +18,28 @@ const OtpQrReader = {
if (screenshotKey) {
screenshotKey = Locale.detSetupOtpAlertBodyWith.replace('{}', '<code>' + screenshotKey + '</code>');
}
const pasteKey = FeatureDetector.isMobile ? ''
: Locale.detSetupOtpAlertBodyWith.replace('{}',
'<code>' + FeatureDetector.actionShortcutSymbol() + 'V</code>');
const pasteKey = FeatureDetector.isMobile
? ''
: Locale.detSetupOtpAlertBodyWith.replace(
'{}',
'<code>' + FeatureDetector.actionShortcutSymbol() + 'V</code>'
);
OtpQrReader.startListenClipoard();
const buttons = [{result: 'manually', title: Locale.detSetupOtpManualButton, silent: true},
Alerts.buttons.cancel];
const buttons = [
{ result: 'manually', title: Locale.detSetupOtpManualButton, silent: true },
Alerts.buttons.cancel
];
if (FeatureDetector.isMobile) {
buttons.unshift({result: 'select', title: Locale.detSetupOtpScanButton});
buttons.unshift({ result: 'select', title: Locale.detSetupOtpScanButton });
}
const line3 = FeatureDetector.isMobile ? Locale.detSetupOtpAlertBody3Mobile
const line3 = FeatureDetector.isMobile
? Locale.detSetupOtpAlertBody3Mobile
: Locale.detSetupOtpAlertBody3.replace('{}', pasteKey || '');
OtpQrReader.alert = Alerts.alert({
icon: 'qrcode',
header: Locale.detSetupOtpAlert,
body: [Locale.detSetupOtpAlertBody,
body: [
Locale.detSetupOtpAlertBody,
Locale.detSetupOtpAlertBody1,
Locale.detSetupOtpAlertBody2.replace('{}', screenshotKey || ''),
line3,
@ -127,7 +134,8 @@ const OtpQrReader = {
logger.error('Error parsing QR code', err);
Alerts.error({
header: Locale.detOtpQrWrong,
body: Locale.detOtpQrWrongBody + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
body:
Locale.detOtpQrWrongBody + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
});
}
} catch (e) {

View File

@ -51,10 +51,18 @@ const PopupNotifier = {
const parts = part.split('=');
settingsObj[parts[0].trim()] = parts[1].trim();
});
if (settingsObj.width) { opts.width = +settingsObj.width; }
if (settingsObj.height) { opts.height = +settingsObj.height; }
if (settingsObj.top) { opts.y = +settingsObj.top; }
if (settingsObj.left) { opts.x = +settingsObj.left; }
if (settingsObj.width) {
opts.width = +settingsObj.width;
}
if (settingsObj.height) {
opts.height = +settingsObj.height;
}
if (settingsObj.top) {
opts.y = +settingsObj.top;
}
if (settingsObj.left) {
opts.x = +settingsObj.left;
}
}
let win = Launcher.openWindow(opts);
win.webContents.on('will-redirect', (e, url) => {
@ -101,8 +109,7 @@ const PopupNotifier = {
},
isOwnUrl(url) {
return url.lastIndexOf(Links.WebApp, 0) === 0 ||
url.lastIndexOf(location.origin + location.pathname, 0) === 0;
return url.lastIndexOf(Links.WebApp, 0) === 0 || url.lastIndexOf(location.origin + location.pathname, 0) === 0;
},
processReturnToApp: function(url) {

View File

@ -90,7 +90,10 @@ Object.defineProperty(SecureInput.prototype, 'value', {
byteLength++;
}
}
return new kdbxweb.ProtectedValue(valueBytes.buffer.slice(0, byteLength), saltBytes.buffer.slice(0, byteLength));
return new kdbxweb.ProtectedValue(
valueBytes.buffer.slice(0, byteLength),
saltBytes.buffer.slice(0, byteLength)
);
}
});

View File

@ -22,8 +22,7 @@ const SettingsManager = {
hc: 'setGenThemeHc'
},
customLocales: {
},
customLocales: {},
setBySettings: function(settings) {
if (settings.get('theme')) {
@ -59,7 +58,7 @@ const SettingsManager = {
setFontSize: function(fontSize) {
const defaultFontSize = FeatureDetector.isMobile ? 14 : 12;
document.documentElement.style.fontSize = (defaultFontSize + (fontSize || 0) * 2) + 'px';
document.documentElement.style.fontSize = defaultFontSize + (fontSize || 0) * 2 + 'px';
},
setLocale(loc) {
@ -83,7 +82,7 @@ const SettingsManager = {
},
getBrowserLocale: function() {
const language = navigator.languages && navigator.languages[0] || navigator.language;
const language = (navigator.languages && navigator.languages[0]) || navigator.language;
if (language && language.lastIndexOf('en', 0) === 0) {
return 'en';
}

View File

@ -30,7 +30,9 @@ const SingleInstanceChecker = {
setKey: function(key, value) {
try {
localStorage.setItem(key, value);
setTimeout(() => { localStorage.removeItem(key); }, 100);
setTimeout(() => {
localStorage.removeItem(key);
}, 100);
} catch (e) {}
}
};

View File

@ -27,57 +27,64 @@ const Transport = {
const opts = Launcher.req('url').parse(config.url);
opts.headers = { 'User-Agent': navigator.userAgent };
Launcher.resolveProxy(config.url, proxy => {
logger.info('Request to ' + config.url + ' ' + (proxy ? 'using proxy ' + proxy.host + ':' + proxy.port : 'without proxy'));
logger.info(
'Request to ' +
config.url +
' ' +
(proxy ? 'using proxy ' + proxy.host + ':' + proxy.port : 'without proxy')
);
if (proxy) {
opts.headers.Host = opts.host;
opts.host = proxy.host;
opts.port = proxy.port;
opts.path = config.url;
}
Launcher.req(proto).get(opts, res => {
logger.info('Response from ' + config.url + ': ' + res.statusCode);
if (res.statusCode === 200) {
if (config.file) {
const file = fs.createWriteStream(tmpFile);
res.pipe(file);
file.on('finish', () => {
file.close(() => {
config.success(tmpFile);
Launcher.req(proto)
.get(opts, res => {
logger.info('Response from ' + config.url + ': ' + res.statusCode);
if (res.statusCode === 200) {
if (config.file) {
const file = fs.createWriteStream(tmpFile);
res.pipe(file);
file.on('finish', () => {
file.close(() => {
config.success(tmpFile);
});
});
});
file.on('error', err => {
config.error(err);
});
file.on('error', err => {
config.error(err);
});
} else {
let data = [];
res.on('data', chunk => {
data.push(chunk);
});
res.on('end', () => {
data = window.Buffer.concat(data);
if (config.utf8) {
data = data.toString('utf8');
}
config.success(data);
});
}
} else if (res.headers.location && [301, 302].indexOf(res.statusCode) >= 0) {
if (config.noRedirect) {
return config.error('Too many redirects');
}
config.url = res.headers.location;
config.noRedirect = true;
Transport.httpGet(config);
} else {
let data = [];
res.on('data', chunk => {
data.push(chunk);
});
res.on('end', () => {
data = window.Buffer.concat(data);
if (config.utf8) {
data = data.toString('utf8');
}
config.success(data);
});
config.error('HTTP status ' + res.statusCode);
}
} else if (res.headers.location && [301, 302].indexOf(res.statusCode) >= 0) {
if (config.noRedirect) {
return config.error('Too many redirects');
})
.on('error', e => {
logger.error('Cannot GET ' + config.url, e);
if (tmpFile) {
fs.unlink(tmpFile, _.noop);
}
config.url = res.headers.location;
config.noRedirect = true;
Transport.httpGet(config);
} else {
config.error('HTTP status ' + res.statusCode);
}
}).on('error', e => {
logger.error('Cannot GET ' + config.url, e);
if (tmpFile) {
fs.unlink(tmpFile, _.noop);
}
config.error(e);
});
config.error(e);
});
});
}
};

View File

@ -32,8 +32,10 @@ const Updater = {
},
updateInProgress: function() {
return UpdateModel.instance.get('status') === 'checking' ||
['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0;
return (
UpdateModel.instance.get('status') === 'checking' ||
['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
);
},
init: function() {
@ -55,7 +57,10 @@ const Updater = {
let timeDiff = this.MinUpdateTimeout;
const lastCheckDate = UpdateModel.instance.get('lastCheckDate');
if (lastCheckDate) {
timeDiff = Math.min(Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout), this.UpdateInterval);
timeDiff = Math.min(
Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout),
this.UpdateInterval
);
}
this.nextCheckTimeout = setTimeout(this.check.bind(this), timeDiff);
logger.info('Next update check will happen in ' + Math.round(timeDiff / 1000) + 's');
@ -107,8 +112,10 @@ const Updater = {
if (!this.canAutoUpdate()) {
return;
}
if (prevLastVersion === UpdateModel.instance.get('lastVersion') &&
UpdateModel.instance.get('updateStatus') === 'ready') {
if (
prevLastVersion === UpdateModel.instance.get('lastVersion') &&
UpdateModel.instance.get('updateStatus') === 'ready'
) {
logger.info('Waiting for the user to apply downloaded update');
return;
}
@ -238,7 +245,9 @@ const Updater = {
checkAppCacheUpdateReady: function() {
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
try { window.applicationCache.swapCache(); } catch (e) { }
try {
window.applicationCache.swapCache();
} catch (e) {}
UpdateModel.instance.set('updateStatus', 'ready');
}
}

View File

@ -1,18 +1,73 @@
const IconMap = [
'key', 'globe', 'exclamation-triangle', 'server', 'thumb-tack',
'comments-o', 'puzzle-piece', 'pencil-square-o', 'plug', 'newspaper-o',
'paperclip', 'camera', 'wifi', 'link', 'battery-three-quarters',
'barcode', 'certificate', 'bullseye', 'desktop', 'envelope-o',
'cog', 'clipboard', 'paper-plane-o', 'television', 'bolt',
'inbox', 'floppy-o', 'hdd-o', 'dot-circle-o', 'expeditedssl',
'terminal', 'print', 'map-signs', 'flag-checkered', 'wrench',
'laptop', 'archive', 'credit-card', 'windows', 'clock-o',
'search', 'flask', 'gamepad', 'trash-o', 'sticky-note-o',
'ban', 'question-circle', 'cube', 'folder-o', 'folder-open-o',
'database', 'unlock-alt', 'lock', 'check', 'pencil',
'picture-o', 'book', 'list-alt', 'user-secret', 'cutlery',
'home', 'star-o', 'linux', 'map-pin', 'apple',
'wikipedia-w', 'usd', 'calendar', 'mobile'
'key',
'globe',
'exclamation-triangle',
'server',
'thumb-tack',
'comments-o',
'puzzle-piece',
'pencil-square-o',
'plug',
'newspaper-o',
'paperclip',
'camera',
'wifi',
'link',
'battery-three-quarters',
'barcode',
'certificate',
'bullseye',
'desktop',
'envelope-o',
'cog',
'clipboard',
'paper-plane-o',
'television',
'bolt',
'inbox',
'floppy-o',
'hdd-o',
'dot-circle-o',
'expeditedssl',
'terminal',
'print',
'map-signs',
'flag-checkered',
'wrench',
'laptop',
'archive',
'credit-card',
'windows',
'clock-o',
'search',
'flask',
'gamepad',
'trash-o',
'sticky-note-o',
'ban',
'question-circle',
'cube',
'folder-o',
'folder-open-o',
'database',
'unlock-alt',
'lock',
'check',
'pencil',
'picture-o',
'book',
'list-alt',
'user-secret',
'cutlery',
'home',
'star-o',
'linux',
'map-pin',
'apple',
'wikipedia-w',
'usd',
'calendar',
'mobile'
];
module.exports = IconMap;

View File

@ -1,6 +1,4 @@
const Handlebars = require('hbs');
// inspired by https://stackoverflow.com/questions/22103989/adding-offset-to-index-when-looping-through-items-in-handlebars/39588001#39588001
Handlebars.registerHelper('add', (lvalue, rvalue) => (
parseInt(lvalue) + parseInt(rvalue)
));
Handlebars.registerHelper('add', (lvalue, rvalue) => parseInt(lvalue) + parseInt(rvalue));

View File

@ -12,7 +12,7 @@ Handlebars.registerHelper('res', function(key, options) {
return value;
});
Handlebars.registerHelper('Res', function(key) { // eslint-disable-line prefer-arrow-callback
Handlebars.registerHelper('Res', key => {
let value = Locale[key];
if (value) {
value = value[0].toUpperCase() + value.substr(1);

View File

@ -18,11 +18,16 @@ const Copyable = {
this.hideFieldCopyTip();
const fieldLabel = e.source.labelEl;
const clipboardTime = e.copyRes.seconds;
const msg = clipboardTime ? Locale.detFieldCopiedTime.replace('{}', clipboardTime)
: Locale.detFieldCopied;
const msg = clipboardTime ? Locale.detFieldCopiedTime.replace('{}', clipboardTime) : Locale.detFieldCopied;
let tip;
if (!this.isHidden()) {
tip = Tip.createTip(fieldLabel[0], {title: msg, placement: 'right', fast: true, force: true, noInit: true});
tip = Tip.createTip(fieldLabel[0], {
title: msg,
placement: 'right',
fast: true,
force: true,
noInit: true
});
this.fieldCopyTip = tip;
tip.show();
}

View File

@ -8,10 +8,7 @@ kdbxweb.ProtectedValue.prototype.isProtected = true;
kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) {
const value = this._value;
const salt = this._salt;
let b,
b1,
b2,
b3;
let b, b1, b2, b3;
for (let i = 0, len = value.length; i < len; i++) {
b = value[i] ^ salt[i];
if (b < 128) {
@ -20,23 +17,32 @@ kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) {
}
continue;
}
i++; b1 = value[i] ^ salt[i];
if (i === len) { break; }
i++;
b1 = value[i] ^ salt[i];
if (i === len) {
break;
}
if (b >= 192 && b < 224) {
if (fn(((b & 0x1f) << 6) | (b1 & 0x3f)) === false) {
return;
}
continue;
}
i++; b2 = value[i] ^ salt[i];
if (i === len) { break; }
i++;
b2 = value[i] ^ salt[i];
if (i === len) {
break;
}
if (b >= 224 && b < 240) {
if (fn(((b & 0xf) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f)) === false) {
return;
}
}
i++; b3 = value[i] ^ salt[i];
if (i === len) { break; }
i++;
b3 = value[i] ^ salt[i];
if (i === len) {
break;
}
if (b >= 240 && b < 248) {
let c = ((b & 7) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
if (c <= 0xffff) {
@ -60,7 +66,9 @@ kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) {
Object.defineProperty(kdbxweb.ProtectedValue.prototype, 'textLength', {
get: function() {
let textLength = 0;
this.forEachChar(() => { textLength++; });
this.forEachChar(() => {
textLength++;
});
return textLength;
}
});

View File

@ -75,7 +75,7 @@ _.extend(Backbone.View.prototype, {
if (this.scroll) {
try {
this.scroll.dispose();
} catch (e) { }
} catch (e) {}
}
Tip.hideTips(this.$el);
this._parentRemove(arguments);

View File

@ -114,28 +114,38 @@ const AppModel = Backbone.Model.extend({
this.fileInfos.reset();
}
config.files
.filter(file => file && file.storage && file.name && file.path &&
!this.fileInfos.getMatch(file.storage, file.name, file.path))
.map(file => new FileInfoModel({
id: IdGenerator.uuid(),
name: file.name,
storage: file.storage,
path: file.path,
opts: file.options
}))
.filter(
file =>
file &&
file.storage &&
file.name &&
file.path &&
!this.fileInfos.getMatch(file.storage, file.name, file.path)
)
.map(
file =>
new FileInfoModel({
id: IdGenerator.uuid(),
name: file.name,
storage: file.storage,
path: file.path,
opts: file.options
})
)
.reverse()
.forEach(fi => this.fileInfos.unshift(fi));
}
if (config.plugins) {
const pluginsPromises = config.plugins
.map(plugin => PluginManager.installIfNew(plugin.url, plugin.manifest, true));
const pluginsPromises = config.plugins.map(plugin =>
PluginManager.installIfNew(plugin.url, plugin.manifest, true)
);
return Promise.all(pluginsPromises).then(() => {
this.settings.set(config.settings);
});
}
if (config.advancedSearch) {
this.advancedSearch = config.advancedSearch;
this.addFilter({advanced: this.advancedSearch});
this.addFilter({ advanced: this.advancedSearch });
}
},
@ -144,7 +154,7 @@ const AppModel = Backbone.Model.extend({
return false;
}
this.files.add(file);
file.get('groups').forEach(function (group) {
file.get('groups').forEach(function(group) {
this.menu.groupsSection.addItem(group);
}, this);
this._addTags(file);
@ -184,9 +194,11 @@ const AppModel = Backbone.Model.extend({
_tagsChanged: function() {
if (this.tags.length) {
this.menu.tagsSection.set('scrollable', true);
this.menu.tagsSection.setItems(this.tags.map(tag => {
return {title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag, editable: true};
}));
this.menu.tagsSection.setItems(
this.tags.map(tag => {
return { title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag, editable: true };
})
);
} else {
this.menu.tagsSection.set('scrollable', false);
this.menu.tagsSection.removeAllItems();
@ -310,8 +322,7 @@ const AppModel = Backbone.Model.extend({
getFirstSelectedGroup: function() {
const selGroupId = this.filter.group;
let file,
group;
let file, group;
if (selGroupId) {
this.files.some(f => {
file = f;
@ -410,19 +421,25 @@ const AppModel = Backbone.Model.extend({
openFile: function(params, callback) {
const logger = new Logger('open', params.name);
logger.info('File open request');
const fileInfo = params.id ? this.fileInfos.get(params.id) : this.fileInfos.getMatch(params.storage, params.name, params.path);
const fileInfo = params.id
? this.fileInfos.get(params.id)
: this.fileInfos.getMatch(params.storage, params.name, params.path);
if (!params.opts && fileInfo && fileInfo.get('opts')) {
params.opts = fileInfo.get('opts');
}
if (fileInfo && fileInfo.get('modified')) {
logger.info('Open file from cache because it is modified');
this.openFileFromCache(params, (err, file) => {
if (!err && file) {
logger.info('Sync just opened modified file');
_.defer(() => this.syncFile(file));
}
callback(err);
}, fileInfo);
this.openFileFromCache(
params,
(err, file) => {
if (!err && file) {
logger.info('Sync just opened modified file');
_.defer(() => this.syncFile(file));
}
callback(err);
},
fileInfo
);
} else if (params.fileData) {
logger.info('Open file from supplied content');
const needSaveToCache = params.storage !== 'file';
@ -430,7 +447,12 @@ const AppModel = Backbone.Model.extend({
} else if (!params.storage) {
logger.info('Open file from cache as main storage');
this.openFileFromCache(params, callback, fileInfo);
} else if (fileInfo && fileInfo.get('openDate') && fileInfo.get('rev') === params.rev && fileInfo.get('storage') !== 'file') {
} else if (
fileInfo &&
fileInfo.get('openDate') &&
fileInfo.get('rev') === params.rev &&
fileInfo.get('storage') !== 'file'
) {
logger.info('Open file from cache because it is latest');
this.openFileFromCache(params, callback, fileInfo);
} else if (!fileInfo || !fileInfo.get('openDate') || params.storage === 'file') {
@ -450,17 +472,17 @@ const AppModel = Backbone.Model.extend({
} else {
logger.info('Open file from content loaded from storage');
params.fileData = data;
params.rev = stat && stat.rev || null;
params.rev = (stat && stat.rev) || null;
const needSaveToCache = storage.name !== 'file';
this.openFileWithData(params, callback, fileInfo, data, needSaveToCache);
}
});
};
const cacheRev = fileInfo && fileInfo.get('rev') || null;
const cacheRev = (fileInfo && fileInfo.get('rev')) || null;
if (cacheRev && storage.stat) {
logger.info('Stat file');
storage.stat(params.path, params.opts, (err, stat) => {
if (fileInfo && storage.name !== 'file' && (err || stat && stat.rev === cacheRev)) {
if (fileInfo && storage.name !== 'file' && (err || (stat && stat.rev === cacheRev))) {
logger.info('Open file from cache because ' + (err ? 'stat error' : 'it is latest'), err);
this.openFileFromCache(params, callback, fileInfo);
} else if (stat) {
@ -476,13 +498,17 @@ const AppModel = Backbone.Model.extend({
}
} else {
logger.info('Open file from cache, will sync after load', params.storage);
this.openFileFromCache(params, (err, file) => {
if (!err && file) {
logger.info('Sync just opened file');
_.defer(() => this.syncFile(file));
}
callback(err);
}, fileInfo);
this.openFileFromCache(
params,
(err, file) => {
if (!err && file) {
logger.info('Sync just opened file');
_.defer(() => this.syncFile(file));
}
callback(err);
},
fileInfo
);
}
},
@ -520,8 +546,8 @@ const AppModel = Backbone.Model.extend({
path: params.path,
keyFileName: params.keyFileName,
keyFilePath: params.keyFilePath,
backup: fileInfo && fileInfo.get('backup') || null,
fingerprint: fileInfo && fileInfo.get('fingerprint') || null
backup: (fileInfo && fileInfo.get('backup')) || null,
fingerprint: (fileInfo && fileInfo.get('fingerprint')) || null
});
const openComplete = err => {
if (err) {
@ -545,7 +571,7 @@ const AppModel = Backbone.Model.extend({
logger.info('Save loaded file to cache');
Storage.cache.save(file.id, null, params.fileData);
}
const rev = params.rev || fileInfo && fileInfo.get('rev');
const rev = params.rev || (fileInfo && fileInfo.get('rev'));
this.setFileOpts(file, params.opts);
this.addToLastOpenFiles(file, rev);
this.addFile(file);
@ -590,7 +616,14 @@ const AppModel = Backbone.Model.extend({
},
addToLastOpenFiles: function(file, rev) {
this.appLogger.debug('Add last open file', file.id, file.get('name'), file.get('storage'), file.get('path'), rev);
this.appLogger.debug(
'Add last open file',
file.id,
file.get('name'),
file.get('storage'),
file.get('path'),
rev
);
const dt = new Date();
const fileInfo = new FileInfoModel({
id: file.id,
@ -642,9 +675,12 @@ const AppModel = Backbone.Model.extend({
fileOpened: function(file, data, params) {
if (file.get('storage') === 'file') {
Storage.file.watch(file.get('path'), _.debounce(() => {
this.syncFile(file);
}, Timeouts.FileChangeSync));
Storage.file.watch(
file.get('path'),
_.debounce(() => {
this.syncFile(file);
}, Timeouts.FileChangeSync)
);
}
if (file.isKeyChangePending(true)) {
Backbone.trigger('key-change-pending', { file: file });
@ -671,8 +707,10 @@ const AppModel = Backbone.Model.extend({
},
getFileInfo: function(file) {
return this.fileInfos.get(file.id) ||
this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path'));
return (
this.fileInfos.get(file.id) ||
this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path'))
);
},
syncFile: function(file, options, callback) {
@ -713,7 +751,9 @@ const AppModel = Backbone.Model.extend({
}
file.setSyncProgress();
const complete = (err, savedToCache) => {
if (!err) { savedToCache = true; }
if (!err) {
savedToCache = true;
}
logger.info('Sync finished', err || 'no error');
file.setSyncComplete(path, storage, err ? err.toString() : null, savedToCache);
fileInfo.set({
@ -735,7 +775,9 @@ const AppModel = Backbone.Model.extend({
this.fileInfos.unshift(fileInfo);
}
this.fileInfos.save();
if (callback) { callback(err); }
if (callback) {
callback(err);
}
};
if (!storage) {
if (!file.get('modified') && fileInfo.id === file.id) {
@ -744,8 +786,10 @@ const AppModel = Backbone.Model.extend({
}
logger.info('Local, save to cache');
file.getData((data, err) => {
if (err) { return complete(err); }
Storage.cache.save(fileInfo.id, null, data, (err) => {
if (err) {
return complete(err);
}
Storage.cache.save(fileInfo.id, null, data, err => {
logger.info('Saved to cache', err || 'no error');
complete(err);
if (!err) {
@ -763,8 +807,10 @@ const AppModel = Backbone.Model.extend({
logger.info('Load from storage, attempt ' + loadLoops);
Storage[storage].load(path, opts, (err, data, stat) => {
logger.info('Load from storage', stat, err || 'no error');
if (err) { return complete(err); }
file.mergeOrUpdate(data, options.remoteKey, (err) => {
if (err) {
return complete(err);
}
file.mergeOrUpdate(data, options.remoteKey, err => {
logger.info('Merge complete', err || 'no error');
this.refresh();
if (err) {
@ -784,8 +830,10 @@ const AppModel = Backbone.Model.extend({
saveToCacheAndStorage();
} else if (file.get('dirty')) {
logger.info('Saving not modified dirty file to cache');
Storage.cache.save(fileInfo.id, null, data, (err) => {
if (err) { return complete(err); }
Storage.cache.save(fileInfo.id, null, data, err => {
if (err) {
return complete(err);
}
file.set('dirty', false);
logger.info('Complete, remove dirty flag');
complete();
@ -797,38 +845,46 @@ const AppModel = Backbone.Model.extend({
});
});
};
const saveToStorage = (data) => {
const saveToStorage = data => {
logger.info('Save data to storage');
const storageRev = fileInfo.get('storage') === storage ? fileInfo.get('rev') : undefined;
Storage[storage].save(path, opts, data, (err, stat) => {
if (err && err.revConflict) {
logger.info('Save rev conflict, reloading from storage');
loadFromStorageAndMerge();
} else if (err) {
logger.info('Error saving data to storage');
complete(err);
} else {
if (stat && stat.rev) {
logger.info('Update rev in file info');
fileInfo.set('rev', stat.rev);
Storage[storage].save(
path,
opts,
data,
(err, stat) => {
if (err && err.revConflict) {
logger.info('Save rev conflict, reloading from storage');
loadFromStorageAndMerge();
} else if (err) {
logger.info('Error saving data to storage');
complete(err);
} else {
if (stat && stat.rev) {
logger.info('Update rev in file info');
fileInfo.set('rev', stat.rev);
}
if (stat && stat.path) {
logger.info('Update path in file info', stat.path);
file.set('path', stat.path);
fileInfo.set('path', stat.path);
path = stat.path;
}
file.set('syncDate', new Date());
logger.info('Save to storage complete, update sync date');
this.scheduleBackupFile(file, data);
complete();
}
if (stat && stat.path) {
logger.info('Update path in file info', stat.path);
file.set('path', stat.path);
fileInfo.set('path', stat.path);
path = stat.path;
}
file.set('syncDate', new Date());
logger.info('Save to storage complete, update sync date');
this.scheduleBackupFile(file, data);
complete();
}
}, storageRev);
},
storageRev
);
};
const saveToCacheAndStorage = () => {
logger.info('Getting file data for saving');
file.getData((data, err) => {
if (err) { return complete(err); }
if (err) {
return complete(err);
}
if (storage === 'file') {
logger.info('Saving to file storage');
saveToStorage(data);
@ -837,8 +893,10 @@ const AppModel = Backbone.Model.extend({
saveToStorage(data);
} else {
logger.info('Saving to cache');
Storage.cache.save(fileInfo.id, null, data, (err) => {
if (err) { return complete(err); }
Storage.cache.save(fileInfo.id, null, data, err => {
if (err) {
return complete(err);
}
file.set('dirty', false);
logger.info('Saved to cache, saving to storage');
saveToStorage(data);
@ -854,9 +912,9 @@ const AppModel = Backbone.Model.extend({
saveToCacheAndStorage();
} else if (file.get('dirty')) {
logger.info('Stat error, dirty, save to cache', err || 'no error');
file.getData((data) => {
file.getData(data => {
if (data) {
Storage.cache.save(fileInfo.id, null, data, (e) => {
Storage.cache.save(fileInfo.id, null, data, e => {
if (!e) {
file.set('dirty', false);
}
@ -896,7 +954,7 @@ const AppModel = Backbone.Model.extend({
this.fileInfos.save();
},
unsetKeyFile: function (fileId) {
unsetKeyFile: function(fileId) {
const fileInfo = this.fileInfos.get(fileId);
fileInfo.set({
keyFileName: null,
@ -927,7 +985,7 @@ const AppModel = Backbone.Model.extend({
if (Storage[backup.storage].getPathForName) {
path = Storage[backup.storage].getPathForName(path);
}
Storage[backup.storage].save(path, opts, data, (err) => {
Storage[backup.storage].save(path, opts, data, err => {
if (err) {
logger.error('Backup error', err);
} else {
@ -1002,10 +1060,16 @@ const AppModel = Backbone.Model.extend({
if (dt.getTime() <= Date.now()) {
needBackup = true;
}
logger.debug('Last backup time: ' + new Date(backup.lastTime) +
', schedule: ' + backup.schedule +
', next time: ' + dt +
', ' + (needBackup ? 'backup now' : 'skip backup'));
logger.debug(
'Last backup time: ' +
new Date(backup.lastTime) +
', schedule: ' +
backup.schedule +
', next time: ' +
dt +
', ' +
(needBackup ? 'backup now' : 'skip backup')
);
}
if (!backup.pending) {
backup.pending = true;

View File

@ -57,7 +57,7 @@ const AppSettingsModel = Backbone.Model.extend({
return SettingsStore.load('app-settings').then(data => {
if (data) {
this.upgrade(data);
this.set(data, {silent: true});
this.set(data, { silent: true });
}
});
},

View File

@ -3,8 +3,7 @@ const Backbone = require('backbone');
const AttachmentModel = Backbone.Model.extend({
defaults: {},
initialize: function() {
},
initialize: function() {},
setAttachment: function(att) {
this.title = att.title;
@ -21,28 +20,83 @@ const AttachmentModel = Backbone.Model.extend({
_getIcon: function(ext) {
switch (ext) {
case 'txt': case 'log': case 'rtf': case 'pem':
case 'txt':
case 'log':
case 'rtf':
case 'pem':
return 'file-text-o';
case 'html': case 'htm': case 'js': case 'css': case 'xml': case 'config': case 'json': case 'yaml':
case 'cpp': case 'c': case 'h': case 'cc': case 'hpp': case 'mm': case 'cs': case 'php': case 'sh':
case 'py': case 'java': case 'rb': case 'cfg': case 'properties': case 'yml': case 'asm': case 'bat':
case 'html':
case 'htm':
case 'js':
case 'css':
case 'xml':
case 'config':
case 'json':
case 'yaml':
case 'cpp':
case 'c':
case 'h':
case 'cc':
case 'hpp':
case 'mm':
case 'cs':
case 'php':
case 'sh':
case 'py':
case 'java':
case 'rb':
case 'cfg':
case 'properties':
case 'yml':
case 'asm':
case 'bat':
return 'file-code-o';
case 'pdf':
return 'file-pdf-o';
case 'zip': case 'rar': case 'bz': case 'bz2': case '7z': case 'gzip': case 'gz': case 'tar':
case 'cab': case 'ace': case 'dmg': case 'jar':
case 'zip':
case 'rar':
case 'bz':
case 'bz2':
case '7z':
case 'gzip':
case 'gz':
case 'tar':
case 'cab':
case 'ace':
case 'dmg':
case 'jar':
return 'file-archive-o';
case 'doc': case 'docx':
case 'doc':
case 'docx':
return 'file-word-o';
case 'xls': case 'xlsx':
case 'xls':
case 'xlsx':
return 'file-excel-o';
case 'ppt': case 'pptx':
case 'ppt':
case 'pptx':
return 'file-powerpoint-o';
case 'jpeg': case 'jpg': case 'png': case 'gif': case 'bmp': case 'tiff': case 'svg': case 'ico': case 'psd':
case 'jpeg':
case 'jpg':
case 'png':
case 'gif':
case 'bmp':
case 'tiff':
case 'svg':
case 'ico':
case 'psd':
return 'file-image-o';
case 'avi': case 'mp4': case '3gp': case 'm4v': case 'mov': case 'mpeg': case 'mpg': case 'mpe':
case 'avi':
case 'mp4':
case '3gp':
case 'm4v':
case 'mov':
case 'mpeg':
case 'mpg':
case 'mpe':
return 'file-video-o';
case 'mp3': case 'wav': case 'flac':
case 'mp3':
case 'wav':
case 'flac':
return 'file-audio-o';
}
return 'file-o';
@ -50,14 +104,43 @@ const AttachmentModel = Backbone.Model.extend({
_getMimeType: function(ext) {
switch (ext) {
case 'txt': case 'log':
case 'html': case 'htm': case 'js': case 'css': case 'xml': case 'config': case 'json': case 'yaml':
case 'cpp': case 'c': case 'h': case 'cc': case 'hpp': case 'mm': case 'cs': case 'php': case 'sh':
case 'py': case 'java': case 'rb': case 'cfg': case 'properties': case 'yml': case 'asm': case 'pem':
case 'txt':
case 'log':
case 'html':
case 'htm':
case 'js':
case 'css':
case 'xml':
case 'config':
case 'json':
case 'yaml':
case 'cpp':
case 'c':
case 'h':
case 'cc':
case 'hpp':
case 'mm':
case 'cs':
case 'php':
case 'sh':
case 'py':
case 'java':
case 'rb':
case 'cfg':
case 'properties':
case 'yml':
case 'asm':
case 'pem':
return 'text/plain';
case 'pdf':
return 'application/pdf';
case 'jpeg': case 'jpg': case 'png': case 'gif': case 'bmp': case 'tiff': case 'svg':
case 'jpeg':
case 'jpg':
case 'png':
case 'gif':
case 'bmp':
case 'tiff':
case 'svg':
return 'image/' + ext;
}
},

View File

@ -13,7 +13,16 @@ const EntryModel = Backbone.Model.extend({
urlRegex: /^https?:\/\//i,
fieldRefRegex: /^\{REF:([TNPAU])@I:(\w{32})}$/,
builtInFields: ['Title', 'Password', 'UserName', 'URL', 'Notes', 'TOTP Seed', 'TOTP Settings', '_etm_template_uuid'],
builtInFields: [
'Title',
'Password',
'UserName',
'URL',
'Notes',
'TOTP Seed',
'TOTP Settings',
'_etm_template_uuid'
],
fieldRefFields: ['title', 'password', 'user', 'url', 'notes'],
fieldRefIds: { T: 'Title', U: 'UserName', P: 'Password', A: 'URL', N: 'Notes' },
@ -32,7 +41,7 @@ const EntryModel = Backbone.Model.extend({
_fillByEntry: function() {
const entry = this.entry;
this.set({id: this.file.subId(entry.uuid.id), uuid: entry.uuid.id}, {silent: true});
this.set({ id: this.file.subId(entry.uuid.id), uuid: entry.uuid.id }, { silent: true });
this.fileName = this.file.get('name');
this.groupName = this.group.get('title');
this.title = this._getFieldString('Title');
@ -120,7 +129,8 @@ const EntryModel = Backbone.Model.extend({
_buildAutoType: function() {
this.autoTypeEnabled = this.entry.autoType.enabled;
this.autoTypeObfuscation = this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
this.autoTypeObfuscation =
this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
this.autoTypeSequence = this.entry.autoType.defaultSequence;
this.autoTypeWindows = this.entry.autoType.items.map(this._convertAutoTypeItem);
},
@ -150,14 +160,18 @@ const EntryModel = Backbone.Model.extend({
_attachmentsToModel: function(binaries) {
const att = [];
_.forEach(binaries, (data, title) => {
if (data && data.ref) {
data = data.value;
}
if (data) {
att.push(AttachmentModel.fromAttachment({data: data, title: title}));
}
}, this);
_.forEach(
binaries,
(data, title) => {
if (data && data.ref) {
data = data.value;
}
if (data) {
att.push(AttachmentModel.fromAttachment({ data: data, title: title }));
}
},
this
);
return att;
},
@ -183,21 +197,25 @@ const EntryModel = Backbone.Model.extend({
},
matches: function(filter) {
return !filter ||
(!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
(!filter.textLower || (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) &&
(!filter.color || filter.color === true && this.searchColor || this.searchColor === filter.color) &&
(!filter.autoType || this.autoTypeEnabled);
return (
!filter ||
((!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
(!filter.textLower ||
(filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) &&
(!filter.color || (filter.color === true && this.searchColor) || this.searchColor === filter.color) &&
(!filter.autoType || this.autoTypeEnabled))
);
},
matchesAdv: function(filter) {
const adv = filter.advanced;
let search,
match;
let search, match;
if (adv.regex) {
try {
search = new RegExp(filter.text, adv.cs ? '' : 'i');
} catch (e) { return false; }
} catch (e) {
return false;
}
match = this.matchRegex;
} else if (adv.cs) {
search = filter.text;
@ -387,7 +405,7 @@ const EntryModel = Backbone.Model.extend({
},
setField: function(field, val, allowEmpty) {
const hasValue = val && (typeof val === 'string' || val.isProtected && val.byteLength);
const hasValue = val && (typeof val === 'string' || (val.isProtected && val.byteLength));
if (hasValue || allowEmpty || this.builtInFields.indexOf(field) >= 0) {
this._entryModified();
val = this.sanitizeFieldValue(val);
@ -542,8 +560,7 @@ const EntryModel = Backbone.Model.extend({
if (settings && settings.isProtected) {
settings = settings.getText();
}
let period,
digits;
let period, digits;
if (settings) {
settings = settings.split(';');
if (settings.length > 0 && settings[0] > 0) {
@ -601,8 +618,9 @@ const EntryModel = Backbone.Model.extend({
setAutoTypeObfuscation: function(enabled) {
this._entryModified();
this.entry.autoType.obfuscation =
enabled ? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard : kdbxweb.Consts.AutoTypeObfuscationOptions.None;
this.entry.autoType.obfuscation = enabled
? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard
: kdbxweb.Consts.AutoTypeObfuscationOptions.None;
this._buildAutoType();
},
@ -674,20 +692,17 @@ const EntryModel = Backbone.Model.extend({
const fieldNames = Object.keys(this.fields);
_.forEach(fieldNames, field => {
ranking.push(
{
field: field,
multiplicator: 2
}
);
ranking.push({
field: field,
multiplicator: 2
});
});
_.forEach(ranking, rankingEntry => {
if (this._getFieldString(rankingEntry.field).toLowerCase() !== '') {
const calculatedRank = Ranking.getStringRank(
searchString,
this._getFieldString(rankingEntry.field).toLowerCase()
) * rankingEntry.multiplicator;
const calculatedRank =
Ranking.getStringRank(searchString, this._getFieldString(rankingEntry.field).toLowerCase()) *
rankingEntry.multiplicator;
rank += calculatedRank;
}
});

View File

@ -19,11 +19,15 @@ const FileInfoModel = Backbone.Model.extend({
},
initialize: function(data, options) {
_.each(data, function(val, key) {
if (/Date$/.test(key)) {
this.set(key, val ? new Date(val) : null, options);
}
}, this);
_.each(
data,
function(val, key) {
if (/Date$/.test(key)) {
this.set(key, val ? new Date(val) : null, options);
}
},
this
);
}
});

View File

@ -57,8 +57,17 @@ const FileModel = Backbone.Model.extend({
if (keyFileData) {
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
}
logger.info('Opened file ' + this.get('name') + ': ' + logger.ts(ts) + ', ' +
this.kdfArgsToString(db.header) + ', ' + Math.round(fileData.byteLength / 1024) + ' kB');
logger.info(
'Opened file ' +
this.get('name') +
': ' +
logger.ts(ts) +
', ' +
this.kdfArgsToString(db.header) +
', ' +
Math.round(fileData.byteLength / 1024) +
' kB'
);
callback();
})
.catch(err => {
@ -77,13 +86,17 @@ const FileModel = Backbone.Model.extend({
kdfArgsToString: function(header) {
if (header.kdfParameters) {
return header.kdfParameters.keys().map(key => {
const val = header.kdfParameters.get(key);
if (val instanceof ArrayBuffer) {
return;
}
return key + '=' + val;
}).filter(p => p).join('&');
return header.kdfParameters
.keys()
.map(key => {
const val = header.kdfParameters.get(key);
if (val instanceof ArrayBuffer) {
return;
}
return key + '=' + val;
})
.filter(p => p)
.join('&');
} else if (header.keyEncryptionRounds) {
return header.keyEncryptionRounds + ' rounds';
} else {
@ -127,14 +140,13 @@ const FileModel = Backbone.Model.extend({
const password = kdbxweb.ProtectedValue.fromString('demo');
const credentials = new kdbxweb.Credentials(password);
const demoFile = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(demoFileData));
kdbxweb.Kdbx.load(demoFile, credentials)
.then(db => {
this.db = db;
this.set('name', 'Demo');
this.readModel();
this.setOpenFile({passwordLength: 4, demo: true});
callback();
});
kdbxweb.Kdbx.load(demoFile, credentials).then(db => {
this.db = db;
this.set('name', 'Demo');
this.readModel();
this.setOpenFile({ passwordLength: 4, demo: true });
callback();
});
},
setOpenFile: function(props) {
@ -153,17 +165,20 @@ const FileModel = Backbone.Model.extend({
readModel: function() {
const groups = new GroupCollection();
this.set({
uuid: this.db.getDefaultGroup().uuid.toString(),
groups: groups,
defaultUser: this.db.meta.defaultUser,
recycleBinEnabled: this.db.meta.recycleBinEnabled,
historyMaxItems: this.db.meta.historyMaxItems,
historyMaxSize: this.db.meta.historyMaxSize,
keyEncryptionRounds: this.db.header.keyEncryptionRounds,
keyChangeForce: this.db.meta.keyChangeForce,
kdfParameters: this.readKdfParams()
}, { silent: true });
this.set(
{
uuid: this.db.getDefaultGroup().uuid.toString(),
groups: groups,
defaultUser: this.db.meta.defaultUser,
recycleBinEnabled: this.db.meta.recycleBinEnabled,
historyMaxItems: this.db.meta.historyMaxItems,
historyMaxSize: this.db.meta.historyMaxSize,
keyEncryptionRounds: this.db.header.keyEncryptionRounds,
keyChangeForce: this.db.meta.keyChangeForce,
kdfParameters: this.readKdfParams()
},
{ silent: true }
);
this.db.groups.forEach(function(group) {
let groupModel = this.getGroup(this.subId(group.uuid.id));
if (groupModel) {
@ -204,12 +219,15 @@ const FileModel = Backbone.Model.extend({
buildObjectMap: function() {
const entryMap = {};
const groupMap = {};
this.forEachGroup(group => {
groupMap[group.id] = group;
group.forEachOwnEntry(null, entry => {
entryMap[entry.id] = entry;
});
}, { includeDisabled: true });
this.forEachGroup(
group => {
groupMap[group.id] = group;
group.forEachOwnEntry(null, entry => {
entryMap[entry.id] = entry;
});
},
{ includeDisabled: true }
);
this.entryMap = entryMap;
this.groupMap = groupMap;
},
@ -371,7 +389,8 @@ const FileModel = Backbone.Model.extend({
binaries: true
});
this.db.cleanup({ binaries: true });
this.db.save()
this.db
.save()
.then(data => {
cb(data);
})
@ -382,8 +401,9 @@ const FileModel = Backbone.Model.extend({
},
getXml: function(cb) {
this.db.saveXml()
.then(xml => { cb(xml); });
this.db.saveXml().then(xml => {
cb(xml);
});
},
getKeyFileHash: function() {
@ -508,7 +528,9 @@ const FileModel = Backbone.Model.extend({
this.db.meta.name = name;
this.db.meta.nameChanged = new Date();
this.set('name', name);
this.get('groups').first().setName(name);
this.get('groups')
.first()
.setName(name);
this.setModified();
this.reload();
},
@ -571,10 +593,13 @@ const FileModel = Backbone.Model.extend({
const trashGroup = this.getTrashGroup();
if (trashGroup) {
let modified = false;
trashGroup.getOwnSubGroups().slice().forEach(function(group) {
this.db.move(group, null);
modified = true;
}, this);
trashGroup
.getOwnSubGroups()
.slice()
.forEach(function(group) {
this.db.move(group, null);
modified = true;
}, this);
trashGroup.group.entries.slice().forEach(function(entry) {
this.db.move(entry, null);
modified = true;

View File

@ -24,28 +24,35 @@ const GroupModel = MenuItemModel.extend({
}),
initialize: function() {
if (!GroupCollection) { GroupCollection = require('../collections/group-collection'); }
if (!EntryCollection) { EntryCollection = require('../collections/entry-collection'); }
if (!GroupCollection) {
GroupCollection = require('../collections/group-collection');
}
if (!EntryCollection) {
EntryCollection = require('../collections/entry-collection');
}
},
setGroup: function(group, file, parentGroup) {
const isRecycleBin = group.uuid.equals(file.db.meta.recycleBinUuid);
const id = file.subId(group.uuid.id);
this.set({
id: id,
uuid: group.uuid.id,
expanded: group.expanded,
visible: !isRecycleBin,
items: new GroupCollection(),
entries: new EntryCollection(),
filterValue: id,
enableSearching: group.enableSearching,
enableAutoType: group.enableAutoType,
autoTypeSeq: group.defaultAutoTypeSeq,
top: !parentGroup,
drag: !!parentGroup,
collapsible: !!parentGroup
}, { silent: true });
this.set(
{
id: id,
uuid: group.uuid.id,
expanded: group.expanded,
visible: !isRecycleBin,
items: new GroupCollection(),
entries: new EntryCollection(),
filterValue: id,
enableSearching: group.enableSearching,
enableAutoType: group.enableAutoType,
autoTypeSeq: group.defaultAutoTypeSeq,
top: !parentGroup,
drag: !!parentGroup,
collapsible: !!parentGroup
},
{ silent: true }
);
this.group = group;
this.file = file;
this.parentGroup = parentGroup;
@ -77,14 +84,17 @@ const GroupModel = MenuItemModel.extend({
},
_fillByGroup: function(silent) {
this.set({
title: this.parentGroup ? this.group.name : this.file.get('name'),
iconId: this.group.icon,
icon: this._iconFromId(this.group.icon),
customIcon: this._buildCustomIcon(),
customIconId: this.group.customIcon ? this.group.customIcon.toString() : null,
expanded: this.group.expanded !== false
}, { silent: silent });
this.set(
{
title: this.parentGroup ? this.group.name : this.file.get('name'),
iconId: this.group.icon,
icon: this._iconFromId(this.group.icon),
customIcon: this._buildCustomIcon(),
customIconId: this.group.customIcon ? this.group.customIcon.toString() : null,
expanded: this.group.expanded !== false
},
{ silent: silent }
);
},
_iconFromId: function(id) {
@ -129,10 +139,12 @@ const GroupModel = MenuItemModel.extend({
},
matches: function(filter) {
return (filter && filter.includeDisabled ||
this.group.enableSearching !== false &&
!this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup)
) && (!filter || !filter.autoType || this.group.enableAutoType !== false);
return (
((filter && filter.includeDisabled) ||
(this.group.enableSearching !== false &&
!this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup))) &&
(!filter || !filter.autoType || this.group.enableAutoType !== false)
);
},
getOwnSubGroups: function() {

View File

@ -16,22 +16,40 @@ const MenuModel = Backbone.Model.extend({
initialize: function() {
this.menus = {};
this.allItemsSection = new MenuSectionModel([{ locTitle: 'menuAllItems', icon: 'th-large', active: true,
shortcut: Keys.DOM_VK_A, filterKey: '*' }]);
this.allItemsSection = new MenuSectionModel([
{ locTitle: 'menuAllItems', icon: 'th-large', active: true, shortcut: Keys.DOM_VK_A, filterKey: '*' }
]);
this.allItemsItem = this.allItemsSection.get('items').models[0];
this.groupsSection = new GroupsMenuModel();
this.colorsSection = new MenuSectionModel([{ locTitle: 'menuColors', icon: 'bookmark', shortcut: Keys.DOM_VK_C,
cls: 'menu__item-colors', filterKey: 'color', filterValue: true }]);
this.colorsSection = new MenuSectionModel([
{
locTitle: 'menuColors',
icon: 'bookmark',
shortcut: Keys.DOM_VK_C,
cls: 'menu__item-colors',
filterKey: 'color',
filterValue: true
}
]);
this.colorsItem = this.colorsSection.get('items').models[0];
const defTags = [this._getDefaultTagItem()];
this.tagsSection = new MenuSectionModel(defTags);
this.tagsSection.set({ scrollable: true, drag: true });
this.tagsSection.defaultItems = defTags;
this.trashSection = new MenuSectionModel([{ locTitle: 'menuTrash', icon: 'trash', shortcut: Keys.DOM_VK_D,
filterKey: 'trash', filterValue: true, drop: true }]);
this.trashSection = new MenuSectionModel([
{
locTitle: 'menuTrash',
icon: 'trash',
shortcut: Keys.DOM_VK_D,
filterKey: 'trash',
filterValue: true,
drop: true
}
]);
Colors.AllColors.forEach(color => {
this.colorsSection.get('items').models[0]
.addOption({ cls: 'fa ' + color + '-color', value: color, filterValue: color });
this.colorsSection
.get('items')
.models[0].addOption({ cls: 'fa ' + color + '-color', value: color, filterValue: color });
});
this.menus.app = new MenuSectionCollection([
this.allItemsSection,
@ -41,8 +59,12 @@ const MenuModel = Backbone.Model.extend({
this.trashSection
]);
this.generalSection = new MenuSectionModel([{ locTitle: 'menuSetGeneral', icon: 'cog', page: 'general', active: true }]);
this.shortcutsSection = new MenuSectionModel([{ locTitle: 'shortcuts', icon: 'keyboard-o', page: 'shortcuts' }]);
this.generalSection = new MenuSectionModel([
{ locTitle: 'menuSetGeneral', icon: 'cog', page: 'general', active: true }
]);
this.shortcutsSection = new MenuSectionModel([
{ locTitle: 'shortcuts', icon: 'keyboard-o', page: 'shortcuts' }
]);
this.pluginsSection = new MenuSectionModel([{ locTitle: 'plugins', icon: 'puzzle-piece', page: 'plugins' }]);
this.aboutSection = new MenuSectionModel([{ locTitle: 'menuSetAbout', icon: 'info', page: 'about' }]);
this.helpSection = new MenuSectionModel([{ locTitle: 'help', icon: 'question', page: 'help' }]);
@ -66,7 +88,9 @@ const MenuModel = Backbone.Model.extend({
select: function(sel) {
const sections = this.get('sections');
sections.forEach(function(section) { this._select(section, sel.item); }, this);
sections.forEach(function(section) {
this._select(section, sel.item);
}, this);
if (sections === this.menus.app) {
this.colorsItem.get('options').forEach(opt => opt.set('active', opt === sel.option));
const selColor = sel.item === this.colorsItem && sel.option ? sel.option.get('value') + '-color' : '';
@ -95,7 +119,7 @@ const MenuModel = Backbone.Model.extend({
if (items) {
items.forEach(it => {
if (it.get('active') && previousItem) {
this.select({item: previousItem});
this.select({ item: previousItem });
return false;
}
return processSection(it);
@ -114,8 +138,8 @@ const MenuModel = Backbone.Model.extend({
if (section.has('visible') && !section.get('visible')) {
return true;
}
if (section.has('active') && activeItem && (section !== activeItem)) {
this.select({item: section});
if (section.has('active') && activeItem && section !== activeItem) {
this.select({ item: section });
activeItem = null;
return false;
}
@ -146,18 +170,24 @@ const MenuModel = Backbone.Model.extend({
_setLocale: function() {
[this.menus.app, this.menus.settings].forEach(menu => {
menu.each(section => section.get('items').each(item => {
if (item.get('locTitle')) {
item.set('title', Format.capFirst(Locale[item.get('locTitle')]));
}
}));
menu.each(section =>
section.get('items').each(item => {
if (item.get('locTitle')) {
item.set('title', Format.capFirst(Locale[item.get('locTitle')]));
}
})
);
});
this.tagsSection.defaultItems[0] = this._getDefaultTagItem();
},
_getDefaultTagItem: function() {
return { title: Format.capFirst(Locale.tags), icon: 'tags', defaultItem: true,
disabled: { header: Locale.menuAlertNoTags, body: Locale.menuAlertNoTagsBody, icon: 'tags' } };
return {
title: Format.capFirst(Locale.tags),
icon: 'tags',
defaultItem: true,
disabled: { header: Locale.menuAlertNoTags, body: Locale.menuAlertNoTagsBody, icon: 'tags' }
};
},
setMenu: function(type) {

View File

@ -15,7 +15,7 @@ const RuntimeDataModel = Backbone.Model.extend({
// we're not using cookies here now
delete data.cookies;
}
this.set(data, {silent: true});
this.set(data, { silent: true });
}
});
},

View File

@ -15,8 +15,7 @@ const UpdateModel = Backbone.Model.extend({
updateManual: false
},
initialize: function() {
},
initialize: function() {},
load: function() {
return SettingsStore.load('update-info').then(data => {
@ -27,8 +26,9 @@ const UpdateModel = Backbone.Model.extend({
data[key] = val ? new Date(val) : null;
}
});
this.set(data, {silent: true});
} catch (e) { /* failed to load model */
this.set(data, { silent: true });
} catch (e) {
/* failed to load model */
}
}
});

View File

@ -50,17 +50,16 @@ const PluginGallery = {
verifySignature(gallery) {
const dataToVerify = JSON.stringify(gallery, null, 2).replace(gallery.signature, '');
return SignatureVerifier.verify(
kdbxweb.ByteUtils.stringToBytes(dataToVerify),
gallery.signature
).then(isValid => {
if (isValid) {
return gallery;
}
this.logger.error('JSON signature invalid');
}).catch(e => {
this.logger.error('Error verifying plugins signature', e);
});
return SignatureVerifier.verify(kdbxweb.ByteUtils.stringToBytes(dataToVerify), gallery.signature)
.then(isValid => {
if (isValid) {
return gallery;
}
this.logger.error('JSON signature invalid');
})
.catch(e => {
this.logger.error('Error verifying plugins signature', e);
});
},
getCachedGallery() {

View File

@ -44,21 +44,23 @@ const PluginManager = Backbone.Model.extend({
install(url, expectedManifest, skipSignatureValidation) {
this.trigger('change');
return Plugin.loadFromUrl(url, expectedManifest).then(plugin => {
return this.uninstall(plugin.id).then(() => {
if (skipSignatureValidation) {
plugin.set('skipSignatureValidation', true);
}
return plugin.install(true, false).then(() => {
this.get('plugins').push(plugin);
this.trigger('change');
this.saveState();
return Plugin.loadFromUrl(url, expectedManifest)
.then(plugin => {
return this.uninstall(plugin.id).then(() => {
if (skipSignatureValidation) {
plugin.set('skipSignatureValidation', true);
}
return plugin.install(true, false).then(() => {
this.get('plugins').push(plugin);
this.trigger('change');
this.saveState();
});
});
})
.catch(e => {
this.trigger('change');
throw e;
});
}).catch(e => {
this.trigger('change');
throw e;
});
},
installIfNew(url, expectedManifest, skipSignatureValidation) {
@ -112,24 +114,35 @@ const PluginManager = Backbone.Model.extend({
update(id) {
const plugins = this.get('plugins');
const oldPlugin = plugins.get(id);
const validStatuses = [Plugin.STATUS_ACTIVE, Plugin.STATUS_INACTIVE, Plugin.STATUS_NONE, Plugin.STATUS_ERROR, Plugin.STATUS_INVALID];
const validStatuses = [
Plugin.STATUS_ACTIVE,
Plugin.STATUS_INACTIVE,
Plugin.STATUS_NONE,
Plugin.STATUS_ERROR,
Plugin.STATUS_INVALID
];
if (!oldPlugin || validStatuses.indexOf(oldPlugin.get('status')) < 0) {
return Promise.reject();
}
const url = oldPlugin.get('url');
this.trigger('change');
return Plugin.loadFromUrl(url).then(newPlugin => {
return oldPlugin.update(newPlugin).then(() => {
this.trigger('change');
this.saveState();
}).catch(e => {
return Plugin.loadFromUrl(url)
.then(newPlugin => {
return oldPlugin
.update(newPlugin)
.then(() => {
this.trigger('change');
this.saveState();
})
.catch(e => {
this.trigger('change');
throw e;
});
})
.catch(e => {
this.trigger('change');
throw e;
});
}).catch(e => {
this.trigger('change');
throw e;
});
},
setAutoUpdate(id, enabled) {
@ -144,7 +157,9 @@ const PluginManager = Backbone.Model.extend({
},
runAutoUpdate() {
const queue = this.get('plugins').filter(p => p.get('autoUpdate')).map(p => p.id);
const queue = this.get('plugins')
.filter(p => p.get('autoUpdate'))
.map(p => p.id);
if (!queue.length) {
return Promise.resolve();
}
@ -163,7 +178,9 @@ const PluginManager = Backbone.Model.extend({
const updateNext = () => {
const pluginId = queue.shift();
if (pluginId) {
return this.update(pluginId).catch(() => {}).then(updateNext);
return this.update(pluginId)
.catch(() => {})
.then(updateNext);
}
};
return updateNext();
@ -178,10 +195,13 @@ const PluginManager = Backbone.Model.extend({
let enabled = desc.enabled;
if (enabled) {
const galleryPlugin = gallery ? gallery.plugins.find(pl => pl.manifest.name === desc.manifest.name) : null;
const expectedPublicKey = galleryPlugin ? galleryPlugin.manifest.publicKey : SignatureVerifier.getPublicKey();
const expectedPublicKey = galleryPlugin
? galleryPlugin.manifest.publicKey
: SignatureVerifier.getPublicKey();
enabled = desc.manifest.publicKey === expectedPublicKey;
}
return plugin.install(enabled, true)
return plugin
.install(enabled, true)
.then(() => plugin)
.catch(() => plugin);
},

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,11 @@ const ThemeVars = {
result = result.replace(/([\w\-]+)\([^()]+\)/, fnText => {
replaced = true;
const [, name, argsStr] = fnText.match(/([\w\-]+)\((.*)\)/);
const args = argsStr.trim().split(/\s*,\s*/).filter(arg => arg).map(arg => this.resolveArg(arg, cssStyle, locals));
const args = argsStr
.trim()
.split(/\s*,\s*/)
.filter(arg => arg)
.map(arg => this.resolveArg(arg, cssStyle, locals));
locals.push(this.fn[name](...args));
return 'L' + (locals.length - 1);
});

View File

@ -17,21 +17,51 @@ EntryPresenter.prototype = {
}
return this;
},
get id() { return this.entry ? this.entry.id : this.group.id; },
get icon() { return this.entry ? this.entry.icon : (this.group.get('icon') || 'folder'); },
get customIcon() { return this.entry ? this.entry.customIcon : undefined; },
get color() { return this.entry ? (this.entry.color || (this.entry.customIcon ? this.noColor : undefined)) : undefined; },
get title() { return this.entry ? this.entry.title : this.group.get('title'); },
get notes() { return this.entry ? this.entry.notes : undefined; },
get url() { return this.entry ? this.entry.displayUrl : undefined; },
get user() { return this.entry ? this.entry.user : undefined; },
get active() { return this.entry ? this.entry.id === this.activeEntryId : this.group.active; },
get created() { return this.entry ? Format.dtStr(this.entry.created) : undefined; },
get updated() { return this.entry ? Format.dtStr(this.entry.updated) : undefined; },
get expired() { return this.entry ? this.entry.expired : false; },
get tags() { return this.entry ? this.entry.tags : undefined; },
get groupName() { return this.entry ? this.entry.groupName : undefined; },
get fileName() { return this.entry ? this.entry.fileName : undefined; },
get id() {
return this.entry ? this.entry.id : this.group.id;
},
get icon() {
return this.entry ? this.entry.icon : this.group.get('icon') || 'folder';
},
get customIcon() {
return this.entry ? this.entry.customIcon : undefined;
},
get color() {
return this.entry ? this.entry.color || (this.entry.customIcon ? this.noColor : undefined) : undefined;
},
get title() {
return this.entry ? this.entry.title : this.group.get('title');
},
get notes() {
return this.entry ? this.entry.notes : undefined;
},
get url() {
return this.entry ? this.entry.displayUrl : undefined;
},
get user() {
return this.entry ? this.entry.user : undefined;
},
get active() {
return this.entry ? this.entry.id === this.activeEntryId : this.group.active;
},
get created() {
return this.entry ? Format.dtStr(this.entry.created) : undefined;
},
get updated() {
return this.entry ? Format.dtStr(this.entry.updated) : undefined;
},
get expired() {
return this.entry ? this.entry.expired : false;
},
get tags() {
return this.entry ? this.entry.tags : undefined;
},
get groupName() {
return this.entry ? this.entry.groupName : undefined;
},
get fileName() {
return this.entry ? this.entry.fileName : undefined;
},
get description() {
if (!this.entry) {
return '[' + Locale.listGroup + ']';

View File

@ -15,11 +15,15 @@ _.extend(IoBrowserCache.prototype, {
const req = idb.open(this.cacheName);
req.onerror = e => {
this.logger.error('Error opening indexed db', e);
if (callback) { callback(e); }
if (callback) {
callback(e);
}
};
req.onsuccess = e => {
this.db = e.target.result;
if (callback) { callback(); }
if (callback) {
callback();
}
};
req.onupgradeneeded = e => {
const db = e.target.result;
@ -27,7 +31,9 @@ _.extend(IoBrowserCache.prototype, {
};
} catch (e) {
this.logger.error('Error opening indexed db', e);
if (callback) { callback(e); }
if (callback) {
callback(e);
}
}
},
@ -39,18 +45,27 @@ _.extend(IoBrowserCache.prototype, {
}
try {
const ts = this.logger.ts();
const req = this.db.transaction(['files'], 'readwrite').objectStore('files').put(data, id);
const req = this.db
.transaction(['files'], 'readwrite')
.objectStore('files')
.put(data, id);
req.onsuccess = () => {
this.logger.debug('Saved', id, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
};
req.onerror = () => {
this.logger.error('Error saving to cache', id, req.error);
if (callback) { callback(req.error); }
if (callback) {
callback(req.error);
}
};
} catch (e) {
this.logger.error('Error saving to cache', id, e);
if (callback) { callback(e); }
if (callback) {
callback(e);
}
}
});
},
@ -63,18 +78,27 @@ _.extend(IoBrowserCache.prototype, {
}
try {
const ts = this.logger.ts();
const req = this.db.transaction(['files'], 'readonly').objectStore('files').get(id);
const req = this.db
.transaction(['files'], 'readonly')
.objectStore('files')
.get(id);
req.onsuccess = () => {
this.logger.debug('Loaded', id, this.logger.ts(ts));
if (callback) { callback(null, req.result); }
if (callback) {
callback(null, req.result);
}
};
req.onerror = () => {
this.logger.error('Error loading from cache', id, req.error);
if (callback) { callback(req.error); }
if (callback) {
callback(req.error);
}
};
} catch (e) {
this.logger.error('Error loading from cache', id, e);
if (callback) { callback(e, null); }
if (callback) {
callback(e, null);
}
}
});
},
@ -87,18 +111,27 @@ _.extend(IoBrowserCache.prototype, {
}
try {
const ts = this.logger.ts();
const req = this.db.transaction(['files'], 'readwrite').objectStore('files').delete(id);
const req = this.db
.transaction(['files'], 'readwrite')
.objectStore('files')
.delete(id);
req.onsuccess = () => {
this.logger.debug('Removed', id, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
};
req.onerror = () => {
this.logger.error('Error removing from cache', id, req.error);
if (callback) { callback(req.error); }
if (callback) {
callback(req.error);
}
};
} catch (e) {
this.logger.error('Error removing from cache', id, e);
if (callback) { callback(e); }
if (callback) {
callback(e);
}
}
});
}

View File

@ -37,10 +37,14 @@ _.extend(IoFileCache.prototype, {
Launcher.writeFile(path, data, err => {
if (err) {
this.logger.error('Error saving file', id, err);
if (callback) { callback(err); }
if (callback) {
callback(err);
}
} else {
this.logger.debug('Saved', id, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
}
});
});
@ -57,10 +61,14 @@ _.extend(IoFileCache.prototype, {
Launcher.readFile(path, undefined, (data, err) => {
if (err) {
this.logger.error('Error loading file', id, err);
if (callback) { callback(err); }
if (callback) {
callback(err);
}
} else {
this.logger.debug('Loaded', id, this.logger.ts(ts));
if (callback) { callback(null, data); }
if (callback) {
callback(null, data);
}
}
});
});
@ -77,10 +85,14 @@ _.extend(IoFileCache.prototype, {
Launcher.deleteFile(path, err => {
if (err) {
this.logger.error('Error removing file', id, err);
if (callback) { callback(err); }
if (callback) {
callback(err);
}
} else {
this.logger.debug('Removed', id, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
}
});
});

View File

@ -7,8 +7,7 @@ const FeatureDetector = require('../util/feature-detector');
const MaxRequestRetries = 3;
const StorageBase = function() {
};
const StorageBase = function() {};
_.extend(StorageBase.prototype, {
name: null,
@ -104,11 +103,19 @@ _.extend(StorageBase.prototype, {
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
const winWidth = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
const winHeight = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
const winWidth = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width;
const winHeight = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
? document.documentElement.clientHeight
: screen.height;
const left = ((winWidth / 2) - (width / 2)) + dualScreenLeft;
const top = ((winHeight / 2) - (height / 2)) + dualScreenTop;
const left = winWidth / 2 - width / 2 + dualScreenLeft;
const top = winHeight / 2 - height / 2 + dualScreenTop;
let settings = {
width: width,
@ -120,7 +127,9 @@ _.extend(StorageBase.prototype, {
scrollbars: 'yes',
location: 'yes'
};
settings = Object.keys(settings).map(key => key + '=' + settings[key]).join(',');
settings = Object.keys(settings)
.map(key => key + '=' + settings[key])
.join(',');
if (FeatureDetector.isStandalone) {
sessionStorage.authStorage = this.name;
}
@ -147,10 +156,12 @@ _.extend(StorageBase.prototype, {
this._oauthToken = oldToken;
return callback();
}
const url = opts.url + '?client_id={cid}&scope={scope}&response_type=token&redirect_uri={url}'
.replace('{cid}', encodeURIComponent(opts.clientId))
.replace('{scope}', encodeURIComponent(opts.scope))
.replace('{url}', encodeURIComponent(this._getOauthRedirectUrl()));
const url =
opts.url +
'?client_id={cid}&scope={scope}&response_type=token&redirect_uri={url}'
.replace('{cid}', encodeURIComponent(opts.clientId))
.replace('{scope}', encodeURIComponent(opts.scope))
.replace('{url}', encodeURIComponent(this._getOauthRedirectUrl()));
this.logger.debug('OAuth: popup opened');
const popupWindow = this._openPopup(url, 'OAuth', opts.width, opts.height);
if (!popupWindow) {
@ -185,8 +196,7 @@ _.extend(StorageBase.prototype, {
window.addEventListener('message', windowMessage);
},
_popupOpened(popupWindow) {
},
_popupOpened(popupWindow) {},
_oauthProcessReturn: function(message) {
const token = this._oauthMsgToToken(message);
@ -201,7 +211,7 @@ _.extend(StorageBase.prototype, {
_oauthMsgToToken: function(data) {
if (!data.token_type) {
if (data.error) {
return {error: data.error, errorDescription: data.error_description};
return { error: data.error, errorDescription: data.error_description };
} else {
return undefined;
}

View File

@ -80,8 +80,21 @@ const StorageDropbox = StorageBase.extend({
return {
desc: 'dropboxSetupDesc',
fields: [
{id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+'},
{id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderDesc', type: 'text', placeholder: 'dropboxFolderPlaceholder'}
{
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
type: 'text',
required: true,
pattern: '\\w+'
},
{
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderDesc',
type: 'text',
placeholder: 'dropboxFolderPlaceholder'
}
]
};
},
@ -89,12 +102,29 @@ const StorageDropbox = StorageBase.extend({
getSettingsConfig: function() {
const fields = [];
const appKey = this._getKey();
const linkField = {id: 'link', title: 'dropboxLink', type: 'select', value: 'custom',
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' } };
const keyField = {id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+',
value: appKey};
const folderField = {id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderSettingsDesc', type: 'text',
value: this.appSettings.get('dropboxFolder') || ''};
const linkField = {
id: 'link',
title: 'dropboxLink',
type: 'select',
value: 'custom',
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' }
};
const keyField = {
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
type: 'text',
required: true,
pattern: '\\w+',
value: appKey
};
const folderField = {
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderSettingsDesc',
type: 'text',
value: this.appSettings.get('dropboxFolder') || ''
};
const canUseBuiltInKeys = this._canUseBuiltInKeys();
if (canUseBuiltInKeys) {
fields.push(linkField);
@ -198,7 +228,7 @@ const StorageDropbox = StorageBase.extend({
statuses: args.statuses || undefined,
success: args.success,
error: (e, xhr) => {
let err = xhr.response && xhr.response.error || new Error('Network error');
let err = (xhr.response && xhr.response.error) || new Error('Network error');
if (err && err.path && err.path['.tag'] === 'not_found') {
err = new Error('File removed');
err.notFound = true;
@ -245,7 +275,9 @@ const StorageDropbox = StorageBase.extend({
stat = { folder: true };
}
this.logger.debug('Stated', path, stat.folder ? 'folder' : stat.rev, this.logger.ts(ts));
if (callback) { callback(null, stat); }
if (callback) {
callback(null, stat);
}
},
error: callback
});
@ -284,13 +316,12 @@ const StorageDropbox = StorageBase.extend({
},
success: data => {
this.logger.debug('Listed', this.logger.ts(ts));
const fileList = data.entries
.map(f => ({
name: f.name,
path: this._toRelPath(f['path_display']),
rev: f.rev,
dir: f['.tag'] !== 'file'
}));
const fileList = data.entries.map(f => ({
name: f.name,
path: this._toRelPath(f['path_display']),
rev: f.rev,
dir: f['.tag'] !== 'file'
}));
callback(null, fileList);
},
error: callback

View File

@ -19,7 +19,7 @@ const StorageFileCache = StorageBase.extend({
const path = Launcher.getUserDataPath('OfflineFiles');
const setPath = (err) => {
const setPath = err => {
this.path = err ? null : path;
if (err) {
this.logger.error('Error opening local offline storage', err);
@ -49,7 +49,9 @@ const StorageFileCache = StorageBase.extend({
return callback && callback(err);
}
this.logger.debug('Saved', id, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
});
});
},

View File

@ -114,10 +114,14 @@ const StorageFile = StorageBase.extend({
Launcher.mkdir(path, err => {
if (err) {
this.logger.error('Error making local dir', path, err);
if (callback) { callback('Error making local dir'); }
if (callback) {
callback('Error making local dir');
}
} else {
this.logger.debug('Made dir', path, this.logger.ts(ts));
if (callback) { callback(); }
if (callback) {
callback();
}
}
});
},

View File

@ -11,7 +11,8 @@ const StorageGDrive = StorageBase.extend({
name: 'gdrive',
enabled: true,
uipos: 30,
iconSvg: '<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path d="M86.657536,76.246208 L47.768064,9 L89.111168,' +
iconSvg:
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path d="M86.657536,76.246208 L47.768064,9 L89.111168,' +
'9 L128,76.246208 L86.657536,76.246208 Z M25.010048,119.08 L102.690048,119.08 L123.36256,83.24 L45.68064,83.24 L25.010048,119.08 L25.010048,' +
'119.08 Z M38.793088,9.003712 L0,76.30496 L20.671872,112.110016 L59.464704,44.808128 L38.793088,9.003712 Z"></path></svg>',
@ -24,20 +25,22 @@ const StorageGDrive = StorageBase.extend({
load: function(path, opts, callback) {
this.stat(path, opts, (err, stat) => {
if (err) { return callback && callback(err); }
if (err) {
return callback && callback(err);
}
this.logger.debug('Load', path);
const ts = this.logger.ts();
const url = this._baseUrl + '/files/{id}/revisions/{rev}?alt=media'
.replace('{id}', path)
.replace('{rev}', stat.rev);
const url =
this._baseUrl +
'/files/{id}/revisions/{rev}?alt=media'.replace('{id}', path).replace('{rev}', stat.rev);
this._xhr({
url: url,
responseType: 'arraybuffer',
success: (response) => {
success: response => {
this.logger.debug('Loaded', path, stat.rev, this.logger.ts(ts));
return callback && callback(null, response, { rev: stat.rev });
},
error: (err) => {
error: err => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -55,17 +58,16 @@ const StorageGDrive = StorageBase.extend({
}
this.logger.debug('Stat', path);
const ts = this.logger.ts();
const url = this._baseUrl + '/files/{id}?fields=headRevisionId'
.replace('{id}', path);
const url = this._baseUrl + '/files/{id}?fields=headRevisionId'.replace('{id}', path);
this._xhr({
url: url,
responseType: 'json',
success: (response) => {
success: response => {
const rev = response.headRevisionId;
this.logger.debug('Stated', path, rev, this.logger.ts(ts));
return callback && callback(null, { rev: rev });
},
error: (err) => {
error: err => {
this.logger.error('Stat error', this.logger.ts(ts), err);
return callback && callback(err);
}
@ -95,18 +97,33 @@ const StorageGDrive = StorageBase.extend({
url = this._baseUrlUpload + '/files?uploadType=multipart&fields=id,headRevisionId';
const fileName = path.replace(NewFileIdPrefix, '') + '.kdbx';
const boundry = 'b' + Date.now() + 'x' + Math.round(Math.random() * 1000000);
data = new Blob([
'--', boundry, '\r\n',
'Content-Type: application/json; charset=UTF-8', '\r\n\r\n',
JSON.stringify({ name: fileName }), '\r\n',
'--', boundry, '\r\n',
'Content-Type: application/octet-stream', '\r\n\r\n',
data, '\r\n',
'--', boundry, '--', '\r\n'
], { type: 'multipart/related; boundary="' + boundry + '"' });
data = new Blob(
[
'--',
boundry,
'\r\n',
'Content-Type: application/json; charset=UTF-8',
'\r\n\r\n',
JSON.stringify({ name: fileName }),
'\r\n',
'--',
boundry,
'\r\n',
'Content-Type: application/octet-stream',
'\r\n\r\n',
data,
'\r\n',
'--',
boundry,
'--',
'\r\n'
],
{ type: 'multipart/related; boundary="' + boundry + '"' }
);
} else {
url = this._baseUrlUpload + '/files/{id}?uploadType=media&fields=headRevisionId'
.replace('{id}', path);
url =
this._baseUrlUpload +
'/files/{id}?uploadType=media&fields=headRevisionId'.replace('{id}', path);
data = new Blob([data], { type: 'application/octet-stream' });
}
this._xhr({
@ -114,7 +131,7 @@ const StorageGDrive = StorageBase.extend({
method: isNew ? 'POST' : 'PATCH',
responseType: 'json',
data: data,
success: (response) => {
success: response => {
this.logger.debug('Saved', path, this.logger.ts(ts));
const newRev = response.headRevisionId;
if (!newRev) {
@ -122,7 +139,7 @@ const StorageGDrive = StorageBase.extend({
}
return callback && callback(null, { rev: newRev, path: isNew ? response.id : null });
},
error: (err) => {
error: err => {
this.logger.error('Save error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -132,20 +149,23 @@ const StorageGDrive = StorageBase.extend({
},
list: function(dir, callback) {
this._oauthAuthorize((err) => {
if (err) { return callback && callback(err); }
this._oauthAuthorize(err => {
if (err) {
return callback && callback(err);
}
this.logger.debug('List');
let query = dir === 'shared' ? 'sharedWithMe=true'
: dir ? `"${dir}" in parents` : '"root" in parents';
let query = dir === 'shared' ? 'sharedWithMe=true' : dir ? `"${dir}" in parents` : '"root" in parents';
query += ' and trashed=false';
const url = this._baseUrl + '/files?fields={fields}&q={q}&pageSize=1000'
.replace('{fields}', encodeURIComponent('files(id,name,mimeType,headRevisionId)'))
.replace('{q}', encodeURIComponent(query));
const url =
this._baseUrl +
'/files?fields={fields}&q={q}&pageSize=1000'
.replace('{fields}', encodeURIComponent('files(id,name,mimeType,headRevisionId)'))
.replace('{q}', encodeURIComponent(query));
const ts = this.logger.ts();
this._xhr({
url: url,
responseType: 'json',
success: (response) => {
success: response => {
if (!response) {
this.logger.error('List error', this.logger.ts(ts));
return callback && callback('list error');
@ -167,7 +187,7 @@ const StorageGDrive = StorageBase.extend({
}
return callback && callback(null, fileList);
},
error: (err) => {
error: err => {
this.logger.error('List error', this.logger.ts(ts), err);
return callback && callback(err);
}
@ -188,7 +208,7 @@ const StorageGDrive = StorageBase.extend({
this.logger.debug('Removed', path, this.logger.ts(ts));
return callback && callback();
},
error: (err) => {
error: err => {
this.logger.error('Remove error', path, err, this.logger.ts(ts));
return callback && callback(err);
}

View File

@ -9,7 +9,8 @@ const StorageOneDrive = StorageBase.extend({
name: 'onedrive',
enabled: true,
uipos: 40,
iconSvg: '<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="256" height="256" version="1.1" viewBox="0 0 256 256">' +
iconSvg:
'<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="256" height="256" version="1.1" viewBox="0 0 256 256">' +
'<g transform="translate(296.64282,-100.61434)"><g transform="translate(222.85714,-11.428576)"><g transform="matrix(0.83394139,0,0,0.83394139,' +
'-86.101383,10.950635)"><path d="m-419.5 365.94c-18.48-4.62-28.77-19.31-28.81-41.1-0.01-6.97 0.49-10.31 2.23-14.79 4.26-10.99 15.55-19.27 ' +
'30.41-22.33 7.39-1.52 9.67-3.15 9.67-6.92 0-1.18 0.88-4.71 1.95-7.83 4.88-14.2 13.93-26.03 23.59-30.87 10.11-5.07 15.22-6.21 27.45-6.14 17.38 ' +
@ -38,7 +39,7 @@ const StorageOneDrive = StorageBase.extend({
this._xhr({
url: url,
responseType: 'json',
success: (response) => {
success: response => {
const downloadUrl = response['@microsoft.graph.downloadUrl'];
let rev = response.eTag;
if (!downloadUrl || !response.eTag) {
@ -52,15 +53,15 @@ const StorageOneDrive = StorageBase.extend({
success: (response, xhr) => {
rev = xhr.getResponseHeader('ETag') || rev;
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
return callback && callback(null, response, {rev: rev});
return callback && callback(null, response, { rev: rev });
},
error: (err) => {
error: err => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
},
error: (err) => {
error: err => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -79,14 +80,14 @@ const StorageOneDrive = StorageBase.extend({
this._xhr({
url: url,
responseType: 'json',
success: (response) => {
success: response => {
const rev = response.eTag;
if (!rev) {
this.logger.error('Stat error', path, 'no eTag', this.logger.ts(ts));
return callback && callback('no eTag');
}
this.logger.debug('Stated', path, rev, this.logger.ts(ts));
return callback && callback(null, {rev: rev});
return callback && callback(null, { rev: rev });
},
error: (err, xhr) => {
if (xhr.status === 404) {
@ -113,7 +114,7 @@ const StorageOneDrive = StorageBase.extend({
method: 'PUT',
responseType: 'json',
headers: rev ? { 'If-Match': rev } : null,
data: new Blob([data], {type: 'application/octet-stream'}),
data: new Blob([data], { type: 'application/octet-stream' }),
statuses: [200, 201, 412],
success: (response, xhr) => {
rev = response.eTag;
@ -126,9 +127,9 @@ const StorageOneDrive = StorageBase.extend({
return callback && callback({ revConflict: true }, { rev: rev });
}
this.logger.debug('Saved', path, rev, this.logger.ts(ts));
return callback && callback(null, {rev: rev});
return callback && callback(null, { rev: rev });
},
error: (err) => {
error: err => {
this.logger.error('Save error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -138,14 +139,16 @@ const StorageOneDrive = StorageBase.extend({
list: function(dir, callback) {
this._oauthAuthorize(err => {
if (err) { return callback && callback(err); }
if (err) {
return callback && callback(err);
}
this.logger.debug('List');
const ts = this.logger.ts();
const url = this._baseUrl + (dir ? `${dir}:/children` : '/drive/root/children');
this._xhr({
url: url,
responseType: 'json',
success: (response) => {
success: response => {
if (!response || !response.value) {
this.logger.error('List error', this.logger.ts(ts), response);
return callback && callback('list error');
@ -161,7 +164,7 @@ const StorageOneDrive = StorageBase.extend({
}));
return callback && callback(null, fileList);
},
error: (err) => {
error: err => {
this.logger.error('List error', this.logger.ts(ts), err);
return callback && callback(err);
}
@ -182,7 +185,7 @@ const StorageOneDrive = StorageBase.extend({
this.logger.debug('Removed', path, this.logger.ts(ts));
return callback && callback();
},
error: (err) => {
error: err => {
this.logger.error('Remove error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -191,7 +194,9 @@ const StorageOneDrive = StorageBase.extend({
mkdir: function(path, callback) {
this._oauthAuthorize(err => {
if (err) { return callback && callback(err); }
if (err) {
return callback && callback(err);
}
this.logger.debug('Make dir', path);
const ts = this.logger.ts();
const url = this._baseUrl + '/drive/root/children';
@ -201,12 +206,12 @@ const StorageOneDrive = StorageBase.extend({
method: 'POST',
responseType: 'json',
statuses: [200, 204],
data: new Blob([data], {type: 'application/json'}),
data: new Blob([data], { type: 'application/json' }),
success: () => {
this.logger.debug('Made dir', path, this.logger.ts(ts));
return callback && callback();
},
error: (err) => {
error: err => {
this.logger.error('Make dir error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
@ -216,8 +221,10 @@ const StorageOneDrive = StorageBase.extend({
setEnabled: function(enabled) {
if (!enabled) {
const url = 'https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri={url}'
.replace('{url}', this._getOauthRedirectUrl());
const url = 'https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri={url}'.replace(
'{url}',
this._getOauthRedirectUrl()
);
this._oauthRevokeToken(url);
}
StorageBase.prototype.setEnabled.call(this, enabled);
@ -244,13 +251,12 @@ const StorageOneDrive = StorageBase.extend({
_popupOpened(popupWindow) {
if (popupWindow.webContents) {
popupWindow.webContents.on('did-finish-load', (e) => {
popupWindow.webContents.on('did-finish-load', e => {
const webContents = e.sender.webContents;
const url = webContents.getURL();
if (url && url.startsWith('https://login.microsoftonline.com/common/oauth2/v2.0/authorize')) {
// click the login button mentioned in #821
const script =
`const selector = '[role="button"][aria-describedby="tileError loginHeader"]';
const script = `const selector = '[role="button"][aria-describedby="tileError loginHeader"]';
if (document.querySelectorAll(selector).length === 1) document.querySelector(selector).click()`;
webContents.executeJavaScript(script).catch(() => {});
}

View File

@ -13,9 +13,21 @@ const StorageWebDav = StorageBase.extend({
getOpenConfig: function() {
return {
fields: [
{id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true},
{id: 'user', title: 'openUser', desc: 'openUserDesc', placeholder: 'openUserPlaceholder', type: 'text'},
{id: 'password', title: 'openPass', desc: 'openPassDesc', placeholder: 'openPassPlaceholder', type: 'password'}
{ id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
{
id: 'user',
title: 'openUser',
desc: 'openUserDesc',
placeholder: 'openUserPlaceholder',
type: 'text'
},
{
id: 'password',
title: 'openPass',
desc: 'openPassDesc',
placeholder: 'openPassPlaceholder',
type: 'password'
}
]
};
},
@ -23,9 +35,13 @@ const StorageWebDav = StorageBase.extend({
getSettingsConfig: function() {
return {
fields: [
{ id: 'webdavSaveMethod', title: 'webdavSaveMethod', type: 'select',
{
id: 'webdavSaveMethod',
title: 'webdavSaveMethod',
type: 'select',
value: this.appSettings.get('webdavSaveMethod') || 'default',
options: { default: 'webdavSaveMove', put: 'webdavSavePut' } }
options: { default: 'webdavSaveMove', put: 'webdavSavePut' }
}
]
};
},
@ -35,27 +51,37 @@ const StorageWebDav = StorageBase.extend({
},
load: function(path, opts, callback) {
this._request({
op: 'Load',
method: 'GET',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
}, callback ? (err, xhr, stat) => {
callback(err, xhr.response, stat);
} : null);
this._request(
{
op: 'Load',
method: 'GET',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
},
callback
? (err, xhr, stat) => {
callback(err, xhr.response, stat);
}
: null
);
},
stat: function(path, opts, callback) {
this._request({
op: 'Stat',
method: 'HEAD',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
}, callback ? (err, xhr, stat) => {
callback(err, stat);
} : null);
this._request(
{
op: 'Stat',
method: 'HEAD',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
},
callback
? (err, xhr, stat) => {
callback(err, stat);
}
: null
);
},
save: function(path, opts, data, callback, rev) {
@ -72,76 +98,142 @@ const StorageWebDav = StorageBase.extend({
password: opts ? opts.password : null
};
const that = this;
this._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
let useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put';
if (err) {
if (!err.notFound) {
return cb(err);
} else {
that.logger.debug('Save: not found, creating');
useTmpPath = false;
this._request(
_.defaults(
{
op: 'Save:stat',
method: 'HEAD'
},
saveOpts
),
(err, xhr, stat) => {
let useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put';
if (err) {
if (!err.notFound) {
return cb(err);
} else {
that.logger.debug('Save: not found, creating');
useTmpPath = false;
}
} else if (stat.rev !== rev) {
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
return cb({ revConflict: true }, xhr, stat);
}
} else if (stat.rev !== rev) {
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
return cb({ revConflict: true }, xhr, stat);
}
if (useTmpPath) {
that._request(_.defaults({
op: 'Save:put', method: 'PUT', path: tmpPath, data: data, nostat: true
}, saveOpts), (err) => {
if (err) { return cb(err); }
that._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
if (err) {
that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts));
return cb(err, xhr, stat);
}
if (stat.rev !== rev) {
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts));
return cb({revConflict: true}, xhr, stat);
}
let movePath = path;
if (movePath.indexOf('://') < 0) {
if (movePath.indexOf('/') === 0) {
movePath = location.protocol + '//' + location.host + movePath;
} else {
movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath);
if (useTmpPath) {
that._request(
_.defaults(
{
op: 'Save:put',
method: 'PUT',
path: tmpPath,
data: data,
nostat: true
},
saveOpts
),
err => {
if (err) {
return cb(err);
}
that._request(
_.defaults(
{
op: 'Save:stat',
method: 'HEAD'
},
saveOpts
),
(err, xhr, stat) => {
if (err) {
that._request(
_.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts)
);
return cb(err, xhr, stat);
}
if (stat.rev !== rev) {
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
that._request(
_.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts)
);
return cb({ revConflict: true }, xhr, stat);
}
let movePath = path;
if (movePath.indexOf('://') < 0) {
if (movePath.indexOf('/') === 0) {
movePath = location.protocol + '//' + location.host + movePath;
} else {
movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath);
}
}
that._request(
_.defaults(
{
op: 'Save:move',
method: 'MOVE',
path: tmpPath,
nostat: true,
headers: { Destination: movePath, 'Overwrite': 'T' }
},
saveOpts
),
err => {
if (err) {
return cb(err);
}
that._request(
_.defaults(
{
op: 'Save:stat',
method: 'HEAD'
},
saveOpts
),
(err, xhr, stat) => {
cb(err, xhr, stat);
}
);
}
);
}
);
}
that._request(_.defaults({
op: 'Save:move', method: 'MOVE', path: tmpPath, nostat: true,
headers: {Destination: movePath, 'Overwrite': 'T'}
}, saveOpts), (err) => {
if (err) { return cb(err); }
that._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
cb(err, xhr, stat);
});
});
});
});
} else {
that._request(_.defaults({
op: 'Save:put', method: 'PUT', data: data, nostat: true
}, saveOpts), (err) => {
if (err) { return cb(err); }
that._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
cb(err, xhr, stat);
});
});
);
} else {
that._request(
_.defaults(
{
op: 'Save:put',
method: 'PUT',
data: data,
nostat: true
},
saveOpts
),
err => {
if (err) {
return cb(err);
}
that._request(
_.defaults(
{
op: 'Save:stat',
method: 'HEAD'
},
saveOpts
),
(err, xhr, stat) => {
cb(err, xhr, stat);
}
);
}
);
}
}
});
);
},
fileOptsToStoreOpts: function(opts, file) {
const result = {user: opts.user, encpass: opts.encpass};
const result = { user: opts.user, encpass: opts.encpass };
if (opts.password) {
const fileId = file.get('uuid');
const password = opts.password;
@ -155,7 +247,7 @@ const StorageWebDav = StorageBase.extend({
},
storeOptsToFileOpts: function(opts, file) {
const result = {user: opts.user, password: opts.password};
const result = { user: opts.user, password: opts.password };
if (opts.encpass) {
const fileId = file.get('uuid');
const encpass = atob(opts.encpass);
@ -192,26 +284,41 @@ const StorageWebDav = StorageBase.extend({
err = 'HTTP status ' + xhr.status;
break;
}
if (callback) { callback(err, xhr); callback = null; }
if (callback) {
callback(err, xhr);
callback = null;
}
return;
}
const rev = xhr.getResponseHeader('Last-Modified');
if (!rev && !config.nostat) {
that.logger.debug(config.op + ' error', config.path, 'no headers', that.logger.ts(ts));
if (callback) { callback('No Last-Modified header', xhr); callback = null; }
if (callback) {
callback('No Last-Modified header', xhr);
callback = null;
}
return;
}
const completedOpName = config.op + (config.op.charAt(config.op.length - 1) === 'e' ? 'd' : 'ed');
that.logger.debug(completedOpName, config.path, rev, that.logger.ts(ts));
if (callback) { callback(null, xhr, rev ? { rev: rev } : null); callback = null; }
if (callback) {
callback(null, xhr, rev ? { rev: rev } : null);
callback = null;
}
});
xhr.addEventListener('error', () => {
that.logger.debug(config.op + ' error', config.path, that.logger.ts(ts));
if (callback) { callback('network error', xhr); callback = null; }
if (callback) {
callback('network error', xhr);
callback = null;
}
});
xhr.addEventListener('abort', () => {
that.logger.debug(config.op + ' error', config.path, 'aborted', that.logger.ts(ts));
if (callback) { callback('aborted', xhr); callback = null; }
if (callback) {
callback('aborted', xhr);
callback = null;
}
});
xhr.open(config.method, config.path);
xhr.responseType = 'arraybuffer';
@ -227,7 +334,7 @@ const StorageWebDav = StorageBase.extend({
xhr.setRequestHeader('Cache-Control', 'no-cache');
}
if (config.data) {
const blob = new Blob([config.data], {type: 'application/octet-stream'});
const blob = new Blob([config.data], { type: 'application/octet-stream' });
xhr.send(blob);
} else {
xhr.send();

View File

@ -52,9 +52,15 @@ Color.prototype.setHsl = function() {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}

View File

@ -1,27 +1,38 @@
const LastChar = String.fromCharCode(0xfffd);
const ciCompare = (window.Intl && window.Intl.Collator && !/Edge/.test(navigator.userAgent)) // bugged in Edge: #808
? new Intl.Collator(undefined, { sensitivity: 'base' }).compare
: (x, y) => x.toLocaleLowerCase().localeCompare(y.toLocaleLowerCase());
const ciCompare =
window.Intl && window.Intl.Collator && !/Edge/.test(navigator.userAgent) // bugged in Edge: #808
? new Intl.Collator(undefined, { sensitivity: 'base' }).compare
: (x, y) => x.toLocaleLowerCase().localeCompare(y.toLocaleLowerCase());
const Comparators = {
stringComparator: function(field, asc) {
if (asc) {
return function (x, y) { return ciCompare(x[field] || LastChar, y[field] || LastChar); };
return function(x, y) {
return ciCompare(x[field] || LastChar, y[field] || LastChar);
};
} else {
return function (x, y) { return ciCompare(y[field], x[field]); };
return function(x, y) {
return ciCompare(y[field], x[field]);
};
}
},
rankComparator: function() {
return function (x, y) { return y.getRank(this.filter.textLower) - x.getRank(this.filter.textLower); };
return function(x, y) {
return y.getRank(this.filter.textLower) - x.getRank(this.filter.textLower);
};
},
dateComparator: function(field, asc) {
if (asc) {
return function (x, y) { return x[field] - y[field]; };
return function(x, y) {
return x[field] - y[field];
};
} else {
return function (x, y) { return y[field] - x[field]; };
return function(x, y) {
return y[field] - x[field];
};
}
}
};

View File

@ -9,7 +9,7 @@ const FeatureDetector = {
isWindows: navigator.platform.indexOf('Win') >= 0,
isiOS: /iPad|iPhone|iPod/i.test(navigator.userAgent),
isMobile: MobileRegex.test(navigator.userAgent) || screen.width < MinDesktopScreenWidth,
isPopup: !!((window.parent !== window.top) || window.opener),
isPopup: !!(window.parent !== window.top || window.opener),
isStandalone: !!navigator.standalone,
isFrame: window.top !== window,
isSelfHosted: !isDesktop && !/^http(s?):\/\/((localhost:8085)|((app|beta)\.keeweb\.info))/.test(location.href),
@ -28,10 +28,18 @@ const FeatureDetector = {
return !this.isMac;
},
screenshotToClipboardShortcut: function() {
if (this.isiOS) { return 'Sleep+Home'; }
if (this.isMobile) { return ''; }
if (this.isMac) { return 'Command-Shift-Control-4'; }
if (this.isWindows) { return 'Alt+PrintScreen'; }
if (this.isiOS) {
return 'Sleep+Home';
}
if (this.isMobile) {
return '';
}
if (this.isMac) {
return 'Command-Shift-Control-4';
}
if (this.isWindows) {
return 'Alt+PrintScreen';
}
return '';
},
supportsTitleBarStyles: function() {

View File

@ -18,8 +18,15 @@ const Format = {
if (typeof dt === 'number') {
dt = new Date(dt);
}
return dt ? this.dStr(dt) + ' ' + this.pad(dt.getHours(), 2) + ':' + this.pad(dt.getMinutes(), 2) +
':' + this.pad(dt.getSeconds(), 2) : '';
return dt
? this.dStr(dt) +
' ' +
this.pad(dt.getHours(), 2) +
':' +
this.pad(dt.getMinutes(), 2) +
':' +
this.pad(dt.getSeconds(), 2)
: '';
},
dStr: function(dt) {
if (typeof dt === 'number') {
@ -37,8 +44,18 @@ const Format = {
if (typeof dt === 'number') {
dt = new Date(dt);
}
return dt ? dt.getFullYear() + '-' + this.pad(dt.getMonth() + 1, 2) + '-' + this.pad(dt.getDate(), 2) + 'T' +
this.pad(dt.getHours(), 2) + '-' + this.pad(dt.getMinutes(), 2) + '-' + this.pad(dt.getSeconds(), 2)
return dt
? dt.getFullYear() +
'-' +
this.pad(dt.getMonth() + 1, 2) +
'-' +
this.pad(dt.getDate(), 2) +
'T' +
this.pad(dt.getHours(), 2) +
'-' +
this.pad(dt.getMinutes(), 2) +
'-' +
this.pad(dt.getSeconds(), 2)
: '';
}
};

View File

@ -4,7 +4,9 @@ const IdGenerator = {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
},
s4: function() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
};

View File

@ -38,22 +38,33 @@ const KdbxwebInit = {
const GB = 1024 * MB;
const WASM_PAGE_SIZE = 64 * 1024;
const totalMemory = (2 * GB - 64 * KB) / 1024 / WASM_PAGE_SIZE;
const initialMemory = Math.min(Math.max(Math.ceil(requiredMemory * 1024 / WASM_PAGE_SIZE), 256) + 256, totalMemory);
const initialMemory = Math.min(
Math.max(Math.ceil((requiredMemory * 1024) / WASM_PAGE_SIZE), 256) + 256,
totalMemory
);
const memoryDecl = `var wasmMemory=new WebAssembly.Memory({initial:${initialMemory},maximum:${totalMemory}});`;
const moduleDecl = 'var Module={' +
const moduleDecl =
'var Module={' +
'wasmJSMethod: "native-wasm",' +
'wasmBinary: Uint8Array.from(atob("' + wasmBinaryBase64 + '"), c => c.charCodeAt(0)),' +
'wasmBinary: Uint8Array.from(atob("' +
wasmBinaryBase64 +
'"), c => c.charCodeAt(0)),' +
'print(...args) { postMessage({op:"log",args}) },' +
'printErr(...args) { postMessage({op:"log",args}) },' +
'postRun:' + this.workerPostRun.toString() + ',' +
'calcHash:' + this.calcHash.toString() + ',' +
'postRun:' +
this.workerPostRun.toString() +
',' +
'calcHash:' +
this.calcHash.toString() +
',' +
'wasmMemory:wasmMemory,' +
'buffer:wasmMemory.buffer,' +
'TOTAL_MEMORY:' + initialMemory * WASM_PAGE_SIZE +
'TOTAL_MEMORY:' +
initialMemory * WASM_PAGE_SIZE +
'}';
const script = argon2LoaderCode.replace(/^var Module.*?}/, memoryDecl + moduleDecl);
const blob = new Blob([script], {type: 'application/javascript'});
const blob = new Blob([script], { type: 'application/javascript' });
const objectUrl = URL.createObjectURL(blob);
const worker = new Worker(objectUrl);
const onMessage = e => {
@ -75,7 +86,7 @@ const KdbxwebInit = {
worker.terminate();
KdbxwebInit.runtimeModule = null;
if (!e.data || e.data.error || !e.data.hash) {
const ex = e.data && e.data.error || 'unexpected error';
const ex = (e.data && e.data.error) || 'unexpected error';
logger.error('Worker error', ex);
reject(ex);
}
@ -126,9 +137,21 @@ const KdbxwebInit = {
const encodedLen = 512;
const encoded = Module.allocate(new Array(encodedLen), 'i8', Module.ALLOC_NORMAL);
const res = Module._argon2_hash(iterations, memory, parallelism,
password, passwordLen, salt, saltLen,
hash, length, encoded, encodedLen, type, version);
const res = Module._argon2_hash(
iterations,
memory,
parallelism,
password,
passwordLen,
salt,
saltLen,
hash,
length,
encoded,
encodedLen,
type,
version
);
if (res) {
throw new Error('Argon2 error ' + res);
}

View File

@ -12,7 +12,7 @@ const MaxLogsToSave = 100;
const lastLogs = [];
const Logger = function(name, id) {
this.prefix = (name ? name + (id ? ':' + id : '') : 'default');
this.prefix = name ? name + (id ? ':' + id : '') : 'default';
this.level = Level.All;
};

View File

@ -18,7 +18,7 @@ const Otp = function(url, params) {
if (params.type === 'hotp' && !params.counter) {
throw 'Bad counter: ' + params.counter;
}
if (params.period && isNaN(params.period) || params.period < 1) {
if ((params.period && isNaN(params.period)) || params.period < 1) {
throw 'Bad period: ' + params.period;
}
@ -70,13 +70,21 @@ Otp.prototype.next = function(callback) {
Otp.prototype.hmac = function(data, callback) {
const subtle = window.crypto.subtle || window.crypto.webkitSubtle;
const algo = { name: 'HMAC', hash: { name: this.algorithm.replace('SHA', 'SHA-') } };
subtle.importKey('raw', this.key, algo, false, ['sign'])
subtle
.importKey('raw', this.key, algo, false, ['sign'])
.then(key => {
subtle.sign(algo, key, data)
.then(sig => { callback(sig); })
.catch(err => { callback(null, err); });
subtle
.sign(algo, key, data)
.then(sig => {
callback(sig);
})
.catch(err => {
callback(null, err);
});
})
.catch(err => { callback(null, err); });
.catch(err => {
callback(null, err);
});
};
Otp.fromBase32 = function(str) {
@ -132,7 +140,12 @@ Otp.isSecret = function(str) {
};
Otp.makeUrl = function(secret, period, digits) {
return 'otpauth://totp/default?secret=' + secret + (period ? '&period=' + period : '') + (digits ? '&digits=' + digits : '');
return (
'otpauth://totp/default?secret=' +
secret +
(period ? '&period=' + period : '') +
(digits ? '&digits=' + digits : '')
);
};
module.exports = Otp;

View File

@ -28,7 +28,9 @@ const PasswordGenerator = {
}
const ranges = Object.keys(this.charRanges)
.filter(r => opts[r])
.map(function(r) { return this.charRanges[r]; }, this);
.map(function(r) {
return this.charRanges[r];
}, this);
if (opts.include && opts.include.length) {
ranges.push(opts.include);
}

View File

@ -11,20 +11,52 @@
*/
const PHONETIC_PRE = [
// Simple phonetics
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p',
'qu', 'r', 's', 't',
'b',
'c',
'd',
'f',
'g',
'h',
'j',
'k',
'l',
'm',
'n',
'p',
'qu',
'r',
's',
't',
// Complex phonetics
'bl',
'ch', 'cl', 'cr',
'ch',
'cl',
'cr',
'dr',
'fl', 'fr',
'gl', 'gr',
'kl', 'kr',
'ph', 'pr', 'pl',
'sc', 'sh', 'sl', 'sn', 'sr', 'st', 'str', 'sw',
'th', 'tr',
'fl',
'fr',
'gl',
'gr',
'kl',
'kr',
'ph',
'pr',
'pl',
'sc',
'sh',
'sl',
'sn',
'sr',
'st',
'str',
'sw',
'th',
'tr',
'br',
'v', 'w', 'y', 'z'
'v',
'w',
'y',
'z'
];
/**
@ -39,9 +71,17 @@ const PHONETIC_PRE_SIMPLE_LENGTH = 16;
*/
const PHONETIC_MID = [
// Simple phonetics
'a', 'e', 'i', 'o', 'u',
'a',
'e',
'i',
'o',
'u',
// Complex phonetics
'ee', 'ie', 'oo', 'ou', 'ue'
'ee',
'ie',
'oo',
'ou',
'ue'
];
/**
@ -56,15 +96,32 @@ const PHONETIC_MID_SIMPLE_LENGTH = 5;
*/
const PHONETIC_POST = [
// Simple phonetics
'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'y',
'b',
'd',
'f',
'g',
'k',
'l',
'm',
'n',
'p',
'r',
's',
't',
'y',
// Complex phonetics
'ch', 'ck',
'ch',
'ck',
'ln',
'nk', 'ng',
'nk',
'ng',
'rn',
'sh', 'sk', 'st',
'sh',
'sk',
'st',
'th',
'x', 'z'
'x',
'z'
];
/**
@ -107,17 +164,14 @@ function addSyllable(wordObj) {
const first = wordObj.word === '';
const preOnFirst = deriv % 6 > 0;
if ((first && preOnFirst) || wordObj.lastSkippedPost || compound) {
wordObj.word += getNextPhonetic(PHONETIC_PRE,
PHONETIC_PRE_SIMPLE_LENGTH, wordObj);
wordObj.word += getNextPhonetic(PHONETIC_PRE, PHONETIC_PRE_SIMPLE_LENGTH, wordObj);
wordObj.lastSkippedPre = false;
} else {
wordObj.lastSkippedPre = true;
}
wordObj.word += getNextPhonetic(PHONETIC_MID, PHONETIC_MID_SIMPLE_LENGTH,
wordObj, first && wordObj.lastSkippedPre);
wordObj.word += getNextPhonetic(PHONETIC_MID, PHONETIC_MID_SIMPLE_LENGTH, wordObj, first && wordObj.lastSkippedPre);
if (wordObj.lastSkippedPre || compound) {
wordObj.word += getNextPhonetic(PHONETIC_POST,
PHONETIC_POST_SIMPLE_LENGTH, wordObj);
wordObj.word += getNextPhonetic(PHONETIC_POST, PHONETIC_POST_SIMPLE_LENGTH, wordObj);
wordObj.lastSkippedPost = false;
} else {
wordObj.lastSkippedPost = true;
@ -206,7 +260,7 @@ function getNumericHash(data) {
data += '-Phonetic';
for (let i = 0, len = data.length; i < len; i++) {
const chr = data.charCodeAt(i);
numeric = ((numeric << 5) - numeric) + chr;
numeric = (numeric << 5) - numeric + chr;
numeric >>>= 0;
}
return numeric;

View File

@ -9,7 +9,7 @@ const SignatureVerifier = {
verify(data, signature, pk) {
return new Promise((resolve, reject) => {
const algo = {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}};
const algo = { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } };
try {
if (!pk) {
pk = this.getPublicKey();
@ -18,29 +18,33 @@ const SignatureVerifier = {
const subtle = window.crypto.subtle;
const keyFormat = 'spki';
pk = kdbxweb.ByteUtils.base64ToBytes(pk);
subtle.importKey(
keyFormat, pk,
algo,
false, ['verify']
).then(cryptoKey => {
try {
subtle.verify(algo, cryptoKey,
kdbxweb.ByteUtils.arrayToBuffer(signature),
kdbxweb.ByteUtils.arrayToBuffer(data)
).then(isValid => {
resolve(isValid);
}).catch(e => {
this.logger.error('Verify error', e);
subtle
.importKey(keyFormat, pk, algo, false, ['verify'])
.then(cryptoKey => {
try {
subtle
.verify(
algo,
cryptoKey,
kdbxweb.ByteUtils.arrayToBuffer(signature),
kdbxweb.ByteUtils.arrayToBuffer(data)
)
.then(isValid => {
resolve(isValid);
})
.catch(e => {
this.logger.error('Verify error', e);
reject();
});
} catch (e) {
this.logger.error('Signature verification error', e);
reject();
});
} catch (e) {
this.logger.error('Signature verification error', e);
}
})
.catch(e => {
this.logger.error('ImportKey error', e);
reject();
}
}).catch(e => {
this.logger.error('ImportKey error', e);
reject();
});
});
} catch (e) {
this.logger.error('Signature key verification error', e);
reject();
@ -50,9 +54,7 @@ const SignatureVerifier = {
getPublicKey() {
if (!this.publicKey) {
this.publicKey = publicKey
.match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1]
.replace(/\s+/g, '');
this.publicKey = publicKey.match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1].replace(/\s+/g, '');
}
return this.publicKey;
}

View File

@ -3,13 +3,13 @@ const FeatureDetector = require('./feature-detector');
const Tip = function(el, config) {
this.el = el;
this.title = config && config.title || el.attr('title');
this.placement = config && config.placement || el.attr('tip-placement');
this.fast = config && config.fast || false;
this.title = (config && config.title) || el.attr('title');
this.placement = (config && config.placement) || el.attr('tip-placement');
this.fast = (config && config.fast) || false;
this.tipEl = null;
this.showTimeout = null;
this.hideTimeout = null;
this.force = config && config.force || false;
this.force = (config && config.force) || false;
this.hide = this.hide.bind(this);
};
@ -26,7 +26,7 @@ Tip.prototype.init = function() {
};
Tip.prototype.show = function() {
if (!Tip.enabled && !this.force || !this.title) {
if ((!Tip.enabled && !this.force) || !this.title) {
return;
}
Backbone.on('page-geometry', this.hide);
@ -37,7 +37,10 @@ Tip.prototype.show = function() {
this.hideTimeout = null;
}
}
const tipEl = this.tipEl = $('<div></div>').addClass('tip').appendTo('body').html(this.title);
const tipEl = (this.tipEl = $('<div></div>')
.addClass('tip')
.appendTo('body')
.html(this.title));
const rect = this.el[0].getBoundingClientRect();
const tipRect = this.tipEl[0].getBoundingClientRect();
const placement = this.placement || this.getAutoPlacement(rect, tipRect);
@ -45,8 +48,7 @@ Tip.prototype.show = function() {
if (this.fast) {
tipEl.addClass('tip--fast');
}
let top,
left;
let top, left;
const offset = 10;
const sideOffset = 10;
switch (placement) {
@ -171,8 +173,7 @@ Tip.hideTip = function(el) {
Tip.updateTip = function(el, props) {
if (el._tip) {
el._tip.hide();
_.extend(el._tip, _.pick(props,
['title', 'placement', 'fast', 'showTimeout', 'hideTimeout']));
_.extend(el._tip, _.pick(props, ['title', 'placement', 'fast', 'showTimeout', 'hideTimeout']));
}
};

View File

@ -41,7 +41,7 @@ const AppView = Backbone.View.extend({
titlebarStyle: 'default',
initialize: function () {
initialize: function() {
this.views = {};
this.views.menu = new MenuView({ model: this.model.menu });
this.views.menuDrag = new DragView('x');
@ -121,16 +121,20 @@ const AppView = Backbone.View.extend({
// https://github.com/keeweb/keeweb/issues/636#issuecomment-304225634
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5782378/
if (FeatureDetector.needFixClicks) {
const msEdgeScrewer = $('<input/>').appendTo(this.$el).focus();
const msEdgeScrewer = $('<input/>')
.appendTo(this.$el)
.focus();
setTimeout(() => msEdgeScrewer.remove(), 0);
}
},
render: function () {
this.$el.html(this.template({
beta: this.model.isBeta,
titlebarStyle: this.titlebarStyle
}));
render: function() {
this.$el.html(
this.template({
beta: this.model.isBeta,
titlebarStyle: this.titlebarStyle
})
);
this.panelEl = this.$el.find('.app__panel:first');
this.views.listWrap.setElement(this.$el.find('.app__list-wrap')).render();
this.views.menu.setElement(this.$el.find('.app__menu')).render();
@ -158,9 +162,13 @@ const AppView = Backbone.View.extend({
this.hideKeyChange();
this.views.open = new OpenView({ model: this.model });
this.views.open.setElement(this.$el.find('.app__body')).render();
this.views.open.on('close', () => {
Backbone.trigger('closed-open-view');
}, this);
this.views.open.on(
'close',
() => {
Backbone.trigger('closed-open-view');
},
this
);
this.views.open.on('close', this.showEntries, this);
},
@ -181,8 +189,7 @@ const AppView = Backbone.View.extend({
},
updateApp: function() {
if (UpdateModel.instance.get('updateStatus') === 'ready' &&
!Launcher && !this.model.files.hasOpenFiles()) {
if (UpdateModel.instance.get('updateStatus') === 'ready' && !Launcher && !this.model.files.hasOpenFiles()) {
window.location.reload();
}
},
@ -324,7 +331,11 @@ const AppView = Backbone.View.extend({
},
beforeUnload: function(e) {
const exitEvent = { preventDefault() { this.prevented = true; } };
const exitEvent = {
preventDefault() {
this.prevented = true;
}
};
Backbone.trigger('main-window-will-close', exitEvent);
if (exitEvent.prevented) {
Launcher.preventExit(e);
@ -344,7 +355,11 @@ const AppView = Backbone.View.extend({
if (Launcher) {
if (!this.exitAlertShown) {
if (this.model.settings.get('autoSave')) {
this.saveAndLock(result => { if (result) { exit(); } });
this.saveAndLock(result => {
if (result) {
exit();
}
});
return Launcher.preventExit(e);
}
this.exitAlertShown = true;
@ -352,13 +367,17 @@ const AppView = Backbone.View.extend({
header: Locale.appUnsavedWarn,
body: Locale.appUnsavedWarnBody,
buttons: [
{result: 'save', title: Locale.saveChanges},
{result: 'exit', title: Locale.discardChanges, error: true},
{result: '', title: Locale.appDontExitBtn}
{ result: 'save', title: Locale.saveChanges },
{ result: 'exit', title: Locale.discardChanges, error: true },
{ result: '', title: Locale.appDontExitBtn }
],
success: result => {
if (result === 'save') {
this.saveAndLock(result => { if (result) { exit(); } });
this.saveAndLock(result => {
if (result) {
exit();
}
});
} else {
exit();
}
@ -374,8 +393,13 @@ const AppView = Backbone.View.extend({
return Launcher.preventExit(e);
}
return Locale.appUnsavedWarnBody;
} else if (Launcher && !Launcher.exitRequested && !Launcher.restartPending &&
Launcher.canMinimize() && this.model.settings.get('minimizeOnClose')) {
} else if (
Launcher &&
!Launcher.exitRequested &&
!Launcher.restartPending &&
Launcher.canMinimize() &&
this.model.settings.get('minimizeOnClose')
) {
Launcher.minimizeApp();
return Launcher.preventExit(e);
}
@ -391,11 +415,11 @@ const AppView = Backbone.View.extend({
}
},
enterFullScreen: function () {
enterFullScreen: function() {
this.$el.addClass('fullscreen');
},
leaveFullScreen: function () {
leaveFullScreen: function() {
this.$el.removeClass('fullscreen');
},
@ -480,7 +504,7 @@ const AppView = Backbone.View.extend({
}
},
handleAutoSaveTimer: function () {
handleAutoSaveTimer: function() {
if (this.model.settings.get('autoSaveInterval') !== 0) {
// trigger periodical auto save
if (this.autoSaveTimeoutId) {
@ -520,10 +544,14 @@ const AppView = Backbone.View.extend({
body: alertBody + ' ' + errorFiles.join(', ')
});
}
if (complete) { complete(true); }
if (complete) {
complete(true);
}
} else {
that.closeAllFilesAndShowFirst();
if (complete) { complete(true); }
if (complete) {
complete(true);
}
}
}
}
@ -536,7 +564,11 @@ const AppView = Backbone.View.extend({
fileToShow = this.model.fileInfos.getLast();
}
if (fileToShow) {
const fileInfo = this.model.fileInfos.getMatch(fileToShow.get('storage'), fileToShow.get('name'), fileToShow.get('path'));
const fileInfo = this.model.fileInfos.getMatch(
fileToShow.get('storage'),
fileToShow.get('name'),
fileToShow.get('path')
);
if (fileInfo) {
this.views.open.showOpenFileInfo(fileInfo);
}
@ -598,13 +630,13 @@ const AppView = Backbone.View.extend({
}
} else {
if (menuItem) {
this.model.menu.select({item: menuItem});
this.model.menu.select({ item: menuItem });
}
}
} else {
this.showSettings();
if (menuItem) {
this.model.menu.select({item: menuItem});
this.model.menu.select({ item: menuItem });
}
}
},
@ -690,8 +722,12 @@ const AppView = Backbone.View.extend({
showSingleInstanceAlert: function() {
this.hideOpenFile();
Alerts.error({
header: Locale.appTabWarn, body: Locale.appTabWarnBody,
esc: false, enter: false, click: false, buttons: []
header: Locale.appTabWarn,
body: Locale.appTabWarnBody,
esc: false,
enter: false,
click: false,
buttons: []
});
},

View File

@ -16,7 +16,7 @@ const AutoTypeHintView = Backbone.View.extend({
this.input.addEventListener('blur', this.inputBlur);
},
render: function () {
render: function() {
this.renderTemplate({
cmd: FeatureDetector.isMac ? 'command' : 'ctrl',
hasCtrl: FeatureDetector.isMac,
@ -24,7 +24,9 @@ const AutoTypeHintView = Backbone.View.extend({
});
const rect = this.input.getBoundingClientRect();
this.$el.appendTo(document.body).css({
left: rect.left, top: rect.bottom + 1, width: rect.width
left: rect.left,
top: rect.bottom + 1,
width: rect.width
});
const selfRect = this.$el[0].getBoundingClientRect();
const bodyRect = document.body.getBoundingClientRect();

View File

@ -58,8 +58,10 @@ const AutoTypePopupView = Backbone.View.extend({
render() {
let topMessage;
if (this.model.filter.title || this.model.filter.url) {
topMessage = Locale.autoTypeMsgMatchedByWindow.replace('{}',
this.model.filter.title || this.model.filter.url);
topMessage = Locale.autoTypeMsgMatchedByWindow.replace(
'{}',
this.model.filter.title || this.model.filter.url
);
} else {
topMessage = Locale.autoTypeMsgNoWindow;
}

View File

@ -8,7 +8,7 @@ const DetailsAddFieldView = Backbone.View.extend({
'click .details__field-value': 'fieldValueClick'
},
render: function () {
render: function() {
this.renderTemplate();
this.labelEl = this.$el.find('.details__field-label');
return this;

View File

@ -1,30 +1,32 @@
const Backbone = require('backbone');
const FeatureDetector = require('../../util/feature-detector');
const DetailsAttachmentView = Backbone.View.extend({
template: require('templates/details/details-attachment.hbs'),
events: {
},
events: {},
render: function(complete) {
this.renderTemplate({}, true);
const shortcut = this.$el.find('.details__attachment-preview-download-text-shortcut');
shortcut.html(FeatureDetector.actionShortcutSymbol(false));
const blob = new Blob([this.model.getBinary()], {type: this.model.mimeType});
const blob = new Blob([this.model.getBinary()], { type: this.model.mimeType });
const dataEl = this.$el.find('.details__attachment-preview-data');
switch ((this.model.mimeType || '').split('/')[0]) {
case 'text':
const reader = new FileReader();
reader.addEventListener('loadend', () => {
$('<pre/>').text(reader.result).appendTo(dataEl);
$('<pre/>')
.text(reader.result)
.appendTo(dataEl);
complete();
});
reader.readAsText(blob);
return this;
case 'image':
$('<img/>').attr('src', URL.createObjectURL(blob)).appendTo(dataEl);
$('<img/>')
.attr('src', URL.createObjectURL(blob))
.appendTo(dataEl);
complete();
return this;
}

View File

@ -1,4 +1,3 @@
const Backbone = require('backbone');
const AutoTypeHintView = require('../auto-type-hint-view');
const Locale = require('../../util/locale');
@ -57,8 +56,10 @@ const DetailsAutoTypeView = Backbone.View.extend({
seqFocus: function(e) {
if (!this.views.hint) {
this.views.hint = new AutoTypeHintView({input: e.target}).render();
this.views.hint.on('remove', () => { delete this.views.hint; });
this.views.hint = new AutoTypeHintView({ input: e.target }).render();
this.views.hint.on('remove', () => {
delete this.views.hint;
});
}
},

View File

@ -22,13 +22,55 @@ const DetailsHistoryView = Backbone.View.extend({
},
formats: [
{ name: 'ms', round: 1, format: function(d) { return Format.dtStr(d); } },
{ name: 'sec', round: 1000, format: function(d) { return Format.dtStr(d); } },
{ name: 'min', round: 1000 * 60, format: function(d) { return Format.dtStr(d).replace(':00 ', ' '); } },
{ name: 'hour', round: 1000 * 60 * 60, format: function(d) { return Format.dtStr(d).replace(':00', ''); } },
{ name: 'day', round: 1000 * 60 * 60 * 24, format: function(d) { return Format.dStr(d); } },
{ name: 'month', round: 1000 * 60 * 60 * 24 * 31, format: function(d) { return Format.dStr(d); } },
{ name: 'year', round: 1000 * 60 * 60 * 24 * 365, format: function(d) { return d.getFullYear(); } }
{
name: 'ms',
round: 1,
format: function(d) {
return Format.dtStr(d);
}
},
{
name: 'sec',
round: 1000,
format: function(d) {
return Format.dtStr(d);
}
},
{
name: 'min',
round: 1000 * 60,
format: function(d) {
return Format.dtStr(d).replace(':00 ', ' ');
}
},
{
name: 'hour',
round: 1000 * 60 * 60,
format: function(d) {
return Format.dtStr(d).replace(':00', '');
}
},
{
name: 'day',
round: 1000 * 60 * 60 * 24,
format: function(d) {
return Format.dStr(d);
}
},
{
name: 'month',
round: 1000 * 60 * 60 * 24 * 31,
format: function(d) {
return Format.dStr(d);
}
},
{
name: 'year',
round: 1000 * 60 * 60 * 24 * 365,
format: function(d) {
return d.getFullYear();
}
}
],
fieldViews: null,
@ -45,14 +87,16 @@ const DetailsHistoryView = Backbone.View.extend({
this.timelineEl = this.$el.find('.details__history-timeline');
this.bodyEl = this.$el.find('.details__history-body');
this.timeline.forEach(function(item, ix) {
$('<i/>').addClass('fa fa-circle details__history-timeline-item')
.css('left', (item.pos * 100) + '%')
$('<i/>')
.addClass('fa fa-circle details__history-timeline-item')
.css('left', item.pos * 100 + '%')
.attr('data-id', ix)
.appendTo(this.timelineEl);
}, this);
this.labels.forEach(function(label) {
$('<div/>').addClass('details__history-timeline-label')
.css('left', (label.pos * 100) + '%')
$('<div/>')
.addClass('details__history-timeline-label')
.css('left', label.pos * 100 + '%')
.text(label.text)
.appendTo(this.timelineEl);
}, this);
@ -78,32 +122,97 @@ const DetailsHistoryView = Backbone.View.extend({
this.activeIx = ix;
this.record = this.timeline[ix].rec;
this.timelineEl.find('.details__history-timeline-item').removeClass('details__history-timeline-item--active');
this.timelineEl.find('.details__history-timeline-item[data-id="' + ix + '"]').addClass('details__history-timeline-item--active');
this.timelineEl
.find('.details__history-timeline-item[data-id="' + ix + '"]')
.addClass('details__history-timeline-item--active');
this.removeFieldViews();
this.bodyEl.html('');
const colorCls = this.record.color ? this.record.color + '-color' : '';
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Updated', title: Locale.detHistorySaved,
value: Format.dtStr(this.record.updated) +
(this.record.unsaved ? ' (' + Locale.detHistoryCurUnsavedState + ')' : '') +
((ix === this.history.length - 1 && !this.record.unsaved) ? ' (' + Locale.detHistoryCurState + ')' : '') } }));
this.fieldViews.push(new FieldViewReadOnlyRaw({ model: { name: '$Title', title: Format.capFirst(Locale.title),
value: '<i class="fa fa-' + this.record.icon + ' ' + colorCls + '"></i> ' +
_.escape(this.record.title) || '(' + Locale.detHistoryNoTitle + ')' } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$UserName', title: Format.capFirst(Locale.user), value: this.record.user } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$Password', title: Format.capFirst(Locale.password), value: this.record.password } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$URL', title: Format.capFirst(Locale.website), value: this.record.url } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$Notes', title: Format.capFirst(Locale.notes), value: this.record.notes } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Tags', title: Format.capFirst(Locale.tags),
value: this.record.tags.join(', ') } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Expires', title: Locale.detExpires,
value: this.record.expires ? Format.dtStr(this.record.expires) : '' } }));
_.forEach(this.record.fields, function(value, field) {
this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$' + field, title: field, value: value } }));
}, this);
this.fieldViews.push(
new FieldViewReadOnly({ model: { name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 } })
);
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Updated',
title: Locale.detHistorySaved,
value:
Format.dtStr(this.record.updated) +
(this.record.unsaved ? ' (' + Locale.detHistoryCurUnsavedState + ')' : '') +
(ix === this.history.length - 1 && !this.record.unsaved
? ' (' + Locale.detHistoryCurState + ')'
: '')
}
})
);
this.fieldViews.push(
new FieldViewReadOnlyRaw({
model: {
name: '$Title',
title: Format.capFirst(Locale.title),
value:
'<i class="fa fa-' +
this.record.icon +
' ' +
colorCls +
'"></i> ' +
_.escape(this.record.title) || '(' + Locale.detHistoryNoTitle + ')'
}
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: '$UserName', title: Format.capFirst(Locale.user), value: this.record.user }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: '$Password', title: Format.capFirst(Locale.password), value: this.record.password }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: '$URL', title: Format.capFirst(Locale.website), value: this.record.url }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: '$Notes', title: Format.capFirst(Locale.notes), value: this.record.notes }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: 'Tags', title: Format.capFirst(Locale.tags), value: this.record.tags.join(', ') }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Expires',
title: Locale.detExpires,
value: this.record.expires ? Format.dtStr(this.record.expires) : ''
}
})
);
_.forEach(
this.record.fields,
function(value, field) {
this.fieldViews.push(
new FieldViewReadOnly({ model: { name: '$' + field, title: field, value: value } })
);
},
this
);
if (this.record.attachments.length) {
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Attachments', title: Locale.detAttachments,
value: this.record.attachments.map(att => att.title).join(', ') } }));
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Attachments',
title: Locale.detAttachments,
value: this.record.attachments.map(att => att.title).join(', ')
}
})
);
}
this.fieldViews.forEach(function(fieldView) {
fieldView.setElement(this.bodyEl).render();
@ -112,12 +221,15 @@ const DetailsHistoryView = Backbone.View.extend({
const buttons = this.$el.find('.details__history-buttons');
buttons.find('.details__history-button-revert').toggle(ix < this.history.length - 1);
buttons.find('.details__history-button-delete').toggle(ix < this.history.length - 1);
buttons.find('.details__history-button-discard').toggle(this.record.unsaved && ix === this.history.length - 1 &&
this.history.length > 1 || false);
buttons
.find('.details__history-button-discard')
.toggle((this.record.unsaved && ix === this.history.length - 1 && this.history.length > 1) || false);
},
timelineItemClick: function(e) {
const id = $(e.target).closest('.details__history-timeline-item').data('id');
const id = $(e.target)
.closest('.details__history-timeline-item')
.data('id');
this.showRecord(id);
},
@ -142,12 +254,13 @@ const DetailsHistoryView = Backbone.View.extend({
}));
const period = lastRec.updated - firstRec.updated;
const format = this.getDateFormat(period);
this.labels = this.getLabels(firstRec.updated.getTime(), lastRec.updated.getTime(), format.round)
.map(label => ({
this.labels = this.getLabels(firstRec.updated.getTime(), lastRec.updated.getTime(), format.round).map(
label => ({
pos: (label - firstRec.updated) / (lastRec.updated - firstRec.updated),
val: label,
text: format.format(new Date(label))
}));
})
);
},
getDateFormat: function(period) {
@ -170,7 +283,7 @@ const DetailsHistoryView = Backbone.View.extend({
labels.push(label);
label += round;
}
if (labels.length > 1 && (labels[0] - first) / (last - first) < 0.10) {
if (labels.length > 1 && (labels[0] - first) / (last - first) < 0.1) {
labels.shift();
}
return labels;

View File

@ -61,7 +61,7 @@ const DetailsView = Backbone.View.extend({
'contextmenu .details': 'contextMenu'
},
initialize: function () {
initialize: function() {
this.fieldViews = [];
this.views = {};
this.initScroll();
@ -100,7 +100,7 @@ const DetailsView = Backbone.View.extend({
this.hideFieldCopyTip();
},
render: function () {
render: function() {
this.removeScroll();
this.removeFieldViews();
this.removeInnerViews();
@ -140,45 +140,175 @@ const DetailsView = Backbone.View.extend({
const fileNames = this.appModel.files.map(function(file) {
return { id: file.id, value: file.get('name'), selected: file === this.model.file };
}, this);
this.fileEditView = new FieldViewSelect({ model: { name: '$File', title: Format.capFirst(Locale.file),
value: function() { return fileNames; } } });
this.fileEditView = new FieldViewSelect({
model: {
name: '$File',
title: Format.capFirst(Locale.file),
value: function() {
return fileNames;
}
}
});
this.fieldViews.push(this.fileEditView);
} else {
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'File', title: Format.capFirst(Locale.file),
value: function() { return model.fileName; } } }));
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'File',
title: Format.capFirst(Locale.file),
value: function() {
return model.fileName;
}
}
})
);
}
this.userEditView = new FieldViewAutocomplete({ model: { name: '$UserName', title: Format.capFirst(Locale.user),
value: function() { return model.user; }, getCompletions: this.getUserNameCompletions.bind(this) } });
this.fieldViews.push(this.userEditView);
this.passEditView = new FieldViewText({ model: { name: '$Password', title: Format.capFirst(Locale.password), canGen: true,
value: function() { return model.password; } } });
this.fieldViews.push(this.passEditView);
this.urlEditView = new FieldViewUrl({ model: { name: '$URL', title: Format.capFirst(Locale.website),
value: function() { return model.url; } } });
this.fieldViews.push(this.urlEditView);
this.fieldViews.push(new FieldViewText({ model: { name: '$Notes', title: Format.capFirst(Locale.notes), multiline: 'true',
value: function() { return model.notes; } } }));
this.fieldViews.push(new FieldViewTags({ model: { name: 'Tags', title: Format.capFirst(Locale.tags), tags: this.appModel.tags,
value: function() { return model.tags; } } }));
this.fieldViews.push(new FieldViewDate({ model: { name: 'Expires', title: Locale.detExpires, lessThanNow: '(' + Locale.detExpired + ')',
value: function() { return model.expires; } } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Group', title: Locale.detGroup,
value: function() { return model.groupName; }, tip: function() { return model.getGroupPath().join(' / '); } } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Created', title: Locale.detCreated,
value: function() { return Format.dtStr(model.created); } } }));
this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Updated', title: Locale.detUpdated,
value: function() { return Format.dtStr(model.updated); } } }));
this.fieldViews.push(new FieldViewHistory({ model: { name: 'History', title: Format.capFirst(Locale.history),
value: function() { return { length: model.historyLength, unsaved: model.unsaved }; } } }));
_.forEach(model.fields, function(value, field) {
if (field === 'otp' && this.model.otpGenerator) {
this.fieldViews.push(new FieldViewOtp({ model: { name: '$' + field, title: field,
value: function() { return model.otpGenerator; } } }));
} else {
this.fieldViews.push(new FieldViewCustom({ model: { name: '$' + field, title: field,
value: function() { return model.fields[field]; } } }));
this.userEditView = new FieldViewAutocomplete({
model: {
name: '$UserName',
title: Format.capFirst(Locale.user),
value: function() {
return model.user;
},
getCompletions: this.getUserNameCompletions.bind(this)
}
}, this);
});
this.fieldViews.push(this.userEditView);
this.passEditView = new FieldViewText({
model: {
name: '$Password',
title: Format.capFirst(Locale.password),
canGen: true,
value: function() {
return model.password;
}
}
});
this.fieldViews.push(this.passEditView);
this.urlEditView = new FieldViewUrl({
model: {
name: '$URL',
title: Format.capFirst(Locale.website),
value: function() {
return model.url;
}
}
});
this.fieldViews.push(this.urlEditView);
this.fieldViews.push(
new FieldViewText({
model: {
name: '$Notes',
title: Format.capFirst(Locale.notes),
multiline: 'true',
value: function() {
return model.notes;
}
}
})
);
this.fieldViews.push(
new FieldViewTags({
model: {
name: 'Tags',
title: Format.capFirst(Locale.tags),
tags: this.appModel.tags,
value: function() {
return model.tags;
}
}
})
);
this.fieldViews.push(
new FieldViewDate({
model: {
name: 'Expires',
title: Locale.detExpires,
lessThanNow: '(' + Locale.detExpired + ')',
value: function() {
return model.expires;
}
}
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Group',
title: Locale.detGroup,
value: function() {
return model.groupName;
},
tip: function() {
return model.getGroupPath().join(' / ');
}
}
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Created',
title: Locale.detCreated,
value: function() {
return Format.dtStr(model.created);
}
}
})
);
this.fieldViews.push(
new FieldViewReadOnly({
model: {
name: 'Updated',
title: Locale.detUpdated,
value: function() {
return Format.dtStr(model.updated);
}
}
})
);
this.fieldViews.push(
new FieldViewHistory({
model: {
name: 'History',
title: Format.capFirst(Locale.history),
value: function() {
return { length: model.historyLength, unsaved: model.unsaved };
}
}
})
);
_.forEach(
model.fields,
function(value, field) {
if (field === 'otp' && this.model.otpGenerator) {
this.fieldViews.push(
new FieldViewOtp({
model: {
name: '$' + field,
title: field,
value: function() {
return model.otpGenerator;
}
}
})
);
} else {
this.fieldViews.push(
new FieldViewCustom({
model: {
name: '$' + field,
title: field,
value: function() {
return model.fields[field];
}
}
})
);
}
},
this
);
const hideEmptyFields = AppSettingsModel.instance.get('hideEmptyFields');
@ -218,8 +348,16 @@ const DetailsView = Backbone.View.extend({
}
}
}
const fieldView = new FieldViewCustom({ model: { name: '$' + newFieldTitle, title: newFieldTitle, newField: newFieldTitle,
value: function() { return ''; } } });
const fieldView = new FieldViewCustom({
model: {
name: '$' + newFieldTitle,
title: newFieldTitle,
newField: newFieldTitle,
value: function() {
return '';
}
}
});
fieldView.on('change', this.fieldChanged.bind(this));
fieldView.setElement(this.$el.find('.details__body-fields')).render();
fieldView.edit();
@ -240,24 +378,27 @@ const DetailsView = Backbone.View.extend({
if (hideEmptyFields) {
this.fieldViews.forEach(fieldView => {
if (fieldView.isHidden()) {
moreOptions.push({value: 'add:' + fieldView.model.name, icon: 'pencil',
text: Locale.detMenuAddField.replace('{}', fieldView.model.title)});
moreOptions.push({
value: 'add:' + fieldView.model.name,
icon: 'pencil',
text: Locale.detMenuAddField.replace('{}', fieldView.model.title)
});
}
}, this);
moreOptions.push({value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField});
moreOptions.push({value: 'toggle-empty', icon: 'eye', text: Locale.detMenuShowEmpty});
moreOptions.push({ value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField });
moreOptions.push({ value: 'toggle-empty', icon: 'eye', text: Locale.detMenuShowEmpty });
} else {
moreOptions.push({value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField});
moreOptions.push({value: 'toggle-empty', icon: 'eye-slash', text: Locale.detMenuHideEmpty});
moreOptions.push({ value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField });
moreOptions.push({ value: 'toggle-empty', icon: 'eye-slash', text: Locale.detMenuHideEmpty });
}
moreOptions.push({value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp});
moreOptions.push({ value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp });
if (AutoType.enabled) {
moreOptions.push({value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoTypeSettings});
moreOptions.push({ value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoTypeSettings });
}
moreOptions.push({value: 'clone', icon: 'clone', text: Locale.detClone});
moreOptions.push({ value: 'clone', icon: 'clone', text: Locale.detClone });
const rect = this.moreView.labelEl[0].getBoundingClientRect();
dropdownView.render({
position: {top: rect.bottom, left: rect.left},
position: { top: rect.bottom, left: rect.left },
options: moreOptions
});
this.views.dropdownView = dropdownView;
@ -301,7 +442,9 @@ const DetailsView = Backbone.View.extend({
},
setSelectedColor: function(color) {
this.$el.find('.details__colors-popup > .details__colors-popup-item').removeClass('details__colors-popup-item--active');
this.$el
.find('.details__colors-popup > .details__colors-popup-item')
.removeClass('details__colors-popup-item--active');
const colorEl = this.$el.find('.details__header-color')[0];
_.forEach(colorEl.classList, cls => {
if (cls.indexOf('color') > 0 && cls.lastIndexOf('details', 0) !== 0) {
@ -309,13 +452,17 @@ const DetailsView = Backbone.View.extend({
}
});
if (color) {
this.$el.find('.details__colors-popup > .' + color + '-color').addClass('details__colors-popup-item--active');
this.$el
.find('.details__colors-popup > .' + color + '-color')
.addClass('details__colors-popup-item--active');
colorEl.classList.add(color + '-color');
}
},
selectColor: function(e) {
let color = $(e.target).closest('.details__colors-popup-item').data('color');
let color = $(e.target)
.closest('.details__colors-popup-item')
.data('color');
if (!color) {
return;
}
@ -336,7 +483,8 @@ const DetailsView = Backbone.View.extend({
el: this.scroller,
model: {
iconId: this.model.customIconId || this.model.iconId,
url: this.model.url, file: this.model.file
url: this.model.url,
file: this.model.file
}
});
this.listenTo(subView, 'select', this.iconSelected);
@ -379,7 +527,7 @@ const DetailsView = Backbone.View.extend({
return;
}
const mimeType = attachment.mimeType || 'application/octet-stream';
const blob = new Blob([data], {type: mimeType});
const blob = new Blob([data], { type: mimeType });
FileSaver.saveAs(blob, attachment.title);
},
@ -408,7 +556,9 @@ const DetailsView = Backbone.View.extend({
},
copyKeyPress: function(editView) {
if (this.isHidden()) { return; }
if (this.isHidden()) {
return;
}
if (!window.getSelection().toString()) {
const fieldValue = editView.value;
const fieldText = fieldValue && fieldValue.isProtected ? fieldValue.getText() : fieldValue;
@ -449,7 +599,9 @@ const DetailsView = Backbone.View.extend({
const tip = new Tip(label, { title: Locale.detCopyHint, placement: 'right' });
tip.show();
this.fieldCopyTip = tip;
setTimeout(() => { tip.hide(); }, Timeouts.AutoHideHint);
setTimeout(() => {
tip.hide();
}, Timeouts.AutoHideHint);
},
settingsToggled: function() {
@ -500,8 +652,11 @@ const DetailsView = Backbone.View.extend({
}
this.entryUpdated(true);
this.fieldViews.forEach(function(fieldView, ix) {
if (fieldView instanceof FieldViewCustom && !fieldView.model.newField &&
!this.model.hasField(fieldView.model.title)) {
if (
fieldView instanceof FieldViewCustom &&
!fieldView.model.newField &&
!this.model.hasField(fieldView.model.title)
) {
fieldView.remove();
this.fieldViews.splice(ix, 1);
} else {
@ -584,13 +739,17 @@ const DetailsView = Backbone.View.extend({
},
addAttachedFiles: function(files) {
_.forEach(files, function(file) {
const reader = new FileReader();
reader.onload = () => {
this.addAttachment(file.name, reader.result);
};
reader.readAsArrayBuffer(file);
}, this);
_.forEach(
files,
function(file) {
const reader = new FileReader();
reader.onload = () => {
this.addAttachment(file.name, reader.result);
};
reader.readAsArrayBuffer(file);
},
this
);
},
addAttachment: function(name, data) {
@ -676,7 +835,8 @@ const DetailsView = Backbone.View.extend({
},
focusNextField: function(config) {
let found = false, nextFieldView;
let found = false,
nextFieldView;
if (config.field === '$Title' && !config.prev) {
found = true;
}
@ -793,10 +953,14 @@ const DetailsView = Backbone.View.extend({
} else {
this.moreView.remove();
this.moreView = null;
const fieldView = new FieldViewCustom({ model: {
name: '$otp', title: 'otp', newField: 'otp',
value: kdbxweb.ProtectedValue.fromString('')
}});
const fieldView = new FieldViewCustom({
model: {
name: '$otp',
title: 'otp',
newField: 'otp',
value: kdbxweb.ProtectedValue.fromString('')
}
});
fieldView.on('change', this.fieldChanged.bind(this));
fieldView.setElement(this.$el.find('.details__body-fields')).render();
fieldView.edit();

View File

@ -5,7 +5,7 @@ const DragView = Backbone.View.extend({
'mousedown': 'mousedown'
},
initialize: function (coord) {
initialize: function(coord) {
this.setCoord(coord);
this.mouseDownTime = -1;
this.mouseDownCount = 0;
@ -17,7 +17,9 @@ const DragView = Backbone.View.extend({
},
render: function() {
$('<div/>').addClass('drag-handle__inner').appendTo(this.$el);
$('<div/>')
.addClass('drag-handle__inner')
.appendTo(this.$el);
},
mousedown: function(e) {
@ -35,7 +37,9 @@ const DragView = Backbone.View.extend({
}
this.initialOffset = e[this.offsetProp];
const cursor = this.$el.css('cursor');
this.dragMask = $('<div/>', {'class': 'drag-mask'}).css('cursor', cursor).appendTo('body');
this.dragMask = $('<div/>', { 'class': 'drag-mask' })
.css('cursor', cursor)
.appendTo('body');
this.dragMask.on('mousemove', this.mousemove.bind(this));
this.dragMask.on('mouseup', this.mouseup.bind(this));
this.trigger('dragstart', { offset: this.initialOffset, coord: this.coord });

View File

@ -7,19 +7,19 @@ const DropdownView = Backbone.View.extend({
'click .dropdown__item': 'itemClick'
},
initialize: function () {
initialize: function() {
this.bodyClick = this.bodyClick.bind(this);
this.listenTo(Backbone, 'show-context-menu', this.bodyClick);
$('body').on('click contextmenu keyup', this.bodyClick);
},
render: function (config) {
render: function(config) {
this.options = config.options;
this.renderTemplate(config);
this.$el.appendTo(document.body);
const ownRect = this.$el[0].getBoundingClientRect();
const bodyRect = document.body.getBoundingClientRect();
let left = config.position.left || (config.position.right - ownRect.right + ownRect.left);
let left = config.position.left || config.position.right - ownRect.right + ownRect.left;
let top = config.position.top;
if (left + ownRect.width > bodyRect.right) {
left = Math.max(0, bodyRect.right - ownRect.width);

View File

@ -61,7 +61,8 @@ const FieldViewAutocomplete = FieldViewText.extend({
moveAutocomplete: function(next) {
const completions = this.model.getCompletions(this.input.val());
if (typeof this.selectedCopmletionIx === 'number') {
this.selectedCopmletionIx = (completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) % completions.length;
this.selectedCopmletionIx =
(completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) % completions.length;
} else {
this.selectedCopmletionIx = next ? 0 : completions.length - 1;
}
@ -70,10 +71,12 @@ const FieldViewAutocomplete = FieldViewText.extend({
updateAutocomplete: function() {
const completions = this.model.getCompletions(this.input.val());
const completionsHtml = completions.map((item, ix) => {
const sel = ix === this.selectedCopmletionIx ? 'details__field-autocomplete-item--selected' : '';
return '<div class="details__field-autocomplete-item ' + sel + '">' + _.escape(item) + '</div>';
}).join('');
const completionsHtml = completions
.map((item, ix) => {
const sel = ix === this.selectedCopmletionIx ? 'details__field-autocomplete-item--selected' : '';
return '<div class="details__field-autocomplete-item ' + sel + '">' + _.escape(item) + '</div>';
})
.join('');
this.autocomplete.html(completionsHtml);
this.autocomplete.toggle(!!completionsHtml);
},
@ -85,7 +88,9 @@ const FieldViewAutocomplete = FieldViewText.extend({
this.input.val(selectedItem);
this.endEdit(selectedItem);
} else {
this.afterPaint(function () { this.input.focus(); });
this.afterPaint(function() {
this.input.focus();
});
}
}
});

View File

@ -22,7 +22,8 @@ const FieldViewCustom = FieldViewText.extend({
this.isProtected = this.value instanceof kdbxweb.ProtectedValue;
}
this.$el.toggleClass('details__field--protected', this.isProtected);
$('<div/>').addClass('details__field-value-btn details__field-value-btn-protect')
$('<div/>')
.addClass('details__field-value-btn details__field-value-btn-protect')
.appendTo(this.valueEl)
.mousedown(this.protectBtnClick.bind(this));
let securityTipTitle = Locale.detLockField;
@ -63,8 +64,11 @@ const FieldViewCustom = FieldViewText.extend({
const text = emptyTitle ? '' : this.model.title || '';
this.labelInput = $('<input/>');
this.labelEl.html('').append(this.labelInput);
this.labelInput.attr({ autocomplete: 'off', spellcheck: 'false' })
.val(text).focus()[0].setSelectionRange(text.length, text.length);
this.labelInput
.attr({ autocomplete: 'off', spellcheck: 'false' })
.val(text)
.focus()[0]
.setSelectionRange(text.length, text.length);
this.labelInput.bind({
input: this.fieldLabelInput.bind(this),
keydown: this.fieldLabelKeydown.bind(this),
@ -152,7 +156,9 @@ const FieldViewCustom = FieldViewText.extend({
if (this.labelInput) {
this.endEditTitle(this.labelInput.val());
}
this.setTimeout(function() { this.input.focus(); });
this.setTimeout(function() {
this.input.focus();
});
}
});

View File

@ -44,7 +44,9 @@ const FieldViewDate = FieldViewText.extend({
endEdit: function(newVal, extra) {
if (this.picker) {
try { this.picker.destroy(); } catch (e) {}
try {
this.picker.destroy();
} catch (e) {}
this.picker = null;
}
newVal = new Date(newVal);

View File

@ -4,13 +4,24 @@ const FieldViewSelect = FieldView.extend({
readonly: true,
renderValue: function(value) {
return '<select>' +
value.map(opt => {
return '<option ' + 'value="' + _.escape(opt.id) + '" ' + (opt.selected ? 'selected ' : '') + '>' +
_.escape(opt.value) +
'</option>';
}).join('') +
'</select>';
return (
'<select>' +
value
.map(opt => {
return (
'<option ' +
'value="' +
_.escape(opt.id) +
'" ' +
(opt.selected ? 'selected ' : '') +
'>' +
_.escape(opt.value) +
'</option>'
);
})
.join('') +
'</select>'
);
},
render: function() {

View File

@ -14,9 +14,14 @@ const FieldViewTags = FieldViewText.extend({
this.model.tags.forEach(tag => {
allTags[tag.toLowerCase()] = tag;
});
return _.unique(val.split(/\s*[;,:]\s*/).filter(_.identity).map(tag => {
return allTags[tag.toLowerCase()] || tag;
}));
return _.unique(
val
.split(/\s*[;,:]\s*/)
.filter(_.identity)
.map(tag => {
return allTags[tag.toLowerCase()] || tag;
})
);
},
endEdit: function(newVal, extra) {
@ -60,9 +65,11 @@ const FieldViewTags = FieldViewText.extend({
setTags: function() {
const availableTags = this.getAvailableTags();
const tagsHtml = availableTags.map(tag => {
return '<div class="details__field-autocomplete-item">' + _.escape(tag) + '</div>';
}).join('');
const tagsHtml = availableTags
.map(tag => {
return '<div class="details__field-autocomplete-item">' + _.escape(tag) + '</div>';
})
.join('');
this.tagsAutocomplete.html(tagsHtml);
this.tagsAutocomplete.toggle(!!tagsHtml);
},
@ -88,7 +95,9 @@ const FieldViewTags = FieldViewText.extend({
this.input.focus();
this.setTags();
}
this.afterPaint(function() { this.input.focus(); });
this.afterPaint(function() {
this.input.focus();
});
}
});

View File

@ -10,7 +10,8 @@ const Tip = require('../../util/tip');
const FieldViewText = FieldView.extend({
renderValue: function(value) {
return value && value.isProtected ? PasswordGenerator.present(value.textLength)
return value && value.isProtected
? PasswordGenerator.present(value.textLength)
: _.escape(value || '').replace(/\n/g, '<br/>');
},
@ -24,8 +25,11 @@ const FieldViewText = FieldView.extend({
this.$el.toggleClass('details__field--protected', isProtected);
this.input = $(document.createElement(this.model.multiline ? 'textarea' : 'input'));
this.valueEl.html('').append(this.input);
this.input.attr({ autocomplete: 'off', spellcheck: 'false' })
.val(text).focus()[0].setSelectionRange(text.length, text.length);
this.input
.attr({ autocomplete: 'off', spellcheck: 'false' })
.val(text)
.focus()[0]
.setSelectionRange(text.length, text.length);
this.input.bind({
input: this.fieldValueInput.bind(this),
keydown: this.fieldValueKeydown.bind(this),
@ -42,7 +46,9 @@ const FieldViewText = FieldView.extend({
this.createMobileControls();
}
if (this.model.canGen) {
$('<div/>').addClass('details__field-value-btn details__field-value-btn-gen').appendTo(this.valueEl)
$('<div/>')
.addClass('details__field-value-btn details__field-value-btn-gen')
.appendTo(this.valueEl)
.click(this.showGeneratorClick.bind(this))
.mousedown(this.showGenerator.bind(this));
}
@ -78,7 +84,9 @@ const FieldViewText = FieldView.extend({
this.hideGenerator();
} else {
const fieldRect = this.input[0].getBoundingClientRect();
this.gen = new GeneratorView({model: {pos: {left: fieldRect.left, top: fieldRect.bottom}, password: this.value}}).render();
this.gen = new GeneratorView({
model: { pos: { left: fieldRect.left, top: fieldRect.bottom }, password: this.value }
}).render();
this.gen.once('remove', this.generatorClosed.bind(this));
this.gen.once('result', this.generatorResult.bind(this));
}
@ -231,8 +239,11 @@ const FieldViewText = FieldView.extend({
mobileFieldControlTouchMove(e) {
const touch = e.originalEvent.targetTouches[0];
const rect = touch.target.getBoundingClientRect();
const inside = touch.clientX >= rect.left && touch.clientX <= rect.right &&
touch.clientY >= rect.top && touch.clientY <= rect.bottom;
const inside =
touch.clientX >= rect.left &&
touch.clientX <= rect.right &&
touch.clientY >= rect.top &&
touch.clientY <= rect.bottom;
if (inside) {
this.$el.attr('active-mobile-action', $(e.target).data('action'));
} else {

View File

@ -4,7 +4,13 @@ const FieldViewUrl = FieldViewText.extend({
displayUrlRegex: /^http:\/\//i,
renderValue: function(value) {
return value ? '<a href="' + _.escape(this.fixUrl(value)) + '" rel="noreferrer noopener" target="_blank">' + _.escape(this.displayUrl(value)) + '</a>' : '';
return value
? '<a href="' +
_.escape(this.fixUrl(value)) +
'" rel="noreferrer noopener" target="_blank">' +
_.escape(this.displayUrl(value)) +
'</a>'
: '';
},
fixUrl: function(url) {

View File

@ -13,8 +13,13 @@ const FieldView = Backbone.View.extend({
render: function() {
this.value = typeof this.model.value === 'function' ? this.model.value() : this.model.value;
this.renderTemplate({ editable: !this.readonly, multiline: this.model.multiline, title: this.model.title,
canEditTitle: this.model.newField, protect: this.value && this.value.isProtected });
this.renderTemplate({
editable: !this.readonly,
multiline: this.model.multiline,
title: this.model.title,
canEditTitle: this.model.newField,
protect: this.value && this.value.isProtected
});
this.valueEl = this.$el.find('.details__field-value');
this.valueEl.html(this.renderValue(this.value));
this.labelEl = this.$el.find('.details__field-label');
@ -38,7 +43,10 @@ const FieldView = Backbone.View.extend({
update: function() {
if (typeof this.model.value === 'function') {
const newVal = this.model.value();
if (!_.isEqual(newVal, this.value) || (this.value && newVal && this.value.toString() !== newVal.toString())) {
if (
!_.isEqual(newVal, this.value) ||
(this.value && newVal && this.value.toString() !== newVal.toString())
) {
this.render();
}
}
@ -121,7 +129,9 @@ const FieldView = Backbone.View.extend({
return;
}
this.editing = false;
setTimeout(() => { this.preventCopy = false; }, 300);
setTimeout(() => {
this.preventCopy = false;
}, 300);
let textEqual;
if (this.value && this.value.isProtected) {
textEqual = this.value.equals(newVal);

View File

@ -16,7 +16,7 @@ const FooterView = Backbone.View.extend({
'click .footer__btn-lock': 'lockWorkspace'
},
initialize: function () {
initialize: function() {
this.views = {};
KeyHandler.onKey(Keys.DOM_VK_L, this.lockWorkspace, this, KeyHandler.SHORTCUT_ACTION, false, true);
@ -31,11 +31,14 @@ const FooterView = Backbone.View.extend({
this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render);
},
render: function () {
this.renderTemplate({
files: this.model.files,
updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
}, { plain: true });
render: function() {
this.renderTemplate(
{
files: this.model.files,
updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
},
{ plain: true }
);
return this;
},
@ -65,12 +68,16 @@ const FooterView = Backbone.View.extend({
const right = bodyRect.right - rect.right;
const bottom = bodyRect.bottom - rect.top;
const generator = new GeneratorView({ model: { copy: true, pos: { right: right, bottom: bottom } } }).render();
generator.once('remove', () => { delete this.views.gen; });
generator.once('remove', () => {
delete this.views.gen;
});
this.views.gen = generator;
},
showFile: function(e) {
const fileId = $(e.target).closest('.footer__db-item').data('file-id');
const fileId = $(e.target)
.closest('.footer__db-item')
.data('file-id');
if (fileId) {
Backbone.trigger('show-file', { fileId: fileId });
}

View File

@ -33,11 +33,14 @@ const GeneratorPresetsView = Backbone.View.extend({
if (!this.selected || !this.presets.some(p => p.name === this.selected)) {
this.selected = (this.presets.filter(p => p.default)[0] || this.presets[0]).name;
}
this.renderTemplate({
presets: this.presets,
selected: this.getPreset(this.selected),
ranges: this.getSelectedRanges()
}, true);
this.renderTemplate(
{
presets: this.presets,
selected: this.getPreset(this.selected),
ranges: this.getSelectedRanges()
},
true
);
this.createScroll({
root: this.$el.find('.gen-ps')[0],
scroller: this.$el.find('.scroller')[0],
@ -97,10 +100,15 @@ const GeneratorPresetsView = Backbone.View.extend({
}
const selected = this.getPreset(this.selected);
const preset = {
name, title,
name,
title,
length: selected.length,
upper: selected.upper, lower: selected.lower, digits: selected.digits,
special: selected.special, brackets: selected.brackets, ambiguous: selected.ambiguous,
upper: selected.upper,
lower: selected.lower,
digits: selected.digits,
special: selected.special,
brackets: selected.brackets,
ambiguous: selected.ambiguous,
include: selected.include
};
GeneratorPresets.add(preset);

View File

@ -29,7 +29,7 @@ const GeneratorView = Backbone.View.extend({
presets: null,
preset: null,
initialize: function () {
initialize: function() {
this.createPresets();
const preset = this.preset;
this.gen = _.clone(_.find(this.presets, pr => pr.name === preset));
@ -40,7 +40,7 @@ const GeneratorView = Backbone.View.extend({
render: function() {
const canCopy = document.queryCommandSupported('copy');
const btnTitle = this.model.copy ? canCopy ? Locale.alertCopy : Locale.alertClose : Locale.alertOk;
const btnTitle = this.model.copy ? (canCopy ? Locale.alertCopy : Locale.alertClose) : Locale.alertOk;
this.renderTemplate({
btnTitle: btnTitle,
showToggleButton: this.model.copy,
@ -112,8 +112,10 @@ const GeneratorView = Backbone.View.extend({
},
optionChanged: function(option) {
if (this.preset === 'Custom' ||
this.preset === 'Pronounceable' && ['length', 'lower', 'upper'].indexOf(option) >= 0) {
if (
this.preset === 'Custom' ||
(this.preset === 'Pronounceable' && ['length', 'lower', 'upper'].indexOf(option) >= 0)
) {
return;
}
this.preset = this.gen.name = 'Custom';
@ -132,7 +134,7 @@ const GeneratorView = Backbone.View.extend({
// AppSettingsModel.instance.unset('generatorHidePassword', { silent: true });
AppSettingsModel.instance.set('generatorHidePassword', this.hide);
const label = this.$el.find('.gen__check-hide-label');
Tip.updateTip(label[0], {title: this.hide ? Locale.genShowPass : Locale.genHidePass});
Tip.updateTip(label[0], { title: this.hide ? Locale.genShowPass : Locale.genHidePass });
this.showPassword();
},

View File

@ -25,17 +25,20 @@ const GrpView = Backbone.View.extend({
render: function() {
this.removeSubView();
if (this.model) {
this.renderTemplate({
title: this.model.get('title'),
icon: this.model.get('icon') || 'folder',
customIcon: this.model.get('customIcon'),
enableSearching: this.model.getEffectiveEnableSearching(),
readonly: this.model.get('top'),
canAutoType: AutoType.enabled,
autoTypeSeq: this.model.get('autoTypeSeq'),
autoTypeEnabled: this.model.getEffectiveEnableAutoType(),
defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq()
}, true);
this.renderTemplate(
{
title: this.model.get('title'),
icon: this.model.get('icon') || 'folder',
customIcon: this.model.get('customIcon'),
enableSearching: this.model.getEffectiveEnableSearching(),
readonly: this.model.get('top'),
canAutoType: AutoType.enabled,
autoTypeSeq: this.model.get('autoTypeSeq'),
autoTypeEnabled: this.model.getEffectiveEnableAutoType(),
defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq()
},
true
);
if (!this.model.get('title')) {
this.$el.find('#grp__field-title').focus();
}
@ -88,8 +91,10 @@ const GrpView = Backbone.View.extend({
focusAutoTypeSeq: function(e) {
if (!this.views.hint) {
this.views.hint = new AutoTypeHintView({input: e.target}).render();
this.views.hint.on('remove', () => { delete this.views.hint; });
this.views.hint = new AutoTypeHintView({ input: e.target }).render();
this.views.hint.on('remove', () => {
delete this.views.hint;
});
}
},

View File

@ -22,12 +22,15 @@ const IconSelectView = Backbone.View.extend({
},
render: function() {
this.renderTemplate({
sel: this.model.iconId,
icons: IconMap,
canDownloadFavicon: !!this.model.url,
customIcons: this.model.file.getCustomIcons()
}, true);
this.renderTemplate(
{
sel: this.model.iconId,
icons: IconMap,
canDownloadFavicon: !!this.model.url,
customIcons: this.model.file.getCustomIcons()
},
true
);
return this;
},
@ -63,13 +66,17 @@ const IconSelectView = Backbone.View.extend({
this.setSpecialImage(img, 'download');
this.$el.find('.icon-select__icon-download img').remove();
this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin');
this.$el.find('.icon-select__icon-download').addClass('icon-select__icon--custom-selected').append(img);
this.$el
.find('.icon-select__icon-download')
.addClass('icon-select__icon--custom-selected')
.append(img);
this.downloadingFavicon = false;
};
img.onerror = e => {
logger.error('Favicon download error: ' + url, e);
this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin');
this.$el.find('.icon-select__icon-download')
this.$el
.find('.icon-select__icon-download')
.removeClass('icon-select__icon--custom-selected')
.addClass('icon-select__icon--download-error');
this.downloadingFavicon = false;
@ -103,7 +110,10 @@ const IconSelectView = Backbone.View.extend({
img.onload = () => {
this.setSpecialImage(img, 'select');
this.$el.find('.icon-select__icon-select img').remove();
this.$el.find('.icon-select__icon-select').addClass('icon-select__icon--custom-selected').append(img);
this.$el
.find('.icon-select__icon-select')
.addClass('icon-select__icon--custom-selected')
.append(img);
};
img.src = e.target.result;
};

View File

@ -65,7 +65,10 @@ const KeyChangeView = Backbone.View.extend({
this.keyFile = null;
this.$el.find('.key-change__keyfile-name').html('');
}
this.$el.find('.key-change__file').val(null).click();
this.$el
.find('.key-change__file')
.val(null)
.click();
this.inputEl.focus();
},

View File

@ -31,18 +31,58 @@ const ListSearchView = Backbone.View.extend({
advancedSearchEnabled: false,
advancedSearch: null,
initialize: function () {
initialize: function() {
this.sortOptions = [
{ value: 'title', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-title', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'website', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-website', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'user', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ) },
{ value: '-user', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA) },
{ value: 'created', icon: 'sort-numeric-asc', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON) },
{ value: '-created', icon: 'sort-numeric-desc', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) },
{ value: 'updated', icon: 'sort-numeric-asc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) },
{ value: '-updated', icon: 'sort-numeric-desc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) },
{
value: 'title',
icon: 'sort-alpha-asc',
loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-title',
icon: 'sort-alpha-desc',
loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'website',
icon: 'sort-alpha-asc',
loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-website',
icon: 'sort-alpha-desc',
loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'user',
icon: 'sort-alpha-asc',
loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-user',
icon: 'sort-alpha-desc',
loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'created',
icon: 'sort-numeric-asc',
loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON)
},
{
value: '-created',
icon: 'sort-numeric-desc',
loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO)
},
{
value: 'updated',
icon: 'sort-numeric-asc',
loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON)
},
{
value: '-updated',
icon: 'sort-numeric-desc',
loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO)
},
{ value: '-attachments', icon: 'sort-amount-desc', loc: () => Locale.searchAttachments },
{ value: '-rank', icon: 'sort-numeric-desc', loc: () => Locale.searchRank }
];
@ -52,11 +92,16 @@ const ListSearchView = Backbone.View.extend({
}, this);
this.views = {};
this.advancedSearch = {
user: true, other: true,
url: true, protect: false,
notes: true, pass: false,
cs: false, regex: false,
history: false, title: true,
user: true,
other: true,
url: true,
protect: false,
notes: true,
pass: false,
cs: false,
regex: false,
history: false,
title: true,
rank: true
};
if (this.model.advancedSearch) {
@ -83,9 +128,16 @@ const ListSearchView = Backbone.View.extend({
},
setLocale: function() {
this.sortOptions.forEach(opt => { opt.text = opt.loc(); });
const entryDesc = FeatureDetector.isMobile ? '' : (' <span class="muted-color">(' + Locale.searchShiftClickOr + ' ' +
FeatureDetector.altShortcutSymbol(true) + 'N)</span>');
this.sortOptions.forEach(opt => {
opt.text = opt.loc();
});
const entryDesc = FeatureDetector.isMobile
? ''
: ' <span class="muted-color">(' +
Locale.searchShiftClickOr +
' ' +
FeatureDetector.altShortcutSymbol(true) +
'N)</span>';
this.createOptions = [
{ value: 'entry', icon: 'key', text: Format.capFirst(Locale.entry) + entryDesc },
{ value: 'group', icon: 'folder', text: Format.capFirst(Locale.group) }
@ -105,7 +157,7 @@ const ListSearchView = Backbone.View.extend({
this.stopListening(KeyHandler, 'keypress', this.documentKeyPress);
},
render: function () {
render: function() {
let searchVal;
if (this.inputEl) {
searchVal = this.inputEl.val();

Some files were not shown because too many files have changed in this diff Show More