From 84a23e4aeabbec46820ec43c376c10a502198b1c Mon Sep 17 00:00:00 2001 From: antelle Date: Fri, 16 Aug 2019 23:05:39 +0200 Subject: [PATCH] prettier --- .eslintrc | 3 +- .prettierrc | 7 + Gruntfile.js | 103 +- app/scripts/app.js | 89 +- app/scripts/auto-type/auto-type-filter.js | 12 +- app/scripts/auto-type/auto-type-obfuscator.js | 14 +- app/scripts/auto-type/auto-type-runner.js | 158 +- .../emitter/auto-type-emitter-darwin.js | 56 +- .../emitter/auto-type-emitter-linux.js | 58 +- .../emitter/auto-type-emitter-win32.js | 56 +- .../helper/auto-type-helper-darwin.js | 31 +- .../helper/auto-type-helper-linux.js | 3 +- .../helper/auto-type-helper-win32.js | 10 +- app/scripts/auto-type/index.js | 20 +- app/scripts/collections/entry-collection.js | 6 +- .../collections/file-info-collection.js | 19 +- app/scripts/comp/alerts.js | 97 +- app/scripts/comp/app-rights-checker.js | 16 +- app/scripts/comp/copy-paste.js | 14 +- app/scripts/comp/dropbox-chooser.js | 7 +- app/scripts/comp/export-api.js | 12 +- app/scripts/comp/feature-tester.js | 25 +- app/scripts/comp/generator-presets.js | 38 +- app/scripts/comp/key-handler.js | 37 +- app/scripts/comp/launcher-cordova.js | 152 +- app/scripts/comp/launcher-electron.js | 29 +- app/scripts/comp/otp-qr-reader.js | 26 +- app/scripts/comp/popup-notifier.js | 19 +- app/scripts/comp/secure-input.js | 5 +- app/scripts/comp/settings-manager.js | 7 +- app/scripts/comp/single-instance-checker.js | 4 +- app/scripts/comp/transport.js | 89 +- app/scripts/comp/updater.js | 21 +- app/scripts/const/icon-map.js | 83 +- app/scripts/helpers/add.js | 4 +- app/scripts/helpers/res.js | 2 +- app/scripts/mixins/copyable.js | 11 +- app/scripts/mixins/protected-value-ex.js | 30 +- app/scripts/mixins/view.js | 2 +- app/scripts/models/app-model.js | 242 ++-- app/scripts/models/app-settings-model.js | 2 +- app/scripts/models/attachment-model.js | 121 +- app/scripts/models/entry-model.js | 83 +- app/scripts/models/file-info-model.js | 14 +- app/scripts/models/file-model.js | 109 +- app/scripts/models/group-model.js | 70 +- app/scripts/models/menu/menu-model.js | 72 +- app/scripts/models/runtime-data-model.js | 2 +- app/scripts/models/update-model.js | 8 +- app/scripts/plugins/plugin-gallery.js | 21 +- app/scripts/plugins/plugin-manager.js | 74 +- app/scripts/plugins/plugin.js | 1049 +++++++------- app/scripts/plugins/theme-vars.js | 6 +- app/scripts/presenters/entry-presenter.js | 60 +- app/scripts/storage/io-browser-cache.js | 63 +- app/scripts/storage/io-file-cache.js | 24 +- app/scripts/storage/storage-base.js | 38 +- app/scripts/storage/storage-dropbox.js | 65 +- app/scripts/storage/storage-file-cache.js | 6 +- app/scripts/storage/storage-file.js | 8 +- app/scripts/storage/storage-gdrive.js | 88 +- app/scripts/storage/storage-onedrive.js | 50 +- app/scripts/storage/storage-webdav.js | 293 ++-- app/scripts/util/color.js | 12 +- app/scripts/util/comparators.js | 27 +- app/scripts/util/feature-detector.js | 18 +- app/scripts/util/format.js | 25 +- app/scripts/util/id-generator.js | 4 +- app/scripts/util/kdbxweb-init.js | 45 +- app/scripts/util/logger.js | 2 +- app/scripts/util/otp.js | 27 +- app/scripts/util/password-generator.js | 4 +- app/scripts/util/phonetic.js | 102 +- app/scripts/util/signature-verifier.js | 52 +- app/scripts/util/tip.js | 21 +- app/scripts/views/app-view.js | 96 +- app/scripts/views/auto-type-hint-view.js | 6 +- .../views/auto-type/auto-type-select-view.js | 6 +- .../views/details/details-add-field-view.js | 2 +- .../views/details/details-attachment-view.js | 14 +- .../views/details/details-auto-type-view.js | 7 +- .../views/details/details-history-view.js | 193 ++- app/scripts/views/details/details-view.js | 304 +++- app/scripts/views/drag-view.js | 10 +- app/scripts/views/dropdown-view.js | 6 +- .../views/fields/field-view-autocomplete.js | 17 +- app/scripts/views/fields/field-view-custom.js | 14 +- app/scripts/views/fields/field-view-date.js | 4 +- app/scripts/views/fields/field-view-select.js | 25 +- app/scripts/views/fields/field-view-tags.js | 23 +- app/scripts/views/fields/field-view-text.js | 25 +- app/scripts/views/fields/field-view-url.js | 8 +- app/scripts/views/fields/field-view.js | 18 +- app/scripts/views/footer-view.js | 23 +- app/scripts/views/generator-presets-view.js | 24 +- app/scripts/views/generator-view.js | 12 +- app/scripts/views/grp-view.js | 31 +- app/scripts/views/icon-select-view.js | 28 +- app/scripts/views/key-change-view.js | 5 +- app/scripts/views/list-search-view.js | 92 +- app/scripts/views/list-view.js | 16 +- app/scripts/views/list-wrap-view.js | 3 +- app/scripts/views/menu/menu-item-view.js | 11 +- app/scripts/views/menu/menu-section-view.js | 6 +- app/scripts/views/menu/menu-view.js | 22 +- app/scripts/views/modal-view.js | 4 +- app/scripts/views/open-config-view.js | 3 +- app/scripts/views/open-view.js | 105 +- .../views/settings/settings-file-view.js | 79 +- .../views/settings/settings-general-view.js | 14 +- .../views/settings/settings-help-view.js | 21 +- .../views/settings/settings-logs-view.js | 6 +- .../views/settings/settings-plugins-view.js | 36 +- .../views/settings/settings-prv-view.js | 2 +- app/scripts/views/settings/settings-view.js | 8 +- app/scripts/views/storage-file-list-view.js | 4 +- app/scripts/views/tag-view.js | 9 +- app/styles/areas/_app.scss | 257 ++-- app/styles/areas/_auto-type.scss | 220 +-- app/styles/areas/_details.scss | 1267 +++++++++-------- app/styles/areas/_footer.scss | 154 +- app/styles/areas/_generator-presets.scss | 67 +- app/styles/areas/_generator.scss | 116 +- app/styles/areas/_grp.scss | 74 +- app/styles/areas/_key-change.scss | 106 +- app/styles/areas/_list.scss | 359 ++--- app/styles/areas/_menu.scss | 364 ++--- app/styles/areas/_open.scss | 620 ++++---- app/styles/areas/_settings.scss | 505 ++++--- app/styles/areas/_tag.scss | 34 +- app/styles/base/_base.scss | 20 +- app/styles/base/_body.scss | 16 +- app/styles/base/_buttons.scss | 175 +-- app/styles/base/_colors.scss | 38 +- app/styles/base/_forms.scss | 447 +++--- app/styles/base/_lists.scss | 42 +- app/styles/base/_media.scss | 18 +- app/styles/base/_theme-vars-support.scss | 12 +- app/styles/base/_theme-vars.scss | 81 +- app/styles/base/_themes.scss | 43 +- app/styles/base/_typography.scss | 121 +- app/styles/base/_variables.scss | 57 +- app/styles/common/_dates.scss | 101 +- app/styles/common/_dropdown.scss | 48 +- app/styles/common/_empty.scss | 39 +- app/styles/common/_fx.scss | 57 +- app/styles/common/_icon-select.scss | 72 +- app/styles/common/_modal.scss | 121 +- app/styles/common/_scroll.scss | 82 +- app/styles/common/_tip.scss | 193 +-- app/styles/main.scss | 64 +- app/styles/themes/_all-themes.scss | 16 +- app/styles/themes/_dark-brown.scss | 25 +- app/styles/themes/_flat-blue.scss | 25 +- app/styles/themes/_high-contrast.scss | 31 +- app/styles/themes/_solarized-dark.scss | 29 +- app/styles/themes/_solarized-light.scss | 27 +- app/styles/themes/_terminal.scss | 25 +- app/styles/themes/_theme-defaults.scss | 10 +- app/styles/themes/_white.scss | 25 +- app/styles/utils/_auto-type-hint.scss | 43 +- app/styles/utils/_back-button.scss | 12 +- app/styles/utils/_common-dropdown.scss | 18 +- app/styles/utils/_drag.scss | 45 +- app/styles/utils/_selection.scss | 55 +- build/tasks/grunt-codesign.js | 36 +- build/tasks/grunt-nsis.js | 31 +- build/tasks/grunt-sign-archive.js | 4 +- build/tasks/grunt-sign-desktop-files.js | 4 +- build/tasks/grunt-sign-dist.js | 4 +- build/tasks/grunt-sign-exe.js | 114 +- build/tasks/grunt-sign-html.js | 32 +- build/tasks/grunt-validate-desktop-update.js | 4 +- desktop/app.js | 68 +- desktop/main.js | 2 +- grunt.entrypoints.js | 5 + grunt.tasks.js | 20 +- package.json | 191 +-- webpack.config.js | 61 +- 179 files changed, 7485 insertions(+), 5091 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc b/.eslintrc index 8b48b14f..cbccbe41 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,6 @@ { - "extends": "standard", + "extends": ["standard", "plugin:prettier/recommended"], "rules": { - "indent": ["error", 4, { "SwitchCase": 1 }], "semi": ["error", "always"], "one-var": "off", "space-before-function-paren": "off", diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..b28a7fcb --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "tabWidth": 4, + "singleQuote": true, + "printWidth": 120, + "trailingComma": "none", + "quoteProps": "preserve" +} diff --git a/Gruntfile.js b/Gruntfile.js index adb2e29e..04c8b948 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -144,7 +144,7 @@ module.exports = function(grunt) { eslint: { app: ['app/scripts/**/*.js'], desktop: ['desktop/**/*.js', '!desktop/node_modules/**'], - grunt: ['Gruntfile.js', 'grunt/**/*.js'] + grunt: ['Gruntfile.js', 'build/**/*.js'] }, inline: { app: { @@ -182,12 +182,21 @@ module.exports = function(grunt) { files: { 'tmp/desktop/app/index.html': 'dist/index.html' } }, 'desktop-public-key': { - options: { replacements: [{ pattern: '\'@@PUBLIC_KEY_CONTENT\'', replacement: - '`' + fs.readFileSync('app/resources/public-key.pem', {encoding: 'utf8'}).trim() + '`' }] }, + options: { + replacements: [ + { + pattern: "'@@PUBLIC_KEY_CONTENT'", + replacement: + '`' + fs.readFileSync('app/resources/public-key.pem', { encoding: 'utf8' }).trim() + '`' + } + ] + }, files: { 'tmp/desktop/app/main.js': 'desktop/main.js' } }, 'cordova-html': { - options: { replacements: [{ pattern: '' + appCopyright: `Copyright © ${year} Antelle`, + appVersion: pkg.version, + buildVersion: '<%= gitinfo.local.branch.current.shortSHA %>' }, linux: { options: { @@ -255,9 +264,9 @@ module.exports = function(grunt) { platform: 'darwin', arch: ['x64'], icon: 'graphics/icon.icns', - 'appBundleId': 'net.antelle.keeweb', - 'appCategoryType': 'public.app-category.productivity', - 'extendInfo': 'package/osx/extend.plist' + appBundleId: 'net.antelle.keeweb', + appCategoryType: 'public.app-category.productivity', + extendInfo: 'package/osx/extend.plist' } }, win32: { @@ -265,13 +274,13 @@ module.exports = function(grunt) { platform: 'win32', arch: ['ia32', 'x64'], icon: 'graphics/icon.ico', - 'buildVersion': pkg.version, + buildVersion: pkg.version, 'version-string': { - 'CompanyName': 'KeeWeb', - 'FileDescription': pkg.description, - 'OriginalFilename': 'KeeWeb.exe', - 'ProductName': 'KeeWeb', - 'InternalName': 'KeeWeb' + CompanyName: 'KeeWeb', + FileDescription: pkg.description, + OriginalFilename: 'KeeWeb.exe', + ProductName: 'KeeWeb', + InternalName: 'KeeWeb' } } } @@ -297,9 +306,7 @@ module.exports = function(grunt) { }, 'desktop-update': { options: { archive: 'dist/desktop/UpdateDesktop.zip', comment: zipCommentPlaceholder }, - files: [ - { cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true } - ] + files: [{ cwd: 'tmp/desktop/update', src: '**', expand: true, nonull: true }] }, 'win32-x64': { options: { archive: `dist/desktop/KeeWeb-${pkg.version}.win.x64.zip` }, @@ -311,13 +318,17 @@ module.exports = function(grunt) { }, 'linux-x64': { options: { archive: `dist/desktop/KeeWeb-${pkg.version}.linux.x64.zip` }, - files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }, - { cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }] + files: [ + { cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }, + { cwd: 'graphics', src: '128x128.png', nonull: true, expand: true } + ] }, 'linux-ia32': { options: { archive: `dist/desktop/KeeWeb-${pkg.version}.linux.ia32.zip` }, - files: [{ cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true }, - { cwd: 'graphics', src: '128x128.png', nonull: true, expand: true }] + files: [ + { cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true }, + { cwd: 'graphics', src: '128x128.png', nonull: true, expand: true } + ] } }, appdmg: { @@ -341,7 +352,9 @@ module.exports = function(grunt) { options: { vars: { version: pkg.version, - rev: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); }, + rev: function() { + return grunt.config.get('gitinfo.local.branch.current.shortSHA'); + }, homepage: pkg.homepage } }, @@ -383,7 +396,9 @@ module.exports = function(grunt) { description: pkg.description, author: pkg.author, homepage: pkg.homepage, - rev: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); } + rev: function() { + return grunt.config.get('gitinfo.local.branch.current.shortSHA'); + } } }, 'linux-x64': { @@ -401,8 +416,18 @@ module.exports = function(grunt) { }, files: [ { cwd: 'package/deb/usr', src: '**', dest: '/usr', expand: true, nonull: true }, - { cwd: 'tmp/desktop/KeeWeb-linux-x64/', src: '**', dest: '/opt/keeweb-desktop', expand: true, nonull: true }, - { src: 'graphics/128x128.png', dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', nonull: true } + { + cwd: 'tmp/desktop/KeeWeb-linux-x64/', + src: '**', + dest: '/opt/keeweb-desktop', + expand: true, + nonull: true + }, + { + src: 'graphics/128x128.png', + dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', + nonull: true + } ] }, 'linux-ia32': { @@ -420,8 +445,18 @@ module.exports = function(grunt) { }, files: [ { cwd: 'package/deb/usr', src: '**', dest: '/usr', expand: true, nonull: true }, - { cwd: 'tmp/desktop/KeeWeb-linux-ia32/', src: '**', dest: '/opt/keeweb-desktop', expand: true, nonull: true }, - { src: 'graphics/128x128.png', dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', nonull: true } + { + cwd: 'tmp/desktop/KeeWeb-linux-ia32/', + src: '**', + dest: '/opt/keeweb-desktop', + expand: true, + nonull: true + }, + { + src: 'graphics/128x128.png', + dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', + nonull: true + } ] } }, @@ -444,18 +479,14 @@ module.exports = function(grunt) { desktop: { options: { file: 'dist/desktop/UpdateDesktop.zip', - expected: [ - 'app.asar', - 'helper/darwin/KeeWebHelper', - 'helper/win32/KeeWebHelper.exe' - ], + expected: ['app.asar', 'helper/darwin/KeeWebHelper', 'helper/win32/KeeWebHelper.exe'], expectedCount: 7, publicKey: 'app/resources/public-key.pem' } } }, 'sign-html': { - 'app': { + app: { options: { file: 'dist/index.html', skip: grunt.option('skip-sign') @@ -521,7 +552,7 @@ module.exports = function(grunt) { } }, 'sign-dist': { - 'dist': { + dist: { options: { sign: 'dist/desktop/Verify.sign.sha256' }, diff --git a/app/scripts/app.js b/app/scripts/app.js index a7c8bdbf..e9de38ab 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -24,7 +24,7 @@ const FeatureDetector = require('./util/feature-detector'); const KdbxwebInit = require('./util/kdbxweb-init'); const Locale = require('./util/locale'); -const ready = Launcher && Launcher.ready || $; +const ready = (Launcher && Launcher.ready) || $; ready(() => { if (AuthReceiver.receive() || FeatureDetector.isFrame) { @@ -51,16 +51,17 @@ ready(() => { } function ensureCanRun() { - return FeatureTester.test() - .catch(e => { - Alerts.error({ - header: Locale.appSettingsError, - body: Locale.appNotSupportedError + '

' + e, - buttons: [], - esc: false, enter: false, click: false - }); - throw 'Feature testing failed: ' + e; + return FeatureTester.test().catch(e => { + Alerts.error({ + header: Locale.appSettingsError, + body: Locale.appNotSupportedError + '

' + e, + buttons: [], + esc: false, + enter: false, + click: false }); + throw 'Feature testing failed: ' + e; + }); } function loadConfigs() { @@ -87,7 +88,9 @@ ready(() => { header: Locale.appSettingsError, body: Locale.appSettingsErrorBody, buttons: [], - esc: false, enter: false, click: false + esc: false, + enter: false, + click: false }); } @@ -96,42 +99,46 @@ ready(() => { SettingsManager.setBySettings(appModel.settings); const configParam = getConfigParam(); if (configParam) { - return appModel.loadConfig(configParam).then(() => { - SettingsManager.setBySettings(appModel.settings); - }).catch(e => { - if (!appModel.settings.get('cacheConfigSettings')) { - showSettingsLoadError(); - throw e; - } - }); + return appModel + .loadConfig(configParam) + .then(() => { + SettingsManager.setBySettings(appModel.settings); + }) + .catch(e => { + if (!appModel.settings.get('cacheConfigSettings')) { + showSettingsLoadError(); + throw e; + } + }); } }); } function showApp() { - return Promise.resolve() - .then(() => { - const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning'); - const protocolIsInsecure = ['https:', 'file:', 'app:'].indexOf(location.protocol) < 0; - const hostIsInsecure = location.hostname !== 'localhost'; - if (protocolIsInsecure && hostIsInsecure && !skipHttpsWarning) { - return new Promise(resolve => { - Alerts.error({ - header: Locale.appSecWarn, icon: 'user-secret', esc: false, enter: false, click: false, - body: Locale.appSecWarnBody1 + '

' + Locale.appSecWarnBody2, - buttons: [ - {result: '', title: Locale.appSecWarnBtn, error: true} - ], - complete: () => { - showView(); - resolve(); - } - }); + return Promise.resolve().then(() => { + const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning'); + const protocolIsInsecure = ['https:', 'file:', 'app:'].indexOf(location.protocol) < 0; + const hostIsInsecure = location.hostname !== 'localhost'; + if (protocolIsInsecure && hostIsInsecure && !skipHttpsWarning) { + return new Promise(resolve => { + Alerts.error({ + header: Locale.appSecWarn, + icon: 'user-secret', + esc: false, + enter: false, + click: false, + body: Locale.appSecWarnBody1 + '

' + Locale.appSecWarnBody2, + buttons: [{ result: '', title: Locale.appSecWarnBtn, error: true }], + complete: () => { + showView(); + resolve(); + } }); - } else { - showView(); - } - }); + }); + } else { + showView(); + } + }); } function postInit() { diff --git a/app/scripts/auto-type/auto-type-filter.js b/app/scripts/auto-type/auto-type-filter.js index d6cb8d1c..92e2b533 100644 --- a/app/scripts/auto-type/auto-type-filter.js +++ b/app/scripts/auto-type/auto-type-filter.js @@ -17,14 +17,13 @@ AutoTypeFilter.prototype.getEntries = function() { autoType: true }; this.prepareFilter(); - let entries = this.appModel.getEntriesByFilter(filter) - .map(e => [e, this.getEntryRank(e)]); + let entries = this.appModel.getEntriesByFilter(filter).map(e => [e, this.getEntryRank(e)]); if (!this.ignoreWindowInfo) { entries = entries.filter(e => e[1]); } - entries = entries.sort((x, y) => x[1] === y[1] ? x[0].title.localeCompare(y[0].title) : y[1] - x[1]); + entries = entries.sort((x, y) => (x[1] === y[1] ? x[0].title.localeCompare(y[0].title) : y[1] - x[1])); entries = entries.map(p => p[0]); - return new EntryCollection(entries, {comparator: 'none'}); + return new EntryCollection(entries, { comparator: 'none' }); }; AutoTypeFilter.prototype.hasWindowInfo = function() { @@ -40,10 +39,7 @@ AutoTypeFilter.prototype.prepareFilter = function() { AutoTypeFilter.prototype.getEntryRank = function(entry) { let rank = 0; if (this.titleLower && entry.title) { - rank += Ranking.getStringRank( - entry.title.toLowerCase(), - this.titleLower - ); + rank += Ranking.getStringRank(entry.title.toLowerCase(), this.titleLower); } if (this.urlParts && entry.url) { const entryUrlParts = urlPartsRegex.exec(entry.url.toLowerCase()); diff --git a/app/scripts/auto-type/auto-type-obfuscator.js b/app/scripts/auto-type/auto-type-obfuscator.js index eba15f7b..6fa494a9 100644 --- a/app/scripts/auto-type/auto-type-obfuscator.js +++ b/app/scripts/auto-type/auto-type-obfuscator.js @@ -33,8 +33,12 @@ AutoTypeObfuscator.prototype.obfuscate = function() { }; AutoTypeObfuscator.prototype.finished = function() { - return this.chars.length === this.inputChars.length && - this.chars.every(function(ch, ix) { return this.inputChars[ix].ch === ch; }, this); + return ( + this.chars.length === this.inputChars.length && + this.chars.every(function(ch, ix) { + return this.inputChars[ix].ch === ch; + }, this) + ); }; AutoTypeObfuscator.prototype.step = function() { @@ -161,7 +165,7 @@ AutoTypeObfuscator.prototype.inputChar = function(ch) { AutoTypeObfuscator.prototype.copyPaste = function(ch) { logger.debug('copyPaste', ch); - this.ops.push({type: 'cmd', value: 'copyPaste', arg: ch}); + this.ops.push({ type: 'cmd', value: 'copyPaste', arg: ch }); this.inputChars.splice(this.inputCursor, this.inputSel, { ch: ch }); this.inputCursor++; this.inputSel = 0; @@ -174,10 +178,10 @@ AutoTypeObfuscator.prototype.selectText = function(backward, count) { ops.push({ type: 'key', value: backward ? 'left' : 'right' }); } if (ops.length === 1) { - ops[0].mod = {'+': true}; + ops[0].mod = { '+': true }; this.ops.push(ops[0]); } else { - this.ops.push({type: 'group', value: ops, mod: {'+': true}}); + this.ops.push({ type: 'group', value: ops, mod: { '+': true } }); } if (backward) { this.inputCursor -= count; diff --git a/app/scripts/auto-type/auto-type-runner.js b/app/scripts/auto-type/auto-type-runner.js index 2e443c05..17a68e1a 100644 --- a/app/scripts/auto-type/auto-type-runner.js +++ b/app/scripts/auto-type/auto-type-runner.js @@ -16,39 +16,127 @@ const AutoTypeRunner = function(ops) { AutoTypeRunner.PendingResolve = { pending: true }; AutoTypeRunner.Keys = { - tab: 'tab', enter: 'enter', space: 'space', - up: 'up', down: 'down', left: 'left', right: 'right', home: 'home', end: 'end', pgup: 'pgup', pgdn: 'pgdn', - insert: 'ins', ins: 'ins', delete: 'del', del: 'del', backspace: 'bs', bs: 'bs', bksp: 'bs', esc: 'esc', - win: 'win', lwin: 'win', rwin: 'rwin', f1: 'f1', f2: 'f2', f3: 'f3', f4: 'f4', f5: 'f5', f6: 'f6', - f7: 'f7', f8: 'f8', f9: 'f9', f10: 'f10', f11: 'f11', f12: 'f12', f13: 'f13', f14: 'f14', f15: 'f15', f16: 'f16', - add: 'add', subtract: 'subtract', multiply: 'multiply', divide: 'divide', - numpad0: 'n0', numpad1: 'n1', numpad2: 'n2', numpad3: 'n3', numpad4: 'n4', - numpad5: 'n5', numpad6: 'n6', numpad7: 'n7', numpad8: 'n8', numpad9: 'n9' + tab: 'tab', + enter: 'enter', + space: 'space', + up: 'up', + down: 'down', + left: 'left', + right: 'right', + home: 'home', + end: 'end', + pgup: 'pgup', + pgdn: 'pgdn', + insert: 'ins', + ins: 'ins', + delete: 'del', + del: 'del', + backspace: 'bs', + bs: 'bs', + bksp: 'bs', + esc: 'esc', + win: 'win', + lwin: 'win', + rwin: 'rwin', + f1: 'f1', + f2: 'f2', + f3: 'f3', + f4: 'f4', + f5: 'f5', + f6: 'f6', + f7: 'f7', + f8: 'f8', + f9: 'f9', + f10: 'f10', + f11: 'f11', + f12: 'f12', + f13: 'f13', + f14: 'f14', + f15: 'f15', + f16: 'f16', + add: 'add', + subtract: 'subtract', + multiply: 'multiply', + divide: 'divide', + numpad0: 'n0', + numpad1: 'n1', + numpad2: 'n2', + numpad3: 'n3', + numpad4: 'n4', + numpad5: 'n5', + numpad6: 'n6', + numpad7: 'n7', + numpad8: 'n8', + numpad9: 'n9' }; AutoTypeRunner.Substitutions = { - title: function(runner, op) { return runner.getEntryFieldKeys('Title', op); }, - username: function(runner, op) { return runner.getEntryFieldKeys('UserName', op); }, - url: function(runner, op) { return runner.getEntryFieldKeys('URL', op); }, - password: function(runner, op) { return runner.getEntryFieldKeys('Password', op); }, - notes: function(runner, op) { return runner.getEntryFieldKeys('Notes', op); }, - group: function(runner) { return runner.getEntryGroupName(); }, - totp: function(runner, op) { return runner.getOtp(op); }, - s: function(runner, op) { return runner.getEntryFieldKeys(op.arg, op); }, - 'dt_simple': function(runner) { return runner.dt('simple'); }, - 'dt_year': function(runner) { return runner.dt('Y'); }, - 'dt_month': function(runner) { return runner.dt('M'); }, - 'dt_day': function(runner) { return runner.dt('D'); }, - 'dt_hour': function(runner) { return runner.dt('h'); }, - 'dt_minute': function(runner) { return runner.dt('m'); }, - 'dt_second': function(runner) { return runner.dt('s'); }, - 'dt_utc_simple': function(runner) { return runner.udt('simple'); }, - 'dt_utc_year': function(runner) { return runner.udt('Y'); }, - 'dt_utc_month': function(runner) { return runner.udt('M'); }, - 'dt_utc_day': function(runner) { return runner.udt('D'); }, - 'dt_utc_hour': function(runner) { return runner.udt('h'); }, - 'dt_utc_minute': function(runner) { return runner.udt('m'); }, - 'dt_utc_second': function(runner) { return runner.udt('s'); } + title: function(runner, op) { + return runner.getEntryFieldKeys('Title', op); + }, + username: function(runner, op) { + return runner.getEntryFieldKeys('UserName', op); + }, + url: function(runner, op) { + return runner.getEntryFieldKeys('URL', op); + }, + password: function(runner, op) { + return runner.getEntryFieldKeys('Password', op); + }, + notes: function(runner, op) { + return runner.getEntryFieldKeys('Notes', op); + }, + group: function(runner) { + return runner.getEntryGroupName(); + }, + totp: function(runner, op) { + return runner.getOtp(op); + }, + s: function(runner, op) { + return runner.getEntryFieldKeys(op.arg, op); + }, + 'dt_simple': function(runner) { + return runner.dt('simple'); + }, + 'dt_year': function(runner) { + return runner.dt('Y'); + }, + 'dt_month': function(runner) { + return runner.dt('M'); + }, + 'dt_day': function(runner) { + return runner.dt('D'); + }, + 'dt_hour': function(runner) { + return runner.dt('h'); + }, + 'dt_minute': function(runner) { + return runner.dt('m'); + }, + 'dt_second': function(runner) { + return runner.dt('s'); + }, + 'dt_utc_simple': function(runner) { + return runner.udt('simple'); + }, + 'dt_utc_year': function(runner) { + return runner.udt('Y'); + }, + 'dt_utc_month': function(runner) { + return runner.udt('M'); + }, + 'dt_utc_day': function(runner) { + return runner.udt('D'); + }, + 'dt_utc_hour': function(runner) { + return runner.udt('h'); + }, + 'dt_utc_minute': function(runner) { + return runner.udt('m'); + }, + 'dt_utc_second': function(runner) { + return runner.udt('s'); + } }; AutoTypeRunner.prototype.resolve = function(entry, callback) { @@ -103,7 +191,7 @@ AutoTypeRunner.prototype.resolveOp = function(op) { op.value = []; const count = +op.arg; for (let i = 0; i < count; i++) { - op.value.push({type: 'key', value: key}); + op.value.push({ type: 'key', value: key }); } } else { // {TAB} @@ -179,9 +267,9 @@ AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) { const ops = []; value.forEachChar(ch => { if (ch === 10 || ch === 13) { - ops.push({type: 'key', value: 'enter'}); + ops.push({ type: 'key', value: 'enter' }); } else { - ops.push({type: 'text', value: String.fromCharCode(ch)}); + ops.push({ type: 'text', value: String.fromCharCode(ch) }); } }); return ops; @@ -194,10 +282,10 @@ AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) { const partsOps = []; parts.forEach(part => { if (partsOps.length) { - partsOps.push({type: 'key', value: 'enter'}); + partsOps.push({ type: 'key', value: 'enter' }); } if (part) { - partsOps.push({type: 'text', value: part}); + partsOps.push({ type: 'text', value: part }); } }); return partsOps; diff --git a/app/scripts/auto-type/emitter/auto-type-emitter-darwin.js b/app/scripts/auto-type/emitter/auto-type-emitter-darwin.js index 6fb22f9a..fdabd8b0 100644 --- a/app/scripts/auto-type/emitter/auto-type-emitter-darwin.js +++ b/app/scripts/auto-type/emitter/auto-type-emitter-darwin.js @@ -3,15 +3,53 @@ const AutoTypeNativeHelper = require('../helper/auto-type-native-helper'); // http://eastmanreference.com/complete-list-of-applescript-key-codes/ const KeyMap = { - tab: 48, enter: 36, space: 49, - up: 126, down: 125, left: 123, right: 124, home: 115, end: 119, pgup: 116, pgdn: 121, - ins: 114, del: 117, bs: 51, esc: 53, - win: 55, rwin: 55, - f1: 122, f2: 120, f3: 99, f4: 118, f5: 96, f6: 97, f7: 98, f8: 100, f9: 101, - f10: 109, f11: 103, f12: 111, f13: 105, f14: 107, f15: 113, f16: 106, - add: 69, subtract: 78, multiply: 67, divide: 75, - n0: 82, n1: 83, n2: 84, n3: 85, n4: 86, - n5: 87, n6: 88, n7: 89, n8: 91, n9: 92 + tab: 48, + enter: 36, + space: 49, + up: 126, + down: 125, + left: 123, + right: 124, + home: 115, + end: 119, + pgup: 116, + pgdn: 121, + ins: 114, + del: 117, + bs: 51, + esc: 53, + win: 55, + rwin: 55, + f1: 122, + f2: 120, + f3: 99, + f4: 118, + f5: 96, + f6: 97, + f7: 98, + f8: 100, + f9: 101, + f10: 109, + f11: 103, + f12: 111, + f13: 105, + f14: 107, + f15: 113, + f16: 106, + add: 69, + subtract: 78, + multiply: 67, + divide: 75, + n0: 82, + n1: 83, + n2: 84, + n3: 85, + n4: 86, + n5: 87, + n6: 88, + n7: 89, + n8: 91, + n9: 92 }; const ModMap = { diff --git a/app/scripts/auto-type/emitter/auto-type-emitter-linux.js b/app/scripts/auto-type/emitter/auto-type-emitter-linux.js index 26a674e2..82e97113 100644 --- a/app/scripts/auto-type/emitter/auto-type-emitter-linux.js +++ b/app/scripts/auto-type/emitter/auto-type-emitter-linux.js @@ -3,15 +3,53 @@ const Locale = require('../../util/locale'); // https://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h const KeyMap = { - tab: 'Tab', enter: 'KP_Enter', space: 'KP_Space', - up: 'Up', down: 'Down', left: 'Left', right: 'Right', home: 'Home', end: 'End', pgup: 'Page_Up', pgdn: 'Page_Down', - ins: 'Insert', del: 'Delete', bs: 'BackSpace', esc: 'Escape', - win: 'Meta_L', rwin: 'Meta_R', - f1: 'F1', f2: 'F2', f3: 'F3', f4: 'F4', f5: 'F5', f6: 'F6', f7: 'F7', f8: 'F8', f9: 'F9', - f10: 'F10', f11: 'F11', f12: 'F12', f13: 'F13', f14: 'F14', f15: 'F15', f16: 'F16', - add: 'KP_Add', subtract: 'KP_Subtract', multiply: 'KP_Multiply', divide: 'KP_Divide', - n0: 'KP_0', n1: 'KP_1', n2: 'KP_2', n3: 'KP_3', n4: 'KP_4', - n5: 'KP_5', n6: 'KP_6', n7: 'KP_7', n8: 'KP_8', n9: 'KP_9' + tab: 'Tab', + enter: 'KP_Enter', + space: 'KP_Space', + up: 'Up', + down: 'Down', + left: 'Left', + right: 'Right', + home: 'Home', + end: 'End', + pgup: 'Page_Up', + pgdn: 'Page_Down', + ins: 'Insert', + del: 'Delete', + bs: 'BackSpace', + esc: 'Escape', + win: 'Meta_L', + rwin: 'Meta_R', + f1: 'F1', + f2: 'F2', + f3: 'F3', + f4: 'F4', + f5: 'F5', + f6: 'F6', + f7: 'F7', + f8: 'F8', + f9: 'F9', + f10: 'F10', + f11: 'F11', + f12: 'F12', + f13: 'F13', + f14: 'F14', + f15: 'F15', + f16: 'F16', + add: 'KP_Add', + subtract: 'KP_Subtract', + multiply: 'KP_Multiply', + divide: 'KP_Divide', + n0: 'KP_0', + n1: 'KP_1', + n2: 'KP_2', + n3: 'KP_3', + n4: 'KP_4', + n5: 'KP_5', + n6: 'KP_6', + n7: 'KP_7', + n8: 'KP_8', + n9: 'KP_9' }; const ModMap = { @@ -69,7 +107,7 @@ AutoTypeEmitter.prototype.copyPaste = function(text) { }; AutoTypeEmitter.prototype.wait = function(time) { - this.pendingScript.push('sleep ' + (time / 1000)); + this.pendingScript.push('sleep ' + time / 1000); this.callback(); }; diff --git a/app/scripts/auto-type/emitter/auto-type-emitter-win32.js b/app/scripts/auto-type/emitter/auto-type-emitter-win32.js index 66ada90c..49bb7dfe 100644 --- a/app/scripts/auto-type/emitter/auto-type-emitter-win32.js +++ b/app/scripts/auto-type/emitter/auto-type-emitter-win32.js @@ -3,15 +3,53 @@ const AutoTypeNativeHelper = require('../helper/auto-type-native-helper'); // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx const KeyMap = { - tab: 0x09, enter: 0x0D, space: 0x20, - up: 0x26, down: 0x28, left: 0x25, right: 0x27, home: 0x24, end: 0x23, pgup: 0x21, pgdn: 0x22, - ins: 0x2D, del: 0x2E, bs: 0x08, esc: 0x1B, - win: 0x5B, rwin: 0x5C, - f1: 0x70, f2: 0x71, f3: 0x72, f4: 0x73, f5: 0x74, f6: 0x75, f7: 0x76, f8: 0x77, f9: 0x78, - f10: 0x79, f11: 0x7A, f12: 0x7B, f13: 0x7C, f14: 0x7D, f15: 0x7E, f16: 0x7F, - add: 0x6B, subtract: 0x6D, multiply: 0x6A, divide: 0x6F, - n0: 0x30, n1: 0x31, n2: 0x32, n3: 0x33, n4: 0x34, - n5: 0x35, n6: 0x36, n7: 0x37, n8: 0x38, n9: 0x39 + tab: 0x09, + enter: 0x0d, + space: 0x20, + up: 0x26, + down: 0x28, + left: 0x25, + right: 0x27, + home: 0x24, + end: 0x23, + pgup: 0x21, + pgdn: 0x22, + ins: 0x2d, + del: 0x2e, + bs: 0x08, + esc: 0x1b, + win: 0x5b, + rwin: 0x5c, + f1: 0x70, + f2: 0x71, + f3: 0x72, + f4: 0x73, + f5: 0x74, + f6: 0x75, + f7: 0x76, + f8: 0x77, + f9: 0x78, + f10: 0x79, + f11: 0x7a, + f12: 0x7b, + f13: 0x7c, + f14: 0x7d, + f15: 0x7e, + f16: 0x7f, + add: 0x6b, + subtract: 0x6d, + multiply: 0x6a, + divide: 0x6f, + n0: 0x30, + n1: 0x31, + n2: 0x32, + n3: 0x33, + n4: 0x34, + n5: 0x35, + n6: 0x36, + n7: 0x37, + n8: 0x38, + n9: 0x39 }; const ModMap = { diff --git a/app/scripts/auto-type/helper/auto-type-helper-darwin.js b/app/scripts/auto-type/helper/auto-type-helper-darwin.js index c8b6852a..5f4dd8d9 100644 --- a/app/scripts/auto-type/helper/auto-type-helper-darwin.js +++ b/app/scripts/auto-type/helper/auto-type-helper-darwin.js @@ -1,13 +1,17 @@ const Launcher = require('../../comp/launcher'); -const ForeMostAppScript = 'tell application "System Events" to set frontApp to name of first process whose frontmost is true'; -const ChromeScript = 'tell application "{}" to set appUrl to URL of active tab of front window\n' + +const ForeMostAppScript = + 'tell application "System Events" to set frontApp to name of first process whose frontmost is true'; +const ChromeScript = + 'tell application "{}" to set appUrl to URL of active tab of front window\n' + 'tell application "{}" to set appTitle to title of active tab of front window\n' + 'return appUrl & "\n" & appTitle'; -const SafariScript = 'tell application "{}" to set appUrl to URL of front document\n' + +const SafariScript = + 'tell application "{}" to set appUrl to URL of front document\n' + 'tell application "{}" to set appTitle to name of front document\n' + 'return appUrl & "\n" & appTitle'; -const OtherAppsScript = 'tell application "System Events"\n' + +const OtherAppsScript = + 'tell application "System Events"\n' + ' tell process "{}"\n' + ' tell (1st window whose value of attribute "AXMain" is true)\n' + ' set windowTitle to value of attribute "AXTitle"\n' + @@ -15,12 +19,13 @@ const OtherAppsScript = 'tell application "System Events"\n' + ' end tell\n' + 'end tell'; -const AutoTypeHelper = function() { -}; +const AutoTypeHelper = function() {}; AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) { AutoTypeHelper.exec(ForeMostAppScript, (err, out) => { - if (err) { return callback(err); } + if (err) { + return callback(err); + } const appName = out.trim(); // getting urls and titles from Chrome or Safari: // - will suit in 90% cases @@ -28,20 +33,26 @@ AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) { // - allows to get url if (['Google Chrome', 'Chromium', 'Google Chrome Canary'].indexOf(appName) >= 0) { AutoTypeHelper.exec(ChromeScript.replace(/\{}/g, appName), (err, out) => { - if (err) { return callback(err); } + if (err) { + return callback(err); + } const parts = out.split('\n'); return callback(null, (parts[1] || '').trim(), parts[0].trim()); }); } else if (['Safari', 'Webkit'].indexOf(appName) >= 0) { AutoTypeHelper.exec(SafariScript.replace(/\{}/g, appName), (err, out) => { - if (err) { return callback(err); } + if (err) { + return callback(err); + } const parts = out.split('\n'); return callback(null, (parts[1] || '').trim(), parts[0].trim()); }); } else { // special cases are not available. this method may ask the user about assistive access AutoTypeHelper.exec(OtherAppsScript.replace(/\{}/g, appName), (err, out) => { - if (err) { return callback(err); } + if (err) { + return callback(err); + } return callback(null, out.trim()); }); } diff --git a/app/scripts/auto-type/helper/auto-type-helper-linux.js b/app/scripts/auto-type/helper/auto-type-helper-linux.js index 9daa25e0..55126712 100644 --- a/app/scripts/auto-type/helper/auto-type-helper-linux.js +++ b/app/scripts/auto-type/helper/auto-type-helper-linux.js @@ -1,7 +1,6 @@ const Launcher = require('../../comp/launcher'); -const AutoTypeHelper = function() { -}; +const AutoTypeHelper = function() {}; AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) { Launcher.spawn({ diff --git a/app/scripts/auto-type/helper/auto-type-helper-win32.js b/app/scripts/auto-type/helper/auto-type-helper-win32.js index c648642a..aac3d882 100644 --- a/app/scripts/auto-type/helper/auto-type-helper-win32.js +++ b/app/scripts/auto-type/helper/auto-type-helper-win32.js @@ -1,18 +1,18 @@ const Launcher = require('../../comp/launcher'); const AutoTypeNativeHelper = require('./auto-type-native-helper'); -const AutoTypeHelper = function() { -}; +const AutoTypeHelper = function() {}; AutoTypeHelper.prototype.getActiveWindowTitle = function(callback) { Launcher.spawn({ cmd: AutoTypeNativeHelper.getHelperPath(), args: ['--window-info'], complete: function(err, out) { - if (err) { return callback(err); } + if (err) { + return callback(err); + } const parts = out.split('\n'); - return callback(null, (parts[0] || '').trim(), - parts[1] ? parts[1].trim() : undefined); + return callback(null, (parts[0] || '').trim(), parts[1] ? parts[1].trim() : undefined); } }); }; diff --git a/app/scripts/auto-type/index.js b/app/scripts/auto-type/index.js index e32cf73a..b8efbea1 100644 --- a/app/scripts/auto-type/index.js +++ b/app/scripts/auto-type/index.js @@ -31,14 +31,16 @@ const AutoType = { }, handleEvent(e) { - const entry = e && e.entry || null; + const entry = (e && e.entry) || null; logger.debug('Auto type event', entry); if (this.running) { logger.debug('Already running, skipping event'); return; } if (entry) { - this.hideWindow(() => { this.runAndHandleResult({ entry }); }); + this.hideWindow(() => { + this.runAndHandleResult({ entry }); + }); } else { if (this.selectEntryView) { return; @@ -180,7 +182,7 @@ const AutoType = { selectEntryAndRun() { this.getActiveWindowTitle((e, title, url) => { - const filter = new AutoTypeFilter({title, url}, this.appModel); + const filter = new AutoTypeFilter({ title, url }, this.appModel); const evt = { filter }; if (!this.appModel.files.hasOpenFiles()) { this.pendingEvent = evt; @@ -224,10 +226,14 @@ const AutoType = { this.selectEntryView.on('show-open-files', () => { this.selectEntryView.hide(); Backbone.trigger('open-file'); - Backbone.once('closed-open-view', () => { - this.selectEntryView.show(); - this.selectEntryView.setupKeys(); - }, this); + Backbone.once( + 'closed-open-view', + () => { + this.selectEntryView.show(); + this.selectEntryView.setupKeys(); + }, + this + ); }); }, diff --git a/app/scripts/collections/entry-collection.js b/app/scripts/collections/entry-collection.js index 6f097779..8fe6210c 100644 --- a/app/scripts/collections/entry-collection.js +++ b/app/scripts/collections/entry-collection.js @@ -19,7 +19,9 @@ const EntryCollection = Backbone.Collection.extend({ '-created': Comparators.dateComparator('created', false), 'updated': Comparators.dateComparator('updated', true), '-updated': Comparators.dateComparator('updated', false), - '-attachments': function(x, y) { return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y)); }, + '-attachments': function(x, y) { + return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y)); + }, '-rank': Comparators.rankComparator() }, @@ -28,7 +30,7 @@ const EntryCollection = Backbone.Collection.extend({ filter: null, initialize: function(models, options) { - const comparatorName = options && options.comparator || this.defaultComparator; + const comparatorName = (options && options.comparator) || this.defaultComparator; this.comparator = this.comparators[comparatorName]; }, diff --git a/app/scripts/collections/file-info-collection.js b/app/scripts/collections/file-info-collection.js index fafadafe..eb1126fb 100644 --- a/app/scripts/collections/file-info-collection.js +++ b/app/scripts/collections/file-info-collection.js @@ -5,30 +5,31 @@ const SettingsStore = require('../comp/settings-store'); const FileInfoCollection = Backbone.Collection.extend({ model: FileInfoModel, - initialize: function () { - }, + initialize: function() {}, - load: function () { + load: function() { return SettingsStore.load('file-info').then(data => { if (data) { - this.reset(data, {silent: true}); + this.reset(data, { silent: true }); } }); }, - save: function () { + save: function() { SettingsStore.save('file-info', this.toJSON()); }, - getLast: function () { + getLast: function() { return this.first(); }, - getMatch: function (storage, name, path) { + getMatch: function(storage, name, path) { return this.find(fi => { - return (fi.get('storage') || '') === (storage || '') && + return ( + (fi.get('storage') || '') === (storage || '') && (fi.get('name') || '') === (name || '') && - (fi.get('path') || '') === (path || ''); + (fi.get('path') || '') === (path || '') + ); }); }, diff --git a/app/scripts/comp/alerts.js b/app/scripts/comp/alerts.js index ed393ca6..4db53aed 100644 --- a/app/scripts/comp/alerts.js +++ b/app/scripts/comp/alerts.js @@ -5,10 +5,30 @@ const Alerts = { alertDisplayed: false, buttons: { - ok: {result: 'yes', get title() { return Locale.alertOk; }}, - yes: {result: 'yes', get title() { return Locale.alertYes; }}, - no: {result: '', get title() { return Locale.alertNo; }}, - cancel: {result: '', get title() { return Locale.alertCancel; }} + ok: { + result: 'yes', + get title() { + return Locale.alertOk; + } + }, + yes: { + result: 'yes', + get title() { + return Locale.alertYes; + } + }, + no: { + result: '', + get title() { + return Locale.alertNo; + } + }, + cancel: { + result: '', + get title() { + return Locale.alertCancel; + } + } }, alert: function(config) { @@ -46,39 +66,54 @@ const Alerts = { }, info: function(config) { - this.alert(_.extend({ - header: '', - body: '', - icon: 'info', - buttons: [this.buttons.ok], - esc: '', - click: '', - enter: '' - }, config)); + this.alert( + _.extend( + { + header: '', + body: '', + icon: 'info', + buttons: [this.buttons.ok], + esc: '', + click: '', + enter: '' + }, + config + ) + ); }, error: function(config) { - this.alert(_.extend({ - header: '', - body: '', - icon: 'exclamation-circle', - buttons: [this.buttons.ok], - esc: '', - click: '', - enter: '' - }, config)); + this.alert( + _.extend( + { + header: '', + body: '', + icon: 'exclamation-circle', + buttons: [this.buttons.ok], + esc: '', + click: '', + enter: '' + }, + config + ) + ); }, yesno: function(config) { - this.alert(_.extend({ - header: '', - body: '', - icon: 'question', - buttons: [this.buttons.yes, this.buttons.no], - esc: '', - click: '', - enter: 'yes' - }, config)); + this.alert( + _.extend( + { + header: '', + body: '', + icon: 'question', + buttons: [this.buttons.yes, this.buttons.no], + esc: '', + click: '', + enter: 'yes' + }, + config + ) + ); } }; diff --git a/app/scripts/comp/app-rights-checker.js b/app/scripts/comp/app-rights-checker.js index 1876d89f..f00cf8fd 100644 --- a/app/scripts/comp/app-rights-checker.js +++ b/app/scripts/comp/app-rights-checker.js @@ -37,13 +37,13 @@ const AppRightsChecker = { this.alert = Alerts.alert({ icon: 'lock', header: Locale.appRightsAlert, - body: Locale.appRightsAlertBody1.replace('{}', `${this.AppPath}`) + - '
' + Locale.appRightsAlertBody2 + `:
${command}
`, - buttons: [ - {result: 'skip', title: Locale.alertDoNotAsk, error: true}, - Alerts.buttons.ok - ], - success: (result) => { + body: + Locale.appRightsAlertBody1.replace('{}', `${this.AppPath}`) + + '
' + + Locale.appRightsAlertBody2 + + `:
${command}
`, + buttons: [{ result: 'skip', title: Locale.alertDoNotAsk, error: true }, Alerts.buttons.ok], + success: result => { if (result === 'skip') { this.dontAskAnymore(); } @@ -54,7 +54,7 @@ const AppRightsChecker = { runInstaller() { Launcher.spawn({ - cmd: this.AppPath + '/Contents/Installer/KeeWeb\ Installer.app/Contents/MacOS/applet', + cmd: this.AppPath + '/Contents/Installer/KeeWeb Installer.app/Contents/MacOS/applet', complete: () => { this.needRunInstaller(needRun => { if (this.alert && !needRun) { diff --git a/app/scripts/comp/copy-paste.js b/app/scripts/comp/copy-paste.js index ac32a577..b2122493 100644 --- a/app/scripts/comp/copy-paste.js +++ b/app/scripts/comp/copy-paste.js @@ -21,13 +21,13 @@ const CopyPaste = { Backbone.off('main-window-will-close', clearClipboard); }, clipboardSeconds * 1000); } - return {success: true, seconds: clipboardSeconds}; + return { success: true, seconds: clipboardSeconds }; } else { try { if (document.execCommand('copy')) { - return {success: true}; + return { success: true }; } - } catch (e) { } + } catch (e) {} return false; } }, @@ -41,8 +41,12 @@ const CopyPaste = { hiddenInput[0].selectionEnd = text.length; hiddenInput.focus(); hiddenInput.on({ - 'copy cut paste': function() { setTimeout(() => hiddenInput.blur(), 0); }, - blur: function() { hiddenInput.remove(); } + 'copy cut paste': function() { + setTimeout(() => hiddenInput.blur(), 0); + }, + blur: function() { + hiddenInput.remove(); + } }); } }; diff --git a/app/scripts/comp/dropbox-chooser.js b/app/scripts/comp/dropbox-chooser.js index 8a0c80dd..5f8d7859 100644 --- a/app/scripts/comp/dropbox-chooser.js +++ b/app/scripts/comp/dropbox-chooser.js @@ -37,7 +37,12 @@ DropboxChooser.prototype.buildUrl = function() { iframe: 'false', version: 2 }; - return 'https://www.dropbox.com/chooser?' + Object.keys(urlParams).map(key => key + '=' + urlParams[key]).join('&'); + return ( + 'https://www.dropbox.com/chooser?' + + Object.keys(urlParams) + .map(key => key + '=' + urlParams[key]) + .join('&') + ); }; DropboxChooser.prototype.onMessage = function(e) { diff --git a/app/scripts/comp/export-api.js b/app/scripts/comp/export-api.js index 5a672cdd..4a82659a 100644 --- a/app/scripts/comp/export-api.js +++ b/app/scripts/comp/export-api.js @@ -2,9 +2,15 @@ const AppSettingsModel = require('../models/app-settings-model'); const ExportApi = { settings: { - get: function(key) { return key ? AppSettingsModel.instance.get(key) : AppSettingsModel.instance.toJSON(); }, - set: function(key, value) { AppSettingsModel.instance.set(key, value); }, - del: function(key) { AppSettingsModel.instance.unset(key); } + get: function(key) { + return key ? AppSettingsModel.instance.get(key) : AppSettingsModel.instance.toJSON(); + }, + set: function(key, value) { + AppSettingsModel.instance.set(key, value); + }, + del: function(key) { + AppSettingsModel.instance.unset(key); + } } }; diff --git a/app/scripts/comp/feature-tester.js b/app/scripts/comp/feature-tester.js index 9ca92413..2ac830ad 100644 --- a/app/scripts/comp/feature-tester.js +++ b/app/scripts/comp/feature-tester.js @@ -25,16 +25,23 @@ const FeatureTester = { const data = 'e567554429098a38d5f819115edffd39'; const iv = '4db46dff4add42cb813b98de98e627c4'; const exp = '46ab4c37d9ec594e5742971f76f7c1620bc29f2e0736b27832d6bcc5c1c39dc1'; - return aesCbc.importKey(kdbxweb.ByteUtils.hexToBytes(key)).then(() => { - return aesCbc.encrypt(kdbxweb.ByteUtils.hexToBytes(data), kdbxweb.ByteUtils.hexToBytes(iv)).then(res => { - if (kdbxweb.ByteUtils.bytesToHex(res) !== exp) { - throw 'AES is not working properly'; - } - if (kdbxweb.CryptoEngine.random(1).length !== 1) { - throw 'Random is not working'; - } + return aesCbc + .importKey(kdbxweb.ByteUtils.hexToBytes(key)) + .then(() => { + return aesCbc + .encrypt(kdbxweb.ByteUtils.hexToBytes(data), kdbxweb.ByteUtils.hexToBytes(iv)) + .then(res => { + if (kdbxweb.ByteUtils.bytesToHex(res) !== exp) { + throw 'AES is not working properly'; + } + if (kdbxweb.CryptoEngine.random(1).length !== 1) { + throw 'Random is not working'; + } + }); + }) + .catch(e => { + throw 'WebCrypto is not supported: ' + e; }); - }).catch(e => { throw 'WebCrypto is not supported: ' + e; }); }); }, diff --git a/app/scripts/comp/generator-presets.js b/app/scripts/comp/generator-presets.js index 80d3f2d4..e6027531 100644 --- a/app/scripts/comp/generator-presets.js +++ b/app/scripts/comp/generator-presets.js @@ -3,33 +3,37 @@ const Locale = require('../util/locale'); const GeneratorPresets = { get defaultPreset() { - return { name: 'Default', title: Locale.genPresetDefault, - length: 16, upper: true, lower: true, digits: true }; + return { name: 'Default', title: Locale.genPresetDefault, length: 16, upper: true, lower: true, digits: true }; }, get builtIn() { return [ this.defaultPreset, - { name: 'Pronounceable', title: Locale.genPresetPronounceable, - length: 10, lower: true, upper: true }, - { name: 'Med', title: Locale.genPresetMed, - length: 16, upper: true, lower: true, digits: true, special: true, brackets: true, ambiguous: true }, - { name: 'Long', title: Locale.genPresetLong, - length: 32, upper: true, lower: true, digits: true }, - { name: 'Pin4', title: Locale.genPresetPin4, - length: 4, digits: true }, - { name: 'Mac', title: Locale.genPresetMac, - length: 17, upper: true, digits: true, special: true }, - { name: 'Hash128', title: Locale.genPresetHash128, - length: 32, lower: true, digits: true }, - { name: 'Hash256', title: Locale.genPresetHash256, - length: 64, lower: true, digits: true } + { name: 'Pronounceable', title: Locale.genPresetPronounceable, length: 10, lower: true, upper: true }, + { + name: 'Med', + title: Locale.genPresetMed, + length: 16, + upper: true, + lower: true, + digits: true, + special: true, + brackets: true, + ambiguous: true + }, + { name: 'Long', title: Locale.genPresetLong, length: 32, upper: true, lower: true, digits: true }, + { name: 'Pin4', title: Locale.genPresetPin4, length: 4, digits: true }, + { name: 'Mac', title: Locale.genPresetMac, length: 17, upper: true, digits: true, special: true }, + { name: 'Hash128', title: Locale.genPresetHash128, length: 32, lower: true, digits: true }, + { name: 'Hash256', title: Locale.genPresetHash256, length: 64, lower: true, digits: true } ]; }, get all() { let presets = this.builtIn; - presets.forEach(preset => { preset.builtIn = true; }); + presets.forEach(preset => { + preset.builtIn = true; + }); const setting = AppSettingsModel.instance.get('generatorPresets'); if (setting) { if (setting.user) { diff --git a/app/scripts/comp/key-handler.js b/app/scripts/comp/key-handler.js index d1039fc3..2cbb75ff 100644 --- a/app/scripts/comp/key-handler.js +++ b/app/scripts/comp/key-handler.js @@ -15,15 +15,22 @@ const KeyHandler = { $(document).bind('keypress', this.keypress.bind(this)); $(document).bind('keydown', this.keydown.bind(this)); - this.shortcuts[Keys.DOM_VK_A] = [{ handler: this.handleAKey, thisArg: this, shortcut: this.SHORTCUT_ACTION, - modal: true, noPrevent: true }]; + this.shortcuts[Keys.DOM_VK_A] = [ + { handler: this.handleAKey, thisArg: this, shortcut: this.SHORTCUT_ACTION, modal: true, noPrevent: true } + ]; }, onKey: function(key, handler, thisArg, shortcut, modal, noPrevent) { let keyShortcuts = this.shortcuts[key]; if (!keyShortcuts) { this.shortcuts[key] = keyShortcuts = []; } - keyShortcuts.push({ handler: handler, thisArg: thisArg, shortcut: shortcut, modal: modal, noPrevent: noPrevent }); + keyShortcuts.push({ + handler: handler, + thisArg: thisArg, + shortcut: shortcut, + modal: modal, + noPrevent: noPrevent + }); }, offKey: function(key, handler, thisArg) { if (this.shortcuts[key]) { @@ -51,16 +58,24 @@ const KeyHandler = { const isActionKey = this.isActionKey(e); switch (sh.shortcut) { case this.SHORTCUT_ACTION: - if (!isActionKey) { continue; } + if (!isActionKey) { + continue; + } break; case this.SHORTCUT_OPT: - if (!e.altKey) { continue; } + if (!e.altKey) { + continue; + } break; case this.SHORTCUT_ACTION + this.SHORTCUT_OPT: - if (!e.altKey || !isActionKey) { continue; } + if (!e.altKey || !isActionKey) { + continue; + } break; default: - if (e.metaKey || e.ctrlKey || e.altKey) { continue; } + if (e.metaKey || e.ctrlKey || e.altKey) { + continue; + } break; } sh.handler.call(sh.thisArg, e, code); @@ -74,11 +89,15 @@ const KeyHandler = { } }, keypress: function(e) { - if (!this.modal && + if ( + !this.modal && e.charCode !== Keys.DOM_VK_RETURN && e.charCode !== Keys.DOM_VK_ESCAPE && e.charCode !== Keys.DOM_VK_TAB && - !e.altKey && !e.ctrlKey && !e.metaKey) { + !e.altKey && + !e.ctrlKey && + !e.metaKey + ) { this.trigger('keypress', e); } else if (this.modal) { this.trigger('keypress:' + this.modal, e); diff --git a/app/scripts/comp/launcher-cordova.js b/app/scripts/comp/launcher-cordova.js index 326f0d10..d9937df9 100644 --- a/app/scripts/comp/launcher-cordova.js +++ b/app/scripts/comp/launcher-cordova.js @@ -10,9 +10,13 @@ const Launcher = { clipboardSupported: false, ready: function(callback) { document.addEventListener('deviceready', callback, false); - document.addEventListener('pause', () => { - Backbone.trigger('app-minimized'); - }, false); + document.addEventListener( + 'pause', + () => { + Backbone.trigger('app-minimized'); + }, + false + ); }, platform: function() { return 'cordova'; @@ -22,7 +26,9 @@ const Launcher = { }, devTools: false, // openDevTools: function() { }, - getSaveFileName: function(defaultPath, callback) { /* skip in cordova */ }, + getSaveFileName: function(defaultPath, callback) { + /* skip in cordova */ + }, getDataPath: function() { const storagePath = window.cordova.file.externalDataDirectory; return [storagePath].concat(Array.from(arguments)).filter(s => !!s); @@ -47,9 +53,14 @@ const Launcher = { }, writeFile: function(path, data, callback) { const createFile = filePath => { - window.resolveLocalFileSystemURL(filePath.dir, dir => { - dir.getFile(filePath.file, {create: true}, writeFile); - }, callback, callback); + window.resolveLocalFileSystemURL( + filePath.dir, + dir => { + dir.getFile(filePath.file, { create: true }, writeFile); + }, + callback, + callback + ); }; const writeFile = fileEntry => { @@ -60,9 +71,11 @@ const Launcher = { }, callback); }; - if (path.startsWith('cdvfile://')) { // then file exists + if (path.startsWith('cdvfile://')) { + // then file exists window.resolveLocalFileSystemURL(path, writeFile, callback, callback); - } else { // create file on sd card + } else { + // create file on sd card const filePath = this.parsePath(path); this.mkdir(filePath.dir, () => { createFile(filePath); @@ -70,55 +83,86 @@ const Launcher = { } }, readFile: function(path, encoding, callback) { - window.resolveLocalFileSystemURL(path, fileEntry => { - fileEntry.file(file => { - const reader = new FileReader(); - reader.onerror = callback; - reader.onloadend = () => { - const contents = new Uint8Array(reader.result); - callback(encoding ? String.fromCharCode.apply(null, contents) : contents); - }; - reader.readAsArrayBuffer(file); - }, err => callback(undefined, err)); - }, err => callback(undefined, err)); + window.resolveLocalFileSystemURL( + path, + fileEntry => { + fileEntry.file( + file => { + const reader = new FileReader(); + reader.onerror = callback; + reader.onloadend = () => { + const contents = new Uint8Array(reader.result); + callback(encoding ? String.fromCharCode.apply(null, contents) : contents); + }; + reader.readAsArrayBuffer(file); + }, + err => callback(undefined, err) + ); + }, + err => callback(undefined, err) + ); }, fileExists: function(path, callback) { window.resolveLocalFileSystemURL(path, fileEntry => callback(true), () => callback(false)); }, deleteFile: function(path, callback) { - window.resolveLocalFileSystemURL(path, fileEntry => { - fileEntry.remove(callback, callback, callback); - }, callback); + window.resolveLocalFileSystemURL( + path, + fileEntry => { + fileEntry.remove(callback, callback, callback); + }, + callback + ); }, statFile: function(path, callback) { - window.resolveLocalFileSystemURL(path, fileEntry => { - fileEntry.file(file => { - callback({ - ctime: new Date(file.lastModified), - mtime: new Date(file.lastModified) - }); - }, err => callback(undefined, err)); - }, err => callback(undefined, err)); + window.resolveLocalFileSystemURL( + path, + fileEntry => { + fileEntry.file( + file => { + callback({ + ctime: new Date(file.lastModified), + mtime: new Date(file.lastModified) + }); + }, + err => callback(undefined, err) + ); + }, + err => callback(undefined, err) + ); }, mkdir: function(dir, callback) { const basePath = this.getDataPath().join('/'); const createDir = (dirEntry, path, callback) => { const name = path.shift(); - dirEntry.getDirectory(name, { create: true }, dirEntry => { - if (path.length) { // there is more to create - createDir(dirEntry, path, callback); - } else { - callback(); - } - }, callback); + dirEntry.getDirectory( + name, + { create: true }, + dirEntry => { + if (path.length) { + // there is more to create + createDir(dirEntry, path, callback); + } else { + callback(); + } + }, + callback + ); }; - const localPath = dir.replace(basePath, '').split('/').filter(s => !!s); + const localPath = dir + .replace(basePath, '') + .split('/') + .filter(s => !!s); if (localPath.length) { - window.resolveLocalFileSystemURL(basePath, dirEntry => { - createDir(dirEntry, localPath, callback); - }, callback); + window.resolveLocalFileSystemURL( + basePath, + dirEntry => { + createDir(dirEntry, localPath, callback); + }, + callback + ); } else { callback(); } @@ -145,11 +189,15 @@ const Launcher = { this.hideApp(); }, - requestExit: function() { /* skip in cordova */ }, + requestExit: function() { + /* skip in cordova */ + }, requestRestart: function() { window.location.reload(); }, - cancelRestart: function() { /* skip in cordova */ }, + cancelRestart: function() { + /* skip in cordova */ + }, setClipboardText: function(text) {}, getClipboardText: function() {}, @@ -169,13 +217,21 @@ const Launcher = { }, // getMainWindow: function() { }, - resolveProxy: function(url, callback) { /* skip in cordova */ }, - openWindow: function(opts) { /* skip in cordova */ }, - hideApp: function() { /* skip in cordova */ }, + resolveProxy: function(url, callback) { + /* skip in cordova */ + }, + openWindow: function(opts) { + /* skip in cordova */ + }, + hideApp: function() { + /* skip in cordova */ + }, isAppFocused: function() { return false; /* skip in cordova */ }, - showMainWindow: function() { /* skip in cordova */ }, + showMainWindow: function() { + /* skip in cordova */ + }, // spawn: function(config) { }, openFileChooser: function(callback) { const onFileSelected = function(selected) { diff --git a/app/scripts/comp/launcher-electron.js b/app/scripts/comp/launcher-electron.js index 460bda02..0a73bf8f 100644 --- a/app/scripts/comp/launcher-electron.js +++ b/app/scripts/comp/launcher-electron.js @@ -28,18 +28,23 @@ const Launcher = { }, devTools: true, openDevTools: function() { - this.electron().remote.getCurrentWindow().openDevTools({ mode: 'bottom' }); + this.electron() + .remote.getCurrentWindow() + .openDevTools({ mode: 'bottom' }); }, getSaveFileName: function(defaultPath, callback) { if (defaultPath) { const homePath = this.remReq('electron').app.getPath('userDesktop'); defaultPath = this.joinPath(homePath, defaultPath); } - this.remReq('electron').dialog.showSaveDialog({ - title: Locale.launcherSave, - defaultPath: defaultPath, - filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }] - }, callback); + this.remReq('electron').dialog.showSaveDialog( + { + title: Locale.launcherSave, + defaultPath: defaultPath, + filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }] + }, + callback + ); }, getUserDataPath: function(fileName) { if (!this.userDataPath) { @@ -110,9 +115,7 @@ const Launcher = { return callback(); } - fs.mkdir(stack.shift(), err => - err ? callback(err) : create(stack, callback) - ); + fs.mkdir(stack.shift(), err => (err ? callback(err) : create(stack, callback))); }; collect(dir, stack, () => create(stack, callback)); @@ -222,8 +225,12 @@ const Launcher = { [ps.stdin, ps.stdout, ps.stderr].forEach(s => s.setEncoding('utf-8')); let stderr = ''; let stdout = ''; - ps.stderr.on('data', d => { stderr += d.toString('utf-8'); }); - ps.stdout.on('data', d => { stdout += d.toString('utf-8'); }); + ps.stderr.on('data', d => { + stderr += d.toString('utf-8'); + }); + ps.stdout.on('data', d => { + stdout += d.toString('utf-8'); + }); ps.on('close', code => { stdout = stdout.trim(); stderr = stderr.trim(); diff --git a/app/scripts/comp/otp-qr-reader.js b/app/scripts/comp/otp-qr-reader.js index b2cdb665..8088abc1 100644 --- a/app/scripts/comp/otp-qr-reader.js +++ b/app/scripts/comp/otp-qr-reader.js @@ -18,21 +18,28 @@ const OtpQrReader = { if (screenshotKey) { screenshotKey = Locale.detSetupOtpAlertBodyWith.replace('{}', '' + screenshotKey + ''); } - const pasteKey = FeatureDetector.isMobile ? '' - : Locale.detSetupOtpAlertBodyWith.replace('{}', - '' + FeatureDetector.actionShortcutSymbol() + 'V'); + const pasteKey = FeatureDetector.isMobile + ? '' + : Locale.detSetupOtpAlertBodyWith.replace( + '{}', + '' + FeatureDetector.actionShortcutSymbol() + 'V' + ); OtpQrReader.startListenClipoard(); - const buttons = [{result: 'manually', title: Locale.detSetupOtpManualButton, silent: true}, - Alerts.buttons.cancel]; + const buttons = [ + { result: 'manually', title: Locale.detSetupOtpManualButton, silent: true }, + Alerts.buttons.cancel + ]; if (FeatureDetector.isMobile) { - buttons.unshift({result: 'select', title: Locale.detSetupOtpScanButton}); + buttons.unshift({ result: 'select', title: Locale.detSetupOtpScanButton }); } - const line3 = FeatureDetector.isMobile ? Locale.detSetupOtpAlertBody3Mobile + const line3 = FeatureDetector.isMobile + ? Locale.detSetupOtpAlertBody3Mobile : Locale.detSetupOtpAlertBody3.replace('{}', pasteKey || ''); OtpQrReader.alert = Alerts.alert({ icon: 'qrcode', header: Locale.detSetupOtpAlert, - body: [Locale.detSetupOtpAlertBody, + body: [ + Locale.detSetupOtpAlertBody, Locale.detSetupOtpAlertBody1, Locale.detSetupOtpAlertBody2.replace('{}', screenshotKey || ''), line3, @@ -127,7 +134,8 @@ const OtpQrReader = { logger.error('Error parsing QR code', err); Alerts.error({ header: Locale.detOtpQrWrong, - body: Locale.detOtpQrWrongBody + '' + body: + Locale.detOtpQrWrongBody + '' }); } } catch (e) { diff --git a/app/scripts/comp/popup-notifier.js b/app/scripts/comp/popup-notifier.js index e7b961ee..b9b6a243 100644 --- a/app/scripts/comp/popup-notifier.js +++ b/app/scripts/comp/popup-notifier.js @@ -51,10 +51,18 @@ const PopupNotifier = { const parts = part.split('='); settingsObj[parts[0].trim()] = parts[1].trim(); }); - if (settingsObj.width) { opts.width = +settingsObj.width; } - if (settingsObj.height) { opts.height = +settingsObj.height; } - if (settingsObj.top) { opts.y = +settingsObj.top; } - if (settingsObj.left) { opts.x = +settingsObj.left; } + if (settingsObj.width) { + opts.width = +settingsObj.width; + } + if (settingsObj.height) { + opts.height = +settingsObj.height; + } + if (settingsObj.top) { + opts.y = +settingsObj.top; + } + if (settingsObj.left) { + opts.x = +settingsObj.left; + } } let win = Launcher.openWindow(opts); win.webContents.on('will-redirect', (e, url) => { @@ -101,8 +109,7 @@ const PopupNotifier = { }, isOwnUrl(url) { - return url.lastIndexOf(Links.WebApp, 0) === 0 || - url.lastIndexOf(location.origin + location.pathname, 0) === 0; + return url.lastIndexOf(Links.WebApp, 0) === 0 || url.lastIndexOf(location.origin + location.pathname, 0) === 0; }, processReturnToApp: function(url) { diff --git a/app/scripts/comp/secure-input.js b/app/scripts/comp/secure-input.js index 67020a1f..be06737d 100644 --- a/app/scripts/comp/secure-input.js +++ b/app/scripts/comp/secure-input.js @@ -90,7 +90,10 @@ Object.defineProperty(SecureInput.prototype, 'value', { byteLength++; } } - return new kdbxweb.ProtectedValue(valueBytes.buffer.slice(0, byteLength), saltBytes.buffer.slice(0, byteLength)); + return new kdbxweb.ProtectedValue( + valueBytes.buffer.slice(0, byteLength), + saltBytes.buffer.slice(0, byteLength) + ); } }); diff --git a/app/scripts/comp/settings-manager.js b/app/scripts/comp/settings-manager.js index 46055f36..17fd3ee4 100644 --- a/app/scripts/comp/settings-manager.js +++ b/app/scripts/comp/settings-manager.js @@ -22,8 +22,7 @@ const SettingsManager = { hc: 'setGenThemeHc' }, - customLocales: { - }, + customLocales: {}, setBySettings: function(settings) { if (settings.get('theme')) { @@ -59,7 +58,7 @@ const SettingsManager = { setFontSize: function(fontSize) { const defaultFontSize = FeatureDetector.isMobile ? 14 : 12; - document.documentElement.style.fontSize = (defaultFontSize + (fontSize || 0) * 2) + 'px'; + document.documentElement.style.fontSize = defaultFontSize + (fontSize || 0) * 2 + 'px'; }, setLocale(loc) { @@ -83,7 +82,7 @@ const SettingsManager = { }, getBrowserLocale: function() { - const language = navigator.languages && navigator.languages[0] || navigator.language; + const language = (navigator.languages && navigator.languages[0]) || navigator.language; if (language && language.lastIndexOf('en', 0) === 0) { return 'en'; } diff --git a/app/scripts/comp/single-instance-checker.js b/app/scripts/comp/single-instance-checker.js index 47a4d493..78c5bcca 100644 --- a/app/scripts/comp/single-instance-checker.js +++ b/app/scripts/comp/single-instance-checker.js @@ -30,7 +30,9 @@ const SingleInstanceChecker = { setKey: function(key, value) { try { localStorage.setItem(key, value); - setTimeout(() => { localStorage.removeItem(key); }, 100); + setTimeout(() => { + localStorage.removeItem(key); + }, 100); } catch (e) {} } }; diff --git a/app/scripts/comp/transport.js b/app/scripts/comp/transport.js index c6d8e0cf..824a87a1 100644 --- a/app/scripts/comp/transport.js +++ b/app/scripts/comp/transport.js @@ -27,57 +27,64 @@ const Transport = { const opts = Launcher.req('url').parse(config.url); opts.headers = { 'User-Agent': navigator.userAgent }; Launcher.resolveProxy(config.url, proxy => { - logger.info('Request to ' + config.url + ' ' + (proxy ? 'using proxy ' + proxy.host + ':' + proxy.port : 'without proxy')); + logger.info( + 'Request to ' + + config.url + + ' ' + + (proxy ? 'using proxy ' + proxy.host + ':' + proxy.port : 'without proxy') + ); if (proxy) { opts.headers.Host = opts.host; opts.host = proxy.host; opts.port = proxy.port; opts.path = config.url; } - Launcher.req(proto).get(opts, res => { - logger.info('Response from ' + config.url + ': ' + res.statusCode); - if (res.statusCode === 200) { - if (config.file) { - const file = fs.createWriteStream(tmpFile); - res.pipe(file); - file.on('finish', () => { - file.close(() => { - config.success(tmpFile); + Launcher.req(proto) + .get(opts, res => { + logger.info('Response from ' + config.url + ': ' + res.statusCode); + if (res.statusCode === 200) { + if (config.file) { + const file = fs.createWriteStream(tmpFile); + res.pipe(file); + file.on('finish', () => { + file.close(() => { + config.success(tmpFile); + }); }); - }); - file.on('error', err => { - config.error(err); - }); + file.on('error', err => { + config.error(err); + }); + } else { + let data = []; + res.on('data', chunk => { + data.push(chunk); + }); + res.on('end', () => { + data = window.Buffer.concat(data); + if (config.utf8) { + data = data.toString('utf8'); + } + config.success(data); + }); + } + } else if (res.headers.location && [301, 302].indexOf(res.statusCode) >= 0) { + if (config.noRedirect) { + return config.error('Too many redirects'); + } + config.url = res.headers.location; + config.noRedirect = true; + Transport.httpGet(config); } else { - let data = []; - res.on('data', chunk => { - data.push(chunk); - }); - res.on('end', () => { - data = window.Buffer.concat(data); - if (config.utf8) { - data = data.toString('utf8'); - } - config.success(data); - }); + config.error('HTTP status ' + res.statusCode); } - } else if (res.headers.location && [301, 302].indexOf(res.statusCode) >= 0) { - if (config.noRedirect) { - return config.error('Too many redirects'); + }) + .on('error', e => { + logger.error('Cannot GET ' + config.url, e); + if (tmpFile) { + fs.unlink(tmpFile, _.noop); } - config.url = res.headers.location; - config.noRedirect = true; - Transport.httpGet(config); - } else { - config.error('HTTP status ' + res.statusCode); - } - }).on('error', e => { - logger.error('Cannot GET ' + config.url, e); - if (tmpFile) { - fs.unlink(tmpFile, _.noop); - } - config.error(e); - }); + config.error(e); + }); }); } }; diff --git a/app/scripts/comp/updater.js b/app/scripts/comp/updater.js index 779f4c51..80d77a15 100644 --- a/app/scripts/comp/updater.js +++ b/app/scripts/comp/updater.js @@ -32,8 +32,10 @@ const Updater = { }, updateInProgress: function() { - return UpdateModel.instance.get('status') === 'checking' || - ['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0; + return ( + UpdateModel.instance.get('status') === 'checking' || + ['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0 + ); }, init: function() { @@ -55,7 +57,10 @@ const Updater = { let timeDiff = this.MinUpdateTimeout; const lastCheckDate = UpdateModel.instance.get('lastCheckDate'); if (lastCheckDate) { - timeDiff = Math.min(Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout), this.UpdateInterval); + timeDiff = Math.min( + Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout), + this.UpdateInterval + ); } this.nextCheckTimeout = setTimeout(this.check.bind(this), timeDiff); logger.info('Next update check will happen in ' + Math.round(timeDiff / 1000) + 's'); @@ -107,8 +112,10 @@ const Updater = { if (!this.canAutoUpdate()) { return; } - if (prevLastVersion === UpdateModel.instance.get('lastVersion') && - UpdateModel.instance.get('updateStatus') === 'ready') { + if ( + prevLastVersion === UpdateModel.instance.get('lastVersion') && + UpdateModel.instance.get('updateStatus') === 'ready' + ) { logger.info('Waiting for the user to apply downloaded update'); return; } @@ -238,7 +245,9 @@ const Updater = { checkAppCacheUpdateReady: function() { if (window.applicationCache.status === window.applicationCache.UPDATEREADY) { - try { window.applicationCache.swapCache(); } catch (e) { } + try { + window.applicationCache.swapCache(); + } catch (e) {} UpdateModel.instance.set('updateStatus', 'ready'); } } diff --git a/app/scripts/const/icon-map.js b/app/scripts/const/icon-map.js index f4fd40b3..9fd2b991 100644 --- a/app/scripts/const/icon-map.js +++ b/app/scripts/const/icon-map.js @@ -1,18 +1,73 @@ const IconMap = [ - 'key', 'globe', 'exclamation-triangle', 'server', 'thumb-tack', - 'comments-o', 'puzzle-piece', 'pencil-square-o', 'plug', 'newspaper-o', - 'paperclip', 'camera', 'wifi', 'link', 'battery-three-quarters', - 'barcode', 'certificate', 'bullseye', 'desktop', 'envelope-o', - 'cog', 'clipboard', 'paper-plane-o', 'television', 'bolt', - 'inbox', 'floppy-o', 'hdd-o', 'dot-circle-o', 'expeditedssl', - 'terminal', 'print', 'map-signs', 'flag-checkered', 'wrench', - 'laptop', 'archive', 'credit-card', 'windows', 'clock-o', - 'search', 'flask', 'gamepad', 'trash-o', 'sticky-note-o', - 'ban', 'question-circle', 'cube', 'folder-o', 'folder-open-o', - 'database', 'unlock-alt', 'lock', 'check', 'pencil', - 'picture-o', 'book', 'list-alt', 'user-secret', 'cutlery', - 'home', 'star-o', 'linux', 'map-pin', 'apple', - 'wikipedia-w', 'usd', 'calendar', 'mobile' + 'key', + 'globe', + 'exclamation-triangle', + 'server', + 'thumb-tack', + 'comments-o', + 'puzzle-piece', + 'pencil-square-o', + 'plug', + 'newspaper-o', + 'paperclip', + 'camera', + 'wifi', + 'link', + 'battery-three-quarters', + 'barcode', + 'certificate', + 'bullseye', + 'desktop', + 'envelope-o', + 'cog', + 'clipboard', + 'paper-plane-o', + 'television', + 'bolt', + 'inbox', + 'floppy-o', + 'hdd-o', + 'dot-circle-o', + 'expeditedssl', + 'terminal', + 'print', + 'map-signs', + 'flag-checkered', + 'wrench', + 'laptop', + 'archive', + 'credit-card', + 'windows', + 'clock-o', + 'search', + 'flask', + 'gamepad', + 'trash-o', + 'sticky-note-o', + 'ban', + 'question-circle', + 'cube', + 'folder-o', + 'folder-open-o', + 'database', + 'unlock-alt', + 'lock', + 'check', + 'pencil', + 'picture-o', + 'book', + 'list-alt', + 'user-secret', + 'cutlery', + 'home', + 'star-o', + 'linux', + 'map-pin', + 'apple', + 'wikipedia-w', + 'usd', + 'calendar', + 'mobile' ]; module.exports = IconMap; diff --git a/app/scripts/helpers/add.js b/app/scripts/helpers/add.js index 6dbae6bb..f5b5b494 100644 --- a/app/scripts/helpers/add.js +++ b/app/scripts/helpers/add.js @@ -1,6 +1,4 @@ const Handlebars = require('hbs'); // inspired by https://stackoverflow.com/questions/22103989/adding-offset-to-index-when-looping-through-items-in-handlebars/39588001#39588001 -Handlebars.registerHelper('add', (lvalue, rvalue) => ( - parseInt(lvalue) + parseInt(rvalue) -)); +Handlebars.registerHelper('add', (lvalue, rvalue) => parseInt(lvalue) + parseInt(rvalue)); diff --git a/app/scripts/helpers/res.js b/app/scripts/helpers/res.js index fe5bd699..ff11f25e 100644 --- a/app/scripts/helpers/res.js +++ b/app/scripts/helpers/res.js @@ -12,7 +12,7 @@ Handlebars.registerHelper('res', function(key, options) { return value; }); -Handlebars.registerHelper('Res', function(key) { // eslint-disable-line prefer-arrow-callback +Handlebars.registerHelper('Res', key => { let value = Locale[key]; if (value) { value = value[0].toUpperCase() + value.substr(1); diff --git a/app/scripts/mixins/copyable.js b/app/scripts/mixins/copyable.js index 481e5e4f..555157e7 100644 --- a/app/scripts/mixins/copyable.js +++ b/app/scripts/mixins/copyable.js @@ -18,11 +18,16 @@ const Copyable = { this.hideFieldCopyTip(); const fieldLabel = e.source.labelEl; const clipboardTime = e.copyRes.seconds; - const msg = clipboardTime ? Locale.detFieldCopiedTime.replace('{}', clipboardTime) - : Locale.detFieldCopied; + const msg = clipboardTime ? Locale.detFieldCopiedTime.replace('{}', clipboardTime) : Locale.detFieldCopied; let tip; if (!this.isHidden()) { - tip = Tip.createTip(fieldLabel[0], {title: msg, placement: 'right', fast: true, force: true, noInit: true}); + tip = Tip.createTip(fieldLabel[0], { + title: msg, + placement: 'right', + fast: true, + force: true, + noInit: true + }); this.fieldCopyTip = tip; tip.show(); } diff --git a/app/scripts/mixins/protected-value-ex.js b/app/scripts/mixins/protected-value-ex.js index 697394f3..256359fa 100644 --- a/app/scripts/mixins/protected-value-ex.js +++ b/app/scripts/mixins/protected-value-ex.js @@ -8,10 +8,7 @@ kdbxweb.ProtectedValue.prototype.isProtected = true; kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) { const value = this._value; const salt = this._salt; - let b, - b1, - b2, - b3; + let b, b1, b2, b3; for (let i = 0, len = value.length; i < len; i++) { b = value[i] ^ salt[i]; if (b < 128) { @@ -20,23 +17,32 @@ kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) { } continue; } - i++; b1 = value[i] ^ salt[i]; - if (i === len) { break; } + i++; + b1 = value[i] ^ salt[i]; + if (i === len) { + break; + } if (b >= 192 && b < 224) { if (fn(((b & 0x1f) << 6) | (b1 & 0x3f)) === false) { return; } continue; } - i++; b2 = value[i] ^ salt[i]; - if (i === len) { break; } + i++; + b2 = value[i] ^ salt[i]; + if (i === len) { + break; + } if (b >= 224 && b < 240) { if (fn(((b & 0xf) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f)) === false) { return; } } - i++; b3 = value[i] ^ salt[i]; - if (i === len) { break; } + i++; + b3 = value[i] ^ salt[i]; + if (i === len) { + break; + } if (b >= 240 && b < 248) { let c = ((b & 7) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f); if (c <= 0xffff) { @@ -60,7 +66,9 @@ kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) { Object.defineProperty(kdbxweb.ProtectedValue.prototype, 'textLength', { get: function() { let textLength = 0; - this.forEachChar(() => { textLength++; }); + this.forEachChar(() => { + textLength++; + }); return textLength; } }); diff --git a/app/scripts/mixins/view.js b/app/scripts/mixins/view.js index de07ef43..bd2b1db7 100644 --- a/app/scripts/mixins/view.js +++ b/app/scripts/mixins/view.js @@ -75,7 +75,7 @@ _.extend(Backbone.View.prototype, { if (this.scroll) { try { this.scroll.dispose(); - } catch (e) { } + } catch (e) {} } Tip.hideTips(this.$el); this._parentRemove(arguments); diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index 0427271e..dfa2b256 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -114,28 +114,38 @@ const AppModel = Backbone.Model.extend({ this.fileInfos.reset(); } config.files - .filter(file => file && file.storage && file.name && file.path && - !this.fileInfos.getMatch(file.storage, file.name, file.path)) - .map(file => new FileInfoModel({ - id: IdGenerator.uuid(), - name: file.name, - storage: file.storage, - path: file.path, - opts: file.options - })) + .filter( + file => + file && + file.storage && + file.name && + file.path && + !this.fileInfos.getMatch(file.storage, file.name, file.path) + ) + .map( + file => + new FileInfoModel({ + id: IdGenerator.uuid(), + name: file.name, + storage: file.storage, + path: file.path, + opts: file.options + }) + ) .reverse() .forEach(fi => this.fileInfos.unshift(fi)); } if (config.plugins) { - const pluginsPromises = config.plugins - .map(plugin => PluginManager.installIfNew(plugin.url, plugin.manifest, true)); + const pluginsPromises = config.plugins.map(plugin => + PluginManager.installIfNew(plugin.url, plugin.manifest, true) + ); return Promise.all(pluginsPromises).then(() => { this.settings.set(config.settings); }); } if (config.advancedSearch) { this.advancedSearch = config.advancedSearch; - this.addFilter({advanced: this.advancedSearch}); + this.addFilter({ advanced: this.advancedSearch }); } }, @@ -144,7 +154,7 @@ const AppModel = Backbone.Model.extend({ return false; } this.files.add(file); - file.get('groups').forEach(function (group) { + file.get('groups').forEach(function(group) { this.menu.groupsSection.addItem(group); }, this); this._addTags(file); @@ -184,9 +194,11 @@ const AppModel = Backbone.Model.extend({ _tagsChanged: function() { if (this.tags.length) { this.menu.tagsSection.set('scrollable', true); - this.menu.tagsSection.setItems(this.tags.map(tag => { - return {title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag, editable: true}; - })); + this.menu.tagsSection.setItems( + this.tags.map(tag => { + return { title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag, editable: true }; + }) + ); } else { this.menu.tagsSection.set('scrollable', false); this.menu.tagsSection.removeAllItems(); @@ -310,8 +322,7 @@ const AppModel = Backbone.Model.extend({ getFirstSelectedGroup: function() { const selGroupId = this.filter.group; - let file, - group; + let file, group; if (selGroupId) { this.files.some(f => { file = f; @@ -410,19 +421,25 @@ const AppModel = Backbone.Model.extend({ openFile: function(params, callback) { const logger = new Logger('open', params.name); logger.info('File open request'); - const fileInfo = params.id ? this.fileInfos.get(params.id) : this.fileInfos.getMatch(params.storage, params.name, params.path); + const fileInfo = params.id + ? this.fileInfos.get(params.id) + : this.fileInfos.getMatch(params.storage, params.name, params.path); if (!params.opts && fileInfo && fileInfo.get('opts')) { params.opts = fileInfo.get('opts'); } if (fileInfo && fileInfo.get('modified')) { logger.info('Open file from cache because it is modified'); - this.openFileFromCache(params, (err, file) => { - if (!err && file) { - logger.info('Sync just opened modified file'); - _.defer(() => this.syncFile(file)); - } - callback(err); - }, fileInfo); + this.openFileFromCache( + params, + (err, file) => { + if (!err && file) { + logger.info('Sync just opened modified file'); + _.defer(() => this.syncFile(file)); + } + callback(err); + }, + fileInfo + ); } else if (params.fileData) { logger.info('Open file from supplied content'); const needSaveToCache = params.storage !== 'file'; @@ -430,7 +447,12 @@ const AppModel = Backbone.Model.extend({ } else if (!params.storage) { logger.info('Open file from cache as main storage'); this.openFileFromCache(params, callback, fileInfo); - } else if (fileInfo && fileInfo.get('openDate') && fileInfo.get('rev') === params.rev && fileInfo.get('storage') !== 'file') { + } else if ( + fileInfo && + fileInfo.get('openDate') && + fileInfo.get('rev') === params.rev && + fileInfo.get('storage') !== 'file' + ) { logger.info('Open file from cache because it is latest'); this.openFileFromCache(params, callback, fileInfo); } else if (!fileInfo || !fileInfo.get('openDate') || params.storage === 'file') { @@ -450,17 +472,17 @@ const AppModel = Backbone.Model.extend({ } else { logger.info('Open file from content loaded from storage'); params.fileData = data; - params.rev = stat && stat.rev || null; + params.rev = (stat && stat.rev) || null; const needSaveToCache = storage.name !== 'file'; this.openFileWithData(params, callback, fileInfo, data, needSaveToCache); } }); }; - const cacheRev = fileInfo && fileInfo.get('rev') || null; + const cacheRev = (fileInfo && fileInfo.get('rev')) || null; if (cacheRev && storage.stat) { logger.info('Stat file'); storage.stat(params.path, params.opts, (err, stat) => { - if (fileInfo && storage.name !== 'file' && (err || stat && stat.rev === cacheRev)) { + if (fileInfo && storage.name !== 'file' && (err || (stat && stat.rev === cacheRev))) { logger.info('Open file from cache because ' + (err ? 'stat error' : 'it is latest'), err); this.openFileFromCache(params, callback, fileInfo); } else if (stat) { @@ -476,13 +498,17 @@ const AppModel = Backbone.Model.extend({ } } else { logger.info('Open file from cache, will sync after load', params.storage); - this.openFileFromCache(params, (err, file) => { - if (!err && file) { - logger.info('Sync just opened file'); - _.defer(() => this.syncFile(file)); - } - callback(err); - }, fileInfo); + this.openFileFromCache( + params, + (err, file) => { + if (!err && file) { + logger.info('Sync just opened file'); + _.defer(() => this.syncFile(file)); + } + callback(err); + }, + fileInfo + ); } }, @@ -520,8 +546,8 @@ const AppModel = Backbone.Model.extend({ path: params.path, keyFileName: params.keyFileName, keyFilePath: params.keyFilePath, - backup: fileInfo && fileInfo.get('backup') || null, - fingerprint: fileInfo && fileInfo.get('fingerprint') || null + backup: (fileInfo && fileInfo.get('backup')) || null, + fingerprint: (fileInfo && fileInfo.get('fingerprint')) || null }); const openComplete = err => { if (err) { @@ -545,7 +571,7 @@ const AppModel = Backbone.Model.extend({ logger.info('Save loaded file to cache'); Storage.cache.save(file.id, null, params.fileData); } - const rev = params.rev || fileInfo && fileInfo.get('rev'); + const rev = params.rev || (fileInfo && fileInfo.get('rev')); this.setFileOpts(file, params.opts); this.addToLastOpenFiles(file, rev); this.addFile(file); @@ -590,7 +616,14 @@ const AppModel = Backbone.Model.extend({ }, addToLastOpenFiles: function(file, rev) { - this.appLogger.debug('Add last open file', file.id, file.get('name'), file.get('storage'), file.get('path'), rev); + this.appLogger.debug( + 'Add last open file', + file.id, + file.get('name'), + file.get('storage'), + file.get('path'), + rev + ); const dt = new Date(); const fileInfo = new FileInfoModel({ id: file.id, @@ -642,9 +675,12 @@ const AppModel = Backbone.Model.extend({ fileOpened: function(file, data, params) { if (file.get('storage') === 'file') { - Storage.file.watch(file.get('path'), _.debounce(() => { - this.syncFile(file); - }, Timeouts.FileChangeSync)); + Storage.file.watch( + file.get('path'), + _.debounce(() => { + this.syncFile(file); + }, Timeouts.FileChangeSync) + ); } if (file.isKeyChangePending(true)) { Backbone.trigger('key-change-pending', { file: file }); @@ -671,8 +707,10 @@ const AppModel = Backbone.Model.extend({ }, getFileInfo: function(file) { - return this.fileInfos.get(file.id) || - this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path')); + return ( + this.fileInfos.get(file.id) || + this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path')) + ); }, syncFile: function(file, options, callback) { @@ -713,7 +751,9 @@ const AppModel = Backbone.Model.extend({ } file.setSyncProgress(); const complete = (err, savedToCache) => { - if (!err) { savedToCache = true; } + if (!err) { + savedToCache = true; + } logger.info('Sync finished', err || 'no error'); file.setSyncComplete(path, storage, err ? err.toString() : null, savedToCache); fileInfo.set({ @@ -735,7 +775,9 @@ const AppModel = Backbone.Model.extend({ this.fileInfos.unshift(fileInfo); } this.fileInfos.save(); - if (callback) { callback(err); } + if (callback) { + callback(err); + } }; if (!storage) { if (!file.get('modified') && fileInfo.id === file.id) { @@ -744,8 +786,10 @@ const AppModel = Backbone.Model.extend({ } logger.info('Local, save to cache'); file.getData((data, err) => { - if (err) { return complete(err); } - Storage.cache.save(fileInfo.id, null, data, (err) => { + if (err) { + return complete(err); + } + Storage.cache.save(fileInfo.id, null, data, err => { logger.info('Saved to cache', err || 'no error'); complete(err); if (!err) { @@ -763,8 +807,10 @@ const AppModel = Backbone.Model.extend({ logger.info('Load from storage, attempt ' + loadLoops); Storage[storage].load(path, opts, (err, data, stat) => { logger.info('Load from storage', stat, err || 'no error'); - if (err) { return complete(err); } - file.mergeOrUpdate(data, options.remoteKey, (err) => { + if (err) { + return complete(err); + } + file.mergeOrUpdate(data, options.remoteKey, err => { logger.info('Merge complete', err || 'no error'); this.refresh(); if (err) { @@ -784,8 +830,10 @@ const AppModel = Backbone.Model.extend({ saveToCacheAndStorage(); } else if (file.get('dirty')) { logger.info('Saving not modified dirty file to cache'); - Storage.cache.save(fileInfo.id, null, data, (err) => { - if (err) { return complete(err); } + Storage.cache.save(fileInfo.id, null, data, err => { + if (err) { + return complete(err); + } file.set('dirty', false); logger.info('Complete, remove dirty flag'); complete(); @@ -797,38 +845,46 @@ const AppModel = Backbone.Model.extend({ }); }); }; - const saveToStorage = (data) => { + const saveToStorage = data => { logger.info('Save data to storage'); const storageRev = fileInfo.get('storage') === storage ? fileInfo.get('rev') : undefined; - Storage[storage].save(path, opts, data, (err, stat) => { - if (err && err.revConflict) { - logger.info('Save rev conflict, reloading from storage'); - loadFromStorageAndMerge(); - } else if (err) { - logger.info('Error saving data to storage'); - complete(err); - } else { - if (stat && stat.rev) { - logger.info('Update rev in file info'); - fileInfo.set('rev', stat.rev); + Storage[storage].save( + path, + opts, + data, + (err, stat) => { + if (err && err.revConflict) { + logger.info('Save rev conflict, reloading from storage'); + loadFromStorageAndMerge(); + } else if (err) { + logger.info('Error saving data to storage'); + complete(err); + } else { + if (stat && stat.rev) { + logger.info('Update rev in file info'); + fileInfo.set('rev', stat.rev); + } + if (stat && stat.path) { + logger.info('Update path in file info', stat.path); + file.set('path', stat.path); + fileInfo.set('path', stat.path); + path = stat.path; + } + file.set('syncDate', new Date()); + logger.info('Save to storage complete, update sync date'); + this.scheduleBackupFile(file, data); + complete(); } - if (stat && stat.path) { - logger.info('Update path in file info', stat.path); - file.set('path', stat.path); - fileInfo.set('path', stat.path); - path = stat.path; - } - file.set('syncDate', new Date()); - logger.info('Save to storage complete, update sync date'); - this.scheduleBackupFile(file, data); - complete(); - } - }, storageRev); + }, + storageRev + ); }; const saveToCacheAndStorage = () => { logger.info('Getting file data for saving'); file.getData((data, err) => { - if (err) { return complete(err); } + if (err) { + return complete(err); + } if (storage === 'file') { logger.info('Saving to file storage'); saveToStorage(data); @@ -837,8 +893,10 @@ const AppModel = Backbone.Model.extend({ saveToStorage(data); } else { logger.info('Saving to cache'); - Storage.cache.save(fileInfo.id, null, data, (err) => { - if (err) { return complete(err); } + Storage.cache.save(fileInfo.id, null, data, err => { + if (err) { + return complete(err); + } file.set('dirty', false); logger.info('Saved to cache, saving to storage'); saveToStorage(data); @@ -854,9 +912,9 @@ const AppModel = Backbone.Model.extend({ saveToCacheAndStorage(); } else if (file.get('dirty')) { logger.info('Stat error, dirty, save to cache', err || 'no error'); - file.getData((data) => { + file.getData(data => { if (data) { - Storage.cache.save(fileInfo.id, null, data, (e) => { + Storage.cache.save(fileInfo.id, null, data, e => { if (!e) { file.set('dirty', false); } @@ -896,7 +954,7 @@ const AppModel = Backbone.Model.extend({ this.fileInfos.save(); }, - unsetKeyFile: function (fileId) { + unsetKeyFile: function(fileId) { const fileInfo = this.fileInfos.get(fileId); fileInfo.set({ keyFileName: null, @@ -927,7 +985,7 @@ const AppModel = Backbone.Model.extend({ if (Storage[backup.storage].getPathForName) { path = Storage[backup.storage].getPathForName(path); } - Storage[backup.storage].save(path, opts, data, (err) => { + Storage[backup.storage].save(path, opts, data, err => { if (err) { logger.error('Backup error', err); } else { @@ -1002,10 +1060,16 @@ const AppModel = Backbone.Model.extend({ if (dt.getTime() <= Date.now()) { needBackup = true; } - logger.debug('Last backup time: ' + new Date(backup.lastTime) + - ', schedule: ' + backup.schedule + - ', next time: ' + dt + - ', ' + (needBackup ? 'backup now' : 'skip backup')); + logger.debug( + 'Last backup time: ' + + new Date(backup.lastTime) + + ', schedule: ' + + backup.schedule + + ', next time: ' + + dt + + ', ' + + (needBackup ? 'backup now' : 'skip backup') + ); } if (!backup.pending) { backup.pending = true; diff --git a/app/scripts/models/app-settings-model.js b/app/scripts/models/app-settings-model.js index 176d4232..a11ef18e 100644 --- a/app/scripts/models/app-settings-model.js +++ b/app/scripts/models/app-settings-model.js @@ -57,7 +57,7 @@ const AppSettingsModel = Backbone.Model.extend({ return SettingsStore.load('app-settings').then(data => { if (data) { this.upgrade(data); - this.set(data, {silent: true}); + this.set(data, { silent: true }); } }); }, diff --git a/app/scripts/models/attachment-model.js b/app/scripts/models/attachment-model.js index 078a9082..ea67af70 100644 --- a/app/scripts/models/attachment-model.js +++ b/app/scripts/models/attachment-model.js @@ -3,8 +3,7 @@ const Backbone = require('backbone'); const AttachmentModel = Backbone.Model.extend({ defaults: {}, - initialize: function() { - }, + initialize: function() {}, setAttachment: function(att) { this.title = att.title; @@ -21,28 +20,83 @@ const AttachmentModel = Backbone.Model.extend({ _getIcon: function(ext) { switch (ext) { - case 'txt': case 'log': case 'rtf': case 'pem': + case 'txt': + case 'log': + case 'rtf': + case 'pem': return 'file-text-o'; - case 'html': case 'htm': case 'js': case 'css': case 'xml': case 'config': case 'json': case 'yaml': - case 'cpp': case 'c': case 'h': case 'cc': case 'hpp': case 'mm': case 'cs': case 'php': case 'sh': - case 'py': case 'java': case 'rb': case 'cfg': case 'properties': case 'yml': case 'asm': case 'bat': + case 'html': + case 'htm': + case 'js': + case 'css': + case 'xml': + case 'config': + case 'json': + case 'yaml': + case 'cpp': + case 'c': + case 'h': + case 'cc': + case 'hpp': + case 'mm': + case 'cs': + case 'php': + case 'sh': + case 'py': + case 'java': + case 'rb': + case 'cfg': + case 'properties': + case 'yml': + case 'asm': + case 'bat': return 'file-code-o'; case 'pdf': return 'file-pdf-o'; - case 'zip': case 'rar': case 'bz': case 'bz2': case '7z': case 'gzip': case 'gz': case 'tar': - case 'cab': case 'ace': case 'dmg': case 'jar': + case 'zip': + case 'rar': + case 'bz': + case 'bz2': + case '7z': + case 'gzip': + case 'gz': + case 'tar': + case 'cab': + case 'ace': + case 'dmg': + case 'jar': return 'file-archive-o'; - case 'doc': case 'docx': + case 'doc': + case 'docx': return 'file-word-o'; - case 'xls': case 'xlsx': + case 'xls': + case 'xlsx': return 'file-excel-o'; - case 'ppt': case 'pptx': + case 'ppt': + case 'pptx': return 'file-powerpoint-o'; - case 'jpeg': case 'jpg': case 'png': case 'gif': case 'bmp': case 'tiff': case 'svg': case 'ico': case 'psd': + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + case 'bmp': + case 'tiff': + case 'svg': + case 'ico': + case 'psd': return 'file-image-o'; - case 'avi': case 'mp4': case '3gp': case 'm4v': case 'mov': case 'mpeg': case 'mpg': case 'mpe': + case 'avi': + case 'mp4': + case '3gp': + case 'm4v': + case 'mov': + case 'mpeg': + case 'mpg': + case 'mpe': return 'file-video-o'; - case 'mp3': case 'wav': case 'flac': + case 'mp3': + case 'wav': + case 'flac': return 'file-audio-o'; } return 'file-o'; @@ -50,14 +104,43 @@ const AttachmentModel = Backbone.Model.extend({ _getMimeType: function(ext) { switch (ext) { - case 'txt': case 'log': - case 'html': case 'htm': case 'js': case 'css': case 'xml': case 'config': case 'json': case 'yaml': - case 'cpp': case 'c': case 'h': case 'cc': case 'hpp': case 'mm': case 'cs': case 'php': case 'sh': - case 'py': case 'java': case 'rb': case 'cfg': case 'properties': case 'yml': case 'asm': case 'pem': + case 'txt': + case 'log': + case 'html': + case 'htm': + case 'js': + case 'css': + case 'xml': + case 'config': + case 'json': + case 'yaml': + case 'cpp': + case 'c': + case 'h': + case 'cc': + case 'hpp': + case 'mm': + case 'cs': + case 'php': + case 'sh': + case 'py': + case 'java': + case 'rb': + case 'cfg': + case 'properties': + case 'yml': + case 'asm': + case 'pem': return 'text/plain'; case 'pdf': return 'application/pdf'; - case 'jpeg': case 'jpg': case 'png': case 'gif': case 'bmp': case 'tiff': case 'svg': + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + case 'bmp': + case 'tiff': + case 'svg': return 'image/' + ext; } }, diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index f46771ad..9a0df724 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -13,7 +13,16 @@ const EntryModel = Backbone.Model.extend({ urlRegex: /^https?:\/\//i, fieldRefRegex: /^\{REF:([TNPAU])@I:(\w{32})}$/, - builtInFields: ['Title', 'Password', 'UserName', 'URL', 'Notes', 'TOTP Seed', 'TOTP Settings', '_etm_template_uuid'], + builtInFields: [ + 'Title', + 'Password', + 'UserName', + 'URL', + 'Notes', + 'TOTP Seed', + 'TOTP Settings', + '_etm_template_uuid' + ], fieldRefFields: ['title', 'password', 'user', 'url', 'notes'], fieldRefIds: { T: 'Title', U: 'UserName', P: 'Password', A: 'URL', N: 'Notes' }, @@ -32,7 +41,7 @@ const EntryModel = Backbone.Model.extend({ _fillByEntry: function() { const entry = this.entry; - this.set({id: this.file.subId(entry.uuid.id), uuid: entry.uuid.id}, {silent: true}); + this.set({ id: this.file.subId(entry.uuid.id), uuid: entry.uuid.id }, { silent: true }); this.fileName = this.file.get('name'); this.groupName = this.group.get('title'); this.title = this._getFieldString('Title'); @@ -120,7 +129,8 @@ const EntryModel = Backbone.Model.extend({ _buildAutoType: function() { this.autoTypeEnabled = this.entry.autoType.enabled; - this.autoTypeObfuscation = this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard; + this.autoTypeObfuscation = + this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard; this.autoTypeSequence = this.entry.autoType.defaultSequence; this.autoTypeWindows = this.entry.autoType.items.map(this._convertAutoTypeItem); }, @@ -150,14 +160,18 @@ const EntryModel = Backbone.Model.extend({ _attachmentsToModel: function(binaries) { const att = []; - _.forEach(binaries, (data, title) => { - if (data && data.ref) { - data = data.value; - } - if (data) { - att.push(AttachmentModel.fromAttachment({data: data, title: title})); - } - }, this); + _.forEach( + binaries, + (data, title) => { + if (data && data.ref) { + data = data.value; + } + if (data) { + att.push(AttachmentModel.fromAttachment({ data: data, title: title })); + } + }, + this + ); return att; }, @@ -183,21 +197,25 @@ const EntryModel = Backbone.Model.extend({ }, matches: function(filter) { - return !filter || - (!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) && - (!filter.textLower || (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) && - (!filter.color || filter.color === true && this.searchColor || this.searchColor === filter.color) && - (!filter.autoType || this.autoTypeEnabled); + return ( + !filter || + ((!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) && + (!filter.textLower || + (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) && + (!filter.color || (filter.color === true && this.searchColor) || this.searchColor === filter.color) && + (!filter.autoType || this.autoTypeEnabled)) + ); }, matchesAdv: function(filter) { const adv = filter.advanced; - let search, - match; + let search, match; if (adv.regex) { try { search = new RegExp(filter.text, adv.cs ? '' : 'i'); - } catch (e) { return false; } + } catch (e) { + return false; + } match = this.matchRegex; } else if (adv.cs) { search = filter.text; @@ -387,7 +405,7 @@ const EntryModel = Backbone.Model.extend({ }, setField: function(field, val, allowEmpty) { - const hasValue = val && (typeof val === 'string' || val.isProtected && val.byteLength); + const hasValue = val && (typeof val === 'string' || (val.isProtected && val.byteLength)); if (hasValue || allowEmpty || this.builtInFields.indexOf(field) >= 0) { this._entryModified(); val = this.sanitizeFieldValue(val); @@ -542,8 +560,7 @@ const EntryModel = Backbone.Model.extend({ if (settings && settings.isProtected) { settings = settings.getText(); } - let period, - digits; + let period, digits; if (settings) { settings = settings.split(';'); if (settings.length > 0 && settings[0] > 0) { @@ -601,8 +618,9 @@ const EntryModel = Backbone.Model.extend({ setAutoTypeObfuscation: function(enabled) { this._entryModified(); - this.entry.autoType.obfuscation = - enabled ? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard : kdbxweb.Consts.AutoTypeObfuscationOptions.None; + this.entry.autoType.obfuscation = enabled + ? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard + : kdbxweb.Consts.AutoTypeObfuscationOptions.None; this._buildAutoType(); }, @@ -674,20 +692,17 @@ const EntryModel = Backbone.Model.extend({ const fieldNames = Object.keys(this.fields); _.forEach(fieldNames, field => { - ranking.push( - { - field: field, - multiplicator: 2 - } - ); + ranking.push({ + field: field, + multiplicator: 2 + }); }); _.forEach(ranking, rankingEntry => { if (this._getFieldString(rankingEntry.field).toLowerCase() !== '') { - const calculatedRank = Ranking.getStringRank( - searchString, - this._getFieldString(rankingEntry.field).toLowerCase() - ) * rankingEntry.multiplicator; + const calculatedRank = + Ranking.getStringRank(searchString, this._getFieldString(rankingEntry.field).toLowerCase()) * + rankingEntry.multiplicator; rank += calculatedRank; } }); diff --git a/app/scripts/models/file-info-model.js b/app/scripts/models/file-info-model.js index 9c31c135..905f9014 100644 --- a/app/scripts/models/file-info-model.js +++ b/app/scripts/models/file-info-model.js @@ -19,11 +19,15 @@ const FileInfoModel = Backbone.Model.extend({ }, initialize: function(data, options) { - _.each(data, function(val, key) { - if (/Date$/.test(key)) { - this.set(key, val ? new Date(val) : null, options); - } - }, this); + _.each( + data, + function(val, key) { + if (/Date$/.test(key)) { + this.set(key, val ? new Date(val) : null, options); + } + }, + this + ); } }); diff --git a/app/scripts/models/file-model.js b/app/scripts/models/file-model.js index 45bf1a55..ad1cc829 100644 --- a/app/scripts/models/file-model.js +++ b/app/scripts/models/file-model.js @@ -57,8 +57,17 @@ const FileModel = Backbone.Model.extend({ if (keyFileData) { kdbxweb.ByteUtils.zeroBuffer(keyFileData); } - logger.info('Opened file ' + this.get('name') + ': ' + logger.ts(ts) + ', ' + - this.kdfArgsToString(db.header) + ', ' + Math.round(fileData.byteLength / 1024) + ' kB'); + logger.info( + 'Opened file ' + + this.get('name') + + ': ' + + logger.ts(ts) + + ', ' + + this.kdfArgsToString(db.header) + + ', ' + + Math.round(fileData.byteLength / 1024) + + ' kB' + ); callback(); }) .catch(err => { @@ -77,13 +86,17 @@ const FileModel = Backbone.Model.extend({ kdfArgsToString: function(header) { if (header.kdfParameters) { - return header.kdfParameters.keys().map(key => { - const val = header.kdfParameters.get(key); - if (val instanceof ArrayBuffer) { - return; - } - return key + '=' + val; - }).filter(p => p).join('&'); + return header.kdfParameters + .keys() + .map(key => { + const val = header.kdfParameters.get(key); + if (val instanceof ArrayBuffer) { + return; + } + return key + '=' + val; + }) + .filter(p => p) + .join('&'); } else if (header.keyEncryptionRounds) { return header.keyEncryptionRounds + ' rounds'; } else { @@ -127,14 +140,13 @@ const FileModel = Backbone.Model.extend({ const password = kdbxweb.ProtectedValue.fromString('demo'); const credentials = new kdbxweb.Credentials(password); const demoFile = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(demoFileData)); - kdbxweb.Kdbx.load(demoFile, credentials) - .then(db => { - this.db = db; - this.set('name', 'Demo'); - this.readModel(); - this.setOpenFile({passwordLength: 4, demo: true}); - callback(); - }); + kdbxweb.Kdbx.load(demoFile, credentials).then(db => { + this.db = db; + this.set('name', 'Demo'); + this.readModel(); + this.setOpenFile({ passwordLength: 4, demo: true }); + callback(); + }); }, setOpenFile: function(props) { @@ -153,17 +165,20 @@ const FileModel = Backbone.Model.extend({ readModel: function() { const groups = new GroupCollection(); - this.set({ - uuid: this.db.getDefaultGroup().uuid.toString(), - groups: groups, - defaultUser: this.db.meta.defaultUser, - recycleBinEnabled: this.db.meta.recycleBinEnabled, - historyMaxItems: this.db.meta.historyMaxItems, - historyMaxSize: this.db.meta.historyMaxSize, - keyEncryptionRounds: this.db.header.keyEncryptionRounds, - keyChangeForce: this.db.meta.keyChangeForce, - kdfParameters: this.readKdfParams() - }, { silent: true }); + this.set( + { + uuid: this.db.getDefaultGroup().uuid.toString(), + groups: groups, + defaultUser: this.db.meta.defaultUser, + recycleBinEnabled: this.db.meta.recycleBinEnabled, + historyMaxItems: this.db.meta.historyMaxItems, + historyMaxSize: this.db.meta.historyMaxSize, + keyEncryptionRounds: this.db.header.keyEncryptionRounds, + keyChangeForce: this.db.meta.keyChangeForce, + kdfParameters: this.readKdfParams() + }, + { silent: true } + ); this.db.groups.forEach(function(group) { let groupModel = this.getGroup(this.subId(group.uuid.id)); if (groupModel) { @@ -204,12 +219,15 @@ const FileModel = Backbone.Model.extend({ buildObjectMap: function() { const entryMap = {}; const groupMap = {}; - this.forEachGroup(group => { - groupMap[group.id] = group; - group.forEachOwnEntry(null, entry => { - entryMap[entry.id] = entry; - }); - }, { includeDisabled: true }); + this.forEachGroup( + group => { + groupMap[group.id] = group; + group.forEachOwnEntry(null, entry => { + entryMap[entry.id] = entry; + }); + }, + { includeDisabled: true } + ); this.entryMap = entryMap; this.groupMap = groupMap; }, @@ -371,7 +389,8 @@ const FileModel = Backbone.Model.extend({ binaries: true }); this.db.cleanup({ binaries: true }); - this.db.save() + this.db + .save() .then(data => { cb(data); }) @@ -382,8 +401,9 @@ const FileModel = Backbone.Model.extend({ }, getXml: function(cb) { - this.db.saveXml() - .then(xml => { cb(xml); }); + this.db.saveXml().then(xml => { + cb(xml); + }); }, getKeyFileHash: function() { @@ -508,7 +528,9 @@ const FileModel = Backbone.Model.extend({ this.db.meta.name = name; this.db.meta.nameChanged = new Date(); this.set('name', name); - this.get('groups').first().setName(name); + this.get('groups') + .first() + .setName(name); this.setModified(); this.reload(); }, @@ -571,10 +593,13 @@ const FileModel = Backbone.Model.extend({ const trashGroup = this.getTrashGroup(); if (trashGroup) { let modified = false; - trashGroup.getOwnSubGroups().slice().forEach(function(group) { - this.db.move(group, null); - modified = true; - }, this); + trashGroup + .getOwnSubGroups() + .slice() + .forEach(function(group) { + this.db.move(group, null); + modified = true; + }, this); trashGroup.group.entries.slice().forEach(function(entry) { this.db.move(entry, null); modified = true; diff --git a/app/scripts/models/group-model.js b/app/scripts/models/group-model.js index 8e68d0ef..06cf1f56 100644 --- a/app/scripts/models/group-model.js +++ b/app/scripts/models/group-model.js @@ -24,28 +24,35 @@ const GroupModel = MenuItemModel.extend({ }), initialize: function() { - if (!GroupCollection) { GroupCollection = require('../collections/group-collection'); } - if (!EntryCollection) { EntryCollection = require('../collections/entry-collection'); } + if (!GroupCollection) { + GroupCollection = require('../collections/group-collection'); + } + if (!EntryCollection) { + EntryCollection = require('../collections/entry-collection'); + } }, setGroup: function(group, file, parentGroup) { const isRecycleBin = group.uuid.equals(file.db.meta.recycleBinUuid); const id = file.subId(group.uuid.id); - this.set({ - id: id, - uuid: group.uuid.id, - expanded: group.expanded, - visible: !isRecycleBin, - items: new GroupCollection(), - entries: new EntryCollection(), - filterValue: id, - enableSearching: group.enableSearching, - enableAutoType: group.enableAutoType, - autoTypeSeq: group.defaultAutoTypeSeq, - top: !parentGroup, - drag: !!parentGroup, - collapsible: !!parentGroup - }, { silent: true }); + this.set( + { + id: id, + uuid: group.uuid.id, + expanded: group.expanded, + visible: !isRecycleBin, + items: new GroupCollection(), + entries: new EntryCollection(), + filterValue: id, + enableSearching: group.enableSearching, + enableAutoType: group.enableAutoType, + autoTypeSeq: group.defaultAutoTypeSeq, + top: !parentGroup, + drag: !!parentGroup, + collapsible: !!parentGroup + }, + { silent: true } + ); this.group = group; this.file = file; this.parentGroup = parentGroup; @@ -77,14 +84,17 @@ const GroupModel = MenuItemModel.extend({ }, _fillByGroup: function(silent) { - this.set({ - title: this.parentGroup ? this.group.name : this.file.get('name'), - iconId: this.group.icon, - icon: this._iconFromId(this.group.icon), - customIcon: this._buildCustomIcon(), - customIconId: this.group.customIcon ? this.group.customIcon.toString() : null, - expanded: this.group.expanded !== false - }, { silent: silent }); + this.set( + { + title: this.parentGroup ? this.group.name : this.file.get('name'), + iconId: this.group.icon, + icon: this._iconFromId(this.group.icon), + customIcon: this._buildCustomIcon(), + customIconId: this.group.customIcon ? this.group.customIcon.toString() : null, + expanded: this.group.expanded !== false + }, + { silent: silent } + ); }, _iconFromId: function(id) { @@ -129,10 +139,12 @@ const GroupModel = MenuItemModel.extend({ }, matches: function(filter) { - return (filter && filter.includeDisabled || - this.group.enableSearching !== false && - !this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup) - ) && (!filter || !filter.autoType || this.group.enableAutoType !== false); + return ( + ((filter && filter.includeDisabled) || + (this.group.enableSearching !== false && + !this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup))) && + (!filter || !filter.autoType || this.group.enableAutoType !== false) + ); }, getOwnSubGroups: function() { diff --git a/app/scripts/models/menu/menu-model.js b/app/scripts/models/menu/menu-model.js index aea7db27..10d246c1 100644 --- a/app/scripts/models/menu/menu-model.js +++ b/app/scripts/models/menu/menu-model.js @@ -16,22 +16,40 @@ const MenuModel = Backbone.Model.extend({ initialize: function() { this.menus = {}; - this.allItemsSection = new MenuSectionModel([{ locTitle: 'menuAllItems', icon: 'th-large', active: true, - shortcut: Keys.DOM_VK_A, filterKey: '*' }]); + this.allItemsSection = new MenuSectionModel([ + { locTitle: 'menuAllItems', icon: 'th-large', active: true, shortcut: Keys.DOM_VK_A, filterKey: '*' } + ]); this.allItemsItem = this.allItemsSection.get('items').models[0]; this.groupsSection = new GroupsMenuModel(); - this.colorsSection = new MenuSectionModel([{ locTitle: 'menuColors', icon: 'bookmark', shortcut: Keys.DOM_VK_C, - cls: 'menu__item-colors', filterKey: 'color', filterValue: true }]); + this.colorsSection = new MenuSectionModel([ + { + locTitle: 'menuColors', + icon: 'bookmark', + shortcut: Keys.DOM_VK_C, + cls: 'menu__item-colors', + filterKey: 'color', + filterValue: true + } + ]); this.colorsItem = this.colorsSection.get('items').models[0]; const defTags = [this._getDefaultTagItem()]; this.tagsSection = new MenuSectionModel(defTags); this.tagsSection.set({ scrollable: true, drag: true }); this.tagsSection.defaultItems = defTags; - this.trashSection = new MenuSectionModel([{ locTitle: 'menuTrash', icon: 'trash', shortcut: Keys.DOM_VK_D, - filterKey: 'trash', filterValue: true, drop: true }]); + this.trashSection = new MenuSectionModel([ + { + locTitle: 'menuTrash', + icon: 'trash', + shortcut: Keys.DOM_VK_D, + filterKey: 'trash', + filterValue: true, + drop: true + } + ]); Colors.AllColors.forEach(color => { - this.colorsSection.get('items').models[0] - .addOption({ cls: 'fa ' + color + '-color', value: color, filterValue: color }); + this.colorsSection + .get('items') + .models[0].addOption({ cls: 'fa ' + color + '-color', value: color, filterValue: color }); }); this.menus.app = new MenuSectionCollection([ this.allItemsSection, @@ -41,8 +59,12 @@ const MenuModel = Backbone.Model.extend({ this.trashSection ]); - this.generalSection = new MenuSectionModel([{ locTitle: 'menuSetGeneral', icon: 'cog', page: 'general', active: true }]); - this.shortcutsSection = new MenuSectionModel([{ locTitle: 'shortcuts', icon: 'keyboard-o', page: 'shortcuts' }]); + this.generalSection = new MenuSectionModel([ + { locTitle: 'menuSetGeneral', icon: 'cog', page: 'general', active: true } + ]); + this.shortcutsSection = new MenuSectionModel([ + { locTitle: 'shortcuts', icon: 'keyboard-o', page: 'shortcuts' } + ]); this.pluginsSection = new MenuSectionModel([{ locTitle: 'plugins', icon: 'puzzle-piece', page: 'plugins' }]); this.aboutSection = new MenuSectionModel([{ locTitle: 'menuSetAbout', icon: 'info', page: 'about' }]); this.helpSection = new MenuSectionModel([{ locTitle: 'help', icon: 'question', page: 'help' }]); @@ -66,7 +88,9 @@ const MenuModel = Backbone.Model.extend({ select: function(sel) { const sections = this.get('sections'); - sections.forEach(function(section) { this._select(section, sel.item); }, this); + sections.forEach(function(section) { + this._select(section, sel.item); + }, this); if (sections === this.menus.app) { this.colorsItem.get('options').forEach(opt => opt.set('active', opt === sel.option)); const selColor = sel.item === this.colorsItem && sel.option ? sel.option.get('value') + '-color' : ''; @@ -95,7 +119,7 @@ const MenuModel = Backbone.Model.extend({ if (items) { items.forEach(it => { if (it.get('active') && previousItem) { - this.select({item: previousItem}); + this.select({ item: previousItem }); return false; } return processSection(it); @@ -114,8 +138,8 @@ const MenuModel = Backbone.Model.extend({ if (section.has('visible') && !section.get('visible')) { return true; } - if (section.has('active') && activeItem && (section !== activeItem)) { - this.select({item: section}); + if (section.has('active') && activeItem && section !== activeItem) { + this.select({ item: section }); activeItem = null; return false; } @@ -146,18 +170,24 @@ const MenuModel = Backbone.Model.extend({ _setLocale: function() { [this.menus.app, this.menus.settings].forEach(menu => { - menu.each(section => section.get('items').each(item => { - if (item.get('locTitle')) { - item.set('title', Format.capFirst(Locale[item.get('locTitle')])); - } - })); + menu.each(section => + section.get('items').each(item => { + if (item.get('locTitle')) { + item.set('title', Format.capFirst(Locale[item.get('locTitle')])); + } + }) + ); }); this.tagsSection.defaultItems[0] = this._getDefaultTagItem(); }, _getDefaultTagItem: function() { - return { title: Format.capFirst(Locale.tags), icon: 'tags', defaultItem: true, - disabled: { header: Locale.menuAlertNoTags, body: Locale.menuAlertNoTagsBody, icon: 'tags' } }; + return { + title: Format.capFirst(Locale.tags), + icon: 'tags', + defaultItem: true, + disabled: { header: Locale.menuAlertNoTags, body: Locale.menuAlertNoTagsBody, icon: 'tags' } + }; }, setMenu: function(type) { diff --git a/app/scripts/models/runtime-data-model.js b/app/scripts/models/runtime-data-model.js index 3c63effa..ba2ef4ac 100644 --- a/app/scripts/models/runtime-data-model.js +++ b/app/scripts/models/runtime-data-model.js @@ -15,7 +15,7 @@ const RuntimeDataModel = Backbone.Model.extend({ // we're not using cookies here now delete data.cookies; } - this.set(data, {silent: true}); + this.set(data, { silent: true }); } }); }, diff --git a/app/scripts/models/update-model.js b/app/scripts/models/update-model.js index 476b6fab..4f9db5c7 100644 --- a/app/scripts/models/update-model.js +++ b/app/scripts/models/update-model.js @@ -15,8 +15,7 @@ const UpdateModel = Backbone.Model.extend({ updateManual: false }, - initialize: function() { - }, + initialize: function() {}, load: function() { return SettingsStore.load('update-info').then(data => { @@ -27,8 +26,9 @@ const UpdateModel = Backbone.Model.extend({ data[key] = val ? new Date(val) : null; } }); - this.set(data, {silent: true}); - } catch (e) { /* failed to load model */ + this.set(data, { silent: true }); + } catch (e) { + /* failed to load model */ } } }); diff --git a/app/scripts/plugins/plugin-gallery.js b/app/scripts/plugins/plugin-gallery.js index e591b2fc..db080fd0 100644 --- a/app/scripts/plugins/plugin-gallery.js +++ b/app/scripts/plugins/plugin-gallery.js @@ -50,17 +50,16 @@ const PluginGallery = { verifySignature(gallery) { const dataToVerify = JSON.stringify(gallery, null, 2).replace(gallery.signature, ''); - return SignatureVerifier.verify( - kdbxweb.ByteUtils.stringToBytes(dataToVerify), - gallery.signature - ).then(isValid => { - if (isValid) { - return gallery; - } - this.logger.error('JSON signature invalid'); - }).catch(e => { - this.logger.error('Error verifying plugins signature', e); - }); + return SignatureVerifier.verify(kdbxweb.ByteUtils.stringToBytes(dataToVerify), gallery.signature) + .then(isValid => { + if (isValid) { + return gallery; + } + this.logger.error('JSON signature invalid'); + }) + .catch(e => { + this.logger.error('Error verifying plugins signature', e); + }); }, getCachedGallery() { diff --git a/app/scripts/plugins/plugin-manager.js b/app/scripts/plugins/plugin-manager.js index 95dc3a1a..0fb13461 100644 --- a/app/scripts/plugins/plugin-manager.js +++ b/app/scripts/plugins/plugin-manager.js @@ -44,21 +44,23 @@ const PluginManager = Backbone.Model.extend({ install(url, expectedManifest, skipSignatureValidation) { this.trigger('change'); - return Plugin.loadFromUrl(url, expectedManifest).then(plugin => { - return this.uninstall(plugin.id).then(() => { - if (skipSignatureValidation) { - plugin.set('skipSignatureValidation', true); - } - return plugin.install(true, false).then(() => { - this.get('plugins').push(plugin); - this.trigger('change'); - this.saveState(); + return Plugin.loadFromUrl(url, expectedManifest) + .then(plugin => { + return this.uninstall(plugin.id).then(() => { + if (skipSignatureValidation) { + plugin.set('skipSignatureValidation', true); + } + return plugin.install(true, false).then(() => { + this.get('plugins').push(plugin); + this.trigger('change'); + this.saveState(); + }); }); + }) + .catch(e => { + this.trigger('change'); + throw e; }); - }).catch(e => { - this.trigger('change'); - throw e; - }); }, installIfNew(url, expectedManifest, skipSignatureValidation) { @@ -112,24 +114,35 @@ const PluginManager = Backbone.Model.extend({ update(id) { const plugins = this.get('plugins'); const oldPlugin = plugins.get(id); - const validStatuses = [Plugin.STATUS_ACTIVE, Plugin.STATUS_INACTIVE, Plugin.STATUS_NONE, Plugin.STATUS_ERROR, Plugin.STATUS_INVALID]; + const validStatuses = [ + Plugin.STATUS_ACTIVE, + Plugin.STATUS_INACTIVE, + Plugin.STATUS_NONE, + Plugin.STATUS_ERROR, + Plugin.STATUS_INVALID + ]; if (!oldPlugin || validStatuses.indexOf(oldPlugin.get('status')) < 0) { return Promise.reject(); } const url = oldPlugin.get('url'); this.trigger('change'); - return Plugin.loadFromUrl(url).then(newPlugin => { - return oldPlugin.update(newPlugin).then(() => { - this.trigger('change'); - this.saveState(); - }).catch(e => { + return Plugin.loadFromUrl(url) + .then(newPlugin => { + return oldPlugin + .update(newPlugin) + .then(() => { + this.trigger('change'); + this.saveState(); + }) + .catch(e => { + this.trigger('change'); + throw e; + }); + }) + .catch(e => { this.trigger('change'); throw e; }); - }).catch(e => { - this.trigger('change'); - throw e; - }); }, setAutoUpdate(id, enabled) { @@ -144,7 +157,9 @@ const PluginManager = Backbone.Model.extend({ }, runAutoUpdate() { - const queue = this.get('plugins').filter(p => p.get('autoUpdate')).map(p => p.id); + const queue = this.get('plugins') + .filter(p => p.get('autoUpdate')) + .map(p => p.id); if (!queue.length) { return Promise.resolve(); } @@ -163,7 +178,9 @@ const PluginManager = Backbone.Model.extend({ const updateNext = () => { const pluginId = queue.shift(); if (pluginId) { - return this.update(pluginId).catch(() => {}).then(updateNext); + return this.update(pluginId) + .catch(() => {}) + .then(updateNext); } }; return updateNext(); @@ -178,10 +195,13 @@ const PluginManager = Backbone.Model.extend({ let enabled = desc.enabled; if (enabled) { const galleryPlugin = gallery ? gallery.plugins.find(pl => pl.manifest.name === desc.manifest.name) : null; - const expectedPublicKey = galleryPlugin ? galleryPlugin.manifest.publicKey : SignatureVerifier.getPublicKey(); + const expectedPublicKey = galleryPlugin + ? galleryPlugin.manifest.publicKey + : SignatureVerifier.getPublicKey(); enabled = desc.manifest.publicKey === expectedPublicKey; } - return plugin.install(enabled, true) + return plugin + .install(enabled, true) .then(() => plugin) .catch(() => plugin); }, diff --git a/app/scripts/plugins/plugin.js b/app/scripts/plugins/plugin.js index 0f89e45b..b11e16ff 100644 --- a/app/scripts/plugins/plugin.js +++ b/app/scripts/plugins/plugin.js @@ -29,537 +29,561 @@ const PluginStatus = { STATUS_ERROR: 'error' }; -const Plugin = Backbone.Model.extend(_.extend({}, PluginStatus, { - idAttribute: 'name', +const Plugin = Backbone.Model.extend( + _.extend({}, PluginStatus, { + idAttribute: 'name', - defaults: { - name: '', - manifest: '', - url: '', - status: '', - autoUpdate: false, - installTime: null, - installError: null, - updateCheckDate: null, - updateError: null, - skipSignatureValidation: false - }, + defaults: { + name: '', + manifest: '', + url: '', + status: '', + autoUpdate: false, + installTime: null, + installError: null, + updateCheckDate: null, + updateError: null, + skipSignatureValidation: false + }, - resources: {}, - module: null, + resources: {}, + module: null, - initialize(options) { - const name = options.manifest.name; - this.set({ name }); - this.logger = new Logger(`plugin:${name}`); - }, + initialize(options) { + const name = options.manifest.name; + this.set({ name }); + this.logger = new Logger(`plugin:${name}`); + }, - install(activate, local) { - const ts = this.logger.ts(); - this.set('status', this.STATUS_INSTALLING); - return Promise.resolve().then(() => { - const error = this.validateManifest(); - if (error) { - this.logger.error('Manifest validation error', error); - this.set('status', this.STATUS_INVALID); - throw 'Plugin validation error: ' + error; - } - this.set('status', this.STATUS_INACTIVE); - if (!activate) { - this.logger.info('Loaded inactive plugin'); - return; - } - return this.installWithManifest(local) - .then(() => this.set('installTime', this.logger.ts() - ts)) - .catch(err => { - this.logger.error('Error installing plugin', err); - this.set({ - status: this.STATUS_ERROR, - installError: err, - installTime: this.logger.ts() - ts, - updateError: null - }); - throw err; - }); - }); - }, - - validateManifest() { - const manifest = this.get('manifest'); - if (!manifest.name) { - return 'No plugin name'; - } - if (!manifest.description) { - return 'No plugin description'; - } - if (!/^\d+\.\d+\.\d+$/.test(manifest.version || '')) { - return 'Invalid plugin version'; - } - if (manifest.manifestVersion !== '0.1.0') { - return 'Invalid manifest version ' + manifest.manifestVersion; - } - if (!manifest.author || !manifest.author.email || !manifest.author.name || !manifest.author.url) { - return 'Invalid plugin author'; - } - if (!manifest.url) { - return 'No plugin url'; - } - if (!manifest.publicKey) { - return 'No plugin public key'; - } - if (!this.get('skipSignatureValidation') && manifest.publicKey !== SignatureVerifier.getPublicKey()) { - return 'Public key mismatch'; - } - if (!manifest.resources || !Object.keys(manifest.resources).length) { - return 'No plugin resources'; - } - if (manifest.resources.loc && - (!manifest.locale || !manifest.locale.title || !/^[a-z]{2}(-[A-Z]{2})?$/.test(manifest.locale.name))) { - return 'Bad plugin locale'; - } - if (manifest.desktop && !RuntimeInfo.launcher) { - return 'Desktop plugin'; - } - if (manifest.versionMin) { - if (!/^\d+\.\d+\.\d+$/.test(manifest.versionMin)) { - return 'Invalid versionMin'; - } - if (SemVer.compareVersions(manifest.versionMin, RuntimeInfo.version) > 0) { - return `Required min app version is ${manifest.versionMin}, actual ${RuntimeInfo.version}`; - } - } - if (manifest.versionMax) { - if (!/^\d+\.\d+\.\d+$/.test(manifest.versionMax)) { - return 'Invalid versionMin'; - } - if (SemVer.compareVersions(manifest.versionMax, RuntimeInfo.version) < 0) { - return `Required max app version is ${manifest.versionMax}, actual ${RuntimeInfo.version}`; - } - } - }, - - validateUpdatedManifest(newManifest) { - const manifest = this.get('manifest'); - if (manifest.name !== newManifest.name) { - return 'Plugin name mismatch'; - } - if (manifest.publicKey !== newManifest.publicKey) { - return 'Public key mismatch'; - } - }, - - installWithManifest(local) { - const manifest = this.get('manifest'); - this.logger.info('Loading plugin with resources', Object.keys(manifest.resources).join(', '), local ? '(local)' : '(url)'); - this.resources = {}; - const ts = this.logger.ts(); - const results = Object.keys(manifest.resources) - .map(res => this.loadResource(res, local)); - return Promise.all(results) - .catch(() => { throw 'Error loading plugin resources'; }) - .then(() => this.installWithResources()) - .then(() => local ? undefined : this.saveResources()) - .then(() => { this.logger.info('Install complete', this.logger.ts(ts)); }); - }, - - getResourcePath(res) { - switch (res) { - case 'css': - return 'plugin.css'; - case 'js': - return 'plugin.js'; - case 'loc': - return this.get('manifest').locale.name + '.json'; - default: - throw `Unknown resource ${res}`; - } - }, - - getStorageResourcePath(res) { - return this.id + '_' + this.getResourcePath(res); - }, - - loadResource(type, local) { - const ts = this.logger.ts(); - let res; - if (local) { - res = new Promise((resolve, reject) => { - const storageKey = this.getStorageResourcePath(type); - io.load(storageKey, (err, data) => err ? reject(err) : resolve(data)); - }); - } else { - const url = this.get('url'); - res = httpGet(url + this.getResourcePath(type), true); - } - return res.then(data => { - this.logger.debug('Resource data loaded', type, this.logger.ts(ts)); - return this.verifyResource(data, type).then(data => { - this.resources[type] = data; - }); - }); - }, - - verifyResource(data, type) { - const ts = this.logger.ts(); - const manifest = this.get('manifest'); - const signature = manifest.resources[type]; - return SignatureVerifier.verify(data, signature, manifest.publicKey) - .then(valid => { - if (valid) { - this.logger.debug('Resource signature validated', type, this.logger.ts(ts)); - return data; - } else { - this.logger.error('Resource signature invalid', type); - throw `Signature invalid: ${type}`; - } - }) - .catch(() => { - this.logger.error('Error validating resource signature', type); - throw `Error validating resource signature for ${type}`; - }); - }, - - installWithResources() { - this.logger.info('Installing plugin resources'); - const manifest = this.get('manifest'); - const promises = []; - if (this.resources.css) { - promises.push(this.applyCss(manifest.name, this.resources.css, manifest.theme)); - } - if (this.resources.js) { - promises.push(this.applyJs(manifest.name, this.resources.js)); - } - if (this.resources.loc) { - promises.push(this.applyLoc(manifest.locale, this.resources.loc)); - } - return Promise.all(promises) - .then(() => { - this.set('status', this.STATUS_ACTIVE); - }) - .catch(e => { - this.logger.info('Install error', e); - this.set('status', this.STATUS_ERROR); - return this.disable().then(() => { throw e; }); - }); - }, - - saveResources() { - const resourceSavePromises = []; - for (const key of Object.keys(this.resources)) { - resourceSavePromises.push(this.saveResource(key, this.resources[key])); - } - return Promise.all(resourceSavePromises) - .catch(e => { - this.logger.debug('Error saving plugin resources', e); - return this.uninstall().then(() => { throw 'Error saving plugin resources'; }); - }); - }, - - saveResource(key, value) { - return new Promise((resolve, reject) => { - const storageKey = this.getStorageResourcePath(key); - io.save(storageKey, value, e => { - if (e) { - reject(e); - } else { - resolve(); - } - }); - }); - }, - - deleteResources() { - const resourceDeletePromises = []; - for (const key of Object.keys(this.resources)) { - resourceDeletePromises.push(this.deleteResource(key)); - } - return Promise.all(resourceDeletePromises); - }, - - deleteResource(key) { - return new Promise(resolve => { - const storageKey = this.getStorageResourcePath(key); - io.remove(storageKey, () => resolve()); - }); - }, - - applyCss(name, data, theme) { - return Promise.resolve().then(() => { - const text = kdbxweb.ByteUtils.bytesToString(data); - const id = 'plugin-css-' + name; - this.createElementInHead('style', id, 'text/css', text); - if (theme) { - const locKey = this.getThemeLocaleKey(theme.name); - SettingsManager.allThemes[theme.name] = locKey; - BaseLocale[locKey] = theme.title; - for (const styleSheet of Array.from(document.styleSheets)) { - if (styleSheet.ownerNode.id === id) { - this.processThemeStyleSheet(styleSheet, theme); - break; - } - } - } - this.logger.debug('Plugin style installed'); - }); - }, - - processThemeStyleSheet(styleSheet, theme) { - const themeSelector = '.th-' + theme.name; - const badSelectors = []; - for (const rule of Array.from(styleSheet.cssRules)) { - if (rule.selectorText && rule.selectorText.lastIndexOf(themeSelector, 0) !== 0) { - badSelectors.push(rule.selectorText); - } - if (rule.selectorText === themeSelector) { - this.addThemeVariables(rule); - } - } - if (badSelectors.length) { - this.logger.error('Themes must not add rules outside theme namespace. Bad selectors:', badSelectors); - throw 'Invalid theme'; - } - }, - - addThemeVariables(rule) { - ThemeVars.apply(rule.style); - }, - - applyJs(name, data) { - return Promise.resolve().then(() => { - let text = kdbxweb.ByteUtils.bytesToString(data); - this.module = {exports: {}}; - const id = 'plugin-' + Date.now().toString() + Math.random().toString(); - global[id] = { - require: PluginApi.require, - module: this.module - }; - text = `(function(require, module){${text}})(window["${id}"].require,window["${id}"].module);`; + install(activate, local) { const ts = this.logger.ts(); - this.createElementInHead('script', 'plugin-js-' + name, 'text/javascript', text); - return new Promise((resolve, reject) => { - setTimeout(() => { - delete global[id]; - if (this.module.exports.uninstall) { - this.logger.debug('Plugin script installed', this.logger.ts(ts)); - this.loadPluginSettings(); - resolve(); - } else { - reject('Plugin script installation failed'); - } - }, 0); - }); - }); - }, - - createElementInHead(tagName, id, type, text) { - let el = document.getElementById(id); - if (el) { - el.parentNode.removeChild(el); - } - el = document.createElement(tagName); - el.appendChild(document.createTextNode(text)); - el.setAttribute('id', id); - el.setAttribute('type', type); - document.head.appendChild(el); - }, - - removeElement(id) { - const el = document.getElementById(id); - if (el) { - el.parentNode.removeChild(el); - } - }, - - applyLoc(locale, data) { - return Promise.resolve().then(() => { - const text = kdbxweb.ByteUtils.bytesToString(data); - const localeData = JSON.parse(text); - SettingsManager.allLocales[locale.name] = locale.title; - SettingsManager.customLocales[locale.name] = localeData; - this.logger.debug('Plugin locale installed'); - }); - }, - - removeLoc(locale) { - delete SettingsManager.allLocales[locale.name]; - delete SettingsManager.customLocales[locale.name]; - if (SettingsManager.activeLocale === locale.name) { - AppSettingsModel.instance.set('locale', 'en'); - } - }, - - getThemeLocaleKey(name) { - return `setGenThemeCustom_${name}`; - }, - - removeTheme(theme) { - delete SettingsManager.allThemes[theme.name]; - if (AppSettingsModel.instance.get('theme') === theme.name) { - AppSettingsModel.instance.set('theme', 'fb'); - } - delete BaseLocale[this.getThemeLocaleKey(theme.name)]; - }, - - loadPluginSettings() { - if (!this.module || !this.module.exports || !this.module.exports.setSettings) { - return; - } - const ts = this.logger.ts(); - const settingPrefix = this.getSettingPrefix(); - let settings = null; - for (const key of Object.keys(AppSettingsModel.instance.attributes)) { - if (key.lastIndexOf(settingPrefix, 0) === 0) { - if (!settings) { - settings = {}; + this.set('status', this.STATUS_INSTALLING); + return Promise.resolve().then(() => { + const error = this.validateManifest(); + if (error) { + this.logger.error('Manifest validation error', error); + this.set('status', this.STATUS_INVALID); + throw 'Plugin validation error: ' + error; } - settings[key.replace(settingPrefix, '')] = AppSettingsModel.instance.attributes[key]; - } - } - if (settings) { - this.setSettings(settings); - } - this.logger.debug('Plugin settings loaded', this.logger.ts(ts)); - }, - - uninstallPluginCode() { - if (this.get('manifest').resources.js && this.module && this.module.exports && this.module.exports.uninstall) { - try { - this.module.exports.uninstall(); - } catch (e) { - this.logger.error('Plugin uninstall method returned an error', e); - } - } - }, - - uninstall() { - const ts = this.logger.ts(); - return this.disable().then(() => { - return this.deleteResources().then(() => { - this.set('status', ''); - this.logger.info('Uninstall complete', this.logger.ts(ts)); + this.set('status', this.STATUS_INACTIVE); + if (!activate) { + this.logger.info('Loaded inactive plugin'); + return; + } + return this.installWithManifest(local) + .then(() => this.set('installTime', this.logger.ts() - ts)) + .catch(err => { + this.logger.error('Error installing plugin', err); + this.set({ + status: this.STATUS_ERROR, + installError: err, + installTime: this.logger.ts() - ts, + updateError: null + }); + throw err; + }); }); - }); - }, + }, - disable() { - const manifest = this.get('manifest'); - 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(() => { - if (manifest.resources.css) { - this.removeElement('plugin-css-' + this.get('name')); - } - if (manifest.resources.js) { - this.uninstallPluginCode(); - this.removeElement('plugin-js-' + this.get('name')); - } - if (manifest.resources.loc) { - this.removeLoc(this.get('manifest').locale); - } - if (manifest.theme) { - this.removeTheme(manifest.theme); - } - this.set('status', this.STATUS_INACTIVE); - this.logger.info('Disable complete', this.logger.ts(ts)); - }); - }, - - update(newPlugin) { - const ts = this.logger.ts(); - const prevStatus = this.get('status'); - this.set('status', this.STATUS_UPDATING); - return Promise.resolve().then(() => { + validateManifest() { 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.logger.info(`v${manifest.version} is the latest plugin version`); + if (!manifest.name) { + return 'No plugin name'; + } + if (!manifest.description) { + return 'No plugin description'; + } + if (!/^\d+\.\d+\.\d+$/.test(manifest.version || '')) { + return 'Invalid plugin version'; + } + if (manifest.manifestVersion !== '0.1.0') { + return 'Invalid manifest version ' + manifest.manifestVersion; + } + if (!manifest.author || !manifest.author.email || !manifest.author.name || !manifest.author.url) { + return 'Invalid plugin author'; + } + if (!manifest.url) { + return 'No plugin url'; + } + if (!manifest.publicKey) { + return 'No plugin public key'; + } + if (!this.get('skipSignatureValidation') && manifest.publicKey !== SignatureVerifier.getPublicKey()) { + return 'Public key mismatch'; + } + if (!manifest.resources || !Object.keys(manifest.resources).length) { + return 'No plugin resources'; + } + if ( + manifest.resources.loc && + (!manifest.locale || !manifest.locale.title || !/^[a-z]{2}(-[A-Z]{2})?$/.test(manifest.locale.name)) + ) { + return 'Bad plugin locale'; + } + if (manifest.desktop && !RuntimeInfo.launcher) { + return 'Desktop plugin'; + } + if (manifest.versionMin) { + if (!/^\d+\.\d+\.\d+$/.test(manifest.versionMin)) { + return 'Invalid versionMin'; + } + if (SemVer.compareVersions(manifest.versionMin, RuntimeInfo.version) > 0) { + return `Required min app version is ${manifest.versionMin}, actual ${RuntimeInfo.version}`; + } + } + if (manifest.versionMax) { + if (!/^\d+\.\d+\.\d+$/.test(manifest.versionMax)) { + return 'Invalid versionMin'; + } + if (SemVer.compareVersions(manifest.versionMax, RuntimeInfo.version) < 0) { + return `Required max app version is ${manifest.versionMax}, actual ${RuntimeInfo.version}`; + } + } + }, + + validateUpdatedManifest(newManifest) { + const manifest = this.get('manifest'); + if (manifest.name !== newManifest.name) { + return 'Plugin name mismatch'; + } + if (manifest.publicKey !== newManifest.publicKey) { + return 'Public key mismatch'; + } + }, + + installWithManifest(local) { + const manifest = this.get('manifest'); + this.logger.info( + 'Loading plugin with resources', + Object.keys(manifest.resources).join(', '), + local ? '(local)' : '(url)' + ); + this.resources = {}; + const ts = this.logger.ts(); + const results = Object.keys(manifest.resources).map(res => this.loadResource(res, local)); + return Promise.all(results) + .catch(() => { + throw 'Error loading plugin resources'; + }) + .then(() => this.installWithResources()) + .then(() => (local ? undefined : this.saveResources())) + .then(() => { + this.logger.info('Install complete', this.logger.ts(ts)); + }); + }, + + getResourcePath(res) { + switch (res) { + case 'css': + return 'plugin.css'; + case 'js': + return 'plugin.js'; + case 'loc': + return this.get('manifest').locale.name + '.json'; + default: + throw `Unknown resource ${res}`; + } + }, + + getStorageResourcePath(res) { + return this.id + '_' + this.getResourcePath(res); + }, + + loadResource(type, local) { + const ts = this.logger.ts(); + let res; + if (local) { + res = new Promise((resolve, reject) => { + const storageKey = this.getStorageResourcePath(type); + io.load(storageKey, (err, data) => (err ? reject(err) : resolve(data))); + }); + } else { + const url = this.get('url'); + res = httpGet(url + this.getResourcePath(type), true); + } + return res.then(data => { + this.logger.debug('Resource data loaded', type, this.logger.ts(ts)); + return this.verifyResource(data, type).then(data => { + this.resources[type] = data; + }); + }); + }, + + verifyResource(data, type) { + const ts = this.logger.ts(); + const manifest = this.get('manifest'); + const signature = manifest.resources[type]; + return SignatureVerifier.verify(data, signature, manifest.publicKey) + .then(valid => { + if (valid) { + this.logger.debug('Resource signature validated', type, this.logger.ts(ts)); + return data; + } else { + this.logger.error('Resource signature invalid', type); + throw `Signature invalid: ${type}`; + } + }) + .catch(() => { + this.logger.error('Error validating resource signature', type); + throw `Error validating resource signature for ${type}`; + }); + }, + + installWithResources() { + this.logger.info('Installing plugin resources'); + const manifest = this.get('manifest'); + const promises = []; + if (this.resources.css) { + promises.push(this.applyCss(manifest.name, this.resources.css, manifest.theme)); + } + if (this.resources.js) { + promises.push(this.applyJs(manifest.name, this.resources.js)); + } + if (this.resources.loc) { + promises.push(this.applyLoc(manifest.locale, this.resources.loc)); + } + return Promise.all(promises) + .then(() => { + this.set('status', this.STATUS_ACTIVE); + }) + .catch(e => { + this.logger.info('Install error', e); + this.set('status', this.STATUS_ERROR); + return this.disable().then(() => { + throw e; + }); + }); + }, + + saveResources() { + const resourceSavePromises = []; + for (const key of Object.keys(this.resources)) { + resourceSavePromises.push(this.saveResource(key, this.resources[key])); + } + return Promise.all(resourceSavePromises).catch(e => { + this.logger.debug('Error saving plugin resources', e); + return this.uninstall().then(() => { + throw 'Error saving plugin resources'; + }); + }); + }, + + saveResource(key, value) { + return new Promise((resolve, reject) => { + const storageKey = this.getStorageResourcePath(key); + io.save(storageKey, value, e => { + if (e) { + reject(e); + } else { + resolve(); + } + }); + }); + }, + + deleteResources() { + const resourceDeletePromises = []; + for (const key of Object.keys(this.resources)) { + resourceDeletePromises.push(this.deleteResource(key)); + } + return Promise.all(resourceDeletePromises); + }, + + deleteResource(key) { + return new Promise(resolve => { + const storageKey = this.getStorageResourcePath(key); + io.remove(storageKey, () => resolve()); + }); + }, + + applyCss(name, data, theme) { + return Promise.resolve().then(() => { + const text = kdbxweb.ByteUtils.bytesToString(data); + const id = 'plugin-css-' + name; + this.createElementInHead('style', id, 'text/css', text); + if (theme) { + const locKey = this.getThemeLocaleKey(theme.name); + SettingsManager.allThemes[theme.name] = locKey; + BaseLocale[locKey] = theme.title; + for (const styleSheet of Array.from(document.styleSheets)) { + if (styleSheet.ownerNode.id === id) { + this.processThemeStyleSheet(styleSheet, theme); + break; + } + } + } + this.logger.debug('Plugin style installed'); + }); + }, + + processThemeStyleSheet(styleSheet, theme) { + const themeSelector = '.th-' + theme.name; + const badSelectors = []; + for (const rule of Array.from(styleSheet.cssRules)) { + if (rule.selectorText && rule.selectorText.lastIndexOf(themeSelector, 0) !== 0) { + badSelectors.push(rule.selectorText); + } + if (rule.selectorText === themeSelector) { + this.addThemeVariables(rule); + } + } + if (badSelectors.length) { + this.logger.error('Themes must not add rules outside theme namespace. Bad selectors:', badSelectors); + throw 'Invalid theme'; + } + }, + + addThemeVariables(rule) { + ThemeVars.apply(rule.style); + }, + + applyJs(name, data) { + return Promise.resolve().then(() => { + let text = kdbxweb.ByteUtils.bytesToString(data); + this.module = { exports: {} }; + const id = 'plugin-' + Date.now().toString() + Math.random().toString(); + global[id] = { + require: PluginApi.require, + module: this.module + }; + text = `(function(require, module){${text}})(window["${id}"].require,window["${id}"].module);`; + const ts = this.logger.ts(); + this.createElementInHead('script', 'plugin-js-' + name, 'text/javascript', text); + return new Promise((resolve, reject) => { + setTimeout(() => { + delete global[id]; + if (this.module.exports.uninstall) { + this.logger.debug('Plugin script installed', this.logger.ts(ts)); + this.loadPluginSettings(); + resolve(); + } else { + reject('Plugin script installation failed'); + } + }, 0); + }); + }); + }, + + createElementInHead(tagName, id, type, text) { + let el = document.getElementById(id); + if (el) { + el.parentNode.removeChild(el); + } + el = document.createElement(tagName); + el.appendChild(document.createTextNode(text)); + el.setAttribute('id', id); + el.setAttribute('type', type); + document.head.appendChild(el); + }, + + removeElement(id) { + const el = document.getElementById(id); + if (el) { + el.parentNode.removeChild(el); + } + }, + + applyLoc(locale, data) { + return Promise.resolve().then(() => { + const text = kdbxweb.ByteUtils.bytesToString(data); + const localeData = JSON.parse(text); + SettingsManager.allLocales[locale.name] = locale.title; + SettingsManager.customLocales[locale.name] = localeData; + this.logger.debug('Plugin locale installed'); + }); + }, + + removeLoc(locale) { + delete SettingsManager.allLocales[locale.name]; + delete SettingsManager.customLocales[locale.name]; + if (SettingsManager.activeLocale === locale.name) { + AppSettingsModel.instance.set('locale', 'en'); + } + }, + + getThemeLocaleKey(name) { + return `setGenThemeCustom_${name}`; + }, + + removeTheme(theme) { + delete SettingsManager.allThemes[theme.name]; + if (AppSettingsModel.instance.get('theme') === theme.name) { + AppSettingsModel.instance.set('theme', 'fb'); + } + delete BaseLocale[this.getThemeLocaleKey(theme.name)]; + }, + + loadPluginSettings() { + if (!this.module || !this.module.exports || !this.module.exports.setSettings) { return; } - 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 }); - throw 'Plugin validation error: ' + error; + const ts = this.logger.ts(); + const settingPrefix = this.getSettingPrefix(); + let settings = null; + for (const key of Object.keys(AppSettingsModel.instance.attributes)) { + if (key.lastIndexOf(settingPrefix, 0) === 0) { + if (!settings) { + settings = {}; + } + settings[key.replace(settingPrefix, '')] = AppSettingsModel.instance.attributes[key]; + } } - this.uninstallPluginCode(); - return newPlugin.installWithManifest(false) - .then(() => { - this.module = newPlugin.module; - this.resources = newPlugin.resources; - this.set({ - status: this.STATUS_ACTIVE, - manifest: newManifest, - installTime: this.logger.ts() - ts, - installError: null, - updateCheckDate: Date.now(), - updateError: null - }); - this.logger.info('Update complete', this.logger.ts(ts)); - }) - .catch(err => { - this.logger.error('Error updating plugin', err); - if (prevStatus === this.STATUS_ACTIVE) { - this.logger.info('Activating previous version'); - return this.installWithResources() - .then(() => { + if (settings) { + this.setSettings(settings); + } + this.logger.debug('Plugin settings loaded', this.logger.ts(ts)); + }, + + uninstallPluginCode() { + if ( + this.get('manifest').resources.js && + this.module && + this.module.exports && + this.module.exports.uninstall + ) { + try { + this.module.exports.uninstall(); + } catch (e) { + this.logger.error('Plugin uninstall method returned an error', e); + } + } + }, + + uninstall() { + const ts = this.logger.ts(); + return this.disable().then(() => { + return this.deleteResources().then(() => { + this.set('status', ''); + this.logger.info('Uninstall complete', this.logger.ts(ts)); + }); + }); + }, + + disable() { + const manifest = this.get('manifest'); + 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(() => { + if (manifest.resources.css) { + this.removeElement('plugin-css-' + this.get('name')); + } + if (manifest.resources.js) { + this.uninstallPluginCode(); + this.removeElement('plugin-js-' + this.get('name')); + } + if (manifest.resources.loc) { + this.removeLoc(this.get('manifest').locale); + } + if (manifest.theme) { + this.removeTheme(manifest.theme); + } + this.set('status', this.STATUS_INACTIVE); + this.logger.info('Disable complete', this.logger.ts(ts)); + }); + }, + + update(newPlugin) { + const ts = this.logger.ts(); + const prevStatus = this.get('status'); + this.set('status', this.STATUS_UPDATING); + return Promise.resolve().then(() => { + 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.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); + if (error) { + this.logger.error('Manifest validation error', error); + this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: error }); + throw 'Plugin validation error: ' + error; + } + this.uninstallPluginCode(); + return newPlugin + .installWithManifest(false) + .then(() => { + this.module = newPlugin.module; + this.resources = newPlugin.resources; + this.set({ + status: this.STATUS_ACTIVE, + manifest: newManifest, + installTime: this.logger.ts() - ts, + installError: null, + updateCheckDate: Date.now(), + updateError: null + }); + this.logger.info('Update complete', this.logger.ts(ts)); + }) + .catch(err => { + this.logger.error('Error updating plugin', err); + if (prevStatus === this.STATUS_ACTIVE) { + this.logger.info('Activating previous version'); + return this.installWithResources().then(() => { this.set({ updateCheckDate: Date.now(), updateError: err }); throw err; }); - } else { - this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: err }); - throw err; - } - }); - }); - }, - - setAutoUpdate(enabled) { - this.set('autoUpdate', !!enabled); - }, - - getSettingPrefix() { - return `plugin:${this.id}:`; - }, - - getSettings() { - if (this.get('status') === PluginStatus.STATUS_ACTIVE && this.module && this.module.exports && this.module.exports.getSettings) { - try { - const settings = this.module.exports.getSettings(); - const settingsPrefix = this.getSettingPrefix(); - if (settings instanceof Array) { - return settings.map(setting => { - setting = _.clone(setting); - const value = AppSettingsModel.instance.get(settingsPrefix + setting.name); - if (value !== undefined) { - setting.value = value; + } else { + this.set({ status: prevStatus, updateCheckDate: Date.now(), updateError: err }); + throw err; } - return setting; }); - } - this.logger.error('getSettings: expected Array, got ', typeof settings); - } catch (e) { - this.logger.error('getSettings error', e); - } - } - }, + }); + }, - setSettings(settings) { - for (const key of Object.keys(settings)) { - const value = settings[key]; - AppSettingsModel.instance.set(this.getSettingPrefix() + key, value); - } - if (this.module.exports.setSettings) { - try { - this.module.exports.setSettings(settings); - } catch (e) { - this.logger.error('setSettings error', e); + setAutoUpdate(enabled) { + this.set('autoUpdate', !!enabled); + }, + + getSettingPrefix() { + return `plugin:${this.id}:`; + }, + + getSettings() { + if ( + this.get('status') === PluginStatus.STATUS_ACTIVE && + this.module && + this.module.exports && + this.module.exports.getSettings + ) { + try { + const settings = this.module.exports.getSettings(); + const settingsPrefix = this.getSettingPrefix(); + if (settings instanceof Array) { + return settings.map(setting => { + setting = _.clone(setting); + const value = AppSettingsModel.instance.get(settingsPrefix + setting.name); + if (value !== undefined) { + setting.value = value; + } + return setting; + }); + } + this.logger.error('getSettings: expected Array, got ', typeof settings); + } catch (e) { + this.logger.error('getSettings error', e); + } + } + }, + + setSettings(settings) { + for (const key of Object.keys(settings)) { + const value = settings[key]; + AppSettingsModel.instance.set(this.getSettingPrefix() + key, value); + } + if (this.module.exports.setSettings) { + try { + this.module.exports.setSettings(settings); + } catch (e) { + this.logger.error('setSettings error', e); + } } } - } -})); + }) +); _.extend(Plugin, PluginStatus); @@ -591,7 +615,8 @@ Plugin.loadFromUrl = function(url, expectedManifest) { } } return new Plugin({ - manifest, url + manifest, + url }); }); }; diff --git a/app/scripts/plugins/theme-vars.js b/app/scripts/plugins/theme-vars.js index b80130d5..18e64817 100644 --- a/app/scripts/plugins/theme-vars.js +++ b/app/scripts/plugins/theme-vars.js @@ -43,7 +43,11 @@ const ThemeVars = { result = result.replace(/([\w\-]+)\([^()]+\)/, fnText => { replaced = true; const [, name, argsStr] = fnText.match(/([\w\-]+)\((.*)\)/); - const args = argsStr.trim().split(/\s*,\s*/).filter(arg => arg).map(arg => this.resolveArg(arg, cssStyle, locals)); + const args = argsStr + .trim() + .split(/\s*,\s*/) + .filter(arg => arg) + .map(arg => this.resolveArg(arg, cssStyle, locals)); locals.push(this.fn[name](...args)); return 'L' + (locals.length - 1); }); diff --git a/app/scripts/presenters/entry-presenter.js b/app/scripts/presenters/entry-presenter.js index 796adca6..08e8d97b 100644 --- a/app/scripts/presenters/entry-presenter.js +++ b/app/scripts/presenters/entry-presenter.js @@ -17,21 +17,51 @@ EntryPresenter.prototype = { } return this; }, - get id() { return this.entry ? this.entry.id : this.group.id; }, - get icon() { return this.entry ? this.entry.icon : (this.group.get('icon') || 'folder'); }, - get customIcon() { return this.entry ? this.entry.customIcon : undefined; }, - get color() { return this.entry ? (this.entry.color || (this.entry.customIcon ? this.noColor : undefined)) : undefined; }, - get title() { return this.entry ? this.entry.title : this.group.get('title'); }, - get notes() { return this.entry ? this.entry.notes : undefined; }, - get url() { return this.entry ? this.entry.displayUrl : undefined; }, - get user() { return this.entry ? this.entry.user : undefined; }, - get active() { return this.entry ? this.entry.id === this.activeEntryId : this.group.active; }, - get created() { return this.entry ? Format.dtStr(this.entry.created) : undefined; }, - get updated() { return this.entry ? Format.dtStr(this.entry.updated) : undefined; }, - get expired() { return this.entry ? this.entry.expired : false; }, - get tags() { return this.entry ? this.entry.tags : undefined; }, - get groupName() { return this.entry ? this.entry.groupName : undefined; }, - get fileName() { return this.entry ? this.entry.fileName : undefined; }, + get id() { + return this.entry ? this.entry.id : this.group.id; + }, + get icon() { + return this.entry ? this.entry.icon : this.group.get('icon') || 'folder'; + }, + get customIcon() { + return this.entry ? this.entry.customIcon : undefined; + }, + get color() { + return this.entry ? this.entry.color || (this.entry.customIcon ? this.noColor : undefined) : undefined; + }, + get title() { + return this.entry ? this.entry.title : this.group.get('title'); + }, + get notes() { + return this.entry ? this.entry.notes : undefined; + }, + get url() { + return this.entry ? this.entry.displayUrl : undefined; + }, + get user() { + return this.entry ? this.entry.user : undefined; + }, + get active() { + return this.entry ? this.entry.id === this.activeEntryId : this.group.active; + }, + get created() { + return this.entry ? Format.dtStr(this.entry.created) : undefined; + }, + get updated() { + return this.entry ? Format.dtStr(this.entry.updated) : undefined; + }, + get expired() { + return this.entry ? this.entry.expired : false; + }, + get tags() { + return this.entry ? this.entry.tags : undefined; + }, + get groupName() { + return this.entry ? this.entry.groupName : undefined; + }, + get fileName() { + return this.entry ? this.entry.fileName : undefined; + }, get description() { if (!this.entry) { return '[' + Locale.listGroup + ']'; diff --git a/app/scripts/storage/io-browser-cache.js b/app/scripts/storage/io-browser-cache.js index 50174a06..d25cb8d6 100644 --- a/app/scripts/storage/io-browser-cache.js +++ b/app/scripts/storage/io-browser-cache.js @@ -15,11 +15,15 @@ _.extend(IoBrowserCache.prototype, { const req = idb.open(this.cacheName); req.onerror = e => { this.logger.error('Error opening indexed db', e); - if (callback) { callback(e); } + if (callback) { + callback(e); + } }; req.onsuccess = e => { this.db = e.target.result; - if (callback) { callback(); } + if (callback) { + callback(); + } }; req.onupgradeneeded = e => { const db = e.target.result; @@ -27,7 +31,9 @@ _.extend(IoBrowserCache.prototype, { }; } catch (e) { this.logger.error('Error opening indexed db', e); - if (callback) { callback(e); } + if (callback) { + callback(e); + } } }, @@ -39,18 +45,27 @@ _.extend(IoBrowserCache.prototype, { } try { const ts = this.logger.ts(); - const req = this.db.transaction(['files'], 'readwrite').objectStore('files').put(data, id); + const req = this.db + .transaction(['files'], 'readwrite') + .objectStore('files') + .put(data, id); req.onsuccess = () => { this.logger.debug('Saved', id, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } }; req.onerror = () => { this.logger.error('Error saving to cache', id, req.error); - if (callback) { callback(req.error); } + if (callback) { + callback(req.error); + } }; } catch (e) { this.logger.error('Error saving to cache', id, e); - if (callback) { callback(e); } + if (callback) { + callback(e); + } } }); }, @@ -63,18 +78,27 @@ _.extend(IoBrowserCache.prototype, { } try { const ts = this.logger.ts(); - const req = this.db.transaction(['files'], 'readonly').objectStore('files').get(id); + const req = this.db + .transaction(['files'], 'readonly') + .objectStore('files') + .get(id); req.onsuccess = () => { this.logger.debug('Loaded', id, this.logger.ts(ts)); - if (callback) { callback(null, req.result); } + if (callback) { + callback(null, req.result); + } }; req.onerror = () => { this.logger.error('Error loading from cache', id, req.error); - if (callback) { callback(req.error); } + if (callback) { + callback(req.error); + } }; } catch (e) { this.logger.error('Error loading from cache', id, e); - if (callback) { callback(e, null); } + if (callback) { + callback(e, null); + } } }); }, @@ -87,18 +111,27 @@ _.extend(IoBrowserCache.prototype, { } try { const ts = this.logger.ts(); - const req = this.db.transaction(['files'], 'readwrite').objectStore('files').delete(id); + const req = this.db + .transaction(['files'], 'readwrite') + .objectStore('files') + .delete(id); req.onsuccess = () => { this.logger.debug('Removed', id, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } }; req.onerror = () => { this.logger.error('Error removing from cache', id, req.error); - if (callback) { callback(req.error); } + if (callback) { + callback(req.error); + } }; } catch (e) { this.logger.error('Error removing from cache', id, e); - if (callback) { callback(e); } + if (callback) { + callback(e); + } } }); } diff --git a/app/scripts/storage/io-file-cache.js b/app/scripts/storage/io-file-cache.js index 8c79acec..2c907446 100644 --- a/app/scripts/storage/io-file-cache.js +++ b/app/scripts/storage/io-file-cache.js @@ -37,10 +37,14 @@ _.extend(IoFileCache.prototype, { Launcher.writeFile(path, data, err => { if (err) { this.logger.error('Error saving file', id, err); - if (callback) { callback(err); } + if (callback) { + callback(err); + } } else { this.logger.debug('Saved', id, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } } }); }); @@ -57,10 +61,14 @@ _.extend(IoFileCache.prototype, { Launcher.readFile(path, undefined, (data, err) => { if (err) { this.logger.error('Error loading file', id, err); - if (callback) { callback(err); } + if (callback) { + callback(err); + } } else { this.logger.debug('Loaded', id, this.logger.ts(ts)); - if (callback) { callback(null, data); } + if (callback) { + callback(null, data); + } } }); }); @@ -77,10 +85,14 @@ _.extend(IoFileCache.prototype, { Launcher.deleteFile(path, err => { if (err) { this.logger.error('Error removing file', id, err); - if (callback) { callback(err); } + if (callback) { + callback(err); + } } else { this.logger.debug('Removed', id, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } } }); }); diff --git a/app/scripts/storage/storage-base.js b/app/scripts/storage/storage-base.js index bcc74de3..001fe2c5 100644 --- a/app/scripts/storage/storage-base.js +++ b/app/scripts/storage/storage-base.js @@ -7,8 +7,7 @@ const FeatureDetector = require('../util/feature-detector'); const MaxRequestRetries = 3; -const StorageBase = function() { -}; +const StorageBase = function() {}; _.extend(StorageBase.prototype, { name: null, @@ -104,11 +103,19 @@ _.extend(StorageBase.prototype, { const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; - const winWidth = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; - const winHeight = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; + const winWidth = window.innerWidth + ? window.innerWidth + : document.documentElement.clientWidth + ? document.documentElement.clientWidth + : screen.width; + const winHeight = window.innerHeight + ? window.innerHeight + : document.documentElement.clientHeight + ? document.documentElement.clientHeight + : screen.height; - const left = ((winWidth / 2) - (width / 2)) + dualScreenLeft; - const top = ((winHeight / 2) - (height / 2)) + dualScreenTop; + const left = winWidth / 2 - width / 2 + dualScreenLeft; + const top = winHeight / 2 - height / 2 + dualScreenTop; let settings = { width: width, @@ -120,7 +127,9 @@ _.extend(StorageBase.prototype, { scrollbars: 'yes', location: 'yes' }; - settings = Object.keys(settings).map(key => key + '=' + settings[key]).join(','); + settings = Object.keys(settings) + .map(key => key + '=' + settings[key]) + .join(','); if (FeatureDetector.isStandalone) { sessionStorage.authStorage = this.name; } @@ -147,10 +156,12 @@ _.extend(StorageBase.prototype, { this._oauthToken = oldToken; return callback(); } - const url = opts.url + '?client_id={cid}&scope={scope}&response_type=token&redirect_uri={url}' - .replace('{cid}', encodeURIComponent(opts.clientId)) - .replace('{scope}', encodeURIComponent(opts.scope)) - .replace('{url}', encodeURIComponent(this._getOauthRedirectUrl())); + const url = + opts.url + + '?client_id={cid}&scope={scope}&response_type=token&redirect_uri={url}' + .replace('{cid}', encodeURIComponent(opts.clientId)) + .replace('{scope}', encodeURIComponent(opts.scope)) + .replace('{url}', encodeURIComponent(this._getOauthRedirectUrl())); this.logger.debug('OAuth: popup opened'); const popupWindow = this._openPopup(url, 'OAuth', opts.width, opts.height); if (!popupWindow) { @@ -185,8 +196,7 @@ _.extend(StorageBase.prototype, { window.addEventListener('message', windowMessage); }, - _popupOpened(popupWindow) { - }, + _popupOpened(popupWindow) {}, _oauthProcessReturn: function(message) { const token = this._oauthMsgToToken(message); @@ -201,7 +211,7 @@ _.extend(StorageBase.prototype, { _oauthMsgToToken: function(data) { if (!data.token_type) { if (data.error) { - return {error: data.error, errorDescription: data.error_description}; + return { error: data.error, errorDescription: data.error_description }; } else { return undefined; } diff --git a/app/scripts/storage/storage-dropbox.js b/app/scripts/storage/storage-dropbox.js index dfb32ef8..36b879c0 100644 --- a/app/scripts/storage/storage-dropbox.js +++ b/app/scripts/storage/storage-dropbox.js @@ -80,8 +80,21 @@ const StorageDropbox = StorageBase.extend({ return { desc: 'dropboxSetupDesc', fields: [ - {id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+'}, - {id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderDesc', type: 'text', placeholder: 'dropboxFolderPlaceholder'} + { + id: 'key', + title: 'dropboxAppKey', + desc: 'dropboxAppKeyDesc', + type: 'text', + required: true, + pattern: '\\w+' + }, + { + id: 'folder', + title: 'dropboxFolder', + desc: 'dropboxFolderDesc', + type: 'text', + placeholder: 'dropboxFolderPlaceholder' + } ] }; }, @@ -89,12 +102,29 @@ const StorageDropbox = StorageBase.extend({ getSettingsConfig: function() { const fields = []; const appKey = this._getKey(); - const linkField = {id: 'link', title: 'dropboxLink', type: 'select', value: 'custom', - options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' } }; - const keyField = {id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+', - value: appKey}; - const folderField = {id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderSettingsDesc', type: 'text', - value: this.appSettings.get('dropboxFolder') || ''}; + const linkField = { + id: 'link', + title: 'dropboxLink', + type: 'select', + value: 'custom', + options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' } + }; + const keyField = { + id: 'key', + title: 'dropboxAppKey', + desc: 'dropboxAppKeyDesc', + type: 'text', + required: true, + pattern: '\\w+', + value: appKey + }; + const folderField = { + id: 'folder', + title: 'dropboxFolder', + desc: 'dropboxFolderSettingsDesc', + type: 'text', + value: this.appSettings.get('dropboxFolder') || '' + }; const canUseBuiltInKeys = this._canUseBuiltInKeys(); if (canUseBuiltInKeys) { fields.push(linkField); @@ -198,7 +228,7 @@ const StorageDropbox = StorageBase.extend({ statuses: args.statuses || undefined, success: args.success, error: (e, xhr) => { - let err = xhr.response && xhr.response.error || new Error('Network error'); + let err = (xhr.response && xhr.response.error) || new Error('Network error'); if (err && err.path && err.path['.tag'] === 'not_found') { err = new Error('File removed'); err.notFound = true; @@ -245,7 +275,9 @@ const StorageDropbox = StorageBase.extend({ stat = { folder: true }; } this.logger.debug('Stated', path, stat.folder ? 'folder' : stat.rev, this.logger.ts(ts)); - if (callback) { callback(null, stat); } + if (callback) { + callback(null, stat); + } }, error: callback }); @@ -284,13 +316,12 @@ const StorageDropbox = StorageBase.extend({ }, success: data => { this.logger.debug('Listed', this.logger.ts(ts)); - const fileList = data.entries - .map(f => ({ - name: f.name, - path: this._toRelPath(f['path_display']), - rev: f.rev, - dir: f['.tag'] !== 'file' - })); + const fileList = data.entries.map(f => ({ + name: f.name, + path: this._toRelPath(f['path_display']), + rev: f.rev, + dir: f['.tag'] !== 'file' + })); callback(null, fileList); }, error: callback diff --git a/app/scripts/storage/storage-file-cache.js b/app/scripts/storage/storage-file-cache.js index b667386b..85a1e4dd 100644 --- a/app/scripts/storage/storage-file-cache.js +++ b/app/scripts/storage/storage-file-cache.js @@ -19,7 +19,7 @@ const StorageFileCache = StorageBase.extend({ const path = Launcher.getUserDataPath('OfflineFiles'); - const setPath = (err) => { + const setPath = err => { this.path = err ? null : path; if (err) { this.logger.error('Error opening local offline storage', err); @@ -49,7 +49,9 @@ const StorageFileCache = StorageBase.extend({ return callback && callback(err); } this.logger.debug('Saved', id, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } }); }); }, diff --git a/app/scripts/storage/storage-file.js b/app/scripts/storage/storage-file.js index 4070dc94..47fc4507 100644 --- a/app/scripts/storage/storage-file.js +++ b/app/scripts/storage/storage-file.js @@ -114,10 +114,14 @@ const StorageFile = StorageBase.extend({ Launcher.mkdir(path, err => { if (err) { this.logger.error('Error making local dir', path, err); - if (callback) { callback('Error making local dir'); } + if (callback) { + callback('Error making local dir'); + } } else { this.logger.debug('Made dir', path, this.logger.ts(ts)); - if (callback) { callback(); } + if (callback) { + callback(); + } } }); }, diff --git a/app/scripts/storage/storage-gdrive.js b/app/scripts/storage/storage-gdrive.js index 062e26c3..da1ff990 100644 --- a/app/scripts/storage/storage-gdrive.js +++ b/app/scripts/storage/storage-gdrive.js @@ -11,7 +11,8 @@ const StorageGDrive = StorageBase.extend({ name: 'gdrive', enabled: true, uipos: 30, - iconSvg: '', @@ -24,20 +25,22 @@ const StorageGDrive = StorageBase.extend({ load: function(path, opts, callback) { this.stat(path, opts, (err, stat) => { - if (err) { return callback && callback(err); } + if (err) { + return callback && callback(err); + } this.logger.debug('Load', path); const ts = this.logger.ts(); - const url = this._baseUrl + '/files/{id}/revisions/{rev}?alt=media' - .replace('{id}', path) - .replace('{rev}', stat.rev); + const url = + this._baseUrl + + '/files/{id}/revisions/{rev}?alt=media'.replace('{id}', path).replace('{rev}', stat.rev); this._xhr({ url: url, responseType: 'arraybuffer', - success: (response) => { + success: response => { this.logger.debug('Loaded', path, stat.rev, this.logger.ts(ts)); return callback && callback(null, response, { rev: stat.rev }); }, - error: (err) => { + error: err => { this.logger.error('Load error', path, err, this.logger.ts(ts)); return callback && callback(err); } @@ -55,17 +58,16 @@ const StorageGDrive = StorageBase.extend({ } this.logger.debug('Stat', path); const ts = this.logger.ts(); - const url = this._baseUrl + '/files/{id}?fields=headRevisionId' - .replace('{id}', path); + const url = this._baseUrl + '/files/{id}?fields=headRevisionId'.replace('{id}', path); this._xhr({ url: url, responseType: 'json', - success: (response) => { + success: response => { const rev = response.headRevisionId; this.logger.debug('Stated', path, rev, this.logger.ts(ts)); return callback && callback(null, { rev: rev }); }, - error: (err) => { + error: err => { this.logger.error('Stat error', this.logger.ts(ts), err); return callback && callback(err); } @@ -95,18 +97,33 @@ const StorageGDrive = StorageBase.extend({ url = this._baseUrlUpload + '/files?uploadType=multipart&fields=id,headRevisionId'; const fileName = path.replace(NewFileIdPrefix, '') + '.kdbx'; const boundry = 'b' + Date.now() + 'x' + Math.round(Math.random() * 1000000); - data = new Blob([ - '--', boundry, '\r\n', - 'Content-Type: application/json; charset=UTF-8', '\r\n\r\n', - JSON.stringify({ name: fileName }), '\r\n', - '--', boundry, '\r\n', - 'Content-Type: application/octet-stream', '\r\n\r\n', - data, '\r\n', - '--', boundry, '--', '\r\n' - ], { type: 'multipart/related; boundary="' + boundry + '"' }); + data = new Blob( + [ + '--', + boundry, + '\r\n', + 'Content-Type: application/json; charset=UTF-8', + '\r\n\r\n', + JSON.stringify({ name: fileName }), + '\r\n', + '--', + boundry, + '\r\n', + 'Content-Type: application/octet-stream', + '\r\n\r\n', + data, + '\r\n', + '--', + boundry, + '--', + '\r\n' + ], + { type: 'multipart/related; boundary="' + boundry + '"' } + ); } else { - url = this._baseUrlUpload + '/files/{id}?uploadType=media&fields=headRevisionId' - .replace('{id}', path); + url = + this._baseUrlUpload + + '/files/{id}?uploadType=media&fields=headRevisionId'.replace('{id}', path); data = new Blob([data], { type: 'application/octet-stream' }); } this._xhr({ @@ -114,7 +131,7 @@ const StorageGDrive = StorageBase.extend({ method: isNew ? 'POST' : 'PATCH', responseType: 'json', data: data, - success: (response) => { + success: response => { this.logger.debug('Saved', path, this.logger.ts(ts)); const newRev = response.headRevisionId; if (!newRev) { @@ -122,7 +139,7 @@ const StorageGDrive = StorageBase.extend({ } return callback && callback(null, { rev: newRev, path: isNew ? response.id : null }); }, - error: (err) => { + error: err => { this.logger.error('Save error', path, err, this.logger.ts(ts)); return callback && callback(err); } @@ -132,20 +149,23 @@ const StorageGDrive = StorageBase.extend({ }, list: function(dir, callback) { - this._oauthAuthorize((err) => { - if (err) { return callback && callback(err); } + this._oauthAuthorize(err => { + if (err) { + return callback && callback(err); + } this.logger.debug('List'); - let query = dir === 'shared' ? 'sharedWithMe=true' - : dir ? `"${dir}" in parents` : '"root" in parents'; + let query = dir === 'shared' ? 'sharedWithMe=true' : dir ? `"${dir}" in parents` : '"root" in parents'; query += ' and trashed=false'; - const url = this._baseUrl + '/files?fields={fields}&q={q}&pageSize=1000' - .replace('{fields}', encodeURIComponent('files(id,name,mimeType,headRevisionId)')) - .replace('{q}', encodeURIComponent(query)); + const url = + this._baseUrl + + '/files?fields={fields}&q={q}&pageSize=1000' + .replace('{fields}', encodeURIComponent('files(id,name,mimeType,headRevisionId)')) + .replace('{q}', encodeURIComponent(query)); const ts = this.logger.ts(); this._xhr({ url: url, responseType: 'json', - success: (response) => { + success: response => { if (!response) { this.logger.error('List error', this.logger.ts(ts)); return callback && callback('list error'); @@ -167,7 +187,7 @@ const StorageGDrive = StorageBase.extend({ } return callback && callback(null, fileList); }, - error: (err) => { + error: err => { this.logger.error('List error', this.logger.ts(ts), err); return callback && callback(err); } @@ -188,7 +208,7 @@ const StorageGDrive = StorageBase.extend({ this.logger.debug('Removed', path, this.logger.ts(ts)); return callback && callback(); }, - error: (err) => { + error: err => { this.logger.error('Remove error', path, err, this.logger.ts(ts)); return callback && callback(err); } diff --git a/app/scripts/storage/storage-onedrive.js b/app/scripts/storage/storage-onedrive.js index 6426380e..214e655d 100644 --- a/app/scripts/storage/storage-onedrive.js +++ b/app/scripts/storage/storage-onedrive.js @@ -9,7 +9,8 @@ const StorageOneDrive = StorageBase.extend({ name: 'onedrive', enabled: true, uipos: 40, - iconSvg: '' + + iconSvg: + '' + ' {}); } diff --git a/app/scripts/storage/storage-webdav.js b/app/scripts/storage/storage-webdav.js index 3f1a1a4d..5bf722a6 100644 --- a/app/scripts/storage/storage-webdav.js +++ b/app/scripts/storage/storage-webdav.js @@ -13,9 +13,21 @@ const StorageWebDav = StorageBase.extend({ getOpenConfig: function() { return { fields: [ - {id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true}, - {id: 'user', title: 'openUser', desc: 'openUserDesc', placeholder: 'openUserPlaceholder', type: 'text'}, - {id: 'password', title: 'openPass', desc: 'openPassDesc', placeholder: 'openPassPlaceholder', type: 'password'} + { id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true }, + { + id: 'user', + title: 'openUser', + desc: 'openUserDesc', + placeholder: 'openUserPlaceholder', + type: 'text' + }, + { + id: 'password', + title: 'openPass', + desc: 'openPassDesc', + placeholder: 'openPassPlaceholder', + type: 'password' + } ] }; }, @@ -23,9 +35,13 @@ const StorageWebDav = StorageBase.extend({ getSettingsConfig: function() { return { fields: [ - { id: 'webdavSaveMethod', title: 'webdavSaveMethod', type: 'select', + { + id: 'webdavSaveMethod', + title: 'webdavSaveMethod', + type: 'select', value: this.appSettings.get('webdavSaveMethod') || 'default', - options: { default: 'webdavSaveMove', put: 'webdavSavePut' } } + options: { default: 'webdavSaveMove', put: 'webdavSavePut' } + } ] }; }, @@ -35,27 +51,37 @@ const StorageWebDav = StorageBase.extend({ }, load: function(path, opts, callback) { - this._request({ - op: 'Load', - method: 'GET', - path: path, - user: opts ? opts.user : null, - password: opts ? opts.password : null - }, callback ? (err, xhr, stat) => { - callback(err, xhr.response, stat); - } : null); + this._request( + { + op: 'Load', + method: 'GET', + path: path, + user: opts ? opts.user : null, + password: opts ? opts.password : null + }, + callback + ? (err, xhr, stat) => { + callback(err, xhr.response, stat); + } + : null + ); }, stat: function(path, opts, callback) { - this._request({ - op: 'Stat', - method: 'HEAD', - path: path, - user: opts ? opts.user : null, - password: opts ? opts.password : null - }, callback ? (err, xhr, stat) => { - callback(err, stat); - } : null); + this._request( + { + op: 'Stat', + method: 'HEAD', + path: path, + user: opts ? opts.user : null, + password: opts ? opts.password : null + }, + callback + ? (err, xhr, stat) => { + callback(err, stat); + } + : null + ); }, save: function(path, opts, data, callback, rev) { @@ -72,76 +98,142 @@ const StorageWebDav = StorageBase.extend({ password: opts ? opts.password : null }; const that = this; - this._request(_.defaults({ - op: 'Save:stat', method: 'HEAD' - }, saveOpts), (err, xhr, stat) => { - let useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put'; - if (err) { - if (!err.notFound) { - return cb(err); - } else { - that.logger.debug('Save: not found, creating'); - useTmpPath = false; + this._request( + _.defaults( + { + op: 'Save:stat', + method: 'HEAD' + }, + saveOpts + ), + (err, xhr, stat) => { + let useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put'; + if (err) { + if (!err.notFound) { + return cb(err); + } else { + that.logger.debug('Save: not found, creating'); + useTmpPath = false; + } + } else if (stat.rev !== rev) { + that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); + return cb({ revConflict: true }, xhr, stat); } - } else if (stat.rev !== rev) { - that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); - return cb({ revConflict: true }, xhr, stat); - } - if (useTmpPath) { - that._request(_.defaults({ - op: 'Save:put', method: 'PUT', path: tmpPath, data: data, nostat: true - }, saveOpts), (err) => { - if (err) { return cb(err); } - that._request(_.defaults({ - op: 'Save:stat', method: 'HEAD' - }, saveOpts), (err, xhr, stat) => { - if (err) { - that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts)); - return cb(err, xhr, stat); - } - if (stat.rev !== rev) { - that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); - that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts)); - return cb({revConflict: true}, xhr, stat); - } - let movePath = path; - if (movePath.indexOf('://') < 0) { - if (movePath.indexOf('/') === 0) { - movePath = location.protocol + '//' + location.host + movePath; - } else { - movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath); + if (useTmpPath) { + that._request( + _.defaults( + { + op: 'Save:put', + method: 'PUT', + path: tmpPath, + data: data, + nostat: true + }, + saveOpts + ), + err => { + if (err) { + return cb(err); } + that._request( + _.defaults( + { + op: 'Save:stat', + method: 'HEAD' + }, + saveOpts + ), + (err, xhr, stat) => { + if (err) { + that._request( + _.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts) + ); + return cb(err, xhr, stat); + } + if (stat.rev !== rev) { + that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); + that._request( + _.defaults({ op: 'Save:delete', method: 'DELETE', path: tmpPath }, saveOpts) + ); + return cb({ revConflict: true }, xhr, stat); + } + let movePath = path; + if (movePath.indexOf('://') < 0) { + if (movePath.indexOf('/') === 0) { + movePath = location.protocol + '//' + location.host + movePath; + } else { + movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath); + } + } + that._request( + _.defaults( + { + op: 'Save:move', + method: 'MOVE', + path: tmpPath, + nostat: true, + headers: { Destination: movePath, 'Overwrite': 'T' } + }, + saveOpts + ), + err => { + if (err) { + return cb(err); + } + that._request( + _.defaults( + { + op: 'Save:stat', + method: 'HEAD' + }, + saveOpts + ), + (err, xhr, stat) => { + cb(err, xhr, stat); + } + ); + } + ); + } + ); } - that._request(_.defaults({ - op: 'Save:move', method: 'MOVE', path: tmpPath, nostat: true, - headers: {Destination: movePath, 'Overwrite': 'T'} - }, saveOpts), (err) => { - if (err) { return cb(err); } - that._request(_.defaults({ - op: 'Save:stat', method: 'HEAD' - }, saveOpts), (err, xhr, stat) => { - cb(err, xhr, stat); - }); - }); - }); - }); - } else { - that._request(_.defaults({ - op: 'Save:put', method: 'PUT', data: data, nostat: true - }, saveOpts), (err) => { - if (err) { return cb(err); } - that._request(_.defaults({ - op: 'Save:stat', method: 'HEAD' - }, saveOpts), (err, xhr, stat) => { - cb(err, xhr, stat); - }); - }); + ); + } else { + that._request( + _.defaults( + { + op: 'Save:put', + method: 'PUT', + data: data, + nostat: true + }, + saveOpts + ), + err => { + if (err) { + return cb(err); + } + that._request( + _.defaults( + { + op: 'Save:stat', + method: 'HEAD' + }, + saveOpts + ), + (err, xhr, stat) => { + cb(err, xhr, stat); + } + ); + } + ); + } } - }); + ); }, fileOptsToStoreOpts: function(opts, file) { - const result = {user: opts.user, encpass: opts.encpass}; + const result = { user: opts.user, encpass: opts.encpass }; if (opts.password) { const fileId = file.get('uuid'); const password = opts.password; @@ -155,7 +247,7 @@ const StorageWebDav = StorageBase.extend({ }, storeOptsToFileOpts: function(opts, file) { - const result = {user: opts.user, password: opts.password}; + const result = { user: opts.user, password: opts.password }; if (opts.encpass) { const fileId = file.get('uuid'); const encpass = atob(opts.encpass); @@ -192,26 +284,41 @@ const StorageWebDav = StorageBase.extend({ err = 'HTTP status ' + xhr.status; break; } - if (callback) { callback(err, xhr); callback = null; } + if (callback) { + callback(err, xhr); + callback = null; + } return; } const rev = xhr.getResponseHeader('Last-Modified'); if (!rev && !config.nostat) { that.logger.debug(config.op + ' error', config.path, 'no headers', that.logger.ts(ts)); - if (callback) { callback('No Last-Modified header', xhr); callback = null; } + if (callback) { + callback('No Last-Modified header', xhr); + callback = null; + } return; } const completedOpName = config.op + (config.op.charAt(config.op.length - 1) === 'e' ? 'd' : 'ed'); that.logger.debug(completedOpName, config.path, rev, that.logger.ts(ts)); - if (callback) { callback(null, xhr, rev ? { rev: rev } : null); callback = null; } + if (callback) { + callback(null, xhr, rev ? { rev: rev } : null); + callback = null; + } }); xhr.addEventListener('error', () => { that.logger.debug(config.op + ' error', config.path, that.logger.ts(ts)); - if (callback) { callback('network error', xhr); callback = null; } + if (callback) { + callback('network error', xhr); + callback = null; + } }); xhr.addEventListener('abort', () => { that.logger.debug(config.op + ' error', config.path, 'aborted', that.logger.ts(ts)); - if (callback) { callback('aborted', xhr); callback = null; } + if (callback) { + callback('aborted', xhr); + callback = null; + } }); xhr.open(config.method, config.path); xhr.responseType = 'arraybuffer'; @@ -227,7 +334,7 @@ const StorageWebDav = StorageBase.extend({ xhr.setRequestHeader('Cache-Control', 'no-cache'); } if (config.data) { - const blob = new Blob([config.data], {type: 'application/octet-stream'}); + const blob = new Blob([config.data], { type: 'application/octet-stream' }); xhr.send(blob); } else { xhr.send(); diff --git a/app/scripts/util/color.js b/app/scripts/util/color.js index 0f239332..f35d881f 100644 --- a/app/scripts/util/color.js +++ b/app/scripts/util/color.js @@ -52,9 +52,15 @@ Color.prototype.setHsl = function() { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; } h /= 6; } diff --git a/app/scripts/util/comparators.js b/app/scripts/util/comparators.js index f70d4688..a4ca2f27 100644 --- a/app/scripts/util/comparators.js +++ b/app/scripts/util/comparators.js @@ -1,27 +1,38 @@ const LastChar = String.fromCharCode(0xfffd); -const ciCompare = (window.Intl && window.Intl.Collator && !/Edge/.test(navigator.userAgent)) // bugged in Edge: #808 - ? new Intl.Collator(undefined, { sensitivity: 'base' }).compare - : (x, y) => x.toLocaleLowerCase().localeCompare(y.toLocaleLowerCase()); +const ciCompare = + window.Intl && window.Intl.Collator && !/Edge/.test(navigator.userAgent) // bugged in Edge: #808 + ? new Intl.Collator(undefined, { sensitivity: 'base' }).compare + : (x, y) => x.toLocaleLowerCase().localeCompare(y.toLocaleLowerCase()); const Comparators = { stringComparator: function(field, asc) { if (asc) { - return function (x, y) { return ciCompare(x[field] || LastChar, y[field] || LastChar); }; + return function(x, y) { + return ciCompare(x[field] || LastChar, y[field] || LastChar); + }; } else { - return function (x, y) { return ciCompare(y[field], x[field]); }; + return function(x, y) { + return ciCompare(y[field], x[field]); + }; } }, rankComparator: function() { - return function (x, y) { return y.getRank(this.filter.textLower) - x.getRank(this.filter.textLower); }; + return function(x, y) { + return y.getRank(this.filter.textLower) - x.getRank(this.filter.textLower); + }; }, dateComparator: function(field, asc) { if (asc) { - return function (x, y) { return x[field] - y[field]; }; + return function(x, y) { + return x[field] - y[field]; + }; } else { - return function (x, y) { return y[field] - x[field]; }; + return function(x, y) { + return y[field] - x[field]; + }; } } }; diff --git a/app/scripts/util/feature-detector.js b/app/scripts/util/feature-detector.js index 2d3b2fed..7401794e 100644 --- a/app/scripts/util/feature-detector.js +++ b/app/scripts/util/feature-detector.js @@ -9,7 +9,7 @@ const FeatureDetector = { isWindows: navigator.platform.indexOf('Win') >= 0, isiOS: /iPad|iPhone|iPod/i.test(navigator.userAgent), isMobile: MobileRegex.test(navigator.userAgent) || screen.width < MinDesktopScreenWidth, - isPopup: !!((window.parent !== window.top) || window.opener), + isPopup: !!(window.parent !== window.top || window.opener), isStandalone: !!navigator.standalone, isFrame: window.top !== window, isSelfHosted: !isDesktop && !/^http(s?):\/\/((localhost:8085)|((app|beta)\.keeweb\.info))/.test(location.href), @@ -28,10 +28,18 @@ const FeatureDetector = { return !this.isMac; }, screenshotToClipboardShortcut: function() { - if (this.isiOS) { return 'Sleep+Home'; } - if (this.isMobile) { return ''; } - if (this.isMac) { return 'Command-Shift-Control-4'; } - if (this.isWindows) { return 'Alt+PrintScreen'; } + if (this.isiOS) { + return 'Sleep+Home'; + } + if (this.isMobile) { + return ''; + } + if (this.isMac) { + return 'Command-Shift-Control-4'; + } + if (this.isWindows) { + return 'Alt+PrintScreen'; + } return ''; }, supportsTitleBarStyles: function() { diff --git a/app/scripts/util/format.js b/app/scripts/util/format.js index c9642b97..60410c30 100644 --- a/app/scripts/util/format.js +++ b/app/scripts/util/format.js @@ -18,8 +18,15 @@ const Format = { if (typeof dt === 'number') { dt = new Date(dt); } - return dt ? this.dStr(dt) + ' ' + this.pad(dt.getHours(), 2) + ':' + this.pad(dt.getMinutes(), 2) + - ':' + this.pad(dt.getSeconds(), 2) : ''; + return dt + ? this.dStr(dt) + + ' ' + + this.pad(dt.getHours(), 2) + + ':' + + this.pad(dt.getMinutes(), 2) + + ':' + + this.pad(dt.getSeconds(), 2) + : ''; }, dStr: function(dt) { if (typeof dt === 'number') { @@ -37,8 +44,18 @@ const Format = { if (typeof dt === 'number') { dt = new Date(dt); } - return dt ? dt.getFullYear() + '-' + this.pad(dt.getMonth() + 1, 2) + '-' + this.pad(dt.getDate(), 2) + 'T' + - this.pad(dt.getHours(), 2) + '-' + this.pad(dt.getMinutes(), 2) + '-' + this.pad(dt.getSeconds(), 2) + return dt + ? dt.getFullYear() + + '-' + + this.pad(dt.getMonth() + 1, 2) + + '-' + + this.pad(dt.getDate(), 2) + + 'T' + + this.pad(dt.getHours(), 2) + + '-' + + this.pad(dt.getMinutes(), 2) + + '-' + + this.pad(dt.getSeconds(), 2) : ''; } }; diff --git a/app/scripts/util/id-generator.js b/app/scripts/util/id-generator.js index 8c8650a6..2069eee0 100644 --- a/app/scripts/util/id-generator.js +++ b/app/scripts/util/id-generator.js @@ -4,7 +4,9 @@ const IdGenerator = { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }, s4: function() { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); } }; diff --git a/app/scripts/util/kdbxweb-init.js b/app/scripts/util/kdbxweb-init.js index 32eacf02..00653e75 100644 --- a/app/scripts/util/kdbxweb-init.js +++ b/app/scripts/util/kdbxweb-init.js @@ -38,22 +38,33 @@ const KdbxwebInit = { const GB = 1024 * MB; const WASM_PAGE_SIZE = 64 * 1024; const totalMemory = (2 * GB - 64 * KB) / 1024 / WASM_PAGE_SIZE; - const initialMemory = Math.min(Math.max(Math.ceil(requiredMemory * 1024 / WASM_PAGE_SIZE), 256) + 256, totalMemory); + const initialMemory = Math.min( + Math.max(Math.ceil((requiredMemory * 1024) / WASM_PAGE_SIZE), 256) + 256, + totalMemory + ); const memoryDecl = `var wasmMemory=new WebAssembly.Memory({initial:${initialMemory},maximum:${totalMemory}});`; - const moduleDecl = 'var Module={' + + const moduleDecl = + 'var Module={' + 'wasmJSMethod: "native-wasm",' + - 'wasmBinary: Uint8Array.from(atob("' + wasmBinaryBase64 + '"), c => c.charCodeAt(0)),' + + 'wasmBinary: Uint8Array.from(atob("' + + wasmBinaryBase64 + + '"), c => c.charCodeAt(0)),' + 'print(...args) { postMessage({op:"log",args}) },' + 'printErr(...args) { postMessage({op:"log",args}) },' + - 'postRun:' + this.workerPostRun.toString() + ',' + - 'calcHash:' + this.calcHash.toString() + ',' + + 'postRun:' + + this.workerPostRun.toString() + + ',' + + 'calcHash:' + + this.calcHash.toString() + + ',' + 'wasmMemory:wasmMemory,' + 'buffer:wasmMemory.buffer,' + - 'TOTAL_MEMORY:' + initialMemory * WASM_PAGE_SIZE + + 'TOTAL_MEMORY:' + + initialMemory * WASM_PAGE_SIZE + '}'; const script = argon2LoaderCode.replace(/^var Module.*?}/, memoryDecl + moduleDecl); - const blob = new Blob([script], {type: 'application/javascript'}); + const blob = new Blob([script], { type: 'application/javascript' }); const objectUrl = URL.createObjectURL(blob); const worker = new Worker(objectUrl); const onMessage = e => { @@ -75,7 +86,7 @@ const KdbxwebInit = { worker.terminate(); KdbxwebInit.runtimeModule = null; if (!e.data || e.data.error || !e.data.hash) { - const ex = e.data && e.data.error || 'unexpected error'; + const ex = (e.data && e.data.error) || 'unexpected error'; logger.error('Worker error', ex); reject(ex); } @@ -126,9 +137,21 @@ const KdbxwebInit = { const encodedLen = 512; const encoded = Module.allocate(new Array(encodedLen), 'i8', Module.ALLOC_NORMAL); - const res = Module._argon2_hash(iterations, memory, parallelism, - password, passwordLen, salt, saltLen, - hash, length, encoded, encodedLen, type, version); + const res = Module._argon2_hash( + iterations, + memory, + parallelism, + password, + passwordLen, + salt, + saltLen, + hash, + length, + encoded, + encodedLen, + type, + version + ); if (res) { throw new Error('Argon2 error ' + res); } diff --git a/app/scripts/util/logger.js b/app/scripts/util/logger.js index 2da18417..2797d857 100644 --- a/app/scripts/util/logger.js +++ b/app/scripts/util/logger.js @@ -12,7 +12,7 @@ const MaxLogsToSave = 100; const lastLogs = []; const Logger = function(name, id) { - this.prefix = (name ? name + (id ? ':' + id : '') : 'default'); + this.prefix = name ? name + (id ? ':' + id : '') : 'default'; this.level = Level.All; }; diff --git a/app/scripts/util/otp.js b/app/scripts/util/otp.js index f75acdd2..2ed108e6 100644 --- a/app/scripts/util/otp.js +++ b/app/scripts/util/otp.js @@ -18,7 +18,7 @@ const Otp = function(url, params) { if (params.type === 'hotp' && !params.counter) { throw 'Bad counter: ' + params.counter; } - if (params.period && isNaN(params.period) || params.period < 1) { + if ((params.period && isNaN(params.period)) || params.period < 1) { throw 'Bad period: ' + params.period; } @@ -70,13 +70,21 @@ Otp.prototype.next = function(callback) { Otp.prototype.hmac = function(data, callback) { const subtle = window.crypto.subtle || window.crypto.webkitSubtle; const algo = { name: 'HMAC', hash: { name: this.algorithm.replace('SHA', 'SHA-') } }; - subtle.importKey('raw', this.key, algo, false, ['sign']) + subtle + .importKey('raw', this.key, algo, false, ['sign']) .then(key => { - subtle.sign(algo, key, data) - .then(sig => { callback(sig); }) - .catch(err => { callback(null, err); }); + subtle + .sign(algo, key, data) + .then(sig => { + callback(sig); + }) + .catch(err => { + callback(null, err); + }); }) - .catch(err => { callback(null, err); }); + .catch(err => { + callback(null, err); + }); }; Otp.fromBase32 = function(str) { @@ -132,7 +140,12 @@ Otp.isSecret = function(str) { }; Otp.makeUrl = function(secret, period, digits) { - return 'otpauth://totp/default?secret=' + secret + (period ? '&period=' + period : '') + (digits ? '&digits=' + digits : ''); + return ( + 'otpauth://totp/default?secret=' + + secret + + (period ? '&period=' + period : '') + + (digits ? '&digits=' + digits : '') + ); }; module.exports = Otp; diff --git a/app/scripts/util/password-generator.js b/app/scripts/util/password-generator.js index 78dad8f1..5f4ffdef 100644 --- a/app/scripts/util/password-generator.js +++ b/app/scripts/util/password-generator.js @@ -28,7 +28,9 @@ const PasswordGenerator = { } const ranges = Object.keys(this.charRanges) .filter(r => opts[r]) - .map(function(r) { return this.charRanges[r]; }, this); + .map(function(r) { + return this.charRanges[r]; + }, this); if (opts.include && opts.include.length) { ranges.push(opts.include); } diff --git a/app/scripts/util/phonetic.js b/app/scripts/util/phonetic.js index ade29a45..b4b495ca 100644 --- a/app/scripts/util/phonetic.js +++ b/app/scripts/util/phonetic.js @@ -11,20 +11,52 @@ */ const PHONETIC_PRE = [ // Simple phonetics - 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', - 'qu', 'r', 's', 't', + 'b', + 'c', + 'd', + 'f', + 'g', + 'h', + 'j', + 'k', + 'l', + 'm', + 'n', + 'p', + 'qu', + 'r', + 's', + 't', // Complex phonetics 'bl', - 'ch', 'cl', 'cr', + 'ch', + 'cl', + 'cr', 'dr', - 'fl', 'fr', - 'gl', 'gr', - 'kl', 'kr', - 'ph', 'pr', 'pl', - 'sc', 'sh', 'sl', 'sn', 'sr', 'st', 'str', 'sw', - 'th', 'tr', + 'fl', + 'fr', + 'gl', + 'gr', + 'kl', + 'kr', + 'ph', + 'pr', + 'pl', + 'sc', + 'sh', + 'sl', + 'sn', + 'sr', + 'st', + 'str', + 'sw', + 'th', + 'tr', 'br', - 'v', 'w', 'y', 'z' + 'v', + 'w', + 'y', + 'z' ]; /** @@ -39,9 +71,17 @@ const PHONETIC_PRE_SIMPLE_LENGTH = 16; */ const PHONETIC_MID = [ // Simple phonetics - 'a', 'e', 'i', 'o', 'u', + 'a', + 'e', + 'i', + 'o', + 'u', // Complex phonetics - 'ee', 'ie', 'oo', 'ou', 'ue' + 'ee', + 'ie', + 'oo', + 'ou', + 'ue' ]; /** @@ -56,15 +96,32 @@ const PHONETIC_MID_SIMPLE_LENGTH = 5; */ const PHONETIC_POST = [ // Simple phonetics - 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'y', + 'b', + 'd', + 'f', + 'g', + 'k', + 'l', + 'm', + 'n', + 'p', + 'r', + 's', + 't', + 'y', // Complex phonetics - 'ch', 'ck', + 'ch', + 'ck', 'ln', - 'nk', 'ng', + 'nk', + 'ng', 'rn', - 'sh', 'sk', 'st', + 'sh', + 'sk', + 'st', 'th', - 'x', 'z' + 'x', + 'z' ]; /** @@ -107,17 +164,14 @@ function addSyllable(wordObj) { const first = wordObj.word === ''; const preOnFirst = deriv % 6 > 0; if ((first && preOnFirst) || wordObj.lastSkippedPost || compound) { - wordObj.word += getNextPhonetic(PHONETIC_PRE, - PHONETIC_PRE_SIMPLE_LENGTH, wordObj); + wordObj.word += getNextPhonetic(PHONETIC_PRE, PHONETIC_PRE_SIMPLE_LENGTH, wordObj); wordObj.lastSkippedPre = false; } else { wordObj.lastSkippedPre = true; } - wordObj.word += getNextPhonetic(PHONETIC_MID, PHONETIC_MID_SIMPLE_LENGTH, - wordObj, first && wordObj.lastSkippedPre); + wordObj.word += getNextPhonetic(PHONETIC_MID, PHONETIC_MID_SIMPLE_LENGTH, wordObj, first && wordObj.lastSkippedPre); if (wordObj.lastSkippedPre || compound) { - wordObj.word += getNextPhonetic(PHONETIC_POST, - PHONETIC_POST_SIMPLE_LENGTH, wordObj); + wordObj.word += getNextPhonetic(PHONETIC_POST, PHONETIC_POST_SIMPLE_LENGTH, wordObj); wordObj.lastSkippedPost = false; } else { wordObj.lastSkippedPost = true; @@ -206,7 +260,7 @@ function getNumericHash(data) { data += '-Phonetic'; for (let i = 0, len = data.length; i < len; i++) { const chr = data.charCodeAt(i); - numeric = ((numeric << 5) - numeric) + chr; + numeric = (numeric << 5) - numeric + chr; numeric >>>= 0; } return numeric; diff --git a/app/scripts/util/signature-verifier.js b/app/scripts/util/signature-verifier.js index ed33bfb7..8093354f 100644 --- a/app/scripts/util/signature-verifier.js +++ b/app/scripts/util/signature-verifier.js @@ -9,7 +9,7 @@ const SignatureVerifier = { verify(data, signature, pk) { return new Promise((resolve, reject) => { - const algo = {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}}; + const algo = { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } }; try { if (!pk) { pk = this.getPublicKey(); @@ -18,29 +18,33 @@ const SignatureVerifier = { const subtle = window.crypto.subtle; const keyFormat = 'spki'; pk = kdbxweb.ByteUtils.base64ToBytes(pk); - subtle.importKey( - keyFormat, pk, - algo, - false, ['verify'] - ).then(cryptoKey => { - try { - subtle.verify(algo, cryptoKey, - kdbxweb.ByteUtils.arrayToBuffer(signature), - kdbxweb.ByteUtils.arrayToBuffer(data) - ).then(isValid => { - resolve(isValid); - }).catch(e => { - this.logger.error('Verify error', e); + subtle + .importKey(keyFormat, pk, algo, false, ['verify']) + .then(cryptoKey => { + try { + subtle + .verify( + algo, + cryptoKey, + kdbxweb.ByteUtils.arrayToBuffer(signature), + kdbxweb.ByteUtils.arrayToBuffer(data) + ) + .then(isValid => { + resolve(isValid); + }) + .catch(e => { + this.logger.error('Verify error', e); + reject(); + }); + } catch (e) { + this.logger.error('Signature verification error', e); reject(); - }); - } catch (e) { - this.logger.error('Signature verification error', e); + } + }) + .catch(e => { + this.logger.error('ImportKey error', e); reject(); - } - }).catch(e => { - this.logger.error('ImportKey error', e); - reject(); - }); + }); } catch (e) { this.logger.error('Signature key verification error', e); reject(); @@ -50,9 +54,7 @@ const SignatureVerifier = { getPublicKey() { if (!this.publicKey) { - this.publicKey = publicKey - .match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1] - .replace(/\s+/g, ''); + this.publicKey = publicKey.match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1].replace(/\s+/g, ''); } return this.publicKey; } diff --git a/app/scripts/util/tip.js b/app/scripts/util/tip.js index b41859db..3982a076 100644 --- a/app/scripts/util/tip.js +++ b/app/scripts/util/tip.js @@ -3,13 +3,13 @@ const FeatureDetector = require('./feature-detector'); const Tip = function(el, config) { this.el = el; - this.title = config && config.title || el.attr('title'); - this.placement = config && config.placement || el.attr('tip-placement'); - this.fast = config && config.fast || false; + this.title = (config && config.title) || el.attr('title'); + this.placement = (config && config.placement) || el.attr('tip-placement'); + this.fast = (config && config.fast) || false; this.tipEl = null; this.showTimeout = null; this.hideTimeout = null; - this.force = config && config.force || false; + this.force = (config && config.force) || false; this.hide = this.hide.bind(this); }; @@ -26,7 +26,7 @@ Tip.prototype.init = function() { }; Tip.prototype.show = function() { - if (!Tip.enabled && !this.force || !this.title) { + if ((!Tip.enabled && !this.force) || !this.title) { return; } Backbone.on('page-geometry', this.hide); @@ -37,7 +37,10 @@ Tip.prototype.show = function() { this.hideTimeout = null; } } - const tipEl = this.tipEl = $('
').addClass('tip').appendTo('body').html(this.title); + const tipEl = (this.tipEl = $('
') + .addClass('tip') + .appendTo('body') + .html(this.title)); const rect = this.el[0].getBoundingClientRect(); const tipRect = this.tipEl[0].getBoundingClientRect(); const placement = this.placement || this.getAutoPlacement(rect, tipRect); @@ -45,8 +48,7 @@ Tip.prototype.show = function() { if (this.fast) { tipEl.addClass('tip--fast'); } - let top, - left; + let top, left; const offset = 10; const sideOffset = 10; switch (placement) { @@ -171,8 +173,7 @@ Tip.hideTip = function(el) { Tip.updateTip = function(el, props) { if (el._tip) { el._tip.hide(); - _.extend(el._tip, _.pick(props, - ['title', 'placement', 'fast', 'showTimeout', 'hideTimeout'])); + _.extend(el._tip, _.pick(props, ['title', 'placement', 'fast', 'showTimeout', 'hideTimeout'])); } }; diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 8a18fb00..5c748d86 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -41,7 +41,7 @@ const AppView = Backbone.View.extend({ titlebarStyle: 'default', - initialize: function () { + initialize: function() { this.views = {}; this.views.menu = new MenuView({ model: this.model.menu }); this.views.menuDrag = new DragView('x'); @@ -121,16 +121,20 @@ const AppView = Backbone.View.extend({ // https://github.com/keeweb/keeweb/issues/636#issuecomment-304225634 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5782378/ if (FeatureDetector.needFixClicks) { - const msEdgeScrewer = $('').appendTo(this.$el).focus(); + const msEdgeScrewer = $('') + .appendTo(this.$el) + .focus(); setTimeout(() => msEdgeScrewer.remove(), 0); } }, - render: function () { - this.$el.html(this.template({ - beta: this.model.isBeta, - titlebarStyle: this.titlebarStyle - })); + render: function() { + this.$el.html( + this.template({ + beta: this.model.isBeta, + titlebarStyle: this.titlebarStyle + }) + ); this.panelEl = this.$el.find('.app__panel:first'); this.views.listWrap.setElement(this.$el.find('.app__list-wrap')).render(); this.views.menu.setElement(this.$el.find('.app__menu')).render(); @@ -158,9 +162,13 @@ const AppView = Backbone.View.extend({ this.hideKeyChange(); this.views.open = new OpenView({ model: this.model }); this.views.open.setElement(this.$el.find('.app__body')).render(); - this.views.open.on('close', () => { - Backbone.trigger('closed-open-view'); - }, this); + this.views.open.on( + 'close', + () => { + Backbone.trigger('closed-open-view'); + }, + this + ); this.views.open.on('close', this.showEntries, this); }, @@ -181,8 +189,7 @@ const AppView = Backbone.View.extend({ }, updateApp: function() { - if (UpdateModel.instance.get('updateStatus') === 'ready' && - !Launcher && !this.model.files.hasOpenFiles()) { + if (UpdateModel.instance.get('updateStatus') === 'ready' && !Launcher && !this.model.files.hasOpenFiles()) { window.location.reload(); } }, @@ -324,7 +331,11 @@ const AppView = Backbone.View.extend({ }, beforeUnload: function(e) { - const exitEvent = { preventDefault() { this.prevented = true; } }; + const exitEvent = { + preventDefault() { + this.prevented = true; + } + }; Backbone.trigger('main-window-will-close', exitEvent); if (exitEvent.prevented) { Launcher.preventExit(e); @@ -344,7 +355,11 @@ const AppView = Backbone.View.extend({ if (Launcher) { if (!this.exitAlertShown) { if (this.model.settings.get('autoSave')) { - this.saveAndLock(result => { if (result) { exit(); } }); + this.saveAndLock(result => { + if (result) { + exit(); + } + }); return Launcher.preventExit(e); } this.exitAlertShown = true; @@ -352,13 +367,17 @@ const AppView = Backbone.View.extend({ header: Locale.appUnsavedWarn, body: Locale.appUnsavedWarnBody, buttons: [ - {result: 'save', title: Locale.saveChanges}, - {result: 'exit', title: Locale.discardChanges, error: true}, - {result: '', title: Locale.appDontExitBtn} + { result: 'save', title: Locale.saveChanges }, + { result: 'exit', title: Locale.discardChanges, error: true }, + { result: '', title: Locale.appDontExitBtn } ], success: result => { if (result === 'save') { - this.saveAndLock(result => { if (result) { exit(); } }); + this.saveAndLock(result => { + if (result) { + exit(); + } + }); } else { exit(); } @@ -374,8 +393,13 @@ const AppView = Backbone.View.extend({ return Launcher.preventExit(e); } return Locale.appUnsavedWarnBody; - } else if (Launcher && !Launcher.exitRequested && !Launcher.restartPending && - Launcher.canMinimize() && this.model.settings.get('minimizeOnClose')) { + } else if ( + Launcher && + !Launcher.exitRequested && + !Launcher.restartPending && + Launcher.canMinimize() && + this.model.settings.get('minimizeOnClose') + ) { Launcher.minimizeApp(); return Launcher.preventExit(e); } @@ -391,11 +415,11 @@ const AppView = Backbone.View.extend({ } }, - enterFullScreen: function () { + enterFullScreen: function() { this.$el.addClass('fullscreen'); }, - leaveFullScreen: function () { + leaveFullScreen: function() { this.$el.removeClass('fullscreen'); }, @@ -480,7 +504,7 @@ const AppView = Backbone.View.extend({ } }, - handleAutoSaveTimer: function () { + handleAutoSaveTimer: function() { if (this.model.settings.get('autoSaveInterval') !== 0) { // trigger periodical auto save if (this.autoSaveTimeoutId) { @@ -520,10 +544,14 @@ const AppView = Backbone.View.extend({ body: alertBody + ' ' + errorFiles.join(', ') }); } - if (complete) { complete(true); } + if (complete) { + complete(true); + } } else { that.closeAllFilesAndShowFirst(); - if (complete) { complete(true); } + if (complete) { + complete(true); + } } } } @@ -536,7 +564,11 @@ const AppView = Backbone.View.extend({ fileToShow = this.model.fileInfos.getLast(); } if (fileToShow) { - const fileInfo = this.model.fileInfos.getMatch(fileToShow.get('storage'), fileToShow.get('name'), fileToShow.get('path')); + const fileInfo = this.model.fileInfos.getMatch( + fileToShow.get('storage'), + fileToShow.get('name'), + fileToShow.get('path') + ); if (fileInfo) { this.views.open.showOpenFileInfo(fileInfo); } @@ -598,13 +630,13 @@ const AppView = Backbone.View.extend({ } } else { if (menuItem) { - this.model.menu.select({item: menuItem}); + this.model.menu.select({ item: menuItem }); } } } else { this.showSettings(); if (menuItem) { - this.model.menu.select({item: menuItem}); + this.model.menu.select({ item: menuItem }); } } }, @@ -690,8 +722,12 @@ const AppView = Backbone.View.extend({ showSingleInstanceAlert: function() { this.hideOpenFile(); Alerts.error({ - header: Locale.appTabWarn, body: Locale.appTabWarnBody, - esc: false, enter: false, click: false, buttons: [] + header: Locale.appTabWarn, + body: Locale.appTabWarnBody, + esc: false, + enter: false, + click: false, + buttons: [] }); }, diff --git a/app/scripts/views/auto-type-hint-view.js b/app/scripts/views/auto-type-hint-view.js index 31f1b254..c1f655f2 100644 --- a/app/scripts/views/auto-type-hint-view.js +++ b/app/scripts/views/auto-type-hint-view.js @@ -16,7 +16,7 @@ const AutoTypeHintView = Backbone.View.extend({ this.input.addEventListener('blur', this.inputBlur); }, - render: function () { + render: function() { this.renderTemplate({ cmd: FeatureDetector.isMac ? 'command' : 'ctrl', hasCtrl: FeatureDetector.isMac, @@ -24,7 +24,9 @@ const AutoTypeHintView = Backbone.View.extend({ }); const rect = this.input.getBoundingClientRect(); this.$el.appendTo(document.body).css({ - left: rect.left, top: rect.bottom + 1, width: rect.width + left: rect.left, + top: rect.bottom + 1, + width: rect.width }); const selfRect = this.$el[0].getBoundingClientRect(); const bodyRect = document.body.getBoundingClientRect(); diff --git a/app/scripts/views/auto-type/auto-type-select-view.js b/app/scripts/views/auto-type/auto-type-select-view.js index 2a22eacd..0b1676f4 100644 --- a/app/scripts/views/auto-type/auto-type-select-view.js +++ b/app/scripts/views/auto-type/auto-type-select-view.js @@ -58,8 +58,10 @@ const AutoTypePopupView = Backbone.View.extend({ render() { let topMessage; if (this.model.filter.title || this.model.filter.url) { - topMessage = Locale.autoTypeMsgMatchedByWindow.replace('{}', - this.model.filter.title || this.model.filter.url); + topMessage = Locale.autoTypeMsgMatchedByWindow.replace( + '{}', + this.model.filter.title || this.model.filter.url + ); } else { topMessage = Locale.autoTypeMsgNoWindow; } diff --git a/app/scripts/views/details/details-add-field-view.js b/app/scripts/views/details/details-add-field-view.js index f29bcb8a..bca3346c 100644 --- a/app/scripts/views/details/details-add-field-view.js +++ b/app/scripts/views/details/details-add-field-view.js @@ -8,7 +8,7 @@ const DetailsAddFieldView = Backbone.View.extend({ 'click .details__field-value': 'fieldValueClick' }, - render: function () { + render: function() { this.renderTemplate(); this.labelEl = this.$el.find('.details__field-label'); return this; diff --git a/app/scripts/views/details/details-attachment-view.js b/app/scripts/views/details/details-attachment-view.js index 3212d038..b3a1cfbd 100644 --- a/app/scripts/views/details/details-attachment-view.js +++ b/app/scripts/views/details/details-attachment-view.js @@ -1,30 +1,32 @@ - const Backbone = require('backbone'); const FeatureDetector = require('../../util/feature-detector'); const DetailsAttachmentView = Backbone.View.extend({ template: require('templates/details/details-attachment.hbs'), - events: { - }, + events: {}, render: function(complete) { this.renderTemplate({}, true); const shortcut = this.$el.find('.details__attachment-preview-download-text-shortcut'); shortcut.html(FeatureDetector.actionShortcutSymbol(false)); - const blob = new Blob([this.model.getBinary()], {type: this.model.mimeType}); + const blob = new Blob([this.model.getBinary()], { type: this.model.mimeType }); const dataEl = this.$el.find('.details__attachment-preview-data'); switch ((this.model.mimeType || '').split('/')[0]) { case 'text': const reader = new FileReader(); reader.addEventListener('loadend', () => { - $('
').text(reader.result).appendTo(dataEl);
+                    $('
')
+                        .text(reader.result)
+                        .appendTo(dataEl);
                     complete();
                 });
                 reader.readAsText(blob);
                 return this;
             case 'image':
-                $('').attr('src', URL.createObjectURL(blob)).appendTo(dataEl);
+                $('')
+                    .attr('src', URL.createObjectURL(blob))
+                    .appendTo(dataEl);
                 complete();
                 return this;
         }
diff --git a/app/scripts/views/details/details-auto-type-view.js b/app/scripts/views/details/details-auto-type-view.js
index 43489307..f395d649 100644
--- a/app/scripts/views/details/details-auto-type-view.js
+++ b/app/scripts/views/details/details-auto-type-view.js
@@ -1,4 +1,3 @@
-
 const Backbone = require('backbone');
 const AutoTypeHintView = require('../auto-type-hint-view');
 const Locale = require('../../util/locale');
@@ -57,8 +56,10 @@ const DetailsAutoTypeView = Backbone.View.extend({
 
     seqFocus: function(e) {
         if (!this.views.hint) {
-            this.views.hint = new AutoTypeHintView({input: e.target}).render();
-            this.views.hint.on('remove', () => { delete this.views.hint; });
+            this.views.hint = new AutoTypeHintView({ input: e.target }).render();
+            this.views.hint.on('remove', () => {
+                delete this.views.hint;
+            });
         }
     },
 
diff --git a/app/scripts/views/details/details-history-view.js b/app/scripts/views/details/details-history-view.js
index 40394ac7..b4b2da38 100644
--- a/app/scripts/views/details/details-history-view.js
+++ b/app/scripts/views/details/details-history-view.js
@@ -22,13 +22,55 @@ const DetailsHistoryView = Backbone.View.extend({
     },
 
     formats: [
-        { name: 'ms', round: 1, format: function(d) { return Format.dtStr(d); } },
-        { name: 'sec', round: 1000, format: function(d) { return Format.dtStr(d); } },
-        { name: 'min', round: 1000 * 60, format: function(d) { return Format.dtStr(d).replace(':00 ', ' '); } },
-        { name: 'hour', round: 1000 * 60 * 60, format: function(d) { return Format.dtStr(d).replace(':00', ''); } },
-        { name: 'day', round: 1000 * 60 * 60 * 24, format: function(d) { return Format.dStr(d); } },
-        { name: 'month', round: 1000 * 60 * 60 * 24 * 31, format: function(d) { return Format.dStr(d); } },
-        { name: 'year', round: 1000 * 60 * 60 * 24 * 365, format: function(d) { return d.getFullYear(); } }
+        {
+            name: 'ms',
+            round: 1,
+            format: function(d) {
+                return Format.dtStr(d);
+            }
+        },
+        {
+            name: 'sec',
+            round: 1000,
+            format: function(d) {
+                return Format.dtStr(d);
+            }
+        },
+        {
+            name: 'min',
+            round: 1000 * 60,
+            format: function(d) {
+                return Format.dtStr(d).replace(':00 ', ' ');
+            }
+        },
+        {
+            name: 'hour',
+            round: 1000 * 60 * 60,
+            format: function(d) {
+                return Format.dtStr(d).replace(':00', '');
+            }
+        },
+        {
+            name: 'day',
+            round: 1000 * 60 * 60 * 24,
+            format: function(d) {
+                return Format.dStr(d);
+            }
+        },
+        {
+            name: 'month',
+            round: 1000 * 60 * 60 * 24 * 31,
+            format: function(d) {
+                return Format.dStr(d);
+            }
+        },
+        {
+            name: 'year',
+            round: 1000 * 60 * 60 * 24 * 365,
+            format: function(d) {
+                return d.getFullYear();
+            }
+        }
     ],
 
     fieldViews: null,
@@ -45,14 +87,16 @@ const DetailsHistoryView = Backbone.View.extend({
         this.timelineEl = this.$el.find('.details__history-timeline');
         this.bodyEl = this.$el.find('.details__history-body');
         this.timeline.forEach(function(item, ix) {
-            $('').addClass('fa fa-circle details__history-timeline-item')
-                .css('left', (item.pos * 100) + '%')
+            $('')
+                .addClass('fa fa-circle details__history-timeline-item')
+                .css('left', item.pos * 100 + '%')
                 .attr('data-id', ix)
                 .appendTo(this.timelineEl);
         }, this);
         this.labels.forEach(function(label) {
-            $('
').addClass('details__history-timeline-label') - .css('left', (label.pos * 100) + '%') + $('
') + .addClass('details__history-timeline-label') + .css('left', label.pos * 100 + '%') .text(label.text) .appendTo(this.timelineEl); }, this); @@ -78,32 +122,97 @@ const DetailsHistoryView = Backbone.View.extend({ this.activeIx = ix; this.record = this.timeline[ix].rec; this.timelineEl.find('.details__history-timeline-item').removeClass('details__history-timeline-item--active'); - this.timelineEl.find('.details__history-timeline-item[data-id="' + ix + '"]').addClass('details__history-timeline-item--active'); + this.timelineEl + .find('.details__history-timeline-item[data-id="' + ix + '"]') + .addClass('details__history-timeline-item--active'); this.removeFieldViews(); this.bodyEl.html(''); const colorCls = this.record.color ? this.record.color + '-color' : ''; - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Updated', title: Locale.detHistorySaved, - value: Format.dtStr(this.record.updated) + - (this.record.unsaved ? ' (' + Locale.detHistoryCurUnsavedState + ')' : '') + - ((ix === this.history.length - 1 && !this.record.unsaved) ? ' (' + Locale.detHistoryCurState + ')' : '') } })); - this.fieldViews.push(new FieldViewReadOnlyRaw({ model: { name: '$Title', title: Format.capFirst(Locale.title), - value: ' ' + - _.escape(this.record.title) || '(' + Locale.detHistoryNoTitle + ')' } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$UserName', title: Format.capFirst(Locale.user), value: this.record.user } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$Password', title: Format.capFirst(Locale.password), value: this.record.password } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$URL', title: Format.capFirst(Locale.website), value: this.record.url } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$Notes', title: Format.capFirst(Locale.notes), value: this.record.notes } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Tags', title: Format.capFirst(Locale.tags), - value: this.record.tags.join(', ') } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Expires', title: Locale.detExpires, - value: this.record.expires ? Format.dtStr(this.record.expires) : '' } })); - _.forEach(this.record.fields, function(value, field) { - this.fieldViews.push(new FieldViewReadOnly({ model: { name: '$' + field, title: field, value: value } })); - }, this); + this.fieldViews.push( + new FieldViewReadOnly({ model: { name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 } }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Updated', + title: Locale.detHistorySaved, + value: + Format.dtStr(this.record.updated) + + (this.record.unsaved ? ' (' + Locale.detHistoryCurUnsavedState + ')' : '') + + (ix === this.history.length - 1 && !this.record.unsaved + ? ' (' + Locale.detHistoryCurState + ')' + : '') + } + }) + ); + this.fieldViews.push( + new FieldViewReadOnlyRaw({ + model: { + name: '$Title', + title: Format.capFirst(Locale.title), + value: + ' ' + + _.escape(this.record.title) || '(' + Locale.detHistoryNoTitle + ')' + } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { name: '$UserName', title: Format.capFirst(Locale.user), value: this.record.user } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { name: '$Password', title: Format.capFirst(Locale.password), value: this.record.password } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { name: '$URL', title: Format.capFirst(Locale.website), value: this.record.url } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { name: '$Notes', title: Format.capFirst(Locale.notes), value: this.record.notes } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { name: 'Tags', title: Format.capFirst(Locale.tags), value: this.record.tags.join(', ') } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Expires', + title: Locale.detExpires, + value: this.record.expires ? Format.dtStr(this.record.expires) : '' + } + }) + ); + _.forEach( + this.record.fields, + function(value, field) { + this.fieldViews.push( + new FieldViewReadOnly({ model: { name: '$' + field, title: field, value: value } }) + ); + }, + this + ); if (this.record.attachments.length) { - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Attachments', title: Locale.detAttachments, - value: this.record.attachments.map(att => att.title).join(', ') } })); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Attachments', + title: Locale.detAttachments, + value: this.record.attachments.map(att => att.title).join(', ') + } + }) + ); } this.fieldViews.forEach(function(fieldView) { fieldView.setElement(this.bodyEl).render(); @@ -112,12 +221,15 @@ const DetailsHistoryView = Backbone.View.extend({ const buttons = this.$el.find('.details__history-buttons'); buttons.find('.details__history-button-revert').toggle(ix < this.history.length - 1); buttons.find('.details__history-button-delete').toggle(ix < this.history.length - 1); - buttons.find('.details__history-button-discard').toggle(this.record.unsaved && ix === this.history.length - 1 && - this.history.length > 1 || false); + buttons + .find('.details__history-button-discard') + .toggle((this.record.unsaved && ix === this.history.length - 1 && this.history.length > 1) || false); }, timelineItemClick: function(e) { - const id = $(e.target).closest('.details__history-timeline-item').data('id'); + const id = $(e.target) + .closest('.details__history-timeline-item') + .data('id'); this.showRecord(id); }, @@ -142,12 +254,13 @@ const DetailsHistoryView = Backbone.View.extend({ })); const period = lastRec.updated - firstRec.updated; const format = this.getDateFormat(period); - this.labels = this.getLabels(firstRec.updated.getTime(), lastRec.updated.getTime(), format.round) - .map(label => ({ + this.labels = this.getLabels(firstRec.updated.getTime(), lastRec.updated.getTime(), format.round).map( + label => ({ pos: (label - firstRec.updated) / (lastRec.updated - firstRec.updated), val: label, text: format.format(new Date(label)) - })); + }) + ); }, getDateFormat: function(period) { @@ -170,7 +283,7 @@ const DetailsHistoryView = Backbone.View.extend({ labels.push(label); label += round; } - if (labels.length > 1 && (labels[0] - first) / (last - first) < 0.10) { + if (labels.length > 1 && (labels[0] - first) / (last - first) < 0.1) { labels.shift(); } return labels; diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index b416d99e..27276ac7 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -61,7 +61,7 @@ const DetailsView = Backbone.View.extend({ 'contextmenu .details': 'contextMenu' }, - initialize: function () { + initialize: function() { this.fieldViews = []; this.views = {}; this.initScroll(); @@ -100,7 +100,7 @@ const DetailsView = Backbone.View.extend({ this.hideFieldCopyTip(); }, - render: function () { + render: function() { this.removeScroll(); this.removeFieldViews(); this.removeInnerViews(); @@ -140,45 +140,175 @@ const DetailsView = Backbone.View.extend({ const fileNames = this.appModel.files.map(function(file) { return { id: file.id, value: file.get('name'), selected: file === this.model.file }; }, this); - this.fileEditView = new FieldViewSelect({ model: { name: '$File', title: Format.capFirst(Locale.file), - value: function() { return fileNames; } } }); + this.fileEditView = new FieldViewSelect({ + model: { + name: '$File', + title: Format.capFirst(Locale.file), + value: function() { + return fileNames; + } + } + }); this.fieldViews.push(this.fileEditView); } else { - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'File', title: Format.capFirst(Locale.file), - value: function() { return model.fileName; } } })); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'File', + title: Format.capFirst(Locale.file), + value: function() { + return model.fileName; + } + } + }) + ); } - this.userEditView = new FieldViewAutocomplete({ model: { name: '$UserName', title: Format.capFirst(Locale.user), - value: function() { return model.user; }, getCompletions: this.getUserNameCompletions.bind(this) } }); - this.fieldViews.push(this.userEditView); - this.passEditView = new FieldViewText({ model: { name: '$Password', title: Format.capFirst(Locale.password), canGen: true, - value: function() { return model.password; } } }); - this.fieldViews.push(this.passEditView); - this.urlEditView = new FieldViewUrl({ model: { name: '$URL', title: Format.capFirst(Locale.website), - value: function() { return model.url; } } }); - this.fieldViews.push(this.urlEditView); - this.fieldViews.push(new FieldViewText({ model: { name: '$Notes', title: Format.capFirst(Locale.notes), multiline: 'true', - value: function() { return model.notes; } } })); - this.fieldViews.push(new FieldViewTags({ model: { name: 'Tags', title: Format.capFirst(Locale.tags), tags: this.appModel.tags, - value: function() { return model.tags; } } })); - this.fieldViews.push(new FieldViewDate({ model: { name: 'Expires', title: Locale.detExpires, lessThanNow: '(' + Locale.detExpired + ')', - value: function() { return model.expires; } } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Group', title: Locale.detGroup, - value: function() { return model.groupName; }, tip: function() { return model.getGroupPath().join(' / '); } } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Created', title: Locale.detCreated, - value: function() { return Format.dtStr(model.created); } } })); - this.fieldViews.push(new FieldViewReadOnly({ model: { name: 'Updated', title: Locale.detUpdated, - value: function() { return Format.dtStr(model.updated); } } })); - this.fieldViews.push(new FieldViewHistory({ model: { name: 'History', title: Format.capFirst(Locale.history), - value: function() { return { length: model.historyLength, unsaved: model.unsaved }; } } })); - _.forEach(model.fields, function(value, field) { - if (field === 'otp' && this.model.otpGenerator) { - this.fieldViews.push(new FieldViewOtp({ model: { name: '$' + field, title: field, - value: function() { return model.otpGenerator; } } })); - } else { - this.fieldViews.push(new FieldViewCustom({ model: { name: '$' + field, title: field, - value: function() { return model.fields[field]; } } })); + this.userEditView = new FieldViewAutocomplete({ + model: { + name: '$UserName', + title: Format.capFirst(Locale.user), + value: function() { + return model.user; + }, + getCompletions: this.getUserNameCompletions.bind(this) } - }, this); + }); + this.fieldViews.push(this.userEditView); + this.passEditView = new FieldViewText({ + model: { + name: '$Password', + title: Format.capFirst(Locale.password), + canGen: true, + value: function() { + return model.password; + } + } + }); + this.fieldViews.push(this.passEditView); + this.urlEditView = new FieldViewUrl({ + model: { + name: '$URL', + title: Format.capFirst(Locale.website), + value: function() { + return model.url; + } + } + }); + this.fieldViews.push(this.urlEditView); + this.fieldViews.push( + new FieldViewText({ + model: { + name: '$Notes', + title: Format.capFirst(Locale.notes), + multiline: 'true', + value: function() { + return model.notes; + } + } + }) + ); + this.fieldViews.push( + new FieldViewTags({ + model: { + name: 'Tags', + title: Format.capFirst(Locale.tags), + tags: this.appModel.tags, + value: function() { + return model.tags; + } + } + }) + ); + this.fieldViews.push( + new FieldViewDate({ + model: { + name: 'Expires', + title: Locale.detExpires, + lessThanNow: '(' + Locale.detExpired + ')', + value: function() { + return model.expires; + } + } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Group', + title: Locale.detGroup, + value: function() { + return model.groupName; + }, + tip: function() { + return model.getGroupPath().join(' / '); + } + } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Created', + title: Locale.detCreated, + value: function() { + return Format.dtStr(model.created); + } + } + }) + ); + this.fieldViews.push( + new FieldViewReadOnly({ + model: { + name: 'Updated', + title: Locale.detUpdated, + value: function() { + return Format.dtStr(model.updated); + } + } + }) + ); + this.fieldViews.push( + new FieldViewHistory({ + model: { + name: 'History', + title: Format.capFirst(Locale.history), + value: function() { + return { length: model.historyLength, unsaved: model.unsaved }; + } + } + }) + ); + _.forEach( + model.fields, + function(value, field) { + if (field === 'otp' && this.model.otpGenerator) { + this.fieldViews.push( + new FieldViewOtp({ + model: { + name: '$' + field, + title: field, + value: function() { + return model.otpGenerator; + } + } + }) + ); + } else { + this.fieldViews.push( + new FieldViewCustom({ + model: { + name: '$' + field, + title: field, + value: function() { + return model.fields[field]; + } + } + }) + ); + } + }, + this + ); const hideEmptyFields = AppSettingsModel.instance.get('hideEmptyFields'); @@ -218,8 +348,16 @@ const DetailsView = Backbone.View.extend({ } } } - const fieldView = new FieldViewCustom({ model: { name: '$' + newFieldTitle, title: newFieldTitle, newField: newFieldTitle, - value: function() { return ''; } } }); + const fieldView = new FieldViewCustom({ + model: { + name: '$' + newFieldTitle, + title: newFieldTitle, + newField: newFieldTitle, + value: function() { + return ''; + } + } + }); fieldView.on('change', this.fieldChanged.bind(this)); fieldView.setElement(this.$el.find('.details__body-fields')).render(); fieldView.edit(); @@ -240,24 +378,27 @@ const DetailsView = Backbone.View.extend({ if (hideEmptyFields) { this.fieldViews.forEach(fieldView => { if (fieldView.isHidden()) { - moreOptions.push({value: 'add:' + fieldView.model.name, icon: 'pencil', - text: Locale.detMenuAddField.replace('{}', fieldView.model.title)}); + moreOptions.push({ + value: 'add:' + fieldView.model.name, + icon: 'pencil', + text: Locale.detMenuAddField.replace('{}', fieldView.model.title) + }); } }, this); - moreOptions.push({value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField}); - moreOptions.push({value: 'toggle-empty', icon: 'eye', text: Locale.detMenuShowEmpty}); + moreOptions.push({ value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField }); + moreOptions.push({ value: 'toggle-empty', icon: 'eye', text: Locale.detMenuShowEmpty }); } else { - moreOptions.push({value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField}); - moreOptions.push({value: 'toggle-empty', icon: 'eye-slash', text: Locale.detMenuHideEmpty}); + moreOptions.push({ value: 'add-new', icon: 'plus', text: Locale.detMenuAddNewField }); + moreOptions.push({ value: 'toggle-empty', icon: 'eye-slash', text: Locale.detMenuHideEmpty }); } - moreOptions.push({value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp}); + moreOptions.push({ value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp }); if (AutoType.enabled) { - moreOptions.push({value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoTypeSettings}); + moreOptions.push({ value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoTypeSettings }); } - moreOptions.push({value: 'clone', icon: 'clone', text: Locale.detClone}); + moreOptions.push({ value: 'clone', icon: 'clone', text: Locale.detClone }); const rect = this.moreView.labelEl[0].getBoundingClientRect(); dropdownView.render({ - position: {top: rect.bottom, left: rect.left}, + position: { top: rect.bottom, left: rect.left }, options: moreOptions }); this.views.dropdownView = dropdownView; @@ -301,7 +442,9 @@ const DetailsView = Backbone.View.extend({ }, setSelectedColor: function(color) { - this.$el.find('.details__colors-popup > .details__colors-popup-item').removeClass('details__colors-popup-item--active'); + this.$el + .find('.details__colors-popup > .details__colors-popup-item') + .removeClass('details__colors-popup-item--active'); const colorEl = this.$el.find('.details__header-color')[0]; _.forEach(colorEl.classList, cls => { if (cls.indexOf('color') > 0 && cls.lastIndexOf('details', 0) !== 0) { @@ -309,13 +452,17 @@ const DetailsView = Backbone.View.extend({ } }); if (color) { - this.$el.find('.details__colors-popup > .' + color + '-color').addClass('details__colors-popup-item--active'); + this.$el + .find('.details__colors-popup > .' + color + '-color') + .addClass('details__colors-popup-item--active'); colorEl.classList.add(color + '-color'); } }, selectColor: function(e) { - let color = $(e.target).closest('.details__colors-popup-item').data('color'); + let color = $(e.target) + .closest('.details__colors-popup-item') + .data('color'); if (!color) { return; } @@ -336,7 +483,8 @@ const DetailsView = Backbone.View.extend({ el: this.scroller, model: { iconId: this.model.customIconId || this.model.iconId, - url: this.model.url, file: this.model.file + url: this.model.url, + file: this.model.file } }); this.listenTo(subView, 'select', this.iconSelected); @@ -379,7 +527,7 @@ const DetailsView = Backbone.View.extend({ return; } const mimeType = attachment.mimeType || 'application/octet-stream'; - const blob = new Blob([data], {type: mimeType}); + const blob = new Blob([data], { type: mimeType }); FileSaver.saveAs(blob, attachment.title); }, @@ -408,7 +556,9 @@ const DetailsView = Backbone.View.extend({ }, copyKeyPress: function(editView) { - if (this.isHidden()) { return; } + if (this.isHidden()) { + return; + } if (!window.getSelection().toString()) { const fieldValue = editView.value; const fieldText = fieldValue && fieldValue.isProtected ? fieldValue.getText() : fieldValue; @@ -449,7 +599,9 @@ const DetailsView = Backbone.View.extend({ const tip = new Tip(label, { title: Locale.detCopyHint, placement: 'right' }); tip.show(); this.fieldCopyTip = tip; - setTimeout(() => { tip.hide(); }, Timeouts.AutoHideHint); + setTimeout(() => { + tip.hide(); + }, Timeouts.AutoHideHint); }, settingsToggled: function() { @@ -500,8 +652,11 @@ const DetailsView = Backbone.View.extend({ } this.entryUpdated(true); this.fieldViews.forEach(function(fieldView, ix) { - if (fieldView instanceof FieldViewCustom && !fieldView.model.newField && - !this.model.hasField(fieldView.model.title)) { + if ( + fieldView instanceof FieldViewCustom && + !fieldView.model.newField && + !this.model.hasField(fieldView.model.title) + ) { fieldView.remove(); this.fieldViews.splice(ix, 1); } else { @@ -584,13 +739,17 @@ const DetailsView = Backbone.View.extend({ }, addAttachedFiles: function(files) { - _.forEach(files, function(file) { - const reader = new FileReader(); - reader.onload = () => { - this.addAttachment(file.name, reader.result); - }; - reader.readAsArrayBuffer(file); - }, this); + _.forEach( + files, + function(file) { + const reader = new FileReader(); + reader.onload = () => { + this.addAttachment(file.name, reader.result); + }; + reader.readAsArrayBuffer(file); + }, + this + ); }, addAttachment: function(name, data) { @@ -676,7 +835,8 @@ const DetailsView = Backbone.View.extend({ }, focusNextField: function(config) { - let found = false, nextFieldView; + let found = false, + nextFieldView; if (config.field === '$Title' && !config.prev) { found = true; } @@ -793,10 +953,14 @@ const DetailsView = Backbone.View.extend({ } else { this.moreView.remove(); this.moreView = null; - const fieldView = new FieldViewCustom({ model: { - name: '$otp', title: 'otp', newField: 'otp', - value: kdbxweb.ProtectedValue.fromString('') - }}); + const fieldView = new FieldViewCustom({ + model: { + name: '$otp', + title: 'otp', + newField: 'otp', + value: kdbxweb.ProtectedValue.fromString('') + } + }); fieldView.on('change', this.fieldChanged.bind(this)); fieldView.setElement(this.$el.find('.details__body-fields')).render(); fieldView.edit(); diff --git a/app/scripts/views/drag-view.js b/app/scripts/views/drag-view.js index dee4ddc2..7e44bf13 100644 --- a/app/scripts/views/drag-view.js +++ b/app/scripts/views/drag-view.js @@ -5,7 +5,7 @@ const DragView = Backbone.View.extend({ 'mousedown': 'mousedown' }, - initialize: function (coord) { + initialize: function(coord) { this.setCoord(coord); this.mouseDownTime = -1; this.mouseDownCount = 0; @@ -17,7 +17,9 @@ const DragView = Backbone.View.extend({ }, render: function() { - $('
').addClass('drag-handle__inner').appendTo(this.$el); + $('
') + .addClass('drag-handle__inner') + .appendTo(this.$el); }, mousedown: function(e) { @@ -35,7 +37,9 @@ const DragView = Backbone.View.extend({ } this.initialOffset = e[this.offsetProp]; const cursor = this.$el.css('cursor'); - this.dragMask = $('
', {'class': 'drag-mask'}).css('cursor', cursor).appendTo('body'); + this.dragMask = $('
', { 'class': 'drag-mask' }) + .css('cursor', cursor) + .appendTo('body'); this.dragMask.on('mousemove', this.mousemove.bind(this)); this.dragMask.on('mouseup', this.mouseup.bind(this)); this.trigger('dragstart', { offset: this.initialOffset, coord: this.coord }); diff --git a/app/scripts/views/dropdown-view.js b/app/scripts/views/dropdown-view.js index 2fe88ec4..316fe9b2 100644 --- a/app/scripts/views/dropdown-view.js +++ b/app/scripts/views/dropdown-view.js @@ -7,19 +7,19 @@ const DropdownView = Backbone.View.extend({ 'click .dropdown__item': 'itemClick' }, - initialize: function () { + initialize: function() { this.bodyClick = this.bodyClick.bind(this); this.listenTo(Backbone, 'show-context-menu', this.bodyClick); $('body').on('click contextmenu keyup', this.bodyClick); }, - render: function (config) { + render: function(config) { this.options = config.options; this.renderTemplate(config); this.$el.appendTo(document.body); const ownRect = this.$el[0].getBoundingClientRect(); const bodyRect = document.body.getBoundingClientRect(); - let left = config.position.left || (config.position.right - ownRect.right + ownRect.left); + let left = config.position.left || config.position.right - ownRect.right + ownRect.left; let top = config.position.top; if (left + ownRect.width > bodyRect.right) { left = Math.max(0, bodyRect.right - ownRect.width); diff --git a/app/scripts/views/fields/field-view-autocomplete.js b/app/scripts/views/fields/field-view-autocomplete.js index 2b871b0e..cf1a221a 100644 --- a/app/scripts/views/fields/field-view-autocomplete.js +++ b/app/scripts/views/fields/field-view-autocomplete.js @@ -61,7 +61,8 @@ const FieldViewAutocomplete = FieldViewText.extend({ moveAutocomplete: function(next) { const completions = this.model.getCompletions(this.input.val()); if (typeof this.selectedCopmletionIx === 'number') { - this.selectedCopmletionIx = (completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) % completions.length; + this.selectedCopmletionIx = + (completions.length + this.selectedCopmletionIx + (next ? 1 : -1)) % completions.length; } else { this.selectedCopmletionIx = next ? 0 : completions.length - 1; } @@ -70,10 +71,12 @@ const FieldViewAutocomplete = FieldViewText.extend({ updateAutocomplete: function() { const completions = this.model.getCompletions(this.input.val()); - const completionsHtml = completions.map((item, ix) => { - const sel = ix === this.selectedCopmletionIx ? 'details__field-autocomplete-item--selected' : ''; - return '
' + _.escape(item) + '
'; - }).join(''); + const completionsHtml = completions + .map((item, ix) => { + const sel = ix === this.selectedCopmletionIx ? 'details__field-autocomplete-item--selected' : ''; + return '
' + _.escape(item) + '
'; + }) + .join(''); this.autocomplete.html(completionsHtml); this.autocomplete.toggle(!!completionsHtml); }, @@ -85,7 +88,9 @@ const FieldViewAutocomplete = FieldViewText.extend({ this.input.val(selectedItem); this.endEdit(selectedItem); } else { - this.afterPaint(function () { this.input.focus(); }); + this.afterPaint(function() { + this.input.focus(); + }); } } }); diff --git a/app/scripts/views/fields/field-view-custom.js b/app/scripts/views/fields/field-view-custom.js index 2ca21ca6..03c4fec3 100644 --- a/app/scripts/views/fields/field-view-custom.js +++ b/app/scripts/views/fields/field-view-custom.js @@ -22,7 +22,8 @@ const FieldViewCustom = FieldViewText.extend({ this.isProtected = this.value instanceof kdbxweb.ProtectedValue; } this.$el.toggleClass('details__field--protected', this.isProtected); - $('
').addClass('details__field-value-btn details__field-value-btn-protect') + $('
') + .addClass('details__field-value-btn details__field-value-btn-protect') .appendTo(this.valueEl) .mousedown(this.protectBtnClick.bind(this)); let securityTipTitle = Locale.detLockField; @@ -63,8 +64,11 @@ const FieldViewCustom = FieldViewText.extend({ const text = emptyTitle ? '' : this.model.title || ''; this.labelInput = $(''); this.labelEl.html('').append(this.labelInput); - this.labelInput.attr({ autocomplete: 'off', spellcheck: 'false' }) - .val(text).focus()[0].setSelectionRange(text.length, text.length); + this.labelInput + .attr({ autocomplete: 'off', spellcheck: 'false' }) + .val(text) + .focus()[0] + .setSelectionRange(text.length, text.length); this.labelInput.bind({ input: this.fieldLabelInput.bind(this), keydown: this.fieldLabelKeydown.bind(this), @@ -152,7 +156,9 @@ const FieldViewCustom = FieldViewText.extend({ if (this.labelInput) { this.endEditTitle(this.labelInput.val()); } - this.setTimeout(function() { this.input.focus(); }); + this.setTimeout(function() { + this.input.focus(); + }); } }); diff --git a/app/scripts/views/fields/field-view-date.js b/app/scripts/views/fields/field-view-date.js index b191c887..22b45a8a 100644 --- a/app/scripts/views/fields/field-view-date.js +++ b/app/scripts/views/fields/field-view-date.js @@ -44,7 +44,9 @@ const FieldViewDate = FieldViewText.extend({ endEdit: function(newVal, extra) { if (this.picker) { - try { this.picker.destroy(); } catch (e) {} + try { + this.picker.destroy(); + } catch (e) {} this.picker = null; } newVal = new Date(newVal); diff --git a/app/scripts/views/fields/field-view-select.js b/app/scripts/views/fields/field-view-select.js index 9edb7894..421d6f45 100644 --- a/app/scripts/views/fields/field-view-select.js +++ b/app/scripts/views/fields/field-view-select.js @@ -4,13 +4,24 @@ const FieldViewSelect = FieldView.extend({ readonly: true, renderValue: function(value) { - return ''; + return ( + '' + ); }, render: function() { diff --git a/app/scripts/views/fields/field-view-tags.js b/app/scripts/views/fields/field-view-tags.js index 94b4f727..d4d0020e 100644 --- a/app/scripts/views/fields/field-view-tags.js +++ b/app/scripts/views/fields/field-view-tags.js @@ -14,9 +14,14 @@ const FieldViewTags = FieldViewText.extend({ this.model.tags.forEach(tag => { allTags[tag.toLowerCase()] = tag; }); - return _.unique(val.split(/\s*[;,:]\s*/).filter(_.identity).map(tag => { - return allTags[tag.toLowerCase()] || tag; - })); + return _.unique( + val + .split(/\s*[;,:]\s*/) + .filter(_.identity) + .map(tag => { + return allTags[tag.toLowerCase()] || tag; + }) + ); }, endEdit: function(newVal, extra) { @@ -60,9 +65,11 @@ const FieldViewTags = FieldViewText.extend({ setTags: function() { const availableTags = this.getAvailableTags(); - const tagsHtml = availableTags.map(tag => { - return '
' + _.escape(tag) + '
'; - }).join(''); + const tagsHtml = availableTags + .map(tag => { + return '
' + _.escape(tag) + '
'; + }) + .join(''); this.tagsAutocomplete.html(tagsHtml); this.tagsAutocomplete.toggle(!!tagsHtml); }, @@ -88,7 +95,9 @@ const FieldViewTags = FieldViewText.extend({ this.input.focus(); this.setTags(); } - this.afterPaint(function() { this.input.focus(); }); + this.afterPaint(function() { + this.input.focus(); + }); } }); diff --git a/app/scripts/views/fields/field-view-text.js b/app/scripts/views/fields/field-view-text.js index 8a6a015d..b5d80da2 100644 --- a/app/scripts/views/fields/field-view-text.js +++ b/app/scripts/views/fields/field-view-text.js @@ -10,7 +10,8 @@ const Tip = require('../../util/tip'); const FieldViewText = FieldView.extend({ renderValue: function(value) { - return value && value.isProtected ? PasswordGenerator.present(value.textLength) + return value && value.isProtected + ? PasswordGenerator.present(value.textLength) : _.escape(value || '').replace(/\n/g, '
'); }, @@ -24,8 +25,11 @@ const FieldViewText = FieldView.extend({ this.$el.toggleClass('details__field--protected', isProtected); this.input = $(document.createElement(this.model.multiline ? 'textarea' : 'input')); this.valueEl.html('').append(this.input); - this.input.attr({ autocomplete: 'off', spellcheck: 'false' }) - .val(text).focus()[0].setSelectionRange(text.length, text.length); + this.input + .attr({ autocomplete: 'off', spellcheck: 'false' }) + .val(text) + .focus()[0] + .setSelectionRange(text.length, text.length); this.input.bind({ input: this.fieldValueInput.bind(this), keydown: this.fieldValueKeydown.bind(this), @@ -42,7 +46,9 @@ const FieldViewText = FieldView.extend({ this.createMobileControls(); } if (this.model.canGen) { - $('
').addClass('details__field-value-btn details__field-value-btn-gen').appendTo(this.valueEl) + $('
') + .addClass('details__field-value-btn details__field-value-btn-gen') + .appendTo(this.valueEl) .click(this.showGeneratorClick.bind(this)) .mousedown(this.showGenerator.bind(this)); } @@ -78,7 +84,9 @@ const FieldViewText = FieldView.extend({ this.hideGenerator(); } else { const fieldRect = this.input[0].getBoundingClientRect(); - this.gen = new GeneratorView({model: {pos: {left: fieldRect.left, top: fieldRect.bottom}, password: this.value}}).render(); + this.gen = new GeneratorView({ + model: { pos: { left: fieldRect.left, top: fieldRect.bottom }, password: this.value } + }).render(); this.gen.once('remove', this.generatorClosed.bind(this)); this.gen.once('result', this.generatorResult.bind(this)); } @@ -231,8 +239,11 @@ const FieldViewText = FieldView.extend({ mobileFieldControlTouchMove(e) { const touch = e.originalEvent.targetTouches[0]; const rect = touch.target.getBoundingClientRect(); - const inside = touch.clientX >= rect.left && touch.clientX <= rect.right && - touch.clientY >= rect.top && touch.clientY <= rect.bottom; + const inside = + touch.clientX >= rect.left && + touch.clientX <= rect.right && + touch.clientY >= rect.top && + touch.clientY <= rect.bottom; if (inside) { this.$el.attr('active-mobile-action', $(e.target).data('action')); } else { diff --git a/app/scripts/views/fields/field-view-url.js b/app/scripts/views/fields/field-view-url.js index 447e79f9..d373ef2e 100644 --- a/app/scripts/views/fields/field-view-url.js +++ b/app/scripts/views/fields/field-view-url.js @@ -4,7 +4,13 @@ const FieldViewUrl = FieldViewText.extend({ displayUrlRegex: /^http:\/\//i, renderValue: function(value) { - return value ? '' + _.escape(this.displayUrl(value)) + '' : ''; + return value + ? '' + + _.escape(this.displayUrl(value)) + + '' + : ''; }, fixUrl: function(url) { diff --git a/app/scripts/views/fields/field-view.js b/app/scripts/views/fields/field-view.js index 922b5611..72046998 100644 --- a/app/scripts/views/fields/field-view.js +++ b/app/scripts/views/fields/field-view.js @@ -13,8 +13,13 @@ const FieldView = Backbone.View.extend({ render: function() { this.value = typeof this.model.value === 'function' ? this.model.value() : this.model.value; - this.renderTemplate({ editable: !this.readonly, multiline: this.model.multiline, title: this.model.title, - canEditTitle: this.model.newField, protect: this.value && this.value.isProtected }); + this.renderTemplate({ + editable: !this.readonly, + multiline: this.model.multiline, + title: this.model.title, + canEditTitle: this.model.newField, + protect: this.value && this.value.isProtected + }); this.valueEl = this.$el.find('.details__field-value'); this.valueEl.html(this.renderValue(this.value)); this.labelEl = this.$el.find('.details__field-label'); @@ -38,7 +43,10 @@ const FieldView = Backbone.View.extend({ update: function() { if (typeof this.model.value === 'function') { const newVal = this.model.value(); - if (!_.isEqual(newVal, this.value) || (this.value && newVal && this.value.toString() !== newVal.toString())) { + if ( + !_.isEqual(newVal, this.value) || + (this.value && newVal && this.value.toString() !== newVal.toString()) + ) { this.render(); } } @@ -121,7 +129,9 @@ const FieldView = Backbone.View.extend({ return; } this.editing = false; - setTimeout(() => { this.preventCopy = false; }, 300); + setTimeout(() => { + this.preventCopy = false; + }, 300); let textEqual; if (this.value && this.value.isProtected) { textEqual = this.value.equals(newVal); diff --git a/app/scripts/views/footer-view.js b/app/scripts/views/footer-view.js index 6e025ad1..aaaff15f 100644 --- a/app/scripts/views/footer-view.js +++ b/app/scripts/views/footer-view.js @@ -16,7 +16,7 @@ const FooterView = Backbone.View.extend({ 'click .footer__btn-lock': 'lockWorkspace' }, - initialize: function () { + initialize: function() { this.views = {}; KeyHandler.onKey(Keys.DOM_VK_L, this.lockWorkspace, this, KeyHandler.SHORTCUT_ACTION, false, true); @@ -31,11 +31,14 @@ const FooterView = Backbone.View.extend({ this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render); }, - render: function () { - this.renderTemplate({ - files: this.model.files, - updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0 - }, { plain: true }); + render: function() { + this.renderTemplate( + { + files: this.model.files, + updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0 + }, + { plain: true } + ); return this; }, @@ -65,12 +68,16 @@ const FooterView = Backbone.View.extend({ const right = bodyRect.right - rect.right; const bottom = bodyRect.bottom - rect.top; const generator = new GeneratorView({ model: { copy: true, pos: { right: right, bottom: bottom } } }).render(); - generator.once('remove', () => { delete this.views.gen; }); + generator.once('remove', () => { + delete this.views.gen; + }); this.views.gen = generator; }, showFile: function(e) { - const fileId = $(e.target).closest('.footer__db-item').data('file-id'); + const fileId = $(e.target) + .closest('.footer__db-item') + .data('file-id'); if (fileId) { Backbone.trigger('show-file', { fileId: fileId }); } diff --git a/app/scripts/views/generator-presets-view.js b/app/scripts/views/generator-presets-view.js index ef9c1eed..a64c0175 100644 --- a/app/scripts/views/generator-presets-view.js +++ b/app/scripts/views/generator-presets-view.js @@ -33,11 +33,14 @@ const GeneratorPresetsView = Backbone.View.extend({ if (!this.selected || !this.presets.some(p => p.name === this.selected)) { this.selected = (this.presets.filter(p => p.default)[0] || this.presets[0]).name; } - this.renderTemplate({ - presets: this.presets, - selected: this.getPreset(this.selected), - ranges: this.getSelectedRanges() - }, true); + this.renderTemplate( + { + presets: this.presets, + selected: this.getPreset(this.selected), + ranges: this.getSelectedRanges() + }, + true + ); this.createScroll({ root: this.$el.find('.gen-ps')[0], scroller: this.$el.find('.scroller')[0], @@ -97,10 +100,15 @@ const GeneratorPresetsView = Backbone.View.extend({ } const selected = this.getPreset(this.selected); const preset = { - name, title, + name, + title, length: selected.length, - upper: selected.upper, lower: selected.lower, digits: selected.digits, - special: selected.special, brackets: selected.brackets, ambiguous: selected.ambiguous, + upper: selected.upper, + lower: selected.lower, + digits: selected.digits, + special: selected.special, + brackets: selected.brackets, + ambiguous: selected.ambiguous, include: selected.include }; GeneratorPresets.add(preset); diff --git a/app/scripts/views/generator-view.js b/app/scripts/views/generator-view.js index 31ff3c34..4d4ac6db 100644 --- a/app/scripts/views/generator-view.js +++ b/app/scripts/views/generator-view.js @@ -29,7 +29,7 @@ const GeneratorView = Backbone.View.extend({ presets: null, preset: null, - initialize: function () { + initialize: function() { this.createPresets(); const preset = this.preset; this.gen = _.clone(_.find(this.presets, pr => pr.name === preset)); @@ -40,7 +40,7 @@ const GeneratorView = Backbone.View.extend({ render: function() { const canCopy = document.queryCommandSupported('copy'); - const btnTitle = this.model.copy ? canCopy ? Locale.alertCopy : Locale.alertClose : Locale.alertOk; + const btnTitle = this.model.copy ? (canCopy ? Locale.alertCopy : Locale.alertClose) : Locale.alertOk; this.renderTemplate({ btnTitle: btnTitle, showToggleButton: this.model.copy, @@ -112,8 +112,10 @@ const GeneratorView = Backbone.View.extend({ }, optionChanged: function(option) { - if (this.preset === 'Custom' || - this.preset === 'Pronounceable' && ['length', 'lower', 'upper'].indexOf(option) >= 0) { + if ( + this.preset === 'Custom' || + (this.preset === 'Pronounceable' && ['length', 'lower', 'upper'].indexOf(option) >= 0) + ) { return; } this.preset = this.gen.name = 'Custom'; @@ -132,7 +134,7 @@ const GeneratorView = Backbone.View.extend({ // AppSettingsModel.instance.unset('generatorHidePassword', { silent: true }); AppSettingsModel.instance.set('generatorHidePassword', this.hide); const label = this.$el.find('.gen__check-hide-label'); - Tip.updateTip(label[0], {title: this.hide ? Locale.genShowPass : Locale.genHidePass}); + Tip.updateTip(label[0], { title: this.hide ? Locale.genShowPass : Locale.genHidePass }); this.showPassword(); }, diff --git a/app/scripts/views/grp-view.js b/app/scripts/views/grp-view.js index 0e114483..613e733e 100644 --- a/app/scripts/views/grp-view.js +++ b/app/scripts/views/grp-view.js @@ -25,17 +25,20 @@ const GrpView = Backbone.View.extend({ render: function() { this.removeSubView(); if (this.model) { - this.renderTemplate({ - title: this.model.get('title'), - icon: this.model.get('icon') || 'folder', - customIcon: this.model.get('customIcon'), - enableSearching: this.model.getEffectiveEnableSearching(), - readonly: this.model.get('top'), - canAutoType: AutoType.enabled, - autoTypeSeq: this.model.get('autoTypeSeq'), - autoTypeEnabled: this.model.getEffectiveEnableAutoType(), - defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq() - }, true); + this.renderTemplate( + { + title: this.model.get('title'), + icon: this.model.get('icon') || 'folder', + customIcon: this.model.get('customIcon'), + enableSearching: this.model.getEffectiveEnableSearching(), + readonly: this.model.get('top'), + canAutoType: AutoType.enabled, + autoTypeSeq: this.model.get('autoTypeSeq'), + autoTypeEnabled: this.model.getEffectiveEnableAutoType(), + defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq() + }, + true + ); if (!this.model.get('title')) { this.$el.find('#grp__field-title').focus(); } @@ -88,8 +91,10 @@ const GrpView = Backbone.View.extend({ focusAutoTypeSeq: function(e) { if (!this.views.hint) { - this.views.hint = new AutoTypeHintView({input: e.target}).render(); - this.views.hint.on('remove', () => { delete this.views.hint; }); + this.views.hint = new AutoTypeHintView({ input: e.target }).render(); + this.views.hint.on('remove', () => { + delete this.views.hint; + }); } }, diff --git a/app/scripts/views/icon-select-view.js b/app/scripts/views/icon-select-view.js index 87b9f8df..8dc3b018 100644 --- a/app/scripts/views/icon-select-view.js +++ b/app/scripts/views/icon-select-view.js @@ -22,12 +22,15 @@ const IconSelectView = Backbone.View.extend({ }, render: function() { - this.renderTemplate({ - sel: this.model.iconId, - icons: IconMap, - canDownloadFavicon: !!this.model.url, - customIcons: this.model.file.getCustomIcons() - }, true); + this.renderTemplate( + { + sel: this.model.iconId, + icons: IconMap, + canDownloadFavicon: !!this.model.url, + customIcons: this.model.file.getCustomIcons() + }, + true + ); return this; }, @@ -63,13 +66,17 @@ const IconSelectView = Backbone.View.extend({ this.setSpecialImage(img, 'download'); this.$el.find('.icon-select__icon-download img').remove(); this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin'); - this.$el.find('.icon-select__icon-download').addClass('icon-select__icon--custom-selected').append(img); + this.$el + .find('.icon-select__icon-download') + .addClass('icon-select__icon--custom-selected') + .append(img); this.downloadingFavicon = false; }; img.onerror = e => { logger.error('Favicon download error: ' + url, e); this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin'); - this.$el.find('.icon-select__icon-download') + this.$el + .find('.icon-select__icon-download') .removeClass('icon-select__icon--custom-selected') .addClass('icon-select__icon--download-error'); this.downloadingFavicon = false; @@ -103,7 +110,10 @@ const IconSelectView = Backbone.View.extend({ img.onload = () => { this.setSpecialImage(img, 'select'); this.$el.find('.icon-select__icon-select img').remove(); - this.$el.find('.icon-select__icon-select').addClass('icon-select__icon--custom-selected').append(img); + this.$el + .find('.icon-select__icon-select') + .addClass('icon-select__icon--custom-selected') + .append(img); }; img.src = e.target.result; }; diff --git a/app/scripts/views/key-change-view.js b/app/scripts/views/key-change-view.js index 78972a01..d6e6e84c 100644 --- a/app/scripts/views/key-change-view.js +++ b/app/scripts/views/key-change-view.js @@ -65,7 +65,10 @@ const KeyChangeView = Backbone.View.extend({ this.keyFile = null; this.$el.find('.key-change__keyfile-name').html(''); } - this.$el.find('.key-change__file').val(null).click(); + this.$el + .find('.key-change__file') + .val(null) + .click(); this.inputEl.focus(); }, diff --git a/app/scripts/views/list-search-view.js b/app/scripts/views/list-search-view.js index d5091f2e..e9ac1aff 100644 --- a/app/scripts/views/list-search-view.js +++ b/app/scripts/views/list-search-view.js @@ -31,18 +31,58 @@ const ListSearchView = Backbone.View.extend({ advancedSearchEnabled: false, advancedSearch: null, - initialize: function () { + initialize: function() { this.sortOptions = [ - { value: 'title', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ) }, - { value: '-title', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA) }, - { value: 'website', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ) }, - { value: '-website', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA) }, - { value: 'user', icon: 'sort-alpha-asc', loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ) }, - { value: '-user', icon: 'sort-alpha-desc', loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA) }, - { value: 'created', icon: 'sort-numeric-asc', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON) }, - { value: '-created', icon: 'sort-numeric-desc', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) }, - { value: 'updated', icon: 'sort-numeric-asc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) }, - { value: '-updated', icon: 'sort-numeric-desc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) }, + { + value: 'title', + icon: 'sort-alpha-asc', + loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ) + }, + { + value: '-title', + icon: 'sort-alpha-desc', + loc: () => Format.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA) + }, + { + value: 'website', + icon: 'sort-alpha-asc', + loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ) + }, + { + value: '-website', + icon: 'sort-alpha-desc', + loc: () => Format.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA) + }, + { + value: 'user', + icon: 'sort-alpha-asc', + loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ) + }, + { + value: '-user', + icon: 'sort-alpha-desc', + loc: () => Format.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA) + }, + { + value: 'created', + icon: 'sort-numeric-asc', + loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON) + }, + { + value: '-created', + icon: 'sort-numeric-desc', + loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) + }, + { + value: 'updated', + icon: 'sort-numeric-asc', + loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) + }, + { + value: '-updated', + icon: 'sort-numeric-desc', + loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) + }, { value: '-attachments', icon: 'sort-amount-desc', loc: () => Locale.searchAttachments }, { value: '-rank', icon: 'sort-numeric-desc', loc: () => Locale.searchRank } ]; @@ -52,11 +92,16 @@ const ListSearchView = Backbone.View.extend({ }, this); this.views = {}; this.advancedSearch = { - user: true, other: true, - url: true, protect: false, - notes: true, pass: false, - cs: false, regex: false, - history: false, title: true, + user: true, + other: true, + url: true, + protect: false, + notes: true, + pass: false, + cs: false, + regex: false, + history: false, + title: true, rank: true }; if (this.model.advancedSearch) { @@ -83,9 +128,16 @@ const ListSearchView = Backbone.View.extend({ }, setLocale: function() { - this.sortOptions.forEach(opt => { opt.text = opt.loc(); }); - const entryDesc = FeatureDetector.isMobile ? '' : (' (' + Locale.searchShiftClickOr + ' ' + - FeatureDetector.altShortcutSymbol(true) + 'N)'); + this.sortOptions.forEach(opt => { + opt.text = opt.loc(); + }); + const entryDesc = FeatureDetector.isMobile + ? '' + : ' (' + + Locale.searchShiftClickOr + + ' ' + + FeatureDetector.altShortcutSymbol(true) + + 'N)'; this.createOptions = [ { value: 'entry', icon: 'key', text: Format.capFirst(Locale.entry) + entryDesc }, { value: 'group', icon: 'folder', text: Format.capFirst(Locale.group) } @@ -105,7 +157,7 @@ const ListSearchView = Backbone.View.extend({ this.stopListening(KeyHandler, 'keypress', this.documentKeyPress); }, - render: function () { + render: function() { let searchVal; if (this.inputEl) { searchVal = this.inputEl.val(); diff --git a/app/scripts/views/list-view.js b/app/scripts/views/list-view.js index b6812229..051ff238 100644 --- a/app/scripts/views/list-view.js +++ b/app/scripts/views/list-view.js @@ -40,7 +40,7 @@ const ListView = Backbone.View.extend({ { val: 'fileName', name: 'file', enabled: false } ], - initialize: function () { + initialize: function() { this.initScroll(); this.views = {}; this.views.search = new ListSearchView({ model: this.model }); @@ -64,7 +64,7 @@ const ListView = Backbone.View.extend({ this.items = new EntryCollection(); }, - render: function () { + render: function() { if (!this.itemsEl) { this.$el.html(this.template()); this.itemsEl = this.$el.find('.list__items>.scroller'); @@ -128,7 +128,9 @@ const ListView = Backbone.View.extend({ }, itemClick: function(e) { - const id = $(e.target).closest('.list__item').attr('id'); + const id = $(e.target) + .closest('.list__item') + .attr('id'); const item = this.items.get(id); if (!item.active) { this.selectItem(item); @@ -167,7 +169,9 @@ const ListView = Backbone.View.extend({ Alerts.yesno({ icon: 'sticky-note-o', header: Locale.listAddTemplateHeader, - body: Locale.listAddTemplateBody1.replace('{}', '') + '
' + + body: + Locale.listAddTemplateBody1.replace('{}', '') + + '
' + Locale.listAddTemplateBody2.replace('{}', 'Templates'), buttons: [Alerts.buttons.ok, Alerts.buttons.cancel], success: () => { @@ -248,7 +252,9 @@ const ListView = Backbone.View.extend({ itemDragStart: function(e) { e.stopPropagation(); - const id = $(e.target).closest('.list__item').attr('id'); + const id = $(e.target) + .closest('.list__item') + .attr('id'); e.originalEvent.dataTransfer.setData('text/entry', id); e.originalEvent.dataTransfer.effectAllowed = 'move'; DragDropInfo.dragObject = this.items.get(id); diff --git a/app/scripts/views/list-wrap-view.js b/app/scripts/views/list-wrap-view.js index b8ce4488..11b199e6 100644 --- a/app/scripts/views/list-wrap-view.js +++ b/app/scripts/views/list-wrap-view.js @@ -1,8 +1,7 @@ const Backbone = require('backbone'); const ListWrapView = Backbone.View.extend({ - events: { - }, + events: {}, initialize: function() { this.listenTo(this.model.settings, 'change:tableView', this.setListLayout); diff --git a/app/scripts/views/menu/menu-item-view.js b/app/scripts/views/menu/menu-item-view.js index 301f0772..23a8cff8 100644 --- a/app/scripts/views/menu/menu-item-view.js +++ b/app/scripts/views/menu/menu-item-view.js @@ -27,7 +27,7 @@ const MenuItemView = Backbone.View.extend({ iconEl: null, itemViews: null, - initialize: function () { + initialize: function() { this.itemViews = []; this.listenTo(this.model, 'change:title', this.changeTitle); this.listenTo(this.model, 'change:icon', this.changeIcon); @@ -52,7 +52,7 @@ const MenuItemView = Backbone.View.extend({ this.iconEl = this.$el.find('i.menu__item-icon'); const items = this.model.get('items'); if (items) { - items.forEach(function (item) { + items.forEach(function(item) { if (item.get('visible')) { this.insertItem(item); } @@ -63,7 +63,7 @@ const MenuItemView = Backbone.View.extend({ }, insertItem: function(item) { - this.itemViews.push(new MenuItemView({el: this.$el, model: item}).render()); + this.itemViews.push(new MenuItemView({ el: this.$el, model: item }).render()); }, remove: function() { @@ -84,7 +84,10 @@ const MenuItemView = Backbone.View.extend({ }, changeTitle: function(model, title) { - this.$el.find('.menu__item-title').first().text(title || '(no title)'); + this.$el + .find('.menu__item-title') + .first() + .text(title || '(no title)'); }, changeIcon: function(model, icon) { diff --git a/app/scripts/views/menu/menu-section-view.js b/app/scripts/views/menu/menu-section-view.js index 1d582da0..589b54f3 100644 --- a/app/scripts/views/menu/menu-section-view.js +++ b/app/scripts/views/menu/menu-section-view.js @@ -12,10 +12,12 @@ const MenuSectionView = Backbone.View.extend({ itemViews: null, minHeight: 55, - maxHeight: function() { return this.$el.parent().height() - 116; }, + maxHeight: function() { + return this.$el.parent().height() - 116; + }, autoHeight: 'auto', - initialize: function () { + initialize: function() { this.itemViews = []; this.listenTo(this.model, 'change-items', this.itemsChanged); this.listenTo(this, 'view-resize', this.viewResized); diff --git a/app/scripts/views/menu/menu-view.js b/app/scripts/views/menu/menu-view.js index a5d2e950..56828c5b 100644 --- a/app/scripts/views/menu/menu-view.js +++ b/app/scripts/views/menu/menu-view.js @@ -16,11 +16,21 @@ const MenuView = Backbone.View.extend({ minWidth: 130, maxWidth: 300, - initialize: function () { + initialize: function() { this.listenTo(this.model, 'change:sections', this.menuChanged); this.listenTo(this, 'view-resize', this.viewResized); - KeyHandler.onKey(Keys.DOM_VK_UP, this.selectPreviousSection, this, KeyHandler.SHORTCUT_ACTION + KeyHandler.SHORTCUT_OPT); - KeyHandler.onKey(Keys.DOM_VK_DOWN, this.selectNextSection, this, KeyHandler.SHORTCUT_ACTION + KeyHandler.SHORTCUT_OPT); + KeyHandler.onKey( + Keys.DOM_VK_UP, + this.selectPreviousSection, + this, + KeyHandler.SHORTCUT_ACTION + KeyHandler.SHORTCUT_OPT + ); + KeyHandler.onKey( + Keys.DOM_VK_DOWN, + this.selectNextSection, + this, + KeyHandler.SHORTCUT_ACTION + KeyHandler.SHORTCUT_OPT + ); }, remove: function() { @@ -29,7 +39,7 @@ const MenuView = Backbone.View.extend({ Backbone.View.prototype.remove.apply(this, arguments); }, - render: function () { + render: function() { this.$el.html(this.template()); const sectionsEl = this.$el.find('.menu'); this.model.get('sections').forEach(function(section) { @@ -37,7 +47,9 @@ const MenuView = Backbone.View.extend({ sectionView.render(); if (section.get('drag')) { const dragView = new DragView('y'); - const dragEl = $('
').addClass('menu__drag-section').appendTo(sectionsEl); + const dragEl = $('
') + .addClass('menu__drag-section') + .appendTo(sectionsEl); sectionView.listenDrag(dragView); dragView.setElement(dragEl).render(); this.sectionViews.push(dragView); diff --git a/app/scripts/views/modal-view.js b/app/scripts/views/modal-view.js index f2f6cc09..9c0d0228 100644 --- a/app/scripts/views/modal-view.js +++ b/app/scripts/views/modal-view.js @@ -12,7 +12,7 @@ const ModalView = Backbone.View.extend({ 'click': 'bodyClick' }, - initialize: function () { + initialize: function() { if (typeof this.model.esc === 'string') { KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, true); } @@ -32,7 +32,7 @@ const ModalView = Backbone.View.extend({ Backbone.View.prototype.remove.apply(this, arguments); }, - render: function () { + render: function() { const parent = this.$el; this.setElement($(this.template(this.model))); parent.append(this.$el); diff --git a/app/scripts/views/open-config-view.js b/app/scripts/views/open-config-view.js index b0193af9..90008ef8 100644 --- a/app/scripts/views/open-config-view.js +++ b/app/scripts/views/open-config-view.js @@ -70,7 +70,8 @@ const OpenConfigView = Backbone.View.extend({ }, setError: function(err) { - const errText = err && err.notFound ? Locale.openConfigErrorNotFound : Locale.openConfigError.replace('{}', err); + const errText = + err && err.notFound ? Locale.openConfigErrorNotFound : Locale.openConfigError.replace('{}', err); this.$el.find('.open__config-error').text(errText); } }); diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index eab99fcf..abd1bf89 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -50,7 +50,7 @@ const OpenView = Backbone.View.extend({ busy: false, currentSelectedIndex: -1, - initialize: function () { + initialize: function() { this.views = {}; this.params = { id: null, @@ -73,7 +73,7 @@ const OpenView = Backbone.View.extend({ this.listenTo(Backbone, 'main-window-focus', this.windowFocused.bind(this)); }, - render: function () { + render: function() { if (this.dragTimeout) { clearTimeout(this.dragTimeout); } @@ -86,7 +86,10 @@ const OpenView = Backbone.View.extend({ }); storageProviders.sort((x, y) => (x.uipos || Infinity) - (y.uipos || Infinity)); const showMore = storageProviders.length || this.model.settings.get('canOpenSettings'); - const showLogo = !showMore && !this.model.settings.get('canOpen') && !this.model.settings.get('canCreate') && + const showLogo = + !showMore && + !this.model.settings.get('canOpen') && + !this.model.settings.get('canCreate') && !(this.model.settings.get('canOpenDemo') && !this.model.settings.get('demoOpened')); this.renderTemplate({ lastOpenFiles: this.getLastOpenFiles(), @@ -165,8 +168,8 @@ const OpenView = Backbone.View.extend({ body: Locale.openLocalFileBody, icon: 'file-text', buttons: [ - {result: 'skip', title: Locale.openLocalFileDontShow, error: true}, - {result: 'ok', title: Locale.alertOk} + { result: 'skip', title: Locale.openLocalFileDontShow, error: true }, + { result: 'ok', title: Locale.alertOk } ], click: '', esc: '', @@ -277,7 +280,9 @@ const OpenView = Backbone.View.extend({ displayOpenKeyFile: function() { this.$el.toggleClass('open--key-file', !!this.params.keyFileName); - this.$el.find('.open__settings-key-file-name').text(this.params.keyFileName || this.params.keyFilePath || Locale.openKeyFile); + this.$el + .find('.open__settings-key-file-name') + .text(this.params.keyFileName || this.params.keyFilePath || Locale.openKeyFile); this.focusInput(); }, @@ -344,7 +349,10 @@ const OpenView = Backbone.View.extend({ this.reading = reading; this.params[reading] = null; - const fileInput = this.$el.find('.open__file-ctrl').attr('accept', ext || '').val(null); + const fileInput = this.$el + .find('.open__file-ctrl') + .attr('accept', ext || '') + .val(null); if (Launcher && Launcher.openFileChooser) { Launcher.openFileChooser((err, file) => { @@ -363,17 +371,19 @@ const OpenView = Backbone.View.extend({ if (this.busy) { return; } - const id = $(e.target).closest('.open__last-item').data('id').toString(); + const id = $(e.target) + .closest('.open__last-item') + .data('id') + .toString(); if ($(e.target).is('.open__last-item-icon-del')) { const fileInfo = this.model.fileInfos.get(id); if (!fileInfo.get('storage') || fileInfo.get('modified')) { Alerts.yesno({ header: Locale.openRemoveLastQuestion, - body: fileInfo.get('modified') ? Locale.openRemoveLastQuestionModBody : Locale.openRemoveLastQuestionBody, - buttons: [ - {result: 'yes', title: Locale.alertYes}, - {result: '', title: Locale.alertNo} - ], + body: fileInfo.get('modified') + ? Locale.openRemoveLastQuestionModBody + : Locale.openRemoveLastQuestionBody, + buttons: [{ result: 'yes', title: Locale.alertYes }, { result: '', title: Locale.alertNo }], success: () => { this.removeFile(id); } @@ -471,11 +481,24 @@ const OpenView = Backbone.View.extend({ this.closeConfig(); this.$el.removeClass('open--drag'); const files = e.target.files || e.originalEvent.dataTransfer.files; - const dataFile = _.find(files, file => file.name.split('.').pop().toLowerCase() === 'kdbx'); - const keyFile = _.find(files, file => file.name.split('.').pop().toLowerCase() === 'key'); + const dataFile = _.find( + files, + file => + file.name + .split('.') + .pop() + .toLowerCase() === 'kdbx' + ); + const keyFile = _.find( + files, + file => + file.name + .split('.') + .pop() + .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)); } }, @@ -616,13 +639,15 @@ const OpenView = Backbone.View.extend({ this.$el.toggleClass('open--opening', true); this.inputEl.attr('disabled', 'disabled'); this.busy = true; - this.afterPaint(() => this.model.importFileWithXml(this.params, err => { - if (err) { - this.params.name = ''; - this.params.fileXml = null; - } - this.openDbComplete(err); - })); + this.afterPaint(() => + this.model.importFileWithXml(this.params, err => { + if (err) { + this.params.name = ''; + this.params.fileXml = null; + } + this.openDbComplete(err); + }) + ); }, toggleMore: function() { @@ -641,7 +666,12 @@ const OpenView = Backbone.View.extend({ if (this.busy) { return; } - const storage = Storage[$(e.target).closest('.open__icon').data('storage')]; + const storage = + Storage[ + $(e.target) + .closest('.open__icon') + .data('storage') + ]; if (!storage) { return; } @@ -670,7 +700,8 @@ const OpenView = Backbone.View.extend({ if (err.lastIndexOf('OAuth', 0) !== 0 && !Alerts.alertDisplayed) { Alerts.error({ header: Locale.openError, - body: Locale.openListErrorBody + '' + body: + Locale.openListErrorBody + '' }); } return; @@ -707,7 +738,7 @@ const OpenView = Backbone.View.extend({ if (file.dir) { this.listStorage(storage, { dir: file.path, - prevDir: config && config.dir || '', + prevDir: (config && config.dir) || '', showHiddenFiles: true }); } else { @@ -718,7 +749,7 @@ const OpenView = Backbone.View.extend({ header: Locale.openSelectFile, body: Locale.openSelectFileBody, icon: storage.icon || 'files-o', - buttons: [{result: '', title: Locale.alertCancel}], + buttons: [{ result: '', title: Locale.alertCancel }], esc: '', click: '', view: listView @@ -746,12 +777,15 @@ const OpenView = Backbone.View.extend({ if (this.views.openConfig) { this.views.openConfig.remove(); } - const config = _.extend({ - id: storage.name, - name: Locale[storage.name] || storage.name, - icon: storage.icon, - buttons: true - }, storage.getOpenConfig()); + const config = _.extend( + { + id: storage.name, + name: Locale[storage.name] || storage.name, + icon: storage.icon, + buttons: true + }, + storage.getOpenConfig() + ); 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)); @@ -834,8 +868,7 @@ 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; } diff --git a/app/scripts/views/settings/settings-file-view.js b/app/scripts/views/settings/settings-file-view.js index 886e6b35..76377b05 100644 --- a/app/scripts/views/settings/settings-file-view.js +++ b/app/scripts/views/settings/settings-file-view.js @@ -65,7 +65,11 @@ const SettingsFileView = Backbone.View.extend({ } if (!prv.system && prv.enabled) { storageProviders.push({ - name: prv.name, icon: prv.icon, iconSvg: prv.iconSvg, own: name === fileStorage, backup: prv.backup + name: prv.name, + icon: prv.icon, + iconSvg: prv.iconSvg, + own: name === fileStorage, + backup: prv.backup }); } }); @@ -86,7 +90,7 @@ 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), @@ -114,15 +118,32 @@ const SettingsFileView = Backbone.View.extend({ const sel = this.$el.find('#settings__file-key-file'); sel.html(''); if (keyFileName && keyFileChanged) { - const text = keyFileName !== 'Generated' ? Locale.setFileUseKeyFile + ' ' + keyFileName : Locale.setFileUseGenKeyFile; - $('