mirror of https://github.com/keeweb/keeweb.git
prettier
This commit is contained in:
parent
176c2a6edd
commit
fa4ff0b0c3
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none",
|
||||
"quoteProps": "preserve"
|
||||
}
|
||||
|
|
76
Gruntfile.js
76
Gruntfile.js
|
@ -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'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
108
app/index.html
108
app/index.html
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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' });
|
||||
};
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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()`;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ const PasswordGenerator = {
|
|||
digits: '123456789',
|
||||
special: '!@#$%^&*_+-=,./?;:`"~\'\\',
|
||||
brackets: '(){}[]<>',
|
||||
high: '¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
|
||||
high:
|
||||
'¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
|
||||
ambiguous: 'O0oIl'
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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'])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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(', ')
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 + ')';
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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() {};
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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`)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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'
|
|
@ -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' },
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue