pull/8/head
Antelle 2015-10-18 00:49:24 +03:00
commit 3fb3db9365
144 changed files with 7452 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
workspace.xml
.DS_Store
node_modules/
bower_components/
*.log
dist/
tmp/
build/
coverage/

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
keepass-webapp

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

22
.idea/compiler.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JSHint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>

View File

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4" />

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JSHintConfiguration" version="2.6.3" use-config-file="true">
<option asi="false" />
<option bitwise="true" />
<option boss="false" />
<option browser="true" />
<option camelcase="false" />
<option couch="false" />
<option curly="true" />
<option debug="false" />
<option devel="false" />
<option dojo="false" />
<option eqeqeq="true" />
<option eqnull="false" />
<option es3="false" />
<option esnext="false" />
<option evil="false" />
<option expr="false" />
<option forin="true" />
<option freeze="false" />
<option funcscope="false" />
<option gcl="false" />
<option globalstrict="false" />
<option immed="false" />
<option iterator="false" />
<option jquery="false" />
<option lastsemic="false" />
<option latedef="false" />
<option laxbreak="false" />
<option laxcomma="false" />
<option loopfunc="false" />
<option maxerr="50" />
<option mootools="false" />
<option moz="false" />
<option multistr="false" />
<option newcap="false" />
<option noarg="true" />
<option node="false" />
<option noempty="true" />
<option nomen="false" />
<option nonbsp="false" />
<option nonew="true" />
<option nonstandard="false" />
<option notypeof="false" />
<option noyield="false" />
<option onevar="false" />
<option passfail="false" />
<option phantom="false" />
<option plusplus="false" />
<option proto="false" />
<option prototypejs="false" />
<option quotmark="false" />
<option rhino="false" />
<option scripturl="false" />
<option shadow="false" />
<option smarttabs="false" />
<option strict="true" />
<option sub="false" />
<option supernew="false" />
<option trailing="false" />
<option undef="true" />
<option unused="false" />
<option validthis="false" />
<option white="false" />
<option worker="false" />
<option wsh="false" />
<option yui="false" />
</component>
</project>

View File

@ -0,0 +1,15 @@
<component name="libraryTable">
<library name="keepass-webapp node_modules" type="javaScript">
<properties>
<option name="frameworkName" value="node_modules" />
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

16
.idea/misc.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="true" assert-keyword="true" jdk-15="true">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/keepass-webapp.iml" filepath="$PROJECT_DIR$/keepass-webapp.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

94
.jshintrc Normal file
View File

@ -0,0 +1,94 @@
{
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 4, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // Unused variables:
// true : all variables, last function parameter
// "vars" : all variables only
// "strict" : all variables, all function parameters
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : 160, // {int} Max number of characters per line
"varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed.
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
"notypeof" : false, // true: Tolerate invalid typeof operator values
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"browserify" : false, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : false, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jasmine" : true, // Jasmine
"jquery" : false, // jQuery
"mocha" : true, // Mocha
"mootools" : false, // MooTools
"node" : false, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"phantom" : false, // PhantomJS
"prototypejs" : false, // Prototype and Scriptaculous
"qunit" : false, // QUnit
"rhino" : false, // Rhino
"shelljs" : false, // ShellJS
"typed" : false, // Globals for typed array constructions
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {
"require": true,
"module": true,
"$": true,
"_": true
}
}

222
Gruntfile.js Normal file
View File

