This commit is contained in:
antelle 2019-08-18 08:05:38 +02:00
parent 176c2a6edd
commit fa4ff0b0c3
83 changed files with 2507 additions and 1620 deletions

View File

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

View File

@ -22,7 +22,8 @@ module.exports = function(grunt) {
const year = date.getFullYear();
const minElectronVersionForUpdate = '4.0.1';
const zipCommentPlaceholderPart = 'zip_comment_placeholder_that_will_be_replaced_with_hash';
const zipCommentPlaceholder = zipCommentPlaceholderPart + '.'.repeat(512 - zipCommentPlaceholderPart.length);
const zipCommentPlaceholder =
zipCommentPlaceholderPart + '.'.repeat(512 - zipCommentPlaceholderPart.length);
const electronVersion = pkg.dependencies.electron.replace(/^\D/, '');
grunt.initConfig({
@ -124,7 +125,8 @@ module.exports = function(grunt) {
},
'desktop-darwin-installer': {
cwd: 'package/osx/KeeWeb Installer.app',
dest: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Installer/KeeWeb Installer.app',
dest:
'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app/Contents/Installer/KeeWeb Installer.app',
src: '**',
expand: true,
nonull: true,
@ -167,18 +169,30 @@ module.exports = function(grunt) {
manifest: {
options: {
replacements: [
{ pattern: '# YYYY-MM-DD:v0.0.0', replacement: '# ' + dt + ':v' + pkg.version },
{ pattern: '# updmin:v0.0.0', replacement: '# updmin:v' + minElectronVersionForUpdate }
{
pattern: '# YYYY-MM-DD:v0.0.0',
replacement: '# ' + dt + ':v' + pkg.version
},
{
pattern: '# updmin:v0.0.0',
replacement: '# updmin:v' + minElectronVersionForUpdate
}
]
},
files: { 'dist/manifest.appcache': 'app/manifest.appcache' }
},
'manifest-html': {
options: { replacements: [{ pattern: '<html', replacement: '<html manifest="manifest.appcache"' }] },
options: {
replacements: [
{ pattern: '<html', replacement: '<html manifest="manifest.appcache"' }
]
},
files: { 'dist/index.html': 'dist/index.html' }
},
'desktop-html': {
options: { replacements: [{ pattern: ' manifest="manifest.appcache"', replacement: '' }] },
options: {
replacements: [{ pattern: ' manifest="manifest.appcache"', replacement: '' }]
},
files: { 'tmp/desktop/app/index.html': 'dist/index.html' }
},
'desktop-public-key': {
@ -187,7 +201,13 @@ module.exports = function(grunt) {
{
pattern: "'@@PUBLIC_KEY_CONTENT'",
replacement:
'`' + fs.readFileSync('app/resources/public-key.pem', { encoding: 'utf8' }).trim() + '`'
'`' +
fs
.readFileSync('app/resources/public-key.pem', {
encoding: 'utf8'
})
.trim() +
'`'
}
]
},
@ -195,7 +215,12 @@ module.exports = function(grunt) {
},
'cordova-html': {
options: {
replacements: [{ pattern: '<script', replacement: '<script src="cordova.js"></script><script' }]
replacements: [
{
pattern: '<script',
replacement: '<script src="cordova.js"></script><script'
}
]
},
files: { 'tmp/cordova/app/index.html': 'dist/index.html' }
}
@ -305,7 +330,10 @@ module.exports = function(grunt) {
level: 6
},
'desktop-update': {
options: { archive: 'dist/desktop/UpdateDesktop.zip', comment: zipCommentPlaceholder },
options: {
archive: 'dist/desktop/UpdateDesktop.zip',
comment: zipCommentPlaceholder
},
files: [{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }]
},
'win32-x64': {
@ -341,7 +369,12 @@ module.exports = function(grunt) {
window: { size: { width: 658, height: 498 } },
contents: [
{ x: 438, y: 344, type: 'link', path: '/Applications' },
{ x: 192, y: 344, type: 'file', path: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app' }
{
x: 192,
y: 344,
type: 'file',
path: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app'
}
]
},
app: {
@ -479,7 +512,11 @@ 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'
}
@ -505,8 +542,10 @@ module.exports = function(grunt) {
files: {
'tmp/desktop/KeeWeb-win32-x64/KeeWeb.exe': 'KeeWeb',
'tmp/desktop/KeeWeb-win32-x64/ffmpeg.dll': '',
'tmp/desktop/KeeWeb-win32-x64/libEGL.dll': 'ANGLE libEGL Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-x64/libGLESv2.dll': 'ANGLE libGLESv2 Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-x64/libEGL.dll':
'ANGLE libEGL Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-x64/libGLESv2.dll':
'ANGLE libGLESv2 Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-x64/osmesa.dll': ''
}
}
@ -516,8 +555,10 @@ module.exports = function(grunt) {
files: {
'tmp/desktop/KeeWeb-win32-ia32/KeeWeb.exe': 'KeeWeb',
'tmp/desktop/KeeWeb-win32-ia32/ffmpeg.dll': '',
'tmp/desktop/KeeWeb-win32-ia32/libEGL.dll': 'ANGLE libEGL Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-ia32/libGLESv2.dll': 'ANGLE libGLESv2 Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-ia32/libEGL.dll':
'ANGLE libEGL Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-ia32/libGLESv2.dll':
'ANGLE libGLESv2 Dynamic Link Library',
'tmp/desktop/KeeWeb-win32-ia32/osmesa.dll': ''
}
}
@ -557,7 +598,10 @@ module.exports = function(grunt) {
sign: 'dist/desktop/Verify.sign.sha256'
},
files: {
'dist/desktop/Verify.sha256': ['dist/desktop/KeeWeb-*', 'dist/desktop/UpdateDesktop.zip']
'dist/desktop/Verify.sha256': [
'dist/desktop/KeeWeb-*',
'dist/desktop/UpdateDesktop.zip'
]
}
}
}

View File

@ -1,40 +1,74 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>KeeWeb</title>
<meta name="application-name" content="KeeWeb">
<meta name="kw-signature" content="">
<meta name="kw-config" content="(no-config)">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="KeeWeb">
<meta name="theme-color" content="#6386ec">
<meta name="msapplication-config" content="browserconfig.xml">
<meta name="msapplication-TileColor" content="#6386ec">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="mask-icon" href="icons/safari-pinned-tab.svg" color="#6386ec">
<link rel="apple-touch-startup-image" href="icons/splash-640x1136.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-750x1294.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-1242x2148.png" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-1536x2048.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-1668x2224.png" media="(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="icons/splash-2048x2732.png" media="(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="css/app.css?__inline=true" />
<script src="js/vendor.js?__inline=true"></script>
<script src="js/app.js?__inline=true"></script>
<script src="js/runtime.js?__inline=true"></script>
</head>
<body class="th-d">
<noscript>
<h1>KeeWeb</h1>
<p>KeeWeb is a password manager written in JavaScript. Please enable JavaScript to run it.</p>
</noscript>
</body>
<head lang="en">
<meta charset="UTF-8" />
<title>KeeWeb</title>
<meta name="application-name" content="KeeWeb" />
<meta name="kw-signature" content="" />
<meta name="kw-config" content="(no-config)" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="KeeWeb" />
<meta name="theme-color" content="#6386ec" />
<meta name="msapplication-config" content="browserconfig.xml" />
<meta name="msapplication-TileColor" content="#6386ec" />
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="mask-icon" href="icons/safari-pinned-tab.svg" color="#6386ec" />
<link
rel="apple-touch-startup-image"
href="icons/splash-640x1136.png"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-750x1294.png"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-1242x2148.png"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-1125x2436.png"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-1536x2048.png"
media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-1668x2224.png"
media="(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="icons/splash-2048x2732.png"
media="(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="css/app.css?__inline=true" />
<script src="js/vendor.js?__inline=true"></script>
<script src="js/app.js?__inline=true"></script>
<script src="js/runtime.js?__inline=true"></script>
</head>
<body class="th-d">
<noscript>
<h1>KeeWeb</h1>
<p>
KeeWeb is a password manager written in JavaScript. Please enable JavaScript to run
it.
</p>
</noscript>
</body>
</html>

View File

@ -1,21 +1,21 @@
{
"name": "KeeWeb",
"short_name": "KeeWeb",
"description": "Free cross-platform password manager compatible with KeePass",
"display": "standalone",
"orientation": "any",
"theme_color": "#6386ec",
"background_color": "#6386ec",
"icons": [
{
"src": "icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
"name": "KeeWeb",
"short_name": "KeeWeb",
"description": "Free cross-platform password manager compatible with KeePass",
"display": "standalone",
"orientation": "any",
"theme_color": "#6386ec",
"background_color": "#6386ec",
"icons": [
{
"src": "icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -116,7 +116,8 @@ ready(() => {
function showApp() {
return Promise.resolve().then(() => {
const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning');
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) {

View File

@ -21,7 +21,9 @@ AutoTypeFilter.prototype.getEntries = function() {
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' });
};

View File

@ -299,7 +299,14 @@ AutoTypeRunner.prototype.getEntryGroupName = function() {
AutoTypeRunner.prototype.dt = function(part) {
switch (part) {
case 'simple':
return this.dt('Y') + this.dt('M') + this.dt('D') + this.dt('h') + this.dt('m') + this.dt('s');
return (
this.dt('Y') +
this.dt('M') +
this.dt('D') +
this.dt('h') +
this.dt('m') +
this.dt('s')
);
case 'Y':
return this.now.getFullYear().toString();
case 'M':
@ -320,7 +327,14 @@ AutoTypeRunner.prototype.dt = function(part) {
AutoTypeRunner.prototype.udt = function(part) {
switch (part) {
case 'simple':
return this.udt('Y') + this.udt('M') + this.udt('D') + this.udt('h') + this.udt('m') + this.udt('s');
return (
this.udt('Y') +
this.udt('M') +
this.udt('D') +
this.udt('h') +
this.udt('m') +
this.udt('s')
);
case 'Y':
return this.now.getUTCFullYear().toString();
case 'M':

View File

@ -42,7 +42,10 @@ const AppRightsChecker = {
'<br/>' +
Locale.appRightsAlertBody2 +
`: <pre>${command}</pre>`,
buttons: [{ result: 'skip', title: Locale.alertDoNotAsk, error: true }, Alerts.buttons.ok],
buttons: [
{ result: 'skip', title: Locale.alertDoNotAsk, error: true },
Alerts.buttons.ok
],
success: result => {
if (result === 'skip') {
this.dontAskAnymore();

View File

@ -11,7 +11,9 @@ const FeatureTester = {
checkWebAssembly() {
try {
const module = new global.WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
const module = new global.WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
return new global.WebAssembly.Instance(module) instanceof global.WebAssembly.Instance;
} catch (e) {
throw 'WebAssembly is not supported';
@ -29,7 +31,10 @@ const FeatureTester = {
.importKey(kdbxweb.ByteUtils.hexToBytes(key))
.then(() => {
return aesCbc
.encrypt(kdbxweb.ByteUtils.hexToBytes(data), kdbxweb.ByteUtils.hexToBytes(iv))
.encrypt(
kdbxweb.ByteUtils.hexToBytes(data),
kdbxweb.ByteUtils.hexToBytes(iv)
)
.then(res => {
if (kdbxweb.ByteUtils.bytesToHex(res) !== exp) {
throw 'AES is not working properly';

View File

@ -3,13 +3,26 @@ 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: 'Pronounceable',
title: Locale.genPresetPronounceable,
length: 10,
lower: true,
upper: true
},
{
name: 'Med',
title: Locale.genPresetMed,
@ -21,11 +34,37 @@ const GeneratorPresets = {
brackets: true,
ambiguous: true
},
{ name: 'Long', title: Locale.genPresetLong, length: 32, upper: true, lower: true, digits: 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: '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
}
];
},

View File

@ -16,7 +16,13 @@ const KeyHandler = {
$(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 }
{
handler: this.handleAKey,
thisArg: this,
shortcut: this.SHORTCUT_ACTION,
modal: true,
noPrevent: true
}
];
},
onKey: function(key, handler, thisArg, shortcut, modal, noPrevent) {
@ -107,7 +113,10 @@ const KeyHandler = {
IdleTracker.regUserAction();
},
handleAKey: function(e) {
if (e.target.tagName.toLowerCase() === 'input' && ['password', 'text'].indexOf(e.target.type) >= 0) {
if (
e.target.tagName.toLowerCase() === 'input' &&
['password', 'text'].indexOf(e.target.type) >= 0
) {
e.stopImmediatePropagation();
} else {
e.preventDefault();

View File

@ -92,7 +92,9 @@ const Launcher = {
reader.onerror = callback;
reader.onloadend = () => {
const contents = new Uint8Array(reader.result);
callback(encoding ? String.fromCharCode.apply(null, contents) : contents);
callback(
encoding ? String.fromCharCode.apply(null, contents) : contents
);
};
reader.readAsArrayBuffer(file);
},

View File

@ -16,7 +16,10 @@ const OtpQrReader = {
read: function() {
let screenshotKey = FeatureDetector.screenshotToClipboardShortcut();
if (screenshotKey) {
screenshotKey = Locale.detSetupOtpAlertBodyWith.replace('{}', '<code>' + screenshotKey + '</code>');
screenshotKey = Locale.detSetupOtpAlertBodyWith.replace(
'{}',
'<code>' + screenshotKey + '</code>'
);
}
const pasteKey = FeatureDetector.isMobile
? ''
@ -95,7 +98,10 @@ const OtpQrReader = {
},
pasteEvent: function(e) {
const item = _.find(e.clipboardData.items, item => item.kind === 'file' && item.type.indexOf('image') !== -1);
const item = _.find(
e.clipboardData.items,
item => item.kind === 'file' && item.type.indexOf('image') !== -1
);
if (!item) {
logger.debug('Paste without file');
return;
@ -135,7 +141,10 @@ const OtpQrReader = {
Alerts.error({
header: Locale.detOtpQrWrong,
body:
Locale.detOtpQrWrongBody + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
Locale.detOtpQrWrongBody +
'<pre class="modal__pre">' +
_.escape(err.toString()) +
'</pre>'
});
}
} catch (e) {

View File

@ -85,12 +85,22 @@ const PopupNotifier = {
win.close();
win = null;
});
win.webContents.on('did-fail-load', (e, errorCode, errorDescription, validatedUrl, isMainFrame) => {
this.logger.debug('did-fail-load', e, errorCode, errorDescription, validatedUrl, isMainFrame);
this.deferCheckClosed(win);
win.close();
win = null;
});
win.webContents.on(
'did-fail-load',
(e, errorCode, errorDescription, validatedUrl, isMainFrame) => {
this.logger.debug(
'did-fail-load',
e,
errorCode,
errorDescription,
validatedUrl,
isMainFrame
);
this.deferCheckClosed(win);
win.close();
win = null;
}
);
win.once('page-title-updated', () => {
setTimeout(() => {
if (win) {
@ -100,7 +110,10 @@ const PopupNotifier = {
}, Timeouts.PopupWaitTime);
});
win.on('closed', () => {
setTimeout(PopupNotifier.triggerClosed.bind(PopupNotifier, win), Timeouts.CheckWindowClosed);
setTimeout(
PopupNotifier.triggerClosed.bind(PopupNotifier, win),
Timeouts.CheckWindowClosed
);
win = null;
});
win.loadURL(url);
@ -109,7 +122,10 @@ 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) {
@ -127,7 +143,10 @@ const PopupNotifier = {
checkClosed: function(win) {
if (win.closed) {
setTimeout(PopupNotifier.triggerClosed.bind(PopupNotifier, win), Timeouts.CheckWindowClosed);
setTimeout(
PopupNotifier.triggerClosed.bind(PopupNotifier, win),
Timeouts.CheckWindowClosed
);
} else {
PopupNotifier.deferCheckClosed(win);
}

View File

@ -20,7 +20,10 @@ const SingleInstanceChecker = {
return;
}
if (e.key === LocalStorageKeyName && e.newValue !== instanceKey) {
SingleInstanceChecker.setKey(LocalStorageResponseKeyName, instanceKey + Math.random().toString());
SingleInstanceChecker.setKey(
LocalStorageResponseKeyName,
instanceKey + Math.random().toString()
);
} else if (e.key === LocalStorageResponseKeyName && e.newValue.indexOf(instanceKey) < 0) {
window.removeEventListener('storage', SingleInstanceChecker.storageChanged);
Backbone.trigger('second-instance');

View File

@ -41,7 +41,10 @@ const Updater = {
init: function() {
this.scheduleNextCheck();
if (!Launcher && window.applicationCache) {
window.applicationCache.addEventListener('updateready', this.checkAppCacheUpdateReady.bind(this));
window.applicationCache.addEventListener(
'updateready',
this.checkAppCacheUpdateReady.bind(this)
);
this.checkAppCacheUpdateReady();
}
},
@ -75,7 +78,9 @@ const Updater = {
// additional protection from broken program logic, to ensure that auto-checks are not performed more than once an hour
const diffMs = new Date() - this.updateCheckDate;
if (isNaN(diffMs) || diffMs < 1000 * 60 * 60) {
logger.error('Prevented update check; last check was performed at ' + this.updateCheckDate);
logger.error(
'Prevented update check; last check was performed at ' + this.updateCheckDate
);
this.scheduleNextCheck();
return;
}
@ -91,7 +96,11 @@ const Updater = {
logger.info('Update check: ' + (match ? match[0] : 'unknown'));
if (!match) {
const errMsg = 'No version info found';
UpdateModel.instance.set({ status: 'error', lastCheckDate: dt, lastCheckError: errMsg });
UpdateModel.instance.set({
status: 'error',
lastCheckDate: dt,
lastCheckError: errMsg
});
UpdateModel.instance.save();
this.scheduleNextCheck();
return;
@ -121,7 +130,12 @@ const Updater = {
}
if (!startedByUser && this.getAutoUpdateType() === 'install') {
this.update(startedByUser);
} else if (SemVer.compareVersions(UpdateModel.instance.get('lastVersion'), RuntimeInfo.version) > 0) {
} else if (
SemVer.compareVersions(
UpdateModel.instance.get('lastVersion'),
RuntimeInfo.version
) > 0
) {
UpdateModel.instance.set('updateStatus', 'found');
}
},
@ -172,7 +186,10 @@ const Updater = {
this.extractAppUpdate(filePath, err => {
if (err) {
logger.error('Error extracting update', err);
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error extracting update' });
UpdateModel.instance.set({
updateStatus: 'error',
updateError: 'Error extracting update'
});
} else {
UpdateModel.instance.set({ updateStatus: 'ready', updateError: null });
if (!startedByUser) {
@ -186,7 +203,10 @@ const Updater = {
},
error: function(e) {
logger.error('Error downloading update', e);
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error downloading update' });
UpdateModel.instance.set({
updateStatus: 'error',
updateError: 'Error downloading update'
});
}
});
},

View File

@ -13,7 +13,20 @@
"November",
"December"
],
"monthsShort": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
"monthsShort": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"weekdays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
"weekdaysShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,9 @@ 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], {
@ -36,7 +38,10 @@ const Copyable = {
tip.hide();
}
this.fieldCopyTip = null;
if (e.source.model.name === '$Password' && AppSettingsModel.instance.get('lockOnCopy')) {
if (
e.source.model.name === '$Password' &&
AppSettingsModel.instance.get('lockOnCopy')
) {
setTimeout(() => {
Backbone.trigger('lock-workspace');
}, Timeouts.BeforeAutoLock);

View File

@ -84,7 +84,11 @@ const AppModel = Backbone.Model.extend({
this.appLogger.error('Invalid app config, no settings section', response);
return reject('Invalid app config, no settings section');
}
this.appLogger.info('Loaded app config from', configLocation, this.appLogger.ts(ts));
this.appLogger.info(
'Loaded app config from',
configLocation,
this.appLogger.ts(ts)
);
resolve(response);
});
xhr.addEventListener('error', () => {
@ -197,7 +201,13 @@ const AppModel = Backbone.Model.extend({
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 };
return {
title: tag,
icon: 'tag',
filterKey: 'tag',
filterValue: tag,
editable: true
};
})
);
} else {
@ -341,12 +351,15 @@ const AppModel = Backbone.Model.extend({
completeUserNames: function(part) {
const userNames = {};
this.files.forEach(file => {
file.forEachEntry({ text: part, textLower: part.toLowerCase(), advanced: { user: true } }, entry => {
const userName = entry.user;
if (userName) {
userNames[userName] = (userNames[userName] || 0) + 1;
file.forEachEntry(
{ text: part, textLower: part.toLowerCase(), advanced: { user: true } },
entry => {
const userName = entry.user;
if (userName) {
userNames[userName] = (userNames[userName] || 0) + 1;
}
}
});
);
});
const matches = _.pairs(userNames);
matches.sort((x, y) => y[1] - x[1]);
@ -483,11 +496,20 @@ const AppModel = Backbone.Model.extend({
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))) {
logger.info('Open file from cache because ' + (err ? 'stat error' : 'it is latest'), err);
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) {
logger.info('Open file from storage (' + stat.rev + ', local ' + cacheRev + ')');
logger.info(
'Open file from storage (' + stat.rev + ', local ' + cacheRev + ')'
);
storageLoad();
} else {
logger.info('Stat error', err);
@ -534,7 +556,10 @@ const AppModel = Backbone.Model.extend({
params.keyFileName = fileInfo.get('keyFileName');
if (this.settings.get('rememberKeyFiles') === 'data') {
params.keyFileData = FileModel.createKeyFileWithHash(fileInfo.get('keyFileHash'));
} else if (this.settings.get('rememberKeyFiles') === 'path' && fileInfo.get('keyFilePath')) {
} else if (
this.settings.get('rememberKeyFiles') === 'path' &&
fileInfo.get('keyFilePath')
) {
params.keyFilePath = fileInfo.get('keyFilePath');
if (Storage.file.enabled) {
needLoadKeyFile = true;
@ -731,7 +756,11 @@ const AppModel = Backbone.Model.extend({
const storage = options.storage || file.get('storage');
let path = options.path || file.get('path');
const opts = options.opts || file.get('opts');
if (storage && Storage[storage].getPathForName && (!path || storage !== file.get('storage'))) {
if (
storage &&
Storage[storage].getPathForName &&
(!path || storage !== file.get('storage'))
) {
path = Storage[storage].getPathForName(file.get('name'));
}
const optionsForLogging = _.clone(options);
@ -856,7 +885,8 @@ const AppModel = Backbone.Model.extend({
};
const saveToStorage = data => {
logger.info('Save data to storage');
const storageRev = fileInfo.get('storage') === storage ? fileInfo.get('rev') : undefined;
const storageRev =
fileInfo.get('storage') === storage ? fileInfo.get('rev') : undefined;
Storage[storage].save(
path,
opts,
@ -927,7 +957,10 @@ const AppModel = Backbone.Model.extend({
if (!e) {
file.set('dirty', false);
}
logger.info('Saved to cache, exit with error', err || 'no error');
logger.info(
'Saved to cache, exit with error',
err || 'no error'
);
complete(err);
});
}

View File

@ -114,7 +114,9 @@ const EntryModel = Backbone.Model.extend({
this.customIcon = null;
this.customIconId = null;
if (this.entry.customIcon) {
this.customIcon = IconUrl.toDataUrl(this.file.db.meta.customIcons[this.entry.customIcon]);
this.customIcon = IconUrl.toDataUrl(
this.file.db.meta.customIcons[this.entry.customIcon]
);
this.customIconId = this.entry.customIcon.toString();
}
},
@ -130,7 +132,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.entry.autoType.obfuscation ===
kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
this.autoTypeSequence = this.entry.autoType.defaultSequence;
this.autoTypeWindows = this.entry.autoType.items.map(this._convertAutoTypeItem);
},
@ -201,8 +204,12 @@ const EntryModel = Backbone.Model.extend({
!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.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))
);
},
@ -701,8 +708,10 @@ const EntryModel = Backbone.Model.extend({
_.forEach(ranking, rankingEntry => {
if (this._getFieldString(rankingEntry.field).toLowerCase() !== '') {
const calculatedRank =
Ranking.getStringRank(searchString, this._getFieldString(rankingEntry.field).toLowerCase()) *
rankingEntry.multiplicator;
Ranking.getStringRank(
searchString,
this._getFieldString(rankingEntry.field).toLowerCase()
) * rankingEntry.multiplicator;
rank += calculatedRank;
}
});

View File

@ -71,8 +71,14 @@ const FileModel = Backbone.Model.extend({
callback();
})
.catch(err => {
if (err.code === kdbxweb.Consts.ErrorCodes.InvalidKey && password && !password.byteLength) {
logger.info('Error opening file with empty password, try to open with null password');
if (
err.code === kdbxweb.Consts.ErrorCodes.InvalidKey &&
password &&
!password.byteLength
) {
logger.info(
'Error opening file with empty password, try to open with null password'
);
return this.open(null, fileData, keyFileData, callback);
}
logger.error('Error opening file', err.code, err.message, err);
@ -139,7 +145,9 @@ const FileModel = Backbone.Model.extend({
openDemo: function(callback) {
const password = kdbxweb.ProtectedValue.fromString('demo');
const credentials = new kdbxweb.Credentials(password);
const demoFile = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(demoFileData));
const demoFile = kdbxweb.ByteUtils.arrayToBuffer(
kdbxweb.ByteUtils.base64ToBytes(demoFileData)
);
kdbxweb.Kdbx.load(demoFile, credentials).then(db => {
this.db = db;
this.set('name', 'Demo');
@ -334,7 +342,9 @@ const FileModel = Backbone.Model.extend({
forEachEntry: function(filter, callback) {
let top = this;
if (filter.trash) {
top = this.getGroup(this.db.meta.recycleBinUuid ? this.subId(this.db.meta.recycleBinUuid.id) : null);
top = this.getGroup(
this.db.meta.recycleBinUuid ? this.subId(this.db.meta.recycleBinUuid.id) : null
);
} else if (filter.group) {
top = this.getGroup(filter.group);
}
@ -359,11 +369,15 @@ const FileModel = Backbone.Model.extend({
},
getTrashGroup: function() {
return this.db.meta.recycleBinEnabled ? this.getGroup(this.subId(this.db.meta.recycleBinUuid.id)) : null;
return this.db.meta.recycleBinEnabled
? this.getGroup(this.subId(this.db.meta.recycleBinUuid.id))
: null;
},
getEntryTemplatesGroup: function() {
return this.db.meta.entryTemplatesGroup ? this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id)) : null;
return this.db.meta.entryTemplatesGroup
? this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id))
: null;
},
createEntryTemplatesGroup: function() {
@ -618,7 +632,9 @@ const FileModel = Backbone.Model.extend({
addCustomIcon: function(iconData) {
const uuid = kdbxweb.KdbxUuid.random();
this.db.meta.customIcons[uuid] = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(iconData));
this.db.meta.customIcons[uuid] = kdbxweb.ByteUtils.arrayToBuffer(
kdbxweb.ByteUtils.base64ToBytes(iconData)
);
return uuid.toString();
},

View File

@ -124,7 +124,8 @@ const GroupModel = MenuItemModel.extend({
let result = true;
this.get('items').forEach(group => {
if (group.matches(filter)) {
result = callback(group) !== false && group.forEachGroup(callback, filter) !== false;
result =
callback(group) !== false && group.forEachGroup(callback, filter) !== false;
}
});
return result;
@ -260,7 +261,9 @@ const GroupModel = MenuItemModel.extend({
},
getParentEffectiveAutoTypeSeq: function() {
return this.parentGroup ? this.parentGroup.getEffectiveAutoTypeSeq() : DefaultAutoTypeSequence;
return this.parentGroup
? this.parentGroup.getEffectiveAutoTypeSeq()
: DefaultAutoTypeSequence;
},
isEntryTemplatesGroup: function() {
@ -315,7 +318,12 @@ const GroupModel = MenuItemModel.extend({
},
moveToTop: function(object) {
if (!object || object.id === this.id || object.file !== this.file || !(object instanceof GroupModel)) {
if (
!object ||
object.id === this.id ||
object.file !== this.file ||
!(object instanceof GroupModel)
) {
return;
}
this.file.setModified();

View File

@ -17,7 +17,13 @@ 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: '*' }
{
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();
@ -49,7 +55,11 @@ const MenuModel = Backbone.Model.extend({
Colors.AllColors.forEach(color => {
this.colorsSection
.get('items')
.models[0].addOption({ cls: 'fa ' + color + '-color', value: color, filterValue: color });
.models[0].addOption({
cls: 'fa ' + color + '-color',
value: color,
filterValue: color
});
});
this.menus.app = new MenuSectionCollection([
this.allItemsSection,
@ -65,9 +75,15 @@ const MenuModel = Backbone.Model.extend({
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' }]);
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' }
]);
this.filesSection = new MenuSectionModel();
this.filesSection.set({ scrollable: true, grow: true });
this.menus.settings = new MenuSectionCollection([
@ -93,7 +109,10 @@ const MenuModel = Backbone.Model.extend({
}, 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' : '';
const selColor =
sel.item === this.colorsItem && sel.option
? sel.option.get('value') + '-color'
: '';
this.colorsItem.set('cls', 'menu__item-colors ' + selColor);
const filterKey = sel.item.get('filterKey');
const filterValue = (sel.option || sel.item).get('filterValue');
@ -101,7 +120,10 @@ const MenuModel = Backbone.Model.extend({
filter[filterKey] = filterValue;
Backbone.trigger('set-filter', filter);
} else if (sections === this.menus.settings) {
Backbone.trigger('set-page', { page: sel.item.get('page'), file: sel.item.get('file') });
Backbone.trigger('set-page', {
page: sel.item.get('page'),
file: sel.item.get('file')
});
}
},
@ -186,7 +208,11 @@ const MenuModel = Backbone.Model.extend({
title: Format.capFirst(Locale.tags),
icon: 'tags',
defaultItem: true,
disabled: { header: Locale.menuAlertNoTags, body: Locale.menuAlertNoTagsBody, icon: 'tags' }
disabled: {
header: Locale.menuAlertNoTags,
body: Locale.menuAlertNoTagsBody,
icon: 'tags'
}
};
},

View File

@ -38,7 +38,10 @@ const PluginGallery = {
this.loading = false;
this.loadError = !gallery;
if (gallery) {
this.logger.debug(`Loaded ${gallery.plugins.length} plugins`, this.logger.ts(ts));
this.logger.debug(
`Loaded ${gallery.plugins.length} plugins`,
this.logger.ts(ts)
);
this.gallery = gallery;
this.saveGallery(gallery);
}
@ -50,7 +53,10 @@ const PluginGallery = {
verifySignature(gallery) {
const dataToVerify = JSON.stringify(gallery, null, 2).replace(gallery.signature, '');
return SignatureVerifier.verify(kdbxweb.ByteUtils.stringToBytes(dataToVerify), gallery.signature)
return SignatureVerifier.verify(
kdbxweb.ByteUtils.stringToBytes(dataToVerify),
gallery.signature
)
.then(isValid => {
if (isValid) {
return gallery;

View File

@ -164,7 +164,9 @@ const PluginManager = Backbone.Model.extend({
return Promise.resolve();
}
const anotherVersion = this.get('autoUpdateAppVersion') !== RuntimeInfo.version;
const wasLongAgo = !this.get('autoUpdateDate') || Date.now() - this.get('autoUpdateDate') > this.UpdateInterval;
const wasLongAgo =
!this.get('autoUpdateDate') ||
Date.now() - this.get('autoUpdateDate') > this.UpdateInterval;
const autoUpdateRequired = anotherVersion || wasLongAgo;
if (!autoUpdateRequired) {
return;
@ -194,7 +196,9 @@ 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 galleryPlugin = gallery
? gallery.plugins.find(pl => pl.manifest.name === desc.manifest.name)
: null;
const expectedPublicKey = galleryPlugin
? galleryPlugin.manifest.publicKey
: SignatureVerifier.getPublicKey();

View File

@ -99,7 +99,12 @@ const Plugin = Backbone.Model.extend(
if (manifest.manifestVersion !== '0.1.0') {
return 'Invalid manifest version ' + manifest.manifestVersion;
}
if (!manifest.author || !manifest.author.email || !manifest.author.name || !manifest.author.url) {
if (
!manifest.author ||
!manifest.author.email ||
!manifest.author.name ||
!manifest.author.url
) {
return 'Invalid plugin author';
}
if (!manifest.url) {
@ -108,7 +113,10 @@ const Plugin = Backbone.Model.extend(
if (!manifest.publicKey) {
return 'No plugin public key';
}
if (!this.get('skipSignatureValidation') && manifest.publicKey !== SignatureVerifier.getPublicKey()) {
if (
!this.get('skipSignatureValidation') &&
manifest.publicKey !== SignatureVerifier.getPublicKey()
) {
return 'Public key mismatch';
}
if (!manifest.resources || !Object.keys(manifest.resources).length) {
@ -116,7 +124,9 @@ const Plugin = Backbone.Model.extend(
}
if (
manifest.resources.loc &&
(!manifest.locale || !manifest.locale.title || !/^[a-z]{2}(-[A-Z]{2})?$/.test(manifest.locale.name))
(!manifest.locale ||
!manifest.locale.title ||
!/^[a-z]{2}(-[A-Z]{2})?$/.test(manifest.locale.name))
) {
return 'Bad plugin locale';
}
@ -160,7 +170,9 @@ const Plugin = Backbone.Model.extend(
);
this.resources = {};
const ts = this.logger.ts();
const results = Object.keys(manifest.resources).map(res => this.loadResource(res, local));
const results = Object.keys(manifest.resources).map(res =>
this.loadResource(res, local)
);
return Promise.all(results)
.catch(() => {
throw 'Error loading plugin resources';
@ -328,7 +340,10 @@ const Plugin = Backbone.Model.extend(
}
}
if (badSelectors.length) {
this.logger.error('Themes must not add rules outside theme namespace. Bad selectors:', badSelectors);
this.logger.error(
'Themes must not add rules outside theme namespace. Bad selectors:',
badSelectors
);
throw 'Invalid theme';
}
},
@ -425,7 +440,8 @@ const Plugin = Backbone.Model.extend(
if (!settings) {
settings = {};
}
settings[key.replace(settingPrefix, '')] = AppSettingsModel.instance.attributes[key];
settings[key.replace(settingPrefix, '')] =
AppSettingsModel.instance.attributes[key];
}
}
if (settings) {
@ -461,7 +477,10 @@ const Plugin = Backbone.Model.extend(
disable() {
const manifest = this.get('manifest');
this.logger.info('Disabling plugin with resources', Object.keys(manifest.resources).join(', '));
this.logger.info(
'Disabling plugin with resources',
Object.keys(manifest.resources).join(', ')
);
this.set('status', this.STATUS_UNINSTALLING);
const ts = this.logger.ts();
return Promise.resolve().then(() => {
@ -491,15 +510,26 @@ const Plugin = Backbone.Model.extend(
const manifest = this.get('manifest');
const newManifest = newPlugin.get('manifest');
if (manifest.version === newManifest.version) {
this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: null });
this.set({
status: prevStatus,
updateCheckDate: Date.now(),
updateError: null
});
this.logger.info(`v${manifest.version} is the latest plugin version`);
return;
}
this.logger.info(`Updating plugin from v${manifest.version} to v${newManifest.version}`);
const error = newPlugin.validateManifest() || this.validateUpdatedManifest(newManifest);
this.logger.info(
`Updating plugin from v${manifest.version} to v${newManifest.version}`
);
const error =
newPlugin.validateManifest() || this.validateUpdatedManifest(newManifest);
if (error) {
this.logger.error('Manifest validation error', error);
this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: error });
this.set({
status: prevStatus,
updateCheckDate: Date.now(),
updateError: error
});
throw 'Plugin validation error: ' + error;
}
this.uninstallPluginCode();
@ -527,7 +557,11 @@ const Plugin = Backbone.Model.extend(
throw err;
});
} else {
this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: err });
this.set({
status: prevStatus,
updateCheckDate: Date.now(),
updateError: err
});
throw err;
}
});
@ -555,7 +589,9 @@ const Plugin = Backbone.Model.extend(
if (settings instanceof Array) {
return settings.map(setting => {
setting = _.clone(setting);
const value = AppSettingsModel.instance.get(settingsPrefix + setting.name);
const value = AppSettingsModel.instance.get(
settingsPrefix + setting.name
);
if (value !== undefined) {
setting.value = value;
}

View File

@ -27,7 +27,9 @@ EntryPresenter.prototype = {
return this.entry ? this.entry.customIcon : undefined;
},
get color() {
return this.entry ? this.entry.color || (this.entry.customIcon ? this.noColor : undefined) : undefined;
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');
@ -76,7 +78,10 @@ EntryPresenter.prototype = {
case 'updated':
return this.updated;
case 'attachments':
return this.entry.attachments.map(a => a.title).join(', ') || '(' + Locale.listNoAttachments + ')';
return (
this.entry.attachments.map(a => a.title).join(', ') ||
'(' + Locale.listNoAttachments + ')'
);
default:
return this.user || this.notes || this.url;
}

View File

@ -72,7 +72,10 @@ _.extend(StorageBase.prototype, {
} else {
config.tryNum = (config.tryNum || 0) + 1;
if (config.tryNum >= MaxRequestRetries) {
this.logger.info('Too many authorize attempts, fail request', config.url);
this.logger.info(
'Too many authorize attempts, fail request',
config.url
);
return config.error && config.error('unauthorized', xhr);
}
this.logger.info('Repeat request, try #' + config.tryNum, config.url);

View File

@ -197,7 +197,10 @@ const StorageDropbox = StorageBase.extend({
},
_encodeJsonHttpHeader(json) {
return json.replace(/[\u007f-\uffff]/g, c => '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4));
return json.replace(
/[\u007f-\uffff]/g,
c => '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4)
);
},
_apiCall: function(args) {
@ -209,7 +212,9 @@ const StorageDropbox = StorageBase.extend({
let headers;
let data = args.data;
if (args.apiArg) {
headers = { 'Dropbox-API-Arg': this._encodeJsonHttpHeader(JSON.stringify(args.apiArg)) };
headers = {
'Dropbox-API-Arg': this._encodeJsonHttpHeader(JSON.stringify(args.apiArg))
};
if (args.data) {
headers['Content-Type'] = 'application/octet-stream';
}
@ -274,7 +279,12 @@ const StorageDropbox = StorageBase.extend({
} else if (stat['.tag'] === 'folder') {
stat = { folder: true };
}
this.logger.debug('Stated', path, stat.folder ? 'folder' : stat.rev, this.logger.ts(ts));
this.logger.debug(
'Stated',
path,
stat.folder ? 'folder' : stat.rev,
this.logger.ts(ts)
);
if (callback) {
callback(null, stat);
}

View File

@ -32,7 +32,9 @@ const StorageGDrive = StorageBase.extend({
const ts = this.logger.ts();
const url =
this._baseUrl +
'/files/{id}/revisions/{rev}?alt=media'.replace('{id}', path).replace('{rev}', stat.rev);
'/files/{id}/revisions/{rev}?alt=media'
.replace('{id}', path)
.replace('{rev}', stat.rev);
this._xhr({
url: url,
responseType: 'arraybuffer',
@ -94,7 +96,9 @@ const StorageGDrive = StorageBase.extend({
const isNew = path.lastIndexOf(NewFileIdPrefix, 0) === 0;
let url;
if (isNew) {
url = this._baseUrlUpload + '/files?uploadType=multipart&fields=id,headRevisionId';
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(
@ -137,7 +141,10 @@ const StorageGDrive = StorageBase.extend({
if (!newRev) {
return callback && callback('save error: no rev');
}
return callback && callback(null, { rev: newRev, path: isNew ? response.id : null });
return (
callback &&
callback(null, { rev: newRev, path: isNew ? response.id : null })
);
},
error: err => {
this.logger.error('Save error', path, err, this.logger.ts(ts));
@ -154,12 +161,20 @@ const StorageGDrive = StorageBase.extend({
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(
'{fields}',
encodeURIComponent('files(id,name,mimeType,headRevisionId)')
)
.replace('{q}', encodeURIComponent(query));
const ts = this.logger.ts();
this._xhr({
@ -225,7 +240,10 @@ const StorageGDrive = StorageBase.extend({
_getOAuthConfig: function() {
let clientId = this.appSettings.get('gdriveClientId');
if (!clientId) {
clientId = location.origin.indexOf('localhost') >= 0 ? GDriveClientId.Local : GDriveClientId.Production;
clientId =
location.origin.indexOf('localhost') >= 0
? GDriveClientId.Local
: GDriveClientId.Production;
}
return {
scope: 'https://www.googleapis.com/auth/drive',

View File

@ -43,7 +43,13 @@ const StorageOneDrive = StorageBase.extend({
const downloadUrl = response['@microsoft.graph.downloadUrl'];
let rev = response.eTag;
if (!downloadUrl || !response.eTag) {
this.logger.debug('Load error', path, 'no download url', response, this.logger.ts(ts));
this.logger.debug(
'Load error',
path,
'no download url',
response,
this.logger.ts(ts)
);
return callback && callback('no download url');
}
this._xhr({
@ -233,7 +239,10 @@ const StorageOneDrive = StorageBase.extend({
_getClientId: function() {
let clientId = this.appSettings.get('onedriveClientId');
if (!clientId) {
clientId = location.origin.indexOf('localhost') >= 0 ? OneDriveClientId.Local : OneDriveClientId.Production;
clientId =
location.origin.indexOf('localhost') >= 0
? OneDriveClientId.Local
: OneDriveClientId.Production;
}
return clientId;
},
@ -254,7 +263,10 @@ const StorageOneDrive = StorageBase.extend({
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')) {
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"]';
if (document.querySelectorAll(selector).length === 1) document.querySelector(selector).click()`;

View File

@ -146,23 +146,46 @@ const StorageWebDav = StorageBase.extend({
(err, xhr, stat) => {
if (err) {
that._request(
_.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts)
_.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.logger.debug(
'Save error',
path,
'rev conflict',
stat.rev,
rev
);
that._request(
_.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts)
_.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;
movePath =
location.protocol + '//' + location.host + movePath;
} else {
movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath);
movePath = location.href
.replace(/\?(.*)/, '')
.replace(/[^/]*$/, movePath);
}
}
that._request(
@ -239,7 +262,9 @@ const StorageWebDav = StorageBase.extend({
const password = opts.password;
let encpass = '';
for (let i = 0; i < password.length; i++) {
encpass += String.fromCharCode(password.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length));
encpass += String.fromCharCode(
password.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length)
);
}
result.encpass = btoa(encpass);
}
@ -253,7 +278,9 @@ const StorageWebDav = StorageBase.extend({
const encpass = atob(opts.encpass);
let password = '';
for (let i = 0; i < encpass.length; i++) {
password += String.fromCharCode(encpass.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length));
password += String.fromCharCode(
encpass.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length)
);
}
result.password = password;
}
@ -271,7 +298,12 @@ const StorageWebDav = StorageBase.extend({
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => {
if ([200, 201, 204].indexOf(xhr.status) < 0) {
that.logger.debug(config.op + ' error', config.path, xhr.status, that.logger.ts(ts));
that.logger.debug(
config.op + ' error',
config.path,
xhr.status,
that.logger.ts(ts)
);
let err;
switch (xhr.status) {
case 404:
@ -292,14 +324,20 @@ const StorageWebDav = StorageBase.extend({
}
const rev = xhr.getResponseHeader('Last-Modified');
if (!rev && !config.nostat) {
that.logger.debug(config.op + ' error', config.path, 'no headers', that.logger.ts(ts));
that.logger.debug(
config.op + ' error',
config.path,
'no headers',
that.logger.ts(ts)
);
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');
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);
@ -323,7 +361,10 @@ const StorageWebDav = StorageBase.extend({
xhr.open(config.method, config.path);
xhr.responseType = 'arraybuffer';
if (config.user) {
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(config.user + ':' + config.password));
xhr.setRequestHeader(
'Authorization',
'Basic ' + btoa(config.user + ':' + config.password)
);
}
if (config.headers) {
_.forEach(config.headers, (value, header) => {

View File

@ -79,7 +79,9 @@ Color.prototype.toRgba = function() {
};
Color.prototype.toHsla = function() {
return `hsla(${Math.round(this.h * 100)},${Math.round(this.s * 100)}%,${Math.round(this.l * 100)}%,${this.a})`;
return `hsla(${Math.round(this.h * 100)},${Math.round(this.s * 100)}%,${Math.round(
this.l * 100
)}%,${this.a})`;
};
Color.prototype.distanceTo = function(color) {

View File

@ -12,7 +12,9 @@ const FeatureDetector = {
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),
isSelfHosted:
!isDesktop &&
!/^http(s?):\/\/((localhost:8085)|((app|beta)\.keeweb\.info))/.test(location.href),
needFixClicks: /Edge\/14/.test(navigator.appVersion),
actionShortcutSymbol: function(formatting) {
@ -22,7 +24,11 @@ const FeatureDetector = {
return this.isMac ? '⌥' : formatting ? '<span class="thin">alt + </span>' : 'alt-';
},
globalShortcutSymbol: function(formatting) {
return this.isMac ? '⌃⌥' : formatting ? '<span class="thin">shift+alt+</span>' : 'shift-alt-';
return this.isMac
? '⌃⌥'
: formatting
? '<span class="thin">shift+alt+</span>'
: 'shift-alt-';
},
globalShortcutIsLarge: function() {
return !this.isMac;

View File

@ -32,7 +32,9 @@ const Format = {
if (typeof dt === 'number') {
dt = new Date(dt);
}
return dt ? dt.getDate() + ' ' + Locale.monthsShort[dt.getMonth()] + ' ' + dt.getFullYear() : '';
return dt
? dt.getDate() + ' ' + Locale.monthsShort[dt.getMonth()] + ' ' + dt.getFullYear()
: '';
},
capFirst: function(str) {
if (!str) {

View File

@ -2,7 +2,9 @@ const kdbxweb = require('kdbxweb');
const IconUrl = {
toDataUrl: function(iconData) {
return iconData ? 'data:image/png;base64,' + kdbxweb.ByteUtils.bytesToBase64(iconData) : null;
return iconData
? 'data:image/png;base64,' + kdbxweb.ByteUtils.bytesToBase64(iconData)
: null;
}
};

View File

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

View File

@ -8,7 +8,8 @@ const PasswordGenerator = {
digits: '123456789',
special: '!@#$%^&*_+-=,./?;:`"~\'\\',
brackets: '(){}[]<>',
high: '¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
high:
'¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
ambiguous: 'O0oIl'
},

View File

@ -169,7 +169,12 @@ function addSyllable(wordObj) {
} 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.lastSkippedPost = false;
@ -219,8 +224,12 @@ function getOptions(overrides) {
overrides = overrides || {};
options.length = overrides.length || 16;
options.seed = overrides.seed || Math.random();
options.phoneticSimplicity = overrides.phoneticSimplicity ? Math.max(overrides.phoneticSimplicity, 1) : 5;
options.compoundSimplicity = overrides.compoundSimplicity ? Math.max(overrides.compoundSimplicity, 1) : 5;
options.phoneticSimplicity = overrides.phoneticSimplicity
? Math.max(overrides.phoneticSimplicity, 1)
: 5;
options.compoundSimplicity = overrides.compoundSimplicity
? Math.max(overrides.compoundSimplicity, 1)
: 5;
return options;
}

View File

@ -54,7 +54,9 @@ 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

@ -173,7 +173,10 @@ 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

@ -189,7 +189,11 @@ 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();
}
},
@ -308,7 +312,9 @@ const AppView = Backbone.View.extend({
},
showFileSettings: function(e) {
const menuItem = this.model.menu.filesSection.get('items').find(item => item.get('file').cid === e.fileId);
const menuItem = this.model.menu.filesSection
.get('items')
.find(item => item.get('file').cid === e.fileId);
if (this.views.settings) {
if (this.views.settings.file === menuItem.get('file')) {
this.showEntries();
@ -538,7 +544,10 @@ const AppView = Backbone.View.extend({
if (--pendingCallbacks === 0) {
if (errorFiles.length && that.model.files.hasDirtyFiles()) {
if (!Alerts.alertDisplayed) {
const alertBody = errorFiles.length > 1 ? Locale.appSaveErrorBodyMul : Locale.appSaveErrorBody;
const alertBody =
errorFiles.length > 1
? Locale.appSaveErrorBodyMul
: Locale.appSaveErrorBody;
Alerts.error({
header: Locale.appSaveError,
body: alertBody + ' ' + errorFiles.join(', ')

View File

@ -32,12 +32,30 @@ const AutoTypePopupView = Backbone.View.extend({
setupKeys() {
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.actionEnterPressed, this, KeyHandler.SHORTCUT_ACTION, true);
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.optEnterPressed, this, KeyHandler.SHORTCUT_OPT, true);
KeyHandler.onKey(
Keys.DOM_VK_RETURN,
this.actionEnterPressed,
this,
KeyHandler.SHORTCUT_ACTION,
true
);
KeyHandler.onKey(
Keys.DOM_VK_RETURN,
this.optEnterPressed,
this,
KeyHandler.SHORTCUT_OPT,
true
);
KeyHandler.onKey(Keys.DOM_VK_UP, this.upPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_DOWN, this.downPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_BACK_SPACE, this.backSpacePressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_O, this.openKeyPressed, this, KeyHandler.SHORTCUT_ACTION, true);
KeyHandler.onKey(
Keys.DOM_VK_O,
this.openKeyPressed,
this,
KeyHandler.SHORTCUT_ACTION,
true
);
KeyHandler.on('keypress:auto-type', this.keyPressed.bind(this));
KeyHandler.setModal('auto-type');
},
@ -180,7 +198,10 @@ const AutoTypePopupView = Backbone.View.extend({
backSpacePressed() {
if (this.model.filter.text) {
this.model.filter.text = this.model.filter.text.substr(0, this.model.filter.text.length - 1);
this.model.filter.text = this.model.filter.text.substr(
0,
this.model.filter.text.length - 1
);
this.render();
}
},

View File

@ -121,7 +121,9 @@ const DetailsHistoryView = Backbone.View.extend({
showRecord: function(ix) {
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')
.removeClass('details__history-timeline-item--active');
this.timelineEl
.find('.details__history-timeline-item[data-id="' + ix + '"]')
.addClass('details__history-timeline-item--active');
@ -129,7 +131,9 @@ const DetailsHistoryView = Backbone.View.extend({
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 } })
new FieldViewReadOnly({
model: { name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 }
})
);
this.fieldViews.push(
new FieldViewReadOnly({
@ -162,27 +166,47 @@ const DetailsHistoryView = Backbone.View.extend({
);
this.fieldViews.push(
new FieldViewReadOnly({
model: { name: '$UserName', title: Format.capFirst(Locale.user), value: this.record.user }
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 }
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 }
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 }
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(', ') }
model: {
name: 'Tags',
title: Format.capFirst(Locale.tags),
value: this.record.tags.join(', ')
}
})
);
this.fieldViews.push(
@ -198,7 +222,9 @@ const DetailsHistoryView = Backbone.View.extend({
this.record.fields,
function(value, field) {
this.fieldViews.push(
new FieldViewReadOnly({ model: { name: '$' + field, title: field, value: value } })
new FieldViewReadOnly({
model: { name: '$' + field, title: field, value: value }
})
);
},
this
@ -223,7 +249,12 @@ const DetailsHistoryView = Backbone.View.extend({
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);
.toggle(
(this.record.unsaved &&
ix === this.history.length - 1 &&
this.history.length > 1) ||
false
);
},
timelineItemClick: function(e) {
@ -254,13 +285,15 @@ 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 => ({
pos: (label - firstRec.updated) / (lastRec.updated - firstRec.updated),
val: label,
text: format.format(new Date(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) {

View File

@ -74,22 +74,53 @@ const DetailsView = Backbone.View.extend({
this.listenTo(Backbone, 'set-locale', this.render);
this.listenTo(OtpQrReader, 'qr-read', this.otpCodeRead);
this.listenTo(OtpQrReader, 'enter-manually', this.otpEnterManually);
KeyHandler.onKey(Keys.DOM_VK_C, this.copyPasswordFromShortcut, this, KeyHandler.SHORTCUT_ACTION, false, true);
KeyHandler.onKey(
Keys.DOM_VK_C,
this.copyPasswordFromShortcut,
this,
KeyHandler.SHORTCUT_ACTION,
false,
true
);
KeyHandler.onKey(Keys.DOM_VK_B, this.copyUserName, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_U, this.copyUrl, this, KeyHandler.SHORTCUT_ACTION);
if (AutoType.enabled) {
KeyHandler.onKey(Keys.DOM_VK_T, this.autoType, this, KeyHandler.SHORTCUT_ACTION);
}
KeyHandler.onKey(Keys.DOM_VK_DELETE, this.deleteKeyPress, this, KeyHandler.SHORTCUT_ACTION, false, true);
KeyHandler.onKey(Keys.DOM_VK_BACK_SPACE, this.deleteKeyPress, this, KeyHandler.SHORTCUT_ACTION, false, true);
KeyHandler.onKey(
Keys.DOM_VK_DELETE,
this.deleteKeyPress,
this,
KeyHandler.SHORTCUT_ACTION,
false,
true
);
KeyHandler.onKey(
Keys.DOM_VK_BACK_SPACE,
this.deleteKeyPress,
this,
KeyHandler.SHORTCUT_ACTION,
false,
true
);
},
remove: function() {
KeyHandler.offKey(Keys.DOM_VK_C, this.copyPassword, this);
KeyHandler.offKey(Keys.DOM_VK_B, this.copyUserName, this);
KeyHandler.offKey(Keys.DOM_VK_U, this.copyUrl, this);
KeyHandler.offKey(Keys.DOM_VK_DELETE, this.deleteKeyPress, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.offKey(Keys.DOM_VK_BACK_SPACE, this.deleteKeyPress, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.offKey(
Keys.DOM_VK_DELETE,
this.deleteKeyPress,
this,
KeyHandler.SHORTCUT_ACTION
);
KeyHandler.offKey(
Keys.DOM_VK_BACK_SPACE,
this.deleteKeyPress,
this,
KeyHandler.SHORTCUT_ACTION
);
this.removeFieldViews();
Backbone.View.prototype.remove.call(this);
},
@ -321,7 +352,10 @@ const DetailsView = Backbone.View.extend({
if (hideEmptyFields) {
const value = fieldView.model.value();
if (!value || value.length === 0 || value.byteLength === 0) {
if (this.model.isJustCreated && ['$UserName', '$Password'].indexOf(fieldView.model.name) >= 0) {
if (
this.model.isJustCreated &&
['$UserName', '$Password'].indexOf(fieldView.model.name) >= 0
) {
return; // don't hide user for new records
}
fieldView.hide();
@ -385,15 +419,35 @@ const DetailsView = Backbone.View.extend({
});
}
}, 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 });
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 });
const rect = this.moreView.labelEl[0].getBoundingClientRect();
@ -561,7 +615,8 @@ const DetailsView = Backbone.View.extend({
}
if (!window.getSelection().toString()) {
const fieldValue = editView.value;
const fieldText = fieldValue && fieldValue.isProtected ? fieldValue.getText() : fieldValue;
const fieldText =
fieldValue && fieldValue.isProtected ? fieldValue.getText() : fieldValue;
if (!fieldText) {
return;
}
@ -701,7 +756,10 @@ const DetailsView = Backbone.View.extend({
e.preventDefault();
e.stopPropagation();
const dt = e.originalEvent.dataTransfer;
if (!dt.types || (dt.types.indexOf ? dt.types.indexOf('Files') === -1 : !dt.types.contains('Files'))) {
if (
!dt.types ||
(dt.types.indexOf ? dt.types.indexOf('Files') === -1 : !dt.types.contains('Files'))
) {
dt.dropEffect = 'none';
return;
}
@ -896,7 +954,11 @@ const DetailsView = Backbone.View.extend({
deleteFromTrash: function() {
Alerts.yesno({
header: Locale.detDelFromTrash,
body: Locale.detDelFromTrashBody + ' <p class="muted-color">' + Locale.detDelFromTrashBodyHint + '</p>',
body:
Locale.detDelFromTrashBody +
' <p class="muted-color">' +
Locale.detDelFromTrashBodyHint +
'</p>',
icon: 'minus-circle',
success: () => {
this.model.deleteFromTrash();
@ -913,8 +975,16 @@ const DetailsView = Backbone.View.extend({
const canCopy = document.queryCommandSupported('copy');
const options = [];
if (canCopy) {
options.push({ value: 'det-copy-password', icon: 'clipboard', text: Locale.detMenuCopyPassword });
options.push({ value: 'det-copy-user', icon: 'clipboard', text: Locale.detMenuCopyUser });
options.push({
value: 'det-copy-password',
icon: 'clipboard',
text: Locale.detMenuCopyPassword
});
options.push({
value: 'det-copy-user',
icon: 'clipboard',
text: Locale.detMenuCopyUser
});
}
options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField });
options.push({ value: 'det-clone', icon: 'clone', text: Locale.detClone });

View File

@ -46,7 +46,9 @@ const FieldViewAutocomplete = FieldViewText.extend({
e.preventDefault();
break;
case Keys.DOM_VK_RETURN:
const selectedItem = this.autocomplete.find('.details__field-autocomplete-item--selected').text();
const selectedItem = this.autocomplete
.find('.details__field-autocomplete-item--selected')
.text();
if (selectedItem) {
this.input.val(selectedItem);
this.endEdit(selectedItem);
@ -62,7 +64,8 @@ const FieldViewAutocomplete = FieldViewText.extend({
const completions = this.model.getCompletions(this.input.val());
if (typeof this.selectedCopmletionIx === 'number') {
this.selectedCopmletionIx =
(completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) % completions.length;
(completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) %
completions.length;
} else {
this.selectedCopmletionIx = next ? 0 : completions.length - 1;
}
@ -73,8 +76,17 @@ const FieldViewAutocomplete = FieldViewText.extend({
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>';
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);

View File

@ -6,7 +6,10 @@ const FieldViewHistory = FieldView.extend({
if (!value.length) {
return Locale.detHistoryEmpty;
}
let text = value.length + ' ' + (value.length % 10 === 1 ? Locale.detHistoryRec : Locale.detHistoryRecs);
let text =
value.length +
' ' +
(value.length % 10 === 1 ? Locale.detHistoryRec : Locale.detHistoryRecs);
if (value.unsaved) {
text += ' (' + Locale.detHistoryModified + ')';
}

View File

@ -38,7 +38,9 @@ const FieldViewTags = FieldViewText.extend({
startEdit: function() {
FieldViewText.prototype.startEdit.call(this);
const fieldRect = this.input[0].getBoundingClientRect();
this.tagsAutocomplete = $('<div class="details__field-autocomplete"></div>').appendTo('body');
this.tagsAutocomplete = $('<div class="details__field-autocomplete"></div>').appendTo(
'body'
);
this.tagsAutocomplete.css({
top: fieldRect.bottom,
left: fieldRect.left,
@ -59,7 +61,10 @@ const FieldViewTags = FieldViewText.extend({
const last = tags[tags.length - 1];
const isLastPart = last && this.model.tags.indexOf(last) < 0;
return this.model.tags.filter(tag => {
return tags.indexOf(tag) < 0 && (!isLastPart || tag.toLowerCase().indexOf(last.toLowerCase()) >= 0);
return (
tags.indexOf(tag) < 0 &&
(!isLastPart || tag.toLowerCase().indexOf(last.toLowerCase()) >= 0)
);
});
},

View File

@ -85,7 +85,10 @@ const FieldViewText = FieldView.extend({
} else {
const fieldRect = this.input[0].getBoundingClientRect();
this.gen = new GeneratorView({
model: { pos: { left: fieldRect.left, top: fieldRect.bottom }, password: this.value }
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));

View File

@ -140,7 +140,8 @@ const FieldView = Backbone.View.extend({
} else {
textEqual = _.isEqual(this.value, newVal);
}
const protectedEqual = (newVal && newVal.isProtected) === (this.value && this.value.isProtected);
const protectedEqual =
(newVal && newVal.isProtected) === (this.value && this.value.isProtected);
const nameChanged = extra && extra.newField;
let arg;
if (newVal !== undefined && (!textEqual || !protectedEqual || nameChanged)) {

View File

@ -19,7 +19,14 @@ const FooterView = Backbone.View.extend({
initialize: function() {
this.views = {};
KeyHandler.onKey(Keys.DOM_VK_L, this.lockWorkspace, this, KeyHandler.SHORTCUT_ACTION, false, true);
KeyHandler.onKey(
Keys.DOM_VK_L,
this.lockWorkspace,
this,
KeyHandler.SHORTCUT_ACTION,
false,
true
);
KeyHandler.onKey(Keys.DOM_VK_G, this.genPass, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_O, this.openFile, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_S, this.saveAll, this, KeyHandler.SHORTCUT_ACTION);
@ -35,7 +42,8 @@ const FooterView = Backbone.View.extend({
this.renderTemplate(
{
files: this.model.files,
updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
updateAvailable:
['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
},
{ plain: true }
);
@ -67,7 +75,9 @@ const FooterView = Backbone.View.extend({
const bodyRect = document.body.getBoundingClientRect();
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();
const generator = new GeneratorView({
model: { copy: true, pos: { right: right, bottom: bottom } }
}).render();
generator.once('remove', () => {
delete this.views.gen;
});

View File

@ -62,15 +62,17 @@ const GeneratorPresetsView = Backbone.View.extend({
const rangeOverride = {
high: '¡¢£¤¥¦§©ª«¬®¯°±¹²´µ¶»¼÷¿ÀÖîü...'
};
return ['Upper', 'Lower', 'Digits', 'Special', 'Brackets', 'High', 'Ambiguous'].map(name => {
const nameLower = name.toLowerCase();
return {
name: nameLower,
title: Locale['genPs' + name],
enabled: sel[nameLower],
sample: rangeOverride[nameLower] || PasswordGenerator.charRanges[nameLower]
};
});
return ['Upper', 'Lower', 'Digits', 'Special', 'Brackets', 'High', 'Ambiguous'].map(
name => {
const nameLower = name.toLowerCase();
return {
name: nameLower,
title: Locale['genPs' + name],
enabled: sel[nameLower],
sample: rangeOverride[nameLower] || PasswordGenerator.charRanges[nameLower]
};
}
);
},
getPreset: function(name) {

View File

@ -24,7 +24,34 @@ const GeneratorView = Backbone.View.extend({
'click .gen__btn-refresh': 'newPass'
},
valuesMap: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 30, 32, 48, 64],
valuesMap: [
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
22,
24,
26,
28,
30,
32,
48,
64
],
presets: null,
preset: null,
@ -40,7 +67,11 @@ 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,
@ -57,7 +88,10 @@ const GeneratorView = Backbone.View.extend({
createPresets: function() {
this.presets = GeneratorPresets.enabled;
if (this.model.password && (!this.model.password.isProtected || this.model.password.byteLength)) {
if (
this.model.password &&
(!this.model.password.isProtected || this.model.password.byteLength)
) {
const derivedPreset = { name: 'Derived', title: Locale.genPresetDerived };
_.extend(derivedPreset, PasswordGenerator.deriveOpts(this.model.password));
this.presets.splice(0, 0, derivedPreset);

View File

@ -57,7 +57,9 @@ const IconSelectView = Backbone.View.extend({
}
this.downloadingFavicon = true;
this.$el.find('.icon-select__icon-download>i').addClass('fa-spinner fa-spin');
this.$el.find('.icon-select__icon-download').removeClass('icon-select__icon--download-error');
this.$el
.find('.icon-select__icon-download')
.removeClass('icon-select__icon--download-error');
const url = this.getIconUrl(true);
const img = document.createElement('img');
img.crossOrigin = 'Anonymous';
@ -87,7 +89,10 @@ const IconSelectView = Backbone.View.extend({
if (!this.model.url) {
return null;
}
let url = this.model.url.replace(/([^\/:]\/.*)?$/, match => (match && match[0]) + '/favicon.ico');
let url = this.model.url.replace(
/([^\/:]\/.*)?$/,
match => (match && match[0]) + '/favicon.ico'
);
if (url.indexOf('://') < 0) {
url = 'http://' + url;
}
@ -120,7 +125,9 @@ const IconSelectView = Backbone.View.extend({
reader.readAsDataURL(file);
} else {
this.$el.find('.icon-select__icon-select img').remove();
this.$el.find('.icon-select__icon-select').removeClass('icon-select__icon--custom-selected');
this.$el
.find('.icon-select__icon-select')
.removeClass('icon-select__icon--custom-selected');
}
},

View File

@ -33,10 +33,14 @@ const KeyChangeView = Backbone.View.extend({
fileName: this.model.file.get('name'),
keyFileName: this.model.file.get('keyFileName'),
title: this.model.expired ? Locale.keyChangeTitleExpired : Locale.keyChangeTitleRemote,
message: this.model.expired ? Locale.keyChangeMessageExpired : Locale.keyChangeMessageRemote,
message: this.model.expired
? Locale.keyChangeMessageExpired
: Locale.keyChangeMessageRemote,
repeat: repeat
});
this.$el.find('.key-change__keyfile-name').text(this.keyFileName ? ': ' + this.keyFileName : '');
this.$el
.find('.key-change__keyfile-name')
.text(this.keyFileName ? ': ' + this.keyFileName : '');
this.inputEl = this.$el.find('.key-change__pass');
this.passwordInput.reset();
this.passwordInput.setElement(this.inputEl);

View File

@ -83,7 +83,11 @@ const ListSearchView = Backbone.View.extend({
icon: 'sort-numeric-desc',
loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO)
},
{ value: '-attachments', icon: 'sort-amount-desc', loc: () => Locale.searchAttachments },
{
value: '-attachments',
icon: 'sort-amount-desc',
loc: () => Locale.searchAttachments
},
{ value: '-rank', icon: 'sort-numeric-desc', loc: () => Locale.searchRank }
];
this.sortIcons = {};
@ -306,7 +310,9 @@ const ListSearchView = Backbone.View.extend({
if (this.views.searchDropdown) {
this.views.searchDropdown.remove();
this.views.searchDropdown = null;
this.$el.find('.list__search-btn-sort,.list__search-btn-new').removeClass('sel--active');
this.$el
.find('.list__search-btn-sort,.list__search-btn-new')
.removeClass('sel--active');
}
},
@ -366,12 +372,18 @@ const ListSearchView = Backbone.View.extend({
options.push({
value: id,
icon: tmpl.entry.icon,
text: hasMultipleFiles ? tmpl.file.get('name') + ' / ' + tmpl.entry.title : tmpl.entry.title
text: hasMultipleFiles
? tmpl.file.get('name') + ' / ' + tmpl.entry.title
: tmpl.entry.title
});
this.entryTemplates[id] = tmpl;
});
options.sort(Comparators.stringComparator('text', true));
options.push({ value: 'tmpl', icon: 'sticky-note-o', text: Format.capFirst(Locale.template) });
options.push({
value: 'tmpl',
icon: 'sticky-note-o',
text: Format.capFirst(Locale.template)
});
return options;
},

View File

@ -81,7 +81,11 @@ const ListView = Backbone.View.extend({
const itemTemplate = this.getItemTemplate();
const itemsTemplate = this.getItemsTemplate();
const noColor = AppSettingsModel.instance.get('colorfulIcons') ? '' : 'grayscale';
const presenter = new EntryPresenter(this.getDescField(), noColor, this.model.activeEntryId);
const presenter = new EntryPresenter(
this.getDescField(),
noColor,
this.model.activeEntryId
);
const columns = {};
this.tableColumns.forEach(col => {
if (col.enabled) {
@ -310,7 +314,9 @@ const ListView = Backbone.View.extend({
},
saveTableColumnsEnabled() {
const tableViewColumns = this.tableColumns.filter(column => column.enabled).map(column => column.name);
const tableViewColumns = this.tableColumns
.filter(column => column.enabled)
.map(column => column.name);
AppSettingsModel.instance.set('tableViewColumns', tableViewColumns);
}
});

View File

@ -91,7 +91,8 @@ const MenuItemView = Backbone.View.extend({
},
changeIcon: function(model, icon) {
this.iconEl[0].className = 'menu__item-icon fa ' + (icon ? 'fa-' + icon : 'menu__item-icon--no-icon');
this.iconEl[0].className =
'menu__item-icon fa ' + (icon ? 'fa-' + icon : 'menu__item-icon--no-icon');
},
changeActive: function(model, active) {

View File

@ -77,7 +77,9 @@ const ModalView = Backbone.View.extend({
},
closeWithResult: function(result) {
const checked = this.model.checkbox ? this.$el.find('#modal__check').is(':checked') : undefined;
const checked = this.model.checkbox
? this.$el.find('#modal__check').is(':checked')
: undefined;
this.trigger('result', result, checked);
this.$el.addClass('modal--hidden');
this.undelegateEvents();

View File

@ -71,7 +71,9 @@ const OpenConfigView = Backbone.View.extend({
setError: function(err) {
const errText =
err && err.notFound ? Locale.openConfigErrorNotFound : Locale.openConfigError.replace('{}', err);
err && err.notFound
? Locale.openConfigErrorNotFound
: Locale.openConfigError.replace('{}', err);
this.$el.find('.open__config-error').text(errText);
}
});

View File

@ -383,7 +383,10 @@ const OpenView = Backbone.View.extend({
body: fileInfo.get('modified')
? Locale.openRemoveLastQuestionModBody
: Locale.openRemoveLastQuestionBody,
buttons: [{ result: 'yes', title: Locale.alertYes }, { result: '', title: Locale.alertNo }],
buttons: [
{ result: 'yes', title: Locale.alertYes },
{ result: '', title: Locale.alertNo }
],
success: () => {
this.removeFile(id);
}
@ -442,7 +445,10 @@ const OpenView = Backbone.View.extend({
e.preventDefault();
e.stopPropagation();
const dt = e.originalEvent.dataTransfer;
if (!dt.types || (dt.types.indexOf ? dt.types.indexOf('Files') === -1 : !dt.types.contains('Files'))) {
if (
!dt.types ||
(dt.types.indexOf ? dt.types.indexOf('Files') === -1 : !dt.types.contains('Files'))
) {
dt.dropEffect = 'none';
return;
}
@ -498,7 +504,11 @@ const OpenView = Backbone.View.extend({
.toLowerCase() === 'key'
);
if (dataFile) {
this.setFile(dataFile, keyFile, dataFile.path ? null : this.showLocalFileAlert.bind(this));
this.setFile(
dataFile,
keyFile,
dataFile.path ? null : this.showLocalFileAlert.bind(this)
);
}
},
@ -604,7 +614,9 @@ const OpenView = Backbone.View.extend({
this.inputEl.attr('disabled', 'disabled');
this.busy = true;
this.params.password = this.passwordInput.value;
this.afterPaint(this.model.openFile.bind(this.model, this.params, this.openDbComplete.bind(this)));
this.afterPaint(
this.model.openFile.bind(this.model, this.params, this.openDbComplete.bind(this))
);
},
openDbComplete: function(err) {
@ -624,7 +636,11 @@ const OpenView = Backbone.View.extend({
}
Alerts.error({
header: Locale.openError,
body: Locale.openErrorDescription + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
body:
Locale.openErrorDescription +
'<pre class="modal__pre">' +
_.escape(err.toString()) +
'</pre>'
});
}
} else {
@ -701,7 +717,10 @@ const OpenView = Backbone.View.extend({
Alerts.error({
header: Locale.openError,
body:
Locale.openListErrorBody + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
Locale.openListErrorBody +
'<pre class="modal__pre">' +
_.escape(err.toString()) +
'</pre>'
});
}
return;
@ -786,7 +805,10 @@ const OpenView = Backbone.View.extend({
},
storage.getOpenConfig()
);
this.views.openConfig = new OpenConfigView({ el: this.$el.find('.open__config-wrap'), model: config }).render();
this.views.openConfig = new OpenConfigView({
el: this.$el.find('.open__config-wrap'),
model: config
}).render();
this.views.openConfig.on('cancel', this.closeConfig.bind(this));
this.views.openConfig.on('apply', this.applyConfig.bind(this));
this.$el.find('.open__pass-area').addClass('hide');
@ -868,7 +890,10 @@ const OpenView = Backbone.View.extend({
moveOpenFileSelection: function(steps) {
const lastOpenFiles = this.getLastOpenFiles();
if (this.currentSelectedIndex + steps >= 0 && this.currentSelectedIndex + steps <= lastOpenFiles.length - 1) {
if (
this.currentSelectedIndex + steps >= 0 &&
this.currentSelectedIndex + steps <= lastOpenFiles.length - 1
) {
this.currentSelectedIndex = this.currentSelectedIndex + steps;
}

View File

@ -51,7 +51,11 @@ const SettingsFileView = Backbone.View.extend({
appModel: null,
initialize: function() {
this.listenTo(this.model, 'change:syncing change:syncError change:syncDate', this.deferRender);
this.listenTo(
this.model,
'change:syncing change:syncError change:syncDate',
this.deferRender
);
},
render: function() {
@ -90,25 +94,34 @@ const SettingsFileView = Backbone.View.extend({
recycleBinEnabled: this.model.get('recycleBinEnabled'),
backupEnabled: backup && backup.enabled,
backupStorage: backup && backup.storage,
backupPath: (backup && backup.path) || DefaultBackupPath.replace('{name}', this.model.get('name')),
backupPath:
(backup && backup.path) ||
DefaultBackupPath.replace('{name}', this.model.get('name')),
backupSchedule: backup ? backup.schedule : DefaultBackupSchedule,
historyMaxItems: this.model.get('historyMaxItems'),
historyMaxSize: Math.round(this.model.get('historyMaxSize') / 1024 / 1024),
keyEncryptionRounds: this.model.get('keyEncryptionRounds'),
keyChangeForce: this.model.get('keyChangeForce') > 0 ? this.model.get('keyChangeForce') : null,
keyChangeForce:
this.model.get('keyChangeForce') > 0 ? this.model.get('keyChangeForce') : null,
kdfParameters: this.kdfParametersToUi(this.model.get('kdfParameters')),
storageProviders: storageProviders,
canBackup: canBackup
});
if (!this.model.get('created')) {
this.$el.find('.settings__file-master-pass-warning').toggle(this.model.get('passwordChanged'));
this.$el.find('#settings__file-master-pass-warning-text').text(Locale.setFilePassChanged);
this.$el
.find('.settings__file-master-pass-warning')
.toggle(this.model.get('passwordChanged'));
this.$el
.find('#settings__file-master-pass-warning-text')
.text(Locale.setFilePassChanged);
}
this.renderKeyFileSelect();
},
kdfParametersToUi: function(kdfParameters) {
return kdfParameters ? _.extend({}, kdfParameters, { memory: Math.round(kdfParameters.memory / 1024) }) : null;
return kdfParameters
? _.extend({}, kdfParameters, { memory: Math.round(kdfParameters.memory / 1024) })
: null;
},
renderKeyFileSelect: function() {
@ -219,7 +232,8 @@ const SettingsFileView = Backbone.View.extend({
if (err) {
Alerts.error({
header: Locale.setFileSaveError,
body: Locale.setFileSaveErrorBody + ' ' + path + ': \n' + err
body:
Locale.setFileSaveErrorBody + ' ' + path + ': \n' + err
});
}
});
@ -299,12 +313,17 @@ const SettingsFileView = Backbone.View.extend({
}
const expName = this.model.get('name').toLowerCase();
const existingFile = _.find(files, file => {
return !file.dir && UrlUtil.getDataFileName(file.name).toLowerCase() === expName;
return (
!file.dir && UrlUtil.getDataFileName(file.name).toLowerCase() === expName
);
});
if (existingFile) {
Alerts.yesno({
header: Locale.setFileAlreadyExists,
body: Locale.setFileAlreadyExistsBody.replace('{}', this.model.escape('name')),
body: Locale.setFileAlreadyExistsBody.replace(
'{}',
this.model.escape('name')
),
success: () => {
this.model.set('syncing', true);
storage.remove(existingFile.path, err => {
@ -404,7 +423,9 @@ const SettingsFileView = Backbone.View.extend({
this.$el.find('.settings__file-master-pass-warning').hide();
} else {
this.$el.find('#settings__file-confirm-master-pass-group').show();
this.$el.find('#settings__file-master-pass-warning-text').text(Locale.setFilePassChange);
this.$el
.find('#settings__file-master-pass-warning-text')
.text(Locale.setFilePassChange);
if (!this.model.get('created')) {
this.$el.find('.settings__file-master-pass-warning').show();
}
@ -437,11 +458,15 @@ const SettingsFileView = Backbone.View.extend({
const masterPassword = this.$el.find('#settings__file-master-pass').val();
const confirmPassword = e.target.value;
if (masterPassword === confirmPassword) {
this.$el.find('#settings__file-master-pass-warning-text').text(Locale.setFilePassChanged);
this.$el
.find('#settings__file-master-pass-warning-text')
.text(Locale.setFilePassChanged);
this.$el.find('.settings__file-confirm-master-pass-warning').hide();
this.model.setPassword(kdbxweb.ProtectedValue.fromString(confirmPassword));
} else {
this.$el.find('#settings__file-master-pass-warning-text').text(Locale.setFilePassChange);
this.$el
.find('#settings__file-master-pass-warning-text')
.text(Locale.setFilePassChange);
this.$el.find('.settings__file-confirm-master-pass-warning').show();
this.model.resetPassword();
}
@ -550,7 +575,11 @@ const SettingsFileView = Backbone.View.extend({
}
Alerts.error({
title: title,
body: description + '<pre class="modal__pre">' + _.escape(err.toString()) + '</pre>'
body:
description +
'<pre class="modal__pre">' +
_.escape(err.toString()) +
'</pre>'
});
}
});

View File

@ -143,12 +143,22 @@ const SettingsGeneralView = Backbone.View.extend({
Format.dtStr(UpdateModel.instance.get('lastSuccessCheckDate'))
) +
': ' +
Locale.setGenLastCheckVer.replace('{}', UpdateModel.instance.get('lastVersion'));
Locale.setGenLastCheckVer.replace(
'{}',
UpdateModel.instance.get('lastVersion')
);
}
return errMsg;
case 'ok':
let msg = Locale.setGenCheckedAt + ' ' + Format.dtStr(UpdateModel.instance.get('lastCheckDate')) + ': ';
const cmp = SemVer.compareVersions(RuntimeInfo.version, UpdateModel.instance.get('lastVersion'));
let msg =
Locale.setGenCheckedAt +
' ' +
Format.dtStr(UpdateModel.instance.get('lastCheckDate')) +
': ';
const cmp = SemVer.compareVersions(
RuntimeInfo.version,
UpdateModel.instance.get('lastVersion')
);
if (cmp >= 0) {
msg += Locale.setGenLatestVer;
} else {
@ -196,7 +206,9 @@ const SettingsGeneralView = Backbone.View.extend({
const locale = e.target.value;
if (locale === '...') {
e.target.value = AppSettingsModel.instance.get('locale') || 'en';
this.appModel.menu.select({ item: this.appModel.menu.pluginsSection.get('items').first() });
this.appModel.menu.select({
item: this.appModel.menu.pluginsSection.get('items').first()
});
return;
}
AppSettingsModel.instance.set('locale', locale);
@ -322,12 +334,16 @@ const SettingsGeneralView = Backbone.View.extend({
if (storage) {
storage.setEnabled(e.target.checked);
AppSettingsModel.instance.set(storage.name, storage.enabled);
this.$el.find('.settings__general-' + storage.name).toggleClass('hide', !e.target.checked);
this.$el
.find('.settings__general-' + storage.name)
.toggleClass('hide', !e.target.checked);
}
},
showAdvancedSettings: function() {
this.$el.find('.settings__general-show-advanced, .settings__general-advanced').toggleClass('hide');
this.$el
.find('.settings__general-show-advanced, .settings__general-advanced')
.toggleClass('hide');
this.scrollToBottom();
},
@ -352,7 +368,9 @@ const SettingsGeneralView = Backbone.View.extend({
if (this.views.logView) {
this.views.logView.remove();
}
this.views.logView = new SettingsLogsView({ el: this.$el.find('.settings__general-advanced') }).render();
this.views.logView = new SettingsLogsView({
el: this.$el.find('.settings__general-advanced')
}).render();
this.scrollToBottom();
},

View File

@ -38,7 +38,11 @@ const SettingsPluginsView = Backbone.View.extend({
initialize() {
this.listenTo(PluginManager, 'change', this.render.bind(this));
this.listenTo(Backbone, 'plugin-gallery-load-complete', this.pluginGalleryLoadComplete.bind(this));
this.listenTo(
Backbone,
'plugin-gallery-load-complete',
this.pluginGalleryLoadComplete.bind(this)
);
},
render() {
@ -103,10 +107,16 @@ const SettingsPluginsView = Backbone.View.extend({
if (plugin.manifest.desktop && !RuntimeInfo.launcher) {
return false;
}
if (plugin.manifest.versionMin && SemVer.compareVersions(plugin.manifest.versionMin, RuntimeInfo.version) > 0) {
if (
plugin.manifest.versionMin &&
SemVer.compareVersions(plugin.manifest.versionMin, RuntimeInfo.version) > 0
) {
return false;
}
if (plugin.manifest.versionMax && SemVer.compareVersions(plugin.manifest.versionMax, RuntimeInfo.version) > 0) {
if (
plugin.manifest.versionMax &&
SemVer.compareVersions(plugin.manifest.versionMax, RuntimeInfo.version) > 0
) {
return false;
}
return true;

View File

@ -38,7 +38,10 @@ const TagView = Backbone.View.extend({
return;
}
if (/[;,:]/.test(title)) {
Alerts.error({ header: Locale.tagBadName, body: Locale.tagBadNameBody.replace('{}', '`,`, `;`, `:`') });
Alerts.error({
header: Locale.tagBadName,
body: Locale.tagBadNameBody.replace('{}', '`,`, `;`, `:`')
});
return;
}
if (this.appModel.tags.some(t => t.toLowerCase() === title.toLowerCase())) {

View File

@ -279,7 +279,8 @@
}
transition: background-color $slow-transition-out, border-color $slow-transition-out;
.details__field--edit[active-mobile-action] & {
transition: background-color $slow-transition-in, border-color $slow-transition-in;
transition: background-color $slow-transition-in,
border-color $slow-transition-in;
}
.details__field--edit[active-mobile-action='apply'] & {
@include th {

View File

@ -175,22 +175,34 @@
&--custom {
vertical-align: text-bottom;
&.yellow {
@include filter(grayscale(1) sepia(1) hue-rotate(20deg) brightness(1.17) saturate(5.7));
@include filter(
grayscale(1) sepia(1) hue-rotate(20deg) brightness(1.17) saturate(5.7)
);
}
&.green {
@include filter(grayscale(1) sepia(1) hue-rotate(55deg) brightness(1.01) saturate(4.9));
@include filter(
grayscale(1) sepia(1) hue-rotate(55deg) brightness(1.01) saturate(4.9)
);
}
&.red {
@include filter(grayscale(1) sepia(1) hue-rotate(316deg) brightness(1.1) saturate(6));
@include filter(
grayscale(1) sepia(1) hue-rotate(316deg) brightness(1.1) saturate(6)
);
}
&.orange {
@include filter(grayscale(1) sepia(1) hue-rotate(355deg) brightness(0.92) saturate(5));
@include filter(
grayscale(1) sepia(1) hue-rotate(355deg) brightness(0.92) saturate(5)
);
}
&.blue {
@include filter(grayscale(1) sepia(1) hue-rotate(180deg) brightness(0.9) saturate(5));
@include filter(
grayscale(1) sepia(1) hue-rotate(180deg) brightness(0.9) saturate(5)
);
}
&.violet {
@include filter(grayscale(1) sepia(1) hue-rotate(238deg) brightness(1) saturate(6.2));
@include filter(
grayscale(1) sepia(1) hue-rotate(238deg) brightness(1) saturate(6.2)
);
}
}
}

View File

@ -2,7 +2,12 @@
@return map-merge(
$t,
(
muted-color: mix(map-get($t, medium-color), map-get($t, background-color), map-get($t, mute-percent)),
muted-color:
mix(
map-get($t, medium-color),
map-get($t, background-color),
map-get($t, mute-percent)
),
muted-color-border:
mix(
map-get($t, medium-color),
@ -29,13 +34,20 @@
base-border-color: mix(map-get($t, medium-color), map-get($t, background-color), 50%),
accent-border-color: mix(map-get($t, medium-color), map-get($t, background-color), 65%),
light-border-color:
mix(map-get($t, medium-color), map-get($t, background-color), map-get($t, light-border-percent)),
mix(
map-get($t, medium-color),
map-get($t, background-color),
map-get($t, light-border-percent)
),
form-box-shadow-color-focus: lightness-alpha(map-get($t, action-color), -5%, -0.3),
form-box-shadow-color-focus-error: lightness-alpha(map-get($t, error-color), -5%, -0.3),
dropdown-box-shadow-color: rgba(map-get($t, medium-color), 0.05),
secondary-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 10%),
intermediate-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 3%),
intermediate-pressed-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 2.6%),
secondary-background-color:
mix(map-get($t, medium-color), map-get($t, background-color), 10%),
intermediate-background-color:
mix(map-get($t, medium-color), map-get($t, background-color), 3%),
intermediate-pressed-background-color:
mix(map-get($t, medium-color), map-get($t, background-color), 2.6%),
disabled-background-color: shade(map-get($t, background-color), 5%),
action-background-color-focus: shade(map-get($t, action-color), 20%),
action-background-color-focus-tr: rgba(shade(map-get($t, action-color), 20%), 0.1),

View File

@ -1,6 +1,7 @@
// Typography
$base-font-family: -apple-system, 'BlinkMacSystemFont', 'Helvetica Neue', 'Helvetica', 'Roboto', 'Arial',
'Microsoft YaHei', '微软雅黑', 'PingFang SC', 'Hiragino Sans GB', 'STXihei', '华文细黑', sans-serif;
$base-font-family: -apple-system, 'BlinkMacSystemFont', 'Helvetica Neue', 'Helvetica', 'Roboto',
'Arial', 'Microsoft YaHei', '微软雅黑', 'PingFang SC', 'Hiragino Sans GB', 'STXihei', '华文细黑',
sans-serif;
$heading-font-family: $base-font-family;
$monospace-font-family: 'SFMono-Regular', Monaco, Consolas, 'Lucida Console', monospace;

View File

@ -12,7 +12,9 @@ module.exports = function(grunt) {
const data = file.slice(0, ix);
sign(grunt, data).then(signature => {
signature = Buffer.from(signature.toString('hex'), 'binary');
if (signature.byteLength !== Buffer.from(this.options().signature, 'binary').byteLength) {
if (
signature.byteLength !== Buffer.from(this.options().signature, 'binary').byteLength
) {
grunt.warn('Bad signature length');
return;
}

View File

@ -27,8 +27,14 @@ module.exports = function(grunt) {
grunt.log.writeln(basename);
}
grunt.file.write(file.dest, results.map(line => `${line.digest} *${line.basename}`).join('\n'));
grunt.file.write(opt.sign, results.map(line => `${line.signature} *${line.basename}`).join('\n'));
grunt.file.write(
file.dest,
results.map(line => `${line.digest} *${line.basename}`).join('\n')
);
grunt.file.write(
opt.sign,
results.map(line => `${line.signature} *${line.basename}`).join('\n')
);
}
done();

View File

@ -24,32 +24,38 @@
const fs = require('fs');
module.exports = function(grunt) {
grunt.registerMultiTask('sign-exe', 'Signs exe file with authenticode certificate', async function() {
const opt = this.options();
const done = this.async();
if (opt.pvk) {
const keytar = require('keytar');
keytar
.getPassword(opt.keytarPasswordService, opt.keytarPasswordAccount)
.then(password => {
if (!password) {
return grunt.warn('Code sign password not found');
}
const promises = Object.keys(opt.files).map(file => signFile(file, opt.files[file], opt, password));
Promise.all(promises).then(done);
})
.catch(e => {
grunt.warn('Code sign error: ' + e);
});
} else {
const sign = require('../util/sign');
const pin = await sign.getPin();
for (const file of Object.keys(opt.files)) {
await signFile(file, opt.files[file], opt, pin);
grunt.registerMultiTask(
'sign-exe',
'Signs exe file with authenticode certificate',
async function() {
const opt = this.options();
const done = this.async();
if (opt.pvk) {
const keytar = require('keytar');
keytar
.getPassword(opt.keytarPasswordService, opt.keytarPasswordAccount)
.then(password => {
if (!password) {
return grunt.warn('Code sign password not found');
}
const promises = Object.keys(opt.files).map(file =>
signFile(file, opt.files[file], opt, password)
);
Promise.all(promises).then(done);
})
.catch(e => {
grunt.warn('Code sign error: ' + e);
});
} else {
const sign = require('../util/sign');
const pin = await sign.getPin();
for (const file of Object.keys(opt.files)) {
await signFile(file, opt.files[file], opt, pin);
}
done();
}
done();
}
});
);
function signFile(file, name, opt, password) {
const signedFile = file + '.sign';

View File

@ -24,7 +24,9 @@ module.exports = function(grunt) {
})
.catch(e => {
if (e === 'Cannot find PIN') {
grunt.warn('Error signing app html. To build without sign, please launch grunt with --skip-sign.');
grunt.warn(
'Error signing app html. To build without sign, please launch grunt with --skip-sign.'
);
} else {
grunt.warn('Sign error: ' + e);
}

View File

@ -1,54 +1,63 @@
module.exports = function(grunt) {
grunt.registerMultiTask('validate-desktop-update', 'Validates desktop update package', function() {
const path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const done = this.async();
const StreamZip = require(path.resolve(__dirname, '../../desktop/node_modules/node-stream-zip'));
const zip = new StreamZip({ file: this.options().file, storeEntries: true });
const expFiles = this.options().expected;
const expFilesCount = this.options().expectedCount;
const publicKey = fs.readFileSync(this.options().publicKey, 'binary');
const zipFileData = fs.readFileSync(this.options().file);
zip.on('error', err => {
grunt.warn(err);
});
zip.on('ready', () => {
let valid = true;
if (!zip.comment) {
grunt.warn('No comment in ZIP');
return;
}
if (zip.comment.length !== 512) {
grunt.warn('Bad comment length in ZIP');
return;
}
const verify = crypto.createVerify('RSA-SHA256');
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
verify.end();
const signature = Buffer.from(zip.comment, 'hex');
if (!verify.verify(publicKey, signature)) {
grunt.warn('Invalid ZIP signature');
return;
}
if (zip.entriesCount !== expFilesCount) {
grunt.warn(`ZIP contains ${zip.entriesCount} entries, expected ${expFilesCount}`);
valid = false;
}
expFiles.forEach(entry => {
try {
if (!zip.entryDataSync(entry)) {
grunt.warn('Corrupted entry in desktop update archive: ' + entry);
valid = false;
}
} catch (e) {
grunt.warn('Entry not found in desktop update archive: ' + entry);
grunt.registerMultiTask(
'validate-desktop-update',
'Validates desktop update package',
function() {
const path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const done = this.async();
const StreamZip = require(path.resolve(
__dirname,
'../../desktop/node_modules/node-stream-zip'
));
const zip = new StreamZip({ file: this.options().file, storeEntries: true });
const expFiles = this.options().expected;
const expFilesCount = this.options().expectedCount;
const publicKey = fs.readFileSync(this.options().publicKey, 'binary');
const zipFileData = fs.readFileSync(this.options().file);
zip.on('error', err => {
grunt.warn(err);
});
zip.on('ready', () => {
let valid = true;
if (!zip.comment) {
grunt.warn('No comment in ZIP');
return;
}
if (zip.comment.length !== 512) {
grunt.warn('Bad comment length in ZIP');
return;
}
const verify = crypto.createVerify('RSA-SHA256');
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
verify.end();
const signature = Buffer.from(zip.comment, 'hex');
if (!verify.verify(publicKey, signature)) {
grunt.warn('Invalid ZIP signature');
return;
}
if (zip.entriesCount !== expFilesCount) {
grunt.warn(
`ZIP contains ${zip.entriesCount} entries, expected ${expFilesCount}`
);
valid = false;
}
expFiles.forEach(entry => {
try {
if (!zip.entryDataSync(entry)) {
grunt.warn('Corrupted entry in desktop update archive: ' + entry);
valid = false;
}
} catch (e) {
grunt.warn('Entry not found in desktop update archive: ' + entry);
valid = false;
}
});
if (valid) {
done();
}
});
if (valid) {
done();
}
});
});
}
);
};

View File

@ -25,7 +25,9 @@ const tempUserDataPath = path.join(userDataDir, 'temp');
const tempUserDataPathRand = Date.now().toString() + Math.random().toString();
const systemNotificationIds = [];
let htmlPath = process.argv.filter(arg => arg.startsWith('--htmlpath=')).map(arg => arg.replace('--htmlpath=', ''))[0];
let htmlPath = process.argv
.filter(arg => arg.startsWith('--htmlpath='))
.map(arg => arg.replace('--htmlpath=', ''))[0];
if (!htmlPath) {
htmlPath = 'file://' + path.join(__dirname, 'index.html');
}
@ -335,7 +337,10 @@ function setMenu() {
},
{
label: 'Window',
submenu: [{ accelerator: 'CmdOrCtrl+M', role: 'minimize' }, { accelerator: 'Command+W', role: 'close' }]
submenu: [
{ accelerator: 'CmdOrCtrl+M', role: 'minimize' },
{ accelerator: 'Command+W', role: 'close' }
]
}
];
const menu = electron.Menu.buildFromTemplate(template);
@ -406,15 +411,21 @@ function subscribePowerEvents() {
emitBackboneEvent('power-monitor-resume');
});
if (process.platform === 'darwin') {
const id = electron.systemPreferences.subscribeNotification('com.apple.screenIsLocked', () => {
emitBackboneEvent('os-lock');
});
const id = electron.systemPreferences.subscribeNotification(
'com.apple.screenIsLocked',
() => {
emitBackboneEvent('os-lock');
}
);
systemNotificationIds.push(id);
}
}
function setEnv() {
if (process.platform === 'linux' && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) {
if (
process.platform === 'linux' &&
['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1
) {
// https://github.com/electron/electron/issues/9046
process.env.XDG_CURRENT_DESKTOP = 'Unity';
}
@ -509,8 +520,10 @@ function coerceMainWindowPositionToConnectedDisplay() {
// 160px width and 2/3s the title bar height should be enough that the user can grab it
for (let i = 0; i < displays.length; ++i) {
const workArea = displays[i].workArea;
const overlapWidth = Math.min(tbRight, workArea.x + workArea.width) - Math.max(tbLeft, workArea.x);
const overlapHeight = Math.min(tbBottom, workArea.y + workArea.height) - Math.max(tbTop, workArea.y);
const overlapWidth =
Math.min(tbRight, workArea.x + workArea.width) - Math.max(tbLeft, workArea.x);
const overlapHeight =
Math.min(tbBottom, workArea.y + workArea.height) - Math.max(tbTop, workArea.y);
if (overlapWidth >= 160 && 3 * overlapHeight >= 2 * (tbBottom - tbTop)) return;
}
// If we get here, no display contains a big enough strip of the title bar

View File

@ -22,11 +22,15 @@ try {
} catch (e) {}
if (userPackageStat) {
const packageStat = fs.statSync(appFilePath);
const userPackageStatTime = Math.max(userPackageStat.mtime.getTime(), userPackageStat.ctime.getTime());
const userPackageStatTime = Math.max(
userPackageStat.mtime.getTime(),
userPackageStat.ctime.getTime()
);
const packageStatTime = Math.max(packageStat.mtime.getTime(), packageStat.ctime.getTime());
if (userPackageStatTime > packageStatTime) {
let versionLocal = require('./package.json').version;
let versionUserData = require(path.join(userDataAppArchivePath, 'package.json')).version;
let versionUserData = require(path.join(userDataAppArchivePath, 'package.json'))
.version;
versionLocal = versionLocal.split('.');
versionUserData = versionUserData.split('.');
for (let i = 0; i < versionLocal.length; i++) {

View File

@ -5,25 +5,32 @@
*/
module.exports.getSettings = function() {
return [{
name: 'MyText',
label: 'Text setting',
type: 'text',
maxlength: 20,
placeholder: 'Please enter something',
value: ''
}, {
name: 'MySel',
label: 'Select setting',
type: 'select',
options: [{value: 'apple', label: 'Green apple'}, {value: 'banana', label: 'Yellow banana'}],
value: 'banana'
}, {
name: 'MyCheckbox',
label: 'Checkbox setting',
type: 'checkbox',
value: true
}];
return [
{
name: 'MyText',
label: 'Text setting',
type: 'text',
maxlength: 20,
placeholder: 'Please enter something',
value: ''
},
{
name: 'MySel',
label: 'Select setting',
type: 'select',
options: [
{ value: 'apple', label: 'Green apple' },
{ value: 'banana', label: 'Yellow banana' }
],
value: 'banana'
},
{
name: 'MyCheckbox',
label: 'Checkbox setting',
type: 'checkbox',
value: true
}
];
};
module.exports.setSettings = function(changes) {
@ -32,9 +39,7 @@ module.exports.setSettings = function(changes) {
// 1. when any of settings fields is modified by user
// 2. after plugin startup, with saved values
// only changed settings will be passed
// example: { MyText: 'value', MySel: 'selected-value', MyCheckbox: true }
};
module.exports.uninstall = function() {
};
module.exports.uninstall = function() {};

View File

@ -18,9 +18,15 @@ const pkg = require('./package.json');
const op = args.shift();
const bumpVersion = args.some(arg => arg === '--bump-version');
const privateKeyPath = args.filter(arg => arg.startsWith('--private-key=')).map(arg => arg.replace('--private-key=', ''))[0];
const signerModule = args.filter(arg => arg.startsWith('--signer-module=')).map(arg => arg.replace('--signer-module=', ''))[0];
const serverPort = args.filter(arg => arg.startsWith('--port=')).map(arg => arg.replace('--port=', ''))[0];
const privateKeyPath = args
.filter(arg => arg.startsWith('--private-key='))
.map(arg => arg.replace('--private-key=', ''))[0];
const signerModule = args
.filter(arg => arg.startsWith('--signer-module='))
.map(arg => arg.replace('--signer-module=', ''))[0];
const serverPort = args
.filter(arg => arg.startsWith('--port='))
.map(arg => arg.replace('--port=', ''))[0];
showBanner();
@ -83,19 +89,24 @@ function signPlugin(packageName) {
});
});
}
signPromise.then(changed => {
if (changed) {
if (bumpVersion) {
manifest.version = manifest.version.replace(/\d+$/, v => +v + 1);
signPromise
.then(changed => {
if (changed) {
if (bumpVersion) {
manifest.version = manifest.version.replace(/\d+$/, v => +v + 1);
}
fs.writeFileSync(
path.join(packageName, 'manifest.json'),
JSON.stringify(manifest, null, 2)
);
console.log('Done, package manifest updated');
} else {
console.log('No changes');
}
fs.writeFileSync(path.join(packageName, 'manifest.json'), JSON.stringify(manifest, null, 2));
console.log('Done, package manifest updated');
} else {
console.log('No changes');
}
}).catch(e => {
console.error('Error', e);
});
})
.catch(e => {
console.error('Error', e);
});
}
function signResource(packageName, fileName) {
@ -104,7 +115,10 @@ function signResource(packageName, fileName) {
if (signerModule) {
return require(signerModule)(data);
} else {
const privateKey = fs.readFileSync(privateKeyPath || path.join(packageName, 'private_key.pem'), 'binary');
const privateKey = fs.readFileSync(
privateKeyPath || path.join(packageName, 'private_key.pem'),
'binary'
);
return Promise.resolve().then(() => {
const sign = crypto.createSign('RSA-SHA256');
sign.write(data);
@ -151,14 +165,20 @@ function servePlugin(packageName) {
} else {
https.get('https://app.keeweb.info', kwRes => {
if (kwRes.statusCode !== 200) {
console.error('Error loading https://app.keeweb.info: HTTP status ' + kwRes.statusCode);
console.error(
'Error loading https://app.keeweb.info: HTTP status ' + kwRes.statusCode
);
res.writeHead(500);
return res.end('Error loading https://app.keeweb.info: HTTP status ' + kwRes.statusCode);
return res.end(
'Error loading https://app.keeweb.info: HTTP status ' + kwRes.statusCode
);
}
const data = [];
kwRes.on('data', chunk => data.push(chunk));
kwRes.on('end', () => {
keeWebHtmlCached = Buffer.concat(data).toString('utf8').replace('(no-config)', 'config.json');
keeWebHtmlCached = Buffer.concat(data)
.toString('utf8')
.replace('(no-config)', 'config.json');
serveKeeWebHtml(res);
});
kwRes.on('error', e => {
@ -177,38 +197,43 @@ function servePlugin(packageName) {
res.writeHead(200);
res.end(`{"settings":{},"plugins":[{"url":"/"}]}`);
};
https.createServer(options, (req, res) => {
console.log('GET', req.connection.remoteAddress, req.url);
const filePath = path.resolve(packageName, '.' + req.url.replace(/\.\./g, '').replace(/\?.*/, ''));
const packagePath = path.resolve(packageName);
if (!filePath.startsWith(packagePath)) {
res.writeHead(404);
res.end('Not found');
return;
}
if (req.url === '/') {
return serveKeeWebHtml(res);
} else if (req.url === '/manifest.appcache') {
return serveManifestAppCache(res);
} else if (req.url === '/config.json') {
return serveConfig(res);
}
fs.readFile(filePath, (err, data) => {
if (err) {
https
.createServer(options, (req, res) => {
console.log('GET', req.connection.remoteAddress, req.url);
const filePath = path.resolve(
packageName,
'.' + req.url.replace(/\.\./g, '').replace(/\?.*/, '')
);
const packagePath = path.resolve(packageName);
if (!filePath.startsWith(packagePath)) {
res.writeHead(404);
res.end('Not found');
} else {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', true);
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.setHeader('Content-type', 'text/plain');
res.writeHead(200);
res.end(data);
return;
}
});
}).listen(port);
if (req.url === '/') {
return serveKeeWebHtml(res);
} else if (req.url === '/manifest.appcache') {
return serveManifestAppCache(res);
} else if (req.url === '/config.json') {
return serveConfig(res);
}
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end('Not found');
} else {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', true);
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.setHeader('Content-type', 'text/plain');
res.writeHead(200);
res.end(data);
}
});
})
.listen(port);
console.log(`Open this URL in your browser or add it to KeeWeb: https://127.0.0.1:${port}`);
console.log('If you see a browser warning about an unsafe website, click Proceed, it\'s safe.');
console.log("If you see a browser warning about an unsafe website, click Proceed, it's safe.");
}
function getPackageArg() {

View File

@ -3,5 +3,8 @@ const fs = require('fs');
const langs = ['de-DE', 'fr-FR'];
for (const lang of langs) {
fs.writeFileSync(`app/scripts/locales/${lang}.json`, fs.readFileSync(`../keeweb-plugins/docs/translations/${lang}/${lang}.json`));
fs.writeFileSync(
`app/scripts/locales/${lang}.json`,
fs.readFileSync(`../keeweb-plugins/docs/translations/${lang}/${lang}.json`)
);
}

11
util/prettier.sh Executable file
View File

@ -0,0 +1,11 @@
prettier --write \
'app/**/*.js' \
'app/**/*.scss' \
'app/**/*.json' \
'app/**/*.html' \
'build/**/*.js' \
'desktop/**/*.js' \
'plugins/**/*.js' \
'util/**/*.js' \
'*.js' \
'package.json'

View File

@ -83,18 +83,26 @@ function config(grunt, mode = 'production') {
replacements: [
{
pattern: /@@VERSION/g,
replacement: () => pkg.version + (grunt.option('beta') ? '-beta' : '')
replacement: () =>
pkg.version + (grunt.option('beta') ? '-beta' : '')
},
{
pattern: /@@BETA/g,
replacement: () => (grunt.option('beta') ? '1' : '')
},
{ pattern: /@@BETA/g, replacement: () => (grunt.option('beta') ? '1' : '') },
{ pattern: /@@DATE/g, replacement: () => dt },
{
pattern: /@@COMMIT/g,
replacement: () => grunt.config.get('gitinfo.local.branch.current.shortSHA')
replacement: () =>
grunt.config.get('gitinfo.local.branch.current.shortSHA')
}
]
})
},
{ test: /baron(\.min)?\.js$/, loader: 'exports-loader?baron; delete window.baron;' },
{
test: /baron(\.min)?\.js$/,
loader: 'exports-loader?baron; delete window.baron;'
},
{ test: /pikaday\.js$/, loader: 'uglify-loader' },
{ test: /handlebars/, loader: 'strip-sourcemap-loader' },
{