@ -0,0 +1,222 @@
'use strict';
var fs = require('fs'),
path = require('path');
var StringReplacePlugin = require("string-replace-webpack-plugin");
module.exports = function(grunt) {
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt);
var webpack = require('webpack');
var pkg = require('./package');
function replaceFont(css) {
css.walkAtRules('font-face', function (rule) {
var fontFamily = rule.nodes.filter(function(n) { return n.prop === 'font-family'; })[0];
if (!fontFamily) {
throw 'Bad font rule: ' + rule.toString();
}
var value = fontFamily.value.replace(/["']/g, '');
var fontFiles = {
FontAwesome: 'fontawesome-webfont.woff'
};
var fontFile = fontFiles[value];
if (!fontFile) {
throw 'Unsupported font ' + value + ': ' + rule.toString();
}
var data = fs.readFileSync('tmp/fonts/' + fontFile, 'base64');
var src = 'url(data:application/font-woff;charset=utf-8;base64,{data}) format(\'woff\')'
.replace('{data}', data);
//var src = 'url(\'../fonts/fontawesome-webfont.woff\') format(\'woff\')';
rule.nodes = rule.nodes.filter(function(n) { return n.prop !== 'src'; });
rule.append({ prop: 'src', value: src });
});
}
grunt.initConfig({
'bower-install-simple': {
install: {
}
},
clean: {
dist: ['dist', 'tmp']
},
copy: {
html: {
src: 'app/index.html',
dest: 'tmp/index.html',
nonull: true
},
favicon: {
src: 'app/favicon.png',
dest: 'tmp/favicon.png',
nonull: true
},
fonts: {
src: 'bower_components/font-awesome/fonts/fontawesome-webfont.*',
dest: 'tmp/fonts/',
nonull: true,
expand: true,
flatten: true
}
},
jshint: {
options: {
jshintrc: true
},
all: ['app/scripts/**/*.js']
},
sass: {
options: {
sourceMap: false,
includePaths: ['./bower_components']
},
dist: {
files: {
'tmp/css/main.css': 'app/styles/main.scss'
}
}
},
postcss: {
options: {
processors: [
replaceFont,
require('cssnano')({discardComments: {removeAll: true}})
]
},
dist: {
src: 'tmp/css/main.css',
dest: 'tmp/css/main.css'
}
},
inline: {
app: {
src: 'tmp/index.html',
dest: 'tmp/app.html'
}
},
htmlmin: {
options: {
removeComments: true,
collapseWhitespace: true
},
app: {
files: {
'dist/app.html': 'tmp/app.html'
}
}
},
webpack: {
js: {
entry: {
app: 'app',
vendor: ['zepto', 'jquery', 'underscore', 'backbone', 'kdbxweb', 'baron', 'dropbox', 'pikaday', 'filesaver']
},
output: {
path: 'tmp/js',
filename: 'app.js'
},
stats: {
colors: false,
modules: true,
reasons: true
},
progress: false,
failOnError: true,
resolve: {
root: [path.join(__dirname, 'app/scripts'), path.join(__dirname, 'bower_components')],
alias: {
backbone: 'backbone/backbone-min.js',
underscore: 'underscore/underscore-min.js',
_: 'underscore/underscore-min.js',
zepto: 'zepto/zepto.min.js',
jquery: 'zepto/zepto.min.js',
kdbxweb: 'kdbxweb/dist/kdbxweb.js',
dropbox: 'dropbox/lib/dropbox.min.js',
baron: 'baron/baron.min.js',
pikaday: 'pikaday/pikaday.js',
filesaver: 'FileSaver.js/FileSaver.min.js',
templates: path.join(__dirname, 'app/templates')
}
},
module: {
loaders: [
{ test: /\.html$/, loader: StringReplacePlugin.replace('ejs', { replacements: [{
pattern: /\r?\n\s*/g,
replacement: function() { return '\n'; }
}]})},
{ test: /zepto(\.min)?\.js$/, loader: 'exports?Zepto; delete window.$; delete window.Zepto;' },
{ test: /baron(\.min)?\.js$/, loader: 'exports?baron; delete window.baron;' },
{ test: /pikadat\.js$/, loader: 'uglify' }
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
new webpack.BannerPlugin('keepass-webapp v' + pkg.version + ', (c) 2015 ' + pkg.author +
', opensource.org/licenses/' + pkg.license),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.ProvidePlugin({ _: 'underscore', $: 'jquery' }),
new webpack.IgnorePlugin(/^(moment)$/),
new StringReplacePlugin()
],
node: {
console: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false
}
}
},
uglify: {
options: {
preserveComments: false
},
app: {
files: { 'tmp/js/app.js': ['tmp/js/app.js'] }
},
vendor: {
options: {
mangle: false,
compress: false
},
files: { 'tmp/js/vendor.js': ['tmp/js/vendor.js'] }
}
},
watch: {
options: {
interrupt: true,
debounceDelay: 500
},
scripts: {
files: ['app/scripts/**/*.js', 'app/templates/**/*.html'],
tasks: ['webpack']
},
styles: {
files: 'app/styles/**/*.scss',
tasks: ['sass']
},
indexhtml: {
files: 'app/index.html',
tasks: ['copy:html']
}
}
});
grunt.registerTask('default', [
'bower-install-simple',
'clean',
'jshint',
'copy:html',
'copy:favicon',
'copy:fonts',
'webpack',
'uglify',
'sass',
'postcss',
'inline',
'htmlmin'
]);
};

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# KeePass web app (unofficial)
This webapp can read KeePass databases. It doesn't require any server or additional resources.
# Status
Deep under construction, far from usable product; see TODO.

21
TODO.md Normal file
View File

@ -0,0 +1,21 @@
# MVP
[ ] add/edit groups
[ ] dropbox
[ ] save+xml
[ ] generate
[ ] tags autocomplete
[ ] file settings
# FUTURE
[ ] custom icons
[ ] advanced search
[ ] merge
[ ] move groups/entries
[ ] switch view
[ ] mobile
[ ] help/tips
[ ] secure fields
[ ] app settings
[ ] trash: groups/empty/untrash

BIN
app/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

18
app/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>KeePass Web App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="favicon.png?__inline=true" />
<link rel="stylesheet" href="css/main.css?__inline=true" />
<script src="js/vendor.js?__inline=true"></script>
<script src="js/app.js?__inline=true"></script>
</head>
<body class="th-d">
<noscript>
<h1>KeePass Web App</h1>
<p>This app is written entirely in JavaScript. Please, enable JavaScript to run it.</p>
</noscript>
</body>
</html>

BIN
app/resources/Demo.kdbx Normal file

Binary file not shown.

31
app/scripts/app.js Normal file
View File

@ -0,0 +1,31 @@
'use strict';
var AppModel = require('./models/app-model'),
AppView = require('./views/app-view'),
KeyHandler = require('./util/key-handler'),
Alerts = require('./util/alerts');
$(function() {
require('./util/view');
KeyHandler.init();
if (['https:', 'file:', 'app:'].indexOf(location.protocol) < 0) {
Alerts.error({ header: 'Not Secure!', icon: 'user-secret', esc: false, enter: false, click: false,
body: 'You have loaded this app with insecure connection. ' +
'Someone may be watching you and stealing your passwords. ' +
'We strongly advice you to stop, unless you clearly understand what you\'re doing.' +
'<br/><br/>' +
'Yes, your database is encrypted but no one can guarantee that the app has not been modified on the way to you.',
buttons: [
{ result: '', title: 'I understand the risks, continue', error: true }
],
complete: showApp
});
} else {
showApp();
}
function showApp() {
new AppView({ model: new AppModel() }).render().showOpenFile();
}
});

View File

@ -0,0 +1,64 @@
'use strict';
var Backbone = require('backbone'),
EntryModel = require('../models/entry-model'),
Comparators = require('../util/comparators');
var EntryCollection = Backbone.Collection.extend({
model: EntryModel,
comparator: function() {},
comparators: {
'title': Comparators.stringComparator('title', true),
'-title': Comparators.stringComparator('title', false),
'website': Comparators.stringComparator('url', true),
'-website': Comparators.stringComparator('url', false),
'user': Comparators.stringComparator('user', true),
'-user': Comparators.stringComparator('user', false),
'created': Comparators.dateComparator('created', true),
'-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)); }
},
defaultComparator: 'title',
activeEntry: null,
initialize: function() {
this.comparator = this.comparators[this.defaultComparator];
},
setActive: function(entry) {
if (!(entry instanceof EntryModel)) {
entry = this.get(entry);
}
this.forEach(function(entry) { entry.active = false; });
if (entry) {
entry.active = true;
}
this.activeEntry = entry;
},
getActive: function() {
return this.activeEntry;
},
sortEntries: function(comparator) {
this.comparator = this.comparators[comparator] || this.comparators[this.defaultComparator];
this.sort();
},
attachmentSortVal: function(entry) {
var att = entry.attachments;
var str = att.length ? String.fromCharCode(64 + att.length) : 'Z';
if (att[0]) {
str += att[0].title;
}
return str;
}
});
module.exports = EntryCollection;

View File

@ -0,0 +1,22 @@
'use strict';
var Backbone = require('backbone'),
FileModel = require('../models/file-model');
var FileCollection = Backbone.Collection.extend({
model: FileModel,
hasOpenFiles: function() {
return this.some(function(file) { return file.get('open'); });
},
hasUnsavedFiles: function() {
return this.some(function(file) { return file.get('modified'); });
},
getByName: function(name) {
return this.find(function(file) { return file.get('name') === name; });
}
});
module.exports = FileCollection;

View File

@ -0,0 +1,19 @@
'use strict';
var Backbone = require('backbone'),
GroupModel = require('../models/group-model');
var GroupCollection = Backbone.Collection.extend({
model: GroupModel,
removeByAttr: function(attr, val) {
var items = this.get('items');
items.forEach(function(item) {
if (item[attr] === val) {
items.remove(item);
}
});
}
});
module.exports = GroupCollection;

View File

@ -0,0 +1,10 @@
'use strict';
var Backbone = require('backbone'),
MenuItemModel = require('../../models/menu/menu-item-model');
var MenuItemCollection = Backbone.Collection.extend({
model: MenuItemModel
});
module.exports = MenuItemCollection;

View File

@ -0,0 +1,10 @@
'use strict';
var Backbone = require('backbone'),
MenuOptionModel = require('../../models/menu/menu-option-model');
var MenuOptionCollection = Backbone.Collection.extend({
model: MenuOptionModel
});
module.exports = MenuOptionCollection;

View File

@ -0,0 +1,10 @@
'use strict';
var Backbone = require('backbone'),
MenuSectionModel = require('../../models/menu/menu-section-model');
var MenuSectionCollection = Backbone.Collection.extend({
model: MenuSectionModel
});
module.exports = MenuSectionCollection;

View File

@ -0,0 +1,16 @@
'use strict';
var Colors = {
AllColors: ['yellow', 'green', 'red', 'orange', 'blue', 'violet'],
ColorsValues: {
yellow: 'ff0',
green: '0f0',
red: 'f00',
orange: 'f80',
blue: '00f',
violet: 'f0f'
}
};
module.exports = Colors;

View File

@ -0,0 +1,20 @@
'use strict';
var 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'
];
module.exports = IconMap;

121
app/scripts/const/keys.js Normal file
View File

@ -0,0 +1,121 @@
'use strict';
var Keys = {
DOM_VK_CANCEL: 3,
DOM_VK_HELP: 6,
DOM_VK_BACK_SPACE: 8,
DOM_VK_TAB: 9,
DOM_VK_CLEAR: 12,
DOM_VK_RETURN: 13,
DOM_VK_ENTER: 14,
DOM_VK_SHIFT: 16,
DOM_VK_CONTROL: 17,
DOM_VK_ALT: 18,
DOM_VK_PAUSE: 19,
DOM_VK_CAPS_LOCK: 20,
DOM_VK_ESCAPE: 27,
DOM_VK_SPACE: 32,
DOM_VK_PAGE_UP: 33,
DOM_VK_PAGE_DOWN: 34,
DOM_VK_END: 35,
DOM_VK_HOME: 36,
DOM_VK_LEFT: 37,
DOM_VK_UP: 38,
DOM_VK_RIGHT: 39,
DOM_VK_DOWN: 40,
DOM_VK_PRINTSCREEN: 44,
DOM_VK_INSERT: 45,
DOM_VK_DELETE: 46,
DOM_VK_0: 48,
DOM_VK_1: 49,
DOM_VK_2: 50,
DOM_VK_3: 51,
DOM_VK_4: 52,
DOM_VK_5: 53,
DOM_VK_6: 54,
DOM_VK_7: 55,
DOM_VK_8: 56,
DOM_VK_9: 57,
DOM_VK_SEMICOLON: 59,
DOM_VK_EQUALS: 61,
DOM_VK_A: 65,
DOM_VK_B: 66,
DOM_VK_C: 67,
DOM_VK_D: 68,
DOM_VK_E: 69,
DOM_VK_F: 70,
DOM_VK_G: 71,
DOM_VK_H: 72,
DOM_VK_I: 73,
DOM_VK_J: 74,
DOM_VK_K: 75,
DOM_VK_L: 76,
DOM_VK_M: 77,
DOM_VK_N: 78,
DOM_VK_O: 79,
DOM_VK_P: 80,
DOM_VK_Q: 81,
DOM_VK_R: 82,
DOM_VK_S: 83,
DOM_VK_T: 84,
DOM_VK_U: 85,
DOM_VK_V: 86,
DOM_VK_W: 87,
DOM_VK_X: 88,
DOM_VK_Y: 89,
DOM_VK_Z: 90,
DOM_VK_CONTEXT_MENU: 93,
DOM_VK_NUMPAD0: 96,
DOM_VK_NUMPAD1: 97,
DOM_VK_NUMPAD2: 98,
DOM_VK_NUMPAD3: 99,
DOM_VK_NUMPAD4: 100,
DOM_VK_NUMPAD5: 101,
DOM_VK_NUMPAD6: 102,
DOM_VK_NUMPAD7: 103,
DOM_VK_NUMPAD8: 104,
DOM_VK_NUMPAD9: 105,
DOM_VK_MULTIPLY: 106,
DOM_VK_ADD: 107,
DOM_VK_SEPARATOR: 108,
DOM_VK_SUBTRACT: 109,
DOM_VK_DECIMAL: 110,
DOM_VK_DIVIDE: 111,
DOM_VK_F1: 112,
DOM_VK_F2: 113,
DOM_VK_F3: 114,
DOM_VK_F4: 115,
DOM_VK_F5: 116,
DOM_VK_F6: 117,
DOM_VK_F7: 118,
DOM_VK_F8: 119,
DOM_VK_F9: 120,
DOM_VK_F10: 121,
DOM_VK_F11: 122,
DOM_VK_F12: 123,
DOM_VK_F13: 124,
DOM_VK_F14: 125,
DOM_VK_F15: 126,
DOM_VK_F16: 127,
DOM_VK_F17: 128,
DOM_VK_F18: 129,
DOM_VK_F19: 130,
DOM_VK_F20: 131,
DOM_VK_F21: 132,
DOM_VK_F22: 133,
DOM_VK_F23: 134,
DOM_VK_F24: 135,
DOM_VK_NUM_LOCK: 144,
DOM_VK_SCROLL_LOCK: 145,
DOM_VK_COMMA: 188,
DOM_VK_PERIOD: 190,
DOM_VK_SLASH: 191,
DOM_VK_BACK_QUOTE: 192,
DOM_VK_OPEN_BRACKET: 219,
DOM_VK_BACK_SLASH: 220,
DOM_VK_CLOSE_BRACKET: 221,
DOM_VK_QUOTE: 222,
DOM_VK_META: 224
};
module.exports = Keys;

View File

@ -0,0 +1,161 @@
'use strict';
var Backbone = require('backbone'),
AppSettingsModel = require('./app-settings-model'),
MenuModel = require('./menu/menu-model'),
EntryModel = require('./entry-model'),
FileCollection = require('../collections/file-collection'),
EntryCollection = require('../collections/entry-collection');
var AppModel = Backbone.Model.extend({
defaults: {},
initialize: function() {
this.tags = [];
this.files = new FileCollection();
this.menu = new MenuModel();
this.filter = {};
this.sort = 'title';
this.settings = AppSettingsModel.instance;
this.listenTo(Backbone, 'refresh', this.refresh);
this.listenTo(Backbone, 'set-filter', this.setFilter);
this.listenTo(Backbone, 'add-filter', this.addFilter);
this.listenTo(Backbone, 'set-sort', this.setSort);
},
addFile: function(file) {
this.files.add(file);
file.get('groups').forEach(function(group) { this.menu.groupsSection.addItem(group); }, this);
this._addTags(file.db);
this._tagsChanged();
this.menu.filesSection.addItem({
icon: 'lock',
title: file.get('name'),
page: 'file',
file: file
});
this.refresh();
},
_addTags: function(group) {
var tagsHash = {};
this.tags.forEach(function(tag) {
tagsHash[tag.toLowerCase()] = true;
});
_.forEach(group.entries, function(entry) {
_.forEach(entry.tags, function(tag) {
if (!tagsHash[tag.toLowerCase()]) {
tagsHash[tag.toLowerCase()] = true;
this.tags.push(tag);
}
}, this);
}, this);
_.forEach(group.groups, function(subGroup) {
this._addTags(subGroup);
}, this);
this.tags.sort();
},
_tagsChanged: function() {
if (this.tags.length) {
this.menu.tagsSection.set('scrollable', true);
this.menu.tagsSection.setItems(this.tags.map(function (tag) {
return {title: tag, icon: 'tag', filterKey: 'tag', filterValue: tag};
}));
} else {
this.menu.tagsSection.set('scrollable', false);
this.menu.tagsSection.removeAllItems();
}
},
updateTags: function() {
var oldTags = this.tags.slice();
this.tags.splice(0, this.tags.length);
this.files.forEach(function(file) {
this._addTags(file.db);
}, this);
if (!_.isEqual(oldTags, this.tags)) {
this._tagsChanged();
}
},
closeAllFiles: function() {
this.files.reset();
this.menu.groupsSection.removeAllItems();
this.menu.tagsSection.set('scrollable', false);
this.menu.tagsSection.removeAllItems();
this.menu.filesSection.removeAllItems();
this.tags.splice(0, this.tags.length);
this.setFilter({});
},
setFilter: function(filter) {
this.filter = filter;
var entries = this.getEntries();
Backbone.trigger('filter', { filter: this.filter, sort: this.sort, entries: entries });
Backbone.trigger('select-entry', entries.length ? entries.first() : null);
},
refresh: function() {
this.setFilter(this.filter);
},
addFilter: function(filter) {
this.setFilter(_.extend(this.filter, filter));
},
setSort: function(sort) {
this.sort = sort;
this.setFilter(this.filter);
},
getEntries: function() {
var entries = new EntryCollection();
var filter = this.prepareFilter();
this.files.forEach(function(file) {
file.forEachEntry(filter, function(entry) {
entries.push(entry);
});
});
entries.sortEntries(this.sort);
if (entries.length) {
entries.setActive(entries.first());
}
return entries;
},
prepareFilter: function() {
var filter = _.clone(this.filter);
if (filter.text) {
filter.textLower = filter.text.toLowerCase();
}
if (filter.tag) {
filter.tagLower = filter.tag.toLowerCase();
}
return filter;
},
createNewEntry: function() {
var selGroupId = this.filter.group;
var file, group;
if (selGroupId) {
this.files.forEach(function(f) {
group = f.getGroup(selGroupId);
if (group) {
file = f;
return false;
}
}, this);
}
if (!group) {
file = this.files.first();
group = file.get('groups').first();
}
var entry = EntryModel.newEntry(group, file);
group.addEntry(entry);
return entry;
}
});
module.exports = AppModel;

View File

@ -0,0 +1,32 @@
'use strict';
var Backbone = require('backbone');
var AppSettingsModel = Backbone.Model.extend({
defaults: {
theme: 'd'
},
initialize: function() {
},
load: function() {
if (typeof localStorage !== 'undefined' && localStorage.appSettings) {
try {
var data = JSON.parse(localStorage.appSettings);
this.set(data);
} catch (e) { /* failed to load settings */ }
}
},
save: function() {
if (typeof localStorage !== 'undefined') {
localStorage.appSettings = JSON.stringify(this.attributes);
}
}
});
AppSettingsModel.instance = new AppSettingsModel();
AppSettingsModel.instance.load();
module.exports = AppSettingsModel;

View File

@ -0,0 +1,90 @@
'use strict';
var Backbone = require('backbone');
var AttachmentModel = Backbone.Model.extend({
defaults: {},
initialize: function() {
},
setAttachment: function(att) {
this.title = att.title;
this.data = att.data;
this.ext = this._getExtension(this.title);
this.icon = this._getIcon(this.ext);
this.mimeType = this._getMimeType(this.ext);
},
_getExtension: function(fileName) {
var ext = fileName ? fileName.split('.').pop() : undefined;
return ext ? ext.toLowerCase() : undefined;
},
_getIcon: function(ext) {
switch (ext) {
case 'txt': case 'log': case 'rtf':
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':
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':
return 'file-archive-o';
case 'doc': case 'docx':
return 'file-word-o';
case 'xls': case 'xlsx':
return 'file-excel-o';
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':
return 'file-image-o';
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':
return 'file-audio-o';
}
return 'file-o';
},
_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':
return 'text/plain';
case 'pdf':
return 'application/pdf';
case 'jpeg': case 'jpg': case 'png': case 'gif': case 'bmp': case 'tiff': case 'svg':
return 'image/' + ext;
}
},
getBinary: function() {
var data = this.data;
if (data && data.ref) {
data = data.value;
}
if (data && data.getBinary) {
data = data.getBinary();
}
if (data instanceof ArrayBuffer && data.byteLength) {
data = new Uint8Array(data);
}
if (data instanceof Uint8Array) {
return data;
}
}
});
AttachmentModel.fromAttachment = function(att) {
var model = new AttachmentModel();
model.setAttachment(att);
return model;
};
module.exports = AttachmentModel;

View File

@ -0,0 +1,244 @@
'use strict';
var Backbone = require('backbone'),
AttachmentModel = require('./attachment-model'),
IconMap = require('../const/icon-map'),
Color = require('../util/color'),
kdbxweb = require('kdbxweb');
var EntryModel = Backbone.Model.extend({
defaults: {},
buildInFields: ['Title', 'Password', 'Notes', 'URL', 'UserName'],
initialize: function() {
},
setEntry: function(entry, group, file) {
this.set({ id: entry.uuid.id }, {silent: true});
this.entry = entry;
this.group = group;
this.file = file;
this._fillByEntry();
this._fillInTrash();
},
_fillByEntry: function() {
var entry = this.entry;
this.fileName = this.file.db.meta.name;
this.title = entry.fields.Title || '';
this.password = entry.fields.Password;
this.notes = entry.fields.Notes || '';
this.url = entry.fields.URL || '';
this.user = entry.fields.UserName || '';
this.iconId = entry.icon;
this.icon = this._iconFromId(entry.icon);
this.tags = entry.tags;
this.color = this._colorToModel(entry.bgColor) || this._colorToModel(entry.fgColor);
this.fields = this._fieldsToModel(entry.fields);
this.attachments = this._attachmentsToModel(entry.binaries);
this.created = entry.times.creationTime;
this.updated = entry.times.lastModTime;
this.expires = entry.times.expires ? entry.times.expiryTime : undefined;
this.expired = entry.times.expires && entry.times.expiryTime <= new Date();
this.historyLength = entry.history.length;
this._buildSearchText();
this._buildSearchTags();
this._buildSearchColor();
},
_buildSearchText: function() {
var text = '';
_.forEach(this.entry.fields, function(value) {
if (typeof value === 'string') {
text += value.toLowerCase() + '\n';
}
});
this.entry.tags.forEach(function(tag) {
text += tag.toLowerCase() + '\n';
});
this.attachments.forEach(function(att) {
text += att.title.toLowerCase() + '\n';
});
this.searchText = text;
},
_buildSearchTags: function() {
this.searchTags = this.entry.tags.map(function(tag) { return tag.toLowerCase(); });
},
_buildSearchColor: function() {
this.searchColor = this.color;
},
_iconFromId: function(id) {
return IconMap[id];
},
_colorToModel: function(color) {
return color ? Color.getNearest(color) : null;
},
_fieldsToModel: function(fields) {
return _.omit(fields, this.buildInFields);
},
_attachmentsToModel: function(binaries) {
var att = [];
_.forEach(binaries, function(data, title) {
att.push(AttachmentModel.fromAttachment({ data: data, title: title }));
}, this);
return att;
},
_fillInTrash: function() {
this.deleted = false;
if (this.file.db.meta.recycleBinEnabled) {
var trashGroupId = this.file.db.meta.recycleBinUuid.id;
for (var group = this.group; group; group = group.group) {
if (group.id === trashGroupId) {
this.deleted = true;
break;
}
}
}
},
_entryModified: function() {
if (!this.unsaved) {
this.unsaved = true;
this.entry.pushHistory();
this.file.setModified();
}
this.entry.times.update();
},
matches: function(filter) {
return (!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
(!filter.textLower || this.searchText.indexOf(filter.textLower) >= 0) &&
(!filter.color || filter.color === true && this.searchColor || this.searchColor === filter.color);
},
setColor: function(color) {
this._entryModified();
this.entry.bgColor = Color.getKnownBgColor(color);
this._fillByEntry();
},
setIcon: function(iconId) {
this._entryModified();
this.entry.icon = iconId;
this._fillByEntry();
},
setExpires: function(dt) {
this._entryModified();
this.entry.times.expiryTime = dt instanceof Date ? dt : undefined;
this.entry.times.expires = !!dt;
this._fillByEntry();
},
setTags: function(tags) {
this._entryModified();
this.entry.tags = tags;
this._fillByEntry();
},
setField: function(field, val) {
this._entryModified();
if (val || this.buildInFields.indexOf(field) >= 0) {
this.entry.fields[field] = val;
} else {
delete this.entry.fields[field];
}
this._fillByEntry();
},
hasField: function(field) {
return this.entry.fields.hasOwnProperty(field);
},
addAttachment: function(name, data) {
this._entryModified();
this.entry.binaries[name] = kdbxweb.ProtectedValue.fromBinary(data);
this._fillByEntry();
},
removeAttachment: function(name) {
this._entryModified();
delete this.entry.binaries[name];
this._fillByEntry();
},
getHistory: function() {
var history = this.entry.history.map(function(rec) {
return EntryModel.fromEntry(rec, this.group, this.file);
}, this);
history.push(this);
history.sort(function(x, y) { return x.updated - y.updated; });
return history;
},
deleteHistory: function(historyEntry) {
var ix = this.entry.history.indexOf(historyEntry);
if (ix >= 0) {
this.entry.history.splice(ix, 1);
}
this._fillByEntry();
},
revertToHistoryState: function(historyEntry) {
var ix = this.entry.history.indexOf(historyEntry);
if (ix < 0) {
return;
}
this.entry.pushHistory();
this.unsaved = true;
this.file.setModified();
this.entry.fields = {};
this.entry.binaries = {};
this.entry.copyFrom(historyEntry);
this._entryModified();
this._fillByEntry();
},
discardUnsaved: function() {
if (this.unsaved) {
this.unsaved = false;
var historyEntry = this.entry.history.pop